<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vishwa</title>
    <description>The latest articles on DEV Community by Vishwa (@vishwa_7743ae1b748200b774).</description>
    <link>https://dev.to/vishwa_7743ae1b748200b774</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4232004%2Fa1297b58-acf5-48d4-a896-2d4e3662882a.png</url>
      <title>DEV Community: Vishwa</title>
      <link>https://dev.to/vishwa_7743ae1b748200b774</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vishwa_7743ae1b748200b774"/>
    <language>en</language>
    <item>
      <title>Cloud SQL: From One Database to Three Shards Without Downtime</title>
      <dc:creator>Vishwa</dc:creator>
      <pubDate>Sun, 08 Nov 2026 08:42:13 +0000</pubDate>
      <link>https://dev.to/vishwa_7743ae1b748200b774/cloud-sql-from-one-database-to-three-shards-without-downtime-2l9e</link>
      <guid>https://dev.to/vishwa_7743ae1b748200b774/cloud-sql-from-one-database-to-three-shards-without-downtime-2l9e</guid>
      <description>&lt;p&gt;&lt;em&gt;Split users into 1024 buckets, then move one bucket at a time: freeze, catch up, flip&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs7k04kmuj0pc6ztuc5zd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs7k04kmuj0pc6ztuc5zd.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Imagine a Flipkart-style store running on one Cloud SQL database. The disk is about 70% full, queries are slowing down, and the sale season is coming. Every order, address and payment still lands on that one primary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;One primary takes every write. Indexes, a cache, replicas and a bigger machine help, but at some point that primary is still the bottleneck. The next step is sharding: spreading per-user data across several databases.&lt;/p&gt;

&lt;p&gt;The hard part is not creating three new databases. It is moving live users onto them while those users keep placing orders. A naive plan, such as "stop the site, copy everything, switch", means downtime. A hash like &lt;code&gt;user_id mod 3&lt;/code&gt; has a different problem: the day you add a fourth shard, almost every user maps to a new place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: buckets and a shard map
&lt;/h2&gt;

&lt;p&gt;I split the problem into two layers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. A fixed rule from user to bucket.&lt;/strong&gt; A bucket is a number computed in code, never stored per user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bucket = int(first 8 hex digits of user UUID) mod 1024
# 07a1c3e4-... -&amp;gt; 128,041,956 mod 1024 -&amp;gt; bucket 996
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rule never changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A small table from bucket to shard.&lt;/strong&gt; Only this table, &lt;code&gt;shard_map&lt;/code&gt;, is stored: one row per bucket, 1024 rows in total.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;bucket&lt;/th&gt;
&lt;th&gt;shard_name (live)&lt;/th&gt;
&lt;th&gt;target_shard (planned)&lt;/th&gt;
&lt;th&gt;frozen&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;996&lt;/td&gt;
&lt;td&gt;old_db&lt;/td&gt;
&lt;td&gt;shard_a&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The app reads &lt;code&gt;shard_map&lt;/code&gt; to route every query. Moving users now means changing rows in a small table, not rehashing anyone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fstegxpl4tsc025c3bnxb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fstegxpl4tsc025c3bnxb.png" alt=" " width="800" height="340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What moves and what stays: &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;addresses&lt;/code&gt; and &lt;code&gt;payments&lt;/code&gt; move to shards A, B and C, about a third of users each. &lt;code&gt;products&lt;/code&gt;, offers and other shared tables stay on the old database, along with &lt;code&gt;shard_map&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fspw9ztlr4bmwrfqyvrqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fspw9ztlr4bmwrfqyvrqw.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: prepare with no visible change
&lt;/h3&gt;

&lt;p&gt;Create the three shards with the same schema. Seed &lt;code&gt;shard_map&lt;/code&gt; with all 1024 buckets pointing to &lt;code&gt;old_db&lt;/code&gt;. Deploy the shard-aware app. It behaves exactly as before, because every bucket still points to the old database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: copy and catch up
&lt;/h3&gt;

&lt;p&gt;A one-off Cloud Run job copies existing users to their target shards, then runs catch-up passes for rows changed since the last pass. Users keep writing to the old database the whole time, so each pass finds fewer rows. Once a pass finds only a small number of rows a few times in a row, the job stops. Catch-up can never reach exactly zero while users are writing, and that is what the cutover is for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: cut over one bucket
&lt;/h3&gt;

&lt;p&gt;For one bucket:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set &lt;code&gt;frozen = true&lt;/code&gt;. New writes for that bucket get a 503; reads still work.&lt;/li&gt;
&lt;li&gt;Wait for in-flight writes that started before the freeze to finish.&lt;/li&gt;
&lt;li&gt;Run a final catch-up for that bucket and verify the counts.&lt;/li&gt;
&lt;li&gt;Flip &lt;code&gt;shard_name&lt;/code&gt; to &lt;code&gt;shard_a&lt;/code&gt; and set &lt;code&gt;frozen = false&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The client retries the 503, and the retry lands on the new shard. Users in that bucket see a few seconds of "try again", and nobody else notices anything.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8cachbufpsmvtubrvg9i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8cachbufpsmvtubrvg9i.png" alt=" " width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: roll out in waves
&lt;/h3&gt;

&lt;p&gt;Start with one quiet bucket as a pilot. Then move a wave of 10, then a larger wave, then the rest. Only one bucket is frozen at a time. Watch errors, latency, 503 counts and shard CPU and disk between waves.&lt;/p&gt;

&lt;p&gt;After the last flip, keep the old copies of the moved tables for a safe window as a rollback option. Take a final backup, then drop them. The old database lives on as the products database, with its replicas and cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a fourth shard later
&lt;/h3&gt;

&lt;p&gt;The same machinery works. Set &lt;code&gt;target_shard = 'shard_d'&lt;/code&gt; for the chosen buckets, copy from each bucket's current shard, then freeze and flip bucket by bucket. The bucket rule never changes, so no user is rehashed. Only whole buckets move.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff48jn8bu4ewzjcgnxpjl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff48jn8bu4ewzjcgnxpjl.png" alt=" " width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs and limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shard last, not first.&lt;/strong&gt; Sharding adds a router, a migration job and cross-shard query problems. I would only do it after indexes, cache, replicas and a bigger machine have run out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-shard queries get harder.&lt;/strong&gt; "All orders in Chennai yesterday" now touches three databases. Reports should come from an analytics copy, not from the shards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 503 is a real cost.&lt;/strong&gt; It is small, but the client must retry safely, so writes need to be idempotent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disk does not shrink by itself.&lt;/strong&gt; Dropping the moved tables frees used space, but the provisioned disk size stays where it was.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick the bucket count up front.&lt;/strong&gt; Changing the bucket rule later means rehashing everyone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Map users to fixed buckets, and store only the bucket-to-shard map.&lt;/p&gt;

&lt;p&gt;Move one bucket at a time: freeze, final catch-up, verify, flip.&lt;/p&gt;

&lt;p&gt;The same steps that create the first shards also add the next one.&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>googlecloud</category>
      <category>database</category>
      <category>postgres</category>
    </item>
  </channel>
</rss>
