Semantic vs. Non-Semantic Logical Separation
I like to boil things down to the conceptual base and strip away the noise.
In The Non-Technical Guide to Confusing Words & Concepts in Tech, I showed how functions, methods, and procedures are all basically the same thing: a reusable block of logic that may take arguments and may return values. The differences are just convention.
The same applies here.
Whether you call it sharding, partitioning, bucketing, or grouping — at its core, it’s just a logical separation of stuff.
And “logical” almost always has physical consequences. Even if you kept everything on one machine, the way you separate work changes which thread, core, NUMA domain, or cache line ends up serving it.
This is important to preempt a common trap:
someone might think “well if it’s all on one box, then surely 100K requests to one topic is better than 1 request each to 100K topics — better for cache locality and pipelining.”
And within one machine, they’d be right.
But here’s the mindset shift: assume distributed.
Once the system spans machines, racks, or regions, the bottleneck isn’t CPU cache — it’s shard imbalance and cluster capacity.
At that scale:
- A single hot shard = a cluster bottleneck.
- Scattering/salting keys = healthier distribution.
So the baseline lesson is:
👉 Always think in terms of distributed systems first. Especially for system design interviews... you dont want to be going down rabbit holes and end up in Wonderland.
1. Semantic Logical Separation
-
Definition: Data is separated based on something semantically meaningful.
-
Examples:
- All rows for
user_id=123
live together. - Customers in the EU vs. customers in the US go to different partitions.
- All rows for
-
Goal: Keep related things local for easier access and transactions.
-
Advantages:
- Locality → one shard contains everything you need for an entity or tenant.
- Efficient joins and transactions within the boundary.
-
Disadvantages:
- Skew & hotspots → if one entity/region is much bigger or busier, that shard can bottleneck.
2. Non-Semantic Logical Separation
-
Definition: Data is separated in a way that has nothing to do with meaning, usually by hashing or ranges.
-
Examples:
hash(user_id) mod N
to pick a shard.- Kafka assigning events to partitions by hashing the key.
-
Goal: Evenly distribute load across machines, regardless of semantics.
-
Advantages:
- Balance → smooth distribution of reads/writes.
- Scalability → easy to add/remove servers with consistent hashing.
-
Disadvantages:
- Loss of locality → data that “belongs together” might be scattered.
- Queries/transactions spanning entities require cross-shard work.
3. Hybrid Strategies
Real-world systems often combine both:
- Group data semantically (e.g. by
user_id
). - Then salt/hash inside the bucket to spread hot users across multiple machines.
- This hybrid avoids skew while keeping some logical grouping.
👉 Hybrid = balance and locality.
4. Relational Databases: Table Partitioning
Relational databases use the term partitioning, but usually in a semantic sense:
- Range partitioning: by date (
sales_2025_Q1
,sales_2025_Q2
). - List partitioning: by region (
region IN ('US','EU')
). - Tenant partitioning: by
customer_id
.
These partitions improve query planning and maintenance (e.g. pruning partitions, faster VACUUMs, easier archival), not necessarily scale-out across machines.
Some RDBMSs support hash partitioning (Postgres USING HASH
, Oracle hash clusters), but the default is almost always semantic because relational workloads rely heavily on range scans and entity locality.
5. KV Store Caches (Redis, Memcached)
Caches highlight the distinction in a different way:
-
At the infrastructure layer:
- Keys are always hashed into slots/servers.
- This is non-semantic distribution — the system doesn’t care about meaning, just balance.
-
At the application layer:
- Developers impose semantic naming:
user:123:profile
,feed:456:page:2
.
- These give the illusion of semantic grouping, but internally those keys may land on totally different servers.
- Developers impose semantic naming:
-
Hot key problem:
- If one cache key (e.g.
post:ABC123
) is hammered, only one shard handles it. - Solution = salting (
post:ABC123#0..99
) or caching at multiple layers (edge/CDN).
- If one cache key (e.g.
👉 So caches are non-semantic by default, but semantic if you design your keys that way.
6. Load Balancing
Even in load balancers, the same axis appears:
-
Semantic routing:
- All requests for
user_id=123
→ same backend (to preserve session state). - Good for locality, but risks hotspots if one user is very busy.
- All requests for
-
Non-semantic routing:
- Pure round-robin, least-connections, or random distribution.
- Good for balance, but no guarantee of locality (session stickiness lost unless you add state).
Many systems use sticky sessions (semantic) on top of hash or round-robin (non-semantic) balancing — a hybrid strategy.
7. Why This Distinction Is Overlooked
- Terminology drift: "partition" in RDBMS vs. "shard" in distributed DBs.
- History: early web sharding = semantic (“user_id bucketed into DBs”); later distributed stores = non-semantic (hash).
- Teaching: system design courses often emphasize scaling (non-semantic) and skip over the semantic vs. non-semantic axis.
8. Quick Framework
- Need locality (per-user, per-tenant, per-session) → semantic.
- Need balance (avoid hotspots, elastic scaling) → non-semantic.
- Need both → hybrid (semantic bucket + hash/salt inside).
✅ Takeaway:
Semantic and non-semantic partitioning isn’t just about databases — it shows up in caches, queues, and load balancers too.
The design choice always comes down to a trade-off between locality and balance.