Skip to content

DuckDB backend leaks native memory: execute_query never closes its cursor #30

Description

@fgregg

Symptom

warehouse-duckdb (the labordata DuckDB deployment) leaks ~5–13 MB/min of RSS and gets OOM-killed every ~2–3 hours (exit_code=137, oom_killed=true) on its 2 GB machine.

It's a native leak, not Python objects

Instrumented the live process (/proc + an in-process gc probe over 12 min):

  • RSS climbed 301 → 451 MB, but tracked Python object count was flat (49,994 → 51,276, +1,282). So the growth is in native (C++) allocations gc can't see.
  • Threads (10), FDs (74), and duckdb connections (56) were all flat — not a thread/FD/connection-count leak.
  • The Python types that grew monotonically each sample were Statement (~45/min) and the weakrefs (ReferenceType) tracking them — i.e. DuckDB prepared statements, each a thin wrapper pinning a large native allocation (~280 KB × ~540 ≈ the ~150 MB).

Root cause

DuckDBBackend.execute_query does cursor = conn.cursor() per query and never closes it — the finally only cancels the time-limit watchdog:

cursor = conn.cursor()
...
finally:
    if timer is not None:
        timer.cancel()
# cursor never closed

conn is a long-lived pooled read connection. DuckDB tracks child cursors on the parent connection, so each unclosed cursor's prepared statement + native result buffers stay alive for the process lifetime → unbounded native RSS growth. (stream_query's cursor was effectively cleaned because Database.execute_stream closes the whole dedicated connection, but execute_query reuses the pooled connection — so that path is the leak.)

It only manifests under sustained real traffic; synthetic loads (even on the exact prod image, all 14 dbs, 2 GB cap — repeated SQL, unique SQL, streams) plateau and don't reproduce it.

Fix

cursor.close() in execute_query's finally (rows are already materialised into raw before it, so it's safe), and defensively in stream_query's finally (closes on generator exhaustion or client-disconnect GeneratorExit). Smoke-tested: queries / CSV streaming / row pages unaffected.

Fixed in cc1705c6 (duckdb-deploy). Deploying to verify the live RSS slope flattens.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions