Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
c7f2a98
feat(storage v3): combined v4 storage-engine PR stack (for macro review)
pquerna May 24, 2026
2659a4e
fix(storage): address v4 review comments
pquerna May 25, 2026
72881f6
perf(storage v3): fat-batch Put*Records + fresh-sync NoSync write path
pquerna May 25, 2026
de17b96
feat(storage v3): wire real pagination through adapter List* methods
pquerna May 25, 2026
60897a9
feat(storage v3): close syncer-blocking gaps (A) + IfNewer (D) + Stat…
pquerna May 25, 2026
f32a53c
fix(storage v3): close reviewer-bot findings (G) — error propagation,…
pquerna May 25, 2026
9df52b0
fix(storage v3): always read-before-write on PutXRecords (no fresh-sy…
pquerna May 25, 2026
bc9e1f6
fix(ci): pin golangci-lint to v2.12.2 + restore nolint directives
pquerna May 25, 2026
937275f
fix(go.mod): pin Go version to 1.25.2 to match CI
pquerna May 25, 2026
1627b04
fix(dotc1z): tiebreak getFinishedSync ORDER BY for Windows time resol…
pquerna May 25, 2026
9676f15
docs(rfc 0004): autoresearch plan for Pebble engine perf loop
pquerna May 25, 2026
d9f5b84
docs(rfc 0004): follow-up RFC for what lands on PR #874 next
pquerna May 26, 2026
aa79751
docs(rfc 0004 follow-ups): S1 simplified — TAR + TAR_ZSTD only
pquerna May 26, 2026
2771d3e
docs(rfc 0004 follow-ups): generalize split-batches to Resources + En…
pquerna May 26, 2026
7c52272
feat(dotc1z v3): TAR + TAR_ZSTD payload encodings (RFC 0004 §1)
pquerna May 26, 2026
a832d6a
perf(dotc1z pebble): vtprotobuf MarshalVT on the write path (RFC 0004…
pquerna May 26, 2026
0a0601c
docs(rfc 0004): rewrite tracker for the squashed-PR layout
pquerna May 26, 2026
77ca336
perf(dotc1z pebble): write-path tier-A cherry-picks (RFC 0004 §3a)
pquerna May 26, 2026
5c1deb4
perf(dotc1z): UnmarshalVT + parallel ExtractZstdTar (RFC 0004 §3b/S4)
pquerna May 26, 2026
14b7e64
docs(rfc 0004): closure note for S0–S5 execution + tracker refresh
pquerna May 26, 2026
b77fcf0
fix(format/v3): restore //nolint:gosec for writeTar walk callback
pquerna May 26, 2026
deab18c
perf(dotc1z pebble): grants split-batch + skipGet + within-call dedup…
pquerna May 26, 2026
44f877a
perf(dotc1z pebble): generalize split-batch + dedup + skipGet to Reso…
pquerna May 26, 2026
aad1c71
perf(dotc1z pebble): grantReadArena pre-allocates nested slots for Un…
pquerna May 26, 2026
ade2b02
test(dotc1z pebble): baton-demo-shaped Sync() bench across both engines
pquerna May 26, 2026
e10e262
feat(dotc1z pebble): complete C1ZStore adapter — Pebble plugs into pk…
pquerna May 26, 2026
0907879
docs(rfc 0004): mark Pebble C1ZStore adapter as shipped
pquerna May 26, 2026
3f00774
feat(dotc1z pebble): FileOps.CloneSync implemented (RFC §3a follow-up)
pquerna May 26, 2026
33c9519
feat(dotc1z pebble): FileOps.GenerateSyncDiff implemented
pquerna May 26, 2026
87bcd61
docs(rfc 0004): mark CloneSync + GenerateSyncDiff as shipped
pquerna May 26, 2026
ea6a3d4
fix(dotc1z pebble): address PR review — pagination cursor, UnmarshalV…
pquerna May 26, 2026
357f4a3
chore(dotc1z pebble): drop bare {} blocks, stale FileOps doc, dead he…
pquerna May 26, 2026
51eb6ce
fix(dotc1z pebble): close two registeredStore dirty-flag escapes + ch…
pquerna May 26, 2026
f11d286
test(dotc1z pebble): explicit benchmarks for the 4 kans-listed lightn…
pquerna May 26, 2026
15fc543
fix(bench): suppress CI gosec G703 on bench-controlled os.Stat path
pquerna May 26, 2026
75f22a8
feat(dotc1z pebble): close needs_expansion index — correctness gap on…
pquerna May 26, 2026
579a715
chore: godot — end keys.go doc comment in a period not a quote
pquerna May 26, 2026
fa5d8d9
test(sync): full Sync() benchmark via NewSyncer — SQLite vs Pebble
pquerna May 26, 2026
40b97da
feat(dotc1z pebble): on-open secondary-index migrations + xlarge benc…
pquerna May 26, 2026
be92d90
fix(bench): drop unused //nolint:gosec on pkg/sync full_sync_bench
pquerna May 26, 2026
5866919
perf(dotc1z): Stats() / GrantStats() O(1) sidecar on both engines (RF…
pquerna May 26, 2026
405db68
perf(dotc1z pebble): idxGrantByPrincipalResourceType (RFC §A2)
pquerna May 26, 2026
63f87a5
feat(reader): bulk-by-id Reader methods (RFC §A3)
pquerna May 26, 2026
deb63a4
feat(reader): ListGrantsForEntitlements batched RPC (RFC §A4)
pquerna May 26, 2026
f382ae0
feat(reader): ListResources trait filter (RFC §B2)
pquerna May 26, 2026
ae04e32
feat(reader): iter.Seq2 streaming reader methods (RFC §B3)
pquerna May 26, 2026
9aefc0b
fix(lint): silence revive/gosec on RFC §A3/§A4/§B3 additions
pquerna May 26, 2026
44badc4
fix(pebble): close audit gaps + cross-engine parity test
pquerna May 26, 2026
4475c71
fix(pebble): wire GrantExpandable annotation through V2↔V3 (reviewer)
pquerna May 26, 2026
cfea290
fix(pebble): close audit gaps in V2↔V3 translation
pquerna May 27, 2026
14902da
fix(lint): drop unused //nolint:gosec on bounds-checked int→uint conv…
pquerna May 27, 2026
5ab08c7
fix(stats-sidecar): log the failure instead of swallowing the error
pquerna May 27, 2026
241db08
chore(proto): drop \`reserved 1, 2\` from PayloadEncoding
pquerna May 27, 2026
5052341
chore(proto): drop dead manifest fields + renumber PayloadEncoding
pquerna May 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 6 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ jobs:
uses: golangci/golangci-lint-action@v9
with:
skip-cache: true
version: latest
# Pinned so lint findings don't drift with new golangci-lint
# releases. New gosec rules (G702/G118/G122/G703/G704) landed
# in v2.10+, breaking PRs that were clean against earlier
# versions. Bump deliberately, fixing any new findings in the
# same PR.
version: v2.12.2
args: --timeout=3m
go-test:
strategy:
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ jobs:
uses: golangci/golangci-lint-action@v9
with:
skip-cache: true
version: latest
# Pinned. See ci.yaml note: a `latest` golangci-lint version
# bump can retroactively break PRs and main when new rules
# land. Update deliberately and fix new findings in the same PR.
version: v2.12.2
args: --timeout=3m
go-test:
strategy:
Expand Down
5 changes: 5 additions & 0 deletions buf.gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ plugins:
- remote: buf.build/bufbuild/validate-go:v1.2.1
out: pb
opt: paths=source_relative
- local: protoc-gen-go-vtproto
out: pb
opt:
- paths=source_relative
- features=marshal+unmarshal+size
126 changes: 126 additions & 0 deletions cmd/baton-fixture-gen/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//go:build batonsdkv2

// Command baton-fixture-gen generates synthetic hyper-scale c1z3
// fixtures for the storage-engine benchmark + equivalence harnesses
// (RFC 0004 Stack 5).
//
// Usage:
//
// baton-fixture-gen \
// -out /tmp/fixture-1m-grants \
// -grants 1000000 \
// -entitlements 1000 \
// -principals 50000 \
// -sync-id 00000000000000000000000001
//
// The output is a fresh Pebble directory containing one sync's worth
// of GrantRecord rows + indexes. Run baton-storage-bench against this
// fixture to time the canonical reader workloads.
//
// Determinism: with the same -seed the same fixture is produced
// byte-for-byte (modulo Pebble's compaction non-determinism, which
// affects file layout but not logical content).
package main

import (
"context"
"flag"
"fmt"
"log"
"math/rand"
"os"
"strconv"
"time"

v3 "github.com/conductorone/baton-sdk/pb/c1/storage/v3"
enginepkg "github.com/conductorone/baton-sdk/pkg/dotc1z/engine/pebble"
)

func main() {
out := flag.String("out", "", "destination directory (must not exist)")
grants := flag.Int("grants", 100_000, "total number of grants to generate")
entitlements := flag.Int("entitlements", 100, "number of distinct entitlements")
principals := flag.Int("principals", 1_000, "number of distinct principals")
syncID := flag.String("sync-id", "", "sync ID to use (default: 25-char KSUID)")
seed := flag.Int64("seed", 1, "PRNG seed for deterministic generation")
flag.Parse()

if *out == "" {
log.Fatal("baton-fixture-gen: -out is required")
}
if _, err := os.Stat(*out); err == nil {
log.Fatalf("baton-fixture-gen: %s already exists", *out)
}

if *syncID == "" {
*syncID = "2N7tQT4Yx8z" + strconv.FormatInt(time.Now().Unix(), 10)
// Pad/truncate to 27 chars (KSUID string length).
if len(*syncID) > 27 {
*syncID = (*syncID)[:27]
}
for len(*syncID) < 27 {
*syncID += "0"
}
}

if err := run(*out, *syncID, *grants, *entitlements, *principals, *seed); err != nil {
log.Fatalf("baton-fixture-gen: %v", err)
}
}

func run(outDir, syncID string, totalGrants, numEnt, numPrinc int, seed int64) error {
ctx := context.Background()
e, err := enginepkg.Open(ctx, outDir)
if err != nil {
return fmt.Errorf("open engine: %w", err)
}
defer e.Close()

if err := e.SetCurrentSync(syncID); err != nil {
return fmt.Errorf("SetCurrentSync: %w", err)
}

rng := rand.New(rand.NewSource(seed)) //nolint:gosec // deterministic fixture generation; not security-sensitive
progressEvery := totalGrants / 20
if progressEvery == 0 {
progressEvery = 1
}

start := time.Now()
for i := 0; i < totalGrants; i++ {
entID := fmt.Sprintf("ent-%06d", rng.Intn(numEnt))
principalID := fmt.Sprintf("user-%08d", rng.Intn(numPrinc))
ext := fmt.Sprintf("grant-%010d", i)

r := v3.GrantRecord_builder{
SyncId: syncID,
ExternalId: ext,
Entitlement: v3.EntitlementRef_builder{
ResourceTypeId: "app",
ResourceId: fmt.Sprintf("app-%d", rng.Intn(10)),
EntitlementId: entID,
}.Build(),
Principal: v3.PrincipalRef_builder{
ResourceTypeId: "user",
ResourceId: principalID,
}.Build(),
}.Build()

if err := e.PutGrantRecord(ctx, r); err != nil {
return fmt.Errorf("PutGrantRecord[%d]: %w", i, err)
}

if i%progressEvery == 0 {
elapsed := time.Since(start)
rps := float64(i) / elapsed.Seconds()
fmt.Fprintf(os.Stderr, "[%5.1f%%] %d/%d grants %.0f r/s elapsed=%v\n",
100*float64(i)/float64(totalGrants), i, totalGrants, rps, elapsed.Round(time.Millisecond))
}
}

elapsed := time.Since(start)
rps := float64(totalGrants) / elapsed.Seconds()
fmt.Fprintf(os.Stderr, "done: %d grants in %v (%.0f r/s)\n",
totalGrants, elapsed.Round(time.Millisecond), rps)
return nil
}
187 changes: 187 additions & 0 deletions cmd/baton-storage-bench/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
//go:build batonsdkv2

// Command baton-storage-bench is the canonical benchmark harness for
// the v3 Pebble storage engine (RFC 0004 §6, Stack 5).
//
// Targets the goal table from RFC §6:
//
// G1 Bulk write grants from a single sync_run
// G2 Random Get by primary key
// G3 List by entitlement
// G4 List by principal
// G5 Full-sync scan
// G6 Cold open + Checkpoint
// G7 Cross-engine compaction (delegated to synccompactor/pebble)
// G8 Memory ceiling under burst
// G9 Save → Open → Read roundtrip
// G10 Engine open count for the multi-engine ReaderCache
//
// Stack 5 MVP implements G1–G5 (the read/write hot path). G6–G10 land
// alongside Stack 5 follow-up commits as the canonical fixture and
// cmd/c1z-bench expand.
//
// Usage:
//
// # generate a fixture first:
// baton-fixture-gen -out /tmp/fix-1m -grants 1000000 \
// -entitlements 1000 -principals 50000
//
// # then bench:
// baton-storage-bench -input /tmp/fix-1m -sync-id ... -bench G3 -duration 30s
package main

import (
"context"
"flag"
"fmt"
"log"
"math/rand"
"os"
"time"

v3 "github.com/conductorone/baton-sdk/pb/c1/storage/v3"
enginepkg "github.com/conductorone/baton-sdk/pkg/dotc1z/engine/pebble"
)

func main() {
input := flag.String("input", "", "input Pebble directory (built by baton-fixture-gen)")
syncID := flag.String("sync-id", "", "sync_id to bench against")
benchName := flag.String("bench", "G3", "benchmark to run: G2|G3|G4|G5")
duration := flag.Duration("duration", 10*time.Second, "minimum total benchmark duration")
entitlements := flag.Int("entitlements", 100, "matches the fixture's -entitlements")
principals := flag.Int("principals", 1000, "matches the fixture's -principals")
flag.Parse()

if *input == "" {
log.Fatal("baton-storage-bench: -input is required")
}
if *syncID == "" {
log.Fatal("baton-storage-bench: -sync-id is required")
}

ctx := context.Background()
e, err := enginepkg.Open(ctx, *input, enginepkg.WithReadOnly(true))
if err != nil {
log.Fatalf("open engine: %v", err)
}
defer e.Close()
if err := e.SetCurrentSync(*syncID); err != nil {
_ = e.Close()
fmt.Fprintf(os.Stderr, "SetCurrentSync: %v\n", err)
os.Exit(1) //nolint:gocritic // close already invoked above
}

switch *benchName {
case "G2":
runG2(ctx, e, *syncID, *duration)
case "G3":
runG3(ctx, e, *syncID, *entitlements, *duration)
case "G4":
runG4(ctx, e, *syncID, *principals, *duration)
case "G5":
runG5(ctx, e, *syncID, *duration)
default:
log.Fatalf("unknown bench %q (G2|G3|G4|G5 supported)", *benchName)
}
}

// G2 — random Get by primary key.
func runG2(ctx context.Context, e *enginepkg.Engine, syncID string, dur time.Duration) {
rng := rand.New(rand.NewSource(1)) //nolint:gosec // deterministic bench seed
deadline := time.Now().Add(dur)
var ops, hits, misses int64

start := time.Now()
for time.Now().Before(deadline) {
ext := fmt.Sprintf("grant-%010d", rng.Intn(1_000_000))
_, err := e.GetGrantRecord(ctx, syncID, ext)
ops++
if err == nil {
hits++
} else {
misses++
}
}
elapsed := time.Since(start)
report("G2 Get-by-PK", elapsed, ops, hits, misses)
}

// G3 — list by entitlement.
func runG3(ctx context.Context, e *enginepkg.Engine, syncID string, numEnt int, dur time.Duration) {
rng := rand.New(rand.NewSource(2)) //nolint:gosec // deterministic bench seed
deadline := time.Now().Add(dur)
var ops, rows int64

start := time.Now()
for time.Now().Before(deadline) {
entID := fmt.Sprintf("ent-%06d", rng.Intn(numEnt))
var got int
err := e.IterateGrantsByEntitlement(ctx, syncID, entID, func(*v3.GrantRecord) bool {
got++
return true
})
if err != nil {
fmt.Fprintf(os.Stderr, "iter error: %v\n", err)
os.Exit(1)
}
ops++
rows += int64(got)
}
elapsed := time.Since(start)
report("G3 ListByEntitlement", elapsed, ops, rows, 0)
}

// G4 — list by principal.
func runG4(ctx context.Context, e *enginepkg.Engine, syncID string, numPrinc int, dur time.Duration) {
rng := rand.New(rand.NewSource(3)) //nolint:gosec // deterministic bench seed
deadline := time.Now().Add(dur)
var ops, rows int64

start := time.Now()
for time.Now().Before(deadline) {
pid := fmt.Sprintf("user-%08d", rng.Intn(numPrinc))
var got int
err := e.IterateGrantsByPrincipal(ctx, syncID, "user", pid, func(*v3.GrantRecord) bool {
got++
return true
})
if err != nil {
fmt.Fprintf(os.Stderr, "iter error: %v\n", err)
os.Exit(1)
}
ops++
rows += int64(got)
}
elapsed := time.Since(start)
report("G4 ListByPrincipal", elapsed, ops, rows, 0)
}

// G5 — full-sync scan.
func runG5(ctx context.Context, e *enginepkg.Engine, syncID string, dur time.Duration) {
deadline := time.Now().Add(dur)
var ops, rows int64

start := time.Now()
for time.Now().Before(deadline) {
var got int
err := e.IterateGrantsBySync(ctx, syncID, func(*v3.GrantRecord) bool {
got++
return true
})
if err != nil {
fmt.Fprintf(os.Stderr, "iter error: %v\n", err)
os.Exit(1)
}
ops++
rows += int64(got)
}
elapsed := time.Since(start)
report("G5 FullSyncScan", elapsed, ops, rows, 0)
}

func report(name string, elapsed time.Duration, ops, a, b int64) {
opsPerSec := float64(ops) / elapsed.Seconds()
nsPerOp := float64(elapsed.Nanoseconds()) / float64(ops)
fmt.Fprintf(os.Stdout, "%s: %d ops in %v %.0f ops/s %.0f ns/op a=%d b=%d\n",
name, ops, elapsed.Round(time.Millisecond), opsPerSec, nsPerOp, a, b)
}
Loading
Loading