Skip to content

feat(bench): enable the eql_v3 OPE ordering scenario#22

Closed
freshtonic wants to merge 4 commits into
feat/eql-v3-benchmarksfrom
feat/cip-3348-ope-bench
Closed

feat(bench): enable the eql_v3 OPE ordering scenario#22
freshtonic wants to merge 4 commits into
feat/eql-v3-benchmarksfrom
feat/cip-3348-ope-bench

Conversation

@freshtonic

Copy link
Copy Markdown
Contributor

Closes CIP-3348.

Enables the ORE_V3 family's _ord_ope scenario stub (was TODO(CIP-3280)). Evidence: cipherstash-client 0.38.1 emits the scalar op (OPE-CLLW) term (0.38.0 did not), so the scenario is now drivable end-to-end.

Stacking

Stacked on #21 (feat/eql-v3-benchmarks, which carries the v3 bench machinery this builds on) and — via a merge of chore/cipherstash-client-0.38 — on #20 (the cipherstash-client/stack-profile =0.38.1 bump this scenario depends on). Merge after both.

Note: the task originally called for basing directly on #20, but the ore_v3 stub, schema-v3.sql and the rest of the v3 machinery only exist on #21's branch, so this PR stacks on #21 and merges #20's 0.38.1 bump in.

What's in here

  1. Merge of origin/chore/cipherstash-client-0.38 — brings the =0.38.1 pins (this branch's scenarios require the op term).
  2. refactor(eql-v3): int4_*integer_* domain rename sweep. The stub predates the EQL v3 scalar-family rename; the current eql_v3 schema ships eql_v3.integer_ord_ope / eql_v3.integer_ord_ore, and TargetDomain::parse in eql-bindings ≥ 0.3.0 resolves against the renamed catalog (no bindings version accepts both spellings). Since the OPE and ORE scenarios share one bench binary, enabling OPE with current names required sweeping the ORE side too. ⚠️ This makes the branch require eql-bindings ≥ 0.3.0 — the pending local commit pinning crates.io eql-bindings =0.2.0 (int4-era names) would need to pin ≥ 0.3.0 instead (0.4.2 is published and verified).
  3. feat(bench): the OPE scenario
    • integer_ope_encrypted_v3 (+ 10k/100k/1M/10M tiers) typed eql_v3.integer_ord_ope in schema-v3.sql
    • per-tier btree (eql_v3.ord_ope_term(value)) index up/down scripts under sql/indexes/v3/
    • encrypt_int_ope_v3 ingest bin (encrypt config: ope index via Index::new_ope(), conversion target integer_ord_ope)
    • OPE_QUERY_TEMPLATES enabled in benches/ore_v3.rs (ope_range_gt_10, ope_range_lt_ordered_10), same thresholds as the ORE twins so the two ordering implementations report side-by-side in the one ORE_V3 group
    • mise tasks (prepare:v3:integer_ope_encrypted, bench:ingest:encrypt_int_ope:v3; bench:query:ore:v3 now preps both tables) and reporter/README entries

Verification

  • cargo build --benches --bins, cargo clippy --benches --bins -- -D warnings, cargo fmt --check, cargo test --lib — all clean (eql-bindings 0.4.2, cipherstash-client 0.38.1).
  • Live DDL check against a scratch database on the local benches Postgres with a current EQL v3 installer build: installer + schema-v3.sql + all OPE index up/down scripts apply cleanly, and EXPLAIN on the enabled shapes shows ope_range_lt_ordered_10 doing an Index Scan on the ord_ope_term btree with the ORDER BY streaming from the index (no Sort node); the bare-form > operator inlines to the ord_ope_term expression as designed.
  • Not verifiable without credentials/data: an actual benchmark run (ingest requires ZeroKMS credentials; timings require populated tiers). The end-to-end claim that the client-emitted op payload round-trips through from_v2 into a queryable stored row is exercised the first time mise run bench:query:ore:v3 10000 runs.

Whole cipherstash-suite lockstep moves to 0.38.1 (stack-auth,
cts-common, cipherstash-config, cipherstash-core; zerokms-protocol
stays 0.12.20). The client's EQL wire-format module is byte-identical
across 0.34.1-alpha.9, 0.38.0, and 0.38.1 (verified by diffing the
registry sources), so the EQL SQL pin stays at eql-2.3.0.
build/clippy/fmt/test all clean with --locked.
…nto feat/cip-3348-ope-bench

# Conflicts:
#	Cargo.toml
…ly rename)

The eql_v3 scalar domain families were renamed (int4 -> integer,
int8 -> bigint, ...) upstream; eql-bindings >= 0.3.0 resolves
TargetDomain::parse against the renamed catalog, so the old int4_*
names fail at runtime. Sweep every int4_ord_ore / int4_eq reference
in code, SQL, mise tasks, reporters and docs to the current names.
The stale int4_ord_ope references in the CIP-3280 stub comments are
rewritten in the follow-up commit that enables the scenario.
Enable the ORE_V3 family's `_ord_ope` stub (was TODO(CIP-3280)):
cipherstash-client 0.38.1 emits the scalar OPE-CLLW `op` term, so the
scenario is now drivable end-to-end.

