Skip to content

Observability: warren.cache(), warren.backends(), warren.progress() (M10 steps 8–9) - #61

Merged
wfaithfull merged 12 commits into
mainfrom
m10-observability
Oct 23, 2026
Merged

wfaithfull merged 12 commits into
mainfrom
m10-observability

Conversation

@wfaithfull

Copy link
Copy Markdown
Contributor

M10 steps 8 and 9 (DESIGN.md §5.7 "Observability", §14 items 48 and 49). Where the implementation departs from the design, DESIGN.md §5.7 "Implemented (steps 8, 9)" records the decision.

Step 8: the activity table and cache counters

  • Activity table: one slot per backend in shared memory, beside the §5.6 registry. Each slot is 512 B and aligned to 64 bytes, so neighbouring slots don't share a cache line. Before that alignment, 4 clients ran 5–8% slower.
  • warren.cache(): this backend's private cache.
  • warren.backends(): what each backend is doing.
  • Cache counters and a wait event.

Step 9: progress

  • The warren-core port: progress::Progress, reached through Interrupts::progress(). Lanczos, local moving and refinement report through it, and jobs on the compute pool count into atomics that the waiting thread reports at each 1 ms poll.
  • Reporting: every call in §5.7's table reports its progress. A load reports as its own nested operation, load.
  • warren.progress() shows the reported progress, and warren.report_progress() lets the library's PL/pgSQL loops report their own.
    • It requires read access to the graph: 42704 if the graph doesn't exist, 42501 if the caller can't read it.
    • Negative counts are refused with 22023, and a NULL command clears the frame.
    • The frame clears at the end of the statement or of the top-level transaction.
    • A call takes about 1.5 µs (PERFORM 1 takes 0.7 µs).
  • Parallel labels: progress is PARALLEL SAFE; report_progress is PARALLEL RESTRICTED, because it writes the leader's slot.

Schema

The changes are in sql/pg_warren--0.5.0--0.6.0.sql: the two new functions, plus CREATE OR REPLACE copies of the 11 library functions that now call report_progress. 0.5.0 is untouched. CLAUDE.md's module list and dependency diagram now include activity, shared_memory and progress.

Measured cost

Measured against main 52c272c: release builds, PG 16 with the preload, a graph of 100k nodes and 300k edges. The machine was shared and loaded, so runs vary by ±15%.

Workload This branch main
degree, 1 client (tps) 31.9k–39.9k 33.3k–35.8k
degree, 4 clients (tps) 66.3k–81.6k 67.9k–78.4k
pagerank, 20 iterations (tps) 6.25–6.60 6.49–6.56

All of these differences are within noise.

Tests

  • pgrx: 740 tests pass on PG 16 and PG 18, and 738 on PG 15.
  • run.py on PG 16: 54/54 in each of preload, on, off and --trusted.
  • run.py on PG 18: the progress, backends, docs-examples and killed-loader tests pass.
  • dump.py and upgrade.py pass.
  • Miri: 417 passed.
  • fmt, clippy and rustdoc are clean.

Generated by Claude Code

claude added 12 commits October 23, 2026 17:11
…ds()

M10 step 8 (DESIGN.md §5.7, §14 items 48 and 49).

- activity: a slot per backend in pg_warren's own shared memory (a
  named DSM segment on PG 17/18, the preload on 15/16), written only by
  its backend with a change-count protocol, so writes take no lock and
  are legal at a statement's end. An exit adds the backend's counters to
  the header's totals and frees its slot in one change.
- The cache counts lookups, hits, waits, loads, evictions and refusals
  by reason, once per statement and version or vector, and records what
  it holds privately and maps.
- warren.cache() lists this backend's private cache; warren.backends()
  lists every backend's, then the cluster's totals, under
  pg_stat_activity's privacy rule.
- The shared cache's wait for another backend's load reports the
  WarrenSharedLoad wait event on PG 17/18.
- shared_memory: the room checks for segments, moved out of
  cache::shared into a leaf that activity uses too.

