0% found this document useful (0 votes)
4 views18 pages

Vector Clocks

The document outlines the implementation of a thread-safe sliding window counter using a concurrent map and ring buffer for efficient updates and memory usage. It also details the design of a file system with functional and non-functional requirements, emphasizing thread safety, extensibility, and correctness. Additionally, it explains the storage and management of vector clocks in distributed systems, highlighting techniques to control their growth and maintain scalability.

Uploaded by

Akshay Pandita
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views18 pages

Vector Clocks

The document outlines the implementation of a thread-safe sliding window counter using a concurrent map and ring buffer for efficient updates and memory usage. It also details the design of a file system with functional and non-functional requirements, emphasizing thread safety, extensibility, and correctness. Additionally, it explains the storage and management of vector clocks in distributed systems, highlighting techniques to control their growth and maintain scalability.

Uploaded by

Akshay Pandita
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

“​ I would implement a thread-safe sliding window counter using a concurrent​

​map keyed by grouping attributes (like IP or IP+Agent). Each key maps to a​


​fixed-size ring buffer of time buckets, allowing O(1) updates and bounded​
​memory. Thread safety is ensured using per-key locking or atomic​
​operations.”​

✅ Final Takeaway​

​👉 Use:​

​●​ ​Concurrent map​​for grouping​

​●​ ​Ring buffer (time buckets)​​for window​

​●​ ​Per-key locking​​for scalability​

​Design a file system:​


​Functional requirements:​

​●​ A ​ llow users to create, delete, read, and write files and directories at any location, with​
​recursive deletion of directories removing all contents.​
​●​ ​Support a hierarchical directory structure that allows arbitrary nesting depth, where every​
​node except the root has exactly one parent, forming a tree structure.​
​●​ ​Enable path-based navigation to resolve nodes by absolute path, with all operations​
​accepting absolute paths and a PathResolver translating string paths into node​
​references by traversing the directory tree.​
​●​ ​Implement per-user file permissions for each node, including READ, WRITE, and​
​EXECUTE, rejecting operations if the current user lacks the required permission, while​
​owners implicitly have all permissions.​
​●​ ​Allow users to search for files and directories by name pattern, starting from a directory​
​and searching recursively through all descendants via depth-first traversal.​

​Non functional requirements:​


​ or the non-functional requirements section of your file system design, you should cover the​
F
​following topics:​
​●​ ​Thread Safety: Address how the system will ensure safe access when multiple threads​
​are reading and writing concurrently. Specifically, mention the use of a ReadWriteLock to​
​allow multiple concurrent readers while ensuring exclusive access for writers.​
​●​ ​Extensibility: Discuss how the design will allow for the addition of new types or behaviors​
​without modifying existing code. For example, new node types like SymbolicLink can be​
​added by extending the FileSystemNode class, maintaining flexibility in your system.​
​●​ ​Correctness: Explain how the system will enforce invariants and ensure that no invalid​
​state can be reached through the public API. This includes rejecting invalid operations to​
​prevent silent corruption of the system's state.​
​●​ ​These non-functional requirements are crucial for ensuring that your file system is​
​robust, maintainable, and reliable in a production environment.​

​Vector Clocks​
​Where are the vector clocks stored in a distributed key value store?​

​ ector clocks aren’t stored in some separate global service—they live​​right next to the data​
V
​they describe​​.​

🧠 Core idea​

​ ​​vector clock is metadata attached to a key (or a​​version of a value)​​that tracks causality​
A
​across replicas.​

👉 So the real answer is:​


​ ector clocks are stored alongside each value (or each version of a value)​
V
​inside the distributed key-value store.​

📦 Where exactly are they stored?​



​1. 🔑 With each key–value entry​
​For a given key:​

​{​
"​ key": "user:123",​
​"value": { ... },​
​"vector_clock": {​
​"nodeA": 3,​
​"nodeB": 5,​
​"nodeC": 2​
​}​
​}​

​​ S
● ​ tored in the same storage layer (disk / memory)​
​●​ ​Retrieved together with the value​

​2. 🔁 Per version (in multi-version systems)​


​In systems that allow conflicts (like Dynamo-style systems):​

{​ ​
​"key": "user:123",​
​"versions": [​
​{​
​"value": "...",​
​"vector_clock": { "A": 2, "B": 1 }​
​},​
​{​
​"value": "...",​
​"vector_clock": { "A": 1, "B": 2 }​
​}​
​]​
​}​

👉 Each conflicting version has its​​own vector clock​


🏗️ Where physically (architecture-wise)?​



​On each replica node​

​●​ ​Each node stores:​


​○​ ​value​
​○​ ​vector clock​
​●​ ​No centralized clock storage​
👉 This is crucial for​​decentralization​

🌐 How it works in distributed systems​



​Used in systems like:​

​​ A
● ​ mazon Dynamo​
​●​ ​Riak​
​●​ ​Apache Cassandra (uses a variant / simplified mechanisms)​

​Flow:​

​ .​ C
1 ​ lient writes → node updates vector clock​
​2.​ ​Replication happens → vector clocks travel with data​
​3.​ ​On conflict:​
​○​ ​Compare vector clocks​
​○​ ​Detect:​
​■​ ​causality​
​■​ ​concurrent updates​

⚠️ Important nuance (interview gold)​



👉 Vector clocks are​​not stored globally per node​​or cluster​

​They are:​

​​ ​
● ❌ Not centralized​
​●​ ​❌ Not shared independently​
✅ Always tied to a​​specific key/version​
​●​ ​

🧠 Why this design?​



​1. 📍 Local reasoning​
​Each key can independently:​

​●​ ​detect conflicts​


​●​ ​resolve causality​

​2. ⚡ Scalability​
​​ N
● ​ o global coordination needed​
​●​ ​Works in partitioned systems​

​3. 🔄 Conflict resolution​


​●​ ​Enables:​
​○​ ​last-write-wins alternatives​
​○​ ​application-level merge​

🔁 Alternative mental model​



​Think of vector clocks as:​

👉 “Version history embedded inside the data”​


​ ot:​
N
👉​ “A system-wide clock service”​

✅ Final takeaway​

​ ector clocks are stored as​​per-key (or per-version)​​metadata alongside the​
V
​value on each replica node​​, enabling decentralized​​conflict detection and​
​resolution.​

​If you want, I can also walk through:​

​​ H
● ​ ow vector clocks detect conflicts step-by-step​
​●​ ​Why they grow in size and how systems mitigate that (very common follow-up)​
​●​ ​Or compare with Lamport timestamps (another interview favorite)​
​ ector clocks are great for causality—but left unchecked, they​​grow with every participating​
V
​node​​, which isn’t sustainable in real systems. So​​production systems use a mix of​​pruning,​
​summarization, and alternative clocks​​to keep them​​bounded.​

🧠 Why vector clocks grow​



​A vector clock has one entry per node that has ever updated the key:​

​{ "A": 5, "B": 3, "C": 7, "D": 2, ... }​

​In large clusters or long-lived keys:​

​​ N
● ​ odes join/leave​
​●​ ​Many replicas touch the same key​

👉 The vector can become​​large and expensive to store/compare​


🛠️ How systems control growth​



​1) ✂️ Truncation (limit the number of entries)​
​Keep only the​​top K entries​​(usually the most recent​​or most relevant), drop the rest.​

​How it’s done:​

​​ S
● ​ ort entries by recency (or last update time)​
​●​ ​Keep, say,​​K = 10–20​
​●​ ​Remove older/least-relevant nodes​

​Trade-off:​

​​ ​
● 👍 Keeps metadata small​
👎 May lose some causality → can create​​false conflicts​
​●​ ​

​Used in systems like Amazon Dynamo and Riak.​

​2) 🧹 Garbage collection after convergence​


​Once replicas​​converge​​and conflicts are resolved:​
​​ O
● ​ lder versions (and their clocks) are discarded​
​●​ ​The surviving version carries a​​reduced clock​

​Idea:​

​●​ ​You don’t need history once it’s no longer relevant​

​3) 📉 Version collapsing / summarization​


​If multiple versions become causally ordered, collapse them into one:​

​{A:1} → {A:2} → {A:3}​

👉 Keep only​​{A:3}​

​Benefit:​

​​ R
● ​ emoves intermediate states​
​●​ ​Shrinks vector size naturally over time​

​4) 🆔 Logical node identifiers (stable, bounded set)​


​Instead of tracking every transient process:​

​​ U
● ​ se a​​fixed set of replica IDs​​(e.g., per shard/partition)​
​●​ ​Avoids unbounded growth from ephemeral nodes​

​5) 🔄 Dotted Version Vectors (DVV)​


​An improvement over classic vector clocks:​

​●​ ​Track a​​compact summary​​+ a​​single “dot”​​(the latest​​event)​

​Why it helps:​

​​ A
● ​ voids storing full history per update​
​●​ ​Reduces ambiguity after truncation​

​Used as an evolution of vector clocks in systems like Riak.​


​6) ⚖️ Switching to simpler clocks (when acceptable)​
​Some systems trade precision for simplicity:​

🕒 Last-Write-Wins (LWW)​

​​ U
● ​ se a timestamp instead of a vector​
​●​ ​Example: Apache Cassandra​

​Trade-off:​

​​ ​
● 👍 Constant size​
​●​ ​👎 Loses causality → can overwrite concurrent updates​

​7) 📦 Bounded contexts (per-partition clocks)​


​​ M
● ​ aintain clocks​​per shard/partition​​, not cluster-wide​
​●​ ​Limits the number of entries per clock​

⚠️ The core trade-off​



​Strategy​ ​Accuracy​ ​Size​ ​Risk​

​Full vector clock​ ✅ High​ ❌​ Large​


​ ​None​

​Truncated vector​ ​⚠️ Medium​ ​✅ ​ ​False conflicts​


​Bounded​

​LWW timestamp​ ​ ❌ Low​ ✅ Tiny​


​ ​Lost updates​

👉 Systems choose based on​​consistency vs efficiency​


🧠 Interview-ready answer​

“​ Systems prevent vector clocks from growing indefinitely by truncating entries to a​
​fixed size, garbage-collecting old versions after convergence, and sometimes using​
​alternatives like dotted version vectors or last-write-wins timestamps. These​
​techniques trade off strict causality for bounded metadata size.”​
✅ Final takeaway​

​​ V
● ​ ector clocks naturally grow with system size​
​●​ ​Real systems​​bound them deliberately​
​●​ ​The key trade-off is:​

​perfect causality vs practical scalability​

​If you want, I can walk you through:​

​​ A
● ​ concrete example of truncation causing a false conflict (very commonly asked)​
​●​ ​Or DVV vs vector clock comparison with diagrams​

​Rector pattern​
​Can you show me reactor pattern in action?​

​ bsolutely — let’s walk through the Reactor pattern like a live simulation so you can​​see​​what​
A
​the event loop is doing internally.​

​We’ll simulate:​

🧠
​ A chat server handling multiple clients​
​with ONE thread​
​using:​

​​ n
● ​ on-blocking sockets​
​●​ ​event loop​
​●​ ​epoll/select style behavior​
⚙️ Traditional Blocking Server​

​Normally:​

​ lient A → Thread A​
C
​Client B → Thread B​
​Client C → Thread C​

​Each thread waits here:​

​data = [Link]()​

​Problem:​

​ ​ t​hread blocked most of the time​



​●​ ​huge memory/context-switch cost​

🔥 Reactor Approach​

​Instead:​

​Event Loop Thread​


​↓​
​Wait for ALL socket events​
​↓​
​React only when ready​

​One thread handles MANY clients.​

🧩 Let’s Simulate It​



​Suppose:​

​​ 3
● ​ clients connected​
​●​ ​all sockets are non-blocking​
​Step 1️⃣ Register Sockets​
​Event loop tells OS:​

​"Notify me when any socket becomes readable"​

​Internally:​

​ [Link](socketA)​
e
​[Link](socketB)​
​[Link](socketC)​

​Step 2️⃣ Event Loop Sleeps Efficiently​


​events = [Link]()​

​ hread is now:​
T
✅​ NOT busy looping​
✅​ NOT polling manually​
✅​ sleeping inside kernel efficiently​

​Step 3️⃣ Client B Sends Message​


​Suppose:​

​Client B → "hello"​

​Kernel notices:​

​socketB is readable​

​Kernel wakes event loop.​

​Step 4️⃣ Event Loop Receives Event​


​events = [socketB]​
​Step 5️⃣ Reactor Dispatches Handler​
​handle_read(socketB)​

​Handler executes:​

​data = [Link]()​

​ ince socket already ready:​


S
✅​ returns immediately​
❌​ no blocking​

​Step 6️⃣ Event Loop Continues​


​After processing:​

​[Link]()​

​goes back to sleep.​

🧠 Key Insight​

​The thread blocks:​

​once centrally​
​instead of:​

​blocking per socket​

​This is the core breakthrough.​


🔥 Visual Timeline​

​Traditional Model​
​ hread A waiting...​
T
​Thread B waiting...​
​Thread C waiting...​

​Huge waste.​

​Reactor Model​
​One thread:​
​sleep until ANY event happens​

​Massively scalable.​

⚡ Real Event Loop Pseudocode​



​This is essentially Reactor:​

​while True:​

​ready_sockets = [Link]()​

​for socket in ready_sockets:​

​if socket == server_socket:​


​accept_new_connection()​

​else:​
​handle_client(socket)​

​That’s it.​
🧩 Real Working Example​

​Here’s a minimal real server:​

i​mport socket​
​import selectors​

​selector = [Link]()​

​ --------------------------------​
#
​# Accept new connections​
​# --------------------------------​
​def accept(server_socket):​

​client_socket, addr = server_socket.accept()​

​print("Connected:", addr)​

​client_socket.setblocking(False)​

​[Link](​
​client_socket,​
​selectors.EVENT_READ,​
​read_client​
​)​

​ --------------------------------​
#
​# Handle client messages​
​# --------------------------------​
​def read_client(client_socket):​

​data = client_socket.recv(1024)​

​if data:​
​print("Received:", [Link]())​

​client_socket.send(b"ACK\n")​

​else:​
​print("Disconnected")​

​ [Link](client_socket)​
s
​client_socket.close()​
​ --------------------------------​
#
​# Setup server​
​# --------------------------------​
​server = [Link]()​

​[Link](("localhost", 9999))​

​[Link]()​

​[Link](False)​

​[Link](​
​server,​
​selectors.EVENT_READ,​
​accept​
​)​

​print("Reactor server started...")​

​ --------------------------------​
#
​# THE EVENT LOOP​
​# --------------------------------​
​while True:​

​events = [Link]()​

​for key, mask in events:​

​callback = [Link]​

​callback([Link])​

🔥 What Makes This Reactor?​


1️⃣ Non-blocking sockets​



​setblocking(False)​
2️⃣ Central event loop​

​[Link]()​

3️⃣ Event dispatching​



​callback([Link])​

⚡ What Happens Internally​



​Suppose:​

​​ 1
● ​ 0,000 clients connected​
​●​ ​only 5 active​

​Traditional:​

​10,000 threads​

​Reactor:​

​ thread​
1
​processes only 5 active sockets​

​Huge efficiency gain.​

⚠️ Important Rule​

​Handlers must stay FAST.​

​Bad:​

​[Link](5)​

​inside handler.​

​Why?​

​Because:​
​entire event loop freezes​

​affecting ALL clients.​

🔥 So What About CPU-heavy Tasks?​



​Modern reactors delegate heavy work:​

​Event Loop​
​↓​
​Worker Thread Pool​

​Very common hybrid architecture.​

🧠 Real-world Systems​

​Nginx​
​​ e
● ​ poll​
​●​ ​worker event loops​

​[Link]​
​●​ ​single-threaded event loop​

​Netty​
​●​ ​Java reactor framework​
🧠 Final Mental Model​

​Reactor pattern is basically:​

​"Don’t waste threads waiting."​

​Instead:​

​ . Register interest in events​


1
​2. Sleep efficiently​
​3. React only when OS says work is ready​

​That’s the entire idea.​

​Understanding interfaces​

​[Link]

​Pdf in downloads pers mac​

​Hashmap collision handling​

​[Link]

​Pdf in downloads pers mac​

You might also like