# GCP Cloud Spanner Instance Idle

> Cloud Spanner bills per node-hour whether or not queries arrive. ZopNight reads the SpannerRequestCount series for each instance and flags only those whose average and maximum both sit at 0 across the full window, treating the entire monthly bill as recoverable because an unqueried instance has no partial value.

Source: https://zop.dev/integrations/gcp/recommendations/gcp-cloud-spanner-instance-idle
Updated: 2026-08-19

---

## Node-hours accrue without queries

Spanner is provisioned capacity: you pay per node-hour (or per processing unit) from the
moment the instance exists, and the database keeps its regional replicas running whether
applications query it or not. A Spanner instance is one of the more expensive things to
forget: even a single-node regional instance runs to hundreds of dollars a month. That is
why this rule carries high severity and a delete lever rather than a resize.

## One metric, read by name

A Spanner instance surfaces 6 different monitoring series, so the rule never reads
whichever metric happens to come first for the resource. It looks up `SpannerRequestCount`
by its exact name and fires only when both the average and the maximum of the full
harvested series are 0. Any measured request, however small, clears the instance. The read
path is Cloud Monitoring's `monitoring.timeSeries.list` permission, part of
`roles/monitoring.viewer`.

The savings math is deliberately unclever: zero requests means 100% of the node-hours are
waste, so the recoverable amount is the instance's full monthly cost, and the optimized
cost is $0.

## Query the request count yourself

```bash
curl -s -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://monitoring.googleapis.com/v3/projects/PROJECT_ID/timeSeries?filter=metric.type%3D%22spanner.googleapis.com%2Fapi%2Fapi_request_count%22&interval.startTime=$(date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)&interval.endTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
```

An empty response over 30 days corroborates what the rule saw.

## Before the delete

Export the data first; the Dataflow template "Cloud Spanner to Avro Files on Cloud
Storage" is the standard route. Then delete the instance to stop the per-node charge. If
the workload is real but bursty, the Spanner autoscaler is the middle path between
always-on nodes and deletion.

## Why some idle instances never surface

The rule abstains whenever pricing for the instance is unknown or zero, and whenever the
`SpannerRequestCount` series is absent. There is intentionally no metadata fallback:
request activity is a monitoring time series, not an asset-inventory field, so an
uninstrumented instance produces no recommendation rather than a guessed one. A
high-severity delete is only ever backed by measured data.
