Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ce6c907dd | |||
| f7c78d3d7c | |||
| d1aadacfdc | |||
| a8ca85d311 | |||
| 4e5a8837ba | |||
| 04104e07d7 | |||
| 43c870a92e | |||
| c252f462e7 | |||
| 755919fd29 | |||
| 601af066c1 | |||
| 7edeab3b10 | |||
| c0f6146308 | |||
| d46c3fa730 | |||
| 46aa587a10 | |||
| 49daa6aa76 | |||
| b12689bea0 | |||
| 2982bec7ae | |||
| 362f3d6d71 | |||
| 51c5119194 | |||
| 2f3d1450d8 | |||
| dd9b9353f1 | |||
| a769f72993 | |||
| bc10d7af53 | |||
| 6b30d43a45 | |||
| 4e053c1dfc |
36
README.md
36
README.md
@@ -31,6 +31,7 @@
|
||||
<li><a href="#о-проекте">О проекте</a></li>
|
||||
<li><a href="#лицензия">Лицензия</a></li>
|
||||
<li><a href="#глоссарий">Глоссарий</a></li>
|
||||
<li><a href="#почему-illumos-openindiana">Почему Illumos OpenIndiana?</a></li>
|
||||
<li><a href="#архитектурные-примечания-и-предложения-безопасности">Архитектурные примечания и предложения безопасности</a></li>
|
||||
<li><a href="#алгоритмы-и-структуры-данных">Алгоритмы и структуры данных</a></li>
|
||||
<li><a href="#системные-требования">Системные требования</a></li>
|
||||
@@ -52,6 +53,7 @@
|
||||
<li><a href="#триггеры">Триггеры</a></li>
|
||||
<li><a href="#сжатие-данных">Сжатие данных</a></li>
|
||||
<li><a href="#графический-интерфейс">Графический интерфейс</a></li>
|
||||
<li><a href="#чаво">ЧАВО</a></li>
|
||||
<li><a href="#план-развития">План развития</a></li>
|
||||
<li><a href="#контакты">Контакты</a></li>
|
||||
</ol>
|
||||
@@ -173,6 +175,24 @@ futriix — это лёгкая распределённая NoSQL‑СУБД н
|
||||
<p align="right">(<a href="#readme-top">К началу</a>)</p>
|
||||
|
||||
|
||||
## Почему Illumos OpenIndiana?
|
||||
|
||||
Несмотря на то, что `futriix` сохраняет кроссплатформенную совместимость и полностью поддерживает Linux, в качестве основной целевой платформы выбрана операционная система **Illumos (OpenIndiana)**. Для резидентных (in-memory) NoSQL-СУБД, эксплуатируемых в Замкнутых Программных Средах (ЗПС), ОС общего назначения (такие как Linux) часто вносят недетерминированные задержки (ресурсный джиттер) и обладают избыточным архитектурным оверхедом. Родословная Solaris в Illumos обеспечивает критически важный фундамент, построенный на строгом детерминизме и эталонном системном проектировании.
|
||||
|
||||
**Ключевые архитектурные причины приоритета ядра Illumos:**
|
||||
|
||||
* **Строгий детерминизм ядра и планирование реального времени:** В отличие от планировщика Linux (CFS/EEVDF), оптимизированного под компромисс между пропускной способностью и интерактивностью, диспетчер задач Illumos гарантирует жесткое и предсказуемое распределение ресурсов процессора. Это исключает внезапные задержки (jitter), критические для wait-free/lock-free алгоритмов обработки транзакций в памяти.
|
||||
* **Масштабируемые порты событий (`port_create`):** Для организации асинхронного высоконагруженного ввода-вывода `futriix` задействует системный интерфейс Event Ports вместо Linux `epoll`. Архитектурное преимущество портов событий заключается в нативном устранении проблемы «эффекта проснувшегося стада» (thundering herd) на уровне ядра и лучшей локальности процессорного кэша при работе с пулами потоков.
|
||||
* **Нативная архитектура управления сбоями (FMA):** Эксплуатация СУБД типа in-memory сопряжена с рисками аппаратных сбоев памяти. Подсистема Illumos FMA непрерывно анализирует телеметрию оборудования: если планка оперативной памяти начинает деградировать, FMA изолирует сбойные страницы «на лету» *до* того, как произойдет Kernel Panic, обеспечивая непрерывную доступность СУБД.
|
||||
* **Эталонная целостность WAL через нативную ZFS:** Сегментированный лог упреждающей записи (WAL) требует абсолютной надежности хранения. В Illumos файловая система ZFS является родной и глубоко интегрирована в слой виртуальной памяти ядра. Она верифицирует блоки данных с помощью криптографических контрольных сумм и автоматически исправляет скрытые повреждения бит (*silent data corruption*), гарантируя надежность (Durability) без потери производительности.
|
||||
* **Жесткая изоляция на уровне ОС (Zones):** Технология контейнеризации Zones «вшита» непосредственно в ядро системы, в отличие от контейнеров Linux, реализованных в виде надстройки через пространства имен (namespaces) и cgroups. Это обеспечивает чистую, математически строгую логическую и физическую изоляцию распределенных узлов СУБД, запущенных на общем оборудовании.
|
||||
* **Лицензия CDDL для применения в ЗПС:** Лицензия Common Development and Distribution License (CDDL) позволяет модифицировать и усиливать безопасность ядра ОС под требования отечественных регуляторов без необходимости раскрывать производный код (в отличие от жестких копилефт-требований GPL в Linux). Это радикально упрощает создание доверенных, сертифицированных программно-аппаратных комплексов.
|
||||
|
||||
|
||||
<p align="right">(<a href="#readme-top">К началу</a>)</p>
|
||||
|
||||
|
||||
|
||||
## Архитектурные примечания и предложения безопасности
|
||||
|
||||
> [!IMPORTANT]
|
||||
@@ -215,7 +235,7 @@ futriix — это лёгкая распределённая NoSQL‑СУБД н
|
||||
|----------|----------|----------|
|
||||
| **Хранилище (Storage)** | `sync.Map` | Конкурентная хэш‑таблица для хранения баз данных, обеспечивает wait‑free чтение и запись |
|
||||
| | Атомарные счётчики (`atomic.Int64`) | Для отслеживания общего количества документов без блокировок |
|
||||
| **База данных (Database)** | `sync.Map` коллекций | Аналог слайса в реляционных СУБД |
|
||||
| **База данных (Database)** | `sync.Map` коллекций | Синоним термнина "база данных" в реляционных СУБД |
|
||||
| | Мьютексы (`sync.RWMutex`) | Для операций изменения структуры (создание/удаление коллекций) |
|
||||
| **Коллекция (Collection)** | `sync.Map` документов | Ключ: ID документа, значение: указатель на `Document` |
|
||||
| | `sync.Map` индексов | Отдельное хранилище для инвертированных индексов |
|
||||
@@ -2510,10 +2530,24 @@ futriix:~> doc compression employees 550e8400-e29b-41d4-a716-446655440000
|
||||
<img src="wui.png" height=400 weight=400 alt="wui.png"></img>
|
||||
|
||||
|
||||
<p align="right">(<a href="#readme-top">К началу</a>)</p>
|
||||
|
||||
|
||||
## ЧАВО
|
||||
|
||||
В данном разделе- **ЧАВО (Часто задаваемые Вопросы и Ответы)** собраны ответы на самые частые вопросы — чтобы вы быстро разобрались в работе проекта и не тратили время на поиск очевидных вещей. Если не нашли нужного ответа — напишите в `issues`: обратная связь помогает нам расставлять приоритеты.<br> Дальше — раздел «План развития»: там показаны, какие улучшения уже реализованы, а каких ждать в будущем.
|
||||
|
||||
**Вопрос: Почему версия обозначена как «Futriix 2 i²», а не просто «2.0»?**<br>
|
||||
<br>
|
||||
<br>
|
||||
**Ответ: «2» — номер версии: здесь собраны ключевые компоненты (шардинг, WAL, MVCC и др.).**
|
||||
**i2i2 — математическая метафора: хотя ii — мнимая единица, её квадрат даёт абсолютно точный результат (i2=−1i2=−1). Так и Futriix: даже в сложной распределённой среде с параллельными операциями (wait‑free, lock‑free) система выдаёт строго предсказуемые результаты — за счёт Raft, ACID и MVCC. Это близко философии OpenIndiana/Illumos: детерминизм и контроль над ресурсами.**
|
||||
|
||||
|
||||
<p align="right">(<a href="#readme-top">К началу</a>)</p>
|
||||
|
||||
|
||||
|
||||
## План развития
|
||||
|
||||
- [x] Реализовать поддержку хранимых процедур
|
||||
|
||||
@@ -85,7 +85,7 @@ func main() {
|
||||
aclManager := acl.NewACLManager()
|
||||
logger.Info("ACL manager initialized")
|
||||
|
||||
// Исправлено: передаём store как второй аргумент
|
||||
// Передаём store как второй аргумент
|
||||
raftCoordinator, err := cluster.NewRaftCoordinator(cfg, store, logger)
|
||||
if err != nil {
|
||||
logger.Error("Failed to start Raft coordinator: " + err.Error())
|
||||
@@ -185,7 +185,7 @@ func main() {
|
||||
|
||||
node := cluster.NewNode(cfg.Cluster.NodeIP, cfg.Cluster.NodePort, store, logger)
|
||||
|
||||
// Исправлено: объявляем переменную maxRetries здесь, чтобы она была доступна
|
||||
// Объявляем переменную maxRetries здесь, чтобы она была доступна
|
||||
maxRetries := 5
|
||||
var registerErr error
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
@@ -205,7 +205,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Исправлено: передаём отдельные параметры из конфигурации плагинов
|
||||
// Передаём отдельные параметры из конфигурации плагинов
|
||||
pluginManager := plugin.NewPluginManager(
|
||||
cfg.Plugins.ScriptDir,
|
||||
logger,
|
||||
|
||||
@@ -225,6 +225,7 @@ func (sm *SchemaMigrator) createDefaultSchema() {
|
||||
UpdatedAt: time.Now().UnixMilli(),
|
||||
}
|
||||
|
||||
sm.currentVersion = "1.0.0"
|
||||
sm.saveSchema()
|
||||
}
|
||||
|
||||
@@ -388,7 +389,13 @@ func (sm *SchemaMigrator) MigrateSchema(targetVersion string) error {
|
||||
}
|
||||
|
||||
// Начинаем транзакцию для миграции
|
||||
// Используем storage.BeginTransaction() из пакета storage
|
||||
tx := storage.BeginTransaction()
|
||||
if tx == nil {
|
||||
return fmt.Errorf("failed to begin transaction")
|
||||
}
|
||||
|
||||
// Обработка паники и откат транзакции
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
storage.AbortCurrentTransaction()
|
||||
@@ -437,12 +444,17 @@ func (sm *SchemaMigrator) MigrateSchema(targetVersion string) error {
|
||||
return fmt.Errorf("failed to save schema: %v", err)
|
||||
}
|
||||
|
||||
// Сохраняем применённые миграции
|
||||
if err := sm.saveAppliedMigrations(); err != nil {
|
||||
storage.AbortCurrentTransaction()
|
||||
return fmt.Errorf("failed to save applied migrations: %v", err)
|
||||
}
|
||||
|
||||
// Коммитим транзакцию
|
||||
if err := storage.CommitCurrentTransaction(); err != nil {
|
||||
return fmt.Errorf("failed to commit schema migration: %v", err)
|
||||
}
|
||||
|
||||
sm.saveAppliedMigrations()
|
||||
sm.currentVersion = targetVersion
|
||||
|
||||
if sm.logger != nil {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
// Файл: internal/storage/fsync.go
|
||||
// Назначение: Реальная синхронизация с диском (fsync) для WAL
|
||||
// Поддерживает все платформы: Linux, macOS, BSD, Windows, Solaris/Illumos (OpenIndiana)
|
||||
|
||||
package storage
|
||||
|
||||
@@ -17,21 +18,29 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RealFsync выполняет реальный fsync на файле
|
||||
// RealFsync выполняет реальный fsync на файле.
|
||||
// Использует стандартный метод Sync() для максимальной совместимости
|
||||
// со всеми платформами включая OpenIndiana (Solaris/Illumos).
|
||||
func RealFsync(file *os.File) error {
|
||||
if file == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Вызываем системный fsync
|
||||
// Вызываем системный fsync через стандартный метод Sync
|
||||
// Это работает на всех платформах: Linux, macOS, BSD, Windows, Solaris/Illumos
|
||||
return file.Sync()
|
||||
}
|
||||
|
||||
// RealFsyncWithRetry выполняет fsync с повторными попытками
|
||||
// RealFsyncWithRetry выполняет fsync с повторными попытками.
|
||||
// Параметры:
|
||||
// - file: файл для синхронизации
|
||||
// - maxRetries: максимальное количество попыток
|
||||
// - retryDelay: задержка между попытками
|
||||
//
|
||||
// Возвращает ошибку, если все попытки не удались.
|
||||
func RealFsyncWithRetry(file *os.File, maxRetries int, retryDelay time.Duration) error {
|
||||
if file == nil {
|
||||
return nil
|
||||
@@ -52,7 +61,8 @@ func RealFsyncWithRetry(file *os.File, maxRetries int, retryDelay time.Duration)
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// FsyncDir синхронизирует директорию (для гарантии, что создание файла записано на диск)
|
||||
// FsyncDir синхронизирует директорию (для гарантии, что создание файла записано на диск).
|
||||
// На некоторых платформах (Windows, Solaris/Illumos) fsync для директорий не поддерживается.
|
||||
func FsyncDir(dirPath string) error {
|
||||
dir, err := os.Open(dirPath)
|
||||
if err != nil {
|
||||
@@ -60,24 +70,84 @@ func FsyncDir(dirPath string) error {
|
||||
}
|
||||
defer dir.Close()
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// На Windows нет прямой поддержки fsync для директорий
|
||||
// На Windows и Solaris/Illumos нет прямой поддержки fsync для директорий
|
||||
// Используем стандартный Sync если доступен
|
||||
switch runtime.GOOS {
|
||||
case "windows", "solaris", "illumos", "openindiana":
|
||||
// На этих платформах fsync для директорий не поддерживается или не нужен
|
||||
// Возвращаем nil, так как данные уже записаны через fsync файлов
|
||||
return nil
|
||||
}
|
||||
|
||||
default:
|
||||
// Для Linux, BSD, macOS используем Sync
|
||||
return dir.Sync()
|
||||
}
|
||||
}
|
||||
|
||||
// FsyncDataOnly выполняет fsync только для данных (не метаданных)
|
||||
// FsyncDataOnly выполняет fsync только для данных (не метаданных).
|
||||
// Использует стандартный Sync для всех платформ.
|
||||
// Это обеспечивает запись данных на диск без синхронизации метаданных.
|
||||
func FsyncDataOnly(file *os.File) error {
|
||||
if file == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fd := int(file.Fd())
|
||||
_, _, err := syscall.Syscall(syscall.SYS_FSYNC, uintptr(fd), 0, 0)
|
||||
if err != 0 {
|
||||
return fmt.Errorf("fsync failed: %v", err)
|
||||
// Используем стандартный Sync для всех платформ
|
||||
// Это обеспечит запись данных на диск
|
||||
return file.Sync()
|
||||
}
|
||||
|
||||
// FsyncWithTimeout выполняет fsync с таймаутом.
|
||||
// Если операция не завершается за указанное время, возвращается ошибка.
|
||||
func FsyncWithTimeout(file *os.File, timeout time.Duration) error {
|
||||
if file == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Создаём канал для результата
|
||||
result := make(chan error, 1)
|
||||
|
||||
// Запускаем fsync в отдельной горутине
|
||||
go func() {
|
||||
result <- file.Sync()
|
||||
}()
|
||||
|
||||
// Ожидаем результат или таймаут
|
||||
select {
|
||||
case err := <-result:
|
||||
return err
|
||||
case <-time.After(timeout):
|
||||
return fmt.Errorf("fsync timeout after %v", timeout)
|
||||
}
|
||||
}
|
||||
|
||||
// MustFsync выполняет fsync и паникует при ошибке.
|
||||
// Используется только в критических секциях, где ошибка синхронизации фатальна.
|
||||
func MustFsync(file *os.File) {
|
||||
if file == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := file.Sync(); err != nil {
|
||||
panic(fmt.Sprintf("Fatal: fsync failed: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// IsFsyncSupported возвращает true если платформа поддерживает fsync.
|
||||
// Все платформы поддерживают fsync через file.Sync().
|
||||
func IsFsyncSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetFsyncPlatformInfo возвращает информацию о платформе для диагностики.
|
||||
func GetFsyncPlatformInfo() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"os": runtime.GOOS,
|
||||
"arch": runtime.GOARCH,
|
||||
"fsync_supported": IsFsyncSupported(),
|
||||
"dir_fsync_supported": !(runtime.GOOS == "windows" ||
|
||||
runtime.GOOS == "solaris" ||
|
||||
runtime.GOOS == "illumos" ||
|
||||
runtime.GOOS == "openindiana"),
|
||||
"method": "file.Sync()",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -857,6 +857,7 @@ func (pm *PersistenceManager) LoadDatabase(dbName string) error {
|
||||
}
|
||||
|
||||
// applyWALEntry применяет одну запись WAL к базе данных
|
||||
// TransactionCommitted = 1 (определено в transactions.go)
|
||||
func (pm *PersistenceManager) applyWALEntry(db *Database, entry WALEntry) error {
|
||||
// Проверяем тип записи (1 = Transaction)
|
||||
if entry.Type != "1" {
|
||||
@@ -882,12 +883,13 @@ func (pm *PersistenceManager) applyWALEntry(db *Database, entry WALEntry) error
|
||||
}
|
||||
|
||||
// Применяем только закоммиченные транзакции (state = 1)
|
||||
if txRecord.State != 1 { // TransactionCommitted
|
||||
// TransactionCommitted = 1 (определено в transactions.go через iota)
|
||||
if txRecord.State != 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, op := range txRecord.Operations {
|
||||
if op.Database != db.name {
|
||||
if op.Database != db.Name() {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user