/
smychkov
/
SStorage
Обзор
Документация
Войти
/
smychkov
/
SStorage
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/memtable/memtable.cpp
116 строк
5 KB
Андрей Смычков
feat: MemTable (std::map, freeze, drainSorted)
25 апр 2026, 09:04
25 апр 2026, 09:04
39a036a
Код
Авторство
О чём код?
#include "memtable.hpp" namespace sstorage { MemTable::MemTable() = default; //============================================================================ // put — вставка или перезапись //============================================================================ // Инвариант: для каждого key хранится запись с максимальным seqNo. // Если пришла запись с меньшим seqNo — игнорируется (такое может быть при // replay WAL, если записи пришли не в порядке). //============================================================================ void MemTable::put(const Record& r) { if (frozen_.load(std::memory_order_acquire)) { // Frozen MemTable не принимает записей — это программная ошибка. // В production можно было бы throw, но для простоты — молчаливый ignore. return; } std::unique_lock<std::shared_mutex> lk(mutex_); auto it = data_.find(r.key()); if (it == data_.end()) { // Новый ключ — просто вставка size_t added = r.approximateSize(); data_.emplace(r.key(), r); sizeBytes_.fetch_add(added, std::memory_order_relaxed); } else { // Ключ существует — побеждает запись с большим seqNo if (r.seqNo() > it->second.seqNo()) { size_t oldSize = it->second.approximateSize(); size_t newSize = r.approximateSize(); it->second = r; sizeBytes_.fetch_add(newSize, std::memory_order_relaxed); sizeBytes_.fetch_sub(oldSize, std::memory_order_relaxed); } // Иначе (r.seqNo() <= existing) — ничего не делаем } } //============================================================================ // get — чтение по ключу //============================================================================ std::optional<Record> MemTable::get(const std::string& key) const { std::shared_lock<std::shared_mutex> lk(mutex_); auto it = data_.find(key); if (it == data_.end()) { return std::nullopt; } return it->second; } //============================================================================ // scan — диапазон ключей //============================================================================ // std::map упорядочен, используем lower_bound/upper_bound для эффективного // обхода. Tombstone'ы возвращаются как есть — фильтрацию делает вызывающий код. //============================================================================ std::vector<Record> MemTable::scan(const std::string& fromKey, const std::string& toKey, size_t limit) const { std::vector<Record> result; std::shared_lock<std::shared_mutex> lk(mutex_); auto it = data_.lower_bound(fromKey); // первый >= fromKey auto end = data_.upper_bound(toKey); // первый > toKey for (; it != end; ++it) { result.push_back(it->second); if (limit > 0 && result.size() >= limit) break; } return result; } size_t MemTable::approximateSize() const { return sizeBytes_.load(std::memory_order_relaxed); } size_t MemTable::recordCount() const { std::shared_lock<std::shared_mutex> lk(mutex_); return data_.size(); } //============================================================================ // freeze — запретить дальнейшие записи //============================================================================ void MemTable::freeze() { frozen_.store(true, std::memory_order_release); } bool MemTable::isFrozen() const { return frozen_.load(std::memory_order_acquire); } bool MemTable::empty() const { std::shared_lock<std::shared_mutex> lk(mutex_); return data_.empty(); } //============================================================================ // drainSorted — выгрузка в отсортированный вектор //============================================================================ // std::map уже отсортирован по key, поэтому просто копируем values. //============================================================================ std::vector<Record> MemTable::drainSorted() const { std::shared_lock<std::shared_mutex> lk(mutex_); std::vector<Record> result; result.reserve(data_.size()); for (const auto& [_, rec] : data_) { result.push_back(rec); } return result; } }