feat(property-vals): aggregate property values per event behind flags#61322
Conversation
|
| #[test] | ||
| fn stamped_event_tuples_carry_source_event_name(name in "[a-z]{1,20}", blob in arb_blob()) { | ||
| let e = Event { | ||
| team_id: 2, | ||
| event: Some(name.clone()), | ||
| properties: Some(blob), | ||
| person_properties: None, | ||
| }; | ||
| for (t, _) in fan_out(&e, &none(), true) { | ||
| prop_assert_eq!(&t.event_name, &name); | ||
| } | ||
| } |
There was a problem hiding this comment.
Missing coverage for
stamp_event_name=true + event.event=None
stamped_event_tuples_carry_source_event_name always constructs events with event: Some(name). The silent fall-through when event.event is None (.unwrap_or("")) is correct, but no proptest covers fan_out(&e, &none(), true) with an arb_event() that can produce event: None, which would leave event_name="" in the key even with the flag on. Adding a parameterised case that uses arb_event() with stamp_event_name=true and asserts that event-type tuples carry event_name matching event.event.as_deref().unwrap_or("") would make this contract explicit.
Prompt To Fix With AI
This is a comment left during a code review.
Path: rust/property-vals-rs/src/fan_out.rs
Line: 264-275
Comment:
**Missing coverage for `stamp_event_name=true` + `event.event=None`**
`stamped_event_tuples_carry_source_event_name` always constructs events with `event: Some(name)`. The silent fall-through when `event.event` is `None` (`.unwrap_or("")`) is correct, but no proptest covers `fan_out(&e, &none(), true)` with an `arb_event()` that can produce `event: None`, which would leave `event_name=""` in the key even with the flag on. Adding a parameterised case that uses `arb_event()` with `stamp_event_name=true` and asserts that event-type tuples carry `event_name` matching `event.event.as_deref().unwrap_or("")` would make this contract explicit.
How can I resolve this? If you propose a fix, please make it concise.
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 1 · PR risk: 0/10 |
Adds an event_name to each aggregated tuple so event property values can be looked up scoped to the event they appear on. The events worker keys event- type values on the source event name; person and group values are never event-scoped and stay empty. Oversized event names are dropped, not stamped across many rows. Two flags decouple this from the ClickHouse schema, both default off: - AGGREGATE_BY_EVENT_NAME keys values on the event, producing the full amplified record count (each value splits into one row per event). - EMIT_EVENT_NAME controls whether the merger writes event_name to the output ClickHouse reads. With AGGREGATE on and EMIT off, the pipeline produces the amplified count in the old wire format, so the existing kafka table ingests it and ClickHouse collapses the duplicates, letting the real write amplification be measured on prod before the ClickHouse column exists. The aggregation, merger seen-cache, and top-K cap all key on event_name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
81334a8 to
115b38d
Compare
8f9f980
into
andy/property-vals-value-len-config
Problem
We want property-value autocomplete to be scopable to a specific event (filtering "events where event = X and property = Y"). For that, the value catalog needs to know which event each value appeared on. This is the service half of that change: it threads the source event name through the aggregation pipeline so a later ClickHouse column can support per-event value lookups.
It is gated and ships dark, so it is safe to land and deploy ahead of the ClickHouse side.
Changes
event_namebecomes part of the in-memory aggregation key, the merger's emit-once seen cache, and the top-K cap, so event values aggregate and cap per(event, key)rather than perkey.AGGREGATE_BY_EVENT_NAMEcontrols whether the event name enters the aggregation key. On, event values fragment per event (this is what drives the write amplification). Off, event values carry an empty name like person/group.EMIT_EVENT_NAMEcontrols whether the merger writes theevent_namefield on the output topic. The field is omitted when empty (serde(skip_serializing_if)), so with this off the messages toclickhouse_property_valuesare byte-identical to the pre-event_nameformat.Splitting the two flags lets us exercise the write-amplification behavior before the ClickHouse column exists. Turn
AGGREGATE_BY_EVENT_NAMEon withEMIT_EVENT_NAMEoff to produce the same fragmented record count (and the same write throughput) the finished feature will, while the wire stays in the old format so the current ClickHouse kafka table keeps consuming it unchanged. Once the ClickHouse column is live, turnEMIT_EVENT_NAMEon too.Rollout order: deploy the ClickHouse PR, deploy this service with both flags off, optionally flip
AGGREGATE_BY_EVENT_NAMEalone to load-test throughput, then flipEMIT_EVENT_NAMEon.One thing to weigh: aggregating per event lowers the collapse ratio, so write throughput to ClickHouse rises (roughly 2-6x at the flush cadence depending on a team's event diversity). Lengthening
FLUSH_INTERVAL_SECSis the lever if that is too much.How did you test this code?
I am an agent (Claude Code). No manual testing. On the
property-vals-rscrate:cargo fmt,cargo clippy, andcargo test(54 passed). New tests cover:event_nameformat) and round-trips back to emptyevent_namethrough from the intermediate messageAutomatic notifications
Docs update
No docs change.
🤖 Agent context
Authored with Claude Code (Read/Edit/Bash) while working through property-value search performance.
Decisions: the two flags (
AGGREGATE_BY_EVENT_NAME,EMIT_EVENT_NAME) are split so the per-event aggregation grain, and its write amplification, can be exercised on production before the consuming ClickHouse column exists, which decouples the two deploys. The output field is omitted when empty so flag-off messages stay byte-identical to the old format. Person and group are intentionally left unscoped because their values are not event-specific (groups are not on the event stream at all). The aggregation key, merger seen cache, and top-K cap all key onevent_nameso the per-event grain is consistent end to end. The ~2-6x write amplification was measured and is called out as a rollout consideration, not a blocker, since it only bites once aggregation is on.Stacked on the value-length base PR: #61325