feat(eap): Resolve session_id and gen_ai.conversation.id to their indexed columns#8185
feat(eap): Resolve session_id and gen_ai.conversation.id to their indexed columns#8185pbhandari wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
I'm not really sure about this file tbh. Feel bad checking something in without tests, and this was very helpful during local development. But I'm not sure it rises to the level of "useful enough to be checked in"
There was a problem hiding this comment.
It's useful to verify we're using the right column. I'd keep it.
phacops
left a comment
There was a problem hiding this comment.
The caveat with this PR is once we enable this, we won't be able to select conversations that are not in the index. Make sure the team knows about it. We've been writing conversations for about 3 weeks.
There was a problem hiding this comment.
It's useful to verify we're using the right column. I'd keep it.
1d71dfc to
a8ac385
Compare
a8ac385 to
f623713
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f623713. Configure here.
| # (empty) value from a real one. | ||
| base = field.parameters[0] | ||
| if isinstance(base, Column) and base.column_name in EMPTY_STRING_DEFAULT_COLUMNS: | ||
| return f.notEmpty(field) |
There was a problem hiding this comment.
Null filters disagree with exists
Medium Severity
exists on gen_ai.conversation.id now treats the empty-string default as absent via notEmpty, but = / != null comparisons still go through isNull on the non-nullable column. Unset rows therefore fail exists while still satisfying != null (and never match = null), so null filters no longer align with existence checks after the map-to-column move.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit f623713. Configure here.
| sentry_column("session_id"): NormalizedColumn("session_id", [AttributeKey.Type.TYPE_STRING]), | ||
| "gen_ai.conversation.id": NormalizedColumn( | ||
| "ai_conversation_id", [AttributeKey.Type.TYPE_STRING] |
There was a problem hiding this comment.
Bug: The session_id column, a UUID, is cast to a String during queries. This CAST operation prevents the ClickHouse bloom filter index from being used, negating performance gains.
Severity: MEDIUM
Suggested Fix
Register session_id with its native UUID type instead of TYPE_STRING. This would likely involve adding a TYPE_UUID to AttributeKey.Type and mapping it correctly to the ClickHouse UUID type. This ensures that queries against session_id do not involve a type cast, allowing the bloom filter index to be utilized effectively for performance.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: snuba/protos/common.py#L61-L63
Potential issue: The `attribute_key_to_expression` function registers the `session_id`
column with `AttributeKey.Type.TYPE_STRING`. This causes queries to wrap the column in
`CAST(session_id, 'String')`. Since `session_id` is a native `UUID` column in ClickHouse
with a bloom filter index, this cast prevents the database from using the index to prune
granules. This defeats the purpose of the bloom filter, leading to inefficient queries
that scan more data than necessary and degrading query performance. Although tests pass,
this is likely due to the test environment, and the optimization will probably be
ineffective in production.
Also affects:
snuba/protos/common.py:337~341


sentry.session_idandgen_ai.conversation.idnow resolve to their dedicated top-level columns (session_id,ai_conversation_id) instead of theattributes_string[...]map, so=/IN <literal>filters on them prunegranules through the
bf_session_id/bf_ai_conversation_idbloom-filter skip indexes.The change is in the shared
attribute_key_to_expressionresolver, so every EAP endpoint (/events/TraceItemTable, TimeSeries, TraceItemStats, GetTraces, GetTrace, TraceItemDetails, ExportTraceItems) gets it automatically.To allow this, we refactored
NORMALIZED_COLUMNS_EAP_ITEMSso that it now maps an attribute name to an explicitNormalizedColumn(column_name, types)named-tuple rather than deriving the column by stripping thesentry.prefix. That is to facilitategen_ai.conversation.idto map toai_conversation_id.We also removed the
COLUMN_PREFIXconstant, as it's no longer serving it's original purpose. The lonesentry.timestampcheck that used it is inlined.Review focus — absence semantics: a missing value for used to read as
NULL(map miss) and now reads as the column default (''for the Stringai_conversation_id, the zero UUID forsession_id). This matches how existing normalized columns such assentry.trace_idalready behave. We expect the columns to already have been populated where appropriate.tests/web/rpc/v1/test_indexed_columns.pyruns each filter through a realTraceItemTablequery and asserts viaEXPLAIN indexes = 1that the expected skip index is applied, and that the returned rows are correct.Related PRs
Refs: https://linear.app/getsentry/issue/EAP-620
🤖 Generated with Claude Code