The registry's format and shared_cache() are unchanged, so 0.6.0 keeps
sharing across the upgrade without a restart on PG 17/18.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
The changelog, the README's status, and docs/limits.md's shared cache
section, with its new example run by docs_examples.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
…nels

M10 step 9, part 1 (DESIGN.md §5.7). `progress::Progress` reports a
phase, units done of a total, and named counters; `Interrupts` hands it
to long work (`None` by default), since both are the main thread's.

- Lanczos reports `lanczos`: steps of max_iter, with the wanted pairs
  converged, restarts and the largest relative residual estimate.
- Local moving reports `moving` (sweeps of max_sweeps, nodes moved) and
  refinement `refining` (communities refined of their number). Both run
  as one job on the pool, so the job counts into a `compute::tally::Tally`
  and the waiting thread reports it at each poll (`Reporting`).

Proptests check that done only grows and never exceeds its total, and
that reporting changes no result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
M10 step 9 (DESIGN.md §5.7): a build reports CSRs built of the plan's,
so the plan says how many there are, by its layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
M10 step 9 (DESIGN.md §5.7, §14 item 49).

- Each activity slot gains a command frame and an operation frame
  (`activity::frame`), and slots are aligned to cache lines.
- `activity::Running` opens a native call's operation and restores the
  one it interrupted when dropped; `phase`, `done` and `detail` report to
  it, directly or through warren-core's port (`PostgresInterrupts`).
- Reporting: refresh and contract (reading nodes and edges, building,
  writing, promoting), compact, loads, moving vectors, eigen, distance,
  local moving and refinement.
- `warren.report_progress` sets the command frame until the statement or
  its transaction ends, resolving its graph once per statement and user.
- `warren.progress()` lists every backend's frames, with
  pg_stat_activity's privacy.
- The library's iterative functions report each iteration, level or hop.
- test/sql/progress.sql watches them from a second session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
DESIGN.md §5.7: what step 9 built and where it differs from the design
(building and writing are one phase; compact's phases; contract's
operation on the quotient), and the cost measured against main. The
changelog, the README's status, and docs/compute.md's library section,
with its example run by docs_examples.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
Conflicts: the changelog, the README's status and DESIGN.md's step list
keep both sides; the upgrade script adds the quotas before observability;
sql/pg_warren--0.6.0.sql regenerated with sql/schema-drift.sh --update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
…d DAG

warren-core's `progress` port and `compute::tally`; pg_warren's
`shared_memory` and `activity`, and `cache::introspection`; activity's
place in the feature DAG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
PR #61's security review (DESIGN.md §5.7):
- progress() names a graph only to callers that may read it, as
  shared_cache(), cache() and info() do; NULL otherwise.
- A slot's role is the authenticated user, pg_stat_activity's usesysid,
  whatever SET SESSION AUTHORIZATION sets.
- report_progress checks its counts and registers the frame's end before
  it resolves and remembers its graph, so a refused report remembers
  nothing past its statement.
- With no room for the table (PG 17/18), a backend looks again at most
  once per statement.
- A slot is published only when its values change.
- The exit callback is registered before the slot is claimed.
- Readers retry through the adapter, which spins and, every 1,024 tries,
  checks for interrupts and sleeps 1 ms.
- Parallel workers read the table but claim no slot.
- PG 15/16 tables no longer carry the unused header lock and tranche.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
Conflicts: the loader's fill takes main's per-id-type sinks and still
reports the bytes copied; the build's imports and intake keep main's
batched mapping, with the row counts and CSR reporting around it;
interrupts keeps main's pool module and the Progress adapter; imports of
CachedIds beside activity's; DESIGN.md's "built so far" lists step 4 and
step 9. sql/pg_warren--0.6.0.sql regenerated with sql/schema-drift.sh
--update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Rxfe2L3iVjSwSBPdUu1Ke
@wfaithfull
wfaithfull merged commit 00e87fd into main Oct 23, 2026
17 checks passed
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.

2 participants