2
0

persistence.zig 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. const std = @import("std");
  2. const storage = @import("storage.zig");
  3. const BUFFER_SIZE = 1024 * 1024 * 8;
  4. const FLUSH_THRESHOLD = (BUFFER_SIZE * 3) / 4;
  5. var storage_file: ?std.fs.File = null;
  6. const c_allocator = std.heap.c_allocator;
  7. var mutex: std.Thread.Mutex = .{};
  8. var write_buffer: [BUFFER_SIZE]u8 = undefined;
  9. var buffer_position: usize = 0;
  10. var instant_wal: bool = false;
  11. const OPCode = enum {
  12. W,
  13. D,
  14. };
  15. pub fn init() !void {
  16. const cwd = std.fs.cwd();
  17. storage_file = cwd.openFile(".db", .{ .mode = .read_write }) catch |err| blk: {
  18. if (err == std.fs.File.OpenError.FileNotFound) {
  19. std.debug.print("No persisted data found, starting fresh...\n", .{});
  20. const file = try cwd.createFile(".db", .{ .read = true });
  21. std.debug.print("Created new storage file .db\n", .{});
  22. break :blk file;
  23. } else {
  24. return err;
  25. }
  26. };
  27. var record_count: usize = 0;
  28. try restoreFromFile(storage_file.?, &record_count);
  29. std.debug.print("Restored {d} records from persistence", .{record_count});
  30. storage_file.?.close();
  31. storage_file = try cwd.openFile(".db", .{ .mode = .write_only });
  32. try storage_file.?.seekFromEnd(0);
  33. }
  34. fn restoreFromFile(file: std.fs.File, record_count: *usize) !void {
  35. var read_buffer: [BUFFER_SIZE]u8 = undefined;
  36. var record_buffer = std.ArrayListUnmanaged(u8){};
  37. defer record_buffer.deinit(c_allocator);
  38. while (true) {
  39. const n = try file.read(&read_buffer);
  40. if (n == 0) break;
  41. var start: usize = 0;
  42. while (std.mem.indexOfScalarPos(u8, read_buffer[0..n], start, '\r')) |end| {
  43. try record_buffer.appendSlice(c_allocator, read_buffer[start..end]);
  44. restoreRecord(record_buffer.items, record_count);
  45. record_buffer.clearRetainingCapacity();
  46. start = end + 1;
  47. }
  48. if (start < n) {
  49. try record_buffer.appendSlice(c_allocator, read_buffer[start..n]);
  50. }
  51. }
  52. if (record_buffer.items.len > 0) {
  53. restoreRecord(record_buffer.items, record_count);
  54. }
  55. }
  56. fn restoreRecord(record: []const u8, record_count: *usize) void {
  57. if (record.len == 0) {
  58. return;
  59. }
  60. record_count.* += 1;
  61. std.debug.print("Restoring record N:{d}\r", .{record_count.*});
  62. const first_pipe = std.mem.indexOfScalar(u8, record, '|') orelse return;
  63. const opcode = record[0..first_pipe];
  64. const remaining = record[first_pipe + 1 ..];
  65. const second_pipe = std.mem.indexOfScalar(u8, remaining, '|') orelse return;
  66. const key = remaining[0..second_pipe];
  67. const value = remaining[second_pipe + 1 ..];
  68. const opcodeEnum = std.meta.stringToEnum(OPCode, opcode) orelse return;
  69. switch (opcodeEnum) {
  70. .W => _ = storage.restore(key, value),
  71. .D => _ = storage.restoreDelete(key),
  72. }
  73. }
  74. pub fn setInstantWal(enabled: bool) void {
  75. instant_wal = enabled;
  76. }
  77. fn recordLen(key: []const u8, value: []const u8) usize {
  78. return 1 + 1 + key.len + 1 + value.len + 1;
  79. }
  80. fn encodeRecord(buf: []u8, opcode: u8, key: []const u8, value: []const u8) usize {
  81. var pos: usize = 0;
  82. buf[pos] = opcode;
  83. pos += 1;
  84. buf[pos] = '|';
  85. pos += 1;
  86. @memcpy(buf[pos .. pos + key.len], key);
  87. pos += key.len;
  88. buf[pos] = '|';
  89. pos += 1;
  90. @memcpy(buf[pos .. pos + value.len], value);
  91. pos += value.len;
  92. buf[pos] = '\r';
  93. pos += 1;
  94. return pos;
  95. }
  96. fn syncFile() !void {
  97. if (storage_file) |f| {
  98. try f.sync();
  99. }
  100. }
  101. pub fn persist(opcode: u8, key: []const u8, value: []const u8) !void {
  102. const record_len = recordLen(key, value);
  103. mutex.lock();
  104. defer mutex.unlock();
  105. if (record_len > BUFFER_SIZE) {
  106. return error.RecordTooLarge;
  107. }
  108. if (buffer_position + record_len > FLUSH_THRESHOLD) {
  109. try flushBuffer();
  110. }
  111. buffer_position += encodeRecord(write_buffer[buffer_position..], opcode, key, value);
  112. if (instant_wal) {
  113. try flushBuffer();
  114. try syncFile();
  115. }
  116. }
  117. pub fn flush() !void {
  118. mutex.lock();
  119. defer mutex.unlock();
  120. try flushBuffer();
  121. try syncFile();
  122. }
  123. fn flushBuffer() !void {
  124. if (buffer_position == 0) {
  125. return;
  126. }
  127. const f = storage_file orelse return error.StorageFileNotOpen;
  128. try f.writeAll(write_buffer[0..buffer_position]);
  129. buffer_position = 0;
  130. }
  131. // -- Tests --
  132. test "recordLen matches encoded record length" {
  133. try std.testing.expectEqual(@as(usize, 4 + 3 + 5), recordLen("key", "value"));
  134. try std.testing.expectEqual(@as(usize, 4), recordLen("", ""));
  135. }
  136. test "encodeRecord produces WAL framing" {
  137. var buf: [64]u8 = undefined;
  138. const written = encodeRecord(&buf, 'W', "key1", "value1");
  139. try std.testing.expectEqualStrings("W|key1|value1\r", buf[0..written]);
  140. try std.testing.expectEqual(@as(usize, recordLen("key1", "value1")), written);
  141. }
  142. test "encodeRecord delete framing" {
  143. var buf: [64]u8 = undefined;
  144. const written = encodeRecord(&buf, 'D', "key2", "");
  145. try std.testing.expectEqualStrings("D|key2|\r", buf[0..written]);
  146. }