a distributed database that splits data across multiple servers using consistent hashing and a gossip protocol. each server runs a segment tree for range queries and handles locking to prevent conflicts.
what it does:
- auto-rebalances shards when load gets uneven (3-second broadcast)
- handles range queries in log time using segment trees
- prevents write conflicts with interval locking
- runs 8 shards in docker, tests with a client
8 shards connected in a ring. a test client sends requests to them over grpc.
[ client ]
|||||
▼▼▼▼▼
[1] ←→ [2] ←→ [3] ←→ ... ←→ [8]
└─────────────────────────────┘
gossip network (every 3s)
when data comes in, the shard:
- routes based on action type (query / lock / insert)
- stores data in a binary tree
- checks tree for conflicts on locks
- broadcasts current size to other shards
each key gets hashed to a shard. each shard owns 160 virtual points on the ring to spread load evenly.
key 42500
→ hash_key(42500)
→ find in sorted hashes
→ return shard id
data stored as a binary interval tree. each node tracks the min/max values in its range. queries walk the tree in log time.
every 500ms, each shard checks if it has too much data (default: > 100 items).
if it does, and 5 seconds have passed since the last rebalance:
- find the midpoint of the data range
- extract all data above the midpoint
- send it to the right neighbor
- shrink own range to below midpoint
- neighbor expands its range to accept new data
this keeps shards balanced without a central coordinator.
client sends request like "give me all keys from 10 to 100".
shard checks if this range overlaps with what it owns. if not, send a redirect (301) to the next shard. if yes, walk the tree and return results.
when multiple shards own parts of the range, the client gets redirected in a chain until all data is collected.
client requests a lock on range [10, 50].
server walks the tree and checks if any node in that range is already locked. if yes, return conflict (409). if no, mark all overlapping nodes as locked and return success (200).
to unlock, send same range with empty data packet.
prevents two clients from writing to overlapping ranges at the same time.
cmd/
client/
client.go - test suite, 5 phases
listener.go - listens to shard responses
server/
main.go - startup, goroutine orchestration
handlers.go - request routing
migration.go - 500ms rebalance monitor
storage/
engine.go - tree operations, locks
tree.go - binary tree implementation
hash_ring.go - consistent hashing
proto/
service.proto - message definitions
docker-compose.yml - 8 shard cluster setup
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
protoc --go_out=. --go-grpc_out=. proto/service.proto proto/metadata.protodocker compose down && docker compose up --buildwaits 10s for shards to start, then test client runs 5 phases:
- basic distribution - 100 points
- heavy load - 5 waves of 500 points each
- query correctness - full range queries
- locking - concurrent lock tests
- cluster consistency - shard state checks