Skip to content

Filters and dimensions

Sometimes a single total per customer per metric is not enough. You want to know how much compute ran in each cloud region, or how many API calls came from each feature, or how much data was transferred in each product tier. Filters are how you answer questions like these without creating a separate metric for every combination.

When you register a metric, you can declare one or more dimensions. A dimension is a name for a tag, like region or feature. Each event you send can carry properties, which are name and value pairs attached to that specific event. If an event has a property whose name matches a declared dimension, Unimeter keeps a second running total just for that dimension value. Later you query with the same property name and value, and Unimeter returns the slice of usage that matched.

Imagine you run a platform that lets customers spin up compute workloads across three clouds. You track compute seconds, and you want to know at the end of the month how many seconds ran on AWS versus GCP versus Azure for each customer. You define a single metric called compute_seconds with aggregation type sum and one dimension called provider whose allowed values are aws, gcp, and azure. Now every event you send for this metric carries the workload’s duration as its value and includes a property named provider with whichever cloud ran the job.

Unimeter keeps two kinds of totals internally. There is the total across all providers, which is what you get when you query without specifying any filter. And there is a per-provider total, one for each value you tagged. When you query for provider=aws, Unimeter returns just the AWS slice. When you query for provider=gcp, you get the GCP slice. When you query without a filter, you get the sum of all three.

This works because Unimeter computes a hash of the property name and value when the event arrives and uses that hash as part of the storage key. Events that share the same tag end up in the same bucket. Queries compute the same hash and look in the same bucket. The math lines up.

A metric can have more than one dimension. You might register compute_seconds with both provider and region dimensions, where region can be values like us-east, us-west, or europe. Events can carry both properties, and Unimeter keeps separate totals per dimension value. You can query provider=aws and get all AWS compute across all regions, or query region=us-east and get all US-East compute across all providers.

When an event matches two or more dimensions, Unimeter stores an additional combined aggregate alongside the per-dimension ones. This lets you query for provider=aws AND region=us-east and get back a single number representing only the events that matched both conditions at once.

Under the hood, each per-dimension aggregate has a hash computed from the dimension name and value. The combined aggregate has a hash that is the XOR of all per-dimension hashes. The query computes the same combined hash and looks it up directly, so AND queries are just as fast as single-dimension queries.

For an event that carries provider=aws and region=us-east on a metric with both dimensions declared, Unimeter creates four aggregates: the unfiltered total, the provider=aws slice, the region=us-east slice, and the provider=aws AND region=us-east combined slice. Each is updated in a single pass when the event arrives.

If an event matches only one of the declared dimensions, no combined aggregate is created for that event. This keeps storage overhead minimal when events do not carry all possible properties.

If an event has a property whose name is not declared as a dimension on its metric, Unimeter ignores it. The event is stored and counted toward the overall total, but no per-property slice is created. This means you can attach arbitrary metadata to events for audit purposes without having to commit to slicing by it up front. When you do decide a tag is interesting, update the metric schema to include that dimension and future events will start contributing to the sliced total.

When to use filters versus separate metrics

Section titled “When to use filters versus separate metrics”

Filters shine when the different slices share the same unit and the same pricing logic, and you just want to know the breakdown. They are much cheaper than defining five separate metrics and much less painful to query because one metric code gives you both the overall total and the slices.

Separate metrics are the right answer when the business treats the categories as genuinely different things. Inbound bandwidth and outbound bandwidth might have different prices and be billed as distinct line items. In that case they are two metrics, not one metric with a direction dimension.

See Send events for the exact syntax of attaching properties to events in your Go code. For defining metric schemas that include dimensions, read Define metrics.