fix(storage): persist deletions and clean up detail files in LargeDataStorage#2050
Conversation
openai0229
left a comment
There was a problem hiding this comment.
Thanks for the focused fix and regression coverage. The last-record index rewrite, explicit delete cleanup, and eviction cleanup are moving in the right direction, but there is one blocking consistency issue and one test gap to address before merge.
-
LargeDataStorage.update still writes detail data when the id no longer exists. getAfterSave(null, update) returns the update object, so save -> delete -> delayed update recreates .json without restoring the id in the index. I reproduced this against the current head: the detail-file assertion fails because the file exists again. Since update and delete are separate HTTP operations and the frontend has asynchronous update calls, this can also occur as a real race. Please make update return or reject when dataMap.get(id) is missing, and serialize save, update, and delete on the same storage instance with one shared lock or synchronized methods. The missing-id check alone does not close the interleaving where update reads the old record, delete completes, and update then writes the file.
-
deletedLastRecordMustNotResurrectAfterReload does not independently verify the empty-index rewrite. If the empty-map branch in saveDataList is removed while detail deletion remains, all three current tests still pass; reload only logs that the stale indexed detail file is missing. Please directly assert that the index file is empty after deleting the final record, and that the eviction index contains only the surviving ids.
Please also add a delayed-update-after-delete regression test and a deterministic update/delete concurrency test. Using a temporary injectable storage root instead of writing test data under the real user home would be a useful cleanup as part of this test work.
After these changes, please update the branch with the latest main and run the required checks, including Java CodeQL because this PR changes Java storage code.
|
Thank you for the original deletion fix and regression tests. I pushed a maintainer follow-up ( The follow-up serializes Verification completed locally: Because this changes Java storage code, I will wait for the refreshed backend CI and Java CodeQL before merging. |
…increasing timestamp Same-user calls within one millisecond returned identical ids, letting LargeDataStorage.save silently overwrite the earlier record. Keep the id format unchanged (ids reach the frontend as JSON numbers and must stay within JS Number.MAX_SAFE_INTEGER) and make the timestamp part strictly increasing via a lock-free CAS that borrows the next millisecond. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a constructor overload taking the storage base path (production behavior unchanged) and moves LargeDataStorageTest onto @tempdir instead of writing under the real user home. Also dedupes detail-file path construction into one helper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Hi, thanks for the detailed review and for reproducing the update-after-delete case — much appreciated. Follow-up on the remaining items from our side:
The branch is up to date with Please take another look when you have a chance — happy to address anything else. Thanks again! |
|
Thanks for adding the injectable storage root and for catching the same-millisecond ID collision. The new During review of the global We pushed maintainer commit Verification:
Because this is Java storage code, we will wait for the refreshed Backend CI and Java CodeQL before approving and merging. |
openai0229
left a comment
There was a problem hiding this comment.
Reviewed the latest maintainer-assisted fixes at f74858b. The storage reactor tests cover serialized save/update/delete behavior, deletion persistence, detail-file cleanup, and ID monotonicity; all 9 focused tests pass, the required checks pass, and Java CodeQL passes. The code issues from the earlier review are resolved.
Related issue
Closes #2049
Summary
LargeDataStorage.delete()did not persist deletions completely: the index rewrite was skipped when the map became empty (deleted records resurrected on restart, reproducible on a fresh desktop install), and neitherdelete()nor capacity eviction removed the record's detail JSON, leaking the user's SQL to disk on every delete/eviction.Minimal fix, no change to storage layout, public API, or callers:
saveDataList()now writes an empty index when the map is empty.delete()removes the record's detail file via a new protecteddeleteDetailData(Long id)helper (best-effort, logged on failure, matchingsaveDataListerror handling).save()deletes the evicted record's detail file and drops a redundantdataMap.remove()(pollFirstEntry()already removes the entry).Affected surfaces
Verification
mvn clean test -pl chat2db-community-storage -am -Dtest=LargeDataStorageTest -Dmaven.test.skip=false -DfailIfNoTests=false -Dsurefire.failIfNoSpecifiedTests=falsePOST /api/operation/saved/create, deleted it viaDELETE /api/operation/saved— index rewritten (including to an empty file), detail JSON removed from~/.chat2db-community/dev/storage/console/, and the record no longer resurrects after backend restart. Before the fix, a fresh desktop install reproduced the resurrection (save one query, delete, restart).Risk and compatibility
chat2db-community-storage.Reviewer map
LargeDataStorage#saveDataList(new else branch) andLargeDataStorage#delete; then the eviction branch in#save.FileUtil.delfails,deleteDetailDatalogs and continues — worst case equals the previous behavior (file left behind); in-memory state and index stay consistent.Contributor declaration
AI assistance: Diagnosis, fix, and tests were developed with substantial assistance from Claude Code (Anthropic); all changes were reviewed and verified locally by the author.