Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

MySQL & MariaDB in Depth  Module 15 – Applications, managed MySQL and Vitess

Scaling MySQL: replicas, sharding and Vitess

Scale MySQL in cost order: tune queries, add read replicas, partition, then shard with Vitess, whose VTGate and vindexes route each query to its shards.

  • Advanced
  • 25 minutes
  • Examples run with MySQL 8.4.11, Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free core, Pro depth: 8 of 25 minutes free
Free: Scale in cost order, not straight to sharding · Vitess puts a MySQL proxy in front of a fleet of servers · A vindex decides which shard a row lives on. With a pass: 2 in-depth parts and 3 interview answers, about 17 minutes.

What you will learn

  • Plan a scale-out path from query tuning through replicas, caching and partitioning to sharding
  • Explain how VTGate, VTTablet, keyspaces, shards and vindexes fit together in Vitess
  • Choose a shard key that keeps common queries on one shard, and predict when a query scatters
  • Compare moving a table and resharding with VReplication, from MoveTables to SwitchTraffic
  • Evaluate what Vitess and PlanetScale change for SQL, transactions and schema changes

Before you start

What you’ll learn · Before you start

What you will learn

  • Plan a scale-out path from query tuning through replicas, caching and partitioning to sharding
  • Explain how VTGate, VTTablet, keyspaces, shards and vindexes fit together in Vitess
  • Choose a shard key that keeps common queries on one shard, and predict when a query scatters
  • Compare moving a table and resharding with VReplication, from MoveTables to SwitchTraffic
  • Evaluate what Vitess and PlanetScale change for SQL, transactions and schema changes

Before you start

On this page

A single MySQL primary takes every write your application makes, and one server has hard limits: the writes it can commit, the disk it holds, the memory its busy rows need. When one server is no longer enough, four moves come before sharding, each costing more to run than the last: tuning queries, read replicas, a cache and partitioning.

Scaling MySQL well means climbing those moves in order, not jumping to the hardest. The ladder below ends at sharding, where Vitess spreads one database across many MySQL servers. You will see what each move helps, why sharding is the last resort, and how Vitess routes a query to the right shard.

Scale in cost order, not straight to sharding

Reach for the cheapest fix first: tuning queries and adding a missing index costs nothing and can turn a slow page fast. When reads are the limit, a read replica is a second server that copies every change from the primary and answers read queries, so reads scale with the number of replicas. A replica adds no write capacity, though: every write still lands on the one primary, and a replica can lag behind it, a replication lag: The time between a transaction's commit on the source and its commit on a replica. SHOW REPLICA STATUS estimates it in whole seconds as Seconds_Behind_Source; performance_schema keeps the timestamps of each transaction a worker applies. More in “Replication lag and parallel apply” readers must tolerate. A cache in front of MySQL answers hot reads from memory, so a repeated query need not reach the database at all.

When one table grows too large, RANGE partitioning: Partitioning in which each partition holds the values from the previous partition's bound up to its own VALUES LESS THAN bound; RANGE COLUMNS compares column values such as dates directly, and a last partition bounded by MAXVALUE takes everything above. More in “Partitioning in MySQL: pruning and retention” splits it into ranges on a single server, so a query scans only the partitions it needs and old data drops in one statement. Only sharding: Splitting a table's rows across many database servers so that reads and writes spread out and each server holds only a slice of the data. splits the rows across many servers, so reads and writes both spread out and each server holds a slice of the data. Sharding is the last resort because it moves joins and transactions across servers: a query that does not carry the shard key: The column whose value decides which shard a row lives on; a query that filters on it with an equality can be routed to a single shard. must reach every shard. The table below weighs them.

Interactive · answer and compare Which scaling step fits your bottleneck? Answer two questions about what runs out first and how your queries range, and see which step fits.
What runs out first as the database grows?
Do your busiest queries span many customers at once?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

The scale-out steps for MySQL, from cheap query fixes to sharding, and what each one actually helps.
Step Scales reads Scales writes Less data per server Keeps joins local
Query and index fixes Good a missing index can cut a query from seconds to milliseconds Mixed lighter statements leave the primary headroom, but one server still takes every write Poor the data is unchanged; tuning reads it faster, not smaller Good one server, so every join and transaction stays on it
Read replicas Good extra servers answer read queries, so reads scale with the replica count Poor every write still goes to the one primary; replicas only copy it Poor each replica keeps the whole dataset, a full copy Good reads can lag, but joins and transactions stay on one server
A cache Good a hit never reaches MySQL, so hot reads cost almost nothing Poor writes still land on the primary and must invalidate the cache Poor the database keeps every row; the cache holds only hot copies Good joins still run on the one MySQL server; only the cached copies can go stale
Partitioning Mixed pruning skips partitions, but all of them still live on one server Poor the one server still takes every write across all partitions Good old partitions drop in one statement, keeping the live set small Good one server still, so joins and transactions stay local
Sharding Good a query on the shard key hits one shard; the rest share the load Good writes spread across shards, so write capacity grows with them Good each shard keeps a slice of the rows, not the whole table Poor joins and transactions across shards need a scatter or 2PC
How the answers decide
  • “What runs out first as the database grows?” Read capacity: Read replicas and a cache add read capacity first, so reach for them before the harder moves.
  • “What runs out first as the database grows?” Write capacity: rules out Read replicas and A cache. Read replicas and a cache add no write capacity: the single primary still takes every write, so only sharding grows writes.
  • “What runs out first as the database grows?” Disk on one server: rules out Read replicas and A cache. A full copy per replica and a hot-only cache do not shrink a server's data; partitioning drops old rows and sharding splits them.
  • “Do your busiest queries span many customers at once?” Yes, across customers: adds a caveat to Sharding. Sharding scatters a cross-customer query to every shard, so keep the busy queries on one shard key or expect the fan-out.
  • “Do your busiest queries span many customers at once?” No, one customer each: Per-customer queries carry the shard key, so they stay on a single shard after sharding.

Checkpoint

Check: Scaling MySQL in cost order

  1. One question Your MySQL primary is at its write ceiling: writes, not reads, are the bottleneck. Which step actually adds write capacity?

    Choose one answer.

    Show the answer: One question

    Answer: Sharding

    Read replicas and a cache scale reads; partitioning shrinks data on one server. Only sharding spreads writes across servers, which is why it is the step for a write ceiling despite being the most disruptive.

Vitess puts a MySQL proxy in front of a fleet of servers

An open-source system that spreads one logical MySQL database across many MySQL servers and routes each query to the servers holding its rows; a graduated project of the Cloud Native Computing Foundation. lets a fleet of MySQL servers answer as one database: each statement goes to the servers that hold its rows, and the application never needs to know which they are. The application connects to a proxy, VTGate: The Vitess proxy that speaks the MySQL protocol to applications and routes each query to the shards that hold its rows., with the ordinary MySQL protocol, so a driver or ORM needs no change, and by default VTGate reports its version as MySQL 8.4.

Beside each MySQL server runs a VTTablet: The Vitess process that runs beside each MySQL server, pooling connections, rewriting queries and managing that one server; a mysqld with its VTTablet is a tablet, and a shard has one or more., which pools connections to it, rewrites queries and guards it. A logical database is a keyspace: In Vitess, a logical database the application sees as one, which can be split into shards spread across many MySQL servers., which can be split into shards, and the cluster’s layout lives in a topology server such as etcd. Step through one query below.

Interactive · step by step How a query reaches a shard in Vitess Step through one query from the application to a shard, and see which component does each job.
How a query reaches a shard in Vitess, step 4 of 4. Showing Application, VTGate proxy (Routes rows), VTTablet (Pools conns), MySQL shard (One shard). In focus: VTTablet, MySQL shard.MySQL wiregRPClocal SQLApplicationVTGate proxyRoutes rowsVTTabletPools connsMySQL shardOne shard

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. The app speaks to VTGate. The application connects to VTGate with the ordinary MySQL protocol, so a driver or ORM needs no change. By default VTGate reports its version as MySQL 8.4.
  2. VTGate plans the query. VTGate parses the query and plans it with the keyspace's vindexes, which it learned earlier from the topology server, so no topology call sits in the query's path. The plan names the shard or shards that hold the rows.
  3. It routes to the right VTTablet. VTGate sends the query over gRPC to the VTTablet in front of each target shard. A single-shard query reaches one VTTablet; a scatter reaches every one.
  4. VTTablet runs it on its mysqld. Each VTTablet runs beside one mysqld, holds a pool of connections to it, and runs the rewritten SQL on that shard, then returns rows to VTGate to merge.

