Logger to append, truncate, replay logs
A log store is a record storage model where updates only need to append rather than seeking and writing. To load the records, the log store is read from a position and replayed back. The ordering of records ensure a consistent model. A log store would grow without bound unless it is maintained. One way reduce its size is to re-append the records to be preserved, then truncate the store. Truncating the store should release storage back to the operating system. However, not all data is required to be released, so long as the disk footprint remains roughly proportional to the data retained.
In C++ or C++14 (or higher), implement a simple log store with the following requirements and functions:
• The implementation must be thread safe
• Records are variably sized opaque blobs of bytes anywhere from 1 byte to 32MB
• The log store must store on disk (not an in-memory log store)
• Truncation must be efficient and not require too much copying of data
append(blob) -> position Append a record blob to the log store.
get_position() -> position Gets the current position of the log store.
truncate(position) -> Removes all data before the given position in the log store. Disk space used must remain roughly proportional to retained data size.
replay(position, callback) Replays all record blobs from the position onward, invoking the callback for each blob visited
Note: The above function signatures are only illustrative. You may vary your interface in whatever way you think makes sense. For example, instead of a callback for replay(…), you may decide to use an iterator pattern.
LogStore<T>:- Creates a log store on the disk. The log store can store log records in custom format.
- To minimize the latency each log record is stored as a single file on the disk.
- An index is maintained to keep track of the number of records stored on disk.
append(blob): Append creates a new temporay file. The record is stored in the temporary file and then the file is moved to the Log store location. Every append operation increaments the index.truncate(position): Deletes the files from thestarting indexto the index passed as parameter.LogIteratoriterates over the records.
To support Multi Threading, mutex lock is used in case of contention.
-
read:lock --> open file --> unlock --> read file
-
append:create new temporary record -> lock --> increament current index --> rename the file --> unlock
-
truncate: the start index is a source of contention as other threads may manipulate it while the truncate operation is in process:lock --> store the start index --> move the start index to the new position --> unlock --> delete the file.
LogStore: Logstore provides APIs to create, append and iterate over log storeLogIterator: LogIterator provides APIs to iterate over logsLogWriter: LogWriter stores, reads logs from the storage
- make
- Clang++
$ make clean
$ make
$ ./mainmake clean: cleans the current foldermake test: builds the test for single threadingmake mt_test: builds the test for multi threading
Basic Test:
$ make clean
$ make test
$ ./testMultithread Test:
$ make clean
$ make mt_test
$ ./mt_testCustom format should inherit the Formatter class and implement the virtual FormatToString.
Example:
class StringBlob: public Formatter {
private:
std::string message;
public:
StringBlob() : message("") {}
StringBlob(std::string _msg) : message(_msg) {}
std::string FormatToString() {
return message;
}
}; // Create LogStore
LogStore<StringBlob> log("LogStore");
// append Logs
log.Append(StringBlob("Message1"));
log.Append(StringBlob("Message2"));
// iterate over logs
for (LogIterator<StringBlob> itr = log.Begin(); itr != log.End(); itr++) {
std::cout << (*itr) << std::endl;
}
// truncate log store
uint64_t pos = log.GetIndex();
log.Truncate(pos-1);