Why Sharding Isn’t Dead: Modern Alternatives and When to Use Them
The article revisits the rise and fall of database sharding, explains why it became problematic, and evaluates newer cloud‑native, distributed‑SQL, and serverless databases as modern replacements, offering a practical four‑step guide to help engineers choose the right solution for their workload and team.
1. The Golden Age of Sharding
In the early days, a single MySQL instance could handle tens of thousands of rows with millisecond latency. As user bases grew to millions and tables reached tens of millions of rows, single‑node databases became a bottleneck for both query speed and concurrency, especially during flash sales.
Sharding—splitting a large logical table into multiple physical tables or databases—was the answer. Horizontal sharding distributes rows across many tables, while vertical sharding separates different business domains (e.g., users, orders, products) into separate databases, reducing contention.
Typical sharding strategies include:
Horizontal splitting : split by year, user‑id modulo, etc.
Vertical splitting : split by business module.
Back in the day, mastering sharding was a must‑have skill for any high‑traffic internet company and a common interview topic.
2. The Pain Points of Sharding
2.1 Cross‑database JOINs
When order data lives in an "order" database and user data lives in a "user" database, a single SQL JOIN is impossible. Developers resort to a two‑step process: fetch orders, collect user IDs, then query the user database and manually merge results in application code.
// Manual cross‑database query example
List<Order> orders = orderMapper.selectByUserId(userId);
List<Long> userIds = orders.stream().map(Order::getUserId).collect(Collectors.toList());
List<User> users = userMapper.selectBatchIds(userIds);
Map<Long, User> userMap = users.stream().collect(Collectors.toMap(User::getId, Function.identity()));
orders.forEach(order -> order.setUser(userMap.get(order.getUserId())));Adding additional filters (e.g., order amount > 1000) multiplies the number of queries, causing severe latency spikes.
2.2 Distributed Transactions
Ensuring atomicity across multiple databases (order, inventory, coupon) requires complex patterns such as 2PC, TCC, SAGA, or local message tables, each bringing its own operational overhead and performance penalties.
// TCC implementation skeleton
@Compensable(confirmMethod = "confirmDeduct", cancelMethod = "cancelDeduct")
public void tryDeduct(Long productId, int amount) {
int rows = stockMapper.freeze(productId, amount);
if (rows == 0) throw new StockInsufficientException();
}
public void confirmDeduct(Long productId, int amount) {
stockMapper.confirmDeduct(productId, amount);
}
public void cancelDeduct(Long productId, int amount) {
stockMapper.unfreeze(productId, amount);
}2.3 Scaling and Migration
Expanding from 8 to 16 shards often requires full data re‑distribution. Two common approaches are:
Stop‑the‑world migration : pause services, run scripts to move data—acceptable for small datasets but disastrous for billions of rows.
Online migration : dual‑write to new shards and sync history, but introduces consistency bugs and duplicate data.
2.4 Operational Overhead
Each shard needs its own backup, monitoring, and failover plan. A modest sharding setup can easily demand a dedicated ops team, inflating personnel costs by hundreds of thousands of dollars annually.
3. New Forces: Cloud‑Native, Distributed‑SQL, and Serverless Databases
3.1 Cloud‑Native Databases
Providers such as Alibaba Cloud PolarDB, AWS Aurora, and Tencent TDSQL abstract sharding behind a “storage‑compute separation” architecture. Developers write ordinary MySQL‑compatible SQL while the platform automatically partitions data and handles cross‑shard consistency.
Compute nodes : execute SQL, manage transactions, scale horizontally.
Storage nodes : store data in many small fragments, invisible to the user.
Example code works unchanged:
@Transactional
public void createOrder(Order order) {
orderMapper.insert(order); // order table
stockMapper.deduct(order.getProductId(), order.getAmount()); // stock table
couponMapper.use(order.getCouponId()); // coupon table
}Migration to PolarDB can reduce ops effort dramatically, though cloud service fees increase with data volume.
3.2 Distributed‑SQL Databases
Systems like TiDB, CockroachDB, and Google Spanner retain MySQL syntax while providing automatic horizontal scaling and true distributed transactions.
TiDB stores data in a distributed KV layer (TiKV). Tables are automatically split into “regions” of tens of megabytes, each placed on a different node.
// TiDB transaction example (same as MySQL syntax)
@Transactional
public void placeOrder(Order order) {
jdbcTemplate.update("UPDATE stock SET amount = amount - ? WHERE product_id = ?",
order.getAmount(), order.getProductId());
jdbcTemplate.update("INSERT INTO orders (id, user_id, amount) VALUES (?, ?, ?)",
order.getId(), order.getUserId(), order.getAmount());
// TiDB guarantees cross‑region atomicity
}Deploying TiDB requires expertise; configuration parameters run into the hundreds.
3.3 Serverless Databases
Serverless offerings (e.g., Alibaba Lindorm Serverless, AWS Aurora Serverless) auto‑scale compute resources on demand and charge only for actual usage. They eliminate manual sharding entirely.
The main drawback is cold‑start latency, which is acceptable for non‑critical workloads such as logging or analytics.
4. How to Choose the Right Approach
4.1 Data Volume & Concurrency
If a single table stays below 10 million rows and peak QPS is under 10 k, a simple cloud RDS instance suffices.
For 10 million–100 million rows and 10 k–50 k QPS, cloud‑native databases are the sweet spot.
Beyond 100 million rows or >50 k QPS, consider distributed‑SQL or custom sharding.
4.2 Team Skillset
Java‑centric teams may prefer cloud‑native services with GUI management.
Teams with strong ops capability can adopt TiDB or CockroachDB for higher performance.
Very small teams should start with serverless options to avoid operational burden.
4.3 Business Criticality
Core services (payments, authentication) demand proven, stable solutions—cloud‑native or well‑tested sharding.
Non‑core services (analytics, logs) can safely experiment with newer serverless or distributed‑SQL offerings.
4.4 Cost Considerations
New buzzwords often look attractive but may increase total cost of ownership. For a modest project, an open‑source sharding middleware (e.g., Sharding‑JDBC) can be far cheaper than a managed distributed‑SQL service.
5. Final Verdict
Sharding is not dead; it has simply moved from the application layer to the database layer. Modern platforms embed the same “split‑into‑small‑chunks” logic internally, offering better ergonomics and lower ops cost.
Engineers should treat sharding as a historical technique: understand its original problem space, know its limitations, and recognize when newer abstractions make it unnecessary.
6. Take‑aways
Don’t be intimidated by terms like cloud‑native, serverless, or HTAP—they are just new wrappers around familiar concepts.
Focus on fundamentals: indexing, query planning, and transaction models remain relevant regardless of the underlying platform.
Choose the simplest solution that meets performance, reliability, and budget requirements; flashy tech is not a substitute for business value.
IT Services Circle
Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
