Обновление схемы БД
This commit is contained in:
parent
dc0c8349c7
commit
18bcf3742d
6 changed files with 180 additions and 41 deletions
|
@ -3,6 +3,10 @@ module cdcdb.cdc.cas;
|
||||||
import cdcdb.db;
|
import cdcdb.db;
|
||||||
import cdcdb.cdc.core;
|
import cdcdb.cdc.core;
|
||||||
|
|
||||||
|
import std.digest.sha : SHA256, digest;
|
||||||
|
import std.format : format;
|
||||||
|
|
||||||
|
// CAS-хранилище (Content-Addressable Storage) со снапшотами
|
||||||
final class CAS
|
final class CAS
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
@ -13,8 +17,13 @@ public:
|
||||||
_db = new DBLite(database);
|
_db = new DBLite(database);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t saveSnapshot(const(ubyte)[] data)
|
size_t saveSnapshot(string filePath, const(ubyte)[] data)
|
||||||
{
|
{
|
||||||
|
ubyte[32] hashSource = digest!SHA256(data);
|
||||||
|
// Сделать запрос в БД по filePath и сверить хеш файлов
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Параметры для CDC вынести в отдельные настройки (продумать)
|
// Параметры для CDC вынести в отдельные настройки (продумать)
|
||||||
auto cdc = new CDC(100, 200, 500, 0xFF, 0x0F);
|
auto cdc = new CDC(100, 200, 500, 0xFF, 0x0F);
|
||||||
// Разбить на фрагменты
|
// Разбить на фрагменты
|
||||||
|
@ -26,7 +35,7 @@ public:
|
||||||
// Записать фрагменты в БД
|
// Записать фрагменты в БД
|
||||||
foreach (chunk; chunks)
|
foreach (chunk; chunks)
|
||||||
{
|
{
|
||||||
writeln(chunk.index);
|
writeln(format("%(%02x%)", chunk.sha256));
|
||||||
}
|
}
|
||||||
_db.commit();
|
_db.commit();
|
||||||
// Записать манифест в БД
|
// Записать манифест в БД
|
||||||
|
|
|
@ -4,6 +4,7 @@ import cdcdb.cdc.types;
|
||||||
|
|
||||||
import std.digest.sha : SHA256, digest;
|
import std.digest.sha : SHA256, digest;
|
||||||
|
|
||||||
|
// Change Data Capture
|
||||||
final class CDC
|
final class CDC
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
module cdcdb.cdc.types;
|
module cdcdb.cdc.types;
|
||||||
|
|
||||||
/// Единица разбиения
|
// Единица разбиения
|
||||||
struct Chunk
|
struct Chunk
|
||||||
{
|
{
|
||||||
size_t index; // 1..N
|
size_t index; // 1..N
|
||||||
|
@ -9,7 +9,7 @@ struct Chunk
|
||||||
ubyte[32] sha256; // hex(SHA-256) содержимого
|
ubyte[32] sha256; // hex(SHA-256) содержимого
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Метаданные снимка
|
// Метаданные снимка
|
||||||
struct SnapshotInfo
|
struct SnapshotInfo
|
||||||
{
|
{
|
||||||
size_t id;
|
size_t id;
|
||||||
|
|
|
@ -9,6 +9,11 @@ private:
|
||||||
string _dbPath;
|
string _dbPath;
|
||||||
// _scheme
|
// _scheme
|
||||||
mixin(import("scheme.d"));
|
mixin(import("scheme.d"));
|
||||||
|
|
||||||
|
SqliteResult sql(T...)(string queryText, T args)
|
||||||
|
{
|
||||||
|
return cast(SqliteResult) query(queryText, args);
|
||||||
|
}
|
||||||
public:
|
public:
|
||||||
this(string database)
|
this(string database)
|
||||||
{
|
{
|
||||||
|
@ -40,8 +45,5 @@ public:
|
||||||
query("ROLLBACK");
|
query("ROLLBACK");
|
||||||
}
|
}
|
||||||
|
|
||||||
SqliteResult sql(T...)(string queryText, T args)
|
// findFile()
|
||||||
{
|
|
||||||
return cast(SqliteResult) query(queryText, args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,75 +1,202 @@
|
||||||
auto _scheme = [
|
auto _scheme = [
|
||||||
q{
|
q{
|
||||||
-- Метаданные снапшота
|
-- ------------------------------------------------------------
|
||||||
|
-- Метаданные снимка
|
||||||
|
-- ------------------------------------------------------------
|
||||||
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,
|
||||||
|
|
||||||
|
-- Произвольная метка/название снимка (для человека).
|
||||||
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,
|
||||||
algo_normal INTEGER NOT NULL,
|
algo_normal INTEGER NOT NULL,
|
||||||
algo_max INTEGER NOT NULL,
|
algo_max INTEGER NOT NULL,
|
||||||
-- Маски для определения границ чанков (быстрый роллинг-хэш/FastCDC).
|
|
||||||
-- Обычно степени вида 2^n - 1. Хранятся для воспроизводимости.
|
-- Маски FastCDC для определения границ чанков (обычно вида 2^n - 1).
|
||||||
|
-- Хранятся для воспроизводимости.
|
||||||
mask_s INTEGER NOT NULL,
|
mask_s INTEGER NOT NULL,
|
||||||
mask_l INTEGER NOT NULL,
|
mask_l INTEGER NOT NULL,
|
||||||
|
|
||||||
-- Состояние снимка:
|
-- Состояние снимка:
|
||||||
-- pending - метаданные созданы, состав не полностью загружен;
|
-- "pending" - метаданные созданы, состав не полностью загружен;
|
||||||
-- ready - все чанки привязаны, снимок готов к использованию.
|
-- "ready" - все чанки привязаны, снимок готов к использованию.
|
||||||
status TEXT NOT NULL DEFAULT "pending" CHECK (status IN ("pending","ready"))
|
status TEXT NOT NULL DEFAULT "pending" CHECK (status IN ("pending","ready"))
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
q{
|
q{
|
||||||
-- Уникальные куски содержимого (сам контент в БД)
|
-- ------------------------------------------------------------
|
||||||
|
-- Уникальные куски содержимого (дедупликация по sha256)
|
||||||
|
-- ------------------------------------------------------------
|
||||||
CREATE TABLE IF NOT EXISTS blobs (
|
CREATE TABLE IF NOT EXISTS blobs (
|
||||||
-- Хэш содержимого чанка. Ключ обеспечивает дедупликацию: одинаковый контент хранится один раз.
|
-- Хэш содержимого чанка. Обеспечивает уникальность контента.
|
||||||
sha256 TEXT PRIMARY KEY,
|
-- Храним как BLOB(32) (сырые 32 байта SHA-256), а не hex-строку.
|
||||||
-- Размер этого чанка в байтах.
|
sha256 BLOB PRIMARY KEY CHECK (length(sha256) = 32),
|
||||||
|
|
||||||
|
-- Размер чанка в байтах. Должен совпадать с длиной content.
|
||||||
size INTEGER NOT NULL,
|
size INTEGER NOT NULL,
|
||||||
|
|
||||||
-- Сырые байты чанка.
|
-- Сырые байты чанка.
|
||||||
content BLOB NOT NULL,
|
content BLOB NOT NULL,
|
||||||
-- Когда этот контент впервые появился в базе (UTC).
|
|
||||||
created_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP)
|
-- Момент, когда этот контент впервые появился в базе (UTC).
|
||||||
|
created_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
|
||||||
|
-- Последний раз, когда на контент сослались (для аналитики/GC).
|
||||||
|
last_seen_utc TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP),
|
||||||
|
|
||||||
|
-- Счётчик ссылок: сколько строк в snapshot_chunks ссылаются на этот sha256.
|
||||||
|
-- Используется для безопасного удаления неиспользуемых blob-ов.
|
||||||
|
refcount INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
-- Дополнительные гарантии целостности:
|
||||||
|
CHECK (refcount >= 0),
|
||||||
|
CHECK (size = length(content))
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
q{
|
q{
|
||||||
-- Состав снапшота (порядок чанков важен)
|
-- ------------------------------------------------------------
|
||||||
|
-- Состав снимка (упорядоченный список чанков)
|
||||||
|
-- ------------------------------------------------------------
|
||||||
CREATE TABLE IF NOT EXISTS snapshot_chunks (
|
CREATE TABLE IF NOT EXISTS snapshot_chunks (
|
||||||
-- Ссылка на snapshots.id. Определяет, к какому снимку относится строка.
|
-- Ссылка на 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 предыдущих чанков по chunk_index,
|
-- Смещение чанка в исходном файле (в байтах).
|
||||||
-- но хранение ускоряет проверки/отладку.
|
-- Можно восстановить суммой size предыдущих чанков, но хранение ускоряет проверки/отладку.
|
||||||
offset INTEGER,
|
offset INTEGER,
|
||||||
-- Размер именно этого чанка в байтах (дублирует blobs.size для быстрого доступа и валидации).
|
|
||||||
|
-- Размер конкретного чанка в составе (дублирует blobs.size для ускорения/валидации).
|
||||||
size INTEGER NOT NULL,
|
size INTEGER NOT NULL,
|
||||||
-- Ссылка на blobs.sha256. Привязывает позицию в снимке к конкретному содержимому.
|
|
||||||
sha256 TEXT NOT NULL,
|
-- Ссылка на blobs.sha256. Привязывает позицию к конкретному содержимому.
|
||||||
-- Гарантирует уникальность позиции чанка в рамках снимка и задаёт естественный порядок.
|
-- Тип BLOB обязан совпадать с типом в родительской таблице.
|
||||||
|
sha256 BLOB NOT NULL,
|
||||||
|
|
||||||
|
-- Уникальность позиции чанка в рамках одного снимка.
|
||||||
PRIMARY KEY (snapshot_id, chunk_index),
|
PRIMARY KEY (snapshot_id, chunk_index),
|
||||||
|
|
||||||
|
-- Внешние ключи и их поведение:
|
||||||
-- При удалении снимка его строки состава удаляются автоматически.
|
-- При удалении снимка его строки состава удаляются автоматически.
|
||||||
-- Обновления id каскадятся (на практике id не меняют).
|
FOREIGN KEY (snapshot_id)
|
||||||
FOREIGN KEY (snapshot_id) REFERENCES snapshots(id) ON UPDATE CASCADE ON DELETE CASCADE,
|
REFERENCES snapshots(id)
|
||||||
-- Нельзя удалить blob, если он где-то используется (RESTRICT).
|
ON UPDATE CASCADE
|
||||||
-- Обновление хэша каскадится (редкий случай).
|
ON DELETE CASCADE,
|
||||||
FOREIGN KEY (sha256) REFERENCES blobs(sha256) ON UPDATE CASCADE ON DELETE RESTRICT
|
|
||||||
|
-- Нельзя удалить blob, если он где-то используется; обновление ключа запрещено.
|
||||||
|
FOREIGN KEY (sha256)
|
||||||
|
REFERENCES blobs(sha256)
|
||||||
|
ON UPDATE RESTRICT
|
||||||
|
ON DELETE RESTRICT
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
q{
|
q{
|
||||||
-- Быстрый выбор всех чанков конкретного снимка (частый запрос).
|
-- Быстрый выбор всех чанков конкретного снимка (частый запрос).
|
||||||
CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_snapshot ON snapshot_chunks(snapshot_id)
|
CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_snapshot
|
||||||
|
ON snapshot_chunks(snapshot_id)
|
||||||
},
|
},
|
||||||
q{
|
q{
|
||||||
-- Быстрый обратный поиск: где используется данный blob (для GC/аналитики).
|
-- Быстрый обратный поиск: где используется данный blob (для GC/аналитики).
|
||||||
CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_sha ON snapshot_chunks(sha256)
|
-- Индекс по BLOB(32) хорошо работает для точного поиска sha256.
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_snapshot_chunks_sha
|
||||||
|
ON snapshot_chunks(sha256)
|
||||||
|
},
|
||||||
|
// -- ------------------------------------------------------------
|
||||||
|
// -- Триггеры для управления refcount и GC blob'ов
|
||||||
|
// -- ------------------------------------------------------------
|
||||||
|
q{
|
||||||
|
-- Инкремент счётчика при привязке чанка к снимку.
|
||||||
|
-- Обновляем last_seen_utc, чтобы фиксировать "живость" контента.
|
||||||
|
-- ВАЖНО: FK гарантирует, что соответствующий blobs.sha256 уже существует.
|
||||||
|
CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_ai
|
||||||
|
AFTER INSERT ON snapshot_chunks
|
||||||
|
BEGIN
|
||||||
|
UPDATE blobs
|
||||||
|
SET refcount = refcount + 1,
|
||||||
|
last_seen_utc = CURRENT_TIMESTAMP
|
||||||
|
WHERE sha256 = NEW.sha256;
|
||||||
|
END
|
||||||
|
},
|
||||||
|
q{
|
||||||
|
-- Декремент счётчика при удалении строки состава.
|
||||||
|
-- Если счётчик стал 0 - удаляем неиспользуемый blob (авто-GC).
|
||||||
|
CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_ad
|
||||||
|
AFTER DELETE ON snapshot_chunks
|
||||||
|
BEGIN
|
||||||
|
UPDATE blobs
|
||||||
|
SET refcount = refcount - 1
|
||||||
|
WHERE sha256 = OLD.sha256;
|
||||||
|
|
||||||
|
DELETE FROM blobs
|
||||||
|
WHERE sha256 = OLD.sha256
|
||||||
|
AND refcount <= 0;
|
||||||
|
END
|
||||||
|
},
|
||||||
|
q{
|
||||||
|
-- Корректировка счётчиков при смене sha256 в составе.
|
||||||
|
-- Триггер срабатывает только при реальном изменении значения.
|
||||||
|
-- Предполагается, что NEW.sha256 существует в blobs (иначе FK не даст обновить).
|
||||||
|
CREATE TRIGGER IF NOT EXISTS trg_snapshot_chunks_au
|
||||||
|
AFTER UPDATE OF sha256 ON snapshot_chunks
|
||||||
|
FOR EACH ROW
|
||||||
|
WHEN NEW.sha256 <> OLD.sha256
|
||||||
|
BEGIN
|
||||||
|
UPDATE blobs
|
||||||
|
SET refcount = refcount - 1
|
||||||
|
WHERE sha256 = OLD.sha256;
|
||||||
|
|
||||||
|
DELETE FROM blobs
|
||||||
|
WHERE sha256 = OLD.sha256
|
||||||
|
AND refcount <= 0;
|
||||||
|
|
||||||
|
UPDATE blobs
|
||||||
|
SET refcount = refcount + 1,
|
||||||
|
last_seen_utc = CURRENT_TIMESTAMP
|
||||||
|
WHERE sha256 = NEW.sha256;
|
||||||
|
END
|
||||||
|
},
|
||||||
|
q{
|
||||||
|
-- Автоматическая смена статуса снимка на "ready",
|
||||||
|
-- когда сумма размеров его чанков стала равна source_length.
|
||||||
|
-- Примечание: простая эвристика; если потом удалишь/поменяешь чанки,
|
||||||
|
-- триггер ниже вернёт статус обратно на "pending".
|
||||||
|
CREATE TRIGGER IF NOT EXISTS trg_snapshots_mark_ready
|
||||||
|
AFTER INSERT ON snapshot_chunks
|
||||||
|
BEGIN
|
||||||
|
UPDATE snapshots
|
||||||
|
SET status = "ready"
|
||||||
|
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);
|
||||||
|
END
|
||||||
|
},
|
||||||
|
q{
|
||||||
|
-- При удалении любого чанка снимок снова помечается как "pending".
|
||||||
|
-- Это простой безопасный фоллбэк; следующая вставка приравняет суммы и вернёт "ready".
|
||||||
|
CREATE TRIGGER IF NOT EXISTS trg_snapshots_mark_pending
|
||||||
|
AFTER DELETE ON snapshot_chunks
|
||||||
|
BEGIN
|
||||||
|
UPDATE snapshots
|
||||||
|
SET status = "pending"
|
||||||
|
WHERE id = OLD.snapshot_id;
|
||||||
|
END
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
@ -7,5 +7,5 @@ import std.file : read;
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
auto cas = new CAS("/tmp/base.db");
|
auto cas = new CAS("/tmp/base.db");
|
||||||
cas.saveSnapshot(cast(ubyte[]) read("/tmp/text"));
|
cas.saveSnapshot("/tmp/text", cast(ubyte[]) read("/tmp/text"));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue