Defining SLAs for Spatial Data Products

A spatial data product without a published service-level agreement is a best-effort dataset wearing a product label. This page shows how to write measurable SLAs and SLOs for a spatial product — availability, freshness, query latency, positional accuracy, and an error budget — and where that SLA table lives. It operationalizes Product Thinking for GIS Datasets within Geospatial Data Mesh Fundamentals, and the numbers you set here become the runtime targets enforced by SLA Monitoring for Spatial Data Products. The SLA is not a wiki page: it is a machine-readable section of the Data Contracts for Spatial Products that the domain publishes, versions, and is paged against.

An SLA that lists five aspirational numbers with no error budget is unenforceable. The discipline is to pair every objective with a measurement window and a budget for how much it may be missed, so an alert fires on burn rate rather than on a single unlucky sample.

Figure — Spatial SLOs feed a shared 30-day error budget whose burn rate drives alerting.

Spatial SLOs and the availability error budget Three SLO cards sit across the top: availability with objective at least 99.9 percent, freshness with staleness at most 15 minutes, and query p95 latency at most 400 milliseconds. Below them a 30-day error budget bar for availability totals 43 minutes and 12 seconds, split into a consumed segment of about 26 minutes and a remaining segment of about 17 minutes. A caption states the burn-rate alert fires when more than two percent of the budget is spent in one hour. Availability SLO ≥ 99.9% Freshness staleness ≤ 15 min Query p95 latency ≤ 400 ms 30-day error budget · availability · 43m 12s total consumed ≈ 26m remaining ≈ 17m Burn-rate alert page owner when > 2% of budget is spent in a 1-hour window

Prerequisites

Requirement Version / assumption Notes
promtool >= 2.45 Validates recording/alerting rules that measure each SLO
Data contract store Git-backed, versioned The SLA table is a section of the product’s contract, not a separate doc
Metrics backend Prometheus-compatible Exposes up, freshness lag, and request-latency histograms per product
CRS assumption Positional accuracy stated at native resolution in EPSG:4326; UTM sources measured in EPSG:32633 metres Accuracy SLO must name the CRS it is measured in
Role product-owner (edits SLA) + platform-sre (owns alert routing) Owner sets targets; SRE wires enforcement
Env PRODUCT_ID, SLO_WINDOW=30d Window must match the error-budget arithmetic

Step-by-Step Implementation

1. Write the SLA/SLO table into the data contract

Each objective needs an indicator (what is measured), an objective (the threshold), a window, and an error budget derived from the objective. Put it in the contract so it versions with the product:

yaml
# contract.yaml — sla section (lives inside the data contract)
product_id: flood-risk-tiles
version: v1.2.0-crs:EPSG:4326-res:10m
sla:
  window: 30d
  objectives:
    - indicator: availability            # successful tile reads / total
      slo: 0.999                          # 99.9%  -> error budget 43m 12s / 30d
      measured_by: probe_success_ratio
    - indicator: freshness               # now - source_commit_time
      slo_max_staleness: 15m
      measured_by: data_freshness_seconds
    - indicator: query_p95_latency
      slo_max: 400ms
      measured_by: request_duration_seconds
    - indicator: positional_accuracy
      slo_max_error_m: 0.5                # metres at native res, EPSG:4326
      measured_by: control_point_rmse

Validate that the objectives are internally consistent (an availability SLO must translate to a finite budget):

bash
python3 -c "w=30*24*60; b=w*(1-0.999); print(f'error budget = {b:.1f} min / 30d')"
# expect: error budget = 43.2 min / 30d

2. Derive the error budget and wire a burn-rate check

Turn the availability SLO into an alerting rule that fires on budget burn, not on a single failed probe. This is the check that SLA Monitoring for Spatial Data Products will run in production:

yaml
# sla_rules.yaml
groups:
  - name: flood-risk-tiles-slo
    rules:
      - record: job:probe_success:ratio_rate1h
        expr: sum(rate(probe_success{product="flood-risk-tiles"}[1h]))
              / sum(rate(probe_total{product="flood-risk-tiles"}[1h]))
      - alert: SpatialErrorBudgetBurn
        # 14.4x burn = 2% of a 30d budget in 1h -> fast-burn page
        expr: (1 - job:probe_success:ratio_rate1h) > (14.4 * (1 - 0.999))
        for: 5m
        labels: {severity: page, product: flood-risk-tiles}
        annotations:
          summary: "Availability budget burning >14.4x for flood-risk-tiles"

Verify the rules parse before committing — a malformed rule silently disables the SLO:

bash
promtool check rules sla_rules.yaml
# SUCCESS: 2 rules found

3. Add a freshness and accuracy gate at publish

Availability and latency are measured at runtime, but freshness and positional accuracy are also gated at publish so a stale or drifted product never reaches consumers. Fail the publish when the staleness or RMSE budget is exceeded:

bash
STALE=$(promtool query instant http://prometheus:9090 \
  'time() - data_source_commit_timestamp{product="flood-risk-tiles"}' | awk '{print $2}')
test "${STALE%.*}" -le 900 && echo "FRESHNESS_OK" || { echo "STALE_BLOCK"; exit 1; }
# 900s == 15m budget; exceeding it blocks the publish, not just alerts

Route a breach to the owning tier: a freshness breach pages the domain owner, an accuracy regression blocks publish and quarantines the artifact, and an availability burn pages platform SRE. The versioning of these thresholds follows Versioning Spatial Data Contracts with SemVer — tightening an SLO is a minor contract change, loosening one is a major, consumer-breaking change.

Configuration Reference

Metric SLO Window Error budget
Availability (tile read success) ≥ 99.9% 30 d 43m 12s / 30 d
Freshness (max staleness) ≤ 15 min rolling 0 — hard gate at publish
Query p95 latency ≤ 400 ms 30 d rolling ≤ 0.1% requests may exceed
Positional accuracy (RMSE) ≤ 0.5 m at native res per publish 0 — hard gate at publish
CRS-contract conformance 100% of publishes per publish 0 — reject at catalog ingest

Common Failure Modes & Fixes

Symptom: the SLA looks healthy on the dashboard but consumers still complain about outages. Root cause: availability is measured from inside the Kubernetes cluster, not from the consumer’s ingress path, so gateway and DNS failures are invisible. Fix: run the availability probe from the consumer edge (through the API gateway) and record probe_success there, so the SLO reflects what consumers actually experience.

Symptom: the burn-rate alert flaps on and off every few minutes. Root cause: the alert is evaluated on a raw ratio with no for: clause, so a single failed scrape trips it. Fix: keep the for: 5m dwell and use a multi-window burn rate (fast 1h + slow 6h) so only a sustained burn pages, as shown in step 2.

Symptom: the positional-accuracy SLO passes but downstream analysis is misaligned. Root cause: RMSE was measured in the source UTM CRS while the product is served in EPSG:4326, so the metre error does not correspond to the delivered geometry. Fix: measure control-point RMSE in the CRS the product is published in, and state that CRS explicitly in the SLO row.

Symptom: freshness “passes” yet the data is hours old. Root cause: the freshness metric measures pipeline completion time, not the age of the underlying source observation. Fix: compute staleness as now - source_commit_timestamp, not now - last_pipeline_run, so latency in the source is counted against the budget.

FAQ

Where should the SLA table actually live?

Inside the product’s data contract, version-controlled alongside the schema and CRS declaration. A separate wiki drifts from reality; a contract-embedded SLA versions with the product and can be validated in CI, so a consumer that pins a contract version also pins the guarantees it was promised.

How do I choose between a 99.9% and a 99.95% availability SLO?

Work backwards from the error budget the owning team can realistically defend: 99.9% permits 43m 12s of downtime per 30 days, 99.95% only 21m 36s. Pick the loosest SLO consumers will accept, because a tighter number you cannot meet burns trust faster than an honest one.

Should freshness have an error budget like availability does?

Usually not — freshness is better modelled as a hard publish gate than a burnable budget, because serving stale spatial data is a correctness problem, not a reliability one. Block the publish when staleness exceeds the objective rather than spending a budget against it.

What makes positional accuracy an SLA concern rather than a QA step?

Because consumers make decisions with the coordinates, an accuracy regression is a broken promise, not an internal defect. Stating RMSE at a named resolution and CRS in the SLA lets a consumer decide whether 0.5 m is fit for their use, and lets the owner block a publish that regresses against the baseline.