Skip to content

Pechalka/my-db

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Учебная база данных на Node.js (TCP + индексы + WAL)

Этот проект — учебная реализация документо-ориентированной базы данных, написанной на Node.js. Цель — понять, как работают внутренние механизмы СУБД: сетевой протокол, пул соединений, персистентность (WAL + снэпшоты), индексы (с поддержкой диапазонных запросов и уникальности), обработка команд, graceful shutdown. Проект не предназначен для продакшена, но отлично подходит для изучения и экспериментов.


🧠 Ключевые реализованные концепции

🔹 TCP‑сервер базы данных

  • Принимает команды в формате JSON, разделённые символом новой строки (\n).
  • Каждая команда содержит поле reqId для мультиплексирования ответов.

🔹 In‑memory хранилище

  • Коллекции хранятся как 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 для сопоставления ответов.

🔹 Graceful shutdown

  • Обработка 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

🚀 Запуск

1. Запустите сервер базы данных

cd my-db-server
pnpm start

Сервер слушает порт 27011 (TCP).

2. Запустите веб‑сервер (Express)

cd web-server
pnpm start

Веб‑сервер запускается на http://localhost:3000.

3. Запустите клиентское приложение (React)

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
  • Составные (композитные) индексы
  • Сортировка результатов
  • Транзакции (группа операций)
  • Асинхронная запись снэпшотов (чтобы не блокировать)
  • Автоматическое восстановление сломанных соединений в пуле (уже частично есть)
  • Шардирование (распределение данных по нескольким экземплярам)

📦 Планы на будущее (переписывание на Go или Rust)

Текущая реализация на Node.js — однопоточная и in‑memory. В дальнейшем планируется переписать ядро базы данных на Go или Rust, чтобы:

  • Использовать настоящие страничные структуры (B‑деревья, буферный кэш)
  • Реализовать многопоточность и эффективную работу с диском
  • Получить высокую производительность и надёжность

Веб‑сервер и клиент могут остаться на Node.js/React.


🧩 Зависимости и архитектурные решения

В процессе разработки возникали циклические зависимости между модулями commands.js и persistence.js.
Я намеренно не использовали DI‑библиотеки (Inversify, Awilix), чтобы сохранить код простым и прозрачным. Проблема была решена путём:

  • выделения чистой логики (applyOperation) без вызовов WAL
  • явной передачи зависимостей (recoverFromWAL(applyOperation))
  • разделения модулей на фабрики там, где это необходимо

Такой подход — отличная иллюстрация того, как ручное управление зависимостями работает в небольших проектах.


📝 Лицензия

MIT — учебный проект, используйте для изучения как угодно.


🌟 Благодарность

Спасибо, что интересуетесь внутренностями баз данных. Надеюсь, этот проект поможет вам понять, как работают индексы, WAL, пулы соединений и другие фундаментальные концепции.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors