Repository navigation
perf(session): bulk-load journal segments so replay costs one round-trip - #3666
ningmao-hlyz wants to merge 5 commits into
Conversation
cc51c83 to
4833661
Compare
Journal replay walks the commit chain segment by segment, and each segment read paid a fresh JDBC connection plus a CREATE TABLE round-trip in SqliteSessionStorage. Listing the sessions of a user with 17 sessions of 100 events therefore spent most of its time in connection setup (~2.8s measured). Two changes: - AtomicSessionStorage gains a readAll(paths) default that falls back to per-path reads; SqliteSessionStorage overrides it to serve the whole batch over one connection. - JournalSessionLog.scan bulk-loads every committed segment up front (one listPaths + one readAll) and walks the hash chain in memory. Backends without path discovery fall back to the previous per-segment reads. Payload blobs are still hydrated per read. Measured traversal of 17 sessions x 100 events: ~2.8s on main, ~0.375s with per-session storage instances, ~0.25s with one shared instance.
4833661 to
a0f4730
Compare
83fa13c to
2aba8c3
Compare
d223f90 to
74d4ddf
Compare
JournalSessionLog.scan now lists paths on every replay, which exposed LocalSessionStorage.listPaths (POSIX branch) to a pre-existing race: a concurrent delete of the namespace directory made Files.walk throw NoSuchFileException mid-iteration, failing the whole scan with UncheckedIOException. Discovery is a best-effort snapshot, so a namespace that vanishes mid-walk is reported as an empty listing instead.
74d4ddf to
9c1a6ac
Compare
The ticket's own poller thread releases the continuation as soon as the durable decision becomes visible, so resolveManaged can return OWNER_UNAVAILABLE when the poller drops the local waiter between the durable resolution and the owner lookup. The decision is accepted in both interleavings; accept either return code and keep the effect assertions exact.
|
The Windows failure on the previous head was ToolConfirmationCoordinatorTest.managedTimeoutWaitsForControlPlaneCancelledDecision in service-dataplane — unrelated to the storage changes here. The same race class was already deflaked in #3616 for a sibling test in that file: the ticket's own 200ms poller thread observes the durable decision and releases the continuation, so resolveManaged can legitimately return OWNER_UNAVAILABLE when the poller drops the local waiter between the durable resolution and the owner lookup. The decision is accepted either way; only the return-code assertion was scheduling-dependent. This branch deflakes it the same way (accepting RESOLVED or OWNER_UNAVAILABLE for that one assertion; the effect assertions — waiter released as cancelled, resolution status, decision event — stay exact). |
AgentScope-Java Version
2.1.0-SNAPSHOT (main)
Description
Reported in #3612,#3647 : listing the sessions of a user with 17 sessions of a few thousand events
took over 6s on Windows.
Core issue: connection setup overhead during replay. In the original implementation on
main, everystorage.readcall opens a new JDBC connection and executes a table-creation DDL statement. Replay traverses the commit chain segment by segment, reading each segment twice. With 17 sessions × 3,000 events, this results in over 20,000 connection setups, which accounts for the 12.6-second runtime. After switching to batch reads, the entire replay requires only two connections—one forlistPathsand one forreadAll—bringing the measured runtime down to 1.6 seconds.Additional benefits: All code paths that use
scanbenefit as well, includinggetAgentState, session restoration, and session search.Changes
AtomicSessionStoragegains areadAll(paths)default that falls back to per-path reads.SqliteSessionStorageoverrides it to serve the whole batch over one connection.JournalSessionLog.scanbulk-loads every committed segment up front (onelistPaths+ onereadAll— two round-trips per journal replay instead of two per segment) and walks the hashchain in memory. The chain checks (cycle detection, contiguity, checksum) are unchanged; they
just run over in-memory data. Payload blobs are still hydrated per read. The listing surfaces
physically-present rows — including segments a concurrent writer has not yet referenced from
head — so a bulk read that trips over one of those degrades to per-segment reads instead of
failing the scan.
so Windows file-handle semantics and all existing tests are untouched.
Measurement
17 sessions × 100 events (one commit per event), traversing all sessions via
readAfter(0, N):And at a larger scale, 17 sessions × 3000 events (51,000 events, one commit per 5 events):
How to test
All green, plus the full
agentscope-core+agentscope-harnesssuites (4.5k tests) pass locally.