fix(db): make compression-point deduplication atomic

The duplicate check for append_compression_point ran as a SELECT before
the UPDATE, so two sessions persisting the same point could both pass it.
The predicate now lives inside the UPDATE's CASE expression and is
evaluated under the row lock; a point without query_index or
compressed_summary never matches and is always appended. Adds a
two-session regression test that races the same point and expects one.
This commit is contained in:
Alex committed 2026-09-05 09:49:22 +01:00
1 parent 1f86139b9c
commit fdba261d11
2 files changed
+78 -38

No files matched your search

@@ -393,6 +393,46 @@ class TestCompressionMetadata:
points = repo.get(conv["id"], "user-1")["compression_metadata"]["compression_points"]
assert [p["query_index"] for p in points] == [3, 5]
def test_append_compression_point_dedupes_across_concurrent_sessions(self, pg_engine):
"""The duplicate check runs inside the UPDATE, under the row lock, so
two sessions racing to persist the same point store it once."""
import threading
import time
with pg_engine.connect() as setup:
with setup.begin():
conv = _repo(setup).create("user-1", "c")
point = {"query_index": 3, "compressed_summary": "S", "original_token_count": 90}
results, errors = [], []
def append(hold_before_commit):
try:
with pg_engine.connect() as conn:
with conn.begin():
results.append(
_repo(conn).append_compression_point(
conv["id"], dict(point), max_points=3
)
)
time.sleep(hold_before_commit)
except Exception as exc: # pragma: no cover - surfaced by the assertion
errors.append(exc)
first = threading.Thread(target=append, args=(0.5,))
second = threading.Thread(target=append, args=(0.0,))
first.start()
time.sleep(0.1) # second blocks on the row lock until first commits
second.start()
first.join()
second.join()
assert not errors
assert results == [True, True]
with pg_engine.connect() as conn:
fetched = _repo(conn).get(conv["id"], "user-1")
points = fetched["compression_metadata"]["compression_points"]
assert [p["query_index"] for p in points] == [3]
class TestResolveAgentRef:
"""The repo must translate Mongo ObjectId-shaped ``agent_id`` values