Оптимизирована схема БД
This commit is contained in:
		
							parent
							
								
									541d09b8f4
								
							
						
					
					
						commit
						d62524e185
					
				
					 5 changed files with 61 additions and 115 deletions
				
			
		| 
						 | 
				
			
			@ -37,7 +37,7 @@ public:
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
		// Параметры для CDC вынести в отдельные настройки (продумать)
 | 
			
		||||
		auto cdc = new CDC(300, 700, 1000, 0xFF, 0x0F);
 | 
			
		||||
		auto cdc = new CDC(256, 512, 1024, 0xFF, 0x0F);
 | 
			
		||||
		// Разбить на фрагменты
 | 
			
		||||
		auto chunks = cdc.split(data);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 = ?
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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).
 | 
			
		||||
			z_size INTEGER,
 | 
			
		||||
 | 
			
		||||
			-- Сырые байты: при zstd=1 здесь лежит сжатый блок, иначе - исходные байты.
 | 
			
		||||
			-- размер сжатого содержимого, байт
 | 
			
		||||
			z_size INTEGER NOT NULL,
 | 
			
		||||
			-- байты (сжатые при 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 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,25 +147,22 @@ 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)
 | 
			
		||||
				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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,7 +40,6 @@ struct SnapshotChunk
 | 
			
		|||
	long snapshotId;
 | 
			
		||||
	long chunkIndex;
 | 
			
		||||
	long offset;
 | 
			
		||||
	long size;
 | 
			
		||||
	ubyte[32] sha256; // BLOB(32)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue