This commit is contained in:
Alexander Zhirov 2025-09-09 19:39:22 +03:00
commit dc0c8349c7
Signed by: alexander
GPG key ID: C8D8BE544A27C511
18 changed files with 666 additions and 0 deletions

36
source/cdcdb/cdc/cas.d Normal file
View file

@ -0,0 +1,36 @@
module cdcdb.cdc.cas;
import cdcdb.db;
import cdcdb.cdc.core;
final class CAS
{
private:
DBLite _db;
public:
this(string database)
{
_db = new DBLite(database);
}
size_t saveSnapshot(const(ubyte)[] data)
{
// Параметры для CDC вынести в отдельные настройки (продумать)
auto cdc = new CDC(100, 200, 500, 0xFF, 0x0F);
// Разбить на фрагменты
auto chunks = cdc.split(data);
import std.stdio : writeln;
_db.beginImmediate();
// Записать фрагменты в БД
foreach (chunk; chunks)
{
writeln(chunk.index);
}
_db.commit();
// Записать манифест в БД
// Вернуть ID манифеста
return 0;
}
}

90
source/cdcdb/cdc/core.d Normal file
View file

@ -0,0 +1,90 @@
module cdcdb.cdc.core;
import cdcdb.cdc.types;
import std.digest.sha : SHA256, digest;
final class CDC
{
private:
size_t _minSize, _normalSize, _maxSize;
ulong _maskS, _maskL;
// _gear
mixin(import("gear.d"));
size_t cut(const(ubyte)[] src) @safe nothrow
{
size_t size = src.length;
if (size == 0)
return 0;
if (size <= _minSize)
return size;
if (size > _maxSize)
size = _maxSize;
auto normalSize = _normalSize;
if (size < normalSize)
normalSize = size;
ulong fingerprint = 0;
size_t index;
// инициализация без cut-check
while (index < _minSize)
{
fingerprint = (fingerprint << 1) + _gear[src[index]];
++index;
}
// строгая маска
while (index < normalSize)
{
fingerprint = (fingerprint << 1) + _gear[src[index]];
if ((fingerprint & _maskS) == 0)
return index;
++index;
}
// слабая маска
while (index < size)
{
fingerprint = (fingerprint << 1) + _gear[src[index]];
if ((fingerprint & _maskL) == 0)
return index;
++index;
}
return size;
}
public:
this(size_t minSize, size_t normalSize, size_t maxSize, ulong maskS, ulong maskL) @safe @nogc nothrow
{
assert(minSize > 0 && minSize < normalSize && normalSize < maxSize,
"Неверные размеры: требуется min < normal < max и min > 0");
_minSize = minSize;
_normalSize = normalSize;
_maxSize = maxSize;
_maskS = maskS;
_maskL = maskL;
}
Chunk[] split(const(ubyte)[] data) @safe
{
Chunk[] chunks;
if (data.length == 0)
return chunks;
chunks.reserve(data.length / _normalSize);
size_t offset = 0;
size_t index = 1;
while (offset < data.length)
{
auto size = cut(data[offset .. $]);
auto bytes = data[offset .. offset + size];
ubyte[32] hash = digest!SHA256(bytes);
chunks ~= Chunk(index, offset, size, hash);
offset += size;
++index;
}
return chunks;
}
}

66
source/cdcdb/cdc/gear.d Normal file
View file

