Этот проект — учебная реализация документо-ориентированной базы данных, написанной на Node.js. Цель — понять, как работают внутренние механизмы СУБД: сетевой протокол, пул соединений, персистентность (WAL + снэпшоты), индексы (с поддержкой диапазонных запросов и уникальности), обработка команд, graceful shutdown. Проект не предназначен для продакшена, но отлично подходит для изучения и экспериментов.
- Принимает команды в формате JSON, разделённые символом новой строки (
\n). - Каждая команда содержит поле
reqIdдля мультиплексирования ответов.
- Коллекции хранятся как
Map<id, документ>. - Индексы — отсортированный массив ключей (
keys) + массив корзин (buckets), что позволяет выполнять:- точное равенство (бинарный поиск)
- диапазонные запросы
$gt,$gte,$lt,$lte(черезrangeQuery)
- Поддержка уникальных индексов (флаг
uniqueпри создании индекса).
- WAL (Write-Ahead Log) — все изменяющие команды синхронно дописываются в файл
db.walперед применением к памяти. - Снэпшоты (Snapshot) — периодически сохраняется полное состояние всех коллекций в
snapshot.json. - Чекпоинты — по команде
checkpointили автоматически (каждые 100 операций) снэпшот сохраняется, а WAL очищается. - Восстановление — при старте загружается последний снэпшот, затем накатываются операции из WAL.
- Постоянные TCP‑сокеты к базе, переиспользование, очередь ожидания.
- Автоматическое переподключение при обрыве (с экспоненциальной задержкой).
- Request ID для сопоставления ответов.
- Обработка SIGINT / SIGTERM: закрытие сервера для новых соединений, ожидание активных запросов, финальный чекпоинт, выход.
- Поддержка фильтров вида
{ age: 30, city: "Moscow" }и{ age: { $gt: 18, $lt: 65 } }. - Использование индексов для всех полей (пересечение множеств) или полное сканирование.
- Страничная организация и буферный кэш — для работы с данными, не помещающимися в ОЗУ. Это потребовало бы полной переработки ядра (файлы страниц, кэш, синхронизацию) и выходит за рамки учебного проекта.
- Многопоточность — база данных однопоточная (как и сам Node.js), что нормально для in‑memory хранилища с малым объёмом данных. В продакшн‑системах (MongoDB, PostgreSQL) используются многопоточные движки.
- B‑деревья — вместо них использован отсортированный массив + бинарный поиск, что упрощает код, но не масштабируется на миллионы записей.
my-db
└───/web-server # Express‑сервер (HTTP → TCP)
│ │ db-client.js # Пул соединений к TCP‑базе
│ │ server.js # Точка входа веб‑сервера
│ └───routes
│ │ admin.js # CRUD для задач
│ │ todos.js # Административные эндпоинты (индексы, тестовые данные, уникальность)
└───/my-db-server # Собственно база данных (TCP)
│ │ db-client.js # Пул соединений к TCP‑базе
│ │ server.js # Точка входа веб‑сервера
│ └───src
│ │ commands.js # Логика команд и восстановление
│ │ config.js
│ │ index.js.js # Точка входа БД
│ │ indexes.js
│ │ persistence.js # WAL + снэпшоты
│ │ server.js # TCP‑сервер и graceful shutdown
│ │ storage.js
│ │ utils.js
└───/client # Простое React‑приложение (Todo‑лист) для тестирования через REST API
cd my-db-server
pnpm startСервер слушает порт 27011 (TCP).
cd web-server
pnpm startВеб‑сервер запускается на http://localhost:3000.
cd client
pnpm startОткроется http://localhost:5000 . Вы сможете создавать, отмечать и удалять задачи через красивый интерфейс.
Все они доступны через GET (для простоты).
| Эндпоинт | Описание |
|---|---|
/api/admin/checkpoint |
Принудительный чекпоинт (снэпшот + очистка WAL) |
/api/admin/createIndex |
Создаёт несколько индексов: users.age, users.city, users.email (уникальный), todos.completed |
/api/admin/collection/:name?filter={...} |
Универсальное чтение любой коллекции с фильтром (JSON в query) |
/api/admin/test-data |
Генерирует 100 случайных пользователей в коллекции users |
/api/admin/test-unique |
Тест уникального индекса на email: создаёт индекс, вставляет двух пользователей с одинаковым email, второй возвращает ошибку |
# Получить всех пользователей
curl "http://localhost:3000/api/admin/collection/users"
# Получить пользователей с age = 30 и city = "Moscow"
curl "http://localhost:3000/api/admin/collection/users?filter={\"age\":30,\"city\":\"Moscow\"}"
# Диапазон по возрасту
curl "http://localhost:3000/api/admin/collection/users?filter={\"age\":{\"$gt\":25,\"$lt\":40}}"- Поддержка
$or,$in,$regex - Составные (композитные) индексы
- Сортировка результатов
- Транзакции (группа операций)
- Асинхронная запись снэпшотов (чтобы не блокировать)
- Автоматическое восстановление сломанных соединений в пуле (уже частично есть)
- Шардирование (распределение данных по нескольким экземплярам)
Текущая реализация на Node.js — однопоточная и in‑memory. В дальнейшем планируется переписать ядро базы данных на Go или Rust, чтобы:
- Использовать настоящие страничные структуры (B‑деревья, буферный кэш)
- Реализовать многопоточность и эффективную работу с диском
- Получить высокую производительность и надёжность
Веб‑сервер и клиент могут остаться на Node.js/React.
В процессе разработки возникали циклические зависимости между модулями commands.js и persistence.js.
Я намеренно не использовали DI‑библиотеки (Inversify, Awilix), чтобы сохранить код простым и прозрачным. Проблема была решена путём:
- выделения чистой логики (
applyOperation) без вызовов WAL - явной передачи зависимостей (
recoverFromWAL(applyOperation)) - разделения модулей на фабрики там, где это необходимо
Такой подход — отличная иллюстрация того, как ручное управление зависимостями работает в небольших проектах.
MIT — учебный проект, используйте для изучения как угодно.
Спасибо, что интересуетесь внутренностями баз данных. Надеюсь, этот проект поможет вам понять, как работают индексы, WAL, пулы соединений и другие фундаментальные концепции.