Extend the persistent key-value store so that data is written across multiple files, each with a maximum size.
Use the following exact on-disk log format for every mutating operation:
- put(key, value) is stored as the record: 'P|key|value\n'
- delete(key) is stored as the record: 'D|key\n'
- get(key) is not written to disk
A file may contain multiple records. When appending the next record would make the current file exceed max_file_size characters, start a new file and write the record there.
Your program runs in two phases:
1. Execute before_restart_ops on an empty store, writing each put/delete to the log files.
2. Simulate a restart by rebuilding the in-memory store only by replaying all files from oldest to newest.
3. Execute after_restart_ops on the restored store, continuing to append new records to the existing files with the same size rule.
Return all get results in order, the final file contents, and the final store contents sorted by key.
Examples
Input: (10, [('put', 'a', '1'), ('put', 'bb', '2'), ('get', 'a')], [('get', 'bb'), ('delete', 'a'), ('get', 'a'), ('put', 'c', '3')])
Expected Output: (['1', '2', None], ['P|a|1\n', 'P|bb|2\n', 'D|a\nP|c|3\n'], [('bb', '2'), ('c', '3')])
Explanation: The second put does not fit in the first file, so a new file is created. After restart, recovery replays both files before new operations continue.
Input: (8, [('put', 'aa', 'b'), ('put', 'x', 'y')], [('get', 'aa'), ('put', 'aa', 'c'), ('get', 'aa')])
Expected Output: (['b', 'c'], ['P|aa|b\n', 'P|x|y\n', 'P|aa|c\n'], [('aa', 'c'), ('x', 'y')])
Explanation: Overwriting a key after restart adds a new log record in a later file, and replaying all files yields the newest value.
Input: (10, [('put', 'a', '1'), ('delete', 'a')], [('get', 'a')])
Expected Output: ([None], ['P|a|1\nD|a\n'], [])
Explanation: Edge case: the delete record fits exactly into the remaining space of the first file, and recovery correctly removes the key.
Input: (12, [('put', 'a', '1')], [('put', 'b', '2'), ('get', 'a'), ('get', 'b')])
Expected Output: (['1', '2'], ['P|a|1\nP|b|2\n'], [('a', '1'), ('b', '2')])
Explanation: After restart, new records should keep using the last file if there is still space.
Input: (5, [], [('get', 'z')])
Expected Output: ([None], [], [])
Explanation: Edge case: no persisted records at all.