fix(cassandra): page the delete-path scans so collections delete fully (#1066) - #1077
Open
Souptik96 wants to merge 1 commit into
Open
fix(cassandra): page the delete-path scans so collections delete fully (#1066)#1077Souptik96 wants to merge 1 commit into
Souptik96 wants to merge 1 commit into
Conversation
async_execute materialises only the first result page. Three delete paths used it to enumerate everything they were about to remove, so a collection larger than fetch_size (5000) was only partly deleted while the operation reported success: direct/cassandra_kg.py async_delete_collection storage/rows/cassandra/write.py delete_collection storage/rows/cassandra/write.py delete_collection_schema All three now use async_scan, which iterates every page and discards each one as it goes, so peak memory stays bounded by fetch_size. It returns a flat list, so no iteration code below the call had to change. The two row-storage sites are the same defect as the reported one, found by auditing the other async_execute callers as the issue suggests. They are arguably worse: both delete the row_partitions manifest rows for the collection afterwards, so a truncated discovery does not merely leave rows behind, it removes the index that would let anything find them again. A repo-wide sweep found no other unbounded SELECT going through async_execute; every remaining caller either carries a LIMIT, is a point lookup, or is a write. Fixes trustgraph-ai#1066
Contributor License Agreement ✅All contributors have signed the CLA. Thank you! |
Author
|
I have read the CLA Document and I hereby sign the CLA. |
Author
|
I have read the CLA Document and I hereby sign the CLA |
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.
Fixes #1066.
Summary
async_executematerialises only the first result page. Three delete paths used it to enumerate everything they were about to remove, so a collection larger thanfetch_size(5000) was only partly deleted while the operation logged success:trustgraph-flow/trustgraph/direct/cassandra_kg.pyasync_delete_collection← reported in #1066trustgraph-flow/trustgraph/storage/rows/cassandra/write.pydelete_collectiontrustgraph-flow/trustgraph/storage/rows/cassandra/write.pydelete_collection_schemaAll three now use
async_scan, which iterates every page and discards each page as it goes, so peak memory stays bounded byfetch_sizerather than by the size of the collection.Why
async_scanand notasync_execute_paged(the issue suggested the latter):async_scanreturns a flat list, so it is a drop-in forasync_executeand none of the iteration code below the call sites had to change.async_execute_pagedreturns a list of pages, which would need flattening at each site and holds every page at once. Both were already present and used elsewhere in the tree; neither is new here.The two extra sites
@zsoltboko's report ends with "Worth auditing the other
async_executecallers for the same unbounded-scan pattern", so I did. The two row-storage sites are the same defect, and they are arguably worse than the reported one: both delete therow_partitionsmanifest rows for the collection after the truncated discovery, so the surviving rows do not merely persist — they lose the index that would let anything find them again.I swept all 87
await async_execute(call sites in the repo, resolving multi-line and variable-held SQL. Exactly four are unboundedSELECTs, three of which are the delete paths above. The fourth istables/config.py:116get_version, which isSELECT version FROM version WHERE id = …— a single-row point lookup, correctly left alone. Every other caller carries an explicitLIMIT, is a point lookup, or is a write. So this should be the complete set.Test plan
Two new test modules, 9 tests, placed next to their subjects:
tests/unit/test_direct/test_entity_centric_delete_pagination.py(4)tests/unit/test_storage/test_rows_cassandra_delete_pagination.py(5)The harness models both halves of the driver contract instead of stubbing the helpers out, which is what makes the tests discriminating:
No production function is patched, so reverting the fix genuinely fails these tests. The storage tests bind the real methods onto a
MagicMockprocessor, following the existing style intests/unit/test_storage/test_rows_cassandra_storage.py, which avoids standing up the whole flow-processor framework.Negative controls — revert one source file, keep the tests:
cassandra_kg.pyonlywrite.pyonlyIn both cases the failures are exactly the paging assertions, while the guard tests pass unpatched —
..._single_page_unchanged(a collection that fits in one page must behave as before) andtest_sync_delete_collection_spans_pages(the sync path was already correct and stays pinned). That is the point of including them: they assert preserved behaviour, so they must not move.Full unit suite (
pytest tests/unit, Python 3.13.4):master@27e0ab8+9 passedis exactly the tests added. The one failure is identical on both sides and unrelated:tests/unit/test_cli/test_load_structured_data.py::TestLoadStructuredDataUnit::test_invalid_descriptor_format.Limitations, stated plainly
cqlshcounts are the end-to-end evidence; a confirming run against a >5000-quad collection would be worth doing before release.tests/unit/test_decodingwas excluded from both runs (3 collection errors, identical before and after). It needstrustgraph-docling, which I did not install because it pulls a torch stack. Nothing in this change is near the decoding path.trustgraph-base,-flow,-cli,-vertexai,-bedrock,-mcpfrom source with plainpip install .aspull-request.yamldoes, generating the*_version.pyfiles withVERSION=2.8.999.makeis unavailable on Windows so I replicated that Makefile target directly; no generated version file is in the diff.master, matching the most recent external merges (Sanitize workspace name used as Cassandra keyspace (#1043) #1053, Update section title in README.md #1051, Update section title in README.md #1044). Happy to retarget at a release branch if that is preferred for a data-loss fix.I will sign the CLA when the bot prompts.