中文翻译暂不可用,显示俄语原文。

Настроить 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 — симулируем:

  1. NVMe (hot): Создаём tmpfs размером 1GB: sudo mount -t tmpfs -o size=1G tmpfs /mnt/hot
  2. SSD (warm): Используем обычный HDD-раздел или создаём loop-устройство с задержкой: dd if=/dev/zero of=/tmp/warm.img bs=1M count=500 && sudo losetup /dev/loop0 /tmp/warm.img
  3. 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 час)

Действия

  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"
    
  2. Проверить задержки каждого уровня

    # 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')"
    
  3. Записать baseline задержек в файл baseline_latency.txt.

Ожидаемый результат этапа Работающие три уровня хранения с измеренными baseline задержками.


Этап 2: Создание тестовой базы данных с метаданными (1.5 часа)

Действия

  1. Создать таблицу в 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;
    
  2. Заполнить таблицу тестовыми данными (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()
    
  3. Настроить 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 часа)

Действия

  1. Написать 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
    
  2. Настроить 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
    
  3. Добавить триггер на обновление 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 часа)

Действия

  1. Написать симулятор запросов для измерения 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")
    
  2. Проверить, что latency соответствует требованиям:

    • Hot tier: avg < 10ms
    • Warm tier: avg < 50ms
    • Cold tier: avg < 1000ms (1s)
  3. Запустить долгий тест (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 час)

Действия

  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)
    
  2. Создать дашборд в Grafana

    • Панель "Query Latency by Tier" (гистограмма)
    • Панель "Tier Size Over Time" (линейный график)
    • Панель "Data Migration Events" (счётчик)
    • Alert: если hot tier latency > 15ms
  3. Настроить алерты

    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-репозитории