Console Login

Breaking the Monolith: Database Sharding Strategies That Actually Work (2023 Edition)

Database Sharding: When UPDATE Takes 5 Seconds, It’s Time to Break Things

I still remember the silence on the Slack channel. It was November 2022, Black Friday week. We had a monolithic PostgreSQL instance running on a massive dedicated server—128 cores, 1TB RAM. We thought we were invincible. Then traffic spiked 400%.

The CPUs weren't the problem. The NVMe drives were saturated. Writes started locking up. The replication lag to the slaves drifted from milliseconds to minutes. We were dead in the water because we relied on vertical scaling (scaling up) for too long. We spent the next 48 hours manually splitting tables while the CEO breathed down our necks.

If you are reading this, you are likely hitting that same ceiling. Your iowait is creeping up, and your slow query log is growing faster than your user base. It is time to talk about sharding.

The Brutal Reality of Vertical Scaling

Vertical scaling is easy. You migrate to a bigger CoolVDS instance, change a few lines in postgresql.conf, and go back to sleep. But physics is undefeated. Eventually, you hit the limits of a single bus speed, a single network interface, or the locking mechanisms of the database engine itself.

Sharding—splitting your data horizontally across multiple nodes—is the only path to infinite write scalability. But it introduces complexity that can kill your application if you get it wrong.

Strategy 1: Application-Level Sharding

This is the "classic" approach. Your application code decides which database node to talk to. It gives you total control but bloats your codebase.

You typically use a Shard Key (like user_id or tenant_id). Here is a simplified logic of how this looks in a Python backend:

import hashlib

# List of database connection strings (CoolVDS NVMe instances)
SHARDS = [
    "postgres://db_node_01_oslo",
    "postgres://db_node_02_oslo",
    "postgres://db_node_03_oslo",
    "postgres://db_node_04_oslo"
]

def get_shard_connection(user_id):
    # Deterministic hashing
    hash_val = int(hashlib.md5(str(user_id).encode('utf-8')).hexdigest(), 16)
    shard_index = hash_val % len(SHARDS)
    return connect(SHARDS[shard_index])

# Now every write for this user hits the specific shard
conn = get_shard_connection(59201)
cursor = conn.cursor()
cursor.execute("INSERT INTO orders ...")

The Trade-off: Resharding is a nightmare. If you add a 5th node, the modulo math changes (% 5 instead of % 4), and you have to migrate 20-25% of your data to new locations. To solve this, you need Consistent Hashing, but that is a topic for another architecture review.

Strategy 2: Native Partitioning (PostgreSQL 15)

Since we are in 2023, PostgreSQL 15 has made declarative partitioning robust enough for production. While partitioning is technically splitting tables on the same disk, combined with Foreign Data Wrappers (postgres_fdw), it acts as a sharding layer.

However, for true multi-node sharding, many of us use the Citus extension. It turns Postgres into a distributed database. But if you want to stick to vanilla Postgres, you use declarative partitioning by hash.

-- Parent table
CREATE TABLE sensor_data (
    id BIGSERIAL,
    device_id INT NOT NULL,
    recorded_at TIMESTAMPTZ NOT NULL,
    payload JSONB
) PARTITION BY HASH (device_id);

-- Create partitions (can be on different tablespaces/disks)
CREATE TABLE sensor_data_0 PARTITION OF sensor_data 
    FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE sensor_data_1 PARTITION OF sensor_data 
    FOR VALUES WITH (MODULUS 4, REMAINDER 1);
-- ... repeat for other remainders
Pro Tip: When using partitioning, ensure your enable_partition_pruning is set to on in your config. Otherwise, the planner scans every single partition, destroying your performance gains.

The Infrastructure Bottleneck: Latency

Here is where most architects fail. They design a perfect sharding strategy on paper, but deploy it on cheap cloud instances with "noisy neighbors."

When you shard, you increase network chatter. A query that used to be a local RAM lookup might now require cross-node communication. If your hosting provider has high jitter or unstable latency, your SELECT queries will hang.

This is why we built the CoolVDS network directly connected to NIX (Norwegian Internet Exchange). In a sharded setup, the latency between Node A and Node B needs to be sub-millisecond. We achieve this by using KVM virtualization (kernel-based) rather than container-based VPS, ensuring your network stack isn't fighting for CPU cycles with 500 other customers.

Performance Tuning for Sharded Nodes

On each CoolVDS node (assuming a standard 16GB RAM instance), you must tune the Linux kernel to handle the increased TCP connections typical in a sharded environment.

# /etc/sysctl.conf adjustments for high-throughput database nodes

# Increase backlog for sudden connection spikes
net.core.somaxconn = 4096

# Allow more local ports for outbound connections to other shards
net.ipv4.ip_local_port_range = 1024 65535

# Reduce TIME_WAIT state to free up sockets faster
net.ipv4.tcp_tw_reuse = 1

# Increase buffer limits for TCP
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

After applying these, run sysctl -p. Don't just copy-paste; monitor your specific load with netstat -s.

The Data Sovereignty Angle (Schrems II & GDPR)

Technical architecture doesn't exist in a vacuum. If you are operating in Norway or the EU, sharding adds a compliance layer. If you shard data across regions, you might accidentally replicate PII (Personally Identifiable Information) to a jurisdiction that violates GDPR.

Keeping your shards within CoolVDS's Oslo data center simplifies this massive headache. You get the redundancy of multiple physical racks without the legal nightmare of data crossing borders. With the Datatilsynet (Norwegian Data Protection Authority) becoming stricter post-2022, local hosting is not just a performance feature; it's a legal shield.

Conclusion: Start Small, but Plan Big

Don't implement sharding if you have 10GB of data. You are over-engineering. Stick to a well-tuned single instance with read replicas.

But if you are pushing 500GB+ and write latency is killing your UX, it is time to split the load. The software (Postgres 15, MySQL 8) is ready. The challenge is the hardware.

Don't let slow I/O kill your SEO or your uptime. Deploy a cluster of high-frequency NVMe instances on CoolVDS today. We provide the raw horsepower; you provide the architectural genius.