Skip to content

memory improvements to changelog and uwsgi reload-on-rss fix - #545

Open
fcollman wants to merge 10 commits into
mainfrom
tabular_changelog_memory
Open

memory improvements to changelog and uwsgi reload-on-rss fix#545
fcollman wants to merge 10 commits into
mainfrom
tabular_changelog_memory

Conversation

@fcollman

@fcollman fcollman commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

This limits the columns read by the tabular_changelog_recent endpoint and adds a uwsgi parameter to reload workers that have large memory usage, which should limit memory leaks.

@fcollman

fcollman commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

I've also added a memory gaurd for the l2graph, and made an option to prelog requests in order to find failed requests for debugging purposes.

@fcollman

Copy link
Copy Markdown
Collaborator Author

i've added a gaurd option on subgraph as well that can be calibrated differently, and switched the code to avoid using reduce and make use of np.concentenate in constructing subgraph return

fcollman and others added 4 commits August 15, 2026 17:38
/subgraph memory scaled with the total edge content of the chunks an object
spans, not with the object. A chunk's edge file holds every object in that
chunk, and get_chunk_edges retained all of it: the compressed blobs, the fully
decompressed buffers, and a concatenated copy all coexisted before anything was
filtered. Measured on minniev7, a ~260-chunk request peaked at 6.28 GiB of a
12 GiB pod limit to return 8.3 MB of edges.

Both consumers discard edges that do not touch the queried object -- the
edges_only path keeps edges with both endpoints in the supervoxel set, and
categorize_edges_v2 drops any edge whose node_ids1 does not remap through
sv_parent_d. So resolve the supervoxels first and pass them down, letting each
chunk be filtered as it is parsed. The arrays from deserialize are np.frombuffer
views into the decompressed chunk, so masking copies out the few edges that
matter and lets the buffer be released instead of pinned until the end.

Edges.filter_touching keeps edges with either endpoint in the set, which is a
superset of both predicates, so filtering early cannot change either result.
in_sorted avoids re-sorting the supervoxel set once per chunk.

get_children moves above the read to supply the set; the edges_only path now
reuses it instead of issuing a second identical read.

The ingest caller passes no supervoxels and is unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…absent

The fallback path already existed for builds without multi-threading support, but
only caught ValueError. zstandard >= 0.23 removed multi_decompress_to_buffer
altogether, so on any newer version _parse_edges raised AttributeError instead of
taking the fallback. The image pins zstandard==0.21.0, which is why this has not
bitten in production, but the fallback should not depend on that pin -- and it
currently blocks running the edge IO tests outside the image.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
get_chunk_edges issued a single cf.get for every chunk in the request, so the
compressed blobs for the whole query were held at once, then handed to
multi_decompress_to_buffer which materialized all of the decompressed buffers at
once as well. Peak scaled with the number of chunks, which is what made a large
bounding box expensive regardless of the size of the object being queried.

Process the files in batches instead, accumulating only the parsed (and, when
supervoxels are given, already filtered) per-chunk results. Each batch's buffers
are released before the next is fetched, so peak tracks batch_size rather than
len(fnames). Batch size is configurable via PCG_EDGES_BATCH_SIZE, default 64.

This composes with the filter commit and depends on it for most of the benefit:
with no supervoxels the parsed arrays are np.frombuffer views that pin their
decompressed buffers, so those cannot be released between batches. Filtering
copies out the survivors, which is what lets each batch be freed.

Tests cover the invariant that neither batching nor filtering changes the result:
output is identical across batch sizes 1..1000, filtering matches filtering the
fully accumulated set, every file is requested exactly once, and missing chunk
files are still skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ic API

multi_decompress_to_buffer was removed in zstandard 0.23, and the serial fallback
added for that case is roughly 4x slower. That matters: the deployment sets
ZSTD_THREADS=4, so the fast path really is parallel today and falling back to
serial would be a real regression on any zstandard upgrade.

Decompression releases the GIL, so a thread pool recovers all of it. Measured on
zstandard 0.21.0, 192 MB across 64 blobs, 4 threads:

  serial decompressobj() [previous fallback]   164.8 ms   1164 MB/s
  serial dctx.decompress() [reuse]             150.1 ms   1278 MB/s
  ThreadPoolExecutor(4)                         41.7 ms   4593 MB/s
  multi_decompress_to_buffer                    42.5 ms   4507 MB/s

The pool matches the removed API, so this drops the version branch entirely
rather than choosing between a fast path and a slow one.

ZstdDecompressor is not thread-safe -- sharing one across workers silently
produces corrupt output rather than raising, which cost a debugging round here --
so each worker keeps its own via threading.local.

dctx.decompress needs the content size in the frame header, which put_chunk_edges
writes and which multi_decompress_to_buffer also required; decompressobj covers
any frame lacking it.

Verified against both zstandard 0.21.0 (the image pin) and 0.25.0: identical
output for 1/2/4/8 threads, threaded matches serial, order preserved, empty input,
and frames written with write_content_size=False.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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