/
smychkov
/
SStorage
Обзор
Документация
Войти
/
smychkov
/
SStorage
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
tests/test_compaction.cpp
224 строки
7 KB
Андрей Смычков
feat: LSM tree with levels, flush, compaction, recovery, block cache
25 апр 2026, 09:14
25 апр 2026, 09:14
4631f83
Код
Авторство
О чём код?
//============================================================================ // Тесты для compaction: dedupe, tombstone elimination, merge //============================================================================ #include "../src/lsm/compaction.hpp" #include "../src/sstable/sstable_writer.hpp" #include "../src/sstable/sstable_reader.hpp" #include <cstdio> #include <cstdlib> #include <filesystem> #include <iostream> #include <memory> #include <string> #include <sys/stat.h> #include <unistd.h> using namespace sstorage; static int g_passed = 0; static int g_failed = 0; #define CHECK(cond) do { \ if (cond) { ++g_passed; } \ else { ++g_failed; std::cerr << "FAIL: " #cond " at line " << __LINE__ << "\n"; } \ } while (0) static std::string makeTempDir(const std::string& prefix) { std::string p = "/tmp/sstorage_compact_" + prefix + "_" + std::to_string(::getpid()) + "_" + std::to_string(rand()); ::mkdir(p.c_str(), 0755); return p; } static void cleanupDir(const std::string& dir) { std::error_code ec; std::filesystem::remove_all(dir, ec); } // Создать SSTable с набором записей, вернуть SSTableReader static SSTablePtr createSST(const std::string& dir, int level, uint64_t seq, const std::vector<Record>& records) { char buf[128]; std::snprintf(buf, sizeof(buf), "%s/sst_L%d_%010llu.sst", dir.c_str(), level, (unsigned long long)seq); std::string path = buf; SSTableWriter w(path, 512); w.open(); for (const auto& r : records) w.add(r); w.finish(); auto reader = std::make_shared<SSTableReader>(path); reader->open(); return reader; } int main() { // 1. Merge двух непересекающихся SSTable { auto dir = makeTempDir("merge_disjoint"); auto a = createSST(dir, 0, 1, { Record("a", "1", 1), Record("b", "2", 2) }); auto b = createSST(dir, 0, 2, { Record("c", "3", 3), Record("d", "4", 4) }); uint64_t nextSeq = 100; std::vector<std::string> newFiles; CompactionOptions opts; opts.targetFileSize = 10 * 1024 * 1024; bool ok = runCompaction( {a, b}, 1, opts, [&](int lvl, uint64_t s) { char buf[128]; std::snprintf(buf, sizeof(buf), "%s/sst_L%d_%010llu.sst", dir.c_str(), lvl, (unsigned long long)s); return std::string(buf); }, [&]() { return nextSeq++; }, [](const std::string&) { return false; }, newFiles); CHECK(ok); CHECK(newFiles.size() == 1); SSTableReader r(newFiles[0]); CHECK(r.open()); CHECK(r.numRecords() == 4); CHECK(r.find("a").has_value()); CHECK(r.find("d").has_value()); cleanupDir(dir); } // 2. Dedupe: более свежая запись (higher seqNo) выигрывает { auto dir = makeTempDir("dedupe"); auto old_sst = createSST(dir, 1, 1, {Record("k", "old", 1)}); auto new_sst = createSST(dir, 0, 2, {Record("k", "new", 10)}); uint64_t nextSeq = 100; std::vector<std::string> newFiles; runCompaction( {new_sst, old_sst}, 1, CompactionOptions{}, [&](int lvl, uint64_t s) { char buf[128]; std::snprintf(buf, sizeof(buf), "%s/sst_L%d_%010llu.sst", dir.c_str(), lvl, (unsigned long long)s); return std::string(buf); }, [&]() { return nextSeq++; }, [](const std::string&) { return false; }, newFiles); CHECK(newFiles.size() == 1); SSTableReader r(newFiles[0]); r.open(); auto rec = r.find("k"); CHECK(rec.has_value()); CHECK(rec->value() == "new"); // самая свежая победила CHECK(rec->seqNo() == 10); cleanupDir(dir); } // 3. Tombstone elimination на последнем уровне { auto dir = makeTempDir("tomb_elim"); auto sst = createSST(dir, 1, 1, { Record("a", "va", 1), Record::makeTombstone("b", 2), Record("c", "vc", 3), }); uint64_t nextSeq = 100; std::vector<std::string> newFiles; runCompaction( {sst}, 2, CompactionOptions{}, [&](int lvl, uint64_t s) { char buf[128]; std::snprintf(buf, sizeof(buf), "%s/sst_L%d_%010llu.sst", dir.c_str(), lvl, (unsigned long long)s); return std::string(buf); }, [&]() { return nextSeq++; }, // Всегда можно удалять (имитируем "ниже нет") [](const std::string&) { return true; }, newFiles); CHECK(newFiles.size() == 1); SSTableReader r(newFiles[0]); r.open(); CHECK(r.numRecords() == 2); // tombstone удалён CHECK(r.find("a").has_value()); CHECK(!r.find("b").has_value()); CHECK(r.find("c").has_value()); cleanupDir(dir); } // 4. Tombstone сохраняется если shouldDropTombstone вернул false { auto dir = makeTempDir("tomb_keep"); auto sst = createSST(dir, 0, 1, { Record::makeTombstone("deleted", 1), }); uint64_t nextSeq = 100; std::vector<std::string> newFiles; runCompaction( {sst}, 1, CompactionOptions{}, [&](int lvl, uint64_t s) { char buf[128]; std::snprintf(buf, sizeof(buf), "%s/sst_L%d_%010llu.sst", dir.c_str(), lvl, (unsigned long long)s); return std::string(buf); }, [&]() { return nextSeq++; }, [](const std::string&) { return false; }, // НЕ удалять newFiles); CHECK(newFiles.size() == 1); SSTableReader r(newFiles[0]); r.open(); auto rec = r.find("deleted"); CHECK(rec.has_value()); CHECK(rec->isTombstone()); cleanupDir(dir); } // 5. MergingIterator на трёх потоках { std::vector<std::vector<Record>> streams = { {Record("a", "v", 1), Record("c", "v", 3)}, {Record("b", "v", 2), Record("d", "v", 4)}, {Record("a", "v2", 10)}, // более свежая версия "a" }; MergingIterator it(std::move(streams)); // Ожидаемый порядок: (a, seqNo=10), (a, seqNo=1), (b), (c), (d) CHECK(it.valid()); CHECK(it.current().key() == "a"); CHECK(it.current().seqNo() == 10); it.next(); CHECK(it.valid()); CHECK(it.current().key() == "a"); CHECK(it.current().seqNo() == 1); it.next(); CHECK(it.current().key() == "b"); it.next(); CHECK(it.current().key() == "c"); it.next(); CHECK(it.current().key() == "d"); it.next(); CHECK(!it.valid()); } std::cout << "test_compaction: passed=" << g_passed << " failed=" << g_failed << "\n"; return g_failed == 0 ? 0 : 1; }