feat: add type and outcome event dimensions - #30
Conversation
Two metric-scoped dimensions, so a metric can be broken down by what the
row is about and how the attempt ended without encoding either into the
metric name.
They replace the pattern where a category becomes part of the name and the
consumer parses it back out — auth.method.phone.{countryCode} is ~190
metric names for one concept, and messages.{type}.{provider}.{sent|failed}
is a cross-product of nine names built from three numbers.
Both are read with `metric` pinned, so the same column carries different
value spaces across metrics — a messaging channel for one, a calling code
for another. That is the shape resourceId already has, where a bucket id
and a function id share a column scoped by resourceType.
set(0) indexed like the other small closed value spaces, and
LowCardinality since each metric contributes only a handful of values.
Gauges are unchanged: GAUGE_COLUMNS does not include them, so the gauge
path still rejects both as unknown tags.
Greptile SummaryThe PR adds metric-scoped
Confidence Score: 4/5The PR is not yet safe to merge because upgraded Database-backed installations do not add the new attributes before tagged events are written. Existing Database collections bypass schema creation after the duplicate-collection result, while the write path now emits category and outcome attributes, leaving upgraded deployments unable to persist the new event shape reliably. Files Needing Attention: src/Usage/Adapter/Database.php and src/Usage/Metric.php Important Files Changed
Reviews (3): Last reviewed commit: "Merge branch 'main' into feat/type-outco..." | Re-trigger Greptile |
CI caught a real collision. The SQL adapter writes a structural `type`
column holding 'event' or 'gauge', and builds its document as
array_merge(['type' => $type, ...], $columns)
with $columns coming from extractColumns(). A dimension named `type` lands
in $columns, which is the second argument, so it silently overwrote the
metric type — event rows were written as type='sms' instead of
type='event'. That is what the 40 DatabaseTest errors were.
Renamed to `category`, which is free in both adapters. `outcome` was never
in conflict.
Also updated the low-cardinality list in ClickHouseSchemaTest, which keeps
its own copy of the adapter's list — that duplication is why the schema
assertion failed separately from the collision.
|
CI caught a real bug — thanks, tests. Fixed in cd214da. The collision: the SQL adapter writes a structural array_merge(['type' => $type, /* ... */], $columns) // Adapter/Database.php:175
Renamed the dimension to Verified the merge now preserves the structural column: The separate schema-assertion failure was a second issue: Worth noting for the record: I flagged in the PR description that the ClickHouse e2e suite hadn't run locally (no Docker here) and that execution was CI's job. It was, and it earned its keep — neither of these was reachable from the schema/query-construction checks I could run. |
| $stringColumn('category', 64), | ||
| $stringColumn('outcome', 32), |
There was a problem hiding this comment.
Existing Database schemas stay stale
If a Database-backed installation already has its event collection, Database::setup() swallows the duplicate-collection result without adding these new attributes, while addBatch() starts including them in documents. Consequently, upgraded deployments cannot reliably persist events containing category or outcome.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/Usage/Metric.php
Line: 683-684
Comment:
**Existing Database schemas stay stale**
If a Database-backed installation already has its event collection, `Database::setup()` swallows the duplicate-collection result without adding these new attributes, while `addBatch()` starts including them in documents. Consequently, upgraded deployments cannot reliably persist events containing `category` or `outcome`.
**Knowledge Base Used:**
- [Usage domain model](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/utopia-php/usage/-/docs/usage-domain-model.md)
- [ClickHouse usage adapter](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/utopia-php/usage/-/docs/clickhouse-usage-adapter.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Adds two metric-scoped event dimensions:
typeandoutcome.Why
Today a category has to be encoded into the metric name and parsed back out by the consumer:
auth.method.phone.{countryCode}— ~190 metric names for one concept. Billing can't group by it, so it enumerates every calling code into anINlist.messages.{type}.{provider}[.sent|.failed]— a cross-product of nine names built from three numbers (recipients, delivered, failed), at three granularities.webhooks.events.{sent|failed}— the same outcome split again.With these two columns, the first becomes
GROUP BY metric, typeand the second collapses to two rows taggedtype+outcome.Metric-scoped by design
Both are read with
metricpinned, so the same column carries different value spaces across metrics — a messaging channel for one, a calling code for another. That's the shaperesourceIdalready has, where a bucket id and a function id share one column scoped byresourceType.Deliberately not reusing
status: it holds HTTP status, andsent/failedsort above400lexicographically, so an error filter (greaterThanEqual("status", "400")) would silently match every message row.Details
set(0)indexed, like the other small closed value spaces (status,method,service,clientType).setsupports ranges as well as equality, unlikebloom_filter.LowCardinality(Nullable(String))— each metric contributes a handful of values.type64 /outcome32.GAUGE_COLUMNSdoesn't include them, so the gauge path still rejects both as unknown tags — verified.setup()already emitsALTER TABLE … ADD COLUMN IF NOT EXISTS, so existing tables pick them up without a migration.Verification
Confirmed end to end, since
extractColumns()is strict (unknown tags throw rather than being dropped):And the query shapes the consumers need actually compile:
MetricTest::testEventColumnsConstantpins the column list and failed until updated — that guard did its job. Unit/schema/column-type tests green (56 tests, 288 assertions), Pint clean, PHPStan level max clean.Not run: the ClickHouse e2e suite — no Docker daemon on this box. The DDL and query construction are verified above; actual execution against ClickHouse is CI's.