- sql/schema-v3.sql: add `integer_ope_encrypted_v3` (+ the four
  row-count tiers) typed `eql_v3.integer_ord_ope`
- sql/indexes/v3/: btree (eql_v3.ord_ope_term(value)) up/down scripts
  per tier — ord_ope_term returns a bytea domain, so ordering is native
  bytea comparison (no per-row plpgsql)
- src/bin/encrypt_int_ope_v3.rs: ingest bin mirroring encrypt_int_v3
  but with an `ope` index (Index::new_ope()) and the integer_ord_ope
  conversion target
- benches/ore_v3.rs: enable OPE_QUERY_TEMPLATES (ope_range_gt_10,
  ope_range_lt_ordered_10) against the new tables, sharing thresholds
  with the ORE scenarios so the two ordering implementations report
  side-by-side in the one ORE_V3 group
- mise.toml: prepare:v3:integer_ope_encrypted +
  bench:ingest:encrypt_int_ope:v3 tasks; bench:query:ore:v3 now preps
  both tables
- report_benchmarks.py / README.md: document the new scenarios

Verified against a live Postgres with a current EQL v3 build: the
installer + schema-v3.sql + index scripts apply cleanly, and EXPLAIN
shows the ope_range_lt_ordered_10 shape streaming from the
ord_ope_term btree with no Sort node.
coderdan added a commit that referenced this pull request Jul 4, 2026
…s approach)

Reviewing James's PR #22 (stacked on #21/#20, a parallel v3 bench
implementation) surfaced two adoptable facts: cipherstash-client 0.38.1
emits the scalar OPE-CLLW `op` term via Index::new_ope() (CIP-3280), and
eql-bindings is published on crates.io. Adopt both into this branch
rather than merging the parallel machinery (which duplicates ours under
different table/task names and has no results):

- Bump cipherstash-client + stack-profile to =0.38.1 (v2 wire format
  unchanged); switch eql-bindings from the git branch dep to crates.io
  =0.4.2.
- encrypt_int_ope_v3 + benches/ope_v3.rs now use Index::new_ope() for
  REAL op terms end-to-end; the synthetic-op machinery is removed from
  src/v3.rs (to_v3_stored_with_synth_ope, ingest_v3_synth_ope,
  i32_order_key, SYNTH_OPE_HEX_WIDTH). The startup order-parity gate now
  verifies real crypto ordering.
- OPE tiers re-ingested with real ciphertexts (33-byte op terms, ~2x the
  synthetic width) and all OPE query benches re-run: medians unchanged
  (0.118-0.124 ms flat at every tier) — the headline OPE numbers hold
  with real crypto.
- All v3 ingest benches re-run under 0.38.1: ORE int and string at
  parity with the alpha.9 numbers (client bump doesn't shift existing
  comparisons), and a NEW headline: real OPE ingest runs 10,933 rec/s at
  10k — 5.3x ORE int ingest (2,076 rec/s), since OPE term generation is
  far cheaper client-side than ORE block generation.
- Synthetic-op caveats removed across README, reports, task descriptions.

Claude-Session: https://claude.ai/code/session_01StQnoycoFXMDdSpQ6zKDav
coderdan added a commit that referenced this pull request Jul 4, 2026
…ipeline

Replace the parallel IngestOptions::ingest_v3(f, "<target>") method with
James's versioning-as-configuration shape from PR #21: the builder gains
.convert_to_v3(TargetDomain) and the single ingest() branches on the
stored eql_target. One batch loop instead of a duplicated one, and the
target is a typed TargetDomain resolved at the builder call site (bad
domain names fail at build time, not mid-ingest). TargetDomain is
re-exported from dbbenches::v3 so binaries don't depend on eql-bindings
directly; to_v3_stored_target() is the pre-parsed hot-loop conversion.

The V3_CONVERT_ONLY decomposition flag moves into the Some(target)
branch unchanged. The custom multi-column binaries (encrypt_combo_v3,
encrypt_scalar_smoke_v3) keep calling to_v3_stored directly - they never
used IngestOptions.

Smoked both branches at 100 records each: v2 (None), integer_ord,
integer_ord_ope (real op, 33-byte terms), text_search - all insert
cleanly. This supersedes the ingest side of PRs #21/#22, which can now
be closed (#20's 0.38.1 bump is merged and adopted here).

Claude-Session: https://claude.ai/code/session_01StQnoycoFXMDdSpQ6zKDav
@coderdan

coderdan commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Closing in favour of #23, which has subsumed this PR's substance (agreed with Dan):

Full results, the v2-vs-v3 comparison report, and the OPE-vs-ORE analysis (including the 44s-vs-1s index build measurement at 1M) are on #23 — see report/V3_COMPARISON.md and v3-regressions-report.md there. Thanks James — the 0.38.1 evidence and the real-OPE path were exactly what #23 needed to retire its biggest caveat. Commits 667c303 and 2ef1b9e on #23 credit the specifics.

@coderdan coderdan closed this Jul 4, 2026
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