Katkat

Execute a TTL Key-Value Store with Transactions

OpendoorBackend Engineer · Technical Screen

Last updated

This problem tests the implementation of an in-memory key-value store supporting TTL expiration, atomic transactions with rollback capabilities, and logical time-based validation.

Problem Statement

Design a simple in-memory key-value store that supports basic operations, TTL-based expiration, and a single active transaction. You must maintain a logical clock (time) that increments with each operation.

Requirements

  1. SET(key, value, ttl): Sets a key to a value that expires at current_time + ttl.
  2. GET(key): Returns the value if the key exists and has not expired; otherwise returns NULL.
  3. DELETE(key): Removes the key.
  4. BEGIN(): Starts a transaction. Only one transaction can be active at a time.
  5. COMMIT(): Persists all changes made since BEGIN().
  6. ROLLBACK(): Discards all changes made since BEGIN().
  7. TICK(): Increments the logical clock by 1.

Transactions are isolated: changes made during a transaction are not visible to GET unless the current scope is within the transaction. If a SET or DELETE is called outside a transaction, it takes effect immediately.

Interface

class KVStore:
    def __init__(self):
        self.time = 0
        
    def tick(self): ...
    def set(self, key, value, ttl): ...
    def get(self, key): ...
    def delete(self, key): ...
    def begin(self): ...
    def commit(self): ...
    def rollback(self): ...

Constraints

  • All keys and values are strings.
  • Time is a non-negative integer.
  • ttl is a positive integer indicating duration from the current time.

Example

kv = KVStore()
kv.set("a", "1", 10)
kv.tick()
kv.get("a") # returns "1"
kv.begin()
kv.set("a", "2", 5)
kv.get("a") # returns "2"
kv.rollback()
kv.get("a") # returns "1" (original value persists)

Hints

Hint 1

Use a dictionary to store the current state and a separate 'staging' dictionary to track changes made during a transaction.

Hint 2

Store expiration times as `absolute_time = current_time + ttl` to simplify expiration checks.

Hint 3

During a transaction, a 'read' should check the staging area first; if the key isn't there, fall back to the main storage.

Follow-up questions

  • How would you optimize the GET operation if the store contained millions of keys with different TTLs?
  • What changes would be required to support nested transactions (e.g., SAVEPOINTs)?
  • How would you handle persistence to disk to ensure the store survives a process restart?