中文翻译暂不可用,显示俄语原文。
Настроить tiered storage (hot/warm/cold)
ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Настроить tiered storage (hot/warm/cold)
1. Цель задачи
Научиться проектировать и настраивать многоуровневое хранение данных (tiered storage) для RAG-системы или аналитической базы данных, где разные уровни доступа к данным имеют разные требования к задержкам. Реализовать политику автоматического перемещения данных между hot (NVMe), warm (SSD) и cold (S3) уровнями в зависимости от частоты запросов и времени последнего доступа.
Ключевой результат Работающая конфигурация tiered storage, где query latency для hot tier < 10ms, для cold tier < 1s, а данные автоматически перемещаются между уровнями на основе заданных правил.
2. Исходные данные
Перед началом необходимо иметь:
| Что нужно | Откуда взять |
|---|---|
| Тестовая база данных (векторная или реляционная) | Создать самостоятельно: 10k-100k записей с метаданными (дата создания, частота запросов) |
| NVMe-диск (или эмуляция) | Локальный /mnt/nvme или tmpfs с ограничением 1GB |
| SSD-диск (или эмуляция) | Локальный /mnt/ssd или диск с HDD-эмуляцией (через losetup) |
| S3-совместимое хранилище | MinIO (локальный Docker-контейнер) или AWS S3 (если есть доступ) |
| Инструмент для тестирования задержек | fio, dd, или Python-скрипт с timeit |
| Симулятор запросов | Python-скрипт, который генерирует запросы к разным уровням данных |
Если нет реального NVMe/SSD — симулируем:
- NVMe (hot): Создаём tmpfs размером 1GB: sudo mount -t tmpfs -o size=1G tmpfs /mnt/hot
- SSD (warm): Используем обычный HDD-раздел или создаём loop-устройство с задержкой: dd if=/dev/zero of=/tmp/warm.img bs=1M count=500 && sudo losetup /dev/loop0 /tmp/warm.img
- Cold (S3): Запускаем MinIO в Docker: docker run -p 9000:9000 -p 9001:9001 minio/minio server /data --console-address ":9001"
3. Технологический стек
| Компонент | Инструменты | Назначение |
|---|---|---|
| База данных | PostgreSQL + pg_partman (для партиционирования) или Qdrant (для векторных данных) | Хранение данных с поддержкой tiered storage |
| S3-совместимое хранилище | MinIO (Docker) | Cold tier |
| Мониторинг задержек | Prometheus + Grafana | Измерение query latency |
| Скрипты миграции данных | Python (boto3, psycopg2) | Автоматическое перемещение данных между уровнями |
| Эмуляция дисков | tmpfs, losetup, fio | Симуляция NVMe/SSD/HDD |
| Планировщик | cron или Apache Airflow | Периодическое выполнение политик перемещения |
4. Этапы выполнения
Этап 1: Подготовка инфраструктуры и эмуляция дисков (1 час)
Действия
-
Создать эмуляцию трёх уровней хранения
# Hot tier (NVMe эмуляция через tmpfs) sudo mkdir -p /mnt/hot sudo mount -t tmpfs -o size=1G tmpfs /mnt/hot # Warm tier (SSD эмуляция через loop-устройство) dd if=/dev/zero of=/tmp/warm.img bs=1M count=2000 sudo losetup /dev/loop0 /tmp/warm.img sudo mkfs.ext4 /dev/loop0 sudo mount /dev/loop0 /mnt/warm # Cold tier (S3 эмуляция через MinIO) docker run -d --name minio \ -p 9000:9000 -p 9001:9001 \ -e MINIO_ROOT_USER=admin \ -e MINIO_ROOT_PASSWORD=password \ -v /mnt/cold:/data \ minio/minio server /data --console-address ":9001" -
Проверить задержки каждого уровня
# Hot tier (ожидаем < 0.1ms) fio --name=test --ioengine=sync --rw=randread --bs=4k --size=100M --runtime=10 --directory=/mnt/hot # Warm tier (ожидаем < 1ms) fio --name=test --ioengine=sync --rw=randread --bs=4k --size=100M --runtime=10 --directory=/mnt/warm # Cold tier (ожидаем > 10ms, так как это S3) python3 -c "import boto3; s3 = boto3.client('s3', endpoint_url='http://localhost:9000'); import time; t=time.time(); s3.list_buckets(); print(f'Latency: {(time.time()-t)*1000:.2f}ms')" -
Записать baseline задержек в файл
baseline_latency.txt.
Ожидаемый результат этапа Работающие три уровня хранения с измеренными baseline задержками.
Этап 2: Создание тестовой базы данных с метаданными (1.5 часа)
Действия
-
Создать таблицу в PostgreSQL с поддержкой партиционирования:
CREATE TABLE documents ( id SERIAL PRIMARY KEY, content TEXT, embedding vector(384), created_at TIMESTAMP DEFAULT NOW(), last_accessed_at TIMESTAMP DEFAULT NOW(), access_count INT DEFAULT 0, tier VARCHAR(10) DEFAULT 'hot' CHECK (tier IN ('hot', 'warm', 'cold')) ) PARTITION BY LIST (tier); -- Создать партиции для каждого уровня CREATE TABLE documents_hot PARTITION OF documents FOR VALUES IN ('hot') TABLESPACE hot_tablespace; CREATE TABLE documents_warm PARTITION OF documents FOR VALUES IN ('warm') TABLESPACE warm_tablespace; CREATE TABLE documents_cold PARTITION OF documents FOR VALUES IN ('cold') TABLESPACE cold_tablespace; -
Заполнить таблицу тестовыми данными (100k записей):
import psycopg2 import random from datetime import datetime, timedelta conn = psycopg2.connect("dbname=test user=postgres") cur = conn.cursor() for i in range(100000): # Симулируем разные паттерны доступа days_ago = random.randint(0, 365) access_count = max(0, int(100 - days_ago * 0.3)) # Чем старее, тем меньше запросов tier = 'hot' if days_ago < 30 else ('warm' if days_ago < 180 else 'cold') cur.execute(""" INSERT INTO documents (content, created_at, last_accessed_at, access_count, tier) VALUES (%s, %s, %s, %s, %s) """, (f"Document {i}", datetime.now() - timedelta(days=days_ago), datetime.now() - timedelta(days=random.randint(0, days_ago)), access_count, tier)) conn.commit() cur.close() conn.close() -
Настроить tablespaces для каждого уровня
CREATE TABLESPACE hot_tablespace LOCATION '/mnt/hot'; CREATE TABLESPACE warm_tablespace LOCATION '/mnt/warm'; CREATE TABLESPACE cold_tablespace LOCATION '/mnt/cold';
Ожидаемый результат этапа Таблица с 100k записей, распределённых по трём уровням.
Этап 3: Реализация политики автоматического перемещения данных (2 часа)
Действия
-
Написать Python-скрипт для перемещения данных между уровнями:
import psycopg2 import boto3 import json from datetime import datetime, timedelta class TieredStorageManager: def __init__(self): self.conn = psycopg2.connect("dbname=test user=postgres") self.s3 = boto3.client('s3', endpoint_url='http://localhost:9000', aws_access_key_id='admin', aws_secret_access_key='password') def move_to_cold(self, days_threshold=180, access_threshold=10): """Переместить старые и редко запрашиваемые данные в cold tier""" cur = self.conn.cursor() cur.execute(""" UPDATE documents SET tier = 'cold' WHERE tier IN ('hot', 'warm') AND last_accessed_at < NOW() - INTERVAL '%s days' AND access_count < %s RETURNING id """, (days_threshold, access_threshold)) moved_ids = [row[0] for row in cur.fetchall()] # Экспорт данных в S3 (MinIO) for doc_id in moved_ids: cur.execute("SELECT content FROM documents WHERE id = %s", (doc_id,)) content = cur.fetchone()[0] self.s3.put_object(Bucket='cold-tier', Key=f'doc_{doc_id}.json', Body=json.dumps({'id': doc_id, 'content': content})) self.conn.commit() cur.close() return len(moved_ids) def promote_to_hot(self, access_threshold=50, days_window=7): """Переместить часто запрашиваемые данные из warm в hot""" cur = self.conn.cursor() cur.execute(""" UPDATE documents SET tier = 'hot' WHERE tier = 'warm' AND access_count > %s AND last_accessed_at > NOW() - INTERVAL '%s days' RETURNING id """, (access_threshold, days_window)) promoted_count = len(cur.fetchall()) self.conn.commit() cur.close() return promoted_count def demote_to_warm(self, days_threshold=30, access_threshold=30): """Переместить редко запрашиваемые hot данные в warm""" cur = self.conn.cursor() cur.execute(""" UPDATE documents SET tier = 'warm' WHERE tier = 'hot' AND (last_accessed_at < NOW() - INTERVAL '%s days' OR access_count < %s) RETURNING id """, (days_threshold, access_threshold)) demoted_count = len(cur.fetchall()) self.conn.commit() cur.close() return demoted_count -
Настроить cron-задачи для периодического выполнения
# Каждый час проверять и перемещать данные 0 * * * * /usr/bin/python3 /opt/tiered_storage/migrate.py --action=demote_to_warm 0 2 * * * /usr/bin/python3 /opt/tiered_storage/migrate.py --action=move_to_cold 0 6 * * * /usr/bin/python3 /opt/tiered_storage/migrate.py --action=promote_to_hot -
Добавить триггер на обновление
last_accessed_atпри каждом запросе:CREATE OR REPLACE FUNCTION update_access_metadata() RETURNS TRIGGER AS $$ BEGIN NEW.last_accessed_at = NOW(); NEW.access_count = NEW.access_count + 1; RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER access_trigger BEFORE SELECT ON documents FOR EACH ROW EXECUTE FUNCTION update_access_metadata();
Ожидаемый результат этапа Работающий скрипт миграции и cron-задачи.
Этап 4: Тестирование задержек и валидация (1.5 часа)
Действия
-
Написать симулятор запросов для измерения latency:
import psycopg2 import time import random import statistics def measure_query_latency(tier, num_queries=100): conn = psycopg2.connect("dbname=test user=postgres") cur = conn.cursor() latencies = [] for _ in range(num_queries): start = time.time() cur.execute(""" SELECT content FROM documents WHERE tier = %s ORDER BY RANDOM() LIMIT 1 """, (tier,)) _ = cur.fetchone() latencies.append((time.time() - start) * 1000) # в ms cur.close() conn.close() return { 'tier': tier, 'avg_latency_ms': statistics.mean(latencies), 'p50_ms': statistics.median(latencies), 'p99_ms': sorted(latencies)[int(len(latencies) * 0.99)], 'min_ms': min(latencies), 'max_ms': max(latencies) } # Тестируем все три уровня for tier in ['hot', 'warm', 'cold']: result = measure_query_latency(tier, 50) print(f"{tier}: avg={result['avg_latency_ms']:.2f}ms, p99={result['p99_ms']:.2f}ms") -
Проверить, что latency соответствует требованиям:
- Hot tier: avg < 10ms
- Warm tier: avg < 50ms
- Cold tier: avg < 1000ms (1s)
-
Запустить долгий тест (1 час) с симуляцией нагрузки:
# Симуляция 1000 запросов с разными паттернами for i in range(1000): # 70% запросов к hot, 20% к warm, 10% к cold tier = random.choices(['hot', 'warm', 'cold'], weights=[0.7, 0.2, 0.1])[0] latency = measure_single_query(tier) log_latency(tier, latency) time.sleep(0.1) # 10 запросов в секунду
Ожидаемый результат этапа Отчёт с метриками latency для каждого уровня.
Этап 5: Мониторинг и визуализация (1 час)
Действия
-
Настроить Prometheus метрики
from prometheus_client import start_http_server, Histogram, Gauge import random import time # Метрики query_latency = Histogram('query_latency_ms', 'Query latency by tier', ['tier'], buckets=[1, 5, 10, 25, 50, 100, 250, 500, 1000]) tier_size = Gauge('tier_size_bytes', 'Size of each tier', ['tier']) if __name__ == '__main__': start_http_server(8000) while True: # Симуляция запросов tier = random.choice(['hot', 'warm', 'cold']) latency = random.gauss(5 if tier == 'hot' else 50, 2) query_latency.labels(tier=tier).observe(latency) time.sleep(1) -
Создать дашборд в Grafana
-
Настроить алерты
groups: - name: tiered_storage rules: - alert: HotTierLatencyHigh expr: histogram_quantile(0.99, rate(query_latency_ms_bucket{tier="hot"}[5m])) > 15 for: 5m annotations: summary: "Hot tier latency above 15ms"
Ожидаемый результат этапа Работающий дашборд с метриками и алертами.
5. Критерии приемки (Definition of Done)
- Все три уровня хранения (hot/warm/cold) настроены и работают
- Query latency для hot tier < 10ms (avg), для cold tier < 1s (avg)
- Данные автоматически перемещаются между уровнями на основе политик (возраст, частота запросов)
- Скрипт миграции данных работает корректно и логирует все перемещения
- Настроен мониторинг (Prometheus + Grafana) с метриками latency и размеров уровней
- Настроены алерты на превышение latency hot tier > 15ms
- Тестовые данные (100k записей) распределены по уровням согласно политикам
- Документация по архитектуре и политикам перемещения написана (README.md)
- Все скрипты и конфиги находятся в Git-репозитории
- Проведён нагрузочный тест (1000 запросов) без ошибок
6. Ожидаемый результат
Основной артефакт Git-репозиторий с:
- docker-compose.yml — инфраструктура (PostgreSQL, MinIO, Prometheus, Grafana)
scripts/— Python-скрипты для миграции данных и тестированияconfig/— конфиги Prometheus и Grafana- README.md — документация с описанием архитектуры и инструкцией по запуску
tests/— результаты тестов latency (файл latency_report.json)
Содержание latency_report.json
{
"hot_tier": {
"avg_latency_ms": 2.3,
"p99_latency_ms": 8.1,
"data_size_mb": 500
},
"warm_tier": {
"avg_latency_ms": 15.7,
"p99_latency_ms": 45.2,
"data_size_mb": 800
},
"cold_tier": {
"avg_latency_ms": 120.4,
"p99_latency_ms": 350.6,
"data_size_mb": 700
}
}
Опционально Дашборд Grafana с экспортированным JSON.
7. Возможные сложности и их решение
| Сложность | Решение |
|---|---|
| tmpfs слишком быстрый, не эмулирует NVMe | Добавить fio с задержкой через --latency или использовать tc для network delay |
| PostgreSQL не поддерживает партиционирование по списку | Использовать pg_partman или реализовать логику на уровне приложения (шардирование) |
| MinIO не запускается из-за портов | Изменить порты в docker-compose или использовать другой S3-эмулятор (LocalStack) |
| Скрипт миграции слишком медленный для 100k записей | Использовать batch-обработку (по 1000 записей за транзакцию) |
| Prometheus не видит метрики | Проверить endpoint /metrics и network policy между контейнерами |
| Данные не перемещаются автоматически | Проверить cron-задачи и права на выполнение скриптов |
8. Бюджет времени (оценка)
| Этап | Время |
|---|---|
| Этап 1: Подготовка инфраструктуры | 1 час |
| Этап 2: Создание тестовой базы данных | 1.5 часа |
| Этап 3: Реализация политики перемещения | 2 часа |
| Этап 4: Тестирование задержек | 1.5 часа |
| Этап 5: Мониторинг и визуализация | 1 час |
| Итого | 7 часов |
Примечание Для первого раза рекомендуется заложить +2 часа на отладку и настройку инфраструктуры (итого ~9 часов).
9. Связанные вопросы из базы знаний
| Вопрос | Тема |
|---|---|
| 12 | Как настроить партиционирование таблиц в PostgreSQL |
| 45 | Оптимизация запросов к большим таблицам |
| 78 | Мониторинг производительности БД с Prometheus |
| 123 | Настройка S3-совместимого хранилища (MinIO) |
| 156 | Автоматизация задач с cron и Airflow |
| 234 | Измерение latency с помощью fio |
| 345 | Создание дашбордов в Grafana |
| 456 | Настройка алертов в Prometheus |
| 567 | Работа с временными рядами в Python |
| 678 | Оптимизация хранения данных для RAG-систем |
10. Чек-лист самопроверки
- Я проверил, что все три уровня хранения работают и имеют разные задержки (hot < warm < cold)
- Я убедился, что данные автоматически перемещаются между уровнями согласно политикам (проверил логи миграции)
- Я измерил query latency для каждого уровня и убедился, что hot < 10ms, cold < 1s
- Я настроил мониторинг и алерты, и они работают корректно (проверил через Grafana)
- Я задокументировал архитектуру и инструкцию по запуску в README.md
- Я провёл нагрузочный тест (1000 запросов) и убедился, что система стабильна
- Я проверил, что все скрипты и конфиги находятся в Git-репозитории