@ -0,0 +1,66 @@
immutable ulong[256] _gear = [
0x2722039f43c57a70, 0x338c1bd5b7ac5204, 0xf9f2c73ff33c98c0, 0x7dee12e6cd31cb32,
0x9688335e0f2decfd, 0x5307003c8e60b963, 0xfd2a2848eb358095, 0xc3614773074ee6b7,
0x6e35235234b6ed0a, 0x9d4cfa9d8e3850cc, 0xaa1b3d8af8ad86bd, 0x79c6d2e28bfb333d,
0x3df08966a00c33ec, 0xfd58bbf83f38c690, 0x5ef9ee9a4552545b, 0x099192a7e5599bdc,
0xa8f2419947f21017, 0xd6a03d010f2fda7c, 0x1fe53de04074fc20, 0x75b5aff7c66605f8,
0x1a94b7484bf509a9, 0xbf2e371a53466ded, 0xedcf13f8eb0f0fdf, 0xfba81285ead7dafe,
0x839fb29274557fa5, 0xeefe64b15cc7f7f0, 0x7d15f8e862726515, 0x59b416e43cca2adc,
0x9c2c925dcde12d4a, 0xf3df9373c3e63a07, 0x747cb5ec08ffa4ef, 0x26c93138f3f19b29,
0xcdade11723bd59ed, 0xc7a6a7d0c18642cb, 0x88c2976f22f5d084, 0x7c48f54cdaf480fe,
0x91ea7c7fd3d06d54, 0xed3e31236e8c9366, 0xa16da2234f420bc4, 0x5ee6136449d30974,
0xe32a921921181e16, 0xa6ab2fb8c7212257, 0x754a8a581ce819ca, 0x22b2de3e7c7a2f57,
0xd2773285e49b9160, 0x19b0449384554129, 0x145e7d2c46da460e, 0xdd720d0e79a3615d,
0x621ae4f0ad576223, 0x4f6da235cc25d5c9, 0x6d6d39e005d67437, 0x5839c8f3d2f71122,
0x691a668bc7f5c153, 0xb2eb484f8546c61d, 0x7955c346c34cbcdc, 0x413c2a0ba6fd0a88,
0x29ad3d418323592b, 0xb7d04d0abf3f8d50, 0x76e742f91dfc77ea, 0x8a5c80d1a0d5ff5c,
0xce1aa9b0bdd16adc, 0x74e4bd6f412c8186, 0xbf1dddc8f63dfc08, 0x11dcb84c1b5c32cb,
0x3320ed259fc0d8c0, 0x13dbd4c934c58e01, 0x344b61dd3741a9f9, 0x935861bea84a6f81,
0xaf70eea3844052f9, 0xc0a83f93799c2e81, 0xdd23b2a943a5af16, 0x05b4efd89b3e818b,
0x75b2a3d0fe099aec, 0x5aab5599ae580c37, 0xe9b64238ed626a6b, 0xb63f47c31c31ec1d,
0x0b50ee03c3425dde, 0xf287ebed924249f6, 0xe09eee62604318c4, 0x0d334cb1bd82bc13,
0xc41abf3d95b18620, 0x869c3bc45e2c9edf, 0x526de53484e093c7, 0xc640fee4784fd9ce,
0x761637787d81c0ea, 0x817bf175cb17e903, 0x94a4846f1158f988, 0x99c254e5f8e698e0,
0xa4623d4d1b76352e, 0x326dae4493475c3a, 0xed2944be79511208, 0x163a0a9b65f40339,
0x336489f8c2f6190c, 0x670d217f8e6bee33, 0x662e19c285c5a4a1, 0xcab8f4512d0b251a,
0x61d4476812ed1017, 0x0ec77209307430af, 0x20a94905901093dc, 0xaa9fe2cae9ffa699,
0xc75f757de6c045dc, 0x141ef38478656459, 0x9b3ce9c4e3dd7858, 0x3ab62b9aa45a3d0d,
0x61a89423e18e5e68, 0x3802972ecadf592d, 0xcfc85c5724ff3af8, 0x381ee916e97a628a,
0x2fa2c37a040e488a, 0x9813a505b4ca4036, 0xc4254f1aaf7b2f42, 0xe8a0720b79a1188d,
0xe663a71adb5d53e3, 0x6e3b5927934102af, 0xbd8c502741b1fcb1, 0x1af6fa2fb1d2e5a6,
0xc88d367a79d06f5d, 0x29fe7cdab66530d9, 0x34bef2ebe612d95f, 0x9ab6977a57db1fa2,
0x73774fc29deac09a, 0x7832f37495fd28fb, 0x1559a3badfbd42a6, 0x7e6831522a50d2bc,
0xddb8564f3aafe3b7, 0x86acb9eca71bc09d, 0x21b0a9469727d4fc, 0x26d3b66f525ebcab,
0x77e3fd126fd97e3a, 0x5306b81a9fe2a92e, 0x7292138f116d8911, 0x285b466c9939b076,
0x40527805d9a4379d, 0x8986c05119c7ca1e, 0x6a7890c402303c31, 0xb1b109dc109405bc,
0x1d71f3997b288f30, 0xfa203ff4dc9ea72c, 0x8ae3eea975cc92da, 0x3468e4305eabb928,
0xd79c37e720467df1, 0x011e6490c0f832d2, 0x29ce2ada8509647a, 0xb4e325b9f3ba783c,
0xa812ca4fad720763, 0x0cdf098645ccb476, 0xf6b47e21637fcd76, 0x3597f48148a297de,
0x5875212868ab81ec, 0x1ea36243940596bb, 0xfd93ac7c39a27586, 0xabb09b0f803a7214,
0x8cc8ec1ea21a16af, 0x824a0db50ae906d1, 0x3d972fb701ca3e70, 0xda60d493e9a20bd0,
0x97d282f6bda26087, 0x9bc8f7842af296d0, 0x14804a1663a0cf7e, 0x3b71cc25885e75f3,
0x131adc05e336042b, 0x566aa36d26eee86c, 0x97d4c4d4fd4b0dd1, 0xd2407b1485c7bee1,
0xcad613e7b92e6df1, 0xe3ceccd99d975088, 0x99e6b93ff96a2636, 0x1ad75dbed057f0d0,
0x5e3ba609dd100c6e, 0x9c5efa00b33a18f3, 0xad89369e692bdb28, 0xf7a546fca26d1d7d,
0x5813db1fe943575f, 0x24c3467f03a144ae, 0xc892f2ce492cb7c8, 0xc44672263508d34b,
0xd400e1c0a5734a40, 0x3ca24ee74bf8e84f, 0xd83bd4e907c351a5, 0xe142297005fa9aa8,
0x0f6d796cf68abda0, 0x6c8e25bc6d9ae2e8, 0xccc235f322a42cf3, 0xabaf39cea8ca450c,
0x02b9cdf615a0d7b6, 0x8aaf7d8b55d4dc39, 0xbe2c2bc6ef13c6c5, 0x6ad98aa4a4bc610f,
0x1051a62ac2a2b434, 0xbd167e6eba260d35, 0xb9b86ac04ac4f811, 0xabe8a6453e196739,
0x439ff734b19246b4, 0xcea324040c9e8981, 0x87f55cf1035e1a22, 0xa227d679c33597f9,
0xbf4d654b6cdd0015, 0xc0302ec55f87a46e, 0xed32173466c70a83, 0x8ceb757b648d2bf2,
0x1873757a6d17446b, 0xeb0f366fea62e77e, 0x145aa2795d34dd93, 0x2fc378be4c471db0,
0x6d1274fb8f6364a2, 0x602a56fd1cc36728, 0x5f8aa6e0c892b4b5, 0x33e2c5653d8b1ad6,
0x1f6c8b2a004714f4, 0x4042b98d54acbfef, 0x4606386f11f6456f, 0xf56bd21a8a35c540,
0xd2b23c57b3718e1f, 0x94726832fe96e61d, 0xa225b072752a823b, 0x0bd957cf585f8cda,
0x533d819bb30b4221, 0xda0f9cff9a0115fa, 0xd14de3b8fe3354ea, 0xa96328e96d9364c0,
0x9078dc0eff2676ab, 0x22585cd4521c6210, 0x5903254df4e402a5, 0x1b54b71b55ae697a,
0xb899b86756b2aa39, 0x5d5d2dd5cd0bce8b, 0x7b3a78a4a0662015, 0xa9fbfc7678fc7931,
0xa732d694f6ab64a0, 0x9fc960e7db3e9716, 0x76c765948f3c2ba5, 0x076a509dca2a4349,
0xca5bfc5973661e59, 0x454ec4d49bddd45d, 0x56115e001997cee2, 0xd689eb8926051c7f,
0xf50df8ca9c355e3f, 0x88a375a9f0492a69, 0xe059fd001d50439a, 0x765c5d6f66d5e788,
0xaf57f4eea178f896, 0x06e8cca68730fbbd, 0xb7b1f6f86904ce4e, 0x3c3b10b0c08cf0bf,
0x1e0e310524778bd4, 0xd65d7cd93cde7c69, 0x18543b187c77fcf3, 0x180f6cdd1af3a60a,
0xe1cd4c2bc3656704, 0x218fdfc5aa282d00, 0x844eeaf2e439b242, 0x05df1a59e415b4c6,
0x14abdd3ace097c2c, 0x7f3b0705b04b14d4, 0xf69c57f60180332b, 0x165fc3f0e65db80f
];

View file

@ -0,0 +1,3 @@
module cdcdb.cdc;
public import cdcdb.cdc.cas;

20
source/cdcdb/cdc/types.d Normal file
View file

@ -0,0 +1,20 @@
module cdcdb.cdc.types;
/// Единица разбиения
struct Chunk
{
size_t index; // 1..N
size_t offset; // смещение в исходном буфере
size_t size; // размер чанка
ubyte[32] sha256; // hex(SHA-256) содержимого
}
/// Метаданные снимка
struct SnapshotInfo
{
size_t id;
string createdUTC; // ISO-8601
string label;
size_t sourceLength;
size_t chunks;
}

47
source/cdcdb/db/dblite.d Normal file
View file

@ -0,0 +1,47 @@
module cdcdb.db.dblite;
import arsd.sqlite;
import std.file : exists, isFile;
final class DBLite : Sqlite
{
private:
string _dbPath;
// _scheme
mixin(import("scheme.d"));
public:
this(string database)
{
_dbPath = database;
super(database);
foreach (schemeQuery; _scheme)
{
sql(schemeQuery);
}
query("PRAGMA journal_mode=WAL");
query("PRAGMA synchronous=NORMAL");
query("PRAGMA foreign_keys=ON");
}
void beginImmediate()
{
query("BEGIN IMMEDIATE");
}
void commit()
{
query("COMMIT");
}
void rollback()
{
query("ROLLBACK");
}
SqliteResult sql(T...)(string queryText, T args)
{
return cast(SqliteResult) query(queryText, args);
}
}

View file

@ -0,0 +1,3 @@
module cdcdb.db;
public import cdcdb.db.dblite;

75
source/cdcdb/db/scheme.d Normal file
View file

@ -0,0 +1,75 @@
auto _scheme = [
q{
-- Метаданные снапшота
CREATE TABLE IF NOT EXISTS snapshots (
-- Уникальный числовой идентификатор снимка. Используется во внешних ключах.
id INTEGER PRIMARY KEY AUTOINCREMENT,
-- Произвольная метка/название снимка.
label TEXT,
-- Время создания записи в UTC. По умолчанию - сейчас.
created_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
-- Полная длина исходного файла в байтах для этого снимка (до разбиения на чанки).
source_length INTEGER NOT NULL,
-- Пороговые размеры FastCDC (минимальный/целевой/максимальный размер чанка) в байтах.
-- Фиксируются здесь, чтобы позже можно было корректно пересобрать/сравнить.
algo_min INTEGER NOT NULL,
algo_normal INTEGER NOT NULL,
algo_max INTEGER NOT NULL,
-- Маски для определения границ чанков (быстрый роллинг-хэш/FastCDC).
-- Обычно степени вида 2^n - 1. Хранятся для воспроизводимости.
mask_s INTEGER NOT NULL,
mask_l INTEGER NOT NULL,
-- Состояние снимка:
-- pending - метаданные созданы, состав не полностью загружен;
-- ready - все чанки привязаны, снимок готов к использованию.
status TEXT NOT NULL DEFAULT "pending" CHECK (status IN ("pending","ready"))
)
},
q{
-- Уникальные куски содержимого (сам контент в БД)
CREATE TABLE IF NOT EXISTS blobs (
-- Хэш содержимого чанка. Ключ обеспечивает дедупликацию: одинаковый контент хранится один раз.
sha256 TEXT PRIMARY KEY,
-- Размер этого чанка в байтах.
size INTEGER NOT NULL,
-- Сырые байты чанка.
content BLOB NOT NULL,
-- Когда этот контент впервые появился в базе (UTC).
created_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP)
)
},
q{
-- Состав снапшота (порядок чанков важен)
CREATE TABLE IF NOT EXISTS snapshot_chunks (
-- Ссылка на snapshots.id. Определяет, к какому снимку относится строка.
snapshot_id INTEGER NOT NULL,
-- Позиция чанка в снимке (индексация).
-- Обеспечивает порядок сборки.
chunk_index INTEGER NOT NULL,
-- Смещение чанка в исходном файле в байтах.
-- Можно восстановить как сумму size предыдущих чанков по chunk_index,
-- но хранение ускоряет проверки/отладку.
offset INTEGER,
-- Размер именно этого чанка в байтах (дублирует blobs.size для быстрого доступа и валидации).
size INTEGER NOT NULL,
-- Ссылка на blobs.sha256. Привязывает позицию в снимке к конкретному содержимому.
sha256 TEXT NOT NULL,
-- Гарантирует уникальность позиции чанка в рамках снимка и задаёт естественный порядок.
PRIMARY KEY (snapshot_id, chunk_index),
-- При удалении снимка его строки состава удаляются автоматически.
-- Обновления id каскадятся (на практике id не меняют).
FOREIGN KEY (snapshot_id) REFERENCES snapshots(id) ON UPDATE CASCADE ON DELETE CASCADE,
-- Нельзя удалить blob, если он где-то используется (RESTRICT).
-- Обновление хэша каскадится (редкий случай).
FOREIGN KEY (sha256) REFERENCES blobs(sha256) ON UPDATE CASCADE ON DELETE RESTRICT
)
},
q{
-- Быстрый выбор всех чанков конкретного снимка (частый запрос).
CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_snapshot ON snapshot_chunks(snapshot_id)
},
q{
-- Быстрый обратный поиск: где используется данный blob (для GC/аналитики).
CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_sha ON snapshot_chunks(sha256)
}
];

