Problem
eql_v3_internal.jsonb_array_to_bytea_array and eql_v3_internal.jsonb_array_to_ore_block_256 are LANGUAGE sql. In the ore_block_256 btree operator-class call path (index scans, sorts, index builds) SQL functions cannot be inlined, so every invocation pays the per-call SQL-function executor — measured at 3.5× the per-call cost of v2's logic-identical plpgsql equivalents.
This is the root cause of the v2→v3 ORE ordered-scan regression in the release benchmarks (ORE/range_lt_ordered_10: 0.513 → 0.736 ms at 1M, +43%; COMBO/bloom_ore_order_limit: 16.6 → 22.7 ms, +36%). Notably it is NOT the plpgsql comparator — per-query comparator time is identical across v2/v3 (function-level attribution via track_functions), and the v2 vs v3 1M index build times are equal (42 s vs 44 s).
Validated fix
A logic-identical, language-only swap of the two helpers to plpgsql was tested on a live 1M bench database (A→B→A, originals restored):
| Scenario (1M) |
v2 |
v3 shipped |
v3 patched |
| ORE/range_lt_ordered_10 |
0.513 ms |
0.736 ms |
0.553 ms |
| COMBO/bloom_ore_order_limit |
16.64 ms |
22.70 ms |
13.97 ms (16% faster than v2) |
Controls without ORE ordering moved ±2.6% (noise). Implementation note: in plpgsql, SELECT <composite> INTO <composite_var> assigns field-wise — return the row constructor directly.
Even better than plpgsql: cache the decoded bytea representation, or do decode+compare in one C pass — per-query attribution splits ~0.19 ms comparator / ~0.26 ms term re-decoding, so a C comparator that still re-parses jsonb per comparison would leave half the cost in place.
Evidence
Full attribution tables and the A→B→A experiment: v3-regressions-report.md on cipherstash/benches#23 (Issue 3 + the COMBO deep-dive). Related: the ORE-vs-OPE gap (0.74 vs 0.12 ms; both ORE versions pay ~0.4–0.5 ms of tracked function time, the fully-inlined OPE path pays zero) — steering ordered workloads to _ord_ope is the structural alternative now that cipherstash-client 0.38.1 emits op.
Problem
eql_v3_internal.jsonb_array_to_bytea_arrayandeql_v3_internal.jsonb_array_to_ore_block_256areLANGUAGE sql. In theore_block_256btree operator-class call path (index scans, sorts, index builds) SQL functions cannot be inlined, so every invocation pays the per-call SQL-function executor — measured at 3.5× the per-call cost of v2's logic-identical plpgsql equivalents.This is the root cause of the v2→v3 ORE ordered-scan regression in the release benchmarks (
ORE/range_lt_ordered_10: 0.513 → 0.736 ms at 1M, +43%;COMBO/bloom_ore_order_limit: 16.6 → 22.7 ms, +36%). Notably it is NOT the plpgsql comparator — per-query comparator time is identical across v2/v3 (function-level attribution viatrack_functions), and the v2 vs v3 1M index build times are equal (42 s vs 44 s).Validated fix
A logic-identical, language-only swap of the two helpers to plpgsql was tested on a live 1M bench database (A→B→A, originals restored):
Controls without ORE ordering moved ±2.6% (noise). Implementation note: in plpgsql,
SELECT <composite> INTO <composite_var>assigns field-wise — return the row constructor directly.Even better than plpgsql: cache the decoded bytea representation, or do decode+compare in one C pass — per-query attribution splits ~0.19 ms comparator / ~0.26 ms term re-decoding, so a C comparator that still re-parses jsonb per comparison would leave half the cost in place.
Evidence
Full attribution tables and the A→B→A experiment:
v3-regressions-report.mdon cipherstash/benches#23 (Issue 3 + the COMBO deep-dive). Related: the ORE-vs-OPE gap (0.74 vs 0.12 ms; both ORE versions pay ~0.4–0.5 ms of tracked function time, the fully-inlined OPE path pays zero) — steering ordered workloads to_ord_opeis the structural alternative now that cipherstash-client 0.38.1 emitsop.