Skip to content

Issue: memory leak when using rowTransaction #338

Closed

Description

@andre996

Description:

when using rowTransactions. Every time a transaction is performed, a memory leak occurs. This is due to new row references being created while old ones aren’t being removed, leading to an increase in Shallow Size and Retained Size.

Image

Sample code:

import dash_ag_grid as dag
from dash import Dash, html, Input, Output, State, ctx, no_update, callback

app = Dash(__name__)

rowData = [
    {"id": "Toyota_0", "make": "Toyota", "model": "Celica", "price": 35000},
    {"id": "Ford_0", "make": "Ford", "model": "Mondeo", "price": 32000},
    {"id": "Porsche_0", "make": "Porsche", "model": "Boxster", "price": 72000},
]

columnDefs = [
    {"field": "id", "checkboxSelection": True, "headerCheckboxSelection": True},
    {"field": "make"},
    {"field": "model"},
    {"field": "price", "cellRenderer": "agAnimateShowChangeCellRenderer", },
]

columnDefs_leet = [
    {"field": "id",  'headerName':  "DSFDS","checkboxSelection": True, "headerCheckboxSelection": True},
    {"field": "make", 'headerName': "DFA VXC" ,},
    {"field": "model", 'headerName': "DSS Z",},
    {"field": "price", 'headerName': "SDEAW", "cellRenderer": "agAnimateShowChangeCellRenderer", },
]


app.layout = html.Div(
    [
        html.Button("Update column def", id="btn-header-names-toggle"),
        html.Button("Add Rows", id="btn-client-side-transaction-add"),
        html.Button("Add Rows at index 2", id="btn-client-side-transaction-add-index-2"),
        html.Button("Update selected", id="btn-client-side-transaction-update"),
        html.Button("Remove Selected", id="btn-client-side-transaction-remove"),
        html.Button("Clear", id="btn-client-side-transaction-clear"),
        html.Button("Start Over", id="btn-client-side-transaction-start"),
        dag.AgGrid(
            id="client-side-transaction",
            rowData=rowData,
            columnDefs=columnDefs,
            columnSize="sizeToFit",
            dashGridOptions={"rowSelection": "multiple"},
            getRowId="params.data.id"
        ),
    ],
)

@callback(
    Output("client-side-transaction", "columnDefs"),
    Input("btn-header-names-toggle", "n_clicks"),
     State("client-side-transaction", "columnDefs"),
    prevent_initial_call=True
)
def toggle_cols(n, column_defs):
    if n % 2 == 0:
        return columnDefs
    return columnDefs_leet
    # print('Changing column def')
    # for col in column_defs:
    #     col["valueFormatter"] = {"function": "'* ' + params.value + ' *'"}
    # print(column_defs)
    # return columnDefs

@callback(
    Output("client-side-transaction", "rowData"),
    Input("btn-client-side-transaction-clear", "n_clicks"),
    Input("btn-client-side-transaction-start", "n_clicks"),
)
def update_rowdata(*_):
    return [] if ctx.triggered_id == "btn-client-side-transaction-clear" else rowData


@callback(
    Output("client-side-transaction", "rowTransaction"),
    Input("btn-client-side-transaction-add", "n_clicks"),
    Input("btn-client-side-transaction-add-index-2", "n_clicks"),
    Input("btn-client-side-transaction-update", "n_clicks"),
    Input("btn-client-side-transaction-remove", "n_clicks"),
    State("client-side-transaction", "selectedRows"),
    prevent_initial_call=True,
)
def update_transaction(n1, n2, n3, n4, selection):
    if ctx.triggered_id in ["btn-client-side-transaction-add", "btn-client-side-transaction-add-index-2"]:
        newRows = [
            {
                "id": row["make"] + '_' + str((n1 or 0) + (n2 or 0)),
                "make": row["make"], "model": row["model"], "price": row["price"]
            } for row in rowData
        ]

        return (
            {"add": newRows}
            if ctx.triggered_id == "btn-client-side-transaction-add"
            else {"add": newRows, 'addIndex': 2}
        )

    if selection:
        if ctx.triggered_id == "btn-client-side-transaction-update":
            for row in selection:
                row["price"] = row["price"] + n3
            return {"update": selection}

        if ctx.triggered_id == "btn-client-side-transaction-remove":
            return {"remove": selection}

    # If no rows selected, no grid update
    return no_update


if __name__ == "__main__":
    app.run(debug=True)

Ref: https://dash.plotly.com/dash-ag-grid/client-side#transaction-updates

FYI: There is also an increase of memory use with rowData but it is smaller

Activity

  1. BSd3v commented on Feb 3, 2025

    @BSd3v
    Collaborator

    Hello @andre996,

    Is there another way to measure this rather than having to watch the network tab?

  2. andre996 commented on Feb 20, 2025

    @andre996
    ContributorAuthor

    Hey @BSd3v, after some research, I found memlab to check for memory leaks. It's a tool developed by Facebook that is used to find memory leaks in JS code.

    Image

    Memlab takes a snapshot of the app when it loads after it interacts, and when it returns to the main stage. In this case, the interaction was to add 12 rows in batches of 3. As you can see on the report the memory usage passed from 17MB to 18.4MB, this change was caused by two types of leaks, the first one I think is related to adding the 12 rows to the ag-grid, and the second is caused by the number of time the add 3 rows button was clicked

    Sample app and code used to generate the report
    data_leaking.zip

  3. BSd3v commented on Apr 27, 2026

    @BSd3v
    Collaborator

    @andre996

    I ran memlab on the latest version of AG Grid, and it isnt finding any memory leaks, even in this scenario. Can you confirm?

  4. andre996 commented on Apr 30, 2026

    @andre996
    ContributorAuthor

    @BSd3v yes, it looks like there is no leak

    Image
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugsomething broken

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions