Katkat

In-Memory Key-Value Store with Nested Transactions

Otter.AiSoftware Engineer · Technical Screen

Last updated

This problem tests the ability to implement a nested transactional key-value store using stack-based state management and scope shadowing.

Problem Statement

Design an in-memory key-value store that supports nested transactions. The store should handle basic set, get, and delete operations, along with begin, commit, and rollback commands to manage transaction scopes.

Requirements

  • set(key, value): Sets the value for a key in the current active transaction scope.
  • get(key): Returns the value for the key. If the key exists in the current scope, return that value. Otherwise, traverse up to parent scopes to find the value. Return None if the key is not found in any scope.
  • delete(key): Effectively hides the key in the current scope. Any get request for this key should return None.
  • begin(): Starts a new nested transaction scope.
  • commit(): Merges the current transaction scope into the parent scope. If there is no parent scope, the changes persist at the global level.
  • rollback(): Discards all changes made in the current transaction scope.

Signature

class TransactionalStore:
    def __init__(self):
        pass
    def set(self, key, value):
        pass
    def get(self, key):
        pass
    def delete(self, key):
        pass
    def begin(self):
        pass
    def commit(self):
        pass
    def rollback(self):
        pass

Examples

Example 1:

store = TransactionalStore()
store.set("a", 1)
print(store.get("a")) # Output: 1
store.begin()
store.set("a", 2)
print(store.get("a")) # Output: 2
store.rollback()
print(store.get("a")) # Output: 1

Constraints

  • Use a stack-based approach to represent the hierarchy of transaction states.
  • Ensure get operations run efficiently relative to the number of active transactions.

Hints

Hint 1

Consider maintaining a list of dictionaries (or hash maps), where each dictionary represents an isolated scope of the current transaction.

Hint 2

For 'set' and 'delete', always modify the dictionary at the top of the stack.

Hint 3

For 'get', search from the top of the stack downwards; if you find a tombstone (marker for deletion), return None immediately.

Follow-up questions

  • How would you modify your implementation to ensure the global (base) state is never lost if an error occurs?
  • What is the time complexity of the 'get' operation in the worst case, and can you optimize it?
  • How would you handle thread-safety if multiple users were calling 'begin' and 'commit' concurrently?