Checkpoint

Check: What VTGate, VTTablet and keyspaces do

  1. One question In a Vitess cluster, what does an application connect to, and how?

    Choose one answer.

    Show the answer: One question

    Answer: VTGate, over the ordinary MySQL protocol

    The application speaks plain MySQL to VTGate; VTGate plans the query from the vindexes and routes it over gRPC to the VTTablet in front of each target shard.

A vindex decides which shard a row lives on

A vindex: A Vitess index that maps a column value to a shard, so the query planner can route a query to the shard or shards that hold its rows. maps a column value to a shard. The primary vindex sits on the shard key and, like a primary key, must be unique, so an equality on the shard key routes a query to exactly one shard. A query whose filter gives VTGate no vindex to route on becomes a scatter query: A query whose filter gives a sharded database no shard key or other index to route on, so it is sent to every shard and the partial answers are merged.: VTGate sends it to every shard and merges the answers. The command-line tool vtexplain shows the difference without a running cluster: give it a schema, a VSchema and queries and it prints, per statement, which shards the planner picks. The run below shards customer and corder by a hash vindex on customer_id, over two shards and then four.

How vtexplain routes four queries over two and four shards Python · vtexplain_session.py
"""Shows how Vitess routes queries across shards, with vtexplain and no running cluster.

vtexplain reads a schema and a VSchema, builds the same query planner VTGate uses, and prints, for each statement,
which shard or shards the query goes to and the SQL each shard runs. It needs no mysqld and no etcd: it starts a
fake MySQL server of its own on a Unix socket in TMPDIR. macOS refuses a Unix socket path longer than 103 characters,
so this program sets TMPDIR to a short relative folder, "tmp", and runs vtexplain from its own folder; an absolute
TMPDIR inside a long folder path makes vtexplain panic.

Only what vtexplain writes to standard output, the per-shard plans, is printed. Its standard error, a few hundred
lines of time-stamped logs from the fake tablets, is dropped.

The keyspace "customer" is sharded with one hash vindex on customer_id, mapped onto both the customer table and the
corder table, so rows of a customer and that customer's orders hash to the same shard. The same four queries are
planned first over two shards and then over four, which is what a reshard from two to four shards changes.
"""

import os
import subprocess
import sys
from pathlib import Path


def toolchain(name):
    """The unpacked Vitess release, a folder toolchains/<name>: in a folder above this program or above the folder it
    runs in, or in one of the folders of a tmp folder there. The filesystem root is not searched."""
    seen = set()
    for start in (Path.cwd().resolve(), Path(__file__).resolve().parent):
        for parent in (start, *start.parents[:-1]):
            if parent in seen:
                continue
            seen.add(parent)
            found = parent / "toolchains" / name
            if found.is_dir():
                return found
            scratch = parent / "tmp"
            if scratch.is_dir():
                for folder in sorted(scratch.iterdir()):
                    found = folder / "toolchains" / name
                    if found.is_dir():
                        return found
    sys.exit(f"the toolchain {name} was not found")


VTEXPLAIN = toolchain("vitess-24.0.5") / "bin" / "vtexplain"

SCHEMA = (
    "CREATE TABLE customer (customer_id BIGINT NOT NULL, email VARCHAR(128), PRIMARY KEY (customer_id));\n"
    "CREATE TABLE corder (order_id BIGINT NOT NULL, customer_id BIGINT NOT NULL, total INT, PRIMARY KEY (order_id));\n"
)

VSCHEMA = """{
  "customer": {
    "sharded": true,
    "vindexes": { "hash": { "type": "hash" } },
    "tables": {
      "customer": { "column_vindexes": [ { "column": "customer_id", "name": "hash" } ] },
      "corder":   { "column_vindexes": [ { "column": "customer_id", "name": "hash" } ] }
    }
  }
}
"""

QUERIES = [
    "SELECT * FROM customer WHERE customer_id = 5",
    "SELECT COUNT(*) FROM corder",
    "SELECT c.email, o.total FROM customer c JOIN corder o ON c.customer_id = o.customer_id WHERE c.customer_id = 5",
    "SELECT * FROM corder WHERE total > 100",
]


def write(name, text):
    Path(name).write_text(text)
    print(f"$ cat {name}")
    print(text.rstrip("\n"))
    print()


