Оптимизирована схема БД
This commit is contained in:
parent
541d09b8f4
commit
194ca8e5a2
5 changed files with 59 additions and 113 deletions
|
@ -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);
|
||||||
|
|
|
@ -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 = ?
|
||||||
|
|
|
@ -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,
|
||||||
|
-- размер сжатого содержимого, байт (или NULL)
|
||||||
-- Размер сжатого чанка (в байтах). Можно держать NOT NULL и заполнять =size при zstd=0,
|
|
||||||
-- либо сделать NULL при zstd=0 (см. CHECK ниже допускает NULL).
|
|
||||||
z_size INTEGER,
|
z_size INTEGER,
|
||||||
|
-- байты (сжатые при zstd=1, иначе исходные)
|
||||||
-- Сырые байты: при 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 z_size IS NOT NULL 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,25 +147,22 @@ 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
|
||||||
|
WHERE sc.snapshot_id = NEW.snapshot_id)
|
||||||
= (SELECT source_length FROM snapshots WHERE 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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue