Skip to content

Kans/pebble default engine - #1087

Merged
kans merged 16 commits into
mainfrom
kans/pebble-default-engine
Aug 21, 2026
Merged

Kans/pebble default engine#1087
kans merged 16 commits into
mainfrom
kans/pebble-default-engine

Conversation

@kans

@kans kans commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

What this does

Flips the default storage engine for newly created .c1z files from SQLite (v1) to Pebble (v3). An engine-less dotc1z.NewStore on a new/empty path now writes a v3 artifact, matching the existing explicit WithEngine(EnginePebble) behavior.

What this does NOT change

  • Existing files keep their format. Engine-less opens of existing files dispatch on the magic byte; a v1 file stays v1. In-place v1→v3 conversion requires an explicit WithEngine(EnginePebble) and is logged at Info.
  • Explicit engine requests are unchanged. WithEngine(EngineSQLite) / --storage-engine sqlite still produce v1 artifacts.
  • NewC1ZFile remains SQLite-only and rejects v3 files, as before.
  • The compactor destination follows the resolved input engine, not the global default.

Versioning

This is a default durable-format change, so it ships as a minor bump (v0.25.0), not a patch. pkg/sdk/version.go is regenerated from the release tag; no hand edit in this PR.

Compatibility evidence

  • TestCheckpointCompatAcrossSDKVersions was run locally against v0.20.2 in both directions, green, with the new-gen artifact being v3. The new_gen_old_resume leg forces the old binary to read the v3 file, so this covers artifact-format readability, not just checkpoints.
  • Fleet audit found no pre-v3 readers in production.

Rollout notes for downstream connectors

  • Connectors that don't pin an engine will start producing v3 artifacts on their next sync after upgrading. Anything that reads those artifacts must be on an SDK with v3 read support (verified back to v0.20.2 above).
  • To stay on v1, pin explicitly: --storage-engine sqlite or WithEngine(EngineSQLite).
  • Known limitation: the hidden --diff-syncs flag requires a multi-sync v1 artifact and returns ErrDiffUnsupported against v3 files. Per RFC 0002's call-site audit this surface has no live callers.

Follow-up (out of scope)

Pebble's fixed tuning (256 MiB block cache, 64 MiB memtables) is not yet overridable through C1ZOptions; memory-constrained runtimes that need to opt down should get plumbing in a separate change.

kans and others added 3 commits August 13, 2026 12:35
An unset engine in NewStore now resolves to EnginePebble: new c1z files
are written in the v3/Pebble format, and writable opens of existing
v1/SQLite files convert to Pebble, matching the existing explicit
WithEngine(EnginePebble) behavior. Explicit WithEngine(EngineSQLite)
still selects the legacy v1 engine, and NewC1ZFile remains SQLite-only.

Downstream tests that relied on the SQLite default are expected to
break; the direct default-assertion tests are updated here.

Co-authored-by: Cursor <cursoragent@cursor.com>
With Pebble as the default engine, an engine-less writable reopen of a
v1 file now converts it to v3. The test's purpose is to prove an
explicit SQLite request never converts, so pass WithEngine(EngineSQLite)
on the reopen.

Co-authored-by: Cursor <cursoragent@cursor.com>
The flag description said "leave unset to use the baton-sdk default"
without naming it; now that the default flipped to pebble, say so.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread pkg/dotc1z/engine_registry.go
Comment thread pkg/dotc1z/engine_registry.go
Comment thread pkg/field/defaults.go
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

General PR Review: Kans/pebble default engine

Blocking Issues: 0 | Suggestions: 1 | Threads Resolved: 0
Criteria: Criteria status: loaded .claude/skills/ci-review.md from trusted base f7333f66e01d.
Review mode: incremental since 9c5b02a8
View review run

Review Summary

The full PR diff was re-scanned for security and correctness; nothing new surfaced. The new commits are test-only: the five NewStoreNewStore(..., WithReadOnly(true)) conversions in pkg/sync/ingest_filter_test.go (368, 439, 484, 564, 800) close out the last review's read-intent finding, and pkg/sync/syncer_cleanup_test.go:124 adds a fail-safe defer f.Close(ctx) — verified correct, pebbleStore.Close is guarded by closed under closeMu (pkg/dotc1z/pebble_store.go:747-752) and syncer.Close already closes the store (pkg/sync/syncer.go:3812), so the happy path really is a no-op. The three remaining carried-over findings were addressed by the PR description rather than by code: versioning now states the v0.25.0 minor bump with the TestCheckpointCompatAcrossSDKVersions v0.20.2 bidirectional result and the no-pre-v3-readers fleet audit, and the fixed Pebble tuning is explicitly deferred to a follow-up.

Risk triage (carried forward, unchanged): silence — yes (a wrong format flip produces a well-formed artifact); durability — yes (c1z on-disk format); uncontrolled dimensions — yes (which SDK version reads the artifact); consumer distance — the c1 platform and every downstream connector. Remediation rung 3–4. Verdict: HIGH. The instrument that gives real coverage on a version-pair flip is a two-artifact cross-version harness, and it now exists and is cited: TestCheckpointCompatAcrossSDKVersions run against v0.20.2 in both directions, with the new_gen_old_resume leg forcing the old binary to read a v3 file. That evidence is in the PR body, not in CI, so it is not re-run on future commits — worth a scheduled compat-check matrix job, but not a merge blocker for this change.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • pkg/field/defaults.go:141 — carried over, downgraded: the --diff-syncs incompatibility is now documented in the PR description (known limitation, ErrDiffUnsupported on v3, no live callers per the RFC 0002 call-site audit), which resolves the communication ask. The remaining gap is code-local: the hidden flag is still registered and fully wired (pkg/cli/commands.goconnectorrunner.WithDiffSyncspkg/tasks/local/differ.go) with a description that gives no hint it now applies only to pre-existing v1 artifacts. (high confidence on the code path; low impact — hidden flag, no live callers)
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `pkg/field/defaults.go`:
- Around line 140-145: The hidden `--diff-syncs` flag's description ("Create a new
  partial SyncID from a base and applied sync.") no longer reflects reality now that
  the default storage engine is Pebble. A v3/Pebble c1z holds exactly one sync by
  contract, so the differ returns ErrDiffUnsupported
  (pkg/dotc1z/engine/pebble/adapter_diff.go) against any default-engine artifact.
  Amend the WithDescription text to state that the flag requires a multi-sync
  v1/SQLite artifact (i.e. one produced with --storage-engine sqlite or predating the
  Pebble default), so an operator who reaches for it is not left debugging an opaque
  error. Same for the sibling base-sync-id / applied-sync-id field descriptions if you
  want them consistent. No behavior change is required; this is help-text only.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking issues found — see review comments.

kans and others added 3 commits August 13, 2026 13:41
With Pebble as the engine default, the default was also triggering the
in-place v1-to-pebble conversion on every engine-less writable open of
an existing v1 file. Read-intent callers (provisioner, local differ,
baton recalculate-stats, baton optimize) open stores without an engine
and would silently rewrite user files; optimize would convert the file
and then report "no changes made".

The conversion now requires an explicit WithEngine(EnginePebble);
engine-less opens of existing files always dispatch on the magic byte.
New files still default to Pebble. The conversion log moves from Debug
to Info so an in-place format migration is visible, and tests cover the
engine-less writable and read-only reopen paths plus the on-disk v3
header of a default-engine artifact.

Co-authored-by: Cursor <cursoragent@cursor.com>
The SQLite branch of doOneCompaction opened the destination with no
engine and relied on the dotc1z default being SQLite. With the default
now Pebble, an all-SQLite compaction created a v3 destination and then
failed in the attached compactor. Always pass the resolved engine when
opening the destination, and fix the comments that claimed the unset
compactor default is SQLite (it follows the inputs).

Co-authored-by: Cursor <cursoragent@cursor.com>
The storage_engine field comment claimed SQLite is the default engine;
regenerated the pb mirrors. RFC 0001 gets an amendment note that the
default flipped to Pebble for new files while existing files keep their
on-disk format.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread pkg/dotc1z/c1file.go Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

The syncer now writes v3/Pebble files by default, and tests that
reopened the artifact with NewC1ZFile (the SQLite-only constructor)
failed with a magic-number mismatch. Reopen through NewStore, reach
ListSyncRuns and CloneSync through their engine-neutral surfaces, and
pin TestCleanupContextDeadlineExceeded to SQLite: its 200ms run budget
is only reliably exceeded by SQLite's row-by-row cleanup, while Pebble
drops old syncs via cheap range deletes and completes the sync.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread pkg/dotc1z/engine_registry.go
Comment thread pkg/sync/syncer_cleanup_test.go
Comment thread pkg/dotc1z/engine_registry.go

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking issues found — see review comments.

Flipping the default engine to pebble made diff-syncs structurally
impossible against default-engine artifacts: a v3 c1z holds a single
sync, so a base and an applied sync can never coexist in one file. The
local differ now wraps ErrDiffUnsupported with remediation (re-run the
syncs with --storage-engine sqlite), the diff-syncs flag documents the
requirement, and tests pin both the rejection and the surviving v1
diff path. Also adds the default-engine sibling of the sqlite-pinned
cleanup deadline test, asserting pebble's replacement-based lifecycle:
the run completes within budget, exactly one sealed sync remains, and
the artifact is v3 on disk.

Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking issues found — see review comments.

kans and others added 3 commits August 14, 2026 11:12
TestExternalResourceUserProfileMatch (merged from main, #1046) opened
its freshly synced c1z with the sqlite-only NewC1ZFile constructor,
which rejects the v3 artifact the pebble default now produces. Same
class and fix as the rest of the pkg/sync sweep in ee6683a.

Co-authored-by: Cursor <cursoragent@cursor.com>
GenerateSyncDiff's only production caller is the local differ, reached
solely through the hidden diff-syncs flag, and RFC 0002's call-site
audit records no live callers. Guidance text, flag docs, and tests for
a workflow nobody runs are noise, so restore the differ and flag
description to their prior state. Pebble's ErrDiffUnsupported still
propagates as-is if the dead path is ever exercised.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread docs/rfcs/0001-pebble-storage-engine.md Outdated
Comment thread pkg/dotc1z/engine_registry_test.go

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking issues found — see review comments.

Review sweep after the pebble default flip: scope WithEngine's and the
Engine constants' "default is pebble" docs to NewStore (NewC1ZFile is
SQLite-only and normalizes unset to sqlite), correct the c1zOptions
field comment that still claimed a SQLite default, reword sanitize's
fallback comment now that SQLite there is an explicit pin, widen the
RFC 0001 amendment to name every superseded SQLite-default statement,
and guard TestNewStoreDefaultsToPebbleDriver so a mid-test failure
cannot leak the open pebble store for the rest of the test binary.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread pkg/dotc1z/engine_registry.go
Comment thread pkg/dotc1z/engine_registry.go
Comment thread pkg/sync/syncer_test.go Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking issues found — see review comments.

The NewC1ZFile-to-NewStore sweep left 19 of 21 post-sync verification
opens unclosed, which under the pebble default leaks an unpacked temp
DB, open fds, and background compaction goroutines for the rest of the
test binary. These opens only read, so request read-only and close
them. TestResumeSyncWithChildResources gains the most: its mid-resume
inspection can no longer write to the artifact syncer2 resumes from.

Also note that the empty-engine fallback in storeOptionsFromC1ZOptions
is defensive only; NewStore overwrites it with the selected driver's
engine on the next line.

Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking issues found — see review comments.

WithC1ZPath with no engine follows the NewStore default, so the named sqlite comparison was running Pebble on both sides after the default flip.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread pkg/sync/ingest_filter_test.go Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

kans and others added 2 commits August 21, 2026 12:13
The NewC1ZFile-to-NewStore sweep left five post-sync verification opens
in ingest_filter_test.go writable, unlike their siblings here and the 13
sites in syncer_test.go. These opens only read after the syncer is
closed, so request read-only and stop re-sealing the v3 envelope on
Close.

Co-authored-by: Cursor <cursoragent@cursor.com>
A require failure between NewStore and syncer.Close left the open
pebble store leaked for the rest of the test binary — the same class
guarded in engine_registry_test.go. Pebble Close is idempotent, so the
fail-safe defer is a no-op on the happy path where syncer.Close owns
the store shutdown.

Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

@kans
kans merged commit 5ad9602 into main Aug 21, 2026
12 checks passed
@kans
kans deleted the kans/pebble-default-engine branch August 21, 2026 19:00
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