dash-ag-grid icon indicating copy to clipboard operation
dash-ag-grid copied to clipboard

Issue: memory leak when using rowTransaction

Open andre996 opened this issue 1 year ago • 2 comments

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

andre996 avatar Nov 05 '24 21:11 andre996

Hello @andre996,

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

BSd3v avatar Feb 03 '25 16:02 BSd3v

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

andre996 avatar Feb 20 '25 18:02 andre996