Naming Spatial Metrics for Cross-Team Reuse
A metric name is an API. Once a dashboard, an alert rule, a runbook and three downstream teams depend on gis.spatial.freshness_seconds, renaming it costs more than the original instrumentation did. Spatial platforms hit this harder than most, because the same underlying quantity gets instrumented independently by the ingestion team, the tile team and the analytics team, each with a plausible name, and six months later nobody can write a rule that covers all three. This guide covers the naming rules that make spatial metrics reusable across teams, how to keep unit and instrument type unambiguous, and how to migrate a badly-named metric without breaking every consumer at once. It belongs to geospatial metric taxonomy for ETL under geospatial observability architecture fundamentals.
Why spatial platforms hit this harder
Three characteristics make naming discipline more valuable here than on a typical service platform.
The first is unit density. A spatial pipeline emits metres, degrees, seconds, ratios, bytes, feature counts and vertex counts, often for closely related quantities. A distance metric that is metres in one subsystem and degrees in another is not a hypothetical; it is the default outcome when the unit is not in the name, and it produces alert thresholds that are wrong by five orders of magnitude.
The second is parallel instrumentation. The same quantity — how stale is this layer — is genuinely observable from the ingestion worker, the database, the tile builder and the serving edge. Each team instruments the view they can see, and without a shared name the platform ends up with four freshness metrics that disagree, none of which anyone trusts enough to alert on.
The third is layer proliferation. Spatial platforms accumulate layers steadily: a new boundary set, a new sensor feed, a new derived product every quarter. Any name that embeds a layer guarantees that coverage lags the catalogue permanently, because instrumenting a new layer requires editing rules rather than simply emitting a new label value.
The rules that actually matter
Four conventions carry nearly all the value; the rest is taste.
State the unit in the name, always. _seconds, _meters, _bytes, _ratio, _total. A spatial platform mixes metres, degrees, seconds and ratios constantly, and a name without a unit guarantees that somebody eventually compares a degree to a metre. This is the rule with the highest cost of omission and the lowest cost of compliance.
Keep the subsystem as its own segment. gis.tile.publish_lag_seconds, not gis.tile_publish_lag_seconds. The separator is what lets a rule, a dashboard, or a retention policy select every metric from one subsystem without enumerating them.
Never bake a dimension into the name. Layer, region, source, zoom and severity are labels. A name containing parcels produces one metric per layer, which means one alert rule per layer, which means new layers silently ship without coverage. This is the same discipline as the cardinality rules in the parent topic, applied in the opposite direction: labels for dimensions, names for quantities.
Make the instrument type inferable. A counter ends in _total, a gauge names a level, a histogram names a measured distribution. A consumer reading gis.join.match_ratio should not need to check the code to know whether to apply rate().
| Convention | Good | Bad |
|---|---|---|
| Unit stated | gis.spatial.freshness_seconds |
gis.spatial.freshness |
| Subsystem separated | gis.tile.publish_lag_seconds |
gis.tile_publish_lag_seconds |
| Dimension as label | gis.etl.features_ingested_total{layer=…} |
gis.etl.parcels_ingested_total |
| Instrument inferable | gis.spatial.topology_error_total |
gis.spatial.topology_errors |
| Domain-specific | gis.raster.nodata_ratio |
gis.raster.bad_pixels |
Implementation: enforce the convention in code review, not in prose
A naming convention that lives only in a document decays. Encode it as a test that runs against the exported metric set.
# test_metric_names.py — the convention as an executable rule.
import re
NAME = re.compile(
r"^gis\." # domain
r"(etl|spatial|tile|raster|join|geocode|contract)\." # known subsystems only
r"[a-z][a-z0-9_]*" # quantity
r"_(seconds|meters|bytes|ratio|total|count|index)$" # explicit unit
)
# Dimensions that must be labels, never name fragments.
FORBIDDEN_FRAGMENTS = ("parcels", "roads", "addresses", "eu_", "us_",
"zoom", "region", "critical", "warning")
def test_exported_metric_names(exported_names):
for name in exported_names:
assert NAME.match(name), f"{name} does not match the naming convention"
for fragment in FORBIDDEN_FRAGMENTS:
assert fragment not in name, f"{name} bakes the dimension '{fragment}' into the name"
Running this against the actual exported set rather than against source code catches the metrics created dynamically by string concatenation, which is where the worst names come from. Adding a new subsystem then requires a deliberate edit to the allow-list, which is exactly the friction you want: a fifth subsystem is a design decision, not an accident of whoever instrumented first.
Migrating a badly-named metric
Renaming is a breaking change to every consumer, so run it the way you would run any other breaking change: emit both, migrate, then retire.
# Phase 1 — emit both names from the same instrument, for one full cycle.
# A recording rule is the cheapest way to alias an existing series.
groups:
- name: metric-aliases
rules:
- record: gis_spatial_freshness_seconds
expr: gis_spatial_freshness # old name, no unit — being retired
# Phase 2 — consumers migrate; track who still reads the old one.
# Most metric backends can report query usage per series; where they cannot,
# grep the dashboards and rule files and count the references.
# Phase 3 — delete the alias only when the reference count is zero.
The mistake to avoid is a hard cutover on a calendar date. The consumers you know about will migrate; the ad-hoc dashboard someone built for a quarterly report will not, and it will break silently at the worst possible moment. Retire on a reference count of zero, exactly as with the contract versioning described in versioning spatial data contracts without breaking consumers.
Verification
Run the naming test against the exported metric set in continuous integration, not against a curated list. The gap between what the code intends to emit and what it actually emits is where non-conforming names live.
Then run a review pass on the labels, because a good name with bad labels is no better. Confirm every metric carries layer where the quantity is per-layer, that no metric carries an unbounded label, and that the label values come from a registry rather than from free text.
Gotchas
Unit in the label instead of the name. gis.spatial.freshness{unit="seconds"} looks clever and defeats every dashboard that assumes a name implies a unit. Put it in the name.
Two teams instrumenting the same quantity differently. The fix is a shared instrumentation library rather than a shared document. If both teams import the same helper, they cannot diverge.
Renaming to fix a typo. Almost never worth it. A metric named gis.spatial.geomtery_error_total is ugly and works; the rename costs a migration. Fix typos only when you are already migrating for another reason.
Subsystem proliferation. Ten subsystems means nobody remembers which one a metric lives under. Keep the list short and force additions through review.
Names that describe the implementation. gis.etl.pg_trigger_fired_total breaks when the trigger becomes a batch job. Name the quantity being measured, not the mechanism measuring it.
FAQ
Should the domain prefix be the product or the data type?
The data type. gis. survives a reorganisation, a rebrand and a platform migration; a product name does not. The point of the prefix is to separate spatial metrics from everything else in the same backend, and the data type is the stable way to express that.
How strict should the subsystem list be?
Strict enough that adding one is a conversation. Five to eight subsystems covers a mature spatial platform: ingestion, spatial quality, tiles, raster, joins, external lookups, and contract enforcement. Beyond that the boundaries stop being obvious and people guess.
What about metrics that come from third-party exporters?
Leave them alone and map them at query time. Renaming a database or collector exporter’s metrics creates a maintenance burden on every upgrade, and recording rules can present them under your convention without touching the source.
Do these rules apply to trace attributes too?
The same shape applies, and consistency between metric names and span attribute keys pays off immediately when correlating the two. Use the same subsystem and quantity segments, with the attribute carrying the value that would otherwise be a label — the pattern described in OpenTelemetry integration for GIS pipelines.
How do I get an existing platform onto the convention?
Freeze the convention for new metrics first, so the problem stops growing. Then migrate the metrics that appear in alert rules, because those are the ones whose ambiguity causes incidents. Dashboards can migrate opportunistically; nobody is paged by a dashboard.
Related
- Geospatial metric taxonomy for ETL — the parent topic defining the canonical metric set.
- Bounding spatial metric tag cardinality — the label-side discipline this pairs with.
- OpenTelemetry integration for GIS pipelines — where the names are emitted from.