def plan(shards):
    sql = "; ".join(QUERIES)
    env = dict(os.environ, TMPDIR="tmp")
    print(f"$ vtexplain --shards {shards} --vschema-file vschema.json --schema-file schema.sql \\")
    print('      --sql "$(cat queries.sql)"')
    done = subprocess.run(
        [str(VTEXPLAIN), "--shards", str(shards), "--vschema-file", "vschema.json",
         "--schema-file", "schema.sql", "--sql", sql],
        stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, cwd=".", env=env)
    print(done.stdout.rstrip("\n"))
    print()


def main():
    Path("tmp").mkdir(exist_ok=True)
    write("schema.sql", SCHEMA)
    write("vschema.json", VSCHEMA)
    write("queries.sql", "\n".join(q + ";" for q in QUERIES) + "\n")
    print("# Two shards: customer_id=5 hashes to one shard; COUNT(*) and the non-key filter scatter to both.")
    print()
    plan(2)
    print("# Four shards: the single-shard lookups still touch one shard; the scatters now touch all four.")
    print()
    plan(4)


if __name__ == "__main__":
    main()

Output

$ cat schema.sql
CREATE TABLE customer (customer_id BIGINT NOT NULL, email VARCHAR(128), PRIMARY KEY (customer_id));
CREATE TABLE corder (order_id BIGINT NOT NULL, customer_id BIGINT NOT NULL, total INT, PRIMARY KEY (order_id));

$ cat vschema.json
{
  "customer": {
    "sharded": true,
    "vindexes": { "hash": { "type": "hash" } },
    "tables": {
      "customer": { "column_vindexes": [ { "column": "customer_id", "name": "hash" } ] },
      "corder":   { "column_vindexes": [ { "column": "customer_id", "name": "hash" } ] }
    }
  }
}

$ cat queries.sql
SELECT * FROM customer WHERE customer_id = 5;
SELECT COUNT(*) FROM corder;
SELECT c.email, o.total FROM customer c JOIN corder o ON c.customer_id = o.customer_id WHERE c.customer_id = 5;
SELECT * FROM corder WHERE total > 100;

# Two shards: customer_id=5 hashes to one shard; COUNT(*) and the non-key filter scatter to both.

$ vtexplain --shards 2 --vschema-file vschema.json --schema-file schema.sql \
      --sql "$(cat queries.sql)"
----------------------------------------------------------------------
SELECT * FROM customer WHERE customer_id = 5

1 customer/-80: select * from customer where customer_id = 5 limit 10001

----------------------------------------------------------------------
SELECT COUNT(*) FROM corder

1 customer/-80: select count(*) from corder limit 10001
1 customer/80-: select count(*) from corder limit 10001

----------------------------------------------------------------------
SELECT c.email, o.total FROM customer c JOIN corder o ON c.customer_id = o.customer_id WHERE c.customer_id = 5

1 customer/-80: select c.email, o.total from customer as c, corder as o where c.customer_id = 5 and c.customer_id = o.customer_id limit 10001

----------------------------------------------------------------------
SELECT * FROM corder WHERE total > 100

1 customer/-80: select * from corder where total > 100 limit 10001
1 customer/80-: select * from corder where total > 100 limit 10001

----------------------------------------------------------------------
<nil>

# Four shards: the single-shard lookups still touch one shard; the scatters now touch all four.

$ vtexplain --shards 4 --vschema-file vschema.json --schema-file schema.sql \
      --sql "$(cat queries.sql)"
----------------------------------------------------------------------
SELECT * FROM customer WHERE customer_id = 5

1 customer/40-80: select * from customer where customer_id = 5 limit 10001

----------------------------------------------------------------------
SELECT COUNT(*) FROM corder

1 customer/-40: select count(*) from corder limit 10001
1 customer/40-80: select count(*) from corder limit 10001
1 customer/80-c0: select count(*) from corder limit 10001
1 customer/c0-: select count(*) from corder limit 10001

----------------------------------------------------------------------
SELECT c.email, o.total FROM customer c JOIN corder o ON c.customer_id = o.customer_id WHERE c.customer_id = 5

1 customer/40-80: select c.email, o.total from customer as c, corder as o where c.customer_id = 5 and c.customer_id = o.customer_id limit 10001