151
source/cdcdb/db/scheme.md Normal file
View file

@ -0,0 +1,151 @@
# Схемы базы данных для хранения снимков (фрагментов)
## Структура базы данных
```mermaid
erDiagram
%% Композитный PK у SNAPSHOT_CHUNKS: (snapshot_id, chunk_index)
SNAPSHOTS {
int id PK
string label
string created_utc
int source_length
int algo_min
int algo_normal
int algo_max
int mask_s
int mask_l
string status
}
BLOBS {
string sha256 PK
int size
blob content
string created_utc
}
SNAPSHOT_CHUNKS {
int snapshot_id FK
int chunk_index
int offset
int size
string sha256 FK
}
%% Связи и поведение внешних ключей
SNAPSHOTS ||--o{ SNAPSHOT_CHUNKS : "1:N, ON DELETE CASCADE"
BLOBS ||--o{ SNAPSHOT_CHUNKS : "1:N, ON DELETE RESTRICT"
```
## Схема последовательности записи в базу данных
```mermaid
sequenceDiagram
autonumber
participant APP as Приложение
participant CH as Разбиение на чанки (FastCDC)
participant HS as Хеширование (SHA-256)
participant DB as База данных (SQLite)
Note over APP,DB: Подготовка
APP->>DB: Открывает соединение, включает PRAGMA (WAL, foreign_keys=ON)
APP->>DB: BEGIN IMMEDIATE (начать транзакцию с блокировкой на запись)
Note over APP,DB: Создание метаданных снимка
APP->>DB: INSERT INTO snapshots(label, source_length, algo_min, algo_normal, algo_max, mask_s, mask_l, status='pending')
DB-->>APP: id снимка = last_insert_rowid()
Note over APP,CH: Поток файла → чанки
APP->>CH: Читает файл, передает параметры FastCDC (min/normal/max, mask_s/mask_l)
loop Для каждого чанка в порядке следования
CH-->>APP: Возвращает {chunk_index, offset, size, bytes}
Note over APP,HS: Хеш содержимого
APP->>HS: Вычисляет SHA-256(bytes)
HS-->>APP: digest (sha256)
Note over APP,DB: Дедупликация контента
APP->>DB: SELECT 1 FROM blobs WHERE sha256 = ?
alt Блоб отсутствует
APP->>DB: INSERT INTO blobs(sha256, size, content)
DB-->>APP: OK
else Блоб уже есть
DB-->>APP: Найден (пропускаем вставку содержимого)
end
Note over APP,DB: Привязка чанка к снимку
APP->>DB: INSERT INTO snapshot_chunks(snapshot_id, chunk_index, offset, size, sha256)
DB-->>APP: OK (PK: (snapshot_id, chunk_index))
end
Note over APP,DB: Валидация и завершение
APP->>DB: SELECT SUM(size) FROM snapshot_chunks WHERE snapshot_id = ?
DB-->>APP: total_size
alt total_size == snapshots.source_length
APP->>DB: UPDATE snapshots SET status='ready' WHERE id = ?
APP->>DB: COMMIT
DB-->>APP: Транзакция зафиксирована
else Несоответствие размеров или ошибка
APP->>DB: ROLLBACK
DB-->>APP: Откат изменений
APP-->>APP: Логирует ошибку, возвращает код/исключение
end
```
## Схема последовательности восстановления из базы данных
```mermaid
sequenceDiagram
autonumber
participant APP as Приложение
participant DB as База данных (SQLite)
participant FS as Целевой файл
participant HS as Хеширование (опц.)
Note over APP,DB: Подготовка к чтению
APP->>DB: Открывает соединение (read), BEGIN (снимок чтения)
Note over APP,DB: Выбор снимка
APP->>DB: Находит нужный снимок по id/label, читает status и source_length
DB-->>APP: id, status, source_length
alt status == "ready"
else снимок не готов
APP-->>APP: Прерывает восстановление с ошибкой
DB-->>APP: END
end
Note over APP,DB: Получение состава снимка
APP->>DB: SELECT chunk_index, offset, size, sha256 FROM snapshot_chunks WHERE snapshot_id=? ORDER BY chunk_index
DB-->>APP: Строки чанков в порядке chunk_index
loop Для каждого чанка
APP->>DB: SELECT content, size FROM blobs WHERE sha256=?
DB-->>APP: content, blob_size
Note over APP,HS: (опц.) контроль целостности чанка
APP->>HS: Вычисляет SHA-256(content)
HS-->>APP: digest
APP-->>APP: Сверяет digest с sha256 и size с blob_size
alt offset задан
APP->>FS: Позиционируется на offset и пишет content (pwrite/seek+write)
else offset отсутствует
APP->>FS: Дописывает content в конец файла
end
end
Note over APP,DB: Финальная проверка
APP-->>APP: Суммирует размеры записанных чанков → total_size
APP->>DB: Берёт snapshots.source_length
DB-->>APP: source_length
alt total_size == source_length
APP->>FS: fsync и close
DB-->>APP: END
APP-->>APP: Успешное восстановление
else размеры не совпали
APP->>FS: Удаляет/помечает файл как повреждённый
DB-->>APP: END
APP-->>APP: Фиксирует ошибку (несоответствие сумм)
end
```

3
source/cdcdb/package.d Normal file
View file

@ -0,0 +1,3 @@
module cdcdb;
public import cdcdb.cdc;