Database Sharding Strategies: Survival Guide for High-Traffic Systems
There is a specific moment in every Systems Architect's career that haunts them. It usually happens around 2:00 AM on a Friday. Your primary database node, despite having 128GB of RAM and the most expensive CPU you could provision, hits 100% utilization. Connection pools drain. The application starts throwing 502s. You have vertically scaled until you hit the hardware ceiling. There is no bigger server to buy.
Welcome to the limit of the monolith. It is time to talk about sharding.
In the Norwegian hosting market, where data sovereignty (thanks to Datatilsynet and Schrems II) is as critical as raw throughput, sharding isn't just a performance tactic; it is often a compliance necessity. But it is also complex, dangerous, and expensive if architected poorly. Letβs dissect how to do it right using technology stable in late 2022, specifically looking at PostgreSQL architectures.
The Brutal Reality of Vertical vs. Horizontal Scaling
Vertical scaling (upgrading the VPS) is easy. You change a flavor in the dashboard, reboot, and you have double the RAM. But CPU clock speeds haven't jumped massively in the last five years. We are getting more cores, not significantly faster ones. Single-threaded database processes can only run so fast.
Horizontal scaling (Sharding) splits your dataset across multiple instances. Instead of one giant users table, you have ten smaller ones distributed across ten servers. The logic sounds simple, but the network overhead is the killer.
Pro Tip: Never start sharding until you have optimized your queries and indexes. If your EXPLAIN ANALYZE shows sequential scans on a 50GB table, sharding is just scaling your inefficiency. Fix the code first.
Architecture 1: Application-Level Sharding
This is the "manual" approach. Your application logic decides which database to connect to based on a Shard Key (usually user_id or tenant_id). It gives you total control but bloats your codebase.
The Logic Flow
function getDatabaseConnection(userId) {
const totalShards = 4;
const shardId = userId % totalShards;
const mapping = {
0: 'db-shard-01.norway-east.internal',
1: 'db-shard-02.norway-east.internal',
2: 'db-shard-03.norway-east.internal',
3: 'db-shard-04.norway-east.internal'
};
return createConnection(mapping[shardId]);
}
This works, but re-balancing is a nightmare. If Shard 02 fills up, moving data to Shard 05 involves manual migration scripts and downtime. This is why we prefer infrastructure-level solutions in 2022.
Architecture 2: PostgreSQL with Citus (The 2022 Standard)
For PostgreSQL users, the Citus extension (now fully integrated into Azure logic but usable as open source) transforms Postgres into a distributed database. It handles the routing for you.
Why do we use this over NoSQL options like MongoDB? ACID compliance. Financial and enterprise data in Oslo generally requires strict transactional integrity that eventual consistency cannot provide.
Implementation Strategy
First, you need to enable the extension in your postgresql.conf. This requires a restart.
# /etc/postgresql/14/main/postgresql.conf
shared_preload_libraries = 'citus'
Once the extension is loaded, you convert a standard table into a distributed one. Here is the SQL execution flow:
-- Step 1: Create the extension
CREATE EXTENSION citus;
-- Step 2: Define your coordinator and workers
-- (Run this on the coordinator node)
SELECT master_add_node('10.0.0.2', 5432);
SELECT master_add_node('10.0.0.3', 5432);
-- Step 3: Create your schema
CREATE TABLE sensor_data (
sensor_id bigint,
recorded_at timestamp,
temperature float,
location text
);
-- Step 4: Shard the table across nodes
-- We shard by 'sensor_id' to keep data for one device together
SELECT create_distributed_table('sensor_data', 'sensor_id');
The Latency Trap: Why Infrastructure Matters
Here is the part most tutorials skip. When you run a query like SELECT avg(temperature) FROM sensor_data, the coordinator node fans out this request to all worker nodes. It waits for all of them to return before aggregating the result.
If Worker Node 3 is on a cheap, oversold VPS where the neighbors are mining crypto, its CPU Steal time goes up. The entire query stalls waiting for the slowest node. This is the "Tail Latency" problem.
For distributed databases, consistent I/O is more important than peak I/O.
| Factor | Standard VPS | CoolVDS (KVM + NVMe) |
|---|---|---|
| CPU Allocation | Shared / Burstable | Dedicated / High Priority |
| Disk Latency | Variable (HDD/SATA SSD mix) | Consistent NVMe (<1ms) |
| Network Jitter | High (Public routes) | Low (Optimized Peering) |
This is why we architect CoolVDS using KVM virtualization. Unlike containers (LXC/OpenVZ) where the kernel is shared, KVM provides stronger isolation. Your database shards get the resources they expect, ensuring the coordinator node isn't left hanging.
Geo-Partitioning for GDPR & Datatilsynet
One of the strongest arguments for sharding in 2022 is legal, not technical. Under GDPR and the fallout of Schrems II, transferring Norwegian user data to US-owned cloud regions is legally perilous.
With sharding, you can map specific partition ranges to specific physical nodes.
-- Create a partition specifically for Norwegian users
CREATE TABLE users_no PARTITION OF users
FOR VALUES IN ('NO');
-- Ensure this table lives on the node located in Oslo
SELECT master_move_shard_placement(
shard_id_for_users_no,
'source_node',
'192.168.1.50' -- The IP of your CoolVDS instance in Oslo
);
This setup allows you to tell your auditors: "German data stays in Frankfurt, Norwegian data stays in Oslo." You achieve global scale while respecting local sovereignty.
Tuning the Linux Kernel for Shards
A distributed database creates thousands of TCP connections between nodes. The default Linux networking stack is often too conservative. On your CoolVDS instances, you need to tune sysctl.conf to handle the chatter.
# Increase the max backlog of connection requests
net.core.somaxconn = 4096
# Reuse Transfer Control Protocol (TCP) connections
net.ipv4.tcp_tw_reuse = 1
# Increase range of ephemeral ports for high outgoing connections
net.ipv4.ip_local_port_range = 1024 65535
# Maximize the backlog for incoming packets
net.core.netdev_max_backlog = 10000
Apply these with sysctl -p. Without this, your application might experience timeouts during traffic spikes, not because the DB is slow, but because the OS is dropping SYN packets.
Conclusion: Don't Shard Prematurely
Sharding introduces complexity. Backups become distributed. Transactions across shards (Distributed Transactions) are slower and harder to manage. However, when your dataset exceeds 2TB or your write throughput saturates a single NVMe drive, it is the only path forward.
If you are at that breaking point, you need a substrate that respects the physics of latency. Low latency, high IOPS, and strict data residency are non-negotiable.
Ready to architect a cluster that survives Black Friday? Spin up a high-performance KVM instance on CoolVDS today and test the network throughput yourself. Your database is only as fast as the wire it travels on.