wal.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. #include "wal.hpp"
  2. #include "../json_parse.hpp"
  3. #include <algorithm>
  4. #include <chrono>
  5. #include <cstring>
  6. #include <iomanip>
  7. #include <sstream>
  8. namespace smartbotic::database {
  9. // ===== CRC32 Implementation =====
  10. namespace crc32 {
  11. // CRC32 lookup table (polynomial 0xEDB88320)
  12. static const uint32_t table[256] = {
  13. 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
  14. 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
  15. 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
  16. 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
  17. 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
  18. 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
  19. 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
  20. 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
  21. 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
  22. 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
  23. 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
  24. 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
  25. 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
  26. 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
  27. 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
  28. 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
  29. 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
  30. 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
  31. 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
  32. 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
  33. 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
  34. 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
  35. 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
  36. 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
  37. 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
  38. 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
  39. 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
  40. 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
  41. 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
  42. 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
  43. 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD706B3, 0x54DE5729, 0x23D967BF,
  44. 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
  45. };
  46. uint32_t calculate(const void* data, size_t length) {
  47. uint32_t crc = 0xFFFFFFFF;
  48. const uint8_t* bytes = static_cast<const uint8_t*>(data);
  49. for (size_t i = 0; i < length; ++i) {
  50. crc = table[(crc ^ bytes[i]) & 0xFF] ^ (crc >> 8);
  51. }
  52. return crc ^ 0xFFFFFFFF;
  53. }
  54. uint32_t calculate(const std::string& str) {
  55. return calculate(str.data(), str.size());
  56. }
  57. } // namespace crc32
  58. // ===== WalEntry Implementation =====
  59. std::vector<uint8_t> WalEntry::serialize() const {
  60. std::vector<uint8_t> result;
  61. // Reserve space for length prefix (will be set at the end)
  62. result.resize(4);
  63. // Write sequence (8 bytes)
  64. for (int i = 0; i < 8; ++i) {
  65. result.push_back(static_cast<uint8_t>((sequence >> (i * 8)) & 0xFF));
  66. }
  67. // Write timestamp (8 bytes)
  68. for (int i = 0; i < 8; ++i) {
  69. result.push_back(static_cast<uint8_t>((timestamp >> (i * 8)) & 0xFF));
  70. }
  71. // Write opType (1 byte)
  72. result.push_back(static_cast<uint8_t>(opType));
  73. // Write collection (2 bytes length + data)
  74. uint16_t collLen = static_cast<uint16_t>(collection.size());
  75. result.push_back(static_cast<uint8_t>(collLen & 0xFF));
  76. result.push_back(static_cast<uint8_t>((collLen >> 8) & 0xFF));
  77. result.insert(result.end(), collection.begin(), collection.end());
  78. // Write documentId (2 bytes length + data)
  79. uint16_t docIdLen = static_cast<uint16_t>(documentId.size());
  80. result.push_back(static_cast<uint8_t>(docIdLen & 0xFF));
  81. result.push_back(static_cast<uint8_t>((docIdLen >> 8) & 0xFF));
  82. result.insert(result.end(), documentId.begin(), documentId.end());
  83. // Write data flag and data (1 byte flag + 4 bytes length + data)
  84. if (data) {
  85. result.push_back(1);
  86. std::string dataStr = data->dump();
  87. uint32_t dataLen = static_cast<uint32_t>(dataStr.size());
  88. for (int i = 0; i < 4; ++i) {
  89. result.push_back(static_cast<uint8_t>((dataLen >> (i * 8)) & 0xFF));
  90. }
  91. result.insert(result.end(), dataStr.begin(), dataStr.end());
  92. } else {
  93. result.push_back(0);
  94. }
  95. // Write collectionOptions flag and data
  96. if (collectionOptions) {
  97. result.push_back(1);
  98. std::string optsStr = collectionOptions->toJson().dump();
  99. uint32_t optsLen = static_cast<uint32_t>(optsStr.size());
  100. for (int i = 0; i < 4; ++i) {
  101. result.push_back(static_cast<uint8_t>((optsLen >> (i * 8)) & 0xFF));
  102. }
  103. result.insert(result.end(), optsStr.begin(), optsStr.end());
  104. } else {
  105. result.push_back(0);
  106. }
  107. // Write vectorData flag and data (1 byte flag + 4 bytes dim + dim * sizeof(float) bytes)
  108. if (vectorData) {
  109. result.push_back(1);
  110. uint32_t dim = static_cast<uint32_t>(vectorData->size());
  111. for (int i = 0; i < 4; ++i) {
  112. result.push_back(static_cast<uint8_t>((dim >> (i * 8)) & 0xFF));
  113. }
  114. const uint8_t* floatBytes = reinterpret_cast<const uint8_t*>(vectorData->data());
  115. result.insert(result.end(), floatBytes, floatBytes + dim * sizeof(float));
  116. } else {
  117. result.push_back(0);
  118. }
  119. // v1.8.0 — origin nodeId trailer (1 byte flag + 2 byte len + bytes).
  120. // Always emitted by current writer. Older readers stop after vectorData
  121. // and never observe this section; new readers handle missing trailer.
  122. if (!nodeId.empty()) {
  123. result.push_back(1);
  124. uint16_t nodeLen = static_cast<uint16_t>(nodeId.size());
  125. result.push_back(static_cast<uint8_t>(nodeLen & 0xFF));
  126. result.push_back(static_cast<uint8_t>((nodeLen >> 8) & 0xFF));
  127. result.insert(result.end(), nodeId.begin(), nodeId.end());
  128. } else {
  129. result.push_back(0);
  130. }
  131. // Calculate and append checksum (excluding length prefix and checksum itself)
  132. uint32_t crc = crc32::calculate(result.data() + 4, result.size() - 4);
  133. for (int i = 0; i < 4; ++i) {
  134. result.push_back(static_cast<uint8_t>((crc >> (i * 8)) & 0xFF));
  135. }
  136. // Set length prefix (total size minus 4 bytes for length prefix)
  137. uint32_t totalLen = static_cast<uint32_t>(result.size() - 4);
  138. result[0] = static_cast<uint8_t>(totalLen & 0xFF);
  139. result[1] = static_cast<uint8_t>((totalLen >> 8) & 0xFF);
  140. result[2] = static_cast<uint8_t>((totalLen >> 16) & 0xFF);
  141. result[3] = static_cast<uint8_t>((totalLen >> 24) & 0xFF);
  142. return result;
  143. }
  144. std::optional<WalEntry> WalEntry::deserialize(const std::vector<uint8_t>& data) {
  145. if (data.size() < 4) {
  146. return std::nullopt;
  147. }
  148. size_t offset = 0;
  149. // Read length prefix
  150. uint32_t length = 0;
  151. for (int i = 0; i < 4; ++i) {
  152. length |= static_cast<uint32_t>(data[offset++]) << (i * 8);
  153. }
  154. if (data.size() < length + 4) {
  155. return std::nullopt;
  156. }
  157. // Verify checksum
  158. uint32_t storedCrc = 0;
  159. for (int i = 0; i < 4; ++i) {
  160. storedCrc |= static_cast<uint32_t>(data[data.size() - 4 + i]) << (i * 8);
  161. }
  162. uint32_t calculatedCrc = crc32::calculate(data.data() + 4, length - 4);
  163. if (storedCrc != calculatedCrc) {
  164. return std::nullopt; // Checksum mismatch
  165. }
  166. WalEntry entry;
  167. // Read sequence
  168. for (int i = 0; i < 8; ++i) {
  169. entry.sequence |= static_cast<uint64_t>(data[offset++]) << (i * 8);
  170. }
  171. // Read timestamp
  172. for (int i = 0; i < 8; ++i) {
  173. entry.timestamp |= static_cast<uint64_t>(data[offset++]) << (i * 8);
  174. }
  175. // Read opType
  176. entry.opType = static_cast<WalOpType>(data[offset++]);
  177. // Read collection
  178. uint16_t collLen = 0;
  179. collLen |= static_cast<uint16_t>(data[offset++]);
  180. collLen |= static_cast<uint16_t>(data[offset++]) << 8;
  181. entry.collection = std::string(reinterpret_cast<const char*>(&data[offset]), collLen);
  182. offset += collLen;
  183. // Read documentId
  184. uint16_t docIdLen = 0;
  185. docIdLen |= static_cast<uint16_t>(data[offset++]);
  186. docIdLen |= static_cast<uint16_t>(data[offset++]) << 8;
  187. entry.documentId = std::string(reinterpret_cast<const char*>(&data[offset]), docIdLen);
  188. offset += docIdLen;
  189. // Read data
  190. uint8_t hasData = data[offset++];
  191. if (hasData) {
  192. uint32_t dataLen = 0;
  193. for (int i = 0; i < 4; ++i) {
  194. dataLen |= static_cast<uint32_t>(data[offset++]) << (i * 8);
  195. }
  196. std::string dataStr(reinterpret_cast<const char*>(&data[offset]), dataLen);
  197. offset += dataLen;
  198. try {
  199. entry.data = smartbotic::db::parse_to_nlohmann(dataStr);
  200. } catch (const nlohmann::json::exception&) {
  201. return std::nullopt;
  202. }
  203. }
  204. // Read collectionOptions
  205. uint8_t hasOpts = data[offset++];
  206. if (hasOpts) {
  207. uint32_t optsLen = 0;
  208. for (int i = 0; i < 4; ++i) {
  209. optsLen |= static_cast<uint32_t>(data[offset++]) << (i * 8);
  210. }
  211. std::string optsStr(reinterpret_cast<const char*>(&data[offset]), optsLen);
  212. offset += optsLen;
  213. try {
  214. entry.collectionOptions = CollectionOptions::fromJson(smartbotic::db::parse_to_nlohmann(optsStr));
  215. } catch (const nlohmann::json::exception&) {
  216. return std::nullopt;
  217. }
  218. }
  219. // Read vectorData (present for VEC_PUT, but flag byte always written)
  220. if (offset < data.size() - 4) { // at least 1 flag byte + 4 checksum bytes remain
  221. uint8_t hasVec = data[offset++];
  222. if (hasVec) {
  223. uint32_t dim = 0;
  224. for (int i = 0; i < 4; ++i) {
  225. dim |= static_cast<uint32_t>(data[offset++]) << (i * 8);
  226. }
  227. if (offset + dim * sizeof(float) > data.size() - 4) {
  228. return std::nullopt; // Truncated vector data
  229. }
  230. std::vector<float> vec(dim);
  231. std::memcpy(vec.data(), &data[offset], dim * sizeof(float));
  232. offset += dim * sizeof(float);
  233. entry.vectorData = std::move(vec);
  234. }
  235. }
  236. // v1.8.0 — origin nodeId (optional trailer). Pre-v1.8 entries don't have
  237. // this section; we leave entry.nodeId empty so the persistence layer can
  238. // treat it as "origin unknown / assume local".
  239. if (offset < data.size() - 4) {
  240. uint8_t hasNode = data[offset++];
  241. if (hasNode) {
  242. if (offset + 2 > data.size() - 4) {
  243. return std::nullopt; // Truncated nodeId length
  244. }
  245. uint16_t nodeLen = static_cast<uint16_t>(data[offset]) |
  246. (static_cast<uint16_t>(data[offset + 1]) << 8);
  247. offset += 2;
  248. if (offset + nodeLen > data.size() - 4) {
  249. return std::nullopt; // Truncated nodeId
  250. }
  251. entry.nodeId.assign(reinterpret_cast<const char*>(&data[offset]), nodeLen);
  252. offset += nodeLen;
  253. }
  254. }
  255. entry.checksum = storedCrc;
  256. return entry;
  257. }
  258. uint32_t WalEntry::calculateChecksum() const {
  259. auto serialized = serialize();
  260. // Exclude length prefix and checksum
  261. return crc32::calculate(serialized.data() + 4, serialized.size() - 8);
  262. }
  263. // ===== WriteAheadLog Implementation =====
  264. WriteAheadLog::WriteAheadLog(Config config)
  265. : config_(std::move(config)) {
  266. }
  267. WriteAheadLog::~WriteAheadLog() {
  268. close();
  269. }
  270. bool WriteAheadLog::open() {
  271. std::lock_guard<std::mutex> lock(writeMutex_);
  272. if (isOpen_.load()) {
  273. return true;
  274. }
  275. // Create WAL directory if it doesn't exist
  276. std::error_code ec;
  277. std::filesystem::create_directories(config_.walDir, ec);
  278. if (ec) {
  279. return false;
  280. }
  281. // Find existing WAL files and determine highest sequence
  282. auto walFiles = getWalFiles();
  283. if (!walFiles.empty()) {
  284. // Read the last file to get the highest sequence
  285. for (auto it = walFiles.rbegin(); it != walFiles.rend(); ++it) {
  286. std::ifstream file(*it, std::ios::binary);
  287. if (!file) continue;
  288. auto header = readHeader(file);
  289. if (!header) continue;
  290. // Read entries to find highest sequence
  291. while (file) {
  292. uint32_t length;
  293. file.read(reinterpret_cast<char*>(&length), 4);
  294. if (!file || length == 0) break;
  295. std::vector<uint8_t> entryData(length + 4);
  296. std::memcpy(entryData.data(), &length, 4);
  297. file.read(reinterpret_cast<char*>(entryData.data() + 4), length);
  298. if (!file) break;
  299. auto entry = WalEntry::deserialize(entryData);
  300. if (entry && entry->sequence > sequence_.load()) {
  301. sequence_.store(entry->sequence);
  302. }
  303. }
  304. }
  305. }
  306. // Open current WAL file for appending
  307. auto currentPath = currentFilePath();
  308. bool fileExists = std::filesystem::exists(currentPath);
  309. currentFile_.open(currentPath, std::ios::binary | std::ios::app);
  310. if (!currentFile_) {
  311. return false;
  312. }
  313. if (!fileExists) {
  314. // Write header for new file
  315. currentFileStartSequence_ = sequence_.load() + 1;
  316. writeHeader();
  317. } else {
  318. // Read existing header
  319. std::ifstream readFile(currentPath, std::ios::binary);
  320. auto header = readHeader(readFile);
  321. if (header) {
  322. currentFileStartSequence_ = header->startSequence;
  323. }
  324. }
  325. currentFileSize_ = std::filesystem::file_size(currentPath);
  326. isOpen_.store(true);
  327. return true;
  328. }
  329. void WriteAheadLog::close() {
  330. std::lock_guard<std::mutex> lock(writeMutex_);
  331. if (!isOpen_.load()) {
  332. return;
  333. }
  334. if (currentFile_.is_open()) {
  335. currentFile_.flush();
  336. currentFile_.close();
  337. }
  338. isOpen_.store(false);
  339. }
  340. void WriteAheadLog::setSequenceFloor(uint64_t seq) {
  341. // CAS loop — monotonically raise sequence_ to at least `seq`.
  342. // Concurrent appends (which `++` the same atomic) are tolerated
  343. // because compare_exchange_weak retries until it observes a
  344. // current value that's already ≥ seq, in which case we leave it.
  345. uint64_t current = sequence_.load();
  346. while (current < seq && !sequence_.compare_exchange_weak(current, seq)) {
  347. // current was updated by compare_exchange_weak — loop and retest.
  348. }
  349. }
  350. uint64_t WriteAheadLog::append(WalEntry entry) {
  351. std::lock_guard<std::mutex> lock(writeMutex_);
  352. if (!isOpen_.load() || !currentFile_.is_open()) {
  353. return 0;
  354. }
  355. // Assign sequence number and timestamp
  356. entry.sequence = ++sequence_;
  357. entry.timestamp = static_cast<uint64_t>(
  358. std::chrono::duration_cast<std::chrono::milliseconds>(
  359. std::chrono::system_clock::now().time_since_epoch()
  360. ).count()
  361. );
  362. // Serialize and write
  363. auto data = entry.serialize();
  364. currentFile_.write(reinterpret_cast<const char*>(data.data()), static_cast<std::streamsize>(data.size()));
  365. currentFileSize_ += data.size();
  366. if (config_.syncOnWrite) {
  367. currentFile_.flush();
  368. sync();
  369. }
  370. // Rotate if file is too large
  371. if (currentFileSize_ >= config_.maxFileSizeBytes) {
  372. rotate();
  373. }
  374. return entry.sequence;
  375. }
  376. void WriteAheadLog::sync() {
  377. if (currentFile_.is_open()) {
  378. currentFile_.flush();
  379. // Note: For true durability, we'd use fsync() here
  380. // std::filesystem doesn't provide this, would need OS-specific code
  381. }
  382. }
  383. uint64_t WriteAheadLog::replay(uint64_t fromSequence, std::function<void(const WalEntry&)> callback) {
  384. auto walFiles = getWalFiles();
  385. uint64_t count = 0;
  386. for (const auto& file : walFiles) {
  387. count += replayFile(file, fromSequence, callback);
  388. }
  389. return count;
  390. }
  391. uint64_t WriteAheadLog::totalSizeBytes() const {
  392. uint64_t total = 0;
  393. auto walFiles = getWalFiles();
  394. for (const auto& file : walFiles) {
  395. std::error_code ec;
  396. total += std::filesystem::file_size(file, ec);
  397. }
  398. return total;
  399. }
  400. void WriteAheadLog::truncateBefore(uint64_t sequence) {
  401. std::lock_guard<std::mutex> lock(writeMutex_);
  402. auto walFiles = getWalFiles();
  403. for (const auto& file : walFiles) {
  404. // Read header to get start sequence
  405. std::ifstream readFile(file, std::ios::binary);
  406. auto header = readHeader(readFile);
  407. readFile.close();
  408. if (header && header->startSequence < sequence) {
  409. // Check if all entries in this file are before the sequence
  410. bool canDelete = true;
  411. uint64_t maxSeqInFile = 0;
  412. std::ifstream scanFile(file, std::ios::binary);
  413. scanFile.seekg(sizeof(Header));
  414. while (scanFile) {
  415. uint32_t length;
  416. scanFile.read(reinterpret_cast<char*>(&length), 4);
  417. if (!scanFile || length == 0) break;
  418. std::vector<uint8_t> entryData(length + 4);
  419. std::memcpy(entryData.data(), &length, 4);
  420. scanFile.read(reinterpret_cast<char*>(entryData.data() + 4), length);
  421. if (!scanFile) break;
  422. auto entry = WalEntry::deserialize(entryData);
  423. if (entry) {
  424. maxSeqInFile = std::max(maxSeqInFile, entry->sequence);
  425. }
  426. }
  427. if (maxSeqInFile < sequence) {
  428. // Safe to delete this file
  429. std::error_code ec;
  430. std::filesystem::remove(file, ec);
  431. }
  432. }
  433. }
  434. }
  435. WalEntry WriteAheadLog::makeInsertEntry(const std::string& collection, const Document& doc,
  436. const std::string& originNodeId) {
  437. WalEntry entry;
  438. entry.opType = WalOpType::INSERT;
  439. entry.collection = collection;
  440. entry.documentId = doc.id;
  441. entry.data = doc.toJson();
  442. entry.nodeId = originNodeId;
  443. return entry;
  444. }
  445. WalEntry WriteAheadLog::makeUpdateEntry(const std::string& collection, const Document& doc,
  446. const std::string& originNodeId) {
  447. WalEntry entry;
  448. entry.opType = WalOpType::UPDATE;
  449. entry.collection = collection;
  450. entry.documentId = doc.id;
  451. entry.data = doc.toJson();
  452. entry.nodeId = originNodeId;
  453. return entry;
  454. }
  455. WalEntry WriteAheadLog::makeDeleteEntry(const std::string& collection, const std::string& id,
  456. const std::string& originNodeId) {
  457. WalEntry entry;
  458. entry.opType = WalOpType::DELETE;
  459. entry.collection = collection;
  460. entry.documentId = id;
  461. entry.nodeId = originNodeId;
  462. return entry;
  463. }
  464. WalEntry WriteAheadLog::makeUpsertEntry(const std::string& collection, const Document& doc,
  465. const std::string& originNodeId) {
  466. WalEntry entry;
  467. entry.opType = WalOpType::UPSERT;
  468. entry.collection = collection;
  469. entry.documentId = doc.id;
  470. entry.data = doc.toJson();
  471. entry.nodeId = originNodeId;
  472. return entry;
  473. }
  474. WalEntry WriteAheadLog::makeCreateCollectionEntry(const std::string& collection, const CollectionOptions& options,
  475. const std::string& originNodeId) {
  476. WalEntry entry;
  477. entry.opType = WalOpType::CREATE_COLLECTION;
  478. entry.collection = collection;
  479. entry.collectionOptions = options;
  480. entry.nodeId = originNodeId;
  481. return entry;
  482. }
  483. WalEntry WriteAheadLog::makeDropCollectionEntry(const std::string& collection,
  484. const std::string& originNodeId) {
  485. WalEntry entry;
  486. entry.opType = WalOpType::DROP_COLLECTION;
  487. entry.collection = collection;
  488. entry.nodeId = originNodeId;
  489. return entry;
  490. }
  491. WalEntry WriteAheadLog::makeVecPutEntry(const std::string& collection,
  492. const std::string& docId, const std::vector<float>& vec,
  493. const std::string& originNodeId) {
  494. WalEntry entry;
  495. entry.opType = WalOpType::VEC_PUT;
  496. entry.collection = collection;
  497. entry.documentId = docId;
  498. entry.vectorData = vec;
  499. entry.nodeId = originNodeId;
  500. return entry;
  501. }
  502. WalEntry WriteAheadLog::makeVecDeleteEntry(const std::string& collection,
  503. const std::string& docId,
  504. const std::string& originNodeId) {
  505. WalEntry entry;
  506. entry.opType = WalOpType::VEC_DELETE;
  507. entry.collection = collection;
  508. entry.documentId = docId;
  509. entry.nodeId = originNodeId;
  510. return entry;
  511. }
  512. std::filesystem::path WriteAheadLog::currentFilePath() const {
  513. return config_.walDir / "wal-current.log";
  514. }
  515. std::vector<std::filesystem::path> WriteAheadLog::getWalFiles() const {
  516. std::vector<std::filesystem::path> files;
  517. std::error_code ec;
  518. for (const auto& entry : std::filesystem::directory_iterator(config_.walDir, ec)) {
  519. if (entry.is_regular_file()) {
  520. const auto& path = entry.path();
  521. if (path.filename().string().starts_with("wal-") &&
  522. path.extension() == ".log") {
  523. files.push_back(path);
  524. }
  525. }
  526. }
  527. // Sort by filename (which includes sequence number for archived files)
  528. std::sort(files.begin(), files.end());
  529. return files;
  530. }
  531. void WriteAheadLog::rotate() {
  532. if (currentFile_.is_open()) {
  533. currentFile_.flush();
  534. currentFile_.close();
  535. }
  536. // Rename current file with sequence range
  537. auto currentPath = currentFilePath();
  538. std::ostringstream oss;
  539. oss << "wal-" << std::setfill('0') << std::setw(16) << currentFileStartSequence_
  540. << "-" << std::setw(16) << sequence_.load() << ".log";
  541. auto archivePath = config_.walDir / oss.str();
  542. std::error_code ec;
  543. std::filesystem::rename(currentPath, archivePath, ec);
  544. // Open new current file
  545. currentFileStartSequence_ = sequence_.load() + 1;
  546. currentFile_.open(currentPath, std::ios::binary | std::ios::app);
  547. if (currentFile_) {
  548. writeHeader();
  549. currentFileSize_ = sizeof(Header);
  550. }
  551. }
  552. std::optional<WriteAheadLog::Header> WriteAheadLog::readHeader(std::ifstream& file) {
  553. Header header;
  554. file.read(reinterpret_cast<char*>(&header), sizeof(Header));
  555. if (!file) {
  556. return std::nullopt;
  557. }
  558. // Verify magic
  559. if (std::memcmp(header.magic, "CALWAL01", 8) != 0) {
  560. return std::nullopt;
  561. }
  562. return header;
  563. }
  564. void WriteAheadLog::writeHeader() {
  565. Header header;
  566. std::memcpy(header.magic, "CALWAL01", 8);
  567. header.startSequence = currentFileStartSequence_;
  568. currentFile_.write(reinterpret_cast<const char*>(&header), sizeof(Header));
  569. currentFile_.flush();
  570. }
  571. uint64_t WriteAheadLog::replayFile(const std::filesystem::path& path, uint64_t fromSequence,
  572. const std::function<void(const WalEntry&)>& callback) {
  573. std::ifstream file(path, std::ios::binary);
  574. if (!file) {
  575. return 0;
  576. }
  577. auto header = readHeader(file);
  578. if (!header) {
  579. return 0;
  580. }
  581. uint64_t count = 0;
  582. while (file) {
  583. // Read entry length
  584. uint32_t length;
  585. file.read(reinterpret_cast<char*>(&length), 4);
  586. if (!file || length == 0) break;
  587. // Read entry data
  588. std::vector<uint8_t> entryData(length + 4);
  589. std::memcpy(entryData.data(), &length, 4);
  590. file.read(reinterpret_cast<char*>(entryData.data() + 4), length);
  591. if (!file) break;
  592. // Deserialize and invoke callback
  593. auto entry = WalEntry::deserialize(entryData);
  594. if (entry && entry->sequence > fromSequence) {
  595. callback(*entry);
  596. count++;
  597. }
  598. }
  599. return count;
  600. }
  601. } // namespace smartbotic::database