Design a Key-Value Store with Hit Counting
DatabricksSoftware Engineer · OnsiteLast updated
Design a concurrent, in-memory key-value store that tracks read-access frequency using a sliding-window hit counter per key while keeping memory footprint bounded.
Problem Statement
Design and implement an in-memory key-value store that maintains a sliding-window hit counter for every key. Every successful get operation increments the key's read hit counter. The system must support querying the hit count for any key over an arbitrary lookback window up to a configured maximum duration max_window_seconds.
Your solution should emphasize thread safety, bounded memory usage per key (preventing unbounded growth under high read throughput), and low latency for both reads and writes.
Class Interface
class HitCountingKVStore:
def __init__(self, max_window_seconds: int):
"""
Initialize the store with a maximum trackable window size in seconds.
"""
pass
def put(self, key: str, value: str) -> None:
"""
Insert or update a key-value pair.
"""
pass
def get(self, key: str, timestamp: int) -> Optional[str]:
"""
Retrieve the value for key. If key exists, record a hit at the given epoch timestamp (in seconds).
Returns None if the key does not exist.
"""
pass
def get_hit_count(self, key: str, window_seconds: int, timestamp: int) -> int:
"""
Return the number of successful hits for key in the interval:
[timestamp - window_seconds + 1, timestamp]
Constraints: 1 bool:
"""
Remove the key and its hit-counting history. Returns True if deleted, False if not found.
"""
pass
Requirements
- Counting Semantics: Only successful
getcalls (where the key is present) register as hits. Failed lookups must not register hits. - Bounded Memory: Memory overhead per key for tracking hits must be
O(max_window_seconds)regardless of how many millions of hits occur within that window. - Concurrency: Multiple threads may call
put,get,delete, andget_hit_countconcurrently. Ensure linearizable reads/writes per key while avoiding a single global lock across the entire store. - Time Monotonicity: Timestamps passed to
getandget_hit_countare non-decreasing per thread, but concurrent calls across threads may arrive slightly out of order within a small delta.
Example
store = HitCountingKVStore(max_window_seconds=300)
store.put("alpha", "data1")
store.get("alpha", timestamp=100) # returns "data1", hit recorded at t=100
store.get("alpha", timestamp=102) # returns "data1", hit recorded at t=102
store.get("beta", timestamp=103) # returns None, no hit recorded
# Query window: [105 - 5 + 1, 105] = [101, 105]
store.get_hit_count("alpha", window_seconds=5, timestamp=105) # returns 1 (only t=102)
# Query window: [105 - 10 + 1, 105] = [96, 105]
store.get_hit_count("alpha", window_seconds=10, timestamp=105) # returns 2 (t=100 and t=102)
Constraints
1 <= max_window_seconds <= 86400(up to 24 hours).1 <= len(key), len(value) <= 1024.- Read throughput is expected to be orders of magnitude higher than write throughput.
Hints
Hint 1
Storing individual timestamps in a dynamic array causes unbounded memory growth under high read volume; consider bucketing hits by second.
Hint 2
Use a fixed-size circular buffer (ring buffer) of length `max_window_seconds` paired with bucket timestamps to achieve `O(1)` memory overhead per key.
Hint 3
To avoid global lock contention, shard the key space across multiple striped locks (or maintain per-key mutexes via concurrent hash map buckets).
Follow-up questions
- How would you optimize `get_hit_count` from `O(window_seconds)` to `O(1)` time complexity without significantly penalizing `get` throughput?
- If timestamps arrive with sub-second resolution and high skew, how does the memory-accuracy trade-off change (e.g., sliding window counter vs. Count-Min Sketch)?
- How would you scale this design to a distributed cluster where `get` requests for the same key are routed across different replica nodes?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.