Scaling Beyond Limits: The Sharding Guide for Norwegian Systems
Let’s be honest: sharding is a nightmare. It is the complexity tax you pay when you have been too successful for your own good. As a systems architect who has spent the last decade watching databases implode under Black Friday loads, I usually give clients one piece of advice before we talk about sharding: Don't do it unless you have to.
Most developers jump to horizontal scaling because it sounds like the "Netflix way" to handle traffic. In reality, you are trading ACID compliance and join complexity for raw throughput. But sometimes, you hit the physical limits of hardware. Even the fastest NVMe storage and the beefiest CPUs have a ceiling. When a single writer node hits 100% usage during a flash sale, or your dataset exceeds the capacity of a single disk array, vertical scaling dies. That is where we start slicing data.
This guide isn't about the theory you read in Computer Science 101. This is about how we implement sharding in production environments in 2023, specifically dealing with the latency constraints of the Nordic region and the data sovereignty requirements of the Datatilsynet.
The Hardware Reality Check
Before you shard, you must verify you have actually maxed out vertical scaling. I have seen teams in Oslo rewrite their entire backend to support sharding when all they needed was to move from a generic cloud instance to a high-performance KVM VPS with dedicated resources.
In a virtualized environment, Steal Time is the enemy. If your database is fighting for CPU cycles with a noisy neighbor, your sharding logic will fail because of timeout inconsistencies. This is why we built CoolVDS on KVM with strict resource isolation. When you run a database, you need to know that an I/O operation takes exactly 0.5ms, not 0.5ms now and 50ms later.
Pro Tip: Check your steal time before optimizing queries. Runtopand look at the%stvalue. If it's above 0.0 on a database server, migrate immediately. Your code isn't the problem; your host is.
Strategy 1: Application-Level Sharding (The "Control Freak" Method)
In this model, the application logic decides which database node to talk to. It is the most flexible but requires strict discipline. We typically use a Consistent Hashing ring to distribute keys. This minimizes data movement when you add new shards—a critical feature when you are scaling live infrastructure.
Here is a Python implementation of a deterministic shard selector we deployed for a high-traffic fintech platform earlier this year:
import hashlib
class ShardManager:
def __init__(self, shards):
self.shards = shards
self.total_shards = len(shards)
def get_shard(self, user_id):
# Use MD5 for speed and uniform distribution (not for security)
hash_val = hashlib.md5(str(user_id).encode('utf-8')).hexdigest()
# Convert first 8 chars to int and modulo by total shards
shard_index = int(hash_val[:8], 16) % self.total_shards
return self.shards[shard_index]
# Configuration map
db_shards = [
{"host": "10.0.0.5", "name": "shard_01"},
{"host": "10.0.0.6", "name": "shard_02"},
{"host": "10.0.0.7", "name": "shard_03"}
]
router = ShardManager(db_shards)
target = router.get_shard(user_id=849201)
print(f"Routing user 849201 to {target['host']}")This code runs on the application server. The beauty of this approach is that the database doesn't know it's sharded. Each CoolVDS instance runs a standard PostgreSQL or MySQL installation. The complexity lives in your code, which allows for infinite customizability but requires rigorous testing.
Strategy 2: The Directory-Based Lookup
For SaaS providers operating under strict GDPR or Schrems II compliance, algorithmic sharding is often insufficient. You need to guarantee that Norwegian user data stays on servers physically located in Norway (like our Oslo datacenter), while German data stays in Frankfurt.
In this scenario, we use a Lookup Service. A lightweight, highly cached database (Redis or a small SQL table) maps a TenantID to a specific database string.
-- The Lookup Table (lives on a highly available central node)
CREATE TABLE shard_map (
tenant_id INT PRIMARY KEY,
region VARCHAR(10),
connection_string VARCHAR(255),
is_active BOOLEAN DEFAULT TRUE
);
-- Example Data
INSERT INTO shard_map (tenant_id, region, connection_string) VALUES
(101, 'NO', 'postgres://user:pass@no-osl-db01.coolvds.com:5432/db_tenant_101'),
(102, 'DE', 'postgres://user:pass@de-fra-db01.coolvds.com:5432/db_tenant_102');This adds a network hop (latency), so caching this map is mandatory. However, it gives you legal compliance superpowers. When Datatilsynet audits your data residency, you can point to the shard_map and prove exactly where every byte resides.
The Infrastructure Layer: Latency Kills Consistency
Sharding introduces network overhead. In a monolithic setup, a `JOIN` is a memory operation. In a sharded setup, a `JOIN` (if you are brave enough to attempt cross-shard joins) is a network operation.
If your servers are in Oslo and your app servers are in Stockholm, you are adding ~10-15ms of latency per round trip. In a complex transaction with 10 queries, that is 150ms of dead time. This is why local hosting is not just about patriotism; it is physics. Placing your application servers on the same CoolVDS subnet as your database shards reduces that latency to sub-millisecond levels.
Optimizing the Kernel for High Connection Counts
When you shard, you explode the number of database connections. Instead of one connection per thread to one DB, you might maintain pools for 10 shards. You will hit Linux file descriptor limits and TCP restrictions fast. We configure all our high-performance images with these sysctl tweaks by default, but here is what you need to verify in /etc/sysctl.conf:
# Allow more open files
fs.file-max = 2097152
# Increase local port range for high outgoing connections to shards
net.ipv4.ip_local_port_range = 1024 65535
# Reuse sockets in TIME_WAIT state for new connections
net.ipv4.tcp_tw_reuse = 1
# Increase backlog for incoming connections
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 8192Apply these with `sysctl -p`. Without these, your sharding architecture will fail under load, not because of the database, but because the OS refuses to open new sockets.
The "Emergency Brake" via ProxySQL
If you are running MySQL 8.0, you don't always need to rewrite your application code. We often deploy ProxySQL as a middleware layer. It sits between your app and the database cluster, handling the sharding logic transparently.
Here is a snippet of a ProxySQL configuration that splits reads and writes, and shards based on user IDs. This allows you to scale the backend on CoolVDS without redeploying the application.
-- Define Hostgroups (Shards)
INSERT INTO mysql_servers (hostgroup_id, hostname, port) VALUES (10, '10.0.0.20', 3306);
INSERT INTO mysql_servers (hostgroup_id, hostname, port) VALUES (20, '10.0.0.21', 3306);
-- Sharding Rule: Users < 10000 go to HG 10, others to HG 20
INSERT INTO mysql_query_rules (rule_id, active, username, match_pattern, destination_hostgroup, apply)
VALUES (1, 1, 'app_user', '^SELECT.*WHERE user_id < 10000', 10, 1);
INSERT INTO mysql_query_rules (rule_id, active, username, match_pattern, destination_hostgroup, apply)
VALUES (2, 1, 'app_user', '^SELECT.*WHERE user_id >= 10000', 20, 1);
LOAD MYSQL QUERY RULES TO RUNTIME;Conclusion: Choose Your Battlefield
Sharding is a powerful tool, but it introduces operational overhead that requires robust infrastructure. You cannot build a stable sharded system on oversold, budget VPS hosting where I/O creates bottlenecks. You need dedicated NVMe throughput, predictable CPU behavior, and low-latency internal networking.
If you are hitting the limits of your current setup, don't just add complexity. Verify your foundation first. Often, a migration to a properly tuned environment like CoolVDS allows you to delay sharding for another year. But when that day comes, we have the architecture ready to support you.
Ready to test your sharding logic? Deploy a high-performance Ubuntu 22.04 instance on CoolVDS in Oslo today and see the difference raw NVMe makes.