<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Vasilios Syrakis - system design</title>
    <subtitle>A simple blog made with Zola and Duckquill</subtitle>
    <link rel="self" type="application/atom+xml" href="https://vsyrakis.dev/tags/system-design/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://vsyrakis.dev"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-07-11T00:00:00+00:00</updated>
    <id>https://vsyrakis.dev/tags/system-design/atom.xml</id>
    <entry xml:lang="en">
        <title>How to Avoid Sharding Yourself</title>
        <published>2026-07-11T00:00:00+00:00</published>
        <updated>2026-07-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Vasilios Syrakis
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://vsyrakis.dev/blog/how-to-avoid-sharding-yourself/"/>
        <id>https://vsyrakis.dev/blog/how-to-avoid-sharding-yourself/</id>
        
        <content type="html" xml:base="https://vsyrakis.dev/blog/how-to-avoid-sharding-yourself/">&lt;p&gt;I watched a video named &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=rxR4nIQ0hCk&quot;&gt;The Problem Sharding a Database
Solves&lt;&#x2F;a&gt; by &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;@WebDevCody&quot;&gt;Web Dev
Cody&lt;&#x2F;a&gt;.&lt;br &#x2F;&gt;
He has quite a large number of subscribers, his video popped up on my feed, and
I enjoyed the video.&lt;&#x2F;p&gt;
&lt;p&gt;It’s about splitting up your database and the advantage of doing so, but it
introduces a small problem and that is the question of how to ensure that each
user accesses the correct database shard.&lt;&#x2F;p&gt;
&lt;img class=&quot;no-hover&quot;alt=&quot;screenshot of cody&amp;#x27;s video&quot;src=&quot;&amp;#x2F;img&amp;#x2F;cody.png&quot;&#x2F;&gt;&lt;small&gt;
Also, the diagramming tool he&#x27;s using is cool and if anyone knows what it is -
please tell me!
&lt;&#x2F;small&gt;
&lt;p&gt;However, one thing stuck out to me that I wanted to intercept in order to
provide advice on, based on my own real professional experience.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-caught-my-attention&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-caught-my-attention&quot; aria-label=&quot;Anchor link for: what-caught-my-attention&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
What caught my attention&lt;&#x2F;h2&gt;
&lt;p&gt;Cody delivers on what I think is the point of his video - to outline the advantage of sharding.
But there is an example for how to implement routing a tenant to the shards and
this is what I’ll be providing advice on, based on my professional experience.&lt;&#x2F;p&gt;
&lt;p&gt;Cody describes a naive approach where you take some kind of tenant identifier
and hash it, which gives you a deterministic key which maps to the shard that
the tenant lives on.&lt;&#x2F;p&gt;
&lt;p&gt;He then rightly points out that if you pick this approach, as the number and
size of tenants grows you’ve now made it harder to rebalance your shards, or
migrate tenants, because when you insert new shards, the deterministic hashing
essentially breaks.&lt;&#x2F;p&gt;
&lt;p&gt;This leads Cody to describe a possibly more elegant approach called “virtual
buckets” where instead of a hash being a 1:1 mapping between a tenant and a
shard, you take the modulus of the hash which maps a range of tenants to a
shard instead.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;algorithmic-routing-can-be-an-anti-pattern&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#algorithmic-routing-can-be-an-anti-pattern&quot; aria-label=&quot;Anchor link for: algorithmic-routing-can-be-an-anti-pattern&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Algorithmic Routing can be an Anti-Pattern&lt;&#x2F;h2&gt;
&lt;p&gt;These methods of routing tenants to shards aren’t broken, but let’s call them anti-patterns.&lt;&#x2F;p&gt;
&lt;p&gt;First, let’s talk about why hashing is an attractive option.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;It’s stateless&lt;&#x2F;li&gt;
&lt;li&gt;It’s fast&lt;&#x2F;li&gt;
&lt;li&gt;It doesn’t need a database lookup&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;But it leaves you open to &lt;strong&gt;operational pain&lt;&#x2F;strong&gt;, even with the use of virtual buckets.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;one-tenant-within-a-bucket-can-dwarf-other-tenants&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#one-tenant-within-a-bucket-can-dwarf-other-tenants&quot; aria-label=&quot;Anchor link for: one-tenant-within-a-bucket-can-dwarf-other-tenants&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
One tenant within a bucket can dwarf other tenants&lt;&#x2F;h4&gt;
&lt;p&gt;If we just assume that the Pareto principle will play out eventually, you’re
going to have one tenant which consumes 80% of a single shards capacity.&lt;&#x2F;p&gt;
&lt;p&gt;In order to rebalance them, you need to change their identifier. Now you have
to choose between accepting the noisy-neighbour problem where a huge tenant
fights for dominance with the other small tenants on the shard, or you add a
special-case to your hashing algorithm that lives on forever, and most likely
grows, needing to be maintained by operators.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;no-flexibility-for-isolation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#no-flexibility-for-isolation&quot; aria-label=&quot;Anchor link for: no-flexibility-for-isolation&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
No flexibility for isolation&lt;&#x2F;h4&gt;
&lt;p&gt;This is semi-related to the above, essentially,  if you wanted to have
different shards, like a “quarantine” shard for misbehaving tenants, or a “free
tier&#x2F;trial” shard, or an “important enterprise customer” shard with only 1
tenant on it, you again have to special-case the algorithm.&lt;&#x2F;p&gt;
&lt;p&gt;These requirements will predictably appear as you grow and get more customers.&lt;br &#x2F;&gt;
Maybe they won’t, but we can take care of this with very little operational
friction as we’ll see later.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;rebalancing-friction-doesn-t-go-away&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#rebalancing-friction-doesn-t-go-away&quot; aria-label=&quot;Anchor link for: rebalancing-friction-doesn-t-go-away&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Rebalancing friction doesn’t go away&lt;&#x2F;h4&gt;
&lt;p&gt;Cody does point out that even when you add new shards, you still might need to
rebalance tenants, but you can maybe do this with some background process or
lazy migration that writes to both shards at the same time.&lt;&#x2F;p&gt;
&lt;p&gt;For some businesses these might be acceptable operational events that someone
has to take care of, perhaps after-hours or during scheduled downtime.&lt;&#x2F;p&gt;
&lt;p&gt;Note: you &lt;em&gt;can&lt;&#x2F;em&gt; mitigate this particular downside with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Consistent_hashing&quot;&gt;Consistent
Hashing&lt;&#x2F;a&gt; but the others are
still unsolved.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;use-a-lookup-table-for-tenant-routing&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#use-a-lookup-table-for-tenant-routing&quot; aria-label=&quot;Anchor link for: use-a-lookup-table-for-tenant-routing&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Use a Lookup Table for Tenant Routing&lt;&#x2F;h2&gt;
&lt;img class=&quot;no-hover&quot;alt=&quot;diagram of architecture that uses a lookup table&quot;src=&quot;&amp;#x2F;img&amp;#x2F;lookup-table.png&quot;&#x2F;&gt;
&lt;p&gt;I know it’s appealing to avoid adding infrastructure to solve a problem, but I
think this situation justifies the addition of a database to act as a lookup
table or directory for your tenants.&lt;&#x2F;p&gt;
&lt;p&gt;For the vast majority of businesses, a dedicated Redis or Postgres instance can
serve this purpose and take you very far.&lt;&#x2F;p&gt;
&lt;p&gt;Yes, it can be a single point of failure, and when it goes down so does your
entire app probably, but do a little back of the napkin calculation on the
probability of redis (in a cluster, with high-availability), becoming suddenly
unavailable for any serious amount of time. It probably won’t go under 3 nines
of availability.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;surgical-rebalancing&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#surgical-rebalancing&quot; aria-label=&quot;Anchor link for: surgical-rebalancing&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Surgical Rebalancing&lt;&#x2F;h4&gt;
&lt;p&gt;Your rebalancing goes from a stressful situation of changing the algorithm,
propagating it to your gateways and praying, to a 5 step process that an intern
can execute without bringing the company down.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Identify a big noisy tenant&lt;&#x2F;li&gt;
&lt;li&gt;Provision a new shard&lt;&#x2F;li&gt;
&lt;li&gt;Run a background job to copy data, stream new writes in the meantime&lt;&#x2F;li&gt;
&lt;li&gt;Switch to the new shard atomically&lt;&#x2F;li&gt;
&lt;li&gt;Wait a week and delete the old copy&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h4 id=&quot;better-isolation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#better-isolation&quot; aria-label=&quot;Anchor link for: better-isolation&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Better Isolation&lt;&#x2F;h4&gt;
&lt;p&gt;Before, changing the way hashing works meant that you were changing the
algorithm for &lt;em&gt;everyone&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;One mistake and everyone is cooked.&lt;&#x2F;p&gt;
&lt;p&gt;With a lookup table, you’re changing a single record.&lt;&#x2F;p&gt;
&lt;p&gt;You also unlock the scenario discussed above where you can move your enterprise
customers to their own shard, or throw the naughty tenants into quarantine so
they don’t annoy everyone else.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;fives-nines-of-availability-no-i-said-nine-fives&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fives-nines-of-availability-no-i-said-nine-fives&quot; aria-label=&quot;Anchor link for: fives-nines-of-availability-no-i-said-nine-fives&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Fives nines of availability? No, I said nine fives…&lt;&#x2F;h2&gt;
&lt;p&gt;If you’ve somehow reached the point where that lookup table is no longer
sufficient (and you’re still reading this blog? lol), or you just feel
uncomfortable with having to make a lookup on every request to a single point
of failure, there’s a pattern you can employ to scale things further and bring
the probability of an outage to a number approximating zero.&lt;&#x2F;p&gt;
&lt;p&gt;You can actually read about
&lt;sup&gt;
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.atlassian.com&#x2F;blog&#x2F;atlassian-engineering&#x2F;aws-scaling-multi-region-low-latency-service&quot;&gt;1&lt;&#x2F;a&gt;
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.atlassian.com&#x2F;blog&#x2F;atlassian-engineering&#x2F;atlassian-critical-services-above-six-nines-of-availability&quot;&gt;2&lt;&#x2F;a&gt;
&lt;&#x2F;sup&gt;
how Atlassian solved this exact problem using the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Command_Query_Responsibility_Segregation&quot;&gt;CQRS pattern&lt;&#x2F;a&gt;.
However, I’m going to explain it here even more concisely.&lt;&#x2F;p&gt;
&lt;p&gt;CQRS, or “Command Query Responsibility Segregation”, can enable a caching
pattern where distributed nodes are able to keep a local copy of the data that
they can query, cutting the latency of tenant lookups to practically zero, and
allowing them to continue to operate while the central lookup table is offline.&lt;&#x2F;p&gt;
&lt;p&gt;Put even more simply:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Something writes to the central lookup table (the Command)&lt;&#x2F;li&gt;
&lt;li&gt;The central lookup table publishes an event to a broker&lt;&#x2F;li&gt;
&lt;li&gt;The nodes pick up the event (the Query)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;img class=&quot;no-hover&quot;alt=&quot;diagram of an architecture that demonstrates cqrs&quot;src=&quot;&amp;#x2F;img&amp;#x2F;cqrs-example.png&quot;&#x2F;&gt;
&lt;p&gt;An example of a broker could be an SNS topic, or a version number on an API, or
whatever else you want to use, as long as it’s cheap for nodes to access and
backed by something highly available.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;a-contrived-example&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#a-contrived-example&quot; aria-label=&quot;Anchor link for: a-contrived-example&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
A Contrived example&lt;&#x2F;h3&gt;
&lt;p&gt;Imagine an isolated scenario on a single machine where there’s a database, an
application with its own sqlite cache, and a file containing a number to
indicate what version the data is at currently.&lt;&#x2F;p&gt;
&lt;p&gt;When a row is added or changed in the database, it increments the number in the
file. The application has a background thread checking the number in the file,
and notices that it’s been incremented, so it replaces the sqlite cache
atomically.&lt;&#x2F;p&gt;
&lt;p&gt;This entire time, the application has been able to read from its local sqlite
database uninterrupted.&lt;&#x2F;p&gt;
&lt;p&gt;The same concept applies to your distributed tenant routing. The central lookup
table acts as the source of truth, but your application nodes or API gateways
can maintain their own fast, resilient local caches of the routing rules.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot;&gt;&lt;i class=&quot;icon&quot;&gt;&lt;&#x2F;i&gt;&lt;&#x2F;a&gt;
Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;While clever routing to shards might seem appealing at first, saving you an
extra database lookup and avoiding a new piece of infrastructure, it introduces
operational complexity as your application scales and your tenants grow.&lt;&#x2F;p&gt;
&lt;p&gt;A lookup table is dead simple, painless, and gives you flexibility for later
on. If you need the scale, you can add it later with zero downtime.&lt;&#x2F;p&gt;
&lt;p&gt;Often, the “boring” infrastructure choice is exactly what you need to avoid
sharding yourself.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
