217 lines
115 KiB
HTML
217 lines
115 KiB
HTML
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><meta name="theme-color" content="#111111"/><meta name="user-signed-in" content="false"/><title>Making 768 servers look like 1 — PlanetScale</title><meta name="description" content="How to make 768 distinct Postgres servers look like 1 to your applications."/><meta name="robots"/><meta property="og:url" content="https://planetscale.com/blog/making-768-servers-look-like-1"/><meta property="og:type" content="website"/><meta property="og:title" content="Making 768 servers look like 1 — PlanetScale"/><meta property="og:image" content="https://planetscale.com/assets/making-768-servers-look-like-1-social-DXjwbEP8.png"/><meta property="og:description" content="How to make 768 distinct Postgres servers look like 1 to your applications."/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:site" content="@PlanetScale"/><meta property="twitter:creator" content="@PlanetScale"/><meta property="twitter:url" content="https://planetscale.com/blog/making-768-servers-look-like-1"/><meta property="twitter:title" content="Making 768 servers look like 1 — PlanetScale"/><meta property="twitter:description" content="How to make 768 distinct Postgres servers look like 1 to your applications."/><meta property="twitter:image" content="https://planetscale.com/assets/making-768-servers-look-like-1-social-DXjwbEP8.png"/><link rel="canonical" href="https://planetscale.com/blog/making-768-servers-look-like-1"/><link rel="preconnect" href="https://planetscale-images.imgix.net"/><link nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g=" rel="icon" href="/favicon.ico" type="image/x-icon" sizes="16x16"/><link nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g=" rel="icon" href="/icon.png" type="image/png" sizes="32x32"/><link nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g=" rel="apple-touch-icon" href="/apple-touch-icon.png" type="image/png" sizes="32x32"/><link nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g=" rel="manifest" href="/manifest.webmanifest"/><link rel="modulepreload" href="/assets/entry.client-DiYH1A1V.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/jsx-runtime-pwqle3qE.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/components-BLtwNKSN.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/_.well-known_.mcp.server-card_.json_-DJn8O8la.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/index-8NvLfvaD.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/errorBoundaries-yLGDCbU5.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/root-DOMFQneg.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/lib-NH-Mo93K.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/analytics.client-q3yXBfwr.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/SiteHeader-C8IOHyKU.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/current-7FcVu_Bp.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/clsx-eT0YPcGk.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/bugs-aQFXyejF.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/keyboard-3wtxEHYT.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/use-tab-direction-BSMfLTB3.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/blog-CDfqizaK.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/blog._slug-YkUgD1w7.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/ContentImage-BDPvDiF9.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/BlogCategoryLink-Be8yE_qo.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/Details-BonjyjR_.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/Skittle-Czoavsr4.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/SiteFooter-CExFN_i9.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/Vimeo-B4x3x_dq.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/YouTube-CgYm3tDl.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/date-CUitv3Ff.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/use-inert-others-DwLISiGN.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/description-DCkKYMp6.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/use-is-mounted-C9p-u-6-.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="modulepreload" href="/assets/types-CTc6JPE_.js" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g="/><link rel="stylesheet" href="/assets/styles-ns8XBZ1D.css"/><script nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g=">window.ENV = {"IMAGE_CDN":"https://planetscale-images.imgix.net","IMAGE_CDN_ENABLED":"true","INTERNAL_API":"https://api.planetscale.com","RELEASE":"dffcc38d-5e2f-4ef2-a67d-ca7705b6731c","SENTRY_DSN":"https://bd81903b44804e22a06bdc0c1a91b303@o499952.ingest.us.sentry.io/4504531942572032"}</script></head><body class="flex min-h-screen flex-col"><div class="bg-neki px-3 py-1 text-center font-medium text-gray-900 dark:font-semibold"><span>Neki, sharded Postgres, is now available.</span> <span class="whitespace-nowrap"><a href="https://auth.planetscale.com/sign-up" class="whitespace-nowrap bg-gray-900 px-sm font-semibold text-white">Get started</a></span></div><header class="relative mb-6 mt-4 bg-primary"><div class="flex flex-col gap-y-3 px-3 sm:px-5 container max-w-7xl"><div class="grid w-full grid-cols-[auto_1fr] grid-rows-1 items-center lg:items-start lg:gap-3"><a aria-label="Go to homepage" class="col-start-1 col-end-2 h-4 w-4 rounded-full text-primary lg:hidden" href="/" data-discover="true"><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 40 40"><path fill="currentColor" d="M0 20C0 8.954 8.954 0 20 0c8.121 0 15.112 4.84 18.245 11.794l-26.45 26.45a20 20 0 0 1-3.225-1.83L24.984 20H20L5.858 34.142A19.94 19.94 0 0 1 0 20M39.999 20.007 20.006 40c11.04-.004 19.99-8.953 19.993-19.993"></path></svg></a><div class="group col-start-2 col-end-3 row-start-1 flex shrink-0 items-center justify-end gap-1.5 lg:gap-3"><div class="flex flex-row gap-2 lg:flex-col lg:gap-1 xl:flex-row"><div class="flex items-center justify-end gap-1 lg:h-4"><a href="https://auth.planetscale.com/sign-in" class="font-semibold text-primary hover:text-orange">Sign in</a></div><div class="flex items-center justify-end gap-0.5 lg:h-4"><form class="btn-sm hidden sm:inline-flex" action="/api/demo-sessions" method="post"><button type="submit" class="btn btn-outline btn-sm hidden sm:inline-flex">View sandbox</button></form><a class="btn btn-sm" href="/contact" data-discover="true">Get in touch</a></div></div></div><div class="col-start-1 col-end-2 flex items-center gap-x-3 lg:row-start-1 lg:h-4"><a aria-label="Go to homepage" class="col-start-1 col-end-2 hidden h-4 w-4 rounded-full text-primary lg:block" href="/" data-discover="true"><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none" viewBox="0 0 40 40"><path fill="currentColor" d="M0 20C0 8.954 8.954 0 20 0c8.121 0 15.112 4.84 18.245 11.794l-26.45 26.45a20 20 0 0 1-3.225-1.83L24.984 20H20L5.858 34.142A19.94 19.94 0 0 1 0 20M39.999 20.007 20.006 40c11.04-.004 19.99-8.953 19.993-19.993"></path></svg></a><nav aria-label="Main" data-orientation="horizontal" class="hidden items-center lg:flex"><ul class="flex flex-wrap gap-x-1 md:flex-nowrap"><li><div data-headlessui-state=""><button class="font-semibold text-primary hover:text-contrast focus-visible:ring-0 ui-open:text-orange" type="button" aria-expanded="false" data-headlessui-state="">Platform<span class="ml-sm inline-block ui-open:rotate-180">▾</span></button></div><span hidden="" style="position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none"></span></li><li class="text-decoration" role="presentation">|</li><li><div data-headlessui-state=""><button class="font-semibold text-primary hover:text-contrast focus-visible:ring-0 ui-open:text-orange" type="button" aria-expanded="false" data-headlessui-state="">Resources<span class="ml-sm inline-block ui-open:rotate-180">▾</span></button></div><span hidden="" style="position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none"></span></li><li class="text-decoration" role="presentation">|</li><li><a class="font-semibold text-primary hover:text-contrast" href="/docs">Documentation</a></li><li class="text-decoration" role="presentation">|</li><li><a class="font-semibold text-primary hover:text-contrast" href="/pricing" data-discover="true">Pricing</a></li><li class="text-decoration" role="presentation">|</li><li><a class="font-semibold text-primary hover:text-contrast" href="/migrate" data-discover="true">Migrate</a></li></ul></nav></div></div><details class="lg:hidden"><summary>Navigation</summary><nav class="dashed-box mt-1 p-3"><ul class="flex flex-wrap gap-x-1 md:flex-nowrap"><li><div data-headlessui-state=""><button class="font-semibold text-primary hover:text-contrast focus-visible:ring-0 ui-open:text-orange" type="button" aria-expanded="false" data-headlessui-state="">Platform<span class="ml-sm inline-block ui-open:rotate-180">▾</span></button></div><span hidden="" style="position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none"></span></li><li class="text-decoration" role="presentation">|</li><li><div data-headlessui-state=""><button class="font-semibold text-primary hover:text-contrast focus-visible:ring-0 ui-open:text-orange" type="button" aria-expanded="false" data-headlessui-state="">Resources<span class="ml-sm inline-block ui-open:rotate-180">▾</span></button></div><span hidden="" style="position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none"></span></li><li class="text-decoration" role="presentation">|</li><li><a class="font-semibold text-primary hover:text-contrast" href="/docs">Documentation</a></li><li class="text-decoration" role="presentation">|</li><li><a class="font-semibold text-primary hover:text-contrast" href="/pricing" data-discover="true">Pricing</a></li><li class="text-decoration" role="presentation">|</li><li><a class="font-semibold text-primary hover:text-contrast" href="/migrate" data-discover="true">Migrate</a></li></ul></nav></details></div></header><main class="container mb-6 flex max-w-7xl flex-1 flex-col px-3 sm:px-5 lg:px-12"><section class=""><p class="block"><a class="pr-sm text-primary hover:text-contrast" href="/blog" data-discover="true">Blog</a><span class="px-sm text-decoration">|</span><a class="px-sm text-blue hover:bg-blue-100 dark:hover:bg-blue-900" href="/blog/category/engineering" data-discover="true">Engineering</a><span class="px-sm text-decoration">|</span><a class="px-sm text-postgres hover:bg-gray-100 dark:hover:bg-gray-800" href="/blog/category/postgres" data-discover="true">PostgreSQL</a></p><div class="flex lg:flex-row-reverse lg:gap-x-6"><div class="lg:sticky lg:top-2 lg:self-start"><button class="absolute right-0 bg-gray-100 px-sm md:block lg:hidden dark:bg-gray-800 -mt-9 hidden"><span class="inline">Table of contents «</span><span class="hidden">Close »</span></button><aside class="tree-nav w-full shrink-0 space-y-3 lg:w-36 hidden lg:block"><div><h4 class="text-secondary">Table of contents</h4><ul><li><a class="font-semibold text-primary hover:text-blue" href="/blog/making-768-servers-look-like-1#growing-pains" data-discover="true">Growing pains</a><ul><li><a class="text-primary hover:text-blue" href="/blog/making-768-servers-look-like-1#1-writes-limited-to-one-server" data-discover="true">1) Writes limited to one server</a></li><li><a class="text-primary hover:text-blue" href="/blog/making-768-servers-look-like-1#2-replicas-do-not-increase-data-capacity" data-discover="true">2) Replicas do not increase data capacity</a></li><li><a class="text-primary hover:text-blue" href="/blog/making-768-servers-look-like-1#3-backups" data-discover="true">3) Backups</a></li></ul></li><li><a class="font-semibold text-primary hover:text-blue" href="/blog/making-768-servers-look-like-1#sharding-with-a-d" data-discover="true">Sharding, with a "d"</a></li><li><a class="font-semibold text-primary hover:text-blue" href="/blog/making-768-servers-look-like-1#the-proxy-layer" data-discover="true">The proxy layer</a></li><li><a class="font-semibold text-primary hover:text-blue" href="/blog/making-768-servers-look-like-1#how-does-it-know" data-discover="true">How does it know?</a></li><li><a class="font-semibold text-primary hover:text-blue" href="/blog/making-768-servers-look-like-1#many-proxies-one-database" data-discover="true">Many proxies, one database</a></li><li><a class="font-semibold text-primary hover:text-blue" href="/blog/making-768-servers-look-like-1#the-full-picture" data-discover="true">The full picture</a></li><li><a class="font-semibold text-primary hover:text-blue" href="/blog/making-768-servers-look-like-1#what-about-everything-else" data-discover="true">What about everything else?</a></li></ul><div class="mb-3 mt-6 border bg-blue-50 p-3 font-semibold text-contrast dark:bg-blue-900"><p>PlanetScale, the fastest cloud Postgres, from $5/month.</p><p><a href="https://app.planetscale.com/new">Start now</a></p></div><p>Get the <a href="/blog/feed.atom">RSS feed</a></p></div></aside></div><article class="min-w-0 flex-grow"><h1>Making 768 servers look like 1</h1><p class="text-secondary"><a class="text-contrast no-underline" href="/blog/author/ben" data-discover="true">Ben Dicken</a> <!-- -->[<a class="no-underline hover:bg-blue-100 dark:hover:bg-blue-900" href="https://x.com/BenjDicken" rel="noopener noreferrer" target="_blank" title="@BenjDicken on X">@<!-- -->BenjDicken</a>]<!-- --> |<!-- --> <time dateTime="2026-07-15">July 15, 2026</time></p><div class="blog-post-body"><iframe class="w-full overflow-hidden [&::-webkit-scrollbar]:hidden aspect-[4/1]" src="/blog/many-servers-appear-as-one/iframe#servers" scrolling="no" style="overflow:hidden;-ms-overflow-style:none;scrollbar-width:none"></iframe><p>This is 768 servers.</p><p>To some, that looks like a lot of computers.<!-- --> <!-- -->To those managing the infrastructure for apps with millions of customers, executing millions of queries per second, pretty normal.<!-- --> <!-- -->Products at this scale frequently require thousands of servers working in unison.</p><p>The most difficult infrastructure component to scale is almost always the database.<!-- --> <!-- -->A single database server cannot handle such demand, so we must spread the queries and data out across many servers with <span class="bg-blue-100/80 text-blue-700 dark:bg-blue-800/80 dark:text-blue-300">database sharding</span>.</p><p>Database sharding is the best way to scale a Postgres or MySQL database for anything beyond a few terabytes of data.<!-- --> <!-- -->Let's look at how we go from a small single-node database, to one with a few terabytes spread across four shards, all the way up to one that is sharded across 768 servers and storing a petabyte of data.</p><h2 id="growing-pains"><a href="#growing-pains">Growing pains</a></h2><p>To understand why sharding is a necessary part of scaling relational databases, we must understand the bottlenecks of less scalable approaches.</p><p>Consider first a simple application architecture.</p><iframe class="w-full overflow-hidden [&::-webkit-scrollbar]:hidden aspect-[10/5]" src="/blog/many-servers-appear-as-one/iframe#popular-arch" scrolling="no" style="overflow:hidden;-ms-overflow-style:none;scrollbar-width:none"></iframe><p>Most applications you've ever used function in this way, or at least did early in their existence.<!-- --> <!-- -->The software running on a client device connects to an app server over the internet.<!-- --> <!-- -->This app server lives in a data center and handles authentication, page loads, and all the server-side logic for how your application behaves.<!-- --> <!-- -->All the persisted data like user accounts, posts, settings, and messages get stored in and retrieved from the database server (where "database server" is typically Postgres or MySQL, though the focus of this article is Postgres).</p><p>Even with a large database servers (10s of CPU cores, 100s of gigabytes of RAM) bottlenecks arise pretty quickly. Typically, it is either CPU constraints due to high query volume, or I/O constraints (IOPS) due to a high volume of reads and writes.</p><p>This is summed up nicely by the Universal Scalability Law:</p><iframe class="w-full overflow-hidden [&::-webkit-scrollbar]:hidden aspect-[10/5]" src="/blog/many-servers-appear-as-one/iframe#universal-scalability-law" scrolling="no" style="overflow:hidden;-ms-overflow-style:none;scrollbar-width:none"></iframe><p>In short, the USL states that resource <em>contention</em> causes scalability to grow sub-linearly with increasing resources, and at a certain point, <em>incoherence</em> causes performance degradation.<!-- --> <!-- -->This is true for Postgres, as with any software system attempting to scale out across many threads or processes on a larger server.</p><p>One way to solve this, at least in the short term, is leveraging read-replicas.</p><iframe class="w-full overflow-hidden [&::-webkit-scrollbar]:hidden aspect-[10/6]" src="/blog/many-servers-appear-as-one/iframe#primary-replicas" scrolling="no" style="overflow:hidden;-ms-overflow-style:none;scrollbar-width:none"></iframe><p>In this configuration, you maintain the original server as a <em>primary</em> and add additional <em>replicas</em> as shown above.</p><p>The primary sends a continuous stream of messages to every replica to ensure they stay up-to-date with the data changes on the primary.<!-- --> <!-- -->Writes (<code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>) can only go to the primary.<!-- --> <!-- -->If writes were allowed to any server, we could end up with conflicting data.<!-- --> <!-- -->Solving this requires complex and slow consensus algorithms, which is possible, but in most cases not ideal for optimal performance.</p><p>However, app servers can send read (<code>SELECT</code>) queries to the replicas.<!-- --> <!-- -->Since most apps have a much higher percent of reads compared to writes, this provides a lot more scalability.<!-- --> <!-- -->(Replicas are also necessary for high availability and data durability, even if query traffic does not require them).</p><p>The database can scale to handle more traffic by adding replicas.<!-- --> <!-- -->An extreme example of this is <a href="https://openai.com/index/scaling-postgresql/">OpenAI's use of 50 replicas on a single Primary</a>.</p><p>It turns out, scaling servers vertically (increasing CPU / RAM) and adding replicas can only take you so far.<!-- --> <!-- -->There are several bottlenecks that cannot be solved in this way</p><h3 id="1-writes-limited-to-one-server"><a href="#1-writes-limited-to-one-server">1) Writes limited to one server</a></h3><p>With high enough write volume, no amount of additional read-only replicas will alleviate an issue.<!-- --> <!-- -->Before Postgres can acknowledge a committed write, it must record the change in its<!-- --> <!-- -->write-ahead log (WAL) and flush that log to durable storage.<!-- --> <!-- -->The WAL is a shared resource amongst all connections on the primary.<!-- --> <!-- -->This is essentially a single write bottleneck across your entire database, even if you have tens of replicas.</p><h3 id="2-replicas-do-not-increase-data-capacity"><a href="#2-replicas-do-not-increase-data-capacity">2) Replicas do not increase data capacity</a></h3><p>A replica is a full copy of the primary's data, including all indexes.<!-- --> <!-- -->Adding replicas gives us more places to run reads, but it does not distribute the data.</p><h3 id="3-backups"><a href="#3-backups">3) Backups</a></h3><p>Backups are an important part of data durability and RPO / RTO guarantees.<!-- --> <!-- -->Taking a backup of a large, monolithic database to object storage can take hours or even days due to the bandwidth limitations of node-to-storage communication.<!-- --> <!-- -->This is unacceptably long for many organizations that rely on frequent and validated backups.</p><p>The most proven way to handle this is sharding.</p><h2 id="sharding-with-a-d"><a href="#sharding-with-a-d">Sharding, with a "d"</a></h2><p>Sharding solves these three bottlenecks by distributing the data and queries across many distinct primaries.<!-- --> <!-- -->For data, it is useful because a single node can only store so much and is limited on write throughput.<!-- --> <!-- -->For queries, this is useful because the network interconnects and CPUs can only process so many queries at a time.</p><iframe class="w-full overflow-hidden [&::-webkit-scrollbar]:hidden aspect-[10/7]" src="/blog/many-servers-appear-as-one/iframe#sharding" scrolling="no" style="overflow:hidden;-ms-overflow-style:none;scrollbar-width:none"></iframe><p>Sharding is useful at all scales past a few terabytes of data.<!-- --> <!-- -->For example, with 2 terabytes of data, we may choose a setup with four shards, each storing 500 gigabytes and handling 1/4th of the total query traffic.<!-- --> <!-- -->When we needed to store a petabyte of data (one million gigabytes), we'd need many more shards.<!-- --> <!-- -->In this case, we can use 256 shards, each with a primary + 2 replicas, and each responsible for storing ~4 terabytes.<!-- --> <!-- -->This requires 256 * 3 = 768 servers!</p><p>Without a good system in place, this adds significant complexity to our app's backend.<!-- --> <!-- -->With so much going on, how does the system...</p><ul><li>Decide which data goes to which server?</li><li>Decide which queries go to which server?</li><li>Handle queries that need to talk to multiple shards simultaneously?</li><li>Take backups across this spread-out database?</li><li>Monitor system-wide health?</li><li>Respond to a failing server?</li></ul><p>There's a lot that could be said in addressing each one of those concerns.<!-- --> <!-- -->But the question to address here in this article is the following:</p><p><span class="bg-blue-100/80 text-blue-700 dark:bg-blue-800/80 dark:text-blue-300">How can these 768 servers look like 1 cohesive database to our apps?</span></p><p>We want to allow the application servers to go from interacting with a complex system, like this:</p><iframe class="w-full overflow-hidden [&::-webkit-scrollbar]:hidden aspect-[2/1]" src="/blog/many-servers-appear-as-one/iframe#tons-of-shards" scrolling="no" style="overflow:hidden;-ms-overflow-style:none;scrollbar-width:none"></iframe><p>To instead interacting with it over a single connection string, making it appear as if it's interfacing with one large, scalable database:</p><iframe class="w-full overflow-hidden [&::-webkit-scrollbar]:hidden aspect-[10/4]" src="/blog/many-servers-appear-as-one/iframe#simple-sharded" scrolling="no" style="overflow:hidden;-ms-overflow-style:none;scrollbar-width:none"></iframe><p>While in reality, utilizing tens or hundreds of shards.<!-- --> <a href="/neki">Neki</a> for Postgres and <a href="https://vitess.io">Vitess</a> for MySQL solve this.<!-- --> <!-- -->Let's see how.</p><h2 id="the-proxy-layer"><a href="#the-proxy-layer">The proxy layer</a></h2><p>The most important amongst several critical pieces here is the proxy layer.</p><p>Proxies are middleware servers that sit between two services.<!-- --> <!-- -->In our case, these two services are the application servers and database servers.</p><p>Proxies are frequently used with Postgres databases.<!-- --> <!-- -->Even when there's no sharding, they are useful for connection pooling and request queuing.<!-- --> <!-- -->For regular (unsharded) Postgres, PgBouncer is a popular proxy that people use to multiplex 1000s of app connections across fewer direct Postgres connections.</p><iframe class="w-full overflow-hidden [&::-webkit-scrollbar]:hidden aspect-[2/1]" src="/blog/many-servers-appear-as-one/iframe#pgbouncer" scrolling="no" style="overflow:hidden;-ms-overflow-style:none;scrollbar-width:none"></iframe><p>PgBouncer has a simple goal.<!-- --> <!-- -->It's built to accept a large number of connections from many clients and route them through a smaller pool of connections that it continually maintains with Postgres.<!-- --> <!-- -->The query queuing is useful for traffic surges and during database failover, so requests can resume when the new primary comes online.<!-- --> <!-- -->We have a whole <a href="/blog/scaling-postgres-connections-with-pgbouncer">blog on PgBouncer</a> if you want to learn more.</p><p>Sharding Postgres requires an even more sophisticated proxy.<!-- --> <!-- -->The biggest difference is that, in addition to multiplexing and buffering, the proxy must understand how data is distributed across servers and route SQL queries to the correct shards.<!-- --> <!-- -->Because of this, we refer to it as a <em>router</em>.</p><p>When inserting data, the router must be aware of how data is to be distributed.<!-- --> <!-- -->This is known as the <a href="/blog/database-sharding#sharding-strategy">sharding strategy</a>.</p><p>A common approach is to shard incoming rows based on a hash of an id column.<!-- --> <!-- -->When inserting row like this into the database:</p><div class="code-block" data-language="SQL"><div class="min-w-0 max-w-full"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> INSERT INTO</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> users (id, username, email) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">VALUES</span></span>
|
|
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'ada'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'ada@example.com'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">),</span></span>
|
|
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'grace'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'grace@example.com'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">),</span></span>
|
|
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">3</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'linus'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'linus@example.com'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">),</span></span>
|
|
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">4</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'margaret'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'margaret@example.com'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">),</span></span>
|
|
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">5</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'dennis'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'dennis@example.com'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">),</span></span>
|
|
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">6</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'barbara'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'barbara@example.com'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">),</span></span>
|
|
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">7</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'donald'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'donald@example.com'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">),</span></span>
|
|
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">8</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'james'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'james@example.com'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
|
|
<span class="line"></span></code></pre></div></div><p>Each of the four shards is assigned a range of IDs that it's responsible for storing, and the router sends the inserts to the correct shard.<!-- --> <!-- -->The insertions first get sent to the router, where it computes a hash of each ID, then forwards it along to the correct shard.</p><iframe class="w-full overflow-hidden [&::-webkit-scrollbar]:hidden aspect-[10/7]" src="/blog/many-servers-appear-as-one/iframe#shard-inserts" scrolling="no" style="overflow:hidden;-ms-overflow-style:none;scrollbar-width:none"></iframe><p>When it comes to reads, some queries are simple enough such that the router passes them along to a single shard.</p><div class="code-block" data-language="sql"><div class="min-w-0 max-w-full"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> email </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> user </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 4</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
|
|
<span class="line"></span></code></pre></div></div><p>In this case, all the router needs to do is have an internal mapping of which user IDs live in which servers, and forward that query on.<!-- --> <!-- -->Based on the example above, this would be the first (top) shard.</p><p>Some cases are more complex.</p><div class="code-block" data-language="sql"><div class="min-w-0 max-w-full"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> email </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">FROM</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> user</span></span>
|
|
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">BETWEEN</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 3</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> AND</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 5</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
|
|
<span class="line"></span></code></pre></div></div><p>Users with this range of IDs are spread out across several shards.<!-- --> <!-- -->The router must understand the data topology, create a plan for distributing the query to all shards that may contain matching results, aggregate the results back at the router, and send the full result set to the client.</p><p>Ultimately, this means the router itself must have a full query parser and routing planner built in.</p><iframe class="w-full overflow-hidden [&::-webkit-scrollbar]:hidden aspect-[3/1]" src="/blog/many-servers-appear-as-one/iframe#proxy-plan" scrolling="no" style="overflow:hidden;-ms-overflow-style:none;scrollbar-width:none"></iframe><p>The router must be able to perform query parsing, planning, connection pooling, and buffering, all within a single system.<!-- --> <!-- -->Complex software is hard to get right.</p><h2 id="how-does-it-know"><a href="#how-does-it-know">How does it know?</a></h2><p>Every database is unique, with its own schema, tables, and query patterns.<!-- --> <!-- -->How then can a router generically know which data, and which queries, go where?</p><p>In both <a href="/neki">Neki</a> and <a href="https://vitess.io/docs/reference/features/vschema/">Vitess</a>, these are specified via JSON files representing the data topology of the system.<!-- --> <!-- -->Vitess' VSchema and Neki's data topology give engineers a ton of flexibility to describe precisely how tables and queries should be distributed.<!-- --> <!-- -->Below is a simplified example of how we would specify a sharding scheme for a <code>user</code> table:</p><div class="code-block" data-language="json"><div class="min-w-0 max-w-full"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">{</span></span>
|
|
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">shard_indexes</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
|
|
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">user_hash</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
|
|
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">type</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "hash"</span></span>
|
|
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> }</span></span>
|
|
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> },</span></span>
|
|
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">tables</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
|
|
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">user</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
|
|
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">shard_by</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "user_hash"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span></span>
|
|
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">column</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "id"</span></span>
|
|
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> }</span></span>
|
|
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> }</span></span>
|
|
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">}</span></span>
|
|
<span class="line"></span></code></pre></div></div><p>This metadata is stored in the router, and tells it that the <code>user</code> table is sharded on its <code>id</code> column using the <code>user_hash</code> shard index.<!-- --> <!-- -->This <code>user_hash</code> shard index uses the router's built-in value hashing.<!-- --> <!-- -->For each incoming row, it hashed the ID, and uses this to send it to the correct shard to be stored.</p><p>Since this is all communicated to the router via text and JSON, AI agents are great for configuration and optimization here.</p><h2 id="many-proxies-one-database"><a href="#many-proxies-one-database">Many proxies, one database</a></h2><p>At a scale of 256 shards spanning 768 servers and millions of queries per second, we cannot route all of this traffic through a single proxy.<!-- --> <!-- -->We need many!<!-- --> <!-- -->Perhaps 10, perhaps 100, depending on the shape of the traffic.</p><p>We'd still like our apps to think of this as a single server.<!-- --> <!-- -->This is where a Network Load Balancer (NLB) helps.</p><p>NLBs have a simple job: Allow connections via a single host/IP, and assign each connection to one of many destinations.<!-- --> <!-- -->This is how traffic is distributed across the routers.<!-- --> <!-- -->Once assigned, a connection remains with the same proxy for its lifetime.</p><iframe class="w-full overflow-hidden [&::-webkit-scrollbar]:hidden aspect-[10/6]" src="/blog/many-servers-appear-as-one/iframe#full-sharded" scrolling="no" style="overflow:hidden;-ms-overflow-style:none;scrollbar-width:none"></iframe><p>In some cases, an NLB is not necessary.<!-- --> <!-- -->Eliminating an NLB adds slightly more complexity to the app server's connection logic, as it will have to be aware of each router's host, but eliminates a network hop, keeping round-trip latency to a minimum.</p><h2 id="the-full-picture"><a href="#the-full-picture">The full picture</a></h2><p>Now all the pieces are in place to make 768 servers storing 1,000 terabytes of data appear as a single, monolithic database to our apps.</p><ol><li>An app server is told "connect to the database at <code>mydb.pscale.com</code>"</li><li>A DNS lookup is performed, returning the NLB's IP address: <code>123.152.100.4</code></li><li>The app requests to connect to the database at <code>123.152.100.4</code></li><li>This routes the connection first through the NLB, then to one of the N proxies</li><li>The app begins sending database queries, which go app -> NLB (optional) -> proxy -> shards. The complex routing logic is hidden from the application. (NLB not pictured below, for simplicity)</li></ol><iframe class="w-full overflow-hidden [&::-webkit-scrollbar]:hidden aspect-[4/3]" src="/blog/many-servers-appear-as-one/iframe#shard-formation" scrolling="no" style="overflow:hidden;-ms-overflow-style:none;scrollbar-width:none"></iframe><p>This example shows scaling up to 1 petabyte, but sharding should begin long before this scale.<!-- --> <!-- -->The precise recommendations depend on each database's size, schema, and QPS, but we recommend sharding Postgres and MySQL for anything beyond a few terabytes of data.<!-- --> <!-- -->That's the point where you typically begin hitting the bottlenecks described earlier: long backups, write bottlenecks, etc.<!-- --> <!-- -->If you are facing challenges scaling relational databases, Neki and Vitess are the solutions.</p><p><a href="/vitess">Vitess</a> for MySQL has been used for over a decade to scale the world's biggest relational databases.<!-- --> <!-- -->We have years of experience operating large, sharded databases for our customers, and are the core maintainers of the Vitess project.<!-- --> <a href="/neki">Neki</a> was developed by the same expert maintainers of Vitess, bringing an even more powerful sharding system to Postgres.</p><h2 id="what-about-everything-else"><a href="#what-about-everything-else">What about everything else?</a></h2><p>We've only scratched the surface of everything sharding systems like Neki and Vitess provide.<!-- --> <!-- -->There are so many other interesting details.<!-- --> <!-- -->What's the best way to shard data?<!-- --> <!-- -->How do sharded databases handle failures?<!-- --> <!-- -->How do you change the number of shards?<!-- --> <!-- -->How do you take backups across 256 shards at the same time?</p><p>Stay tuned for more here.<!-- --> <!-- -->Follow our <a href="/blog/feed.atom">RSS feed</a> or on <a href="https://x.com/planetscale">X</a> to stay in the loop.</p><p>Happy sharding.</p></div></article></div></section></main><footer class="mb-6 mt-10 px-3 sm:px-5 container max-w-7xl"><nav class="grid grid-cols-1 text-left sm:grid-cols-2 lg:grid-cols-5 lg:mx-7"><div class="dashed-box dashed-box-x-t sm:dashed-box-l-t lg:dashed-box-y-l p-3"><h2 class="font-semibold">Company</h2><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/about" data-discover="true">About</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/brand" data-discover="true">Brand</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/blog" data-discover="true">Blog</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/changelog" data-discover="true">Changelog</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/careers" data-discover="true">Careers</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/events" data-discover="true">Events</a></div><div class="dashed-box dashed-box-x-t lg:dashed-box-y-l p-3"><h2 class="font-semibold">Product</h2><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/case-studies" data-discover="true">Case studies</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/enterprise" data-discover="true">Enterprise</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/pricing" data-discover="true">Pricing</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/benchmarks" data-discover="true">Benchmarks</a></div><div class="dashed-box dashed-box-x-t sm:dashed-box-l-t lg:dashed-box-y-l p-3"><h2 class="font-semibold">Resources</h2><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/docs">Documentation</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/migrate" data-discover="true">Migrate</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="https://support.planetscale.com/hc/en-us" rel="nofollow noopener noreferrer" target="_blank">Support</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="https://planetscalestatus.com" rel="nofollow noopener noreferrer" target="_blank">Status</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="https://trust.planetscale.com" rel="nofollow noopener noreferrer" target="_blank">Trust Center</a></div><div class="dashed-box dashed-box-x-t lg:dashed-box-y-l p-3"><h2 class="font-semibold">Courses</h2><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/learn/courses/mysql-for-developers" data-discover="true">MySQL for Developers</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/learn/courses/database-scaling" data-discover="true">Database Scaling</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/learn/courses/vitess" data-discover="true">Learn Vitess</a></div><div class="dashed-box p-3 sm:col-span-2 lg:col-span-1"><h2 class="font-semibold text-primary hover:text-contrast">Open source</h2><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="/vitess" data-discover="true">Vitess</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="https://vitess.io/slack" rel="nofollow noopener noreferrer" target="_blank">Vitess community</a><a class="block pl-1ch -indent-1ch text-primary hover:text-contrast" href="https://github.com/planetscale" rel="me nofollow noopener noreferrer" target="_blank">GitHub</a></div></nav><div class="dashed-box dashed-box-x-b p-3 lg:mx-7"><p class="mb-3 md:mb-0"><a class="text-primary" rel="nofollow" href="/legal/privacy" data-discover="true">Privacy</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a class="text-primary" rel="nofollow" href="/legal/siteterms" data-discover="true">Terms</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a class="text-primary" rel="nofollow" href="/legal/cookies" data-discover="true">Cookies</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a class="text-primary" rel="nofollow" href="/legal/patents" data-discover="true">Patents</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a class="text-primary" rel="nofollow" href="/legal/privacy#privacy-rights-and-choices" data-discover="true">Do Not Share My Personal Information</a></p><p class="text-secondary">© <!-- -->2026<!-- --> PlanetScale, Inc. All rights reserved.</p></div><p class="mb-0 mt-3 break-normal lg:mx-7"><a class="text-primary" href="https://github.com/planetscale" rel="me nofollow noopener noreferrer" target="_blank">GitHub</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a aria-label="X (formerly Twitter)" class="text-primary" href="https://twitter.com/planetscale" rel="me nofollow noopener noreferrer" target="_blank">X</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a aria-label="LinkedIn" class="text-primary" href="https://www.linkedin.com/company/planetscale" target="_blank" rel="noreferrer">LinkedIn</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a class="text-primary" href="https://www.youtube.com/planetscale" rel="me nofollow noopener noreferrer" target="_blank">YouTube</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a aria-label="Discord" class="text-primary" href="https://pscale.link/community" rel="nofollow noopener noreferrer" target="_blank">Discord</a><span class="text-decoration" role="presentation"> <!-- -->|<!-- --> </span><a class="text-primary" href="https://www.facebook.com/planetscaledata" rel="me nofollow noopener noreferrer" target="_blank">Facebook</a></p></footer><script nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g=">((storageKey2, restoreKey) => {
|
|
if (!window.history.state || !window.history.state.key) {
|
|
let key2 = Math.random().toString(32).slice(2);
|
|
window.history.replaceState({ key: key2 }, "");
|
|
}
|
|
try {
|
|
let storedY = JSON.parse(sessionStorage.getItem(storageKey2) || "{}")[restoreKey || window.history.state.key];
|
|
if (typeof storedY === "number") window.scrollTo(0, storedY);
|
|
} catch (error2) {
|
|
console.error(error2);
|
|
sessionStorage.removeItem(storageKey2);
|
|
}
|
|
})("react-router-scroll-positions", null)</script><script nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g=">window.__reactRouterContext = {"basename":"/","future":{"unstable_enableNodeReadableStream":false,"unstable_optimizeDeps":true},"routeDiscovery":{"mode":"lazy","manifestPath":"/__manifest"},"ssr":true,"isSpaMode":false};window.__reactRouterContext.stream = new ReadableStream({start(controller){window.__reactRouterContext.streamController = controller;}}).pipeThrough(new TextEncoderStream());</script><script nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g=" type="module" async="">;
|
|
import * as route0 from "/assets/root-DOMFQneg.js";
|
|
import * as route1 from "/assets/blog-CDfqizaK.js";
|
|
import * as route2 from "/assets/blog._slug-YkUgD1w7.js";
|
|
window.__reactRouterManifest = {
|
|
"entry": {
|
|
"module": "/assets/entry.client-DiYH1A1V.js",
|
|
"imports": [
|
|
"/assets/jsx-runtime-pwqle3qE.js",
|
|
"/assets/components-BLtwNKSN.js",
|
|
"/assets/_.well-known_.mcp.server-card_.json_-DJn8O8la.js",
|
|
"/assets/index-8NvLfvaD.js",
|
|
"/assets/errorBoundaries-yLGDCbU5.js"
|
|
],
|
|
"css": []
|
|
},
|
|
"routes": {
|
|
"root": {
|
|
"id": "root",
|
|
"path": "",
|
|
"hasAction": false,
|
|
"hasLoader": true,
|
|
"hasClientAction": false,
|
|
"hasClientLoader": false,
|
|
"hasClientMiddleware": false,
|
|
"hasDefaultExport": true,
|
|
"hasErrorBoundary": true,
|
|
"module": "/assets/root-DOMFQneg.js",
|
|
"imports": [
|
|
"/assets/jsx-runtime-pwqle3qE.js",
|
|
"/assets/components-BLtwNKSN.js",
|
|
"/assets/_.well-known_.mcp.server-card_.json_-DJn8O8la.js",
|
|
"/assets/index-8NvLfvaD.js",
|
|
"/assets/errorBoundaries-yLGDCbU5.js",
|
|
"/assets/lib-NH-Mo93K.js",
|
|
"/assets/analytics.client-q3yXBfwr.js",
|
|
"/assets/SiteHeader-C8IOHyKU.js",
|
|
"/assets/current-7FcVu_Bp.js",
|
|
"/assets/clsx-eT0YPcGk.js",
|
|
"/assets/bugs-aQFXyejF.js",
|
|
"/assets/keyboard-3wtxEHYT.js",
|
|
"/assets/use-tab-direction-BSMfLTB3.js"
|
|
],
|
|
"css": []
|
|
},
|
|
"routes/blog": {
|
|
"id": "routes/blog",
|
|
"parentId": "root",
|
|
"path": "blog",
|
|
"hasAction": false,
|
|
"hasLoader": false,
|
|
"hasClientAction": false,
|
|
"hasClientLoader": false,
|
|
"hasClientMiddleware": false,
|
|
"hasDefaultExport": false,
|
|
"hasErrorBoundary": false,
|
|
"module": "/assets/blog-CDfqizaK.js",
|
|
"imports": [
|
|
"/assets/_.well-known_.mcp.server-card_.json_-DJn8O8la.js"
|
|
],
|
|
"css": []
|
|
},
|
|
"routes/blog.$slug": {
|
|
"id": "routes/blog.$slug",
|
|
"parentId": "routes/blog",
|
|
"path": ":slug",
|
|
"hasAction": false,
|
|
"hasLoader": true,
|
|
"hasClientAction": false,
|
|
"hasClientLoader": false,
|
|
"hasClientMiddleware": false,
|
|
"hasDefaultExport": true,
|
|
"hasErrorBoundary": false,
|
|
"module": "/assets/blog._slug-YkUgD1w7.js",
|
|
"imports": [
|
|
"/assets/components-BLtwNKSN.js",
|
|
"/assets/lib-NH-Mo93K.js",
|
|
"/assets/jsx-runtime-pwqle3qE.js",
|
|
"/assets/ContentImage-BDPvDiF9.js",
|
|
"/assets/clsx-eT0YPcGk.js",
|
|
"/assets/BlogCategoryLink-Be8yE_qo.js",
|
|
"/assets/_.well-known_.mcp.server-card_.json_-DJn8O8la.js",
|
|
"/assets/Details-BonjyjR_.js",
|
|
"/assets/Skittle-Czoavsr4.js",
|
|
"/assets/SiteFooter-CExFN_i9.js",
|
|
"/assets/SiteHeader-C8IOHyKU.js",
|
|
"/assets/Vimeo-B4x3x_dq.js",
|
|
"/assets/YouTube-CgYm3tDl.js",
|
|
"/assets/date-CUitv3Ff.js",
|
|
"/assets/errorBoundaries-yLGDCbU5.js",
|
|
"/assets/keyboard-3wtxEHYT.js",
|
|
"/assets/use-tab-direction-BSMfLTB3.js",
|
|
"/assets/index-8NvLfvaD.js",
|
|
"/assets/use-inert-others-DwLISiGN.js",
|
|
"/assets/description-DCkKYMp6.js",
|
|
"/assets/use-is-mounted-C9p-u-6-.js",
|
|
"/assets/types-CTc6JPE_.js",
|
|
"/assets/current-7FcVu_Bp.js",
|
|
"/assets/analytics.client-q3yXBfwr.js",
|
|
"/assets/bugs-aQFXyejF.js"
|
|
],
|
|
"css": []
|
|
},
|
|
"routes/_index": {
|
|
"id": "routes/_index",
|
|
"parentId": "root",
|
|
"index": true,
|
|
"hasAction": false,
|
|
"hasLoader": true,
|
|
"hasClientAction": false,
|
|
"hasClientLoader": false,
|
|
"hasClientMiddleware": false,
|
|
"hasDefaultExport": true,
|
|
"hasErrorBoundary": false,
|
|
"module": "/assets/_index-BUbedsx2.js",
|
|
"imports": [
|
|
"/assets/components-BLtwNKSN.js",
|
|
"/assets/lib-NH-Mo93K.js",
|
|
"/assets/jsx-runtime-pwqle3qE.js",
|
|
"/assets/Logo-BkZqDvxt.js",
|
|
"/assets/SiteFooter-CExFN_i9.js",
|
|
"/assets/SiteHeader-C8IOHyKU.js",
|
|
"/assets/_.well-known_.mcp.server-card_.json_-DJn8O8la.js",
|
|
"/assets/bugs-aQFXyejF.js",
|
|
"/assets/keyboard-3wtxEHYT.js",
|
|
"/assets/use-is-mounted-C9p-u-6-.js",
|
|
"/assets/use-tab-direction-BSMfLTB3.js",
|
|
"/assets/errorBoundaries-yLGDCbU5.js",
|
|
"/assets/clsx-eT0YPcGk.js",
|
|
"/assets/current-7FcVu_Bp.js",
|
|
"/assets/analytics.client-q3yXBfwr.js",
|
|
"/assets/index-8NvLfvaD.js"
|
|
],
|
|
"css": []
|
|
},
|
|
"routes/blog._index": {
|
|
"id": "routes/blog._index",
|
|
"parentId": "routes/blog",
|
|
"index": true,
|
|
"hasAction": false,
|
|
"hasLoader": true,
|
|
"hasClientAction": false,
|
|
"hasClientLoader": false,
|
|
"hasClientMiddleware": false,
|
|
"hasDefaultExport": true,
|
|
"hasErrorBoundary": false,
|
|
"module": "/assets/blog._index-Bhw4PhiA.js",
|
|
"imports": [
|
|
"/assets/components-BLtwNKSN.js",
|
|
"/assets/jsx-runtime-pwqle3qE.js",
|
|
"/assets/social-Cd2AtOZM.js",
|
|
"/assets/BlogCategoryLink-Be8yE_qo.js",
|
|
"/assets/BlogPostLink-CAObyJ0I.js",
|
|
"/assets/BlogCategoryNav-BBAvISRt.js",
|
|
"/assets/Paginator-DP94NGkp.js",
|
|
"/assets/SiteFooter-CExFN_i9.js",
|
|
"/assets/SiteHeader-C8IOHyKU.js",
|
|
"/assets/date-CUitv3Ff.js",
|
|
"/assets/_.well-known_.mcp.server-card_.json_-DJn8O8la.js",
|
|
"/assets/lib-NH-Mo93K.js",
|
|
"/assets/errorBoundaries-yLGDCbU5.js",
|
|
"/assets/clsx-eT0YPcGk.js",
|
|
"/assets/types-CTc6JPE_.js",
|
|
"/assets/enumerator-C3t_Umyh.js",
|
|
"/assets/current-7FcVu_Bp.js",
|
|
"/assets/analytics.client-q3yXBfwr.js",
|
|
"/assets/bugs-aQFXyejF.js",
|
|
"/assets/keyboard-3wtxEHYT.js",
|
|
"/assets/use-tab-direction-BSMfLTB3.js",
|
|
"/assets/index-8NvLfvaD.js"
|
|
],
|
|
"css": []
|
|
}
|
|
},
|
|
"url": "/assets/manifest-ba054bc1.js",
|
|
"version": "ba054bc1"
|
|
};
|
|
window.__reactRouterRouteModules = {"root":route0,"routes/blog":route1,"routes/blog.$slug":route2};
|
|
|
|
import("/assets/entry.client-DiYH1A1V.js");</script><script type="application/ld+json" nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g=">{"@context":"https://schema.org","@type":"Organization","name":"PlanetScale, Inc.","url":"https://planetscale.com","sameAs":["https://twitter.com/PlanetScale","https://www.facebook.com/planetscaledata/","https://www.instagram.com/planetscale/"],"address":{"@type":"PostalAddress","streetAddress":"WeWork c/o PlanetScale, 535 Mission Street, 14th Floor","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94105","addressCountry":"US"}}</script><!--$--><script nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g=">window.__reactRouterContext.streamController.enqueue("[{\"_1\":2,\"_3\":-5,\"_4\":-5},\"loaderData\",{\"_5\":6,\"_7\":8},\"actionData\",\"errors\",\"root\",{\"_878\":879},\"routes/blog.$slug\",{\"_9\":10,\"_5\":11},\"blog\",{\"_12\":13,\"_14\":15,\"_16\":-7,\"_17\":18,\"_19\":20,\"_21\":22,\"_23\":24,\"_25\":26,\"_27\":28,\"_29\":30,\"_31\":32},\"https://planetscale.com\",\"body\",[91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174],\"body_text\",\"This is 768 servers.\\nTo some, that looks like a lot of computers.To those managing the infrastructure for apps with millions of customers, executing millions of queries per second, pretty normal.Products at this scale frequently require thousands of servers working in unison.\\nThe most difficult infrastructure component to scale is almost always the database.A single database server cannot handle such demand, so we must spread the queries and data out across many servers with database sharding.\\nDatabase sharding is the best way to scale a Postgres or MySQL database for anything beyond a few terabytes of data.Let's look at how we go from a small single-node database, to one with a few terabytes spread across four shards, all the way up to one that is sharded across 768 servers and storing a petabyte of data.\\nGrowing pains\\nTo understand why sharding is a necessary part of scaling relational databases, we must understand the bottlenecks of less scalable approaches.\\nConsider first a simple application architecture.\\nMost applications you've ever used function in this way, or at least did early in their existence.The software running on a client device connects to an app server over the internet.This app server lives in a data center and handles authentication, page loads, and all the server-side logic for how your application behaves.All the persisted data like user accounts, posts, settings, and messages get stored in and retrieved from the database server (where \\\"database server\\\" is typically Postgres or MySQL, though the focus of this article is Postgres).\\nEven with a large database servers (10s of CPU cores, 100s of gigabytes of RAM) bottlenecks arise pretty quickly. Typically, it is either CPU constraints due to high query volume, or I/O constraints (IOPS) due to a high volume of reads and writes.\\nThis is summed up nicely by the Universal Scalability Law:\\nIn short, the USL states that resource contention causes scalability to grow sub-linearly with increasing resources, and at a certain point, incoherence causes performance degradation.This is true for Postgres, as with any software system attempting to scale out across many threads or processes on a larger server.\\nOne way to solve this, at least in the short term, is leveraging read-replicas.\\nIn this configuration, you maintain the original server as a primary and add additional replicas as shown above.\\nThe primary sends a continuous stream of messages to every replica to ensure they stay up-to-date with the data changes on the primary.Writes (INSERT, UPDATE, DELETE) can only go to the primary.If writes were allowed to any server, we could end up with conflicting data.Solving this requires complex and slow consensus algorithms, which is possible, but in most cases not ideal for optimal performance.\\nHowever, app servers can send read (SELECT) queries to the replicas.Since most apps have a much higher percent of reads compared to writes, this provides a lot more scalability.(Replicas are also necessary for high availability and data durability, even if query traffic does not require them).\\nThe database can scale to handle more traffic by adding replicas.An extreme example of this is OpenAI's use of 50 replicas on a single Primary.\\nIt turns out, scaling servers vertically (increasing CPU / RAM) and adding replicas can only take you so far.There are several bottlenecks that cannot be solved in this way\\n1) Writes limited to one server\\nWith high enough write volume, no amount of additional read-only replicas will alleviate an issue.Before Postgres can acknowledge a committed write, it must record the change in itswrite-ahead log (WAL) and flush that log to durable storage.The WAL is a shared resource amongst all connections on the primary.This is essentially a single write bottleneck across your entire database, even if you have tens of replicas.\\n2) Replicas do not increase data capacity\\nA replica is a full copy of the primary's data, including all indexes.Adding replicas gives us more places to run reads, but it does not distribute the data.\\n3) Backups\\nBackups are an important part of data durability and RPO / RTO guarantees.Taking a backup of a large, monolithic database to object storage can take hours or even days due to the bandwidth limitations of node-to-storage communication.This is unacceptably long for many organizations that rely on frequent and validated backups.\\nThe most proven way to handle this is sharding.\\nSharding, with a \\\"d\\\"\\nSharding solves these three bottlenecks by distributing the data and queries across many distinct primaries.For data, it is useful because a single node can only store so much and is limited on write throughput.For queries, this is useful because the network interconnects and CPUs can only process so many queries at a time.\\nSharding is useful at all scales past a few terabytes of data.For example, with 2 terabytes of data, we may choose a setup with four shards, each storing 500 gigabytes and handling 1/4th of the total query traffic.When we needed to store a petabyte of data (one million gigabytes), we'd need many more shards.In this case, we can use 256 shards, each with a primary + 2 replicas, and each responsible for storing ~4 terabytes.This requires 256 * 3 = 768 servers!\\nWithout a good system in place, this adds significant complexity to our app's backend.With so much going on, how does the system...\\nDecide which data goes to which server?\\nDecide which queries go to which server?\\nHandle queries that need to talk to multiple shards simultaneously?\\nTake backups across this spread-out database?\\nMonitor system-wide health?\\nRespond to a failing server?\\nThere's a lot that could be said in addressing each one of those concerns.But the question to address here in this article is the following:\\nHow can these 768 servers look like 1 cohesive database to our apps?\\nWe want to allow the application servers to go from interacting with a complex system, like this:\\nTo instead interacting with it over a single connection string, making it appear as if it's interfacing with one large, scalable database:\\nWhile in reality, utilizing tens or hundreds of shards.Neki for Postgres and Vitess for MySQL solve this.Let's see how.\\nThe proxy layer\\nThe most important amongst several critical pieces here is the proxy layer.\\nProxies are middleware servers that sit between two services.In our case, these two services are the application servers and database servers.\\nProxies are frequently used with Postgres databases.Even when there's no sharding, they are useful for connection pooling and request queuing.For regular (unsharded) Postgres, PgBouncer is a popular proxy that people use to multiplex 1000s of app connections across fewer direct Postgres connections.\\nPgBouncer has a simple goal.It's built to accept a large number of connections from many clients and route them through a smaller pool of connections that it continually maintains with Postgres.The query queuing is useful for traffic surges and during database failover, so requests can resume when the new primary comes online.We have a whole blog on PgBouncer if you want to learn more.\\nSharding Postgres requires an even more sophisticated proxy.The biggest difference is that, in addition to multiplexing and buffering, the proxy must understand how data is distributed across servers and route SQL queries to the correct shards.Because of this, we refer to it as a router.\\nWhen inserting data, the router must be aware of how data is to be distributed.This is known as the sharding strategy.\\nA common approach is to shard incoming rows based on a hash of an id column.When inserting row like this into the database: INSERT INTO users (id, username, email) VALUES\\n (1, 'ada', 'ada@example.com'),\\n (2, 'grace', 'grace@example.com'),\\n (3, 'linus', 'linus@example.com'),\\n (4, 'margaret', 'margaret@example.com'),\\n (5, 'dennis', 'dennis@example.com'),\\n (6, 'barbara', 'barbara@example.com'),\\n (7, 'donald', 'donald@example.com'),\\n (8, 'james', 'james@example.com');\\n\\nEach of the four shards is assigned a range of IDs that it's responsible for storing, and the router sends the inserts to the correct shard.The insertions first get sent to the router, where it computes a hash of each ID, then forwards it along to the correct shard.\\nWhen it comes to reads, some queries are simple enough such that the router passes them along to a single shard.SELECT email from user where id = 4;\\n\\nIn this case, all the router needs to do is have an internal mapping of which user IDs live in which servers, and forward that query on.Based on the example above, this would be the first (top) shard.\\nSome cases are more complex.SELECT email FROM user\\n WHERE id BETWEEN 3 AND 5;\\n\\nUsers with this range of IDs are spread out across several shards.The router must understand the data topology, create a plan for distributing the query to all shards that may contain matching results, aggregate the results back at the router, and send the full result set to the client.\\nUltimately, this means the router itself must have a full query parser and routing planner built in.\\nThe router must be able to perform query parsing, planning, connection pooling, and buffering, all within a single system.Complex software is hard to get right.\\nHow does it know?\\nEvery database is unique, with its own schema, tables, and query patterns.How then can a router generically know which data, and which queries, go where?\\nIn both Neki and Vitess, these are specified via JSON files representing the data topology of the system.Vitess' VSchema and Neki's data topology give engineers a ton of flexibility to describe precisely how tables and queries should be distributed.Below is a simplified example of how we would specify a sharding scheme for a user table:{\\n \\\"shard_indexes\\\": {\\n \\\"user_hash\\\": {\\n \\\"type\\\": \\\"hash\\\"\\n }\\n },\\n \\\"tables\\\": {\\n \\\"user\\\": {\\n \\\"shard_by\\\": \\\"user_hash\\\",\\n \\\"column\\\": \\\"id\\\"\\n }\\n }\\n}\\n\\nThis metadata is stored in the router, and tells it that the user table is sharded on its id column using the user_hash shard index.This user_hash shard index uses the router's built-in value hashing.For each incoming row, it hashed the ID, and uses this to send it to the correct shard to be stored.\\nSince this is all communicated to the router via text and JSON, AI agents are great for configuration and optimization here.\\nMany proxies, one database\\nAt a scale of 256 shards spanning 768 servers and millions of queries per second, we cannot route all of this traffic through a single proxy.We need many!Perhaps 10, perhaps 100, depending on the shape of the traffic.\\nWe'd still like our apps to think of this as a single server.This is where a Network Load Balancer (NLB) helps.\\nNLBs have a simple job: Allow connections via a single host/IP, and assign each connection to one of many destinations.This is how traffic is distributed across the routers.Once assigned, a connection remains with the same proxy for its lifetime.\\nIn some cases, an NLB is not necessary.Eliminating an NLB adds slightly more complexity to the app server's connection logic, as it will have to be aware of each router's host, but eliminates a network hop, keeping round-trip latency to a minimum.\\nThe full picture\\nNow all the pieces are in place to make 768 servers storing 1,000 terabytes of data appear as a single, monolithic database to our apps.\\nAn app server is told \\\"connect to the database at mydb.pscale.com\\\"\\nA DNS lookup is performed, returning the NLB's IP address: 123.152.100.4\\nThe app requests to connect to the database at 123.152.100.4\\nThis routes the connection first through the NLB, then to one of the N proxies\\nThe app begins sending database queries, which go app -\u003e NLB (optional) -\u003e proxy -\u003e shards. The complex routing logic is hidden from the application. (NLB not pictured below, for simplicity)\\nThis example shows scaling up to 1 petabyte, but sharding should begin long before this scale.The precise recommendations depend on each database's size, schema, and QPS, but we recommend sharding Postgres and MySQL for anything beyond a few terabytes of data.That's the point where you typically begin hitting the bottlenecks described earlier: long backups, write bottlenecks, etc.If you are facing challenges scaling relational databases, Neki and Vitess are the solutions.\\nVitess for MySQL has been used for over a decade to scale the world's biggest relational databases.We have years of experience operating large, sharded databases for our customers, and are the core maintainers of the Vitess project.Neki was developed by the same expert maintainers of Vitess, bringing an even more powerful sharding system to Postgres.\\nWhat about everything else?\\nWe've only scratched the surface of everything sharding systems like Neki and Vitess provide.There are so many other interesting details.What's the best way to shard data?How do sharded databases handle failures?How do you change the number of shards?How do you take backups across 256 shards at the same time?\\nStay tuned for more here.Follow our RSS feed or on X to stay in the loop.\\nHappy sharding.\",\"aside\",\"toc\",[46,47,48,49,50,51,52],\"title\",\"Making 768 servers look like 1\",\"authors\",[40],\"categories\",[38,39],\"excerpt\",\"How to make 768 distinct Postgres servers look like 1 to your applications.\",\"createdAt\",\"2026-07-15\",\"slug\",\"making-768-servers-look-like-1\",\"meta\",{\"_33\":34,\"_35\":26,\"_36\":37,\"_19\":20},\"canonical\",\"https://planetscale.com/blog/making-768-servers-look-like-1\",\"description\",\"image\",\"/assets/making-768-servers-look-like-1-social-DXjwbEP8.png\",\"engineering\",\"postgres\",{\"_29\":41,\"_42\":43,\"_44\":45},\"ben\",\"name\",\"Ben Dicken\",\"x\",\"BenjDicken\",{\"_53\":75,\"_55\":76,\"_57\":58,\"_19\":77},{\"_53\":72,\"_55\":73,\"_57\":58,\"_19\":74},{\"_53\":69,\"_55\":70,\"_57\":58,\"_19\":71},{\"_53\":66,\"_55\":67,\"_57\":58,\"_19\":68},{\"_53\":63,\"_55\":64,\"_57\":58,\"_19\":65},{\"_53\":60,\"_55\":61,\"_57\":58,\"_19\":62},{\"_53\":54,\"_55\":56,\"_57\":58,\"_19\":59},\"children\",[],\"id\",\"what-about-everything-else\",\"level\",2,\"What about everything else?\",[],\"the-full-picture\",\"The full picture\",[],\"many-proxies-one-database\",\"Many proxies, one database\",[],\"how-does-it-know\",\"How does it know?\",[],\"the-proxy-layer\",\"The proxy layer\",[],\"sharding-with-a-d\",\"Sharding, with a \\\"d\\\"\",[78,79,80],\"growing-pains\",\"Growing pains\",{\"_53\":88,\"_55\":89,\"_57\":83,\"_19\":90},{\"_53\":85,\"_55\":86,\"_57\":83,\"_19\":87},{\"_53\":81,\"_55\":82,\"_57\":83,\"_19\":84},[],\"3-backups\",3,\"3) Backups\",[],\"2-replicas-do-not-increase-data-capacity\",\"2) Replicas do not increase data capacity\",[],\"1-writes-limited-to-one-server\",\"1) Writes limited to one server\",[\"SingleFetchClassInstance\",873],[\"SingleFetchClassInstance\",869],[\"SingleFetchClassInstance\",863],[\"SingleFetchClassInstance\",853],[\"SingleFetchClassInstance\",848],[\"SingleFetchClassInstance\",840],[\"SingleFetchClassInstance\",836],[\"SingleFetchClassInstance\",832],[\"SingleFetchClassInstance\",828],[\"SingleFetchClassInstance\",821],[\"SingleFetchClassInstance\",817],[\"SingleFetchClassInstance\",813],[\"SingleFetchClassInstance\",808],[\"SingleFetchClassInstance\",791],[\"SingleFetchClassInstance\",787],[\"SingleFetchClassInstance\",783],[\"SingleFetchClassInstance\",767],[\"SingleFetchClassInstance\",743],[\"SingleFetchClassInstance\",731],[\"SingleFetchClassInstance\",720],[\"SingleFetchClassInstance\",715],[\"SingleFetchClassInstance\",707],[\"SingleFetchClassInstance\",699],[\"SingleFetchClassInstance\",691],[\"SingleFetchClassInstance\",686],[\"SingleFetchClassInstance\",677],[\"SingleFetchClassInstance\",671],[\"SingleFetchClassInstance\",667],[\"SingleFetchClassInstance\",659],[\"SingleFetchClassInstance\",653],[\"SingleFetchClassInstance\",649],[\"SingleFetchClassInstance\",641],[\"SingleFetchClassInstance\",636],[\"SingleFetchClassInstance\",602],[\"SingleFetchClassInstance\",597],[\"SingleFetchClassInstance\",586],[\"SingleFetchClassInstance\",582],[\"SingleFetchClassInstance\",578],[\"SingleFetchClassInstance\",574],[\"SingleFetchClassInstance\",569],[\"SingleFetchClassInstance\",553],[\"SingleFetchClassInstance\",545],[\"SingleFetchClassInstance\",541],[\"SingleFetchClassInstance\",536],[\"SingleFetchClassInstance\",530],[\"SingleFetchClassInstance\",525],[\"SingleFetchClassInstance\",511],[\"SingleFetchClassInstance\",499],[\"SingleFetchClassInstance\",487],[\"SingleFetchClassInstance\",482],[\"SingleFetchClassInstance\",477],[\"SingleFetchClassInstance\",472],[\"SingleFetchClassInstance\",467],[\"SingleFetchClassInstance\",463],[\"SingleFetchClassInstance\",459],[\"SingleFetchClassInstance\",454],[\"SingleFetchClassInstance\",450],[\"SingleFetchClassInstance\",445],[\"SingleFetchClassInstance\",440],[\"SingleFetchClassInstance\",436],[\"SingleFetchClassInstance\",431],[\"SingleFetchClassInstance\",426],[\"SingleFetchClassInstance\",418],[\"SingleFetchClassInstance\",413],[\"SingleFetchClassInstance\",391],[\"SingleFetchClassInstance\",381],[\"SingleFetchClassInstance\",353],[\"SingleFetchClassInstance\",349],[\"SingleFetchClassInstance\",341],[\"SingleFetchClassInstance\",335],[\"SingleFetchClassInstance\",330],[\"SingleFetchClassInstance\",324],[\"SingleFetchClassInstance\",319],[\"SingleFetchClassInstance\",314],[\"SingleFetchClassInstance\",306],[\"SingleFetchClassInstance\",302],[\"SingleFetchClassInstance\",256],[\"SingleFetchClassInstance\",248],[\"SingleFetchClassInstance\",241],[\"SingleFetchClassInstance\",223],[\"SingleFetchClassInstance\",214],[\"SingleFetchClassInstance\",205],[\"SingleFetchClassInstance\",183],[\"SingleFetchClassInstance\",175],{\"_176\":177,\"_42\":178,\"_179\":180,\"_53\":181},\"$$mdtype\",\"Tag\",\"p\",\"attributes\",{},[182],\"Happy sharding.\",{\"_176\":177,\"_42\":178,\"_179\":184,\"_53\":185},{},[186,187,188,189,190,191,192],\"Stay tuned for more here.\",\" \",\"Follow our \",[\"SingleFetchClassInstance\",200],\" or on \",[\"SingleFetchClassInstance\",193],\" to stay in the loop.\",{\"_176\":177,\"_42\":194,\"_179\":195,\"_53\":196},\"a\",{\"_198\":199},[197],\"X\",\"href\",\"https://x.com/planetscale\",{\"_176\":177,\"_42\":194,\"_179\":201,\"_53\":202},{\"_198\":204},[203],\"RSS feed\",\"/blog/feed.atom\",{\"_176\":177,\"_42\":178,\"_179\":206,\"_53\":207},{},[208,187,209,187,210,187,211,187,212,187,213],\"We've only scratched the surface of everything sharding systems like Neki and Vitess provide.\",\"There are so many other interesting details.\",\"What's the best way to shard data?\",\"How do sharded databases handle failures?\",\"How do you change the number of shards?\",\"How do you take backups across 256 shards at the same time?\",{\"_176\":177,\"_42\":215,\"_179\":216,\"_53\":217},\"h2\",{\"_55\":56},[218],[\"SingleFetchClassInstance\",219],{\"_176\":177,\"_42\":194,\"_179\":220,\"_53\":221},{\"_198\":222},[59],\"#what-about-everything-else\",{\"_176\":177,\"_42\":178,\"_179\":224,\"_53\":225},{},[226,227,187,228,187,229,230],[\"SingleFetchClassInstance\",236],\" for MySQL has been used for over a decade to scale the world's biggest relational databases.\",\"We have years of experience operating large, sharded databases for our customers, and are the core maintainers of the Vitess project.\",[\"SingleFetchClassInstance\",231],\" was developed by the same expert maintainers of Vitess, bringing an even more powerful sharding system to Postgres.\",{\"_176\":177,\"_42\":194,\"_179\":232,\"_53\":233},{\"_198\":235},[234],\"Neki\",\"/neki\",{\"_176\":177,\"_42\":194,\"_179\":237,\"_53\":238},{\"_198\":240},[239],\"Vitess\",\"/vitess\",{\"_176\":177,\"_42\":178,\"_179\":242,\"_53\":243},{},[244,187,245,187,246,187,247],\"This example shows scaling up to 1 petabyte, but sharding should begin long before this scale.\",\"The precise recommendations depend on each database's size, schema, and QPS, but we recommend sharding Postgres and MySQL for anything beyond a few terabytes of data.\",\"That's the point where you typically begin hitting the bottlenecks described earlier: long backups, write bottlenecks, etc.\",\"If you are facing challenges scaling relational databases, Neki and Vitess are the solutions.\",{\"_176\":177,\"_42\":249,\"_179\":250,\"_53\":251},\"Iframe\",{\"_252\":253,\"_254\":255},[],\"aspect\",\"4/3\",\"src\",\"/blog/many-servers-appear-as-one/iframe#shard-formation\",{\"_176\":177,\"_42\":257,\"_179\":258,\"_53\":259},\"ol\",{},[260,261,262,263,264],[\"SingleFetchClassInstance\",292],[\"SingleFetchClassInstance\",284],[\"SingleFetchClassInstance\",274],[\"SingleFetchClassInstance\",270],[\"SingleFetchClassInstance\",265],{\"_176\":177,\"_42\":266,\"_179\":267,\"_53\":268},\"li\",{},[269],\"The app begins sending database queries, which go app -\u003e NLB (optional) -\u003e proxy -\u003e shards. The complex routing logic is hidden from the application. (NLB not pictured below, for simplicity)\",{\"_176\":177,\"_42\":266,\"_179\":271,\"_53\":272},{},[273],\"This routes the connection first through the NLB, then to one of the N proxies\",{\"_176\":177,\"_42\":266,\"_179\":275,\"_53\":276},{},[277,278],\"The app requests to connect to the database at \",[\"SingleFetchClassInstance\",279],{\"_176\":177,\"_42\":280,\"_179\":281,\"_53\":282},\"code\",{},[283],\"123.152.100.4\",{\"_176\":177,\"_42\":266,\"_179\":285,\"_53\":286},{},[287,288],\"A DNS lookup is performed, returning the NLB's IP address: \",[\"SingleFetchClassInstance\",289],{\"_176\":177,\"_42\":280,\"_179\":290,\"_53\":291},{},[283],{\"_176\":177,\"_42\":266,\"_179\":293,\"_53\":294},{},[295,296,297],\"An app server is told \\\"connect to the database at \",[\"SingleFetchClassInstance\",298],\"\\\"\",{\"_176\":177,\"_42\":280,\"_179\":299,\"_53\":300},{},[301],\"mydb.pscale.com\",{\"_176\":177,\"_42\":178,\"_179\":303,\"_53\":304},{},[305],\"Now all the pieces are in place to make 768 servers storing 1,000 terabytes of data appear as a single, monolithic database to our apps.\",{\"_176\":177,\"_42\":215,\"_179\":307,\"_53\":308},{\"_55\":61},[309],[\"SingleFetchClassInstance\",310],{\"_176\":177,\"_42\":194,\"_179\":311,\"_53\":312},{\"_198\":313},[62],\"#the-full-picture\",{\"_176\":177,\"_42\":178,\"_179\":315,\"_53\":316},{},[317,187,318],\"In some cases, an NLB is not necessary.\",\"Eliminating an NLB adds slightly more complexity to the app server's connection logic, as it will have to be aware of each router's host, but eliminates a network hop, keeping round-trip latency to a minimum.\",{\"_176\":177,\"_42\":249,\"_179\":320,\"_53\":321},{\"_252\":322,\"_254\":323},[],\"10/6\",\"/blog/many-servers-appear-as-one/iframe#full-sharded\",{\"_176\":177,\"_42\":178,\"_179\":325,\"_53\":326},{},[327,187,328,187,329],\"NLBs have a simple job: Allow connections via a single host/IP, and assign each connection to one of many destinations.\",\"This is how traffic is distributed across the routers.\",\"Once assigned, a connection remains with the same proxy for its lifetime.\",{\"_176\":177,\"_42\":178,\"_179\":331,\"_53\":332},{},[333,187,334],\"We'd still like our apps to think of this as a single server.\",\"This is where a Network Load Balancer (NLB) helps.\",{\"_176\":177,\"_42\":178,\"_179\":336,\"_53\":337},{},[338,187,339,187,340],\"At a scale of 256 shards spanning 768 servers and millions of queries per second, we cannot route all of this traffic through a single proxy.\",\"We need many!\",\"Perhaps 10, perhaps 100, depending on the shape of the traffic.\",{\"_176\":177,\"_42\":215,\"_179\":342,\"_53\":343},{\"_55\":64},[344],[\"SingleFetchClassInstance\",345],{\"_176\":177,\"_42\":194,\"_179\":346,\"_53\":347},{\"_198\":348},[65],\"#many-proxies-one-database\",{\"_176\":177,\"_42\":178,\"_179\":350,\"_53\":351},{},[352],\"Since this is all communicated to the router via text and JSON, AI agents are great for configuration and optimization here.\",{\"_176\":177,\"_42\":178,\"_179\":354,\"_53\":355},{},[356,357,358,359,360,361,362,187,363,364,365,187,366],\"This metadata is stored in the router, and tells it that the \",[\"SingleFetchClassInstance\",377],\" table is sharded on its \",[\"SingleFetchClassInstance\",374],\" column using the \",[\"SingleFetchClassInstance\",371],\" shard index.\",\"This \",[\"SingleFetchClassInstance\",367],\" shard index uses the router's built-in value hashing.\",\"For each incoming row, it hashed the ID, and uses this to send it to the correct shard to be stored.\",{\"_176\":177,\"_42\":280,\"_179\":368,\"_53\":369},{},[370],\"user_hash\",{\"_176\":177,\"_42\":280,\"_179\":372,\"_53\":373},{},[370],{\"_176\":177,\"_42\":280,\"_179\":375,\"_53\":376},{},[55],{\"_176\":177,\"_42\":280,\"_179\":378,\"_53\":379},{},[380],\"user\",{\"_176\":177,\"_42\":382,\"_179\":383,\"_53\":384},\"CodeBlock\",{\"_385\":386,\"_387\":388,\"_389\":390,\"_280\":-7},[],\"html\",\"\u003cpre class=\\\"shiki shiki-themes planetscale-light planetscale-dark\\\" style=\\\"--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a\\\" tabindex=\\\"0\\\"\u003e\u003ccode\u003e\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e{\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e \\\"\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003eshard_indexes\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e\\\"\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e:\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e {\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e \\\"\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003euser_hash\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e\\\"\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e:\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e {\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e \\\"\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003etype\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e\\\"\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e:\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e \\\"hash\\\"\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e }\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e },\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e \\\"\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003etables\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e\\\"\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e:\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e {\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e \\\"\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003euser\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e\\\"\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e:\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e {\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e \\\"\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003eshard_by\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e\\\"\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e:\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e \\\"user_hash\\\"\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e,\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e \\\"\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003ecolumn\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e\\\"\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e:\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e \\\"id\\\"\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e }\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e }\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#616161;--shiki-dark:#C1C1C1\\\"\u003e}\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\",\"language\",\"json\",\"clipboard\",false,{\"_176\":177,\"_42\":178,\"_179\":392,\"_53\":393},{},[394,395,396,397,398,187,399,187,400,401,402],\"In both \",[\"SingleFetchClassInstance\",410],\" and \",[\"SingleFetchClassInstance\",406],\", these are specified via JSON files representing the data topology of the system.\",\"Vitess' VSchema and Neki's data topology give engineers a ton of flexibility to describe precisely how tables and queries should be distributed.\",\"Below is a simplified example of how we would specify a sharding scheme for a \",[\"SingleFetchClassInstance\",403],\" table:\",{\"_176\":177,\"_42\":280,\"_179\":404,\"_53\":405},{},[380],{\"_176\":177,\"_42\":194,\"_179\":407,\"_53\":408},{\"_198\":409},[239],\"https://vitess.io/docs/reference/features/vschema/\",{\"_176\":177,\"_42\":194,\"_179\":411,\"_53\":412},{\"_198\":235},[234],{\"_176\":177,\"_42\":178,\"_179\":414,\"_53\":415},{},[416,187,417],\"Every database is unique, with its own schema, tables, and query patterns.\",\"How then can a router generically know which data, and which queries, go where?\",{\"_176\":177,\"_42\":215,\"_179\":419,\"_53\":420},{\"_55\":67},[421],[\"SingleFetchClassInstance\",422],{\"_176\":177,\"_42\":194,\"_179\":423,\"_53\":424},{\"_198\":425},[68],\"#how-does-it-know\",{\"_176\":177,\"_42\":178,\"_179\":427,\"_53\":428},{},[429,187,430],\"The router must be able to perform query parsing, planning, connection pooling, and buffering, all within a single system.\",\"Complex software is hard to get right.\",{\"_176\":177,\"_42\":249,\"_179\":432,\"_53\":433},{\"_252\":434,\"_254\":435},[],\"3/1\",\"/blog/many-servers-appear-as-one/iframe#proxy-plan\",{\"_176\":177,\"_42\":178,\"_179\":437,\"_53\":438},{},[439],\"Ultimately, this means the router itself must have a full query parser and routing planner built in.\",{\"_176\":177,\"_42\":178,\"_179\":441,\"_53\":442},{},[443,187,444],\"Users with this range of IDs are spread out across several shards.\",\"The router must understand the data topology, create a plan for distributing the query to all shards that may contain matching results, aggregate the results back at the router, and send the full result set to the client.\",{\"_176\":177,\"_42\":382,\"_179\":446,\"_53\":447},{\"_385\":448,\"_387\":449,\"_389\":390,\"_280\":-7},[],\"\u003cpre class=\\\"shiki shiki-themes planetscale-light planetscale-dark\\\" style=\\\"--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a\\\" tabindex=\\\"0\\\"\u003e\u003ccode\u003e\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003eSELECT\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e email \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003eFROM\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e user\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003e WHERE\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e id \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003eBETWEEN\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#D92038;--shiki-dark:#FF7082\\\"\u003e 3\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003e AND\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#D92038;--shiki-dark:#FF7082\\\"\u003e 5\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e;\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\",\"sql\",{\"_176\":177,\"_42\":178,\"_179\":451,\"_53\":452},{},[453],\"Some cases are more complex.\",{\"_176\":177,\"_42\":178,\"_179\":455,\"_53\":456},{},[457,187,458],\"In this case, all the router needs to do is have an internal mapping of which user IDs live in which servers, and forward that query on.\",\"Based on the example above, this would be the first (top) shard.\",{\"_176\":177,\"_42\":382,\"_179\":460,\"_53\":461},{\"_385\":462,\"_387\":449,\"_389\":390,\"_280\":-7},[],\"\u003cpre class=\\\"shiki shiki-themes planetscale-light planetscale-dark\\\" style=\\\"--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a\\\" tabindex=\\\"0\\\"\u003e\u003ccode\u003e\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003eSELECT\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e email \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003efrom\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e user \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003ewhere\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e id \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003e=\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#D92038;--shiki-dark:#FF7082\\\"\u003e 4\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e;\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\",{\"_176\":177,\"_42\":178,\"_179\":464,\"_53\":465},{},[466],\"When it comes to reads, some queries are simple enough such that the router passes them along to a single shard.\",{\"_176\":177,\"_42\":249,\"_179\":468,\"_53\":469},{\"_252\":470,\"_254\":471},[],\"10/7\",\"/blog/many-servers-appear-as-one/iframe#shard-inserts\",{\"_176\":177,\"_42\":178,\"_179\":473,\"_53\":474},{},[475,187,476],\"Each of the four shards is assigned a range of IDs that it's responsible for storing, and the router sends the inserts to the correct shard.\",\"The insertions first get sent to the router, where it computes a hash of each ID, then forwards it along to the correct shard.\",{\"_176\":177,\"_42\":382,\"_179\":478,\"_53\":479},{\"_385\":480,\"_387\":481,\"_389\":390,\"_280\":-7},[],\"\u003cpre class=\\\"shiki shiki-themes planetscale-light planetscale-dark\\\" style=\\\"--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a\\\" tabindex=\\\"0\\\"\u003e\u003ccode\u003e\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003e INSERT INTO\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e users (id, username, email) \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#F35815;--shiki-dark:#F35815\\\"\u003eVALUES\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e (\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#D92038;--shiki-dark:#FF7082\\\"\u003e1\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'ada'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'ada@example.com'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e),\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e (\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#D92038;--shiki-dark:#FF7082\\\"\u003e2\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'grace'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'grace@example.com'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e),\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e (\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#D92038;--shiki-dark:#FF7082\\\"\u003e3\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'linus'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'linus@example.com'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e),\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e (\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#D92038;--shiki-dark:#FF7082\\\"\u003e4\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'margaret'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'margaret@example.com'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e),\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e (\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#D92038;--shiki-dark:#FF7082\\\"\u003e5\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'dennis'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'dennis@example.com'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e),\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e (\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#D92038;--shiki-dark:#FF7082\\\"\u003e6\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'barbara'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'barbara@example.com'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e),\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e (\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#D92038;--shiki-dark:#FF7082\\\"\u003e7\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'donald'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'donald@example.com'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e),\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e (\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#D92038;--shiki-dark:#FF7082\\\"\u003e8\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'james'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e, \u003c/span\u003e\u003cspan style=\\\"--shiki-light:#13862E;--shiki-dark:#75DB8C\\\"\u003e'james@example.com'\u003c/span\u003e\u003cspan style=\\\"--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1\\\"\u003e);\u003c/span\u003e\u003c/span\u003e\\n\u003cspan class=\\\"line\\\"\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\",\"SQL\",{\"_176\":177,\"_42\":178,\"_179\":483,\"_53\":484},{},[485,187,486],\"A common approach is to shard incoming rows based on a hash of an id column.\",\"When inserting row like this into the database:\",{\"_176\":177,\"_42\":178,\"_179\":488,\"_53\":489},{},[490,187,491,492,493],\"When inserting data, the router must be aware of how data is to be distributed.\",\"This is known as the \",[\"SingleFetchClassInstance\",494],\".\",{\"_176\":177,\"_42\":194,\"_179\":495,\"_53\":496},{\"_198\":498},[497],\"sharding strategy\",\"/blog/database-sharding#sharding-strategy\",{\"_176\":177,\"_42\":178,\"_179\":500,\"_53\":501},{},[502,187,503,187,504,505,493],\"Sharding Postgres requires an even more sophisticated proxy.\",\"The biggest difference is that, in addition to multiplexing and buffering, the proxy must understand how data is distributed across servers and route SQL queries to the correct shards.\",\"Because of this, we refer to it as a \",[\"SingleFetchClassInstance\",506],{\"_176\":177,\"_42\":507,\"_179\":508,\"_53\":509},\"em\",{},[510],\"router\",{\"_176\":177,\"_42\":178,\"_179\":512,\"_53\":513},{},[514,187,515,187,516,187,517,518,519],\"PgBouncer has a simple goal.\",\"It's built to accept a large number of connections from many clients and route them through a smaller pool of connections that it continually maintains with Postgres.\",\"The query queuing is useful for traffic surges and during database failover, so requests can resume when the new primary comes online.\",\"We have a whole \",[\"SingleFetchClassInstance\",520],\" if you want to learn more.\",{\"_176\":177,\"_42\":194,\"_179\":521,\"_53\":522},{\"_198\":524},[523],\"blog on PgBouncer\",\"/blog/scaling-postgres-connections-with-pgbouncer\",{\"_176\":177,\"_42\":249,\"_179\":526,\"_53\":527},{\"_252\":528,\"_254\":529},[],\"2/1\",\"/blog/many-servers-appear-as-one/iframe#pgbouncer\",{\"_176\":177,\"_42\":178,\"_179\":531,\"_53\":532},{},[533,187,534,187,535],\"Proxies are frequently used with Postgres databases.\",\"Even when there's no sharding, they are useful for connection pooling and request queuing.\",\"For regular (unsharded) Postgres, PgBouncer is a popular proxy that people use to multiplex 1000s of app connections across fewer direct Postgres connections.\",{\"_176\":177,\"_42\":178,\"_179\":537,\"_53\":538},{},[539,187,540],\"Proxies are middleware servers that sit between two services.\",\"In our case, these two services are the application servers and database servers.\",{\"_176\":177,\"_42\":178,\"_179\":542,\"_53\":543},{},[544],\"The most important amongst several critical pieces here is the proxy layer.\",{\"_176\":177,\"_42\":215,\"_179\":546,\"_53\":547},{\"_55\":70},[548],[\"SingleFetchClassInstance\",549],{\"_176\":177,\"_42\":194,\"_179\":550,\"_53\":551},{\"_198\":552},[71],\"#the-proxy-layer\",{\"_176\":177,\"_42\":178,\"_179\":554,\"_53\":555},{},[556,187,557,558,559,560,187,561],\"While in reality, utilizing tens or hundreds of shards.\",[\"SingleFetchClassInstance\",566],\" for Postgres and \",[\"SingleFetchClassInstance\",562],\" for MySQL solve this.\",\"Let's see how.\",{\"_176\":177,\"_42\":194,\"_179\":563,\"_53\":564},{\"_198\":565},[239],\"https://vitess.io\",{\"_176\":177,\"_42\":194,\"_179\":567,\"_53\":568},{\"_198\":235},[234],{\"_176\":177,\"_42\":249,\"_179\":570,\"_53\":571},{\"_252\":572,\"_254\":573},[],\"10/4\",\"/blog/many-servers-appear-as-one/iframe#simple-sharded\",{\"_176\":177,\"_42\":178,\"_179\":575,\"_53\":576},{},[577],\"To instead interacting with it over a single connection string, making it appear as if it's interfacing with one large, scalable database:\",{\"_176\":177,\"_42\":249,\"_179\":579,\"_53\":580},{\"_252\":528,\"_254\":581},[],\"/blog/many-servers-appear-as-one/iframe#tons-of-shards\",{\"_176\":177,\"_42\":178,\"_179\":583,\"_53\":584},{},[585],\"We want to allow the application servers to go from interacting with a complex system, like this:\",{\"_176\":177,\"_42\":178,\"_179\":587,\"_53\":588},{},[589],[\"SingleFetchClassInstance\",590],{\"_176\":177,\"_42\":591,\"_179\":592,\"_53\":593},\"Skittle\",{\"_595\":596},[594],\"How can these 768 servers look like 1 cohesive database to our apps?\",\"color\",\"blue\",{\"_176\":177,\"_42\":178,\"_179\":598,\"_53\":599},{},[600,187,601],\"There's a lot that could be said in addressing each one of those concerns.\",\"But the question to address here in this article is the following:\",{\"_176\":177,\"_42\":603,\"_179\":604,\"_53\":605},\"ul\",{},[606,607,608,609,610,611],[\"SingleFetchClassInstance\",632],[\"SingleFetchClassInstance\",628],[\"SingleFetchClassInstance\",624],[\"SingleFetchClassInstance\",620],[\"SingleFetchClassInstance\",616],[\"SingleFetchClassInstance\",612],{\"_176\":177,\"_42\":266,\"_179\":613,\"_53\":614},{},[615],\"Respond to a failing server?\",{\"_176\":177,\"_42\":266,\"_179\":617,\"_53\":618},{},[619],\"Monitor system-wide health?\",{\"_176\":177,\"_42\":266,\"_179\":621,\"_53\":622},{},[623],\"Take backups across this spread-out database?\",{\"_176\":177,\"_42\":266,\"_179\":625,\"_53\":626},{},[627],\"Handle queries that need to talk to multiple shards simultaneously?\",{\"_176\":177,\"_42\":266,\"_179\":629,\"_53\":630},{},[631],\"Decide which queries go to which server?\",{\"_176\":177,\"_42\":266,\"_179\":633,\"_53\":634},{},[635],\"Decide which data goes to which server?\",{\"_176\":177,\"_42\":178,\"_179\":637,\"_53\":638},{},[639,187,640],\"Without a good system in place, this adds significant complexity to our app's backend.\",\"With so much going on, how does the system...\",{\"_176\":177,\"_42\":178,\"_179\":642,\"_53\":643},{},[644,187,645,187,646,187,647,187,648],\"Sharding is useful at all scales past a few terabytes of data.\",\"For example, with 2 terabytes of data, we may choose a setup with four shards, each storing 500 gigabytes and handling 1/4th of the total query traffic.\",\"When we needed to store a petabyte of data (one million gigabytes), we'd need many more shards.\",\"In this case, we can use 256 shards, each with a primary + 2 replicas, and each responsible for storing ~4 terabytes.\",\"This requires 256 * 3 = 768 servers!\",{\"_176\":177,\"_42\":249,\"_179\":650,\"_53\":651},{\"_252\":470,\"_254\":652},[],\"/blog/many-servers-appear-as-one/iframe#sharding\",{\"_176\":177,\"_42\":178,\"_179\":654,\"_53\":655},{},[656,187,657,187,658],\"Sharding solves these three bottlenecks by distributing the data and queries across many distinct primaries.\",\"For data, it is useful because a single node can only store so much and is limited on write throughput.\",\"For queries, this is useful because the network interconnects and CPUs can only process so many queries at a time.\",{\"_176\":177,\"_42\":215,\"_179\":660,\"_53\":661},{\"_55\":73},[662],[\"SingleFetchClassInstance\",663],{\"_176\":177,\"_42\":194,\"_179\":664,\"_53\":665},{\"_198\":666},[74],\"#sharding-with-a-d\",{\"_176\":177,\"_42\":178,\"_179\":668,\"_53\":669},{},[670],\"The most proven way to handle this is sharding.\",{\"_176\":177,\"_42\":178,\"_179\":672,\"_53\":673},{},[674,187,675,187,676],\"Backups are an important part of data durability and RPO / RTO guarantees.\",\"Taking a backup of a large, monolithic database to object storage can take hours or even days due to the bandwidth limitations of node-to-storage communication.\",\"This is unacceptably long for many organizations that rely on frequent and validated backups.\",{\"_176\":177,\"_42\":678,\"_179\":679,\"_53\":680},\"h3\",{\"_55\":82},[681],[\"SingleFetchClassInstance\",682],{\"_176\":177,\"_42\":194,\"_179\":683,\"_53\":684},{\"_198\":685},[84],\"#3-backups\",{\"_176\":177,\"_42\":178,\"_179\":687,\"_53\":688},{},[689,187,690],\"A replica is a full copy of the primary's data, including all indexes.\",\"Adding replicas gives us more places to run reads, but it does not distribute the data.\",{\"_176\":177,\"_42\":678,\"_179\":692,\"_53\":693},{\"_55\":86},[694],[\"SingleFetchClassInstance\",695],{\"_176\":177,\"_42\":194,\"_179\":696,\"_53\":697},{\"_198\":698},[87],\"#2-replicas-do-not-increase-data-capacity\",{\"_176\":177,\"_42\":178,\"_179\":700,\"_53\":701},{},[702,187,703,187,704,187,705,187,706],\"With high enough write volume, no amount of additional read-only replicas will alleviate an issue.\",\"Before Postgres can acknowledge a committed write, it must record the change in its\",\"write-ahead log (WAL) and flush that log to durable storage.\",\"The WAL is a shared resource amongst all connections on the primary.\",\"This is essentially a single write bottleneck across your entire database, even if you have tens of replicas.\",{\"_176\":177,\"_42\":678,\"_179\":708,\"_53\":709},{\"_55\":89},[710],[\"SingleFetchClassInstance\",711],{\"_176\":177,\"_42\":194,\"_179\":712,\"_53\":713},{\"_198\":714},[90],\"#1-writes-limited-to-one-server\",{\"_176\":177,\"_42\":178,\"_179\":716,\"_53\":717},{},[718,187,719],\"It turns out, scaling servers vertically (increasing CPU / RAM) and adding replicas can only take you so far.\",\"There are several bottlenecks that cannot be solved in this way\",{\"_176\":177,\"_42\":178,\"_179\":721,\"_53\":722},{},[723,187,724,725,493],\"The database can scale to handle more traffic by adding replicas.\",\"An extreme example of this is \",[\"SingleFetchClassInstance\",726],{\"_176\":177,\"_42\":194,\"_179\":727,\"_53\":728},{\"_198\":730},[729],\"OpenAI's use of 50 replicas on a single Primary\",\"https://openai.com/index/scaling-postgresql/\",{\"_176\":177,\"_42\":178,\"_179\":732,\"_53\":733},{},[734,735,736,187,737,187,738],\"However, app servers can send read (\",[\"SingleFetchClassInstance\",739],\") queries to the replicas.\",\"Since most apps have a much higher percent of reads compared to writes, this provides a lot more scalability.\",\"(Replicas are also necessary for high availability and data durability, even if query traffic does not require them).\",{\"_176\":177,\"_42\":280,\"_179\":740,\"_53\":741},{},[742],\"SELECT\",{\"_176\":177,\"_42\":178,\"_179\":744,\"_53\":745},{},[746,187,747,748,749,750,749,751,752,187,753,187,754],\"The primary sends a continuous stream of messages to every replica to ensure they stay up-to-date with the data changes on the primary.\",\"Writes (\",[\"SingleFetchClassInstance\",763],\", \",[\"SingleFetchClassInstance\",759],[\"SingleFetchClassInstance\",755],\") can only go to the primary.\",\"If writes were allowed to any server, we could end up with conflicting data.\",\"Solving this requires complex and slow consensus algorithms, which is possible, but in most cases not ideal for optimal performance.\",{\"_176\":177,\"_42\":280,\"_179\":756,\"_53\":757},{},[758],\"DELETE\",{\"_176\":177,\"_42\":280,\"_179\":760,\"_53\":761},{},[762],\"UPDATE\",{\"_176\":177,\"_42\":280,\"_179\":764,\"_53\":765},{},[766],\"INSERT\",{\"_176\":177,\"_42\":178,\"_179\":768,\"_53\":769},{},[770,771,772,773,774],\"In this configuration, you maintain the original server as a \",[\"SingleFetchClassInstance\",779],\" and add additional \",[\"SingleFetchClassInstance\",775],\" as shown above.\",{\"_176\":177,\"_42\":507,\"_179\":776,\"_53\":777},{},[778],\"replicas\",{\"_176\":177,\"_42\":507,\"_179\":780,\"_53\":781},{},[782],\"primary\",{\"_176\":177,\"_42\":249,\"_179\":784,\"_53\":785},{\"_252\":322,\"_254\":786},[],\"/blog/many-servers-appear-as-one/iframe#primary-replicas\",{\"_176\":177,\"_42\":178,\"_179\":788,\"_53\":789},{},[790],\"One way to solve this, at least in the short term, is leveraging read-replicas.\",{\"_176\":177,\"_42\":178,\"_179\":792,\"_53\":793},{},[794,795,796,797,798,187,799],\"In short, the USL states that resource \",[\"SingleFetchClassInstance\",804],\" causes scalability to grow sub-linearly with increasing resources, and at a certain point, \",[\"SingleFetchClassInstance\",800],\" causes performance degradation.\",\"This is true for Postgres, as with any software system attempting to scale out across many threads or processes on a larger server.\",{\"_176\":177,\"_42\":507,\"_179\":801,\"_53\":802},{},[803],\"incoherence\",{\"_176\":177,\"_42\":507,\"_179\":805,\"_53\":806},{},[807],\"contention\",{\"_176\":177,\"_42\":249,\"_179\":809,\"_53\":810},{\"_252\":811,\"_254\":812},[],\"10/5\",\"/blog/many-servers-appear-as-one/iframe#universal-scalability-law\",{\"_176\":177,\"_42\":178,\"_179\":814,\"_53\":815},{},[816],\"This is summed up nicely by the Universal Scalability Law:\",{\"_176\":177,\"_42\":178,\"_179\":818,\"_53\":819},{},[820],\"Even with a large database servers (10s of CPU cores, 100s of gigabytes of RAM) bottlenecks arise pretty quickly. Typically, it is either CPU constraints due to high query volume, or I/O constraints (IOPS) due to a high volume of reads and writes.\",{\"_176\":177,\"_42\":178,\"_179\":822,\"_53\":823},{},[824,187,825,187,826,187,827],\"Most applications you've ever used function in this way, or at least did early in their existence.\",\"The software running on a client device connects to an app server over the internet.\",\"This app server lives in a data center and handles authentication, page loads, and all the server-side logic for how your application behaves.\",\"All the persisted data like user accounts, posts, settings, and messages get stored in and retrieved from the database server (where \\\"database server\\\" is typically Postgres or MySQL, though the focus of this article is Postgres).\",{\"_176\":177,\"_42\":249,\"_179\":829,\"_53\":830},{\"_252\":811,\"_254\":831},[],\"/blog/many-servers-appear-as-one/iframe#popular-arch\",{\"_176\":177,\"_42\":178,\"_179\":833,\"_53\":834},{},[835],\"Consider first a simple application architecture.\",{\"_176\":177,\"_42\":178,\"_179\":837,\"_53\":838},{},[839],\"To understand why sharding is a necessary part of scaling relational databases, we must understand the bottlenecks of less scalable approaches.\",{\"_176\":177,\"_42\":215,\"_179\":841,\"_53\":842},{\"_55\":76},[843],[\"SingleFetchClassInstance\",844],{\"_176\":177,\"_42\":194,\"_179\":845,\"_53\":846},{\"_198\":847},[77],\"#growing-pains\",{\"_176\":177,\"_42\":178,\"_179\":849,\"_53\":850},{},[851,187,852],\"Database sharding is the best way to scale a Postgres or MySQL database for anything beyond a few terabytes of data.\",\"Let's look at how we go from a small single-node database, to one with a few terabytes spread across four shards, all the way up to one that is sharded across 768 servers and storing a petabyte of data.\",{\"_176\":177,\"_42\":178,\"_179\":854,\"_53\":855},{},[856,187,857,858,493],\"The most difficult infrastructure component to scale is almost always the database.\",\"A single database server cannot handle such demand, so we must spread the queries and data out across many servers with \",[\"SingleFetchClassInstance\",859],{\"_176\":177,\"_42\":591,\"_179\":860,\"_53\":861},{\"_595\":596},[862],\"database sharding\",{\"_176\":177,\"_42\":178,\"_179\":864,\"_53\":865},{},[866,187,867,187,868],\"To some, that looks like a lot of computers.\",\"To those managing the infrastructure for apps with millions of customers, executing millions of queries per second, pretty normal.\",\"Products at this scale frequently require thousands of servers working in unison.\",{\"_176\":177,\"_42\":178,\"_179\":870,\"_53\":871},{},[872],\"This is 768 servers.\",{\"_176\":177,\"_42\":249,\"_179\":874,\"_53\":875},{\"_252\":876,\"_254\":877},[],\"4/1\",\"/blog/many-servers-appear-as-one/iframe#servers\",\"current\",{\"_880\":390,\"_881\":882,\"_883\":390},\"development\",\"env\",{\"_884\":885,\"_886\":887,\"_888\":889,\"_890\":891,\"_892\":893},\"userSignedIn\",\"IMAGE_CDN\",\"https://planetscale-images.imgix.net\",\"IMAGE_CDN_ENABLED\",\"true\",\"INTERNAL_API\",\"https://api.planetscale.com\",\"RELEASE\",\"dffcc38d-5e2f-4ef2-a67d-ca7705b6731c\",\"SENTRY_DSN\",\"https://bd81903b44804e22a06bdc0c1a91b303@o499952.ingest.us.sentry.io/4504531942572032\"]\n");</script><!--$--><script nonce="5LnKd/jJDF4+5qLyrXr93cjGs6ttQ8d2dVzaw0J5Z6g=">window.__reactRouterContext.streamController.close();</script><!--/$--><!--/$--></body></html> |