Skip to content

fix: let read-only users query Recall on ClickHouse and read each table once - #1218

Merged
kvlonge merged 7 commits into
mainfrom
fix/recall-readonly-single-pass
Oct 22, 2026
Merged

kvlonge merged 7 commits into
mainfrom
fix/recall-readonly-single-pass

Conversation

@kvlonge

@kvlonge kvlonge commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Why

Recall reads failed for ClickHouse readonly = 1 users. invocations read recall_events three times with memory-heavy joins, statements twice; every view used FINAL; long commands wrote many small inserts.

Changes

  • Read connections apply the pinned session settings only when the session may change them (not under readonly = 1). recall query sets readonly = 2 only if the session is not read-only, and caps rows while reading when it may not set limit; a setting it may not change fails naming it and readonly = 2.
  • invocations and output_invocations are one UNION ALL folded by invocation, without joins; statements folds one events read by resource, then by statement; artifacts nulls its stored side before the join. No view sets SETTINGS or depends on join_use_nulls.
  • invocations, statements and the delivery/context reads of output_invocations skip FINAL (their folds ignore repeated copies); listing and counting views keep it.
  • ClickHouse batches wait up to 5 s (was 1 s), or less once 8 MiB is queued; about 27 to 16 inserts for a 15 s command. A killed process can lose up to ~5 s of unsent history (was ~1 s); normal exit still flushes.
  • Recall schema version 3: ClickHouse writers with rights replace the views, limited writers warn, --ddl prints them. Local SQLite stores recreate their unchanged views once, and new rows on every backend carry schema_version = 3.

Closes CHI-772, CHI-773 and items 1-2 of CHI-777. Item 3 (LowCardinality) measured at 4% smaller tables, so not included.

Verification

  • ClickHouse E2E: readonly = 1, readonly = 2 and normal readers (settings, every view, schema, --ddl, output); rewritten views equal the previous ones (EXCEPT both ways, same DESCRIBE) and are unchanged with join_use_nulls = 0 and duplicated rows; each table read once by its key; upgrade from version 2.
  • Rust test for an early byte-triggered batch; Recall integration tests; make check-ci.

The ClickHouse invocations view read recall_events three times and
joined four subqueries, so every lookup paid for three event reads and
hash-join preallocation. It is now one UNION ALL of the context rows,
heartbeats and lifecycle events folded by invocation, without joins.
Rows and column types are unchanged; the Recall schema version rises to
3 so writers replace the view through the existing upgrade path.

Refs: CHI-773
The statements view read the events twice, once for statement facts
and once for resource names. It now folds one read by resource, which
names the resource and gathers its statement events, then by statement.
sql_available stays a lookup of the stored SQL, which is shared across
invocations.

Refs: CHI-773
A read connection sent SQLBuild's session settings and every recall
query set readonly = 2, which a readonly = 1 user may not do. A read
connection now applies the pinned session settings only when the
session may change settings; a query sets readonly = 2 only when the
session is not already read-only, sends the row limit only when the
user may set it and otherwise stops reading at the cap, and a setting
the user may not change fails with an error naming it and readonly = 2.
output_invocations is folded without joins and artifacts makes its
stored side nullable before the join, so no view depends on
join_use_nulls or sets SETTINGS.

Refs: CHI-772
Copies of a Recall key hold the same content, since a write with other
content is refused as a conflict. The invocations and statements views
only take the first, last, smallest or largest value of a key's copies,
so they read without FINAL, as do the delivery and context branches of
output_invocations. Views that list or count rows keep FINAL.

Refs: CHI-777
A long command wrote about one small part per table every second.
ClickHouse batches now wait up to five seconds; heartbeats still go out
at once and a closing command sends everything it holds. A batch is
also sent early once a quarter of the queue's byte capacity is ready,
so a byte-heavy command fills the queue no sooner than before.

Refs: CHI-777
@kvlonge
kvlonge enabled auto-merge (squash) October 22, 2026 12:32
@kvlonge
kvlonge merged commit 32aa75a into main Oct 22, 2026
46 checks passed
@kvlonge
kvlonge deleted the fix/recall-readonly-single-pass branch October 22, 2026 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant