Skip to content

fix(query): IDR-129 Query RSS kept after warmup bursts - #46

Merged
BobbyAxerol merged 3 commits into
devfrom
fix/idr129-query-rss
Oct 19, 2026
Merged

BobbyAxerol merged 3 commits into
devfrom
fix/idr129-query-rss

Conversation

@BobbyAxerol

Copy link
Copy Markdown
Owner

IDR-129 (for DL v2.2.5). Production query_kn_1 reached 2045/2048 MiB (restart 12:02:55Z); RSS steps up per warmup burst.

Root cause (isolated 64-warmup bursts on the production image 3e9e4d3a, RSS 472 -> 683 MiB over 12 bursts, malloc in use 155 -> 398 MiB):

  1. router.py sent each warmup body (2-20 MB) as one http.response.body -> one transport.write; asyncio sslproto encrypts it into the connection's ssl.MemoryBIO, which keeps the capacity of its largest write until the SSL object is freed (host check: 8 x 10 MiB, RSS 27 -> 107 MiB with drained BIOs).
  2. Closed connections and refused-item tracebacks are reference cycles; QUERY_GC_THRESHOLDS = (100_000, 50, 100) means generation-1/2 collections practically never run (production: 16 gen0 / 0 gen1 in 32 h). DEBUG_SAVEALL: freeing ~9k garbage objects (39 closed TLS connections, 137 refused-item cycles) released 130 MiB.
  3. glibc kept freed arena memory (malloc_trim returned 150-350 MiB).

Fix:

  • SlicedBodyResponse: warmup and warmup:batch bodies as 256 KiB more_body slices (same bytes, Content-Length, status).
  • assemble_batch releases each refused item's traceback (IDR-114 residual).
  • qdl/runtime/memory_maintenance.py: gc.collect + malloc_trim when RSS >= QDL_QUERY_MEMORY_SOFT_LIMIT_MIB (default 448, 0 disables), large-warmup lane idle >= 3 s, at most every 120 s; one qdl.stall kind=memory_maintenance line. GC thresholds unchanged.
  • scripts/idr129_query_rss_isolated.py: the isolated reproduction harness.

Proof: built image qdl-v2-python:2.2.5-84512ab sha256:69304bd478ee7822625c21e8573354c0a24c2baf1f16cc317d216c227f38ef27, same bursts x 12: RSS 361-445 MiB, malloc in use flat 94-116 MiB; maintenance ran 3x (450->378, 451->374, 454->406 MiB; gc 128-152 ms, trim 8-12 ms). Warmup latency p50 6.6-8.3 s / p95 9.3-10.0 s vs 6.4-8.7 / 9.3-10.2 s before.

Tests: new tests/test_query_idr129_memory.py 12 OK (negative control fails on the unfixed service); 37 affected modules 552 tests, 1 environment failure (image-baked catalog older than dev IDR-127). Journal: DATA_LAYER_UNIFIED_IMPLEMENTATION_PLAN.md, IDR-129 root cause and fix entry.

Root cause (isolated 64-warmup bursts on the production image, RSS 472 -> 683 MiB
over 12 bursts): a warmup body went out as one transport.write, so each TLS
connection's ssl.MemoryBIO kept the capacity of its largest body; closed
connections and refused-item tracebacks are reference cycles that the Query GC
thresholds practically never collect; glibc kept the freed arena memory.

- SlicedBodyResponse: warmup and warmup:batch bodies in 256 KiB more_body
  slices (same bytes, Content-Length, status).
- assemble_batch releases each refused item's traceback (IDR-114 residual).
- memory_maintenance: gc.collect + malloc_trim when RSS >= soft limit
  (QDL_QUERY_MEMORY_SOFT_LIMIT_MIB, default 448) and the large-warmup lane
  is idle, at most every 120 s, one qdl.stall line.
- scripts/idr129_query_rss_isolated.py: the isolated reproduction harness.
- tests/test_query_idr129_memory.py, plan journal entry.
# Conflicts:
#	DATA_LAYER_UNIFIED_IMPLEMENTATION_PLAN.md
@BobbyAxerol
BobbyAxerol merged commit 9b6747f into dev Oct 19, 2026
13 checks passed
@BobbyAxerol
BobbyAxerol deleted the fix/idr129-query-rss branch October 20, 2026 06:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant