perf(normalize): stream Claude Code .jsonl instead of loading it whole (900MB -> 63MB peak) - #412
Open
Lcstyle wants to merge 1 commit into
Open
Conversation
normalize_conversations() reads the entire transcript through
_read_transcript_file(), then takes a second full copy via
content.split("\n"). Measured peak RSS for one real 78 MB Claude Code
session was 900 MB; transcripts of 436 MB and 769 MB exist in practice,
so a single file can approach available RAM. MAX_FILE_SIZE skips above
500 MB, but everything below it is eligible.
Split _try_claude_code_jsonl into a thin string wrapper plus
_try_claude_code_jsonl_lines(line_iter, verbatim=...) holding the
unchanged body, and try the iterator form against the open file for
.jsonl before the eager read. When it returns a result we return it;
when it returns None we fall through to the existing path untouched.
Behaviour preservation is a property of reusing the same parser body
rather than a claim. The short-circuit is safe because
_try_normalize_json_split already tries _try_claude_code_jsonl first, so
a successful stream returns exactly what the eager path would have. The
fallthrough preserves the ">= 3 lines starting with '>'" passthrough and
every non-Claude-Code format, since those do not parse as Claude Code
JSONL and the streaming attempt simply returns None.
Measured on the same 78 MB transcript:
peak RSS 900.0 MB -> 63.5 MB (14.2x lower)
output byte-identical
Verified across 14 real transcripts (up to 4.9M normalized chars) by
comparing sha256 of the joined normalize_conversations() output:
14/14 identical, 0 differences, 0 errors. End-to-end local mine cost
dropped from ~11.5x file size to 1.76x.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
normalize_conversations()reads the whole transcript through_read_transcript_file(), then takes a second full copy withcontent.split("\n"). Measured peak RSS on one real 78 MB Claude Code session:Transcripts of 436 MB and 769 MB exist in practice on long-running hosts.
MAX_FILE_SIZEskips above 500 MB, but everything below it is eligible, so onefile can approach available RAM on a smaller box and take the miner with it.
Change
Split
_try_claude_code_jsonlinto a thin string wrapper plus_try_claude_code_jsonl_lines(line_iter, verbatim=...)holding the unchangedbody, then try the iterator form against the open file for
.jsonlbefore theeager read. If it returns a result, return it; if it returns
None, fall throughto the existing path untouched.
+27 -1, one file.Why this is behaviour-preserving
Not a claim — a property of reusing the same parser body. Specifically:
tool_use_map,is_tool_onlymerging,strip_noise, assistant-turn mergingand
verbatimall behave identically._try_normalize_json_splitalready tries_try_claude_code_jsonlfirst; a successful stream returns exactly whatthe eager path would have returned.
">= 3 lines starting with '>'"passthrough andevery non-Claude-Code format (Codex, Gemini, claude.ai and ChatGPT bundles,
plain text), because such a file does not parse as Claude Code JSONL and the
streaming attempt simply returns
None.This is deliberately narrower than upstream MemPalace#468, which
introduces its own parser and removes
.jsonlfrom the existing dispatch. Thatapproach would change normalized output — and normalized text is stamped with
NORMALIZE_VERSION, so it would re-mine existing drawers with different content.Verification
comparing sha256 of the joined
normalize_conversations()output patched vsunpatched — 14/14 identical, 0 differences, 0 errors.
parser is a
TextIOWrapper, i.e. the open file, not a pre-split list.a 98 MB transcript peaks at 72.8 MB. End-to-end local mine cost fell from
~11.5x file size to 1.76x.
tests/test_convo_miner.py,tests/test_convo_miner_unit.py,tests/test_convo_miner_size_cap.py— 108 passed.Running on two production palaces (roughly 730k and 1.3M drawers).