Комментарии 13
Сделайте бенч

Running 1 benchmarks...
Benchmark lensdb-bench: RUNNING...
benchmarking Core Operations/set operation
time 364.0 μs (343.0 μs .. 397.6 μs)
0.863 R² (0.766 R² .. 0.972 R²)
mean 434.6 μs (401.7 μs .. 483.6 μs)
std dev 143.0 μs (99.53 μs .. 207.3 μs)
variance introduced by outliers: 98% (severely inflated)
benchmarking Core Operations/get operation
time 232.2 μs (221.4 μs .. 248.3 μs)
0.969 R² (0.943 R² .. 0.991 R²)
mean 249.4 μs (239.2 μs .. 263.4 μs)
std dev 40.42 μs (30.57 μs .. 52.04 μs)
variance introduced by outliers: 91% (severely inflated)
benchmarking Core Operations/delete operation
time 519.6 μs (492.0 μs .. 556.8 μs)
0.955 R² (0.932 R² .. 0.978 R²)
mean 645.1 μs (601.4 μs .. 700.3 μs)
std dev 183.1 μs (145.3 μs .. 227.1 μs)
variance introduced by outliers: 97% (severely inflated)
benchmarking Core Operations/exists operation
time 2.379 μs (2.371 μs .. 2.386 μs)
1.000 R² (1.000 R² .. 1.000 R²)
mean 2.392 μs (2.378 μs .. 2.410 μs)
std dev 48.11 ns (33.22 ns .. 67.71 ns)
variance introduced by outliers: 22% (moderately inflated)
benchmarking Storage Operations/storage statistics
time 330.2 μs (327.1 μs .. 333.1 μs)
0.999 R² (0.998 R² .. 0.999 R²)
mean 329.8 μs (326.9 μs .. 334.6 μs)
std dev 12.01 μs (8.662 μs .. 16.29 μs)
variance introduced by outliers: 31% (moderately inflated)
benchmarking Storage Operations/cleanup operations
time 332.0 μs (326.5 μs .. 339.4 μs)
0.998 R² (0.996 R² .. 0.999 R²)
mean 332.1 μs (328.6 μs .. 336.2 μs)
std dev 13.21 μs (10.30 μs .. 17.36 μs)
variance introduced by outliers: 35% (moderately inflated)
benchmarking Protocol Operations/message serialization
time 546.5 ns (471.6 ns .. 634.0 ns)
0.907 R² (0.872 R² .. 0.971 R²)
mean 608.5 ns (531.9 ns .. 771.8 ns)
std dev 366.1 ns (131.9 ns .. 595.8 ns)
variance introduced by outliers: 100% (severely inflated)
benchmarking Protocol Operations/message deserialization
time 776.0 ns (591.8 ns .. 1.026 μs)
0.641 R² (0.543 R² .. 0.813 R²)
mean 772.3 ns (655.8 ns .. 957.5 ns)
std dev 471.1 ns (315.4 ns .. 772.8 ns)
variance introduced by outliers: 100% (severely inflated)
benchmarking Protocol Operations/response serialization
time 449.2 ns (442.4 ns .. 457.3 ns)
0.997 R² (0.995 R² .. 0.998 R²)
mean 458.9 ns (448.0 ns .. 478.2 ns)
std dev 49.68 ns (27.01 ns .. 83.54 ns)
variance introduced by outliers: 91% (severely inflated)
benchmarking Concurrent Operations/concurrent sets
time 2.178 ms (2.128 ms .. 2.230 ms)
0.997 R² (0.995 R² .. 0.998 R²)
mean 2.185 ms (2.161 ms .. 2.214 ms)
std dev 90.47 μs (74.03 μs .. 116.4 μs)
variance introduced by outliers: 27% (moderately inflated)
benchmarking Concurrent Operations/concurrent gets
time 748.2 μs (725.9 μs .. 788.1 μs)
0.974 R² (0.956 R² .. 0.991 R²)
mean 764.4 μs (741.2 μs .. 799.7 μs)
std dev 97.61 μs (73.92 μs .. 126.4 μs)
variance introduced by outliers: 83% (severely inflated)
Benchmark lensdb-bench: FINISH
это при альфе версии так, в будущем я думаю еще ускорить и оптимизировать
В общем, не быстро: большой разброс и значительно медленнее хешмапы вроде dashmap (Rust). Причём, не похоже на время disk io.
Советы от LLM (ну, чем богаты...)
-- Вероятно, у тебя что-то вроде:
atomically $ do
store <- readTVar globalMap
let newStore = Map.insert key (serialize value) store -- ❌ Копирование всей Map!
writeTVar globalMap newStoreПроблема не в сериализации, а в:
Иммутабельность + STM = копирование всей структуры при каждой записи (даже с ByteString)
GC — видно по 98% outliers: каждая запись создаёт новые объекты, сборщик мусора "останавливает мир"
Data.Map = дерево с логарифмической сложностью и аллокациями на каждый узел
Я бы наоборот, взял dashmap и потихоньку шаг за шагом прикручивать сетевой io и персистентность
Ну типа такого
use dashmap::DashMap;
use tokio::net::TcpListener;
use bytes::Bytes;
struct LensDB {
store: DashMap<Bytes, Entry>,
wal: WalWriter, // добавишь потом
}
struct Entry {
value: Bytes,
expires_at: Option<Instant>,
}
impl LensDB {
// Этап 0 — чистый DashMap
fn get(&self, key: &[u8]) -> Option<Bytes> {
self.store.get(key).map(|e| e.value.clone())
}
fn set(&self, key: Bytes, value: Bytes) {
self.store.insert(key, Entry { value, expires_at: None });
}
}
// Этап 1 — добавляем TCP
async fn serve(db: Arc<LensDB>, addr: &str) -> Result<()> {
let listener = TcpListener::bind(addr).await?;
loop {
let (socket, _) = listener.accept().await?;
let db = db.clone();
tokio::spawn(async move {
handle_connection(db, socket).await
});
}
}А потом усложнял бы
use tokio::sync::mpsc;
struct WalWriter {
tx: mpsc::Sender<WalEntry>,
}
impl LensDB {
async fn set_durable(&self, key: Bytes, value: Bytes) {
// 1. Сначала в память (быстро)
self.store.insert(key.clone(), Entry { value: value.clone(), .. });
// 2. Асинхронно в WAL (не блокирует)
let _ = self.wal.tx.send(WalEntry::Set(key, value)).await;
}
}
// Отдельный таск пишет на диск
async fn wal_flusher(mut rx: mpsc::Receiver<WalEntry>, file: File) {
let mut buffer = Vec::with_capacity(4096);
loop {
// Собираем батч
while let Ok(entry) = rx.try_recv() {
entry.encode_into(&mut buffer);
}
if !buffer.is_empty() {
file.write_all(&buffer).await?;
// fsync по таймеру или по размеру
buffer.clear();
}
tokio::time::sleep(Duration::from_millis(1)).await;
}
}Ожидаемые результаты на уровне Redis (100-200 kops/sec).
И затраты на сериализацию можно смотреть только если уже упёрлись в остальные ограничения.
P.S. код довольно кривой, только для объяснения базового алгоритма
Судя по зависимостям так и есть ))) Вот на тему как раз
https://www.parsonsmatt.org/2025/12/17/the_subtle_footgun_of_tvar_(map____).html
Смотрели ли в сторону rocksdb? Интересно было бы сравнить
Привет! Интересный прокет.
Вопросы:
TTL для записей есть? как реализован, если есть? Мне надо избавляться от старых записей без лишних телодвижений.
Как настраивается соотношение "в памяти" к "на диске"? Если в кубере запускать, то не надо заходить за лимиты.
Вы там memcached изобретаете что ли?
В традиционных NoSQL‑системах, таких как Redis, данные передаются в виде строк или сериализованных объектов
Не передаю́тся, а МОГУТ передаваться, что часто и делается, но это не является ограничением. Здесь например туда решили класть msgpack - бинарный формат сообщений, и сохранили 50% памяти.

Как из идеи Shared Memory кэша родился LensDB