engine.zig 82 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713
  1. const std = @import("std");
  2. const pkvdb = @import("pkvdb.zig");
  3. const keydir = @import("keydir.zig");
  4. const ordered_index = @import("ordered_index.zig");
  5. pub const RecordRef = keydir.RecordRef;
  6. const KeyReader = keydir.Reader;
  7. pub const pkvdb_max_frame = pkvdb.max_transaction_size;
  8. pub const Operation = struct {
  9. opcode: pkvdb.Opcode,
  10. key: []const u8,
  11. value: []const u8 = "",
  12. };
  13. pub const Value = struct {
  14. bytes: []u8,
  15. lsn: u64,
  16. };
  17. pub const CompareCheck = struct {
  18. key: []const u8,
  19. expected_lsn: u64,
  20. };
  21. pub const CompareBatchResult = struct {
  22. lsn: u64,
  23. committed: bool,
  24. };
  25. pub const ScanEntry = struct {
  26. key: []u8,
  27. value: ?[]u8,
  28. lsn: u64,
  29. };
  30. pub const ScanBatch = struct {
  31. entries: []ScanEntry,
  32. next_cursor: []u8,
  33. done: bool,
  34. pub fn deinit(self: *ScanBatch, allocator: std.mem.Allocator) void {
  35. for (self.entries) |entry| {
  36. allocator.free(entry.key);
  37. if (entry.value) |value| allocator.free(value);
  38. }
  39. allocator.free(self.entries);
  40. allocator.free(self.next_cursor);
  41. self.* = undefined;
  42. }
  43. };
  44. pub const Status = struct {
  45. uuid: [16]u8,
  46. file_bytes: u64,
  47. latest_lsn: u64,
  48. oldest_lsn: u64,
  49. checkpoint_lsn: u64,
  50. journal_bytes_since_checkpoint: u64,
  51. live_keys: u64,
  52. keydir_bytes: u64,
  53. ordered_index_bytes: u64,
  54. bytes_written: u64,
  55. checksum_failures: u64,
  56. partial_tails: u64,
  57. recovery_ns: u64,
  58. checkpoint_ns: u64,
  59. connection_bytes: u64,
  60. active_requests: u64,
  61. commit_groups: u64,
  62. committed_transactions: u64,
  63. largest_commit_group: u64,
  64. };
  65. const Root = struct {
  66. block: pkvdb.Superblock,
  67. manifest: ?pkvdb.Manifest,
  68. };
  69. const PendingWrite = struct {
  70. operations: []const Operation,
  71. metadata: []const u8,
  72. checks: []const CompareCheck = &.{},
  73. next: ?*PendingWrite = null,
  74. completion: *WriteCompletion,
  75. lsn: u64 = 0,
  76. committed: bool = true,
  77. frame_position: usize = 0,
  78. changed: bool = false,
  79. prepared_position: usize = 0,
  80. prepared_count: usize = 0,
  81. bytes: usize,
  82. };
  83. const WriteCompletion = struct {
  84. condition: std.Thread.Condition = .{},
  85. remaining: usize,
  86. failure: ?anyerror = null,
  87. };
  88. const max_group_transactions = 4096;
  89. const max_group_bytes = 64 * 1024 * 1024;
  90. const max_queued_bytes = 128 * 1024 * 1024;
  91. const group_wait_ns = 250 * std.time.ns_per_us;
  92. const map_interval = 64 * 1024 * 1024;
  93. const Mapping = struct {
  94. bytes: []align(std.heap.page_size_min) u8,
  95. file_start: u64,
  96. logical_start: u64,
  97. logical_end: u64,
  98. };
  99. pub const Engine = struct {
  100. allocator: std.mem.Allocator,
  101. file: std.fs.File,
  102. directory: keydir.KeyDir,
  103. ordered: ordered_index.OrderedIndex,
  104. ordered_ready: bool = false,
  105. mappings: std.ArrayListUnmanaged(Mapping) = .{},
  106. mapping_lock: std.Thread.RwLock = .{},
  107. mapped_length: u64 = 0,
  108. lock: std.Thread.RwLock = .{},
  109. io_mutex: std.Thread.Mutex = .{},
  110. checkpoint_mutex: std.Thread.Mutex = .{},
  111. ordered_gate: std.Thread.Mutex = .{},
  112. queue_mutex: std.Thread.Mutex = .{},
  113. queue_condition: std.Thread.Condition = .{},
  114. queue_head: ?*PendingWrite = null,
  115. queue_tail: ?*PendingWrite = null,
  116. queued_bytes: usize = 0,
  117. writer_thread: ?std.Thread = null,
  118. writer_stopping: bool = false,
  119. writer_failed: bool = false,
  120. commit_groups: u64 = 0,
  121. committed_transactions: u64 = 0,
  122. largest_commit_group: u64 = 0,
  123. file_length: u64,
  124. latest_lsn: u64 = 0,
  125. oldest_lsn: u64 = 0,
  126. checkpoint_lsn: u64 = 0,
  127. journal_bytes_since_checkpoint: u64 = 0,
  128. bytes_written: u64 = 0,
  129. checksum_failures: u64 = 0,
  130. partial_tails: u64 = 0,
  131. recovery_ns: u64 = 0,
  132. checkpoint_ns: u64 = 0,
  133. connection_bytes: std.atomic.Value(u64) = std.atomic.Value(u64).init(0),
  134. active_requests: std.atomic.Value(u64) = std.atomic.Value(u64).init(0),
  135. uuid: [16]u8,
  136. generation: u64,
  137. active_superblock: u1,
  138. created_ns: i64,
  139. pub fn open(allocator: std.mem.Allocator, path: []const u8) !Engine {
  140. const started = std.time.nanoTimestamp();
  141. const file = std.fs.cwd().openFile(path, .{ .mode = .read_write }) catch |err| switch (err) {
  142. error.FileNotFound => try std.fs.cwd().createFile(path, .{ .read = true, .truncate = false }),
  143. else => return err,
  144. };
  145. errdefer file.close();
  146. var directory = try keydir.KeyDir.init(allocator);
  147. errdefer directory.deinit();
  148. var engine = Engine{
  149. .allocator = allocator,
  150. .file = file,
  151. .directory = directory,
  152. .ordered = ordered_index.OrderedIndex.init(allocator),
  153. .file_length = try file.getEndPos(),
  154. .uuid = undefined,
  155. .generation = 0,
  156. .active_superblock = 0,
  157. .created_ns = now(),
  158. };
  159. errdefer engine.ordered.deinit();
  160. if (engine.file_length == 0) {
  161. try engine.initialize();
  162. } else {
  163. try engine.recover();
  164. }
  165. const elapsed = std.time.nanoTimestamp() - started;
  166. engine.recovery_ns = if (elapsed > 0) @intCast(elapsed) else 0;
  167. engine.mapTail(true);
  168. return engine;
  169. }
  170. pub fn close(self: *Engine) void {
  171. self.queue_mutex.lock();
  172. self.writer_stopping = true;
  173. self.queue_condition.broadcast();
  174. self.queue_mutex.unlock();
  175. if (self.writer_thread) |thread| thread.join();
  176. for (self.mappings.items) |mapping| std.posix.munmap(mapping.bytes);
  177. self.mappings.deinit(self.allocator);
  178. self.ordered.deinit();
  179. self.directory.deinit();
  180. self.file.close();
  181. self.* = undefined;
  182. }
  183. fn now() i64 {
  184. const value = std.time.nanoTimestamp();
  185. return std.math.cast(i64, value) orelse if (value < 0) std.math.minInt(i64) else std.math.maxInt(i64);
  186. }
  187. fn mapTail(self: *Engine, force: bool) void {
  188. if (self.file_length <= self.mapped_length or !force and self.file_length - self.mapped_length < map_interval) return;
  189. const page_size = std.heap.pageSize();
  190. const file_start = self.mapped_length - self.mapped_length % page_size;
  191. const length = std.math.cast(usize, self.file_length - file_start) orelse return;
  192. const bytes = std.posix.mmap(null, length, std.posix.PROT.READ, .{ .TYPE = .SHARED }, self.file.handle, file_start) catch return;
  193. if (!force) self.mapping_lock.lock();
  194. defer if (!force) self.mapping_lock.unlock();
  195. self.mappings.append(self.allocator, .{ .bytes = bytes, .file_start = file_start, .logical_start = self.mapped_length, .logical_end = self.file_length }) catch {
  196. std.posix.munmap(bytes);
  197. return;
  198. };
  199. self.mapped_length = self.file_length;
  200. }
  201. fn readBytes(self: *Engine, destination: []u8, file_offset: u64) !usize {
  202. const read_end = try std.math.add(u64, file_offset, destination.len);
  203. self.mapping_lock.lockShared();
  204. var index = self.mappings.items.len;
  205. while (index != 0) {
  206. index -= 1;
  207. const mapping = self.mappings.items[index];
  208. if (file_offset >= mapping.logical_start and read_end <= mapping.logical_end) {
  209. const start: usize = @intCast(file_offset - mapping.file_start);
  210. @memcpy(destination, mapping.bytes[start .. start + destination.len]);
  211. self.mapping_lock.unlockShared();
  212. return destination.len;
  213. }
  214. }
  215. self.mapping_lock.unlockShared();
  216. return self.file.preadAll(destination, file_offset);
  217. }
  218. fn readKeyBytes(context: *const anyopaque, destination: []u8, file_offset: u64) anyerror!usize {
  219. const self: *Engine = @ptrCast(@alignCast(@constCast(context)));
  220. return self.readBytes(destination, file_offset);
  221. }
  222. fn keyReader(self: *Engine) KeyReader {
  223. return .{ .context = self, .readFn = readKeyBytes };
  224. }
  225. fn initialize(self: *Engine) !void {
  226. std.crypto.random.bytes(&self.uuid);
  227. self.generation = 1;
  228. self.created_ns = now();
  229. var first: [4096]u8 = undefined;
  230. var second: [4096]u8 = undefined;
  231. pkvdb.encodeSuperblock(.{
  232. .generation = 1,
  233. .uuid = self.uuid,
  234. .manifest_offset = 0,
  235. .checkpoint_lsn = 0,
  236. .known_lsn = 0,
  237. .known_file_length = pkvdb.data_offset,
  238. .created_ns = self.created_ns,
  239. .updated_ns = self.created_ns,
  240. }, &first);
  241. pkvdb.encodeSuperblock(.{
  242. .generation = 0,
  243. .uuid = self.uuid,
  244. .manifest_offset = 0,
  245. .checkpoint_lsn = 0,
  246. .known_lsn = 0,
  247. .known_file_length = pkvdb.data_offset,
  248. .created_ns = self.created_ns,
  249. .updated_ns = self.created_ns,
  250. }, &second);
  251. try self.file.pwriteAll(&first, 0);
  252. try self.file.pwriteAll(&second, pkvdb.superblock_size);
  253. try self.file.sync();
  254. self.file_length = pkvdb.data_offset;
  255. try self.file.seekTo(self.file_length);
  256. self.bytes_written = pkvdb.data_offset;
  257. }
  258. fn readExtentHeader(self: *Engine, offset: u64) !pkvdb.ExtentHeader {
  259. var bytes: [64]u8 = undefined;
  260. if (try self.file.preadAll(&bytes, offset) != bytes.len) return error.Truncated;
  261. return pkvdb.decodeExtentHeader(&bytes) catch |err| {
  262. if (err == error.ChecksumMismatch) self.checksum_failures += 1;
  263. return err;
  264. };
  265. }
  266. fn validatePayload(self: *Engine, offset: u64, length: u64, expected: u32) !void {
  267. var crc: u32 = 0xffffffff;
  268. var buffer: [64 * 1024]u8 = undefined;
  269. var done: u64 = 0;
  270. while (done < length) {
  271. const amount: usize = @intCast(@min(buffer.len, length - done));
  272. if (try self.file.preadAll(buffer[0..amount], try std.math.add(u64, offset, done)) != amount) return error.Truncated;
  273. crc = pkvdb.crc32cUpdate(crc, buffer[0..amount]);
  274. done += amount;
  275. }
  276. if (~crc != expected) {
  277. self.checksum_failures += 1;
  278. return error.ChecksumMismatch;
  279. }
  280. }
  281. fn readExtentPayload(self: *Engine, offset: u64, header: pkvdb.ExtentHeader, max: u64) ![]u8 {
  282. if (header.payload_length > max or header.payload_length > std.math.maxInt(usize)) return error.InvalidLength;
  283. const payload_offset = try std.math.add(u64, offset, pkvdb.extent_header_size);
  284. const payload = try self.allocator.alloc(u8, @intCast(header.payload_length));
  285. errdefer self.allocator.free(payload);
  286. if (try self.file.preadAll(payload, payload_offset) != payload.len) return error.Truncated;
  287. if (pkvdb.crc32c(payload) != header.payload_crc) {
  288. self.checksum_failures += 1;
  289. return error.ChecksumMismatch;
  290. }
  291. return payload;
  292. }
  293. fn extentEnd(offset: u64, payload_length: u64) !u64 {
  294. return pkvdb.align8(try std.math.add(u64, try std.math.add(u64, offset, pkvdb.extent_header_size), payload_length));
  295. }
  296. fn loadManifest(self: *Engine, sb: pkvdb.Superblock) !?pkvdb.Manifest {
  297. if (sb.manifest_offset == 0) return null;
  298. const header = try self.readExtentHeader(sb.manifest_offset);
  299. if (header.extent_type != .manifest or header.version != 1 or header.payload_length != pkvdb.manifest_size) return error.InvalidManifest;
  300. if (try extentEnd(sb.manifest_offset, header.payload_length) > sb.known_file_length) return error.InvalidManifest;
  301. const payload = try self.readExtentPayload(sb.manifest_offset, header, pkvdb.manifest_size);
  302. defer self.allocator.free(payload);
  303. const manifest = try pkvdb.decodeManifest(payload);
  304. if (!std.mem.eql(u8, &manifest.uuid, &sb.uuid) or manifest.generation != sb.generation or manifest.checkpoint_lsn != sb.checkpoint_lsn) return error.InvalidManifest;
  305. if (manifest.known_tail != sb.known_file_length or manifest.known_lsn != sb.known_lsn) return error.InvalidManifest;
  306. if (manifest.entries_offset == 0 or manifest.entries_offset >= sb.manifest_offset or manifest.replay_offset < pkvdb.data_offset or manifest.replay_offset > manifest.entries_offset) return error.InvalidManifest;
  307. try self.validateCheckpointExtent(manifest.entries_offset, manifest.checkpoint_lsn, sb.known_file_length);
  308. return manifest;
  309. }
  310. fn validateCheckpointExtent(self: *Engine, offset: u64, lsn: u64, file_length: u64) !void {
  311. const header = try self.readExtentHeader(offset);
  312. if (header.extent_type != .checkpoint_entries or header.version != 1 or header.first_lsn != lsn or header.last_lsn != lsn) return error.InvalidCheckpoint;
  313. const end = try extentEnd(offset, header.payload_length);
  314. if (end > file_length) return error.InvalidCheckpoint;
  315. try self.validatePayload(offset + pkvdb.extent_header_size, header.payload_length, header.payload_crc);
  316. var bytes: [56]u8 = undefined;
  317. if (header.payload_length < bytes.len or try self.file.preadAll(&bytes, offset + pkvdb.extent_header_size) != bytes.len) return error.InvalidCheckpoint;
  318. const checkpoint_header = try pkvdb.decodeCheckpointHeaderOnly(&bytes, header.payload_length);
  319. if (checkpoint_header.lsn != lsn) return error.InvalidCheckpoint;
  320. }
  321. fn recover(self: *Engine) !void {
  322. if (self.file_length < pkvdb.data_offset) return error.Truncated;
  323. var blocks: [2][4096]u8 = undefined;
  324. _ = try self.file.preadAll(&blocks[0], 0);
  325. _ = try self.file.preadAll(&blocks[1], pkvdb.superblock_size);
  326. var roots: [2]?Root = .{ null, null };
  327. for (0..2) |index| {
  328. const sb = pkvdb.decodeSuperblock(&blocks[index], self.file_length) catch continue;
  329. const manifest = self.loadManifest(sb) catch continue;
  330. roots[index] = .{ .block = sb, .manifest = manifest };
  331. }
  332. var selected_index: usize = 0;
  333. const selected = if (roots[0] != null and roots[1] != null) blk: {
  334. selected_index = if (roots[1].?.block.generation > roots[0].?.block.generation) 1 else 0;
  335. break :blk roots[selected_index].?;
  336. } else if (roots[0]) |root| root else if (roots[1]) |root| blk: {
  337. selected_index = 1;
  338. break :blk root;
  339. } else return error.NoUsableSuperblock;
  340. self.uuid = selected.block.uuid;
  341. self.generation = selected.block.generation;
  342. self.active_superblock = @intCast(selected_index);
  343. self.created_ns = selected.block.created_ns;
  344. self.latest_lsn = selected.block.checkpoint_lsn;
  345. self.checkpoint_lsn = selected.block.checkpoint_lsn;
  346. var replay_offset = pkvdb.data_offset;
  347. if (selected.manifest) |manifest| {
  348. try self.loadCheckpoint(manifest.entries_offset, selected.block.known_file_length);
  349. replay_offset = manifest.replay_offset;
  350. self.oldest_lsn = manifest.history_start_lsn;
  351. }
  352. try self.scanExtents(replay_offset);
  353. try self.file.setEndPos(self.file_length);
  354. try self.file.seekTo(self.file_length);
  355. self.bytes_written = self.file_length;
  356. }
  357. fn loadCheckpoint(self: *Engine, offset: u64, known_length: u64) !void {
  358. const extent = try self.readExtentHeader(offset);
  359. var header_bytes: [56]u8 = undefined;
  360. const payload_offset = offset + pkvdb.extent_header_size;
  361. if (try self.file.preadAll(&header_bytes, payload_offset) != header_bytes.len) return error.Truncated;
  362. const header = try pkvdb.decodeCheckpointHeaderOnly(&header_bytes, extent.payload_length);
  363. if (header.entry_count > std.math.maxInt(usize)) return error.InvalidLength;
  364. try self.directory.ensureAdditional(self.file, @intCast(header.entry_count));
  365. var index: u64 = 0;
  366. while (index < header.entry_count) : (index += 1) {
  367. var bytes: [48]u8 = undefined;
  368. const entry_offset = try std.math.add(u64, payload_offset + pkvdb.checkpoint_header_size, try std.math.mul(u64, index, pkvdb.checkpoint_entry_size));
  369. if (try self.file.preadAll(&bytes, entry_offset) != bytes.len) return error.Truncated;
  370. const entry = try pkvdb.decodeCheckpointEntry(&bytes, known_length);
  371. const record = RecordRef{ .hash = entry.hash, .lsn = entry.lsn, .key_offset = entry.key_offset, .value_offset = entry.value_offset, .key_len = entry.key_len, .value_len = entry.value_len, .flags = entry.flags };
  372. try self.directory.put(self.file, record);
  373. }
  374. }
  375. fn scanExtents(self: *Engine, start: u64) !void {
  376. var offset = start;
  377. var last_journal_lsn: u64 = 0;
  378. while (offset < self.file_length) {
  379. if (self.file_length - offset < pkvdb.extent_header_size) {
  380. self.partial_tails += 1;
  381. self.file_length = offset;
  382. break;
  383. }
  384. const header = self.readExtentHeader(offset) catch |failure| {
  385. if (self.file_length - offset == pkvdb.extent_header_size) {
  386. self.partial_tails += 1;
  387. self.file_length = offset;
  388. break;
  389. }
  390. return failure;
  391. };
  392. const end = extentEnd(offset, header.payload_length) catch return error.InvalidLength;
  393. if (end > self.file_length) {
  394. self.partial_tails += 1;
  395. self.file_length = offset;
  396. break;
  397. }
  398. try self.validatePayload(offset + pkvdb.extent_header_size, header.payload_length, header.payload_crc);
  399. if (header.extent_type == .journal and header.version == 1) {
  400. if (header.payload_length > pkvdb.max_transaction_size + pkvdb.group_header_size) return error.InvalidLength;
  401. const payload = try self.readExtentPayload(offset, header, pkvdb.max_transaction_size + pkvdb.group_header_size);
  402. defer self.allocator.free(payload);
  403. last_journal_lsn = try self.replayJournal(offset, payload, last_journal_lsn);
  404. self.journal_bytes_since_checkpoint += end - offset;
  405. } else if (header.extent_type == .store_metadata and header.version == 1) {
  406. if (header.payload_length > pkvdb.max_transaction_size) return error.InvalidLength;
  407. const payload = try self.readExtentPayload(offset, header, pkvdb.max_transaction_size);
  408. defer self.allocator.free(payload);
  409. try self.replayBaseline(offset, payload);
  410. }
  411. offset = end;
  412. }
  413. }
  414. fn replayBaseline(self: *Engine, extent_offset: u64, payload: []const u8) !void {
  415. if (payload.len < 24 or readInt(u16, payload, 0) != 1 or readInt(u64, payload, 8) != 1) return error.InvalidBaseline;
  416. const count = readInt(u32, payload, 4);
  417. if (count > pkvdb.max_operations) return error.InvalidBaseline;
  418. try self.directory.ensureAdditional(self.file, count);
  419. var position: usize = 24;
  420. for (0..count) |_| {
  421. if (position > payload.len or payload.len - position < 8) return error.InvalidBaseline;
  422. const key_length = readInt(u32, payload, position);
  423. const value_length = readInt(u32, payload, position + 4);
  424. if (key_length > pkvdb.max_key_size or value_length > pkvdb.max_value_size) return error.InvalidBaseline;
  425. const key_start = position + 8;
  426. const key_end = try std.math.add(usize, key_start, key_length);
  427. const value_end = try std.math.add(usize, key_end, value_length);
  428. if (value_end > payload.len) return error.InvalidBaseline;
  429. const key = payload[key_start..key_end];
  430. const key_offset = extent_offset + pkvdb.extent_header_size + key_start;
  431. try self.replaceRecord(key, .{ .hash = keydir.KeyDir.hash(key), .lsn = 1, .key_offset = key_offset, .value_offset = key_offset + key_length, .key_len = key_length, .value_len = value_length });
  432. position = @intCast(try pkvdb.align8(value_end));
  433. }
  434. if (position != payload.len) return error.InvalidBaseline;
  435. self.latest_lsn = 1;
  436. self.oldest_lsn = 1;
  437. }
  438. fn replayJournal(self: *Engine, extent_offset: u64, payload: []const u8, previous_lsn: u64) !u64 {
  439. if (payload.len < pkvdb.group_header_size or readInt(u16, payload, 0) != 1) return error.InvalidJournal;
  440. const count = readInt(u32, payload, 4);
  441. if (count == 0 or count > pkvdb.max_operations) return error.InvalidJournal;
  442. var position: usize = pkvdb.group_header_size;
  443. var last = previous_lsn;
  444. for (0..count) |_| {
  445. if (position > payload.len or payload.len - position < pkvdb.transaction_header_size) return error.InvalidJournal;
  446. const tx = try pkvdb.decodeTransactionHeader(payload[position..]);
  447. if (tx.total_length > payload.len - position) return error.InvalidJournal;
  448. const tx_end = position + tx.total_length;
  449. const body = payload[position + pkvdb.transaction_header_size .. tx_end];
  450. if (pkvdb.crc32c(body) != tx.payload_crc or tx.metadata_length > body.len) return error.ChecksumMismatch;
  451. if (last != 0 and tx.lsn <= last) return error.InvalidLsn;
  452. last = tx.lsn;
  453. if (tx.lsn > self.latest_lsn) try self.applyTransaction(extent_offset + pkvdb.extent_header_size, position, tx, payload[position..tx_end]);
  454. position = tx_end;
  455. }
  456. if (position != payload.len) return error.InvalidJournal;
  457. return last;
  458. }
  459. fn applyTransaction(self: *Engine, extent_payload_offset: u64, tx_position: usize, tx: pkvdb.TransactionHeader, frame: []const u8) !void {
  460. var position: usize = pkvdb.transaction_header_size + tx.metadata_length;
  461. try self.directory.ensureAdditional(self.file, tx.operation_count);
  462. for (0..tx.operation_count) |_| {
  463. if (position > frame.len or frame.len - position < pkvdb.operation_header_size) return error.InvalidJournal;
  464. const operation = try pkvdb.decodeOperationHeader(frame[position..]);
  465. const data_start = try std.math.add(usize, position, pkvdb.operation_header_size);
  466. const key_end = try std.math.add(usize, data_start, operation.key_length);
  467. const value_end = try std.math.add(usize, key_end, operation.value_length);
  468. const extension_end = try std.math.add(usize, value_end, operation.extension_length);
  469. if (extension_end > frame.len) return error.InvalidJournal;
  470. const key = frame[data_start..key_end];
  471. switch (operation.opcode) {
  472. .put => {
  473. const key_offset = try std.math.add(u64, extent_payload_offset, tx_position + data_start);
  474. const record = RecordRef{ .hash = keydir.KeyDir.hash(key), .lsn = tx.lsn, .key_offset = key_offset, .value_offset = key_offset + operation.key_length, .key_len = operation.key_length, .value_len = operation.value_length };
  475. try self.replaceRecord(key, record);
  476. },
  477. .delete => _ = try self.removeRecord(key),
  478. }
  479. position = @intCast(try pkvdb.align8(extension_end));
  480. }
  481. if (position != frame.len) return error.InvalidJournal;
  482. self.latest_lsn = tx.lsn;
  483. if (self.oldest_lsn == 0) self.oldest_lsn = tx.lsn;
  484. }
  485. fn replaceRecord(self: *Engine, key: []const u8, record: RecordRef) !void {
  486. if (self.ordered_ready) try self.ordered.put(key, record);
  487. try self.directory.putWithKey(self.file, key, record);
  488. }
  489. fn removeRecord(self: *Engine, key: []const u8) !bool {
  490. const removed = try self.directory.remove(self.file, key);
  491. if (removed and self.ordered_ready and !self.ordered.remove(key)) return error.IndexInconsistent;
  492. return removed;
  493. }
  494. fn appendExtent(self: *Engine, extent_type: pkvdb.ExtentType, first_lsn: u64, last_lsn: u64, payload: []const u8) !u64 {
  495. const offset = try pkvdb.align8(self.file_length);
  496. if (offset != self.file_length) {
  497. const padding = [_]u8{0} ** 8;
  498. try self.file.pwriteAll(padding[0 .. offset - self.file_length], self.file_length);
  499. }
  500. var header: [64]u8 = undefined;
  501. pkvdb.encodeExtentHeader(.{ .extent_type = extent_type, .payload_length = payload.len, .first_lsn = first_lsn, .last_lsn = last_lsn, .payload_crc = pkvdb.crc32c(payload) }, &header);
  502. const old_length = self.file_length;
  503. errdefer self.file.setEndPos(old_length) catch {};
  504. try self.file.pwriteAll(&header, offset);
  505. try self.file.pwriteAll(payload, offset + header.len);
  506. const end = try extentEnd(offset, payload.len);
  507. if (end > offset + header.len + payload.len) {
  508. const padding = [_]u8{0} ** 8;
  509. try self.file.pwriteAll(padding[0 .. end - (offset + header.len + payload.len)], offset + header.len + payload.len);
  510. }
  511. self.file_length = end;
  512. self.bytes_written += end - offset;
  513. return offset;
  514. }
  515. fn transactionLength(operations: []const Operation, metadata: []const u8) !usize {
  516. var frame_length: u64 = pkvdb.transaction_header_size + metadata.len;
  517. for (operations) |operation| {
  518. const raw = try std.math.add(u64, pkvdb.operation_header_size + operation.key.len + operation.value.len, 0);
  519. frame_length = try pkvdb.align8(try std.math.add(u64, frame_length, raw));
  520. }
  521. if (frame_length > pkvdb.max_transaction_size) return error.TransactionTooLarge;
  522. return @intCast(frame_length);
  523. }
  524. fn encodeTransaction(frame: []u8, operations: []const Operation, metadata: []const u8, lsn: u64, transaction_id: u64, timestamp_ns: i64) !void {
  525. @memset(frame, 0);
  526. var position: usize = pkvdb.transaction_header_size;
  527. @memcpy(frame[position .. position + metadata.len], metadata);
  528. position += metadata.len;
  529. for (operations) |operation| {
  530. var header: [16]u8 = undefined;
  531. pkvdb.encodeOperationHeader(.{ .opcode = operation.opcode, .key_length = @intCast(operation.key.len), .value_length = @intCast(operation.value.len) }, &header);
  532. @memcpy(frame[position .. position + header.len], &header);
  533. position += header.len;
  534. @memcpy(frame[position .. position + operation.key.len], operation.key);
  535. position += operation.key.len;
  536. @memcpy(frame[position .. position + operation.value.len], operation.value);
  537. position += operation.value.len;
  538. position = @intCast(try pkvdb.align8(position));
  539. }
  540. if (position != frame.len) return error.InvalidLength;
  541. const body = frame[pkvdb.transaction_header_size..];
  542. var tx_header: [56]u8 = undefined;
  543. pkvdb.encodeTransactionHeader(.{ .total_length = @intCast(frame.len), .lsn = lsn, .transaction_id = transaction_id, .timestamp_ns = timestamp_ns, .operation_count = @intCast(operations.len), .metadata_length = @intCast(metadata.len), .payload_crc = pkvdb.crc32c(body) }, &tx_header);
  544. @memcpy(frame[0..tx_header.len], &tx_header);
  545. }
  546. pub fn batchWrite(self: *Engine, operations: []const Operation, metadata: []const u8) !u64 {
  547. if (operations.len == 0 or operations.len > pkvdb.max_operations or metadata.len > pkvdb.max_transaction_size) return error.InvalidLength;
  548. for (operations) |operation| {
  549. if (operation.key.len > pkvdb.max_key_size or operation.value.len > pkvdb.max_value_size) return error.InvalidLength;
  550. if (operation.opcode == .delete and operation.value.len != 0) return error.InvalidLength;
  551. }
  552. const frame_length = try transactionLength(operations, metadata);
  553. var completion = WriteCompletion{ .remaining = 1 };
  554. var pending = PendingWrite{ .operations = operations, .metadata = metadata, .bytes = frame_length, .completion = &completion };
  555. try self.enqueueAndWait(&.{&pending}, &completion);
  556. if (completion.failure) |failure| return failure;
  557. return pending.lsn;
  558. }
  559. pub fn compareBatchWrite(self: *Engine, checks: []const CompareCheck, operations: []const Operation, metadata: []const u8) !CompareBatchResult {
  560. if (operations.len == 0 or operations.len > pkvdb.max_operations or metadata.len > pkvdb.max_transaction_size) return error.InvalidLength;
  561. if (checks.len > pkvdb.max_operations) return error.InvalidLength;
  562. for (operations) |operation| {
  563. if (operation.key.len > pkvdb.max_key_size or operation.value.len > pkvdb.max_value_size) return error.InvalidLength;
  564. if (operation.opcode == .delete and operation.value.len != 0) return error.InvalidLength;
  565. }
  566. for (checks) |check| {
  567. if (check.key.len > pkvdb.max_key_size) return error.InvalidLength;
  568. }
  569. const frame_length = try transactionLength(operations, metadata);
  570. var completion = WriteCompletion{ .remaining = 1 };
  571. var pending = PendingWrite{ .operations = operations, .metadata = metadata, .checks = checks, .bytes = frame_length, .completion = &completion };
  572. try self.enqueueAndWait(&.{&pending}, &completion);
  573. if (completion.failure) |failure| return failure;
  574. return .{ .lsn = pending.lsn, .committed = pending.committed };
  575. }
  576. pub fn putMany(self: *Engine, operations: []const Operation, lsns: []u64) !void {
  577. if (operations.len == 0 or operations.len != lsns.len or operations.len > max_group_transactions) return error.InvalidLength;
  578. const pending = try self.allocator.alloc(PendingWrite, operations.len);
  579. defer self.allocator.free(pending);
  580. const pointers = try self.allocator.alloc(*PendingWrite, operations.len);
  581. defer self.allocator.free(pointers);
  582. var completion = WriteCompletion{ .remaining = operations.len };
  583. for (operations, 0..) |operation, index| {
  584. if (operation.opcode != .put or operation.key.len > pkvdb.max_key_size or operation.value.len > pkvdb.max_value_size) return error.InvalidLength;
  585. const operation_slice = operations[index .. index + 1];
  586. pending[index] = .{ .operations = operation_slice, .metadata = "", .bytes = try transactionLength(operation_slice, ""), .completion = &completion };
  587. pointers[index] = &pending[index];
  588. }
  589. try self.enqueueAndWait(pointers, &completion);
  590. for (pending, 0..) |result, index| {
  591. if (completion.failure) |failure| return failure;
  592. lsns[index] = result.lsn;
  593. }
  594. }
  595. fn enqueueAndWait(self: *Engine, pending: []const *PendingWrite, completion: *WriteCompletion) !void {
  596. self.queue_mutex.lock();
  597. defer self.queue_mutex.unlock();
  598. if (self.writer_failed or self.writer_stopping) return error.StorageUnavailable;
  599. if (self.writer_thread == null) self.writer_thread = try std.Thread.spawn(.{}, writerMain, .{self});
  600. for (pending) |write| {
  601. while (self.queued_bytes > max_queued_bytes - write.bytes and !self.writer_failed and !self.writer_stopping) self.queue_condition.wait(&self.queue_mutex);
  602. if (self.writer_failed or self.writer_stopping) return error.StorageUnavailable;
  603. if (self.queue_tail) |tail| tail.next = write else self.queue_head = write;
  604. self.queue_tail = write;
  605. self.queued_bytes += write.bytes;
  606. }
  607. self.queue_condition.signal();
  608. while (completion.remaining != 0) completion.condition.wait(&self.queue_mutex);
  609. }
  610. fn writerMain(self: *Engine) void {
  611. var group: [max_group_transactions]*PendingWrite = undefined;
  612. while (true) {
  613. self.queue_mutex.lock();
  614. while (self.queue_head == null and !self.writer_stopping) self.queue_condition.wait(&self.queue_mutex);
  615. if (self.queue_head == null and self.writer_stopping) {
  616. self.queue_mutex.unlock();
  617. return;
  618. }
  619. // Conditional transactions cannot share this commit group, so
  620. // waiting for more queue entries cannot amortize their fsync.
  621. if (canCoalesce(self.queue_head.?)) {
  622. _ = self.queue_condition.timedWait(&self.queue_mutex, group_wait_ns) catch {};
  623. }
  624. var count: usize = 0;
  625. var bytes: usize = pkvdb.group_header_size;
  626. var conditional: bool = false;
  627. while (self.queue_head) |pending| {
  628. if (count != 0 and (count == group.len or bytes + pending.bytes > max_group_bytes or conditional or pending.checks.len != 0)) break;
  629. self.queue_head = pending.next;
  630. if (self.queue_head == null) self.queue_tail = null;
  631. pending.next = null;
  632. group[count] = pending;
  633. count += 1;
  634. bytes += pending.bytes;
  635. self.queued_bytes -= pending.bytes;
  636. conditional = pending.checks.len != 0;
  637. }
  638. self.queue_condition.broadcast();
  639. self.queue_mutex.unlock();
  640. self.processGroup(group[0..count], bytes) catch |failure| {
  641. self.queue_mutex.lock();
  642. self.writer_failed = true;
  643. for (group[0..count]) |pending| {
  644. pending.completion.failure = failure;
  645. pending.completion.remaining -= 1;
  646. if (pending.completion.remaining == 0) pending.completion.condition.signal();
  647. }
  648. while (self.queue_head) |pending| {
  649. self.queue_head = pending.next;
  650. pending.completion.failure = error.StorageUnavailable;
  651. pending.completion.remaining -= 1;
  652. if (pending.completion.remaining == 0) pending.completion.condition.signal();
  653. }
  654. self.queue_tail = null;
  655. self.queued_bytes = 0;
  656. self.queue_condition.broadcast();
  657. self.queue_mutex.unlock();
  658. continue;
  659. };
  660. self.queue_mutex.lock();
  661. for (group[0..count]) |pending| {
  662. pending.completion.remaining -= 1;
  663. if (pending.completion.remaining == 0) pending.completion.condition.signal();
  664. }
  665. self.queue_mutex.unlock();
  666. }
  667. }
  668. fn canCoalesce(pending: *const PendingWrite) bool {
  669. return pending.checks.len == 0;
  670. }
  671. fn checkConditions(self: *Engine, checks: []const CompareCheck) !bool {
  672. for (checks) |check| {
  673. const record = try self.directory.getWithReader(self.keyReader(), check.key);
  674. if (check.expected_lsn == 0) {
  675. if (record != null) return false;
  676. } else if (record == null or record.?.lsn != check.expected_lsn) {
  677. return false;
  678. }
  679. }
  680. return true;
  681. }
  682. fn processGroup(self: *Engine, group: []*PendingWrite, payload_length: usize) !void {
  683. var operation_count: usize = 0;
  684. for (group) |pending| operation_count = try std.math.add(usize, operation_count, pending.operations.len);
  685. const payload = try self.allocator.alloc(u8, payload_length);
  686. defer self.allocator.free(payload);
  687. self.ordered_gate.lock();
  688. defer self.ordered_gate.unlock();
  689. self.lock.lock();
  690. if (group[0].checks.len != 0) {
  691. if (group.len != 1) {
  692. self.lock.unlock();
  693. return error.InvalidConditionalGroup;
  694. }
  695. const conditions_match = self.checkConditions(group[0].checks) catch |failure| {
  696. self.lock.unlock();
  697. return failure;
  698. };
  699. if (!conditions_match) {
  700. group[0].committed = false;
  701. group[0].lsn = 0;
  702. self.lock.unlock();
  703. return;
  704. }
  705. }
  706. self.directory.ensureAdditional(self.file, operation_count) catch |failure| {
  707. self.lock.unlock();
  708. return failure;
  709. };
  710. const first_lsn = std.math.add(u64, self.latest_lsn, 1) catch |failure| {
  711. self.lock.unlock();
  712. return failure;
  713. };
  714. const prepare_ordered = self.ordered_ready;
  715. self.lock.unlock();
  716. var prepared_storage: []ordered_index.Prepared = &.{};
  717. if (prepare_ordered) prepared_storage = try self.allocator.alloc(ordered_index.Prepared, operation_count);
  718. var prepared_count: usize = 0;
  719. defer {
  720. for (prepared_storage[0..prepared_count]) |*entry| self.ordered.discard(entry);
  721. if (prepared_storage.len != 0) self.allocator.free(prepared_storage);
  722. }
  723. if (prepare_ordered) for (group) |pending| {
  724. pending.prepared_position = prepared_count;
  725. for (pending.operations) |operation| if (operation.opcode == .put) {
  726. prepared_storage[prepared_count] = try self.ordered.prepare(operation.key);
  727. prepared_count += 1;
  728. };
  729. pending.prepared_count = prepared_count - pending.prepared_position;
  730. };
  731. @memset(payload, 0);
  732. const group_timestamp = now();
  733. writeInt(u16, payload, 0, 1);
  734. writeInt(u32, payload, 4, @intCast(group.len));
  735. writeInt(i64, payload, 8, group_timestamp);
  736. var position: usize = pkvdb.group_header_size;
  737. var lsn = first_lsn;
  738. for (group, 0..) |pending, index| {
  739. pending.frame_position = position;
  740. const timestamp = std.math.add(i64, group_timestamp, @intCast(index)) catch std.math.maxInt(i64);
  741. try encodeTransaction(payload[position .. position + pending.bytes], pending.operations, pending.metadata, lsn, lsn, timestamp);
  742. pending.lsn = lsn;
  743. position += pending.bytes;
  744. lsn = try std.math.add(u64, lsn, 1);
  745. }
  746. const last_lsn = lsn - 1;
  747. self.io_mutex.lock();
  748. defer self.io_mutex.unlock();
  749. const offset = try self.appendExtent(.journal, first_lsn, last_lsn, payload);
  750. self.file.sync() catch |failure| {
  751. self.file.setEndPos(offset) catch {};
  752. self.file_length = offset;
  753. return failure;
  754. };
  755. self.mapTail(false);
  756. self.lock.lock();
  757. defer self.lock.unlock();
  758. for (group) |pending| {
  759. try self.publishPending(offset + pkvdb.extent_header_size, pending, prepared_storage[0..prepared_count]);
  760. }
  761. self.journal_bytes_since_checkpoint += self.file_length - offset;
  762. self.commit_groups += 1;
  763. self.committed_transactions += group.len;
  764. self.largest_commit_group = @max(self.largest_commit_group, group.len);
  765. }
  766. fn publishPending(self: *Engine, extent_payload_offset: u64, pending: *PendingWrite, prepared: []ordered_index.Prepared) !void {
  767. var position: usize = pkvdb.transaction_header_size + pending.metadata.len;
  768. var prepared_position = pending.prepared_position;
  769. for (pending.operations) |operation| {
  770. const key_offset = try std.math.add(u64, extent_payload_offset, pending.frame_position + position + pkvdb.operation_header_size);
  771. switch (operation.opcode) {
  772. .put => {
  773. const record = RecordRef{ .hash = keydir.KeyDir.hash(operation.key), .lsn = pending.lsn, .key_offset = key_offset, .value_offset = key_offset + operation.key.len, .key_len = @intCast(operation.key.len), .value_len = @intCast(operation.value.len) };
  774. if (self.ordered_ready) {
  775. self.ordered.putPrepared(&prepared[prepared_position], record);
  776. prepared_position += 1;
  777. }
  778. try self.directory.putWithKey(self.file, operation.key, record);
  779. },
  780. .delete => pending.changed = (try self.removeRecord(operation.key)) or pending.changed,
  781. }
  782. position = @intCast(try pkvdb.align8(position + pkvdb.operation_header_size + operation.key.len + operation.value.len));
  783. }
  784. if (prepared_position != pending.prepared_position + pending.prepared_count) return error.IndexInconsistent;
  785. if (position != pending.bytes) return error.InvalidLength;
  786. self.latest_lsn = pending.lsn;
  787. if (self.oldest_lsn == 0) self.oldest_lsn = pending.lsn;
  788. }
  789. pub fn importBaseline(self: *Engine, operations: []const Operation) !void {
  790. if (operations.len > pkvdb.max_operations) return error.InvalidLength;
  791. self.io_mutex.lock();
  792. defer self.io_mutex.unlock();
  793. self.lock.lock();
  794. defer self.lock.unlock();
  795. if (self.checkpoint_lsn != 0 or self.latest_lsn > 1 or self.directory.count != 0 and self.latest_lsn == 0) return error.InvalidState;
  796. var length: u64 = 24;
  797. for (operations) |operation| {
  798. if (operation.opcode != .put or operation.key.len > pkvdb.max_key_size or operation.value.len > pkvdb.max_value_size) return error.InvalidLength;
  799. length = try pkvdb.align8(try std.math.add(u64, length, 8 + operation.key.len + operation.value.len));
  800. }
  801. if (length > pkvdb.max_transaction_size) return error.TransactionTooLarge;
  802. const payload = try self.allocator.alloc(u8, @intCast(length));
  803. defer self.allocator.free(payload);
  804. @memset(payload, 0);
  805. writeInt(u16, payload, 0, 1);
  806. writeInt(u32, payload, 4, @intCast(operations.len));
  807. writeInt(u64, payload, 8, 1);
  808. writeInt(i64, payload, 16, now());
  809. var position: usize = 24;
  810. for (operations) |operation| {
  811. writeInt(u32, payload, position, @intCast(operation.key.len));
  812. writeInt(u32, payload, position + 4, @intCast(operation.value.len));
  813. position += 8;
  814. @memcpy(payload[position .. position + operation.key.len], operation.key);
  815. position += operation.key.len;
  816. @memcpy(payload[position .. position + operation.value.len], operation.value);
  817. position += operation.value.len;
  818. const aligned: usize = @intCast(try pkvdb.align8(position));
  819. @memset(payload[position..aligned], 0);
  820. position = aligned;
  821. }
  822. try self.directory.ensureAdditional(self.file, operations.len);
  823. const offset = try self.appendExtent(.store_metadata, 1, 1, payload);
  824. try self.file.sync();
  825. try self.replayBaseline(offset, payload);
  826. }
  827. pub fn put(self: *Engine, key: []const u8, value: []const u8) !u64 {
  828. return self.batchWrite(&.{.{ .opcode = .put, .key = key, .value = value }}, "");
  829. }
  830. pub fn delete(self: *Engine, key: []const u8) !bool {
  831. if (key.len > pkvdb.max_key_size) return error.InvalidLength;
  832. const operations = [_]Operation{.{ .opcode = .delete, .key = key }};
  833. var completion = WriteCompletion{ .remaining = 1 };
  834. var pending = PendingWrite{ .operations = &operations, .metadata = "", .bytes = try transactionLength(&operations, ""), .completion = &completion };
  835. try self.enqueueAndWait(&.{&pending}, &completion);
  836. if (completion.failure) |failure| return failure;
  837. return pending.changed;
  838. }
  839. pub fn get(self: *Engine, allocator: std.mem.Allocator, key: []const u8) !?Value {
  840. const record = try self.getRef(key) orelse return null;
  841. const bytes = try allocator.alloc(u8, record.value_len);
  842. errdefer allocator.free(bytes);
  843. _ = try self.readValue(record, bytes, 0);
  844. return .{ .bytes = bytes, .lsn = record.lsn };
  845. }
  846. pub fn getRef(self: *Engine, key: []const u8) !?RecordRef {
  847. if (key.len > pkvdb.max_key_size) return error.InvalidLength;
  848. self.lock.lockShared();
  849. defer self.lock.unlockShared();
  850. return try self.directory.getWithReader(self.keyReader(), key);
  851. }
  852. pub fn readValue(self: *Engine, record: RecordRef, destination: []u8, value_position: u32) !usize {
  853. if (value_position > record.value_len) return error.InvalidOffset;
  854. const amount = @min(destination.len, record.value_len - value_position);
  855. const file_offset = record.value_offset + value_position;
  856. const got = try self.readBytes(destination[0..amount], file_offset);
  857. if (got != amount) return error.Truncated;
  858. return amount;
  859. }
  860. pub fn exists(self: *Engine, key: []const u8) !bool {
  861. self.lock.lockShared();
  862. defer self.lock.unlockShared();
  863. return (try self.directory.getWithReader(self.keyReader(), key)) != null;
  864. }
  865. pub fn multiGet(self: *Engine, allocator: std.mem.Allocator, keys: []const []const u8) ![]?Value {
  866. if (keys.len > pkvdb.max_operations) return error.InvalidLength;
  867. const refs = try allocator.alloc(?RecordRef, keys.len);
  868. defer allocator.free(refs);
  869. @memset(refs, null);
  870. self.lock.lockShared();
  871. for (keys, 0..) |key, index| {
  872. if (key.len > pkvdb.max_key_size) {
  873. self.lock.unlockShared();
  874. return error.InvalidLength;
  875. }
  876. refs[index] = self.directory.getWithReader(self.keyReader(), key) catch |failure| {
  877. self.lock.unlockShared();
  878. return failure;
  879. };
  880. }
  881. self.lock.unlockShared();
  882. const values = try allocator.alloc(?Value, keys.len);
  883. errdefer allocator.free(values);
  884. @memset(values, null);
  885. errdefer for (values) |value| if (value) |present| allocator.free(present.bytes);
  886. for (refs, 0..) |entry, index| {
  887. const record = entry orelse continue;
  888. const bytes = try allocator.alloc(u8, record.value_len);
  889. errdefer allocator.free(bytes);
  890. _ = try self.readValue(record, bytes, 0);
  891. values[index] = .{ .bytes = bytes, .lsn = record.lsn };
  892. }
  893. return values;
  894. }
  895. pub fn scan(self: *Engine, allocator: std.mem.Allocator, prefix: []const u8, cursor: []const u8, limit: u32, include_values: bool, max_bytes: u32) !ScanBatch {
  896. if (prefix.len > pkvdb.max_key_size or cursor.len > pkvdb.max_key_size or limit == 0 or limit > 4096 or max_bytes == 0 or max_bytes > pkvdb.max_key_size + pkvdb.max_value_size + 1024) return error.InvalidLength;
  897. try self.ensureOrdered();
  898. const ScanRef = struct { record: RecordRef, key_offset: ?u32 = null };
  899. const refs = try allocator.alloc(ScanRef, limit);
  900. defer allocator.free(refs);
  901. // Copy short keys into a bounded arena to avoid adding pread calls for
  902. // small, not-yet-mapped journal tails. Large keys use immutable records.
  903. const key_snapshot = try allocator.alloc(u8, @min(max_bytes, limit * 64, 64 * 1024));
  904. defer allocator.free(key_snapshot);
  905. const snapshot = blk: {
  906. self.lock.lockShared();
  907. defer self.lock.unlockShared();
  908. var node = self.ordered.lowerBound(if (cursor.len == 0) prefix else cursor);
  909. if (cursor.len != 0 and node != null and std.mem.eql(u8, node.?.key, cursor)) node = ordered_index.OrderedIndex.next(node.?);
  910. var count: usize = 0;
  911. var bytes_used: usize = 0;
  912. var key_bytes: usize = 0;
  913. while (node) |current| {
  914. if (count >= limit or !std.mem.startsWith(u8, current.key, prefix)) break;
  915. const record = current.record;
  916. const next_size = 16 + current.key.len + if (include_values) record.value_len else 0;
  917. if (next_size > max_bytes) return error.ScanEntryTooLarge;
  918. if (bytes_used + next_size > max_bytes) break;
  919. refs[count] = .{ .record = record };
  920. if (current.key.len <= key_snapshot.len - key_bytes) {
  921. @memcpy(key_snapshot[key_bytes .. key_bytes + current.key.len], current.key);
  922. refs[count].key_offset = @intCast(key_bytes);
  923. key_bytes += current.key.len;
  924. }
  925. count += 1;
  926. bytes_used += next_size;
  927. node = ordered_index.OrderedIndex.next(current);
  928. }
  929. break :blk .{ .count = count, .done = node == null or !std.mem.startsWith(u8, node.?.key, prefix) };
  930. };
  931. // Journal records remain immutable for the engine's lifetime, just as
  932. // for getRef/multiGet. Never retain ordered-tree nodes past the gate:
  933. // deletes may free them while these bounded record copies are read.
  934. const entries = try allocator.alloc(ScanEntry, snapshot.count);
  935. var initialized: usize = 0;
  936. errdefer {
  937. for (entries[0..initialized]) |entry| {
  938. allocator.free(entry.key);
  939. if (entry.value) |value| allocator.free(value);
  940. }
  941. allocator.free(entries);
  942. }
  943. for (refs[0..snapshot.count], entries) |saved, *entry| {
  944. const record = saved.record;
  945. const key = try allocator.alloc(u8, record.key_len);
  946. errdefer allocator.free(key);
  947. if (saved.key_offset) |offset| {
  948. @memcpy(key, key_snapshot[offset .. offset + key.len]);
  949. } else if (try self.readBytes(key, record.key_offset) != key.len) return error.Truncated;
  950. var value: ?[]u8 = null;
  951. if (include_values) {
  952. value = try allocator.alloc(u8, record.value_len);
  953. errdefer allocator.free(value.?);
  954. _ = try self.readValue(record, value.?, 0);
  955. }
  956. entry.* = .{ .key = key, .value = value, .lsn = record.lsn };
  957. initialized += 1;
  958. }
  959. const next_cursor = if (entries.len == 0) try allocator.alloc(u8, 0) else try allocator.dupe(u8, entries[entries.len - 1].key);
  960. return .{ .entries = entries, .next_cursor = next_cursor, .done = snapshot.done };
  961. }
  962. fn ensureOrdered(self: *Engine) !void {
  963. self.lock.lockShared();
  964. const ready = self.ordered_ready;
  965. self.lock.unlockShared();
  966. if (ready) return;
  967. self.ordered_gate.lock();
  968. defer self.ordered_gate.unlock();
  969. self.lock.lock();
  970. defer self.lock.unlock();
  971. if (self.ordered_ready) return;
  972. const records = try self.directory.records(self.allocator);
  973. defer self.allocator.free(records);
  974. for (records) |record| {
  975. const key = try self.allocator.alloc(u8, record.key_len);
  976. defer self.allocator.free(key);
  977. if (try self.file.preadAll(key, record.key_offset) != key.len) return error.Truncated;
  978. try self.ordered.put(key, record);
  979. }
  980. self.ordered_ready = true;
  981. }
  982. pub fn checkpoint(self: *Engine) !void {
  983. const started = std.time.nanoTimestamp();
  984. self.checkpoint_mutex.lock();
  985. defer self.checkpoint_mutex.unlock();
  986. self.io_mutex.lock();
  987. self.lock.lockShared();
  988. const records = self.directory.records(self.allocator) catch |err| {
  989. self.lock.unlockShared();
  990. self.io_mutex.unlock();
  991. return err;
  992. };
  993. const lsn = self.latest_lsn;
  994. const replay_offset = self.file_length;
  995. self.lock.unlockShared();
  996. self.io_mutex.unlock();
  997. defer self.allocator.free(records);
  998. const payload_length = try std.math.add(usize, pkvdb.checkpoint_header_size, try std.math.mul(usize, records.len, pkvdb.checkpoint_entry_size));
  999. const payload = try self.allocator.alloc(u8, payload_length);
  1000. defer self.allocator.free(payload);
  1001. var header: [56]u8 = undefined;
  1002. pkvdb.encodeCheckpointHeader(.{ .lsn = lsn, .timestamp_ns = now(), .entry_count = records.len, .source_start = pkvdb.data_offset, .source_end = replay_offset }, &header);
  1003. @memcpy(payload[0..header.len], &header);
  1004. for (records, 0..) |record, index| {
  1005. if (record.flags > std.math.maxInt(u16)) return error.InvalidFlags;
  1006. var entry: [48]u8 = undefined;
  1007. pkvdb.encodeCheckpointEntry(.{ .hash = record.hash, .lsn = record.lsn, .key_offset = record.key_offset, .value_offset = record.value_offset, .key_len = record.key_len, .value_len = record.value_len, .flags = @intCast(record.flags) }, &entry);
  1008. @memcpy(payload[pkvdb.checkpoint_header_size + index * pkvdb.checkpoint_entry_size ..][0..pkvdb.checkpoint_entry_size], &entry);
  1009. }
  1010. self.io_mutex.lock();
  1011. defer self.io_mutex.unlock();
  1012. self.lock.lockShared();
  1013. const known_lsn = self.latest_lsn;
  1014. const next_generation = std.math.add(u64, self.generation, 1) catch |failure| {
  1015. self.lock.unlockShared();
  1016. return failure;
  1017. };
  1018. const history_start_lsn = self.oldest_lsn;
  1019. self.lock.unlockShared();
  1020. const entries_offset = try self.appendExtent(.checkpoint_entries, lsn, lsn, payload);
  1021. const manifest_offset = try pkvdb.align8(self.file_length);
  1022. const known_tail = try extentEnd(manifest_offset, pkvdb.manifest_size);
  1023. var manifest_bytes: [104]u8 = undefined;
  1024. pkvdb.encodeManifest(.{ .uuid = self.uuid, .generation = next_generation, .checkpoint_lsn = lsn, .entries_offset = entries_offset, .ordered_offset = 0, .replay_offset = replay_offset, .known_tail = known_tail, .known_lsn = known_lsn, .history_start_lsn = history_start_lsn }, &manifest_bytes);
  1025. const actual_manifest = try self.appendExtent(.manifest, lsn, known_lsn, &manifest_bytes);
  1026. if (actual_manifest != manifest_offset or self.file_length != known_tail) return error.InvalidManifest;
  1027. try self.file.sync();
  1028. const inactive: u1 = self.active_superblock ^ 1;
  1029. var block: [4096]u8 = undefined;
  1030. pkvdb.encodeSuperblock(.{ .generation = next_generation, .uuid = self.uuid, .manifest_offset = manifest_offset, .checkpoint_lsn = lsn, .known_lsn = known_lsn, .known_file_length = known_tail, .created_ns = self.created_ns, .updated_ns = now() }, &block);
  1031. try self.file.pwriteAll(&block, @as(u64, inactive) * pkvdb.superblock_size);
  1032. try self.file.sync();
  1033. self.lock.lock();
  1034. self.active_superblock = inactive;
  1035. self.generation = next_generation;
  1036. self.checkpoint_lsn = lsn;
  1037. self.journal_bytes_since_checkpoint = self.file_length - replay_offset;
  1038. const elapsed = std.time.nanoTimestamp() - started;
  1039. self.checkpoint_ns = if (elapsed > 0) @intCast(elapsed) else 0;
  1040. self.lock.unlock();
  1041. }
  1042. pub fn status(self: *Engine) Status {
  1043. self.io_mutex.lock();
  1044. defer self.io_mutex.unlock();
  1045. self.lock.lockShared();
  1046. defer self.lock.unlockShared();
  1047. return .{
  1048. .uuid = self.uuid,
  1049. .file_bytes = self.file_length,
  1050. .latest_lsn = self.latest_lsn,
  1051. .oldest_lsn = self.oldest_lsn,
  1052. .checkpoint_lsn = self.checkpoint_lsn,
  1053. .journal_bytes_since_checkpoint = self.journal_bytes_since_checkpoint,
  1054. .live_keys = self.directory.count,
  1055. .keydir_bytes = self.directory.bytes(),
  1056. .ordered_index_bytes = self.ordered.allocated_bytes,
  1057. .bytes_written = self.bytes_written,
  1058. .checksum_failures = self.checksum_failures,
  1059. .partial_tails = self.partial_tails,
  1060. .recovery_ns = self.recovery_ns,
  1061. .checkpoint_ns = self.checkpoint_ns,
  1062. .connection_bytes = self.connection_bytes.load(.monotonic),
  1063. .active_requests = self.active_requests.load(.monotonic),
  1064. .commit_groups = self.commit_groups,
  1065. .committed_transactions = self.committed_transactions,
  1066. .largest_commit_group = self.largest_commit_group,
  1067. };
  1068. }
  1069. pub fn addConnectionBytes(self: *Engine, amount: u64) void {
  1070. _ = self.connection_bytes.fetchAdd(amount, .monotonic);
  1071. }
  1072. pub fn removeConnectionBytes(self: *Engine, amount: u64) void {
  1073. _ = self.connection_bytes.fetchSub(amount, .monotonic);
  1074. }
  1075. pub fn beginRequest(self: *Engine) void {
  1076. _ = self.active_requests.fetchAdd(1, .monotonic);
  1077. }
  1078. pub fn endRequest(self: *Engine) void {
  1079. _ = self.active_requests.fetchSub(1, .monotonic);
  1080. }
  1081. };
  1082. fn readInt(comptime T: type, bytes: []const u8, offset: usize) T {
  1083. return std.mem.readInt(T, bytes[offset..][0..@sizeOf(T)], .little);
  1084. }
  1085. fn writeInt(comptime T: type, bytes: []u8, offset: usize, value: T) void {
  1086. std.mem.writeInt(T, bytes[offset..][0..@sizeOf(T)], value, .little);
  1087. }
  1088. test "insert overwrite delete recreate and recovery" {
  1089. var tmp = std.testing.tmpDir(.{});
  1090. defer tmp.cleanup();
  1091. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1092. const directory = try tmp.dir.realpath(".", &path_buffer);
  1093. const path = try std.fmt.allocPrint(std.testing.allocator, "{s}/test.pkvdb", .{directory});
  1094. defer std.testing.allocator.free(path);
  1095. var engine = try Engine.open(std.testing.allocator, path);
  1096. _ = try engine.put("key", "one");
  1097. _ = try engine.put("key", "two");
  1098. try std.testing.expect(try engine.delete("key"));
  1099. _ = try engine.put("key", "three");
  1100. var value = (try engine.get(std.testing.allocator, "key")).?;
  1101. try std.testing.expectEqualStrings("three", value.bytes);
  1102. std.testing.allocator.free(value.bytes);
  1103. engine.close();
  1104. engine = try Engine.open(std.testing.allocator, path);
  1105. defer engine.close();
  1106. value = (try engine.get(std.testing.allocator, "key")).?;
  1107. defer std.testing.allocator.free(value.bytes);
  1108. try std.testing.expectEqualStrings("three", value.bytes);
  1109. try std.testing.expectEqual(@as(u64, 4), value.lsn);
  1110. }
  1111. test "atomic batch checkpoint tail and ordered scan" {
  1112. var tmp = std.testing.tmpDir(.{});
  1113. defer tmp.cleanup();
  1114. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1115. const directory = try tmp.dir.realpath(".", &path_buffer);
  1116. const path = try std.fmt.allocPrint(std.testing.allocator, "{s}/test.pkvdb", .{directory});
  1117. defer std.testing.allocator.free(path);
  1118. var engine = try Engine.open(std.testing.allocator, path);
  1119. _ = try engine.batchWrite(&.{ .{ .opcode = .put, .key = "p/2", .value = "b" }, .{ .opcode = .put, .key = "p/1", .value = "a" } }, "meta");
  1120. try engine.checkpoint();
  1121. _ = try engine.put("p/3", "c");
  1122. engine.close();
  1123. engine = try Engine.open(std.testing.allocator, path);
  1124. defer engine.close();
  1125. var batch = try engine.scan(std.testing.allocator, "p/", "", 2, true, 1024);
  1126. try std.testing.expectEqual(@as(usize, 2), batch.entries.len);
  1127. try std.testing.expectEqualStrings("p/1", batch.entries[0].key);
  1128. const cursor = try std.testing.allocator.dupe(u8, batch.next_cursor);
  1129. batch.deinit(std.testing.allocator);
  1130. defer std.testing.allocator.free(cursor);
  1131. batch = try engine.scan(std.testing.allocator, "p/", cursor, 2, true, 1024);
  1132. defer batch.deinit(std.testing.allocator);
  1133. try std.testing.expectEqual(@as(usize, 1), batch.entries.len);
  1134. try std.testing.expectEqualStrings("p/3", batch.entries[0].key);
  1135. }
  1136. test "scan supports full-size PKBFI pages and accounts for entry framing" {
  1137. var tmp = std.testing.tmpDir(.{});
  1138. defer tmp.cleanup();
  1139. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1140. const path = try testPath(&tmp, "large-scan.pkvdb", &path_buffer);
  1141. defer std.testing.allocator.free(path);
  1142. var engine = try Engine.open(std.testing.allocator, path);
  1143. defer engine.close();
  1144. const value = try std.testing.allocator.alloc(u8, 2 * 1024 * 1024);
  1145. defer std.testing.allocator.free(value);
  1146. @memset(value, 'x');
  1147. _ = try engine.put("large/key", value);
  1148. const entry_size = 16 + "large/key".len + value.len;
  1149. var batch = try engine.scan(std.testing.allocator, "large/", "", 1, true, @intCast(entry_size));
  1150. defer batch.deinit(std.testing.allocator);
  1151. try std.testing.expectEqual(@as(usize, 1), batch.entries.len);
  1152. try std.testing.expectEqual(value.len, batch.entries[0].value.?.len);
  1153. try std.testing.expectError(
  1154. error.ScanEntryTooLarge,
  1155. engine.scan(std.testing.allocator, "large/", "", 1, true, @intCast(entry_size - 1)),
  1156. );
  1157. }
  1158. fn testPath(tmp: *std.testing.TmpDir, name: []const u8, buffer: *[std.fs.max_path_bytes]u8) ![]const u8 {
  1159. const directory = try tmp.dir.realpath(".", buffer);
  1160. return std.fs.path.join(std.testing.allocator, &.{ directory, name });
  1161. }
  1162. test "partial final extent is ignored and removed" {
  1163. var tmp = std.testing.tmpDir(.{});
  1164. defer tmp.cleanup();
  1165. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1166. const path = try testPath(&tmp, "partial.pkvdb", &path_buffer);
  1167. defer std.testing.allocator.free(path);
  1168. var engine = try Engine.open(std.testing.allocator, path);
  1169. _ = try engine.put("durable", "value");
  1170. const valid_length = engine.status().file_bytes;
  1171. engine.close();
  1172. const file = try std.fs.cwd().openFile(path, .{ .mode = .read_write });
  1173. var header: [64]u8 = undefined;
  1174. pkvdb.encodeExtentHeader(.{ .extent_type = .journal, .payload_length = 100, .first_lsn = 2, .last_lsn = 2, .payload_crc = 0 }, &header);
  1175. try file.pwriteAll(&header, valid_length);
  1176. try file.pwriteAll("partial transaction", valid_length + header.len);
  1177. file.close();
  1178. engine = try Engine.open(std.testing.allocator, path);
  1179. defer engine.close();
  1180. const value = (try engine.get(std.testing.allocator, "durable")).?;
  1181. defer std.testing.allocator.free(value.bytes);
  1182. try std.testing.expectEqualStrings("value", value.bytes);
  1183. try std.testing.expectEqual(valid_length, engine.status().file_bytes);
  1184. try std.testing.expectEqual(@as(u64, 1), engine.status().partial_tails);
  1185. }
  1186. test "corruption in committed journal is explicit" {
  1187. var tmp = std.testing.tmpDir(.{});
  1188. defer tmp.cleanup();
  1189. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1190. const path = try testPath(&tmp, "corrupt.pkvdb", &path_buffer);
  1191. defer std.testing.allocator.free(path);
  1192. var engine = try Engine.open(std.testing.allocator, path);
  1193. _ = try engine.put("key", "value");
  1194. engine.close();
  1195. const file = try std.fs.cwd().openFile(path, .{ .mode = .read_write });
  1196. var byte: [1]u8 = undefined;
  1197. const offset = pkvdb.data_offset + pkvdb.extent_header_size + pkvdb.group_header_size + pkvdb.transaction_header_size + pkvdb.operation_header_size + 3;
  1198. _ = try file.preadAll(&byte, offset);
  1199. byte[0] ^= 1;
  1200. try file.pwriteAll(&byte, offset);
  1201. file.close();
  1202. try std.testing.expectError(error.ChecksumMismatch, Engine.open(std.testing.allocator, path));
  1203. }
  1204. test "one corrupted superblock falls back and adopts tail" {
  1205. var tmp = std.testing.tmpDir(.{});
  1206. defer tmp.cleanup();
  1207. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1208. const path = try testPath(&tmp, "root.pkvdb", &path_buffer);
  1209. defer std.testing.allocator.free(path);
  1210. var engine = try Engine.open(std.testing.allocator, path);
  1211. _ = try engine.put("before", "one");
  1212. try engine.checkpoint();
  1213. _ = try engine.put("after", "two");
  1214. engine.close();
  1215. const file = try std.fs.cwd().openFile(path, .{ .mode = .read_write });
  1216. var byte: [1]u8 = undefined;
  1217. _ = try file.preadAll(&byte, pkvdb.superblock_size + 24);
  1218. byte[0] ^= 1;
  1219. try file.pwriteAll(&byte, pkvdb.superblock_size + 24);
  1220. file.close();
  1221. engine = try Engine.open(std.testing.allocator, path);
  1222. defer engine.close();
  1223. const value = (try engine.get(std.testing.allocator, "after")).?;
  1224. defer std.testing.allocator.free(value.bytes);
  1225. try std.testing.expectEqualStrings("two", value.bytes);
  1226. try std.testing.expectEqual(@as(u64, 2), engine.status().latest_lsn);
  1227. }
  1228. test "unrooted checkpoint and manifest do not hide journal" {
  1229. var tmp = std.testing.tmpDir(.{});
  1230. defer tmp.cleanup();
  1231. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1232. const path = try testPath(&tmp, "interrupted-checkpoint.pkvdb", &path_buffer);
  1233. defer std.testing.allocator.free(path);
  1234. var engine = try Engine.open(std.testing.allocator, path);
  1235. _ = try engine.put("key", "value");
  1236. var roots: [8192]u8 = undefined;
  1237. _ = try engine.file.preadAll(&roots, 0);
  1238. try engine.checkpoint();
  1239. engine.close();
  1240. const file = try std.fs.cwd().openFile(path, .{ .mode = .read_write });
  1241. try file.pwriteAll(&roots, 0);
  1242. file.close();
  1243. engine = try Engine.open(std.testing.allocator, path);
  1244. defer engine.close();
  1245. const value = (try engine.get(std.testing.allocator, "key")).?;
  1246. defer std.testing.allocator.free(value.bytes);
  1247. try std.testing.expectEqualStrings("value", value.bytes);
  1248. try std.testing.expectEqual(@as(u64, 1), engine.status().latest_lsn);
  1249. }
  1250. test "unknown compatible extent is skipped by length" {
  1251. var tmp = std.testing.tmpDir(.{});
  1252. defer tmp.cleanup();
  1253. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1254. const path = try testPath(&tmp, "unknown.pkvdb", &path_buffer);
  1255. defer std.testing.allocator.free(path);
  1256. var engine = try Engine.open(std.testing.allocator, path);
  1257. _ = try engine.put("key", "value");
  1258. const offset = engine.status().file_bytes;
  1259. engine.close();
  1260. const file = try std.fs.cwd().openFile(path, .{ .mode = .read_write });
  1261. var header: [64]u8 = undefined;
  1262. pkvdb.encodeExtentHeader(.{ .extent_type = @enumFromInt(99), .version = 9, .payload_length = 3, .first_lsn = 0, .last_lsn = 0, .payload_crc = pkvdb.crc32c("new") }, &header);
  1263. try file.pwriteAll(&header, offset);
  1264. try file.pwriteAll("new", offset + header.len);
  1265. try file.pwriteAll(&([_]u8{0} ** 5), offset + header.len + 3);
  1266. file.close();
  1267. engine = try Engine.open(std.testing.allocator, path);
  1268. defer engine.close();
  1269. try std.testing.expect(try engine.exists("key"));
  1270. }
  1271. test "repeated overwrite and delete keep directory memory bounded" {
  1272. var tmp = std.testing.tmpDir(.{});
  1273. defer tmp.cleanup();
  1274. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1275. const path = try testPath(&tmp, "memory.pkvdb", &path_buffer);
  1276. defer std.testing.allocator.free(path);
  1277. var engine = try Engine.open(std.testing.allocator, path);
  1278. defer engine.close();
  1279. _ = try engine.put("same", "first");
  1280. const initial = engine.status().keydir_bytes;
  1281. for (0..100) |index| {
  1282. var value: [16]u8 = undefined;
  1283. const encoded = try std.fmt.bufPrint(&value, "{d}", .{index});
  1284. _ = try engine.put("same", encoded);
  1285. }
  1286. try std.testing.expect(try engine.delete("same"));
  1287. _ = try engine.put("same", "last");
  1288. try std.testing.expectEqual(initial, engine.status().keydir_bytes);
  1289. try std.testing.expectEqual(@as(u64, 1), engine.status().live_keys);
  1290. }
  1291. test "concurrent reads and writes preserve complete values" {
  1292. var tmp = std.testing.tmpDir(.{});
  1293. defer tmp.cleanup();
  1294. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1295. const path = try testPath(&tmp, "concurrent.pkvdb", &path_buffer);
  1296. defer std.testing.allocator.free(path);
  1297. var engine = try Engine.open(std.testing.allocator, path);
  1298. defer engine.close();
  1299. _ = try engine.put("key", "00000000");
  1300. var failed = std.atomic.Value(bool).init(false);
  1301. const Writer = struct {
  1302. fn run(target: *Engine, failure: *std.atomic.Value(bool)) void {
  1303. for (0..50) |index| {
  1304. var value: [8]u8 = undefined;
  1305. _ = std.fmt.bufPrint(&value, "{d:0>8}", .{index}) catch {
  1306. failure.store(true, .release);
  1307. return;
  1308. };
  1309. _ = target.put("key", &value) catch {
  1310. failure.store(true, .release);
  1311. return;
  1312. };
  1313. }
  1314. }
  1315. };
  1316. const thread = try std.Thread.spawn(.{}, Writer.run, .{ &engine, &failed });
  1317. for (0..50) |_| {
  1318. const value = try engine.get(std.testing.allocator, "key") orelse return error.TestUnexpectedResult;
  1319. try std.testing.expectEqual(@as(usize, 8), value.bytes.len);
  1320. std.testing.allocator.free(value.bytes);
  1321. }
  1322. thread.join();
  1323. try std.testing.expect(!failed.load(.acquire));
  1324. }
  1325. fn copyPrefix(source_path: []const u8, destination_path: []const u8) !void {
  1326. const source = try std.fs.cwd().openFile(source_path, .{ .mode = .read_only });
  1327. defer source.close();
  1328. const length = try source.getEndPos();
  1329. const destination = try std.fs.cwd().createFile(destination_path, .{ .read = true, .truncate = true });
  1330. defer destination.close();
  1331. var buffer: [4096]u8 = undefined;
  1332. var offset: u64 = 0;
  1333. while (offset < length) {
  1334. const amount: usize = @intCast(@min(buffer.len, length - offset));
  1335. const got = try source.preadAll(buffer[0..amount], offset);
  1336. if (got == 0) break;
  1337. try destination.writeAll(buffer[0..got]);
  1338. offset += got;
  1339. }
  1340. }
  1341. test "every live copy recovers while writes continue" {
  1342. var tmp = std.testing.tmpDir(.{});
  1343. defer tmp.cleanup();
  1344. var source_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1345. const source = try testPath(&tmp, "live.pkvdb", &source_buffer);
  1346. defer std.testing.allocator.free(source);
  1347. var engine = try Engine.open(std.testing.allocator, source);
  1348. defer engine.close();
  1349. _ = try engine.put("seed", "value");
  1350. var failed = std.atomic.Value(bool).init(false);
  1351. const Writer = struct {
  1352. fn run(target: *Engine, failure: *std.atomic.Value(bool)) void {
  1353. for (0..30) |index| {
  1354. var key: [16]u8 = undefined;
  1355. const encoded = std.fmt.bufPrint(&key, "key-{d}", .{index}) catch {
  1356. failure.store(true, .release);
  1357. return;
  1358. };
  1359. _ = target.put(encoded, "value") catch {
  1360. failure.store(true, .release);
  1361. return;
  1362. };
  1363. if (index == 15) target.checkpoint() catch {
  1364. failure.store(true, .release);
  1365. return;
  1366. };
  1367. }
  1368. }
  1369. };
  1370. const thread = try std.Thread.spawn(.{}, Writer.run, .{ &engine, &failed });
  1371. for (0..8) |index| {
  1372. var name: [32]u8 = undefined;
  1373. const filename = try std.fmt.bufPrint(&name, "copy-{d}.pkvdb", .{index});
  1374. var copy_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1375. const destination = try testPath(&tmp, filename, &copy_buffer);
  1376. defer std.testing.allocator.free(destination);
  1377. try copyPrefix(source, destination);
  1378. var copy = try Engine.open(std.testing.allocator, destination);
  1379. try std.testing.expect(try copy.exists("seed"));
  1380. copy.close();
  1381. }
  1382. thread.join();
  1383. try std.testing.expect(!failed.load(.acquire));
  1384. }
  1385. test "group commit preserves independent transaction LSNs" {
  1386. var tmp = std.testing.tmpDir(.{});
  1387. defer tmp.cleanup();
  1388. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1389. const path = try testPath(&tmp, "groups.pkvdb", &path_buffer);
  1390. defer std.testing.allocator.free(path);
  1391. var engine = try Engine.open(std.testing.allocator, path);
  1392. var operations: [256]Operation = undefined;
  1393. var keys: [256][8]u8 = undefined;
  1394. var lsns: [256]u64 = undefined;
  1395. for (&operations, 0..) |*operation, index| {
  1396. const key = try std.fmt.bufPrint(&keys[index], "k{d}", .{index});
  1397. operation.* = .{ .opcode = .put, .key = key, .value = "value" };
  1398. }
  1399. try engine.putMany(&operations, &lsns);
  1400. const status = engine.status();
  1401. try std.testing.expectEqual(@as(u64, 1), status.commit_groups);
  1402. try std.testing.expectEqual(@as(u64, 256), status.committed_transactions);
  1403. for (lsns, 0..) |lsn, index| try std.testing.expectEqual(index + 1, lsn);
  1404. engine.close();
  1405. engine = try Engine.open(std.testing.allocator, path);
  1406. defer engine.close();
  1407. try std.testing.expectEqual(@as(u64, 256), engine.status().latest_lsn);
  1408. for (operations) |operation| try std.testing.expect(try engine.exists(operation.key));
  1409. }
  1410. test "ordered index stays current after lazy construction" {
  1411. var tmp = std.testing.tmpDir(.{});
  1412. defer tmp.cleanup();
  1413. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1414. const path = try testPath(&tmp, "lazy-order.pkvdb", &path_buffer);
  1415. defer std.testing.allocator.free(path);
  1416. var engine = try Engine.open(std.testing.allocator, path);
  1417. defer engine.close();
  1418. _ = try engine.put("p/a", "one");
  1419. var batch = try engine.scan(std.testing.allocator, "p/", "", 10, false, 1024);
  1420. batch.deinit(std.testing.allocator);
  1421. _ = try engine.put("p/b", "two");
  1422. _ = try engine.put("p/a", "updated");
  1423. try std.testing.expect(try engine.delete("p/b"));
  1424. _ = try engine.put("p/c", "three");
  1425. batch = try engine.scan(std.testing.allocator, "p/", "", 10, true, 1024);
  1426. defer batch.deinit(std.testing.allocator);
  1427. try std.testing.expectEqual(@as(usize, 2), batch.entries.len);
  1428. try std.testing.expectEqualStrings("p/a", batch.entries[0].key);
  1429. try std.testing.expectEqualStrings("updated", batch.entries[0].value.?);
  1430. try std.testing.expectEqualStrings("p/c", batch.entries[1].key);
  1431. }
  1432. test "concurrent delete reports one removal" {
  1433. var tmp = std.testing.tmpDir(.{});
  1434. defer tmp.cleanup();
  1435. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1436. const path = try testPath(&tmp, "delete-race.pkvdb", &path_buffer);
  1437. defer std.testing.allocator.free(path);
  1438. var engine = try Engine.open(std.testing.allocator, path);
  1439. defer engine.close();
  1440. _ = try engine.put("key", "value");
  1441. var results: [2]bool = undefined;
  1442. var failed = std.atomic.Value(bool).init(false);
  1443. const Deleter = struct {
  1444. fn run(target: *Engine, result: *bool, failure: *std.atomic.Value(bool)) void {
  1445. result.* = target.delete("key") catch {
  1446. failure.store(true, .release);
  1447. return;
  1448. };
  1449. }
  1450. };
  1451. const first = try std.Thread.spawn(.{}, Deleter.run, .{ &engine, &results[0], &failed });
  1452. const second = try std.Thread.spawn(.{}, Deleter.run, .{ &engine, &results[1], &failed });
  1453. first.join();
  1454. second.join();
  1455. try std.testing.expect(!failed.load(.acquire));
  1456. try std.testing.expect(results[0] != results[1]);
  1457. }
  1458. test "compare batch write absent match and stale conflict" {
  1459. var tmp = std.testing.tmpDir(.{});
  1460. defer tmp.cleanup();
  1461. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1462. const path = try testPath(&tmp, "compare.pkvdb", &path_buffer);
  1463. defer std.testing.allocator.free(path);
  1464. var engine = try Engine.open(std.testing.allocator, path);
  1465. const absent_checks = [_]CompareCheck{.{ .key = "k", .expected_lsn = 0 }};
  1466. var result = try engine.compareBatchWrite(&absent_checks, &.{.{ .opcode = .put, .key = "k", .value = "one" }}, "meta");
  1467. try std.testing.expect(result.committed);
  1468. try std.testing.expectEqual(@as(u64, 1), result.lsn);
  1469. const match_checks = [_]CompareCheck{.{ .key = "k", .expected_lsn = result.lsn }};
  1470. result = try engine.compareBatchWrite(&match_checks, &.{.{ .opcode = .put, .key = "k", .value = "two" }}, "");
  1471. try std.testing.expect(result.committed);
  1472. try std.testing.expectEqual(@as(u64, 2), result.lsn);
  1473. const stale_checks = [_]CompareCheck{.{ .key = "k", .expected_lsn = 1 }};
  1474. result = try engine.compareBatchWrite(&stale_checks, &.{.{ .opcode = .put, .key = "k", .value = "three" }}, "");
  1475. try std.testing.expect(!result.committed);
  1476. try std.testing.expectEqual(@as(u64, 0), result.lsn);
  1477. var value = (try engine.get(std.testing.allocator, "k")).?;
  1478. try std.testing.expectEqualStrings("two", value.bytes);
  1479. try std.testing.expectEqual(@as(u64, 2), value.lsn);
  1480. std.testing.allocator.free(value.bytes);
  1481. engine.close();
  1482. engine = try Engine.open(std.testing.allocator, path);
  1483. defer engine.close();
  1484. value = (try engine.get(std.testing.allocator, "k")).?;
  1485. defer std.testing.allocator.free(value.bytes);
  1486. try std.testing.expectEqualStrings("two", value.bytes);
  1487. try std.testing.expectEqual(@as(u64, 2), value.lsn);
  1488. }
  1489. test "scan materializes immutable records outside the publication gate" {
  1490. var tmp = std.testing.tmpDir(.{});
  1491. defer tmp.cleanup();
  1492. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1493. const path = try testPath(&tmp, "scan-snapshot.pkvdb", &path_buffer);
  1494. defer std.testing.allocator.free(path);
  1495. var engine = try Engine.open(std.testing.allocator, path);
  1496. defer engine.close();
  1497. const original = [_]u8{'x'} ** 12345;
  1498. _ = try engine.put("a", &original);
  1499. _ = try engine.put("b", &original);
  1500. const long_key = [_]u8{'c'} ** 1024;
  1501. _ = try engine.put(&long_key, &original);
  1502. const Probe = struct {
  1503. engine: *Engine,
  1504. large_key: []const u8,
  1505. locked_allocations: usize = 0,
  1506. changed: bool = false,
  1507. fn alloc(context: *anyopaque, len: usize, alignment: std.mem.Alignment, address: usize) ?[*]u8 {
  1508. const self: *@This() = @ptrCast(@alignCast(context));
  1509. const unlocked = self.engine.lock.tryLock();
  1510. if (unlocked) self.engine.lock.unlock() else self.locked_allocations += 1;
  1511. if (unlocked and len == 12345 and !self.changed) {
  1512. self.changed = true;
  1513. _ = self.engine.put("a", "replacement") catch return null;
  1514. _ = self.engine.delete("b") catch return null;
  1515. _ = self.engine.delete(self.large_key) catch return null;
  1516. self.engine.checkpoint() catch return null;
  1517. }
  1518. return std.testing.allocator.rawAlloc(len, alignment, address);
  1519. }
  1520. fn free(_: *anyopaque, bytes: []u8, alignment: std.mem.Alignment, address: usize) void {
  1521. std.testing.allocator.rawFree(bytes, alignment, address);
  1522. }
  1523. };
  1524. var probe = Probe{ .engine = &engine, .large_key = &long_key };
  1525. const allocator = std.mem.Allocator{ .ptr = &probe, .vtable = &.{ .alloc = Probe.alloc, .resize = std.mem.Allocator.noResize, .remap = std.mem.Allocator.noRemap, .free = Probe.free } };
  1526. var batch = try engine.scan(allocator, "", "", 8, true, 50000);
  1527. defer batch.deinit(allocator);
  1528. try std.testing.expectEqual(@as(usize, 0), probe.locked_allocations);
  1529. try std.testing.expect(probe.changed);
  1530. try std.testing.expect(batch.done);
  1531. try std.testing.expectEqual(@as(usize, 3), batch.entries.len);
  1532. try std.testing.expectEqualStrings("a", batch.entries[0].key);
  1533. try std.testing.expectEqualStrings("b", batch.entries[1].key);
  1534. try std.testing.expectEqualStrings(&long_key, batch.entries[2].key);
  1535. try std.testing.expectEqualStrings(&original, batch.entries[0].value.?);
  1536. try std.testing.expectEqualStrings(&original, batch.entries[1].value.?);
  1537. try std.testing.expectEqual(@as(u64, 1), batch.entries[0].lsn);
  1538. try std.testing.expectEqual(@as(u64, 2), batch.entries[1].lsn);
  1539. try std.testing.expectEqualStrings(&original, batch.entries[2].value.?);
  1540. try std.testing.expectEqual(@as(u64, 3), batch.entries[2].lsn);
  1541. try std.testing.expectEqualStrings(&long_key, batch.next_cursor);
  1542. const current = (try engine.get(std.testing.allocator, "a")).?;
  1543. defer std.testing.allocator.free(current.bytes);
  1544. try std.testing.expectEqualStrings("replacement", current.bytes);
  1545. try std.testing.expect((try engine.getRef("b")) == null);
  1546. try std.testing.expect((try engine.getRef(&long_key)) == null);
  1547. _ = try engine.put(&long_key, "long-value");
  1548. const FailureProbe = struct {
  1549. fn run(alloc: std.mem.Allocator, target: *Engine) !void {
  1550. var values = try target.scan(alloc, "", "", 8, true, 50000);
  1551. defer values.deinit(alloc);
  1552. var keys = try target.scan(alloc, "", "", 8, false, 50000);
  1553. defer keys.deinit(alloc);
  1554. var empty = try target.scan(alloc, "missing", "", 8, true, 50000);
  1555. defer empty.deinit(alloc);
  1556. }
  1557. };
  1558. try std.testing.checkAllAllocationFailures(std.testing.allocator, FailureProbe.run, .{&engine});
  1559. }
  1560. test "conditional queue heads bypass group coalescing" {
  1561. var completion = WriteCompletion{ .remaining = 1 };
  1562. var pending = PendingWrite{ .operations = &.{}, .metadata = "", .completion = &completion, .bytes = 0 };
  1563. try std.testing.expect(Engine.canCoalesce(&pending));
  1564. pending.checks = &.{.{ .key = "k", .expected_lsn = 1 }};
  1565. try std.testing.expect(!Engine.canCoalesce(&pending));
  1566. }
  1567. test "compare absent check rejects existing key" {
  1568. var tmp = std.testing.tmpDir(.{});
  1569. defer tmp.cleanup();
  1570. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1571. const path = try testPath(&tmp, "compare-absent.pkvdb", &path_buffer);
  1572. defer std.testing.allocator.free(path);
  1573. var engine = try Engine.open(std.testing.allocator, path);
  1574. defer engine.close();
  1575. _ = try engine.put("k", "seed");
  1576. const checks = [_]CompareCheck{.{ .key = "k", .expected_lsn = 0 }};
  1577. const result = try engine.compareBatchWrite(&checks, &.{.{ .opcode = .put, .key = "k", .value = "x" }}, "");
  1578. try std.testing.expect(!result.committed);
  1579. try std.testing.expectEqual(@as(u64, 0), result.lsn);
  1580. const value = (try engine.get(std.testing.allocator, "k")).?;
  1581. defer std.testing.allocator.free(value.bytes);
  1582. try std.testing.expectEqualStrings("seed", value.bytes);
  1583. }
  1584. test "concurrent compare batch writes expecting same lsn commit exactly one" {
  1585. var tmp = std.testing.tmpDir(.{});
  1586. defer tmp.cleanup();
  1587. var path_buffer: [std.fs.max_path_bytes]u8 = undefined;
  1588. const path = try testPath(&tmp, "compare-race.pkvdb", &path_buffer);
  1589. defer std.testing.allocator.free(path);
  1590. var engine = try Engine.open(std.testing.allocator, path);
  1591. defer engine.close();
  1592. _ = try engine.put("key", "seed");
  1593. var results: [2]CompareBatchResult = undefined;
  1594. var failed = std.atomic.Value(bool).init(false);
  1595. const Worker = struct {
  1596. fn run(target: *Engine, result: *CompareBatchResult, failure: *std.atomic.Value(bool)) void {
  1597. const checks = [_]CompareCheck{.{ .key = "key", .expected_lsn = 1 }};
  1598. const operations = [_]Operation{.{ .opcode = .put, .key = "key", .value = "winner" }};
  1599. result.* = target.compareBatchWrite(&checks, &operations, "") catch {
  1600. failure.store(true, .release);
  1601. return;
  1602. };
  1603. }
  1604. };
  1605. const first = try std.Thread.spawn(.{}, Worker.run, .{ &engine, &results[0], &failed });
  1606. const second = try std.Thread.spawn(.{}, Worker.run, .{ &engine, &results[1], &failed });
  1607. first.join();
  1608. second.join();
  1609. try std.testing.expect(!failed.load(.acquire));
  1610. const committed = @as(u64, @intFromBool(results[0].committed)) + @as(u64, @intFromBool(results[1].committed));
  1611. try std.testing.expectEqual(@as(u64, 1), committed);
  1612. const winner = if (results[0].committed) results[0] else results[1];
  1613. try std.testing.expectEqual(@as(u64, 2), winner.lsn);
  1614. const value = (try engine.get(std.testing.allocator, "key")).?;
  1615. defer std.testing.allocator.free(value.bytes);
  1616. try std.testing.expectEqualStrings("winner", value.bytes);
  1617. }