Оптимизирована схема БД

This commit is contained in:
Alexander Zhirov 2025-09-11 13:13:10 +03:00
parent 541d09b8f4
commit d62524e185
Signed by: alexander
GPG key ID: C8D8BE544A27C511
5 changed files with 61 additions and 115 deletions

View file

@ -37,7 +37,7 @@ public:
// Параметры для CDC вынести в отдельные настройки (продумать) // Параметры для CDC вынести в отдельные настройки (продумать)
auto cdc = new CDC(300, 700, 1000, 0xFF, 0x0F); auto cdc = new CDC(256, 512, 1024, 0xFF, 0x0F);
// Разбить на фрагменты // Разбить на фрагменты
auto chunks = cdc.split(data); auto chunks = cdc.split(data);
@ -81,7 +81,6 @@ public:
snapshotChunk.snapshotId = idSnapshot; snapshotChunk.snapshotId = idSnapshot;
snapshotChunk.chunkIndex = chunk.index; snapshotChunk.chunkIndex = chunk.index;
snapshotChunk.offset = chunk.offset; snapshotChunk.offset = chunk.offset;
snapshotChunk.size = chunk.size;
snapshotChunk.sha256 = chunk.sha256; snapshotChunk.sha256 = chunk.sha256;
_db.addSnapshotChunk(snapshotChunk); _db.addSnapshotChunk(snapshotChunk);

View file

