Repository navigation
perf(translate): hard-link PDF cache entries instead of copying - #133
Merged
Merged
Conversation
A whole-PDF cache entry and the job output it came from hold the same bytes, so store() and materializing a hit now go through pdf_cache.link_or_copy: a hard link staged at .tmp and os.replace'd, falling back to a copy where links are refused (EXDEV from a tmpfs /tmp, FAT/exFAT, some network shares). Saves one PDF of disk and the copy per translation. A .tmp left by a crashed store is removed before linking: copying onto it would rewrite in place the bytes it may share with an earlier output. The new rule this rests on — never rewrite a translated PDF in place — is recorded in CLAUDE.md and translation-pipeline.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
A whole-PDF cache entry and the job output it came from hold the same bytes, so they are now one file under two names instead of two copies.
pdf_cache.link_or_copy(src, dest): hard link todest.tmp, copy if the link is refused, thenos.replace. Removes the.tmpon failure; a missingsrcstill raisesFileNotFoundError, which the cache-hit path already treats as a miss.store()and the cache-hit materialize inprocessor.pyboth use it, replacing two copy-and-rename blocks.EXDEV(tmpfs/tmp), FAT/exFAT, some network shares..tmpleft by a crashed store is removed before linking. Copying onto it would rewrite in place the bytes it may share with an earlier run's output.Saves one PDF's worth of disk and the copy time per translation.
The invariant this rests on
Nothing may rewrite a translated PDF in place. Removing or replacing a name is safe (the next job's rmtree, LRU eviction,
os.replace), but an in-place write would reach the cache entry too. This already holds: every rolling version and job dir is a fresh name, andsubset_embedded_fonts/export_pdfstage a new file and rename it. Recorded inCLAUDE.mdanddocs/architecture/translation-pipeline.md.Side effects, also documented: on Windows an eviction while the viewer streams the same bytes can fail its unlink (warning; next store of that key replaces the file), and an evicted entry's space returns only once the job's name is gone at the next job.
Tests
Written test-first in
tests/test_pdf_cache_store.py:EXDEV,EPERM) gets a copy (red → green).tmpfrom a crash is replaced, not written through (red → green)FileNotFoundErrorand leaves nothing. These passed on arrival; checked against a copy-instead-of-link and a skip-the-unlink mutation of the helper, both caught.Two existing tests adjusted, assertions unchanged:
test_the_whole_document_answers_any_selectionrewrote the stored file in place, which the pipeline never does; the second artifact now comes from its own job dir.test_a_failed_copy_leaves_no_row_and_no_temp_filenow refuses the link as well as the copy, so the failure path still runs.python -m pytest tests: 1408 passed, 8 skipped.ruff check src tests scripts: clean.Not covered
process_pdfitself isn't unit-tested. Manual check: translate a document twice inpnpm tauri dev; the second run should be a cache hit andstaton the job's_v999.pdfshould show 2 links.🤖 Generated with Claude Code