Distributed System - Learning
Here is learning concepts for distributed system through implementation.
1. The Messenger
1.1. Design a system where multiple services communicate reliably without shared memory. What primitives would you use and why?
-
Shared memory: in same machine
-
Message passing: different machines.
Answer: Message queues (Kafka, SQS) for async decoupling; gRPC/HTTP for synchronous RPCs. Each message must be self-contained. Discuss at-least-once vs at-most-once delivery, idempotency keys to handle retries, and correlation IDs for request tracing across services.
1.2. How does a service know which message it received is a response to a specific request it sent earlier?
Answer: Correlation/request IDs: the sender attaches a unique ID to each outgoing request. The receiver echoes this ID in its response. The sender maps incoming response IDs to pending callbacks. This is how HTTP/2 stream IDs, Kafka consumer group offsets, and Maelstrom msg_ids all work.
- How to know the client
- Using request id.
- How to know the offset of multiple packets
-
Sequence number: the byte that send in the payload.
-
Acknowledge number: the byte is expected next.
1.3. A service sends a request and never gets a response. How do you decide whether to retry?
-
Use timeouts with exponential backoff and jitter. Retry only idempotent operations (GET, PUT with full replacement) or operations with idempotency keys.
-
Use circuit breakers to stop retrying against a consistently failing downstream. Distinguish between 503 (retry) and 400/404 (do not retry).
1.4. What is the difference between a message broker and a service mesh? When would you choose each?
-
A message broker (Kafka, RabbitMQ) provides async, durable message delivery with decoupled producers and consumers.
-
A service mesh (Istio, Linkerd) handles synchronous service-to-service traffic with features like mTLS, retries, circuit breaking, and observability.
-
Use a broker when you need temporal decoupling or fan-out; use a mesh for sync RPC with cross-cutting network concerns.
1.5. How would you debug a system where messages are being processed more than once?
-
Check for duplicate delivery: is the queue at-least-once? Is the consumer crashing after processing but before acknowledging?
-
Add idempotency: track processed message IDs in a store and skip duplicates.
-
Use exactly-once semantics in Kafka (requires transactions + idempotent producers) for critical flows.
-
Log message IDs at each processing step to trace duplicates.
1.6. Serialisation Message: JSON vs Protobuf vs MessagePack
| Dimension | JSON | Protobuf | MessagePack |
|---|---|---|---|
| Human readable | ✅ Yes | ❌ No (binary) | ❌ No (binary) |
| Payload size | Large (field names repeated) | Small (field tags, no names) | Medium (compact binary JSON) |
| Parse speed | Slow | Fast | Fast |
| Schema required | No | Yes (.proto file) |
No |
| Schema evolution | Flexible but fragile | Excellent (field numbers) | Good if using maps; fragile if using arrays |
| Debugging ease | Easy (curl, browser) | Hard (need .proto) |
Hard (binary) |
| Best for | REST APIs, prototyping | gRPC, high-throughput microservices | Cache serialization, Redis, IPC |
Verdict: Start with JSON for correctness, migrate to Protobuf when payload size or parse latency becomes a bottleneck.
Example:
JSON:
{
"id": 123,
"name": "Alice",
"active": true
}
Message Pack:
map(3)
string("id")
int(123)
string("name")
string("Alice")
string("active")
true
1.7. Transport: TCP or UDP
| Dimension | TCP | UDP |
|---|---|---|
| Delivery guarantee | Exactly once (at the OS level) | Best effort — packets may be lost |
| Ordering | In-order delivery guaranteed | Out-of-order delivery possible |
| Latency | Higher (3-way handshake, retransmissions, ACKs) | Lower (fire-and-forget) |
| Head-of-line blocking | Yes — one lost packet blocks subsequent packets until it is retransmitted | No — each datagram is independent |
| Connection setup | Connection-oriented (3-way handshake required) | Connectionless (first packet is sent immediately) |
| Flow & congestion control | Built-in (sliding window, congestion control) | None (application must implement if needed) |
| Reliability | Reliable (ACKs + retransmissions) | Unreliable (no ACKs or retransmissions) |
| Packet boundaries | Byte stream (no message boundaries) | Datagram-oriented (message boundaries preserved) |
| Header size | 20–60 bytes | 8 bytes |
| Typical use cases | HTTP/HTTPS, databases, file transfer, email, SSH | DNS, VoIP, video streaming, online gaming, QUIC |
Verdict: TCP for reliability by default. UDP only when you implement your own reliability layer (like QUIC) or can tolerate loss (metrics, video).
1.8. Why Grpc sync call needed timeout:
-
Sends a message to another node
-
Blocks until a response is received (matched by in_reply_to)
-
Returns the response body
-
Times out after a configurable duration (default: 1 second)
1.7. Why server know what client request id
-
Add request id in the header.
-
Server response: what the client it reply to.
1.8. What is asynchronous RPC callback ?
-
Sends a message to a target node
-
Registers a callback function keyed by the outgoing msg_id
-
Returns immediately (non-blocking)
-
When a reply arrives (with matching in_reply_to), invokes the callback with the reply body
1.9. What is callback reaper to store message, when recipient crashes or the network drops ?
- DLQ for failure process consumer.
1.10. How server handle callback reaper ?
-
Records the timestamp when each callback is registered
-
Periodically scans for callbacks older than a threshold (default: 2 seconds)
-
Removes expired callbacks and invokes them with a timeout error
-
Reports how many callbacks were reaped
1.11. Using add jitter for thunderherd retry
- Add jitter for pods do not retry at the same time.
1.12. JSON Encode from and to Byte
-
Merge from byte -> data structure: int, string, map.
-
Convert it to JSON again.
1.13. Log for message tracing
- Add request, response and message_type.
1.14. Deduplication in network layer using LRU Cache
-
Dedup by using (src, msg_id).
-
But use LRU for dedup it -> but around message IDs (capacity: 1000)
1.15. Chaos Testing - Drop network packet loss
- When chaos mode is enabled, the node randomly drops a configurable percentage of outgoing messages (does not send them to stdout)
1.16. Benchmark Node Throughput and Latency
- Benchmark latency and throughput per node.