Skip to content

PATTERN Cited by 1 source

Split sharded + unsharded keyspaces

Pattern

Split an application's table set across two keyspaces in the same Vitess cluster: a sharded keyspace holding the write-hot / large tables (each sharded by a common key) and an unsharded keyspace holding small, low-traffic metadata tables. VTGate routes every query to the right keyspace transparently; the application sees a single SQL surface.

Canonical instance: Temporal's production VSchema on PlanetScale, as documented by Savannah Longoria (2022-12-14). Small Temporal metadata tables (namespaces, cluster_metadata, queue, schema_version, buffered_events, signal_info_maps, …) live in the unsharded keyspace; the 13 write-hot Temporal tables (executions, history_node, history_tree, tasks, replication_tasks, timer_tasks, transfer_tasks, visibility_tasks, …) live in the sharded keyspace with xxhash as the Primary Vindex on their shard_id or range_hash column. (Source: sources/2026-04-21-planetscale-temporal-workflows-at-scale-sharding-in-production.)

When to apply

  • Skewed per-table traffic: some tables are write-hot + large (execution state, event histories, task queues); others are small + rarely-written (cluster metadata, schema version rows).
  • The hot tables share a natural shard key: a shard_id / range_hash / tenant_id column in each hot table's primary key.
  • The metadata tables don't share that shard key and wouldn't benefit from sharding anyway — or would be actively hurt by it (e.g. cross-shard scans for a lookup that's O(10s) of rows).
  • Cross-keyspace JOINs are rare or absent. Temporal fits this criterion: the hot tables query by shard_id, the metadata tables query by namespace — they're in different semantic domains and rarely joined.

Why split keyspaces rather than shard everything

Sharding every table — including tiny metadata tables — adds overhead (a scatter-gather lookup for a 10-row table is slower than a single-primary read) and operational complexity (more VSchema rules, more potential for cross-shard constraints) without benefit. Sharding only the hot tables localises the complexity where it pays off.

Sharding every table also blocks the degenerate case where a small metadata table legitimately needs a global index or a unique-across-the-fleet constraint: those are trivially expressible on an unsharded keyspace and require cross-shard lookups or consistent-lookup-vindex-style rigmarole on a sharded one.

VSchema shape

Two VSchemas, applied to the two keyspaces respectively.

Unsharded keyspace — just name the tables:

{
  "tables": {
    "buffered_events": {},
    "cluster_membership": {},
    "cluster_metadata": {},
    "namespace_metadata": {},
    "namespaces": {},
    "queue": {},
    "queue_metadata": {},
    "schema_update_history": {},
    "schema_version": {}
  }
}

Sharded keyspace — declare sharded: true, a Vindex function (xxhash), and each table's Primary Vindex column:

{
  "sharded": true,
  "vindexes": {
    "xxhash": { "type": "xxhash" }
  },
  "tables": {
    "executions":       { "columnVindexes": [ { "column": "shard_id",   "name": "xxhash" } ] },
    "history_node":     { "columnVindexes": [ { "column": "shard_id",   "name": "xxhash" } ] },
    "history_tree":     { "columnVindexes": [ { "column": "shard_id",   "name": "xxhash" } ] },
    "tasks":            { "columnVindexes": [ { "column": "range_hash", "name": "xxhash" } ] },
    "task_queues":      { "columnVindexes": [ { "column": "range_hash", "name": "xxhash" } ] },
    "timer_tasks":      { "columnVindexes": [ { "column": "shard_id",   "name": "xxhash" } ] },
    "transfer_tasks":   { "columnVindexes": [ { "column": "shard_id",   "name": "xxhash" } ] },
    "visibility_tasks": { "columnVindexes": [ { "column": "shard_id",   "name": "xxhash" } ] }
  }
}

Abbreviated — the full production VSchema has 13 sharded + 14 unsharded tables.

Migration path for an existing unsharded deployment

Longoria's post documents the canonical sequence for migrating an already-running Temporal-on-PlanetScale workload from one-unsharded-keyspace to two-keyspaces:

  1. Keep the existing unsharded keyspace with its existing tables.
  2. Create the new sharded keyspace with the VSchema above (sharded: true, xxhash Vindex, Primary Vindex declared per table, no tables initially).
  3. Use MoveTables to copy each write-hot table from the unsharded keyspace to the new sharded keyspace. VReplication handles the copy + binlog catch-up.
  4. SwitchTraffic to atomically swap query routing from the source unsharded keyspace to the target sharded keyspace for the moved tables. "Once the MoveTables process completes, the routing rules stay in place. Then we manually switched traffic from the unsharded source keyspace to the new sharded keyspace using the SwitchTraffic command." (Source: sources/2026-04-21-planetscale-temporal-workflows-at-scale-sharding-in-production.)
  5. The remaining (metadata) tables stay on the unsharded keyspace; no migration needed.

This is the same routing-rule-swap cutover pattern applied at keyspace granularity rather than shard granularity.

Why the pattern composes cleanly with Temporal

Temporal serialises updates per shard — every row Temporal writes is pre-addressed by shard_id or range_hash. Using that column as the Vitess Primary Vindex ensures each Temporal shard's rows stay on one MySQL primary, which naturally preserves Temporal's serialisation invariant without any cross-shard coordination on the storage side.

The unsharded-keyspace tables are write-light enough that a single MySQL primary handles them without load concerns; sharding them would cost latency without return.

Trade-offs

  • Two keyspaces = two cutover points during schema changes — a DDL that touches both the hot and metadata tables has to coordinate across keyspaces.
  • Cross-keyspace JOINs are more expensive than within-keyspace ones; the split works only when the application query shape tolerates the separation. Temporal does; many multi-tenant OLTP workloads don't.
  • Failure-domain asymmetry: a single unsharded-keyspace outage affects all namespaces (metadata unavailable), whereas a single sharded-keyspace shard outage affects only ~1/N of workflows. Per-keyspace HA/backup policy should reflect this.
  • Operational cost of two keyspaces (separate VSchema, separate MoveTables workflows if the table set evolves) is modest for a fixed table set like Temporal's, but compounds if the application frequently adds or reshapes tables.

Seen in

  • sources/2026-04-21-planetscale-temporal-workflows-at-scale-sharding-in-production — Savannah Longoria (PlanetScale, 2022-12-14) canonicalises the two-keyspace split as the production-validated shape for running Temporal on PlanetScale. Customer case study reports sustained QPS fluctuating 40k–200k across Black Friday / Cyber Monday with no interruptions. Migration from one-unsharded-keyspace to two-keyspaces is done via MoveTables + SwitchTraffic on the hot tables; the metadata tables are left in place.
Last updated · 550 distilled / 1,221 read