This episode dissects vindexes as the key to routing in Vitess: configuring vindexes for a keyspace, the difference between global and local vindexes along with their trade-offs, and designing schemas for sharded and unsharded tables with VSchema.

In episode 4 you met the hash vindex as a mapping of values to shards. Episode 8 goes deeper: vindexes come in many types with different roles — there are global vindexes that share data across shards and local vindexes that belong to a single shard. Your vindex choice determines how fast your queries run, how easy resharding is, and how consistent routing is.
Episode 8 roadmap: a deeper look at the vindex concept, comparing global and local vindexes, designing sharded vs unsharded schemas, and configuring vindexes in practice. This is a design episode — its effects are felt for the entire lifetime of the system.
A vindex is not an ordinary database index. It's a function that answers one question: "which shard does value X live on?" Three main roles a vindex can play:
hash vindex is unique: one user_id only exists in one shard.tenant_id where a single tenant spreads out. The unicode_loose_md5 vindex is often used for this.{
"sharded": true,
"vindexes": {
"hash": { "type": "hash" },
"tenant_lookup": { "type": "consistent_lookup_unique" }
},
"tables": {
"users": {
"column_vindexes": [
{ "column": "user_id", "name": "hash" }
],
"columns": [
{ "name": "tenant_id", "vindex": "tenant_lookup" }
]
}
}
}Notice that the tenant_id column also gets a vindex — this is an example of a secondary vindex used to speed up queries based on tenant_id without causing a scatter.
A local vindex lives inside a single shard: its data isn't shared between shards. The hash vindex is an example — a pure function computed from the value, storing no data. Local vindexes are simple, with no extra storage cost and no consistency issues. However, they can't answer queries based on a column that isn't the sharding key column.
A global vindex is a mapping that can route a query from a non-sharding-key value to the right shard. How it works: the mapping data is stored as a lookup table — either in its own shard (an unsharded lookup keyspace) or inside a table with another vindex. When the application queries WHERE tenant_id = 'X', VTGate consults the lookup vindex to find the shard for that tenant_id value, without a scatter.
{
"vindexes": {
"tenant_lookup": {
"type": "consistent_lookup_unique",
"params": {
"table": "tenant_lookup",
"from": "tenant_id",
"to": "user_id"
}
}
}
}The consistent_lookup_unique vindex uses the tenant_lookup table to map tenant_id to user_id, which is then routed through the primary hash vindex. This bridges two worlds: a query based on tenant_id can find its shard without a scatter.
Info
Design rule: start with a local vindex for the sharding key column. Add global lookup vindexes only for columns genuinely used in query filters. Every lookup vindex brings storage cost and transaction complexity.
Sharded tables store rows in different shards based on a vindex. This has an important consequence: foreign keys between tables in different shards can't be consistently guaranteed, because related rows may live in different MySQL instances. Vitess handles this by disabling FK enforcement across shards and pushing integrity maintenance to application design.
Common design patterns:
orders) are sharded by user_id, just like users.users JOIN orders ON user_id query can be resolved in one shard.Not every table needs sharding. Small reference tables that rarely change — country codes, small product lists, configuration — are better left unsharded: stored in one shard (or in a separate unsharded keyspace), so queries always hit exactly one location with no scatter.
CREATE TABLE countries (
code char(2) primary key,
name varchar(100)
)The CREATE TABLE countries command in an unsharded keyspace stores the table in one location. Vitess also has reference tables, a mechanism that copies an unsharded table to every shard of a sharded keyspace — so joins with sharded tables can run without leaving the shard.
The usual workflow:
vtctlclient ApplyVschema -vschema_file=vschema.json users
vtctlclient GetVschema usersvtctlclient GetVschema displays the active VSchema — a quick way to verify the configuration is correct and consistent with your intent.
Warning
Changing a vindex after data is stored can't be done casually: data already distributed by the old vindex must be recomputed, usually through resharding. Designing the vindex upfront is cheaper than fixing it later.
In this episode 8 you understood vindexes in depth: the unique, non-unique, and lookup roles, the difference between global and local vindexes along with their trade-offs, designing schemas for sharded and unsharded tables, and the vindex configuration workflow with VSchema.
Key takeaways:
In the next episode, episode 9, we handle guarantees: transactions and consistency models — ACID in Vitess, single-shard vs cross-shard transactions, XA support, locking, and consistency best practices. See you there!