@ -115,13 +115,12 @@ public:
{ {
sql( sql(
q{ q{
INSERT INTO snapshot_chunks (snapshot_id, chunk_index, offset, size, sha256) INSERT INTO snapshot_chunks (snapshot_id, chunk_index, offset, sha256)
VALUES(?,?,?,?,?) VALUES(?,?,?,?)
}, },
snapshotChunk.snapshotId, snapshotChunk.snapshotId,
snapshotChunk.chunkIndex, snapshotChunk.chunkIndex,
snapshotChunk.offset, snapshotChunk.offset,
snapshotChunk.size,
snapshotChunk.sha256[] snapshotChunk.sha256[]
); );
} }
@ -225,8 +224,8 @@ public:
SnapshotDataChunk[] getChunks(long snapshotId) { SnapshotDataChunk[] getChunks(long snapshotId) {
auto queryResult = sql( auto queryResult = sql(
q{ q{
SELECT sc.chunk_index, sc.offset, sc.size, SELECT sc.chunk_index, sc.offset,
b.content, b.zstd, b.z_size, b.sha256, b.z_sha256 b.size, b.content, b.zstd, b.z_size, b.sha256, b.z_sha256
FROM snapshot_chunks sc FROM snapshot_chunks sc
JOIN blobs b ON b.sha256 = sc.sha256 JOIN blobs b ON b.sha256 = sc.sha256
WHERE sc.snapshot_id = ? WHERE sc.snapshot_id = ?

View file

@ -1,124 +1,86 @@
auto _scheme = [ auto _scheme = [
q{ q{
-- ------------------------------------------------------------ -- ------------------------------------------------------------
-- Метаданные снимка -- Таблица snapshots
-- ------------------------------------------------------------ -- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS snapshots ( CREATE TABLE IF NOT EXISTS snapshots (
-- Уникальный числовой идентификатор снимка. Используется во внешних ключах. -- идентификатор снимка
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
-- путь к исходному файлу
-- Путь к исходному файлу, для удобства навигации/поиска.
file_path TEXT, file_path TEXT,
-- SHA-256 всего файла (BLOB(32))
file_sha256 BLOB NOT NULL CHECK (length(file_sha256) = 32), file_sha256 BLOB NOT NULL CHECK (length(file_sha256) = 32),
-- метка/название снимка
-- Произвольная метка/название снимка (для человека).
label TEXT, label TEXT,
-- время создания (UTC)
-- Время создания записи (UTC). По умолчанию - текущее.
created_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), created_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
-- длина исходного файла в байтах
-- Полная длина исходного файла в байтах (до разбиения на чанки).
source_length INTEGER NOT NULL, source_length INTEGER NOT NULL,
-- FastCDC: минимальный размер чанка
-- Пороговые размеры FastCDC: минимальный/целевой/максимальный размер чанка (в байтах).
-- Фиксируются для воспроизводимости и сравнения результатов.
algo_min INTEGER NOT NULL, algo_min INTEGER NOT NULL,
-- FastCDC: целевой размер чанка
algo_normal INTEGER NOT NULL, algo_normal INTEGER NOT NULL,
-- FastCDC: максимальный размер чанка
algo_max INTEGER NOT NULL, algo_max INTEGER NOT NULL,
-- FastCDC: маска S
-- Маски FastCDC для определения границ чанков (обычно вида 2^n - 1).
-- Хранятся для воспроизводимости.
mask_s INTEGER NOT NULL, mask_s INTEGER NOT NULL,
-- FastCDC: маска L
mask_l INTEGER NOT NULL, mask_l INTEGER NOT NULL,
-- 0=pending, 1=ready
-- Состояние снимка:
-- 0 - "pending" - метаданные созданы, состав не полностью загружен;
-- 1 - "ready" - все чанки привязаны, снимок готов к использованию.
status INTEGER NOT NULL DEFAULT 0 status INTEGER NOT NULL DEFAULT 0
CHECK (typeof(status) = "integer" AND status IN (0,1)) CHECK (status IN (0,1))
) )
}, },
q{ q{
-- ------------------------------------------------------------ -- ------------------------------------------------------------
-- Уникальные куски содержимого (дедупликация по sha256) -- Таблица blobs
-- ------------------------------------------------------------ -- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS blobs ( CREATE TABLE IF NOT EXISTS blobs (
-- Хэш содержимого чанка. Храним как BLOB(32) (сырые 32 байта SHA-256). -- SHA-256 исходного содержимого (BLOB(32))
sha256 BLOB PRIMARY KEY CHECK (length(sha256) = 32), sha256 BLOB PRIMARY KEY CHECK (length(sha256) = 32),
-- SHA-256 сжатого содержимого (BLOB(32)) или NULL
-- Хэш сжатого содержимого (если zstd=1). Может быть NULL при zstd=0.
z_sha256 BLOB, z_sha256 BLOB,
-- размер исходного содержимого, байт
-- Размер чанка в байтах (до сжатия).
size INTEGER NOT NULL, size INTEGER NOT NULL,
-- размер сжатого содержимого, байт
-- Размер сжатого чанка (в байтах). Можно держать NOT NULL и заполнять =size при zstd=0, z_size INTEGER NOT NULL,
-- либо сделать NULL при zstd=0 (см. CHECK ниже допускает NULL). -- байты (сжатые при zstd=1, иначе исходные)
z_size INTEGER,
-- Сырые байты: при zstd=1 здесь лежит сжатый блок, иначе - исходные байты.
content BLOB NOT NULL, content BLOB NOT NULL,
-- время создания записи (UTC)
-- Таймштампы
created_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), created_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
-- время последней ссылки (UTC)
last_seen_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), last_seen_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
-- число ссылок из snapshot_chunks
-- Счётчик ссылок из snapshot_chunks
refcount INTEGER NOT NULL DEFAULT 0, refcount INTEGER NOT NULL DEFAULT 0,
-- 0=нет сжатия, 1=zstd
-- Флаг сжатия: 0 - без сжатия; 1 - zstd
zstd INTEGER NOT NULL DEFAULT 0 zstd INTEGER NOT NULL DEFAULT 0
CHECK (typeof(zstd) = "integer" AND zstd IN (0,1)), CHECK (zstd IN (0,1)),
-- Дополнительные гарантии целостности:
CHECK (refcount >= 0), CHECK (refcount >= 0),
-- Если zstd=1, длина content должна равняться z_size;
-- если zstd=0 - длина content должна равняться size.
CHECK ( CHECK (
(zstd = 1 AND z_size IS NOT NULL AND length(content) = z_size) (zstd = 1 AND length(content) = z_size) OR
OR (zstd = 0 AND length(content) = size ) (zstd = 0 AND length(content) = size)
), ),
-- Согласованность z_sha256 (если задан)
CHECK (z_sha256 IS NULL OR length(z_sha256) = 32) CHECK (z_sha256 IS NULL OR length(z_sha256) = 32)
) )
}, },
q{ q{
-- ------------------------------------------------------------ -- ------------------------------------------------------------
-- Состав снимка (упорядоченный список чанков) -- Таблица snapshot_chunks
-- ------------------------------------------------------------ -- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS snapshot_chunks ( CREATE TABLE IF NOT EXISTS snapshot_chunks (
-- Ссылка на snapshots.id. Определяет, к какому снимку относится строка. -- FK -> snapshots.id
snapshot_id INTEGER NOT NULL, snapshot_id INTEGER NOT NULL,
-- порядковый номер чанка в снимке
-- Позиция чанка в снимке (индексация на твой выбор - 0/1-based; важно быть последовательным).
-- Обеспечивает порядок сборки.
chunk_index INTEGER NOT NULL, chunk_index INTEGER NOT NULL,
-- смещение чанка в исходном файле, байт
-- Смещение чанка в исходном файле (в байтах).
-- Можно восстановить суммой size предыдущих чанков, но хранение ускоряет проверки/отладку.
offset INTEGER, offset INTEGER,
-- FK -> blobs.sha256 (BLOB(32))
-- Размер конкретного чанка в составе (дублирует blobs.size для ускорения/валидации).
size INTEGER NOT NULL,
-- Ссылка на blobs.sha256. Привязывает позицию к конкретному содержимому.
-- Тип BLOB обязан совпадать с типом в родительской таблице.
sha256 BLOB NOT NULL, sha256 BLOB NOT NULL,
-- Уникальность позиции чанка в рамках одного снимка.
PRIMARY KEY (snapshot_id, chunk_index), PRIMARY KEY (snapshot_id, chunk_index),
-- Внешние ключи и их поведение:
-- При удалении снимка его строки состава удаляются автоматически.
FOREIGN KEY (snapshot_id) FOREIGN KEY (snapshot_id)
REFERENCES snapshots(id) REFERENCES snapshots(id)
ON UPDATE CASCADE ON UPDATE CASCADE
ON DELETE CASCADE, ON DELETE CASCADE,
-- Нельзя удалить blob, если он где-то используется; обновление ключа запрещено.
FOREIGN KEY (sha256) FOREIGN KEY (sha256)
REFERENCES blobs(sha256) REFERENCES blobs(sha256)
ON UPDATE RESTRICT ON UPDATE RESTRICT
@ -126,27 +88,20 @@ auto _scheme = [
) )
}, },
q{ q{
-- Индекс для запросов вида: WHERE file_path=? AND file_sha256=?
CREATE INDEX IF NOT EXISTS idx_snapshots_path_sha CREATE INDEX IF NOT EXISTS idx_snapshots_path_sha
ON snapshots(file_path, file_sha256) ON snapshots(file_path, file_sha256)
}, },
q{ q{
-- Быстрый выбор всех чанков конкретного снимка (частый запрос). -- Индекс для обратного поиска использования blob по sha256
CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_snapshot
ON snapshot_chunks(snapshot_id)
},
q{
-- Быстрый обратный поиск: где используется данный blob (для GC/аналитики).
-- Индекс по BLOB(32) хорошо работает для точного поиска sha256.
CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_sha CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_sha
ON snapshot_chunks(sha256) ON snapshot_chunks(sha256)
}, },
// -- ------------------------------------------------------------ // ------------------------------------------------------------
// -- Триггеры для управления refcount и GC blob'ов // Триггеры на поддержание refcount и статуса снимка
// -- ------------------------------------------------------------ // ------------------------------------------------------------
q{ q{
-- Инкремент счётчика при привязке чанка к снимку. -- AFTER INSERT: увеличить refcount и обновить last_seen_utc
-- Обновляем last_seen_utc, чтобы фиксировать "живость" контента.
-- ВАЖНО: FK гарантирует, что соответствующий blobs.sha256 уже существует.
CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_ai CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_ai
AFTER INSERT ON snapshot_chunks AFTER INSERT ON snapshot_chunks
BEGIN BEGIN
@ -157,8 +112,7 @@ auto _scheme = [
END END
}, },
q{ q{
-- Декремент счётчика при удалении строки состава. -- AFTER DELETE: уменьшить refcount и удалить blob при refcount <= 0
-- Если счётчик стал 0 - удаляем неиспользуемый blob (авто-GC).
CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_ad CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_ad
AFTER DELETE ON snapshot_chunks AFTER DELETE ON snapshot_chunks
BEGIN BEGIN
@ -172,9 +126,7 @@ auto _scheme = [
END END
}, },
q{ q{
-- Корректировка счётчиков при смене sha256 в составе. -- AFTER UPDATE OF sha256: корректировка счётчиков при смене ссылки
-- Триггер срабатывает только при реальном изменении значения.
-- Предполагается, что NEW.sha256 существует в blobs (иначе FK не даст обновить).
CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_au CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_au
AFTER UPDATE OF sha256 ON snapshot_chunks AFTER UPDATE OF sha256 ON snapshot_chunks
FOR EACH ROW FOR EACH ROW
@ -195,31 +147,28 @@ auto _scheme = [
END END
}, },
q{ q{
-- Автоматическая смена статуса снимка на 1 - "ready", -- AFTER INSERT: установить status=1 при совпадении суммы размеров с source_length
-- когда сумма размеров его чанков стала равна source_length.
-- Примечание: простая эвристика; если потом удалишь/поменяешь чанки,
-- триггер ниже вернёт статус обратно на 0 - "pending".
CREATE TRIGGER IF NOT EXISTS trg_snapshots_mark_ready CREATE TRIGGER IF NOT EXISTS trg_snapshots_mark_ready
AFTER INSERT ON snapshot_chunks AFTER INSERT ON snapshot_chunks
BEGIN BEGIN
UPDATE snapshots UPDATE snapshots
SET status = 1 SET status = 1
WHERE id = NEW.snapshot_id WHERE id = NEW.snapshot_id
AND (SELECT COALESCE(SUM(size),0) AND (SELECT COALESCE(SUM(b.size),0)
FROM snapshot_chunks FROM snapshot_chunks sc
WHERE snapshot_id = NEW.snapshot_id) JOIN blobs b ON b.sha256 = sc.sha256
= (SELECT source_length FROM snapshots WHERE id = NEW.snapshot_id); WHERE sc.snapshot_id = NEW.snapshot_id)
= (SELECT source_length FROM snapshots WHERE id = NEW.snapshot_id);
END END
}, },
q{ q{
-- При удалении любого чанка снимок снова помечается как 0 - "pending". -- AFTER DELETE: установить status=0 для снимка, из которого удалён чанк
-- Это простой безопасный фоллбэк; следующая вставка приравняет суммы и вернёт 1 - "ready".
CREATE TRIGGER IF NOT EXISTS trg_snapshots_mark_pending CREATE TRIGGER IF NOT EXISTS trg_snapshots_mark_pending
AFTER DELETE ON snapshot_chunks AFTER DELETE ON snapshot_chunks
BEGIN BEGIN
UPDATE snapshots UPDATE snapshots
SET status = 0 SET status = 0
WHERE id = OLD.snapshot_id; WHERE id = OLD.snapshot_id;
END END
} }
]; ];

View file

@ -40,7 +40,6 @@ struct SnapshotChunk
long snapshotId; long snapshotId;
long chunkIndex; long chunkIndex;
long offset; long offset;
long size;
ubyte[32] sha256; // BLOB(32) ubyte[32] sha256; // BLOB(32)
} }

View file

@ -7,6 +7,6 @@ import std.file : read;
void main() void main()
{ {
auto cas = new CAS("/tmp/base.db", true); auto cas = new CAS("/tmp/base.db", true);
// cas.saveSnapshot("/tmp/text", cast(ubyte[]) read("/tmp/text")); cas.saveSnapshot("/tmp/text", cast(ubyte[]) read("/tmp/text"));
cas.restoreSnapshot(); // cas.restoreSnapshot();
} }