Skip to content

feat(storage): enable single writer by default and harden pool timeout - #393

Merged
matthyx merged 2 commits into
mainfrom
feat/single-writer-default-and-pool-timeout
Sep 3, 2026
Merged

feat(storage): enable single writer by default and harden pool timeout#393
matthyx merged 2 commits into
mainfrom
feat/single-writer-default-and-pool-timeout

Conversation

@matthyx

@matthyx matthyx commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  1. Enable single-writer by default: Flips SingleWriterEnabled in pkg/config/config.go and singleWriterEnabled in pkg/registry/file/singlewriter.go to true.
  2. Prevent query interruption from pool checkout timeout: sqlitex.Pool.Take(poolCtx) ties the connection's SQLite interrupt handler to poolCtx.Done(). In both legacy write paths (Create, GuaranteedUpdate) and single-writer phases (createSingleWriter, guaranteedUpdateSingleWriter, and singleWriter.commit), we now rebind conn.SetInterrupt(ctx.Done()) (or job.ctx.Done()) and clear it upon release. This prevents the pool checkout timeout from interrupting valid in-flight writes.
  3. Relax DefaultPoolTimeout: Bumps DefaultPoolTimeout from 5s to 15s to provide breathing room during burst connection acquisition under heavy write loads.
  4. Fix cross-connection deadlock in SaveContainerProfile: When SaveContainerProfile is called inside an active transaction (e.g. from ContainerProfileProcessor), it preserves the active transaction connection instead of attempting to acquire a second connection through the single writer.
  5. Populate Spec in single writer metaOut: In createSingleWriter and guaranteedUpdateSingleWriter, metaOut is now populated with the full persisted candidate object rather than the stripped metadata-only object.
  6. Isolate test pools: Properly scopes test pools in TestVulnSummaryStorageImpl_Get subtests.

- Flip singleWriterEnabled to true by default
- Rebind conn.SetInterrupt(ctx.Done()) across Create, GuaranteedUpdate, and singleWriter commit/prepare phases so poolTimeout does not prematurely interrupt SQLite statements
- Bump DefaultPoolTimeout from 5s to 15s to provide headroom during high contention
- Avoid cross-connection deadlock in SaveContainerProfile when called inside an active transaction
- Populate metaOut with full candidate object in single writer Create/GuaranteedUpdate (preserving Spec)
- Properly isolate test pool in TestVulnSummaryStorageImpl_Get subtests

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: b11f4fa7-0f0c-406c-ac16-d53e4bc76b84


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…dlock

Calling conn.SetInterrupt on connections managed by sqlitex.Pool causes
races and deadlocks in zombiezen sqlite's cancelInterrupt (blocking on nil chan)
when pool.Put concurrently cleans up or when contexts have nil Done channels.
sqlitex.Pool already manages SetInterrupt internally during Take and Put.

Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

Summary:

  • License scan: failure
  • Credentials scan: failure
  • Vulnerabilities scan: failure
  • Unit test: success
  • Go linting: success

@matthyx matthyx added the release label Sep 3, 2026
@matthyx
matthyx merged commit 4d99860 into main Sep 3, 2026
7 checks passed
@matthyx
matthyx deleted the feat/single-writer-default-and-pool-timeout branch September 3, 2026 10:37
matthyx added a commit that referenced this pull request Sep 7, 2026
The single-writer write path (default-on since #393) funneled every
Create/GuaranteedUpdate/SaveContainerProfile commit for ALL keys through
one goroutine per StorageImpl, collapsing write concurrency from the
legacy per-key-lock path's ~SqlitePoolSize-way parallelism to strictly
1-way. kubescape/node-agent's CI went from 0-1 flaky failures to 18-19
of ~30 jobs failing, every one on context deadline exceeded against the
storage apiserver.

Correctness only ever required one in-flight committer per key: commit()
re-acquires the per-key lock and does an authoritative resourceVersion
compare-and-commit, and writes to different keys touch disjoint metadata
rows and payload files. Route commit jobs to N=8 shard goroutines by
FNV-1a over the key instead, so same-key commits stay serialized in
submission order while different-key commits run in parallel.

highBurstLimit's high-vs-low arbitration becomes per-shard. Jobs on
different shards never competed for a goroutine's time, so there is
nothing there to arbitrate; only same-shard cross-priority contention is
affected.

Shard count is a fixed part of what SingleWriterEnabled turns on
(DefaultSingleWriterShards, 8), not independently configurable --
validated empirically against kubescape/node-agent's real CI, which
showed raising it further (16, with pool size 24) doesn't reduce the
residual failure rate over the default, so exposing it as a tunable
wasn't worth the added config surface.

Empirical validation: with this fix, node-agent's CI went from 18-19/30
failing (every one a sustained 20-minute hang against the storage
apiserver) to 15/30 (fast, self-healing failures on unrelated
assertions; one representative test run went from 323 failed create
attempts to 6 out of 61).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LLN3CgGftcnAikV13qD6yJ
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant