almost done db
This commit is contained in:
parent
83a161d55d
commit
b6cefc8536
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[submodule "utils/toml"]
|
||||||
|
path = utils/toml
|
||||||
|
url = https://github.com/marzer/tomlplusplus.git
|
||||||
|
[submodule "utils/hash/xxhash"]
|
||||||
|
path = utils/hash/xxhash
|
||||||
|
url = https://github.com/Cyan4973/xxHash.git
|
11
core/Memtable.cpp
Normal file
11
core/Memtable.cpp
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//
|
||||||
|
// Created by Kirill Zhukov on 20.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Memtable.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace usub::shared_storage
|
||||||
|
{
|
||||||
|
} // shared_storage
|
||||||
|
// usub
|
140
core/Memtable.h
Normal file
140
core/Memtable.h
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
//
|
||||||
|
// Created by Kirill Zhukov on 20.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef MEMTABLE_H
|
||||||
|
#define MEMTABLE_H
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <optional>
|
||||||
|
#include <mutex>
|
||||||
|
#include "utils/io/Wal.h"
|
||||||
|
|
||||||
|
namespace usub::shared_storage
|
||||||
|
{
|
||||||
|
template <typename SkipList>
|
||||||
|
class MemTableManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MemTableManager(const std::string& wal_file, size_t max_size);
|
||||||
|
|
||||||
|
~MemTableManager();
|
||||||
|
|
||||||
|
void put(const typename SkipList::key_type& key, const typename SkipList::value_type& value);
|
||||||
|
|
||||||
|
void remove(const typename SkipList::key_type& key);
|
||||||
|
|
||||||
|
std::optional<typename SkipList::value_type> get(const typename SkipList::key_type& key);
|
||||||
|
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
void flush_batch();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::atomic<SkipList*> active_memtable;
|
||||||
|
utils::WAL wal;
|
||||||
|
size_t max_memtable_size;
|
||||||
|
std::mutex batch_mutex;
|
||||||
|
std::vector<std::pair<typename SkipList::key_type, typename SkipList::value_type>> write_batch;
|
||||||
|
std::atomic<bool> flushing{false};
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t estimate_memtable_size() const;
|
||||||
|
|
||||||
|
size_t estimate_batch_size() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename SkipList>
|
||||||
|
MemTableManager<SkipList>::MemTableManager(const std::string& wal_file, size_t max_size) : wal(wal_file),
|
||||||
|
max_memtable_size(max_size)
|
||||||
|
{
|
||||||
|
this->active_memtable.store(new SkipList());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SkipList>
|
||||||
|
MemTableManager<SkipList>::~MemTableManager()
|
||||||
|
{
|
||||||
|
delete this->active_memtable.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SkipList>
|
||||||
|
void MemTableManager<SkipList>::put(const typename SkipList::key_type& key,
|
||||||
|
const typename SkipList::value_type& value)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(batch_mutex);
|
||||||
|
write_batch.emplace_back(key, value);
|
||||||
|
}
|
||||||
|
if (estimate_batch_size() >= 64)
|
||||||
|
{
|
||||||
|
flush_batch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SkipList>
|
||||||
|
void MemTableManager<SkipList>::remove(const typename SkipList::key_type& key)
|
||||||
|
{
|
||||||
|
this->wal.write_delete(key);
|
||||||
|
this->active_memtable.load()->erase(key);
|
||||||
|
if (estimate_memtable_size() > this->max_memtable_size)
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SkipList>
|
||||||
|
std::optional<typename SkipList::value_type> MemTableManager<SkipList>::get(const typename SkipList::key_type& key)
|
||||||
|
{
|
||||||
|
return this->active_memtable.load()->find(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SkipList>
|
||||||
|
void MemTableManager<SkipList>::flush()
|
||||||
|
{
|
||||||
|
if (this->flushing.exchange(true)) return;
|
||||||
|
|
||||||
|
auto old_memtable = this->active_memtable.exchange(new SkipList());
|
||||||
|
std::string filename = "sstable_" + std::to_string(std::time(nullptr)) + ".dat";
|
||||||
|
write_sstable_v2(*old_memtable, filename);
|
||||||
|
delete old_memtable;
|
||||||
|
this->wal.close();
|
||||||
|
this->flushing.store(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SkipList>
|
||||||
|
void MemTableManager<SkipList>::flush_batch()
|
||||||
|
{
|
||||||
|
std::vector<std::pair<typename SkipList::key_type, typename SkipList::value_type>> local_batch;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(batch_mutex);
|
||||||
|
local_batch.swap(write_batch);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [key, value] : local_batch)
|
||||||
|
{
|
||||||
|
wal.write_put(key, value);
|
||||||
|
active_memtable.load()->insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (estimate_memtable_size() > max_memtable_size)
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SkipList>
|
||||||
|
size_t MemTableManager<SkipList>::estimate_memtable_size() const
|
||||||
|
{
|
||||||
|
// For simplicity: count the number of elements * average size
|
||||||
|
return this->active_memtable.load()->unsafe_size() * 128; // The error is acceptable
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SkipList>
|
||||||
|
size_t MemTableManager<SkipList>::estimate_batch_size() const
|
||||||
|
{
|
||||||
|
return write_batch.size();
|
||||||
|
}
|
||||||
|
} // shared_storage
|
||||||
|
// usub
|
||||||
|
|
||||||
|
#endif //MEMTABLE_H
|
5
utils/datastructures/LFSkipList.cpp
Normal file
5
utils/datastructures/LFSkipList.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//
|
||||||
|
// Created by Kirill Zhukov on 20.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "LFSkipList.h"
|
289
utils/datastructures/LFSkipList.h
Normal file
289
utils/datastructures/LFSkipList.h
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
#ifndef LFSKIPLIST_H
|
||||||
|
#define LFSKIPLIST_H
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <array>
|
||||||
|
#include <random>
|
||||||
|
#include <optional>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <cassert>
|
||||||
|
#include <thread>
|
||||||
|
#include <cstdint>
|
||||||
|
#include "utils/io/VersionManager.h"
|
||||||
|
|
||||||
|
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86)
|
||||||
|
#include <immintrin.h>
|
||||||
|
inline void cpu_relax() noexcept { _mm_pause(); }
|
||||||
|
inline void prefetch_for_read(const void* ptr) noexcept {
|
||||||
|
_mm_prefetch(reinterpret_cast<const char*>(ptr), _MM_HINT_T0);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
inline void cpu_relax() noexcept
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void prefetch_for_read(const void*) noexcept
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace usub::utils
|
||||||
|
{
|
||||||
|
constexpr int MAX_LEVEL = 16;
|
||||||
|
|
||||||
|
inline int random_level()
|
||||||
|
{
|
||||||
|
static thread_local std::mt19937 rng(std::random_device{}());
|
||||||
|
static thread_local std::uniform_int_distribution<int> dist(0, 1);
|
||||||
|
|
||||||
|
int lvl = 1;
|
||||||
|
while (lvl < MAX_LEVEL && dist(rng)) ++lvl;
|
||||||
|
return lvl;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Key, typename Value>
|
||||||
|
class LFSkipList
|
||||||
|
{
|
||||||
|
struct Node
|
||||||
|
{
|
||||||
|
Key key;
|
||||||
|
Value value;
|
||||||
|
int topLevel;
|
||||||
|
bool is_tombstone;
|
||||||
|
uint64_t version;
|
||||||
|
std::array<std::atomic<Node*>, MAX_LEVEL> next;
|
||||||
|
std::atomic<bool> marked{false};
|
||||||
|
|
||||||
|
Node(const Key& k, const Value& v, int level, bool tombstone, uint64_t ver)
|
||||||
|
: key(k), value(v), topLevel(level), is_tombstone(tombstone), version(ver)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < MAX_LEVEL; ++i)
|
||||||
|
next[i].store(nullptr, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Node* head;
|
||||||
|
usub::utils::VersionManager& version_manager;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using key_type = Key;
|
||||||
|
using value_type = Value;
|
||||||
|
|
||||||
|
LFSkipList(usub::utils::VersionManager& vm)
|
||||||
|
: version_manager(vm)
|
||||||
|
{
|
||||||
|
head = new Node(std::numeric_limits<Key>::min(), Value{}, MAX_LEVEL, false, version_manager.next_version());
|
||||||
|
}
|
||||||
|
|
||||||
|
~LFSkipList()
|
||||||
|
{
|
||||||
|
Node* curr = head;
|
||||||
|
while (curr)
|
||||||
|
{
|
||||||
|
Node* next = next_node(curr->next[0].load(std::memory_order_relaxed));
|
||||||
|
delete curr;
|
||||||
|
curr = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool insert(const Key& key, const Value& value)
|
||||||
|
{
|
||||||
|
return insert_internal(key, value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool erase(const Key& key)
|
||||||
|
{
|
||||||
|
return insert_internal(key, Value{}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Value> find(const Key& key) const
|
||||||
|
{
|
||||||
|
Node* best = nullptr;
|
||||||
|
Node* node = head->next[0].load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
while (node)
|
||||||
|
{
|
||||||
|
prefetch_for_read(node);
|
||||||
|
if (node->key == key && !node->marked.load(std::memory_order_acquire))
|
||||||
|
{
|
||||||
|
if (!best || node->version > best->version)
|
||||||
|
{
|
||||||
|
best = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node = next_node(node->next[0].load(std::memory_order_acquire));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best && !best->is_tombstone)
|
||||||
|
return best->value;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
void for_each(F&& func) const
|
||||||
|
{
|
||||||
|
Node* node = head->next[0].load(std::memory_order_acquire);
|
||||||
|
while (node)
|
||||||
|
{
|
||||||
|
prefetch_for_read(node);
|
||||||
|
if (!node->marked.load(std::memory_order_acquire) && !node->is_tombstone)
|
||||||
|
{
|
||||||
|
func(node->key, node->value);
|
||||||
|
}
|
||||||
|
node = next_node(node->next[0].load(std::memory_order_acquire));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
void for_each_raw(F&& func) const
|
||||||
|
{
|
||||||
|
Node* node = head->next[0].load(std::memory_order_acquire);
|
||||||
|
while (node)
|
||||||
|
{
|
||||||
|
prefetch_for_read(node);
|
||||||
|
if (!node->marked.load(std::memory_order_acquire))
|
||||||
|
{
|
||||||
|
func(node->key, node->value, node->is_tombstone, node->version);
|
||||||
|
}
|
||||||
|
node = next_node(node->next[0].load(std::memory_order_acquire));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool insert_raw(const Key& key, const Value& value, bool tombstone, uint64_t version)
|
||||||
|
{
|
||||||
|
Node* preds[MAX_LEVEL]{};
|
||||||
|
Node* succs[MAX_LEVEL]{};
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
bool found = find_internal(key, preds, succs);
|
||||||
|
|
||||||
|
int topLevel = random_level();
|
||||||
|
Node* newNode = new Node(key, value, topLevel, tombstone, version);
|
||||||
|
|
||||||
|
for (int i = 0; i < topLevel; ++i)
|
||||||
|
newNode->next[i].store(succs[i], std::memory_order_relaxed);
|
||||||
|
|
||||||
|
if (!preds[0]->next[0].compare_exchange_strong(
|
||||||
|
succs[0], newNode, std::memory_order_acq_rel, std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
delete newNode;
|
||||||
|
cpu_relax();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < topLevel; ++i)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (preds[i]->next[i].compare_exchange_strong(
|
||||||
|
succs[i], newNode, std::memory_order_acq_rel, std::memory_order_relaxed))
|
||||||
|
break;
|
||||||
|
cpu_relax();
|
||||||
|
find_internal(key, preds, succs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !found || tombstone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] size_t unsafe_size() const
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
Node* node = head->next[0].load(std::memory_order_relaxed);
|
||||||
|
while (node)
|
||||||
|
{
|
||||||
|
if (!node->marked.load(std::memory_order_relaxed) && !node->is_tombstone)
|
||||||
|
++count;
|
||||||
|
node = next_node(node->next[0].load(std::memory_order_relaxed));
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool insert_internal(const Key& key, const Value& value, bool tombstone)
|
||||||
|
{
|
||||||
|
Node* preds[MAX_LEVEL]{};
|
||||||
|
Node* succs[MAX_LEVEL]{};
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
bool found = find_internal(key, preds, succs);
|
||||||
|
|
||||||
|
int topLevel = random_level();
|
||||||
|
Node* newNode = new Node(key, value, topLevel, tombstone, version_manager.next_version());
|
||||||
|
|
||||||
|
for (int i = 0; i < topLevel; ++i)
|
||||||
|
newNode->next[i].store(succs[i], std::memory_order_relaxed);
|
||||||
|
|
||||||
|
if (!preds[0]->next[0].compare_exchange_strong(
|
||||||
|
succs[0], newNode, std::memory_order_acq_rel, std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
delete newNode;
|
||||||
|
cpu_relax();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < topLevel; ++i)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (preds[i]->next[i].compare_exchange_strong(
|
||||||
|
succs[i], newNode, std::memory_order_acq_rel, std::memory_order_relaxed))
|
||||||
|
break;
|
||||||
|
cpu_relax();
|
||||||
|
find_internal(key, preds, succs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !found || tombstone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool find_internal(const Key& key, Node** preds, Node** succs) const
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
Node* pred = head;
|
||||||
|
|
||||||
|
for (int level = MAX_LEVEL - 1; level >= 0; --level)
|
||||||
|
{
|
||||||
|
Node* curr = pred->next[level].load(std::memory_order_acquire);
|
||||||
|
while (curr)
|
||||||
|
{
|
||||||
|
prefetch_for_read(curr);
|
||||||
|
Node* next = curr->next[level].load(std::memory_order_acquire);
|
||||||
|
if (reinterpret_cast<uintptr_t>(next) & 1)
|
||||||
|
{
|
||||||
|
curr = next_node(next);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (curr->key < key)
|
||||||
|
{
|
||||||
|
pred = curr;
|
||||||
|
curr = next;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
preds[level] = pred;
|
||||||
|
succs[level] = curr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (succs[0] && succs[0]->key == key && !succs[0]->marked.load(std::memory_order_acquire))
|
||||||
|
found = true;
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Node* next_node(Node* n)
|
||||||
|
{
|
||||||
|
return reinterpret_cast<Node*>(reinterpret_cast<uintptr_t>(n) & ~uintptr_t(1));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace usub::utils
|
||||||
|
|
||||||
|
#endif //LFSKIPLIST_H
|
53
utils/hash/Hash128.h
Normal file
53
utils/hash/Hash128.h
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// Created by Kirill Zhukov on 20.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HASHSTRUCTURE_H
|
||||||
|
#define HASHSTRUCTURE_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "utils/hash/xxhash/xxhash.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace usub::utils
|
||||||
|
{
|
||||||
|
struct alignas(16) Hash128
|
||||||
|
{
|
||||||
|
uint64_t low;
|
||||||
|
uint64_t high;
|
||||||
|
|
||||||
|
Hash128() : low(0), high(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const Hash128& other) const
|
||||||
|
{
|
||||||
|
return (this->high == other.high) ? (this->low < other.low) : (this->high < other.high);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Hash128& other) const
|
||||||
|
{
|
||||||
|
return this->low == other.low && this->high == other.high;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline Hash128 compute_hash128(const void* data, size_t length)
|
||||||
|
{
|
||||||
|
Hash128 h;
|
||||||
|
XXH128_hash_t hash = XXH3_128bits(data, length);
|
||||||
|
h.low = hash.low64;
|
||||||
|
h.high = hash.high64;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Hash128 compute_hash128(const std::string& s)
|
||||||
|
{
|
||||||
|
return compute_hash128(s.data(), s.size());
|
||||||
|
}
|
||||||
|
} // namespace usub::utils
|
||||||
|
|
||||||
|
|
||||||
|
#endif //HASHSTRUCTURE_H
|
1
utils/hash/xxhash
Submodule
1
utils/hash/xxhash
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 953a09abc39096da9e216b6eb0002c681cdc1199
|
77
utils/io/Compactor.cpp
Normal file
77
utils/io/Compactor.cpp
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
//
|
||||||
|
// Created by Kirill Zhukov on 20.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Compactor.h"
|
||||||
|
|
||||||
|
namespace usub::utils
|
||||||
|
{
|
||||||
|
Compactor::Compactor(VersionManager& vm) : version_manager(vm)
|
||||||
|
{
|
||||||
|
this->background_thread = std::thread([this] { this->run(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compactor::add_sstable_l0(const std::string& filename)
|
||||||
|
|
||||||
|
{
|
||||||
|
this->level0_files.push_back(filename);
|
||||||
|
if (this->level0_files.size() >= 4)
|
||||||
|
{
|
||||||
|
compact_level0();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compactor::compact_level0()
|
||||||
|
{
|
||||||
|
if (this->level0_files.size() < 2) return;
|
||||||
|
|
||||||
|
std::string merged_filename = "L1_" + std::to_string(std::time(nullptr)) + ".dat";
|
||||||
|
|
||||||
|
LFSkipList<std::string, std::string> merged(this->version_manager);
|
||||||
|
for (const auto& file : this->level0_files)
|
||||||
|
{
|
||||||
|
read_sstable_with_mmap(merged, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
write_sstable_with_index(merged, merged_filename);
|
||||||
|
|
||||||
|
this->level1_files.push_back(merged_filename);
|
||||||
|
for (const auto& file : this->level0_files)
|
||||||
|
{
|
||||||
|
::remove(file.c_str());
|
||||||
|
}
|
||||||
|
this->level0_files.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compactor::compact_level1()
|
||||||
|
{
|
||||||
|
if (this->level1_files.size() < 4) return;
|
||||||
|
|
||||||
|
std::string merged_filename = "L2_" + std::to_string(std::time(nullptr)) + ".dat";
|
||||||
|
|
||||||
|
LFSkipList<std::string, std::string> merged(this->version_manager);
|
||||||
|
for (const auto& file : this->level1_files)
|
||||||
|
{
|
||||||
|
read_sstable_with_mmap(merged, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
write_sstable_with_index(merged, merged_filename);
|
||||||
|
|
||||||
|
this->level2_files.push_back(merged_filename);
|
||||||
|
for (const auto& file : this->level1_files)
|
||||||
|
{
|
||||||
|
::remove(file.c_str());
|
||||||
|
}
|
||||||
|
this->level1_files.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compactor::run()
|
||||||
|
{
|
||||||
|
while (this->running.load())
|
||||||
|
{
|
||||||
|
compact_level0();
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // utils
|
||||||
|
// usub
|
42
utils/io/Compactor.h
Normal file
42
utils/io/Compactor.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// Created by Kirill Zhukov on 20.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef COMPACTOR_H
|
||||||
|
#define COMPACTOR_H
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include "utils/datastructures/LFSkipList.h"
|
||||||
|
#include "utils/io/SSTableIO.h"
|
||||||
|
|
||||||
|
namespace usub::utils
|
||||||
|
{
|
||||||
|
class Compactor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit Compactor(VersionManager& vm);
|
||||||
|
|
||||||
|
void add_sstable_l0(const std::string& filename);
|
||||||
|
|
||||||
|
|
||||||
|
void run();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void compact_level0();
|
||||||
|
|
||||||
|
void compact_level1();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::string> level0_files;
|
||||||
|
std::vector<std::string> level1_files;
|
||||||
|
std::vector<std::string> level2_files;
|
||||||
|
std::atomic<bool> running{true};
|
||||||
|
std::thread background_thread;
|
||||||
|
VersionManager& version_manager;
|
||||||
|
}; // utils
|
||||||
|
} // usub
|
||||||
|
|
||||||
|
|
||||||
|
#endif //COMPACTOR_H
|
69
utils/io/RecoveryLog.cpp
Normal file
69
utils/io/RecoveryLog.cpp
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
//
|
||||||
|
// Created by Kirill Zhukov on 20.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "RecoveryLog.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace usub::utils
|
||||||
|
{
|
||||||
|
RecoveryLog::RecoveryLog(const std::string& dbname)
|
||||||
|
: db_name(dbname),
|
||||||
|
metadata_dir("metadata/" + db_name + "/"),
|
||||||
|
log_file(metadata_dir + "recovery.log")
|
||||||
|
{
|
||||||
|
ensure_metadata_dir();
|
||||||
|
this->log_out.open(this->log_file, std::ios::binary | std::ios::app);
|
||||||
|
if (!this->log_out.is_open())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to open recovery log for " + this->db_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RecoveryLog::~RecoveryLog()
|
||||||
|
{
|
||||||
|
if (this->log_out.is_open())
|
||||||
|
{
|
||||||
|
this->log_out.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecoveryLog::log_put(const std::string& key, const std::string& value)
|
||||||
|
{
|
||||||
|
uint8_t op = 0;
|
||||||
|
uint32_t key_len = key.size();
|
||||||
|
uint32_t value_len = value.size();
|
||||||
|
this->log_out.write(reinterpret_cast<const char*>(&op), sizeof(op));
|
||||||
|
this->log_out.write(reinterpret_cast<const char*>(&key_len), sizeof(key_len));
|
||||||
|
this->log_out.write(key.data(), key_len);
|
||||||
|
this->log_out.write(reinterpret_cast<const char*>(&value_len), sizeof(value_len));
|
||||||
|
this->log_out.write(value.data(), value_len);
|
||||||
|
this->log_out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecoveryLog::log_delete(const std::string& key)
|
||||||
|
{
|
||||||
|
uint8_t op = 1;
|
||||||
|
uint32_t key_len = key.size();
|
||||||
|
this->log_out.write(reinterpret_cast<const char*>(&op), sizeof(op));
|
||||||
|
this->log_out.write(reinterpret_cast<const char*>(&key_len), sizeof(key_len));
|
||||||
|
this->log_out.write(key.data(), key_len);
|
||||||
|
this->log_out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecoveryLog::ensure_metadata_dir()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!std::filesystem::exists(this->metadata_dir))
|
||||||
|
{
|
||||||
|
std::filesystem::create_directories(this->metadata_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to create metadata dir: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // utils
|
||||||
|
// usub
|
37
utils/io/RecoveryLog.h
Normal file
37
utils/io/RecoveryLog.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// Created by Kirill Zhukov on 20.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef RECOVERYLOG_H
|
||||||
|
#define RECOVERYLOG_H
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace usub::utils
|
||||||
|
{
|
||||||
|
class RecoveryLog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit RecoveryLog(const std::string& dbname);
|
||||||
|
|
||||||
|
~RecoveryLog();
|
||||||
|
|
||||||
|
void log_put(const std::string& key, const std::string& value);
|
||||||
|
|
||||||
|
void log_delete(const std::string& key);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ensure_metadata_dir();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string db_name;
|
||||||
|
std::string metadata_dir;
|
||||||
|
std::string log_file;
|
||||||
|
std::ofstream log_out;
|
||||||
|
};
|
||||||
|
} // utils
|
||||||
|
// usub
|
||||||
|
|
||||||
|
#endif //RECOVERYLOG_H
|
5
utils/io/SSTableIO.cpp
Normal file
5
utils/io/SSTableIO.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//
|
||||||
|
// Created by Kirill Zhukov on 20.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "SSTableIO.h"
|
476
utils/io/SSTableIO.h
Normal file
476
utils/io/SSTableIO.h
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
//
|
||||||
|
// Created by Kirill Zhukov on 20.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef MEMTABLE_H
|
||||||
|
#define MEMTABLE_H
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <utility>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
namespace usub::utils
|
||||||
|
{
|
||||||
|
template <typename SkipList>
|
||||||
|
void write_sstable(const SkipList& memtable, const std::string& filename)
|
||||||
|
{
|
||||||
|
int fd = ::open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
|
||||||
|
if (fd < 0) throw std::runtime_error("Failed to open SSTable");
|
||||||
|
|
||||||
|
FILE* file = ::fdopen(fd, "wb");
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
::close(fd);
|
||||||
|
throw std::runtime_error("Failed to fdopen SSTable");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t current_offset = 0;
|
||||||
|
std::vector<std::pair<typename SkipList::key_type, uint64_t>> index_entries;
|
||||||
|
|
||||||
|
memtable.for_each([&](const auto& key, const auto& value)
|
||||||
|
{
|
||||||
|
uint8_t is_tombstone = 0;
|
||||||
|
uint64_t version = 0;
|
||||||
|
|
||||||
|
if constexpr (requires { value.is_tombstone; })
|
||||||
|
{
|
||||||
|
is_tombstone = value.is_tombstone ? 1 : 0;
|
||||||
|
version = value.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t key_len = sizeof(key);
|
||||||
|
uint32_t value_len = value.size();
|
||||||
|
|
||||||
|
::fwrite(&key_len, sizeof(key_len), 1, file);
|
||||||
|
::fwrite(&key, key_len, 1, file);
|
||||||
|
::fwrite(&value_len, sizeof(value_len), 1, file);
|
||||||
|
::fwrite(value.data(), value_len, 1, file);
|
||||||
|
::fwrite(&is_tombstone, sizeof(is_tombstone), 1, file);
|
||||||
|
::fwrite(&version, sizeof(version), 1, file);
|
||||||
|
|
||||||
|
index_entries.emplace_back(key, current_offset);
|
||||||
|
current_offset += sizeof(key_len) + key_len + sizeof(value_len) + value_len + sizeof(is_tombstone) + sizeof(
|
||||||
|
version);
|
||||||
|
});
|
||||||
|
|
||||||
|
uint64_t index_offset = current_offset;
|
||||||
|
|
||||||
|
for (const auto& [key, offset] : index_entries)
|
||||||
|
{
|
||||||
|
uint32_t key_len = sizeof(key);
|
||||||
|
::fwrite(&key_len, sizeof(key_len), 1, file);
|
||||||
|
::fwrite(&key, key_len, 1, file);
|
||||||
|
::fwrite(&offset, sizeof(offset), 1, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
::fwrite(&index_offset, sizeof(index_offset), 1, file);
|
||||||
|
|
||||||
|
::fflush(file);
|
||||||
|
::fsync(fd);
|
||||||
|
::fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template <typename SkipList>
|
||||||
|
void read_sstable(SkipList& memtable, const std::string& filename)
|
||||||
|
{
|
||||||
|
std::ifstream sstable(filename, std::ios::binary);
|
||||||
|
if (!sstable.is_open())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to open file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sstable.peek() != EOF)
|
||||||
|
{
|
||||||
|
uint32_t key_len = 0;
|
||||||
|
sstable.read(reinterpret_cast<char*>(&key_len), sizeof(key_len));
|
||||||
|
|
||||||
|
if (key_len != sizeof(typename SkipList::key_type))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Key size mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
typename SkipList::key_type key{};
|
||||||
|
sstable.read(reinterpret_cast<char*>(&key), key_len);
|
||||||
|
|
||||||
|
uint32_t value_len = 0;
|
||||||
|
sstable.read(reinterpret_cast<char*>(&value_len), sizeof(value_len));
|
||||||
|
|
||||||
|
std::string value(value_len, '\0');
|
||||||
|
sstable.read(value.data(), value_len);
|
||||||
|
|
||||||
|
memtable.insert(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SkipList>
|
||||||
|
void write_sstable_with_index(const SkipList& memtable, const std::string& filename)
|
||||||
|
{
|
||||||
|
int fd = ::open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to open file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* file = ::fdopen(fd, "wb");
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
::close(fd);
|
||||||
|
throw std::runtime_error("Failed to fdopen file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<typename SkipList::key_type, uint64_t>> index_entries;
|
||||||
|
uint64_t current_offset = 0;
|
||||||
|
|
||||||
|
memtable.for_each_raw([&](const auto& key, const auto& value, bool is_tombstone, uint64_t version)
|
||||||
|
{
|
||||||
|
uint32_t key_len = key.size();
|
||||||
|
uint32_t value_len = value.size();
|
||||||
|
uint8_t tombstone_flag = is_tombstone ? 1 : 0;
|
||||||
|
|
||||||
|
::fwrite(&key_len, sizeof(key_len), 1, file);
|
||||||
|
::fwrite(key.data(), key_len, 1, file);
|
||||||
|
::fwrite(&value_len, sizeof(value_len), 1, file);
|
||||||
|
::fwrite(value.data(), value_len, 1, file);
|
||||||
|
::fwrite(&tombstone_flag, sizeof(tombstone_flag), 1, file);
|
||||||
|
::fwrite(&version, sizeof(version), 1, file);
|
||||||
|
|
||||||
|
index_entries.emplace_back(key, current_offset);
|
||||||
|
|
||||||
|
current_offset += sizeof(key_len) + key_len + sizeof(value_len) + value_len + sizeof(tombstone_flag) +
|
||||||
|
sizeof(version);
|
||||||
|
});
|
||||||
|
|
||||||
|
uint64_t index_start_offset = current_offset;
|
||||||
|
|
||||||
|
// Записываем индекс
|
||||||
|
for (const auto& [key, offset] : index_entries)
|
||||||
|
{
|
||||||
|
uint32_t key_len = key.size();
|
||||||
|
::fwrite(&key_len, sizeof(key_len), 1, file);
|
||||||
|
::fwrite(key.data(), key_len, 1, file);
|
||||||
|
::fwrite(&offset, sizeof(offset), 1, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
::fwrite(&index_start_offset, sizeof(index_start_offset), 1, file);
|
||||||
|
|
||||||
|
::fflush(file);
|
||||||
|
::fsync(fd);
|
||||||
|
::fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SkipList>
|
||||||
|
void read_sstable_with_index(SkipList& memtable, const std::string& filename)
|
||||||
|
{
|
||||||
|
std::ifstream sstable(filename, std::ios::binary);
|
||||||
|
if (!sstable.is_open())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to open file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
sstable.seekg(-sizeof(uint64_t), std::ios::end);
|
||||||
|
uint64_t index_offset = 0;
|
||||||
|
sstable.read(reinterpret_cast<char*>(&index_offset), sizeof(index_offset));
|
||||||
|
|
||||||
|
sstable.seekg(index_offset, std::ios::beg);
|
||||||
|
|
||||||
|
std::vector<std::pair<typename SkipList::key_type, uint64_t>> index_entries;
|
||||||
|
|
||||||
|
while (sstable.tellg() < static_cast<std::streamoff>(sstable.end))
|
||||||
|
{
|
||||||
|
uint32_t key_len;
|
||||||
|
if (!sstable.read(reinterpret_cast<char*>(&key_len), sizeof(key_len)))
|
||||||
|
break;
|
||||||
|
|
||||||
|
typename SkipList::key_type key{};
|
||||||
|
sstable.read(reinterpret_cast<char*>(&key), key_len);
|
||||||
|
|
||||||
|
uint64_t offset = 0;
|
||||||
|
sstable.read(reinterpret_cast<char*>(&offset), sizeof(offset));
|
||||||
|
|
||||||
|
index_entries.emplace_back(key, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [key, offset] : index_entries)
|
||||||
|
{
|
||||||
|
sstable.seekg(offset, std::ios::beg);
|
||||||
|
|
||||||
|
uint32_t key_len = 0;
|
||||||
|
sstable.read(reinterpret_cast<char*>(&key_len), sizeof(key_len));
|
||||||
|
|
||||||
|
typename SkipList::key_type file_key{};
|
||||||
|
sstable.read(reinterpret_cast<char*>(&file_key), key_len);
|
||||||
|
|
||||||
|
uint32_t value_len = 0;
|
||||||
|
sstable.read(reinterpret_cast<char*>(&value_len), sizeof(value_len));
|
||||||
|
|
||||||
|
std::string value(value_len, '\0');
|
||||||
|
sstable.read(value.data(), value_len);
|
||||||
|
|
||||||
|
memtable.insert(file_key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SkipList, typename Callback>
|
||||||
|
void range_query_sstable(const std::string& filename,
|
||||||
|
const typename SkipList::key_type& from_key,
|
||||||
|
const typename SkipList::key_type& to_key,
|
||||||
|
Callback&& callback)
|
||||||
|
{
|
||||||
|
std::ifstream sstable(filename, std::ios::binary);
|
||||||
|
if (!sstable.is_open())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to open file: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
sstable.seekg(-sizeof(uint64_t), std::ios::end);
|
||||||
|
uint64_t index_offset = 0;
|
||||||
|
sstable.read(reinterpret_cast<char*>(&index_offset), sizeof(index_offset));
|
||||||
|
|
||||||
|
sstable.seekg(index_offset, std::ios::beg);
|
||||||
|
|
||||||
|
std::vector<std::pair<typename SkipList::key_type, uint64_t>> index_entries;
|
||||||
|
|
||||||
|
while (sstable.peek() != EOF)
|
||||||
|
{
|
||||||
|
uint32_t key_len;
|
||||||
|
if (!sstable.read(reinterpret_cast<char*>(&key_len), sizeof(key_len)))
|
||||||
|
break;
|
||||||
|
|
||||||
|
typename SkipList::key_type key{};
|
||||||
|
sstable.read(reinterpret_cast<char*>(&key), key_len);
|
||||||
|
|
||||||
|
uint64_t offset = 0;
|
||||||
|
sstable.read(reinterpret_cast<char*>(&offset), sizeof(offset));
|
||||||
|
|
||||||
|
index_entries.emplace_back(key, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = std::lower_bound(index_entries.begin(), index_entries.end(), from_key,
|
||||||
|
[](const auto& pair, const auto& key) { return pair.first < key; });
|
||||||
|
|
||||||
|
for (; it != index_entries.end() && it->first <= to_key; ++it)
|
||||||
|
{
|
||||||
|
sstable.seekg(it->second, std::ios::beg);
|
||||||
|
|
||||||
|
uint32_t key_len;
|
||||||
|
sstable.read(reinterpret_cast<char*>(&key_len), sizeof(key_len));
|
||||||
|
|
||||||
|
typename SkipList::key_type file_key{};
|
||||||
|
sstable.read(reinterpret_cast<char*>(&file_key), key_len);
|
||||||
|
|
||||||
|
uint32_t value_len;
|
||||||
|
sstable.read(reinterpret_cast<char*>(&value_len), sizeof(value_len));
|
||||||
|
|
||||||
|
std::string value(value_len, '\0');
|
||||||
|
sstable.read(value.data(), value_len);
|
||||||
|
|
||||||
|
callback(file_key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SkipList>
|
||||||
|
void replay_wal(SkipList& memtable, const std::string& wal_filename)
|
||||||
|
{
|
||||||
|
std::ifstream wal(wal_filename, std::ios::binary);
|
||||||
|
if (!wal.is_open())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to open WAL file: " + wal_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (wal.peek() != EOF)
|
||||||
|
{
|
||||||
|
uint8_t op;
|
||||||
|
wal.read(reinterpret_cast<char*>(&op), sizeof(op));
|
||||||
|
|
||||||
|
uint32_t key_len;
|
||||||
|
wal.read(reinterpret_cast<char*>(&key_len), sizeof(key_len));
|
||||||
|
|
||||||
|
std::string key(key_len, '\0');
|
||||||
|
wal.read(key.data(), key_len);
|
||||||
|
|
||||||
|
if (op == 0)
|
||||||
|
{
|
||||||
|
// PUT
|
||||||
|
uint32_t value_len;
|
||||||
|
wal.read(reinterpret_cast<char*>(&value_len), sizeof(value_len));
|
||||||
|
std::string value(value_len, '\0');
|
||||||
|
wal.read(value.data(), value_len);
|
||||||
|
memtable.insert(key, value);
|
||||||
|
}
|
||||||
|
else if (op == 1)
|
||||||
|
{
|
||||||
|
memtable.erase(key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Unknown WAL operation code");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SkipList>
|
||||||
|
void read_sstable_with_mmap(SkipList& memtable, const std::string& filename)
|
||||||
|
{
|
||||||
|
int fd = ::open(filename.c_str(), O_RDONLY);
|
||||||
|
if (fd < 0) throw std::runtime_error("Failed to open SSTable");
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (fstat(fd, &st) != 0)
|
||||||
|
{
|
||||||
|
::close(fd);
|
||||||
|
throw std::runtime_error("Failed to stat SSTable");
|
||||||
|
}
|
||||||
|
|
||||||
|
void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||||
|
if (data == MAP_FAILED)
|
||||||
|
{
|
||||||
|
::close(fd);
|
||||||
|
throw std::runtime_error("Failed to mmap SSTable");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* ptr = reinterpret_cast<const char*>(data);
|
||||||
|
const char* end = ptr + st.st_size;
|
||||||
|
|
||||||
|
uint64_t index_offset = *reinterpret_cast<const uint64_t*>(end - sizeof(uint64_t));
|
||||||
|
const char* index_ptr = ptr + index_offset;
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, uint64_t>> index_entries;
|
||||||
|
while (index_ptr < end - sizeof(uint64_t))
|
||||||
|
{
|
||||||
|
uint32_t key_len = *reinterpret_cast<const uint32_t*>(index_ptr);
|
||||||
|
index_ptr += sizeof(uint32_t);
|
||||||
|
|
||||||
|
std::string key(key_len, '\0');
|
||||||
|
std::memcpy(key.data(), index_ptr, key_len);
|
||||||
|
index_ptr += key_len;
|
||||||
|
|
||||||
|
uint64_t offset = *reinterpret_cast<const uint64_t*>(index_ptr);
|
||||||
|
index_ptr += sizeof(uint64_t);
|
||||||
|
|
||||||
|
index_entries.emplace_back(key, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [key, offset] : index_entries)
|
||||||
|
{
|
||||||
|
const char* record = ptr + offset;
|
||||||
|
|
||||||
|
uint32_t key_len = *reinterpret_cast<const uint32_t*>(record);
|
||||||
|
record += sizeof(uint32_t);
|
||||||
|
|
||||||
|
std::string file_key(key_len, '\0');
|
||||||
|
std::memcpy(file_key.data(), record, key_len);
|
||||||
|
record += key_len;
|
||||||
|
|
||||||
|
uint32_t value_len = *reinterpret_cast<const uint32_t*>(record);
|
||||||
|
record += sizeof(uint32_t);
|
||||||
|
|
||||||
|
std::string value(value_len, '\0');
|
||||||
|
std::memcpy(value.data(), record, value_len);
|
||||||
|
record += value_len;
|
||||||
|
|
||||||
|
uint8_t tombstone_flag = *reinterpret_cast<const uint8_t*>(record);
|
||||||
|
record += sizeof(uint8_t);
|
||||||
|
|
||||||
|
uint64_t version = *reinterpret_cast<const uint64_t*>(record);
|
||||||
|
record += sizeof(uint64_t);
|
||||||
|
|
||||||
|
memtable.insert_raw(file_key, value, tombstone_flag == 1, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
munmap(data, st.st_size);
|
||||||
|
::close(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SkipList, typename Callback>
|
||||||
|
void optimized_range_query_sstable(const std::string& filename,
|
||||||
|
const typename SkipList::key_type& from_key,
|
||||||
|
const typename SkipList::key_type& to_key,
|
||||||
|
Callback&& callback)
|
||||||
|
{
|
||||||
|
int fd = ::open(filename.c_str(), O_RDONLY);
|
||||||
|
if (fd < 0) throw std::runtime_error("Failed to open file");
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (fstat(fd, &st) != 0)
|
||||||
|
{
|
||||||
|
::close(fd);
|
||||||
|
throw std::runtime_error("Failed to stat file");
|
||||||
|
}
|
||||||
|
|
||||||
|
void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||||
|
if (data == MAP_FAILED)
|
||||||
|
{
|
||||||
|
::close(fd);
|
||||||
|
throw std::runtime_error("Failed to mmap file");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* ptr = reinterpret_cast<const char*>(data);
|
||||||
|
const char* end = ptr + st.st_size;
|
||||||
|
|
||||||
|
uint64_t index_offset = *reinterpret_cast<const uint64_t*>(end - sizeof(uint64_t));
|
||||||
|
const char* index_ptr = ptr + index_offset;
|
||||||
|
|
||||||
|
std::vector<std::pair<typename SkipList::key_type, uint64_t>> index_entries;
|
||||||
|
while (index_ptr < end - sizeof(uint64_t))
|
||||||
|
{
|
||||||
|
uint32_t key_len = *reinterpret_cast<const uint32_t*>(index_ptr);
|
||||||
|
index_ptr += sizeof(uint32_t);
|
||||||
|
|
||||||
|
typename SkipList::key_type key;
|
||||||
|
std::memcpy(&key, index_ptr, key_len);
|
||||||
|
index_ptr += key_len;
|
||||||
|
|
||||||
|
uint64_t offset = *reinterpret_cast<const uint64_t*>(index_ptr);
|
||||||
|
index_ptr += sizeof(uint64_t);
|
||||||
|
|
||||||
|
index_entries.emplace_back(key, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// lower_bound по from_key
|
||||||
|
auto it = std::lower_bound(index_entries.begin(), index_entries.end(), from_key,
|
||||||
|
[](const auto& pair, const auto& key) { return pair.first < key; });
|
||||||
|
|
||||||
|
for (; it != index_entries.end() && it->first <= to_key; ++it)
|
||||||
|
{
|
||||||
|
const char* record = ptr + it->second;
|
||||||
|
|
||||||
|
uint32_t key_len = *reinterpret_cast<const uint32_t*>(record);
|
||||||
|
record += sizeof(uint32_t);
|
||||||
|
|
||||||
|
typename SkipList::key_type file_key;
|
||||||
|
std::memcpy(&file_key, record, key_len);
|
||||||
|
record += key_len;
|
||||||
|
|
||||||
|
uint32_t value_len = *reinterpret_cast<const uint32_t*>(record);
|
||||||
|
record += sizeof(uint32_t);
|
||||||
|
|
||||||
|
std::string value(value_len, '\0');
|
||||||
|
std::memcpy(value.data(), record, value_len);
|
||||||
|
record += value_len;
|
||||||
|
|
||||||
|
uint8_t is_tombstone = *reinterpret_cast<const uint8_t*>(record);
|
||||||
|
record += sizeof(uint8_t);
|
||||||
|
|
||||||
|
uint64_t version = *reinterpret_cast<const uint64_t*>(record);
|
||||||
|
record += sizeof(uint64_t);
|
||||||
|
|
||||||
|
if (!is_tombstone)
|
||||||
|
{
|
||||||
|
callback(file_key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
munmap(data, st.st_size);
|
||||||
|
::close(fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //MEMTABLE_H
|
75
utils/io/VersionManager.cpp
Normal file
75
utils/io/VersionManager.cpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
//
|
||||||
|
// Created by Kirill Zhukov on 20.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "VersionManager.h"
|
||||||
|
|
||||||
|
namespace usub::utils
|
||||||
|
{
|
||||||
|
VersionManager::VersionManager(const std::string& dbname)
|
||||||
|
: db_name(dbname),
|
||||||
|
metadata_dir("metadata/" + db_name + "/"),
|
||||||
|
version_file(metadata_dir + "version.meta")
|
||||||
|
{
|
||||||
|
ensure_metadata_dir();
|
||||||
|
load_version();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t VersionManager::next_version()
|
||||||
|
{
|
||||||
|
return this->version.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VersionManager::ensure_metadata_dir()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!std::filesystem::exists(this->metadata_dir))
|
||||||
|
{
|
||||||
|
std::filesystem::create_directories(this->metadata_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to create metadata dir: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VersionManager::load_version()
|
||||||
|
{
|
||||||
|
std::ifstream f(this->version_file, std::ios::binary);
|
||||||
|
if (f.is_open())
|
||||||
|
{
|
||||||
|
uint64_t v = 0;
|
||||||
|
f.read(reinterpret_cast<char*>(&v), sizeof(v));
|
||||||
|
if (f.good() && v > 0)
|
||||||
|
{
|
||||||
|
this->version.store(v, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << "Warning: empty or corrupted version file for " << this->db_name << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << "Warning: no version file for " << this->db_name << ", starting fresh\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VersionManager::save_version()
|
||||||
|
{
|
||||||
|
std::ofstream f(this->version_file, std::ios::binary | std::ios::trunc);
|
||||||
|
if (!f.is_open())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: cannot save version for " << this->db_name << "\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint64_t v = this->version.load(std::memory_order_relaxed);
|
||||||
|
f.write(reinterpret_cast<const char*>(&v), sizeof(v));
|
||||||
|
if (!f.good())
|
||||||
|
{
|
||||||
|
std::cerr << "Error: cannot write version for " << this->db_name << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
utils/io/VersionManager.h
Normal file
44
utils/io/VersionManager.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// Created by Kirill Zhukov on 20.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef VERSIONMANAGER_H
|
||||||
|
#define VERSIONMANAGER_H
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <fstream>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace usub::utils
|
||||||
|
{
|
||||||
|
class VersionManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit VersionManager(const std::string& dbname);
|
||||||
|
|
||||||
|
~VersionManager()
|
||||||
|
{
|
||||||
|
save_version();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t next_version();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ensure_metadata_dir();
|
||||||
|
|
||||||
|
void load_version();
|
||||||
|
|
||||||
|
void save_version();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::atomic<uint64_t> version{1};
|
||||||
|
std::string db_name;
|
||||||
|
std::string metadata_dir;
|
||||||
|
std::string version_file;
|
||||||
|
};
|
||||||
|
} // namespace usub::utils
|
||||||
|
|
||||||
|
#endif //VERSIONMANAGER_H
|
43
utils/io/Wal.cpp
Normal file
43
utils/io/Wal.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// Created by Kirill Zhukov on 20.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Wal.h"
|
||||||
|
|
||||||
|
namespace usub::utils
|
||||||
|
{
|
||||||
|
WAL::WAL(const std::string& filename)
|
||||||
|
{
|
||||||
|
this->out.open(filename, std::ios::binary | std::ios::app);
|
||||||
|
if (!this->out.is_open()) throw std::runtime_error("Failed to open WAL");
|
||||||
|
}
|
||||||
|
|
||||||
|
void WAL::write_put(const std::string& key, const std::string& value)
|
||||||
|
{
|
||||||
|
uint8_t op = 0;
|
||||||
|
uint32_t key_len = key.size();
|
||||||
|
uint32_t value_len = value.size();
|
||||||
|
this->out.write(reinterpret_cast<char*>(&op), sizeof(op));
|
||||||
|
this->out.write(reinterpret_cast<char*>(&key_len), sizeof(key_len));
|
||||||
|
this->out.write(key.data(), key_len);
|
||||||
|
this->out.write(reinterpret_cast<char*>(&value_len), sizeof(value_len));
|
||||||
|
this->out.write(value.data(), value_len);
|
||||||
|
this->out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WAL::write_delete(const std::string& key)
|
||||||
|
{
|
||||||
|
uint8_t op = 1;
|
||||||
|
uint32_t key_len = key.size();
|
||||||
|
this->out.write(reinterpret_cast<char*>(&op), sizeof(op));
|
||||||
|
this->out.write(reinterpret_cast<char*>(&key_len), sizeof(key_len));
|
||||||
|
this->out.write(key.data(), key_len);
|
||||||
|
this->out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WAL::close()
|
||||||
|
{
|
||||||
|
this->out.close();
|
||||||
|
}
|
||||||
|
} // utils
|
||||||
|
// usub
|
28
utils/io/Wal.h
Normal file
28
utils/io/Wal.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// Created by Kirill Zhukov on 20.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef WAL_H
|
||||||
|
#define WAL_H
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace usub::utils
|
||||||
|
{
|
||||||
|
class WAL
|
||||||
|
{
|
||||||
|
std::ofstream out;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit WAL(const std::string& filename);
|
||||||
|
|
||||||
|
void write_put(const std::string& key, const std::string& value);
|
||||||
|
|
||||||
|
void write_delete(const std::string& key);
|
||||||
|
|
||||||
|
void close();
|
||||||
|
};
|
||||||
|
} // utils
|
||||||
|
// usub
|
||||||
|
|
||||||
|
#endif //WAL_H
|
1
utils/toml
Submodule
1
utils/toml
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit fea1d905f2d2a8ad830f1985fe879f4fd4601fe5
|
Loading…
x
Reference in New Issue
Block a user