From 194ca8e5a2a01a4d25501474786755b3813255be Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Thu, 11 Sep 2025 13:13:10 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=BF=D1=82=D0=B8=D0=BC=D0=B8=D0=B7?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B0=20=D1=81=D1=85=D0=B5?= =?UTF-8?q?=D0=BC=D0=B0=20=D0=91=D0=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/cdcdb/cdc/cas.d | 1 - source/cdcdb/db/dblite.d | 9 +-- source/cdcdb/db/scheme.d | 157 +++++++++++++-------------------------- source/cdcdb/db/types.d | 1 - test/app.d | 4 +- 5 files changed, 59 insertions(+), 113 deletions(-) diff --git a/source/cdcdb/cdc/cas.d b/source/cdcdb/cdc/cas.d index 16e054e..fd2e076 100644 --- a/source/cdcdb/cdc/cas.d +++ b/source/cdcdb/cdc/cas.d @@ -81,7 +81,6 @@ public: snapshotChunk.snapshotId = idSnapshot; snapshotChunk.chunkIndex = chunk.index; snapshotChunk.offset = chunk.offset; - snapshotChunk.size = chunk.size; snapshotChunk.sha256 = chunk.sha256; _db.addSnapshotChunk(snapshotChunk); diff --git a/source/cdcdb/db/dblite.d b/source/cdcdb/db/dblite.d index 242e9db..3a1f09c 100644 --- a/source/cdcdb/db/dblite.d +++ b/source/cdcdb/db/dblite.d @@ -115,13 +115,12 @@ public: { sql( q{ - INSERT INTO snapshot_chunks (snapshot_id, chunk_index, offset, size, sha256) - VALUES(?,?,?,?,?) + INSERT INTO snapshot_chunks (snapshot_id, chunk_index, offset, sha256) + VALUES(?,?,?,?) }, snapshotChunk.snapshotId, snapshotChunk.chunkIndex, snapshotChunk.offset, - snapshotChunk.size, snapshotChunk.sha256[] ); } @@ -225,8 +224,8 @@ public: SnapshotDataChunk[] getChunks(long snapshotId) { auto queryResult = sql( q{ - SELECT sc.chunk_index, sc.offset, sc.size, - b.content, b.zstd, b.z_size, b.sha256, b.z_sha256 + SELECT sc.chunk_index, sc.offset, + b.size, b.content, b.zstd, b.z_size, b.sha256, b.z_sha256 FROM snapshot_chunks sc JOIN blobs b ON b.sha256 = sc.sha256 WHERE sc.snapshot_id = ? diff --git a/source/cdcdb/db/scheme.d b/source/cdcdb/db/scheme.d index adfcb88..eabfa8e 100644 --- a/source/cdcdb/db/scheme.d +++ b/source/cdcdb/db/scheme.d @@ -1,124 +1,86 @@ auto _scheme = [ q{ -- ------------------------------------------------------------ - -- Метаданные снимка + -- Таблица snapshots -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS snapshots ( - -- Уникальный числовой идентификатор снимка. Используется во внешних ключах. + -- идентификатор снимка id INTEGER PRIMARY KEY AUTOINCREMENT, - - -- Путь к исходному файлу, для удобства навигации/поиска. + -- путь к исходному файлу file_path TEXT, - + -- SHA-256 всего файла (BLOB(32)) file_sha256 BLOB NOT NULL CHECK (length(file_sha256) = 32), - - -- Произвольная метка/название снимка (для человека). + -- метка/название снимка label TEXT, - - -- Время создания записи (UTC). По умолчанию - текущее. + -- время создания (UTC) created_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), - - -- Полная длина исходного файла в байтах (до разбиения на чанки). + -- длина исходного файла в байтах source_length INTEGER NOT NULL, - - -- Пороговые размеры FastCDC: минимальный/целевой/максимальный размер чанка (в байтах). - -- Фиксируются для воспроизводимости и сравнения результатов. + -- FastCDC: минимальный размер чанка algo_min INTEGER NOT NULL, + -- FastCDC: целевой размер чанка algo_normal INTEGER NOT NULL, + -- FastCDC: максимальный размер чанка algo_max INTEGER NOT NULL, - - -- Маски FastCDC для определения границ чанков (обычно вида 2^n - 1). - -- Хранятся для воспроизводимости. + -- FastCDC: маска S mask_s INTEGER NOT NULL, + -- FastCDC: маска L mask_l INTEGER NOT NULL, - - -- Состояние снимка: - -- 0 - "pending" - метаданные созданы, состав не полностью загружен; - -- 1 - "ready" - все чанки привязаны, снимок готов к использованию. + -- 0=pending, 1=ready status INTEGER NOT NULL DEFAULT 0 - CHECK (typeof(status) = "integer" AND status IN (0,1)) + CHECK (status IN (0,1)) ) }, q{ -- ------------------------------------------------------------ - -- Уникальные куски содержимого (дедупликация по sha256) + -- Таблица blobs -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS blobs ( - -- Хэш содержимого чанка. Храним как BLOB(32) (сырые 32 байта SHA-256). + -- SHA-256 исходного содержимого (BLOB(32)) sha256 BLOB PRIMARY KEY CHECK (length(sha256) = 32), - - -- Хэш сжатого содержимого (если zstd=1). Может быть NULL при zstd=0. + -- SHA-256 сжатого содержимого (BLOB(32)) или NULL z_sha256 BLOB, - - -- Размер чанка в байтах (до сжатия). + -- размер исходного содержимого, байт size INTEGER NOT NULL, - - -- Размер сжатого чанка (в байтах). Можно держать NOT NULL и заполнять =size при zstd=0, - -- либо сделать NULL при zstd=0 (см. CHECK ниже допускает NULL). + -- размер сжатого содержимого, байт (или NULL) z_size INTEGER, - - -- Сырые байты: при zstd=1 здесь лежит сжатый блок, иначе - исходные байты. + -- байты (сжатые при zstd=1, иначе исходные) content BLOB NOT NULL, - - -- Таймштампы + -- время создания записи (UTC) created_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), + -- время последней ссылки (UTC) last_seen_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), - - -- Счётчик ссылок из snapshot_chunks + -- число ссылок из snapshot_chunks refcount INTEGER NOT NULL DEFAULT 0, - - -- Флаг сжатия: 0 - без сжатия; 1 - zstd + -- 0=нет сжатия, 1=zstd zstd INTEGER NOT NULL DEFAULT 0 - CHECK (typeof(zstd) = "integer" AND zstd IN (0,1)), - - -- Дополнительные гарантии целостности: + CHECK (zstd IN (0,1)), CHECK (refcount >= 0), - - -- Если zstd=1, длина content должна равняться z_size; - -- если zstd=0 - длина content должна равняться size. CHECK ( - (zstd = 1 AND z_size IS NOT NULL AND length(content) = z_size) - OR (zstd = 0 AND length(content) = size ) + (zstd = 1 AND z_size IS NOT NULL AND length(content) = z_size) OR + (zstd = 0 AND length(content) = size) ), - - -- Согласованность z_sha256 (если задан) CHECK (z_sha256 IS NULL OR length(z_sha256) = 32) ) }, q{ -- ------------------------------------------------------------ - -- Состав снимка (упорядоченный список чанков) + -- Таблица snapshot_chunks -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS snapshot_chunks ( - -- Ссылка на snapshots.id. Определяет, к какому снимку относится строка. + -- FK -> snapshots.id snapshot_id INTEGER NOT NULL, - - -- Позиция чанка в снимке (индексация на твой выбор - 0/1-based; важно быть последовательным). - -- Обеспечивает порядок сборки. + -- порядковый номер чанка в снимке chunk_index INTEGER NOT NULL, - - -- Смещение чанка в исходном файле (в байтах). - -- Можно восстановить суммой size предыдущих чанков, но хранение ускоряет проверки/отладку. + -- смещение чанка в исходном файле, байт offset INTEGER, - - -- Размер конкретного чанка в составе (дублирует blobs.size для ускорения/валидации). - size INTEGER NOT NULL, - - -- Ссылка на blobs.sha256. Привязывает позицию к конкретному содержимому. - -- Тип BLOB обязан совпадать с типом в родительской таблице. + -- FK -> blobs.sha256 (BLOB(32)) sha256 BLOB NOT NULL, - - -- Уникальность позиции чанка в рамках одного снимка. PRIMARY KEY (snapshot_id, chunk_index), - - -- Внешние ключи и их поведение: - -- При удалении снимка его строки состава удаляются автоматически. FOREIGN KEY (snapshot_id) REFERENCES snapshots(id) ON UPDATE CASCADE ON DELETE CASCADE, - - -- Нельзя удалить blob, если он где-то используется; обновление ключа запрещено. FOREIGN KEY (sha256) REFERENCES blobs(sha256) ON UPDATE RESTRICT @@ -126,27 +88,20 @@ auto _scheme = [ ) }, q{ + -- Индекс для запросов вида: WHERE file_path=? AND file_sha256=? CREATE INDEX IF NOT EXISTS idx_snapshots_path_sha ON snapshots(file_path, file_sha256) }, q{ - -- Быстрый выбор всех чанков конкретного снимка (частый запрос). - CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_snapshot - ON snapshot_chunks(snapshot_id) - }, - q{ - -- Быстрый обратный поиск: где используется данный blob (для GC/аналитики). - -- Индекс по BLOB(32) хорошо работает для точного поиска sha256. + -- Индекс для обратного поиска использования blob по sha256 CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_sha ON snapshot_chunks(sha256) }, - // -- ------------------------------------------------------------ - // -- Триггеры для управления refcount и GC blob'ов - // -- ------------------------------------------------------------ + // ------------------------------------------------------------ + // Триггеры на поддержание refcount и статуса снимка + // ------------------------------------------------------------ q{ - -- Инкремент счётчика при привязке чанка к снимку. - -- Обновляем last_seen_utc, чтобы фиксировать "живость" контента. - -- ВАЖНО: FK гарантирует, что соответствующий blobs.sha256 уже существует. + -- AFTER INSERT: увеличить refcount и обновить last_seen_utc CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_ai AFTER INSERT ON snapshot_chunks BEGIN @@ -157,8 +112,7 @@ auto _scheme = [ END }, q{ - -- Декремент счётчика при удалении строки состава. - -- Если счётчик стал 0 - удаляем неиспользуемый blob (авто-GC). + -- AFTER DELETE: уменьшить refcount и удалить blob при refcount <= 0 CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_ad AFTER DELETE ON snapshot_chunks BEGIN @@ -172,9 +126,7 @@ auto _scheme = [ END }, q{ - -- Корректировка счётчиков при смене sha256 в составе. - -- Триггер срабатывает только при реальном изменении значения. - -- Предполагается, что NEW.sha256 существует в blobs (иначе FK не даст обновить). + -- AFTER UPDATE OF sha256: корректировка счётчиков при смене ссылки CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_au AFTER UPDATE OF sha256 ON snapshot_chunks FOR EACH ROW @@ -195,31 +147,28 @@ auto _scheme = [ END }, q{ - -- Автоматическая смена статуса снимка на 1 - "ready", - -- когда сумма размеров его чанков стала равна source_length. - -- Примечание: простая эвристика; если потом удалишь/поменяешь чанки, - -- триггер ниже вернёт статус обратно на 0 - "pending". + -- AFTER INSERT: установить status=1 при совпадении суммы размеров с source_length CREATE TRIGGER IF NOT EXISTS trg_snapshots_mark_ready AFTER INSERT ON snapshot_chunks BEGIN - UPDATE snapshots - SET status = 1 - WHERE id = NEW.snapshot_id - AND (SELECT COALESCE(SUM(size),0) - FROM snapshot_chunks - WHERE snapshot_id = NEW.snapshot_id) - = (SELECT source_length FROM snapshots WHERE id = NEW.snapshot_id); + UPDATE snapshots + SET status = 1 + WHERE id = NEW.snapshot_id + AND (SELECT COALESCE(SUM(b.size),0) + FROM snapshot_chunks sc + JOIN blobs b ON b.sha256 = sc.sha256 + WHERE sc.snapshot_id = NEW.snapshot_id) + = (SELECT source_length FROM snapshots WHERE id = NEW.snapshot_id); END }, q{ - -- При удалении любого чанка снимок снова помечается как 0 - "pending". - -- Это простой безопасный фоллбэк; следующая вставка приравняет суммы и вернёт 1 - "ready". + -- AFTER DELETE: установить status=0 для снимка, из которого удалён чанк CREATE TRIGGER IF NOT EXISTS trg_snapshots_mark_pending AFTER DELETE ON snapshot_chunks BEGIN - UPDATE snapshots - SET status = 0 - WHERE id = OLD.snapshot_id; + UPDATE snapshots + SET status = 0 + WHERE id = OLD.snapshot_id; END } ]; diff --git a/source/cdcdb/db/types.d b/source/cdcdb/db/types.d index 3ef0dcb..1c595a7 100644 --- a/source/cdcdb/db/types.d +++ b/source/cdcdb/db/types.d @@ -40,7 +40,6 @@ struct SnapshotChunk long snapshotId; long chunkIndex; long offset; - long size; ubyte[32] sha256; // BLOB(32) } diff --git a/test/app.d b/test/app.d index 9da94df..ee523a9 100644 --- a/test/app.d +++ b/test/app.d @@ -7,6 +7,6 @@ import std.file : read; void main() { auto cas = new CAS("/tmp/base.db", true); - // cas.saveSnapshot("/tmp/text", cast(ubyte[]) read("/tmp/text")); - cas.restoreSnapshot(); + cas.saveSnapshot("/tmp/text", cast(ubyte[]) read("/tmp/text")); + // cas.restoreSnapshot(); }