exp(targeting): bench six exposure-storage shapes for fcap_keys[]#106
Open
bokelley wants to merge 1 commit into
Open
exp(targeting): bench six exposure-storage shapes for fcap_keys[]#106bokelley wants to merge 1 commit into
bokelley wants to merge 1 commit into
Conversation
…a model
Six storage variants benched end-to-end against real valkey:
- binary: generalized fixed-stride byte slab (current shape, fcap_keys-extended)
- zset-array: one ZSET per user, member encodes [imp_hash, fcap_keys[]]
- zset-perkey: one ZSET per user, K members per impression
- zset-perkeyed: one ZSET per (user, fcap_key) pair
- bucket-day: SET per (user, day) — singleton-per-period rule shape
- bucket-count: HASH per (user, day) — count-per-period rule shape
Sanity test verifies all rolling-window variants compute identical answers
under the same workload; bucket variants are excluded from that test
because they answer a different rule semantic (calendar bucket vs. rolling).
Findings (heavy 30K-impression user, 30-day retention):
- Binary log loses on writes by 100+× due to read-modify-write per impression
- ZSET wire-size advantage shrinks at 30d rules (no server-side window filter benefit)
- ZSET-perkeyed wins single-key reads; ZSET-array wins large-batch
- bucket-day/bucket-count win every dimension when rules fit calendar buckets:
reads flat across batch sizes (single SMEMBERS/HGETALL), 6-21× smaller memory,
free TTL-driven cleanup
Bench package is its own go module to keep redis client deps out of the main
module. Run with `VALKEY_ADDR=host:port go test ./targeting/expbench/`.
Exploratory work for the fcap_keys generalization (issue #104). Not intended
to be production code; kept in-tree as durable comparison artifact.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Empirical comparison of six storage shapes for the per-user exposure log under the fcap_keys[] data model proposed in #104. Sanity test verifies all rolling-window variants compute identical eligibility answers; bucket variants implement different rule semantics (calendar bucket vs. rolling) and are tested separately.
Run against real valkey 7.2 (docker), six variants, single-client local, 200 iterations per measurement.
Variants
targeting/exposure_binary.goshape, extended to carry up to 8 fcap_key hashes per impression[imp_hash, fcap_key_hashes[]](user, fcap_key)pair — single-key reads only fetch the relevant data(user, day)— singleton-per-period rule shape (the AppNexus model)(user, day)— count-per-period rule shapeHeadline finding
The fcap rule shape matters more than the storage shape. Splitting rules into bucket-per-period (90% of real rules) vs. rolling-window (10%) collapses most of #104's complexity:
singleton:{day,week,month,lifetime}count:{day,week,month}rolling:N:NsBinary log loses on writes by 100+× — the read-modify-write cost on every impression is structural, not optimizable. Goes away in the proposed split.
ZSET wire-size advantage shrinks at 30d rules — 24h rule benchmarks understated the worst case by ~10×; 30d caps pull the entire log because there's no server-side window filter to skip.
Full results table and analysis posted on #104.
What this is — and isn't
targeting/expbench/go.mod) so the redis client dep doesn't leak into the main module.Running
Test plan
TestSanity_Equivalence)Decision pending on #104
Issue comment proposes the AppNexus-style split as the new scope: schema declares
rule_shapeper fcap_key, two thin stores (BucketStore + RollingStore), engine dispatches by shape, binary log apparatus deleted entirely. This PR is the data behind that proposal.Related: #104, adcontextprotocol/adcp#3359
🤖 Generated with Claude Code