----------------------------------------------------------------------
SELECT * FROM corder WHERE total > 100

1 customer/-40: select * from corder where total > 100 limit 10001
1 customer/40-80: select * from corder where total > 100 limit 10001
1 customer/80-c0: select * from corder where total > 100 limit 10001
1 customer/c0-: select * from corder where total > 100 limit 10001

----------------------------------------------------------------------
<nil>

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 vtexplain_session.py

A lookup of customer_id = 5 hit one shard, and the join stayed there because it joins on the shard key. The COUNT(*) and the filter on total, which is not the shard key, scattered to every shard. Planned over four shards, as after a reshard, the lookup stayed on one shard and each scatter widened from two shards to four.

Key idea

A query that pins the shard key with an equality reaches one shard; one that gives the vindex nothing to route on scatters to all of them, and adding shards only widens that scatter.

Checkpoint

Check: How a vindex routes a query

  1. One question Over the run's two shards, which of these statements goes to both shards?

    Choose one answer.

    Show the answer: One question

    Answer: SELECT COUNT(*) FROM corder

    The run showed customer_id = 5 and the shard-key join on one shard, while COUNT(*) and the filter on total, a non-key column, scattered to both shards.

Choosing a shard key, adding a lookup vindex, and resharding from two shards to four Pro: opens with a Pro, Premium or Ultimate pass

A sharded database is only as fast as its shard key: the right one keeps the busy queries on one shard, the wrong one scatters them to all. The key comes from the access pattern, a lookup vindex serves the queries that cannot carry it, and a keyspace can grow from two shards to four while it keeps serving.

In this part:

  • Choosing the shard key from the access pattern
  • A lookup vindex for the queries that cannot carry it
  • Resharding from two shards to four

1 diagram · about 11 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Moving a table into a sharded keyspace, and the SQL Vitess and PlanetScale restrict Pro: opens with a Pro, Premium or Ultimate pass

Getting onto Vitess rarely starts sharded. A table moves from an unsharded keyspace into a sharded one with the same VReplication machinery as a reshard, and once it is there, some SQL that worked on a single MySQL no longer does.

In this part:

  • Moving a table with MoveTables
  • What changes for SQL and transactions
  • The limits PlanetScale adds
  • Key takeaways on shard keys, resharding and moving tables

2 diagrams · about 8 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Practise

Work the exercise, then the self-check, weakest idea first.

Exercise · Medium · Python

Route queries across shards like VTGate

VTGate decides where a query goes from the VSchema: which column, if any, shards each table. A query that filters on the shard key of every sharded table it touches reaches one shard; if any sharded table is not pinned by its shard key, the query scatters to all of them; and a query that touches no sharded table does not shard at all.

Write route(vschema, query) in route.py. It returns one of three strings: "single-shard", "scatter" or "unsharded".

The inputs are plain data, already parsed for you:

  • vschema is a dictionary mapping a table name to its shard key column, or to None when the table is unsharded. For example {"customer": "customer_id", "corder": "customer_id", "product": None}.
  • query is a dictionary with two keys:
    • "tables": the list of tables the query reads.
    • "equalities": a dictionary mapping a table to the list of its columns that the query compares to a constant with =, directly or through a join on equal columns: in customer c JOIN corder o ON c.customer_id = o.customer_id WHERE c.customer_id = 5, both customer and corder list customer_id. A range such as total > 100, or no filter at all, puts nothing here for that table.

Return:

  • "unsharded" if none of the query's tables is sharded (every one maps to None or is absent from the vschema).
  • "single-shard" if at least one table is sharded and every sharded table the query touches has its shard key among its equality columns.
  • "scatter" if at least one sharded table the query touches is not pinned by an equality on its shard key.

For example, with the vschema above, a query on customer with "equalities": {"customer": ["customer_id"]} is "single-shard"; a COUNT(*) on corder with no equalities is "scatter"; and a query that reads only product is "unsharded". Only an equality on the shard key pins a table to one shard: a filter on another column, or a range, does not.

Starter code · route.py

def route(vschema, query):
    """Return how VTGate routes this query over the sharded keyspace:
    'single-shard', 'scatter' or 'unsharded'. See prompt.md."""
    # Replace this with your router.
    return "scatter"
The sample tests · test_route.py
"""Sample tests: two VSchemas, a shop and a multi-tenant product, and queries like those of the lesson's vtexplain run."""

from route import route

# A shop sharded by customer, with an unsharded product catalogue.
SHOP = {"customer": "customer_id", "corder": "customer_id", "product": None}
# A multi-tenant product sharded by tenant.
SAAS = {"account": "tenant_id", "invoice": "tenant_id"}


def test_lookup_on_shard_key_is_single_shard():
    q = {"tables": ["customer"], "equalities": {"customer": ["customer_id"]}}
    assert route(SHOP, q) == "single-shard"


def test_join_on_shard_key_is_single_shard():
    q = {"tables": ["customer", "corder"],
         "equalities": {"customer": ["customer_id"], "corder": ["customer_id"]}}
    assert route(SHOP, q) == "single-shard"


def test_count_with_no_filter_scatters():
    q = {"tables": ["corder"], "equalities": {}}
    assert route(SHOP, q) == "scatter"


def test_range_on_non_key_scatters():
    q = {"tables": ["corder"], "equalities": {}}
    assert route(SHOP, q) == "scatter"


def test_equality_on_non_key_scatters():
    q = {"tables": ["customer"], "equalities": {"customer": ["email"]}}
    assert route(SHOP, q) == "scatter"


def test_one_pinned_one_not_scatters():
    q = {"tables": ["customer", "corder"],
         "equalities": {"customer": ["customer_id"]}}
    assert route(SHOP, q) == "scatter"


def test_only_unsharded_table_is_unsharded():
    q = {"tables": ["product"], "equalities": {"product": ["sku"]}}
    assert route(SHOP, q) == "unsharded"


def test_sharded_plus_unsharded_uses_only_sharded():
    q = {"tables": ["customer", "product"],
         "equalities": {"customer": ["customer_id"]}}
    assert route(SHOP, q) == "single-shard"


def test_tenant_key_is_single_shard():
    q = {"tables": ["account"], "equalities": {"account": ["tenant_id"]}}
    assert route(SAAS, q) == "single-shard"


def test_tenant_without_key_scatters():
    q = {"tables": ["invoice"], "equalities": {"invoice": ["invoice_no"]}}
    assert route(SAAS, q) == "scatter"
A hint

Start by collecting the sharded tables the query touches: the ones whose vschema value is truthy (not None and present). If that list is empty, return "unsharded". Otherwise check each sharded table: if its shard key is not in query["equalities"].get(table, []), the query scatters. If every sharded table is pinned, it is single-shard.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Practice that adapts to you

Practise this lesson

6 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 6 One page is slow and reads dominate the load. What is the cheapest first move on the scale-out ladder?

    Choose one answer.

    Show the answer: Question 1 of 6

    Answer: Tune the query and add the missing index

    Climb the ladder in cost order: fix queries and indexes first, then replicas and a cache for reads, then partitioning, and shard only when a cheaper step cannot help.

  2. Question 2 of 6 A team adds three read replicas and write latency stays high at peak. Why did replicas not help the writes?

    Choose one answer.

    Show the answer: Question 2 of 6

    Answer: Every write still goes to the single primary; replicas only copy it

    Read replicas scale reads only. A write ceiling needs write capacity, which comes from sharding, not from copying every write to more servers.

  3. Question 3 of 6 What runs beside each MySQL server in a Vitess cluster and manages connections to it?

    Choose one answer.

    Show the answer: Question 3 of 6

    Answer: A VTTablet

    VTGate is the one proxy in front; a VTTablet sits beside every mysqld and does the per-server work of pooling, rewriting and guarding.

  4. Question 4 of 6 In Vitess, what is a keyspace?

    Choose one answer.

    Show the answer: Question 4 of 6

    Answer: A logical database that can be split into shards

    A keyspace is the logical database; sharding splits it into shards, and a vindex maps a row's shard key to the shard that holds it.

  5. Question 5 of 6 In the run's keyspace, where the hash vindex on customer_id is the only vindex, what has to be true for VTGate to send a SELECT to exactly one shard?

    Choose one answer.

    Show the answer: Question 5 of 6

    Answer: The query filters on the shard key with an equality

    With only the hash vindex on customer_id, an equality on customer_id is the one filter VTGate can route to a single shard; the run's COUNT(*) and its filter on total had none and went to every shard.

  6. Question 6 of 6 Why is a scatter query only as fast as the slowest shard?

    Choose one answer.

    Show the answer: Question 6 of 6

    Answer: VTGate must wait for every shard's answer before it can merge and return

    A scatter fans out to all shards and merges their answers, so its latency is the slowest shard's, and adding shards widens the fan-out rather than speeding it up.

Interview questions

Answer the warm-up aloud before reading its answer, then plan the other three.

Warm-up: what is a scatter query in a sharded database, and why is it usually slower than a single-shard query?

A scatter query is one that a sharded database cannot narrow to particular shards, so the proxy sends it to every shard and merges the answers. It happens when the query’s filter gives the proxy no shard key or other index to route on, so any shard might hold a match. It is slower for two reasons: the work is the sum across all shards, so a count costs about as much as on the unsharded table; and the query is only as fast as the slowest shard. Resharding widens the fan-out, so keep the busy queries on the shard key or add a lookup vindex.

  1. Mid to senior

    You run a 400 GB MySQL on one primary; reads are slow at peak while writes are still comfortable. Walk through what you try before sharding, and name the signal that finally makes sharding the right move.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A multi-tenant SaaS on MySQL has outgrown one server and will shard with Vitess. How do you choose the shard key, and what happens to queries that do not carry it?

    Model answer, Pro. First step and follow-ups

  3. Senior to staff

    You must move the orders table into a sharded keyspace and later go from two shards to four, with no downtime. Describe the Vitess workflow, how you verify each step, and how you roll back if a switch goes wrong.

    Model answer, Pro. First step and follow-ups

Flashcards · 5 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. In which order should a MySQL service try scaling moves?

    By cost: tune queries and indexes, add read replicas and a cache for reads, partition to shrink the live set, and shard only when writes or data outgrow one server.

  2. What do read replicas and a cache not add?

    Write capacity: every write still lands on the one primary. Replicas also lag behind it, and readers must tolerate that replication lag.

  3. Why is sharding the last resort?

    It spreads reads and writes across servers, each holding a slice of the rows, but joins and transactions then cross servers, and any query without the shard key in its filter has to visit every shard.

  4. What are VTGate, VTTablet and a keyspace in Vitess?

    VTGate is the proxy applications connect to with the ordinary MySQL protocol (reporting MySQL 8.4 by default); a VTTablet beside each MySQL server pools connections, rewrites queries and guards it; a keyspace is a logical database that can be split into shards, with the layout in a topology server such as etcd.

  5. When does a Vitess query reach one shard, and when does it scatter?

    An equality on the shard key goes through the primary vindex to exactly one shard. A filter that gives VTGate no vindex to route on scatters to every shard and merges the answers, and adding shards only widens that scatter.

Key takeaways

  • Scale in cost order: tune queries and indexes, add read replicas and a cache for reads, partition to shrink the live set, and shard only when writes or data outgrow one server.
  • Read replicas and a cache add read capacity but no write capacity, and both leave every write on the single primary.
  • Vitess puts VTGate, a MySQL-protocol proxy, in front of a fleet of MySQL servers, and a vindex maps the shard key to a shard: an equality on the shard key reaches one shard, a query with nothing to route on scatters to all, and more shards widen that scatter.

Terms in this lesson

keyspace
In Vitess, a logical database the application sees as one, which can be split into shards spread across many MySQL servers.
resharding
Changing the number of shards in a keyspace, such as splitting two shards into four, by copying rows with VReplication and switching traffic.
scatter query
A query whose filter gives a sharded database no shard key or other index to route on, so it is sent to every shard and the partial answers are merged.
shard key
The column whose value decides which shard a row lives on; a query that filters on it with an equality can be routed to a single shard.
sharding
Splitting a table's rows across many database servers so that reads and writes spread out and each server holds only a slice of the data.
vindex
A Vitess index that maps a column value to a shard, so the query planner can route a query to the shard or shards that hold its rows.
VReplication
The Vitess engine that copies rows between shards or keyspaces to power MoveTables, Reshard and managed online schema changes.
VTGate
The Vitess proxy that speaks the MySQL protocol to applications and routes each query to the shards that hold its rows.
VTTablet
The Vitess process that runs beside each MySQL server, pooling connections, rewriting queries and managing that one server; a mysqld with its VTTablet is a tablet, and a shard has one or more.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.