Microsegment.ru
  • Главная страница
  • О проекте
  • Портфолио
  • Блог
Системы

Cоздание минимального жизнеспособного гибридного хранилища данных

Cоздание минимального жизнеспособного гибридного хранилища данных
Системы

Данная инструкция связана с созданием минимального жизнеспособного (англ. minimum viable product, далее сокр. MVP) гибридного хранилища данных (англ. Data Lakehouse, далее сокр. DLH) с использованием расчетов параметров инфраструктуры проекта.

Содержание:

  1. Инструкция по шагу 0.1: Проектирование логической архитектуры Data Lakehouse (DLH)
  2. Инструкция по шагу 0.2: Подготовка виртуальной инфраструктуры (с использованием gdisk)
  3. Подробная инструкция по шагу 1.1: Установка PostgreSQL
  4. Подробная инструкция по шагу 1.2: Создание слоя STG (таблица-песочница)
  5. Следует ли в PostgreSQL сохранять формат названий таблиц и их полей из исходной БД MS SQL (например, Table1 вместо table_1)?
  6. Подробная инструкция по шагу 1.3: Создание слоя RAW с партиционированием по кварталам
  7. Подробная инструкция по шагу 1.4: Управление жизненным циклом партиций и автоматизация архивации
  8. Пояснения к коду файла archive_raw_partition.sh
  9. Пояснения к коду файла restore_raw_partition.sh
  10. Пояснения к коду файла create_new_partition.sh

Инструкция по шагу 0.1: Проектирование логической архитектуры Data Lakehouse (DLH)¶

Шаг 0.1 итоговой инструкции — это проектирование логической архитектуры Data Lakehouse (DLH). Это фундаментальный этап, от которого зависят все последующие решения по инфраструктуре, выбору инструментов и настройке ETL/ELT-процессов. В закрытом контуре особенно важно сделать это правильно, чтобы избежать дорогостоящих переделок.

Ниже представлена подробная инструкция по данному шагу.


Технологический стек шага 0.1¶

Этот шаг — аналитический и не требует установки программного обеспечения.

Компонент Назначение
Бизнес-требования Определяют цели, метрики и потребителей данных
Схема слоёв (Layered Architecture) Логическая структура хранилища: STG → RAW → ODS → DDS → CDM
Модель данных Определение структур таблиц, связей и ключей
Документация Фиксация всех архитектурных решений

Пошаговая инструкция¶

Шаг 0.1.1: Определение бизнес-целей и требований к данным¶

Прежде чем создавать таблицы и писать скрипты, чётко сформулируйте, для чего создаётся хранилище.

  1. Определите потребителей данных:

    • Кто будет использовать хранилище? (Аналитики, руководители, Data Scientists, операционные системы?)
    • Каковы их ключевые вопросы и отчёты? (аналитика продаж, операционные дашборды, отчёты для руководства, обучение ML-моделей)
  2. Определите источники и объёмы данных:

    • Основной источник: таблица table_1 (∼3 ТБ) в MS SQL Server, с ежегодным приростом ~2 ТБ.
    • Потенциальные будущие источники: другие таблицы из той же БД, внешние системы.
    • Требование к историчности: Хранение данных минимум за 5 лет.
  3. Определите нефункциональные требования:

    • Время загрузки (окно 7:00–8:00).
    • Производительность запросов (аналитика и операционное использование).
    • Надёжность и возможность восстановления (бэкапы, WAL-архивация).

Шаг 0.1.2: Проектирование многослойной архитектуры (Medallion Architecture)¶

Лучшей мировой практикой для Lakehouse является многослойная архитектура (Medallion Architecture), где данные последовательно проходят через слои, повышая своё качество и ценность. Для нашего проекта предлагается следующая структура из пяти слоёв.

  1. Слой STAGING (STG) — «Песочница»
  • Назначение: Быстрая загрузка сырых данных из источника. Данные загружаются в исходном виде, без изменений.
  • Особенности: Полностью перезаписывается при каждой загрузке. Хранит данные только за последние 30 суток.
  • Технология: Таблица в PostgreSQL без индексов.
  1. Слой RAW DATA LAKE (RAW) — «Озеро сырых данных»
  • Назначение: Долгосрочное, неизменяемое хранилище сырых данных в том виде, в котором они были получены из STG.
  • Особенности: Данные партиционируются по кварталам (3 месяца) в отдельные LVM-тома, которые можно архивировать и отключать. Начальное хранение — 3 месяца (2–4 партиции), в перспективе — до 1–3 лет (до 20 партиций). На этом слое выполняется проверка целостности.
  • Технология: Партиционированная таблица PostgreSQL. Каждая партиция в отдельном табличном пространстве на своём LVM-томе.
  1. Слой OPERATIONAL DATA STORE (ODS) — «Операционное хранилище»
  • Назначение: Очистка и первичная обработка данных из RAW. Данные структурируются и приводятся к плоскому формату.
  • Особенности: Слой для оперативной работы. Здесь происходит нормализация (разбор JSON-полей, например) и подготовка данных для дальнейших трансформаций.
  • Технология: Таблицы PostgreSQL, близкие по структуре к исходным, но с очищенными данными.
  1. Слой DETAIL DATA STORE (DDS) — «Хранилище детализированных данных»
  • Назначение: Хранение детализированных, историчных и нормализованных данных.
  • Особенности: Данные организованы по модели Data Vault 2.0 или 3-ей нормальной форме (3НФ), что обеспечивает гибкость, масштабируемость и полную историчность.
  • Технология: Нормализованные таблицы PostgreSQL с SCD Type 2 (Slowly Changing Dimensions) для отслеживания истории изменений.
  1. Слой COMMON DATA MARTS (CDM) — «Общие витрины данных»
  • Назначение: Предоставление данных для конечного использования: аналитика, дашборды, отчёты.
  • Особенности: Данные агрегированы и денормализованы для быстрых запросов. Используются схемы «звезда» или «снежинка».
  • Технология: Материализованные представления и таблицы PostgreSQL, оптимизированные под конкретные бизнес-запросы.

Шаг 0.1.3: Определение потоков данных (ETL/ELT)¶

Опишите, как данные будут перемещаться между слоями.

  1. Extract (Извлечение): Из MS SQL Server в слой STG.
  2. Load (Загрузка): Из STG в RAW.
  3. Transform (Трансформация): Последовательные преобразования: RAW → ODS → DDS → CDM.
    • ODS: Парсинг JSON, очистка, валидация.
    • DDS: Нормализация, применение SCD Type 2.
    • CDM: Агрегация, денормализация, создание витрин.

На этапе MVP реализуются только первые два слоя (STG → RAW). Остальные слои добавляются на этапах промышленного внедрения и развития.


Шаг 0.1.4: Проектирование модели данных¶

Разработайте структуру таблиц для каждого слоя.

  • STG и RAW: Таблица table_1 с колонками (id, dt, json_data, col4, col5).
  • ODS: Таблица, где поля из json_data вынесены в отдельные колонки.
  • DDS: Таблицы, нормализованные по модели Data Vault 2.0 (Хабы, Линки, Сателлиты).
  • CDM: Таблицы-витрины по схеме «звезда» (таблицы фактов и измерений).

Шаг 0.1.5: Планирование инфраструктуры¶

На основе спроектированной архитектуры рассчитайте требования к инфраструктуре.

  1. Дисковая подсистема:
    • RAID-массив: Для надёжности и производительности рекомендуется использовать RAID 6 (как минимум 8 дисков).
    • Разделение дисков: Выделите отдельные тома для:
      • Данных PostgreSQL (горячие)
      • Бэкапов
      • Логов
  2. Вычислительные ресурсы: 16 vCPU, 64 ГБ RAM (как указано в ТЗ).
  3. Сетевое взаимодействие: Обеспечьте доступ к MS SQL Server в окно 7:00–8:00.

Шаг 0.1.6: Документирование архитектуры¶

Создайте документ «Архитектурная схема DLH», который будет включать:

  • Общую схему со всеми слоями и потоками данных.
  • Описание каждого слоя: назначение, технологии, структура данных.
  • Описание ETL/ELT-процессов: как данные перемещаются и трансформируются.
  • План инфраструктуры: требования к ВМ, дискам, сети.
  • План развития: как архитектура будет эволюционировать на этапах промышленного внедрения и развития (добавление Airflow, мониторинга, новых слоёв и источников).

Итоговый чек-лист шага 0.1¶

Действие Статус
Определены бизнес-цели и ключевые потребители данных ☐
Определены источники данных и требования к историчности ☐
Спроектирована многослойная архитектура (STG, RAW, ODS, DDS, CDM) ☐
Для каждого слоя определено назначение и технология реализации ☐
Описаны потоки данных (ETL/ELT) между слоями ☐
Спроектирована модель данных для каждого слоя ☐
Рассчитаны требования к инфраструктуре (диски, RAID, память) ☐
Создан и утверждён документ «Архитектурная схема DLH» ☐

💡 Важное замечание: Учитывая, что проект ведётся в закрытом контуре, все инструменты и библиотеки, которые потребуются на следующих этапах (PostgreSQL, Airflow, Zabbix, Grafana, Python-пакеты), должны быть заранее загружены в локальное хранилище (например, на внутреннем сервере RPM-пакетов или в виде Docker-образов). Архитектура должна проектироваться с учётом этого ограничения.


Инструкция по шагу 0.2: Подготовка виртуальной инфраструктуры (с использованием gdisk)¶

Ниже представлена исправленная и дополненная инструкция по шагу 0.2 «Подготовка виртуальной инфраструктуры» с учётом использования gdisk вместо fdisk для работы с диском свыше 2 ТБ.

Инструкция полностью адаптирована для закрытого контура (без доступа в интернет), предполагает использование только локального хранилища и учитывает лучшие мировые практики виртуализации и работы с GPT-разделами.


Технологический стек шага 0.2¶

Компонент Технология / Версия
Гипервизор VMware vSphere / ESXi 7.0 Update 3 или новее
Гостевая ОС РЕД ОС 8 (серверная редакция)
Архитектура x86_64
Система хранения Виртуальные диски (VMDK)
Схема разделов GPT (GUID Partition Table)
Инструмент разметки gdisk (для дисков >2 ТБ)
Файловая система XFS (рекомендуется для больших томов)
Управление томами LVM2 (Logical Volume Manager)
Сетевой стек Виртуальный сетевой адаптер VMXNET3
Дополнительные драйверы Open VM Tools (open-vm-tools)

Пошаговая инструкция¶

Шаг 0.2.1: Подготовка установочных образов в локальном хранилище¶

Поскольку доступ в интернет отсутствует, заранее подготовьте все необходимые файлы в локальном хранилище (например, на внутреннем файловом сервере или USB-носителе):

  1. ISO-образ РЕД ОС 8 (серверная редакция)

    • Убедитесь, что образ содержит все необходимые пакеты для установки.
  2. Пакет gdisk (RPM) — если он отсутствует в базовом репозитории РЕД ОС, его необходимо заранее загрузить в локальное хранилище. В РЕД ОС 8 пакет gdisk обычно доступен в репозитории baseos.

  3. Разместите ISO-образы и RPM-пакеты в каталоге, доступном с хоста VMware (например, на общем сетевом хранилище или локальном диске).


Шаг 0.2.2: Создание виртуальной машины в VMware¶

0.2.2.1. Запуск мастера создания виртуальной машины¶
  1. Подключитесь к VMware vSphere Client или VMware Workstation.
  2. Выберите действие:
    • В vSphere Client: правой кнопкой мыши на кластере/хосте → New Virtual Machine.
    • В VMware Workstation: File → New Virtual Machine.
  3. Выберите тип конфигурации:
    • В Workstation: выберите Custom (advanced) для полного контроля над настройками.
    • В vSphere: выберите Create a new virtual machine.
0.2.2.2. Выбор установочного носителя¶

На этапе выбора установочного диска укажите путь к ISO-образу РЕД ОС 8 из локального хранилища:

  • Browse → укажите путь к ISO-образу (например, /mnt/storage/redos-8-server.iso).
0.2.2.3. Выбор гостевой операционной системы¶
  1. Guest OS Family: Linux.
  2. Guest OS Version: Red Hat Enterprise Linux 8 (64-bit).
    РЕД ОС 8 базируется на RHEL 8, поэтому эта опция обеспечивает максимальную совместимость.
0.2.2.4. Настройка имени и расположения ВМ¶
  1. Virtual machine name: DLH-PG-SRV (или другое осмысленное имя).
  2. Location: выберите каталог хранения файлов ВМ (желательно на быстром хранилище).
0.2.2.5. Настройка процессора и памяти¶

В соответствии с требованиями проекта:

Параметр Значение Обоснование
Количество vCPU 16 Для параллельной обработки больших объёмов данных и работы ETL-процессов
Количество ядер на сокет 2 (рекомендуется) Оптимизация для гостевой ОС
Оперативная память (RAM) 64 ГБ Для эффективного кэширования и работы PostgreSQL

Рекомендация: в vSphere включите опцию Enable CPU Hot Add и Enable Memory Hot Add для возможности расширения ресурсов без остановки ВМ.

0.2.2.6. Настройка сетевого адаптера¶
  1. Network adapter type: VMXNET3 (паравиртуальный драйвер, обеспечивает лучшую производительность).
  2. Network connection: выберите нужную VLAN/порт-группу для доступа к MS SQL Server и другим системам.
  3. MAC address: оставьте автоматическую генерацию.
0.2.2.7. Настройка SCSI-контроллера¶
  1. SCSI controller type: VMware Paravirtual (рекомендуется для дисков с высокой нагрузкой).
  2. Disk type: Thick Provision Eager Zeroed (для лучшей производительности) или Thin Provision (для экономии места, если хранилище ограничено).
0.2.2.8. Создание виртуальных дисков¶

В соответствии с архитектурой DLH необходимо создать несколько виртуальных дисков для разделения данных, логов и бэкапов.

Диск Размер Контроллер Назначение
Диск 0 (системный) 100 ГБ SCSI 0:0 Операционная система РЕД ОС и системные файлы
Диск 1 (данные PostgreSQL) 3,5 ТБ SCSI 1:0 Хранение данных PostgreSQL (слой STG и активные партиции RAW)
Диск 3 (бэкапы) 1.4 ТБ SCSI 1:2 Еженедельные бэкапы и архивы
Диск 4 (логи) 300 ГБ SCSI 1:3 Системные логи и логи ETL-процессов

Важно: Общий объём дисков (100 ГБ + 3,5 ТБ + 1.4 ТБ + 300 ГБ) ≈ 5,3 ТБ, что соответствует требованиям ТЗ (5 ТБ HDD).

Рекомендации по размещению дисков:

  • Разместите диски на разных LUN/датасторах для повышения производительности ввода-вывода.
  • Для диска с данными используйте отдельный RAID-массив (RAID 10 или RAID 6).
  • Для бэкапов можно использовать более медленное хранилище.
0.2.2.9. Настройка дополнительных параметров¶
  1. CD/DVD drive: оставьте подключение к ISO-образу РЕД ОС.
  2. USB controller: не требуется.
  3. Video card: оставьте стандартные настройки (достаточно 4 МБ видеопамяти).
  4. VMware Tools: отключите автоматическую установку (установим позже вручную через Open VM Tools).

Шаг 0.2.3: Установка РЕД ОС 8 на виртуальную машину¶

0.2.3.1. Запуск ВМ и начало установки¶
  1. Включите виртуальную машину.
  2. Загрузка с ISO: система автоматически загрузится с подключенного ISO-образа.
  3. Выберите пункт меню: Install RED OS 8.
0.2.3.2. Настройка языка и раскладки¶
  1. Выберите язык установки: Русский.
  2. Раскладка клавиатуры: Russian (добавьте English (US) для удобства).
0.2.3.3. Настройка установочных параметров¶

Выбор типа установки:

  • Выберите «Установка РЕД ОС» (полная установка, не обновление).

Настройка дисков (разметка). Для диска 0 (системный, 100 ГБ) рекомендуется следующая схема разметки:

Точка монтирования Размер Файловая система Назначение
/boot/efi 200 МБ vfat Загрузочный раздел для UEFI (если используется UEFI)
/boot 1 ГБ ext4 Загрузочный раздел
/ (root) 50 ГБ xfs Корневая файловая система
/var 20 ГБ xfs Логи и временные файлы
/home 10 ГБ xfs Домашние каталоги пользователей
swap 16 ГБ swap Подкачка (равен объёму RAM для возможности дампа памяти)
Остальное ~3 ГБ — Свободное пространство для будущих нужд

Важно: Для дисков 1–4 (данные, WAL, бэкапы, логи) не создавайте разделов в Anaconda. Они будут подключены и отформатированы позже как отдельные тома.

Выбор программного обеспечения:

  • Software Selection: выберите «Сервер» (Server).
  • Дополнительные модули: отметьте:
    • Headless Management
    • Standard или Minimal (для снижения потребления ресурсов)

Настройка сети:

  1. Hostname: dlh-pg-srv.local (или согласно корпоративной политике именования).
  2. Network interfaces: настройте статический IP-адрес для гарантированной доступности:
    • IP-адрес: согласно плану сети
    • Маска подсети: согласно плану сети
    • Шлюз: согласно плану сети
    • DNS-серверы: согласно плану сети

Настройка времени и локали:

  1. Time Zone: выберите свой часовой пояс (например, Europe/Moscow).
  2. NTP: настройте синхронизацию с корпоративным NTP-сервером (в закрытом контуре).

Создание пользователей:

  1. Пароль root: установите надёжный пароль для суперпользователя.
  2. Создание пользователя: создайте учётную запись для администратора (например, admin).

Начало установки:

  1. Нажмите «Начать установку».
  2. Дождитесь завершения установки (обычно 15–30 минут).
  3. После завершения перезагрузите систему.

Шаг 0.2.4: Первичная настройка системы¶

0.2.4.1. Вход в систему¶
  1. Войдите под пользователем root.
  2. Проверьте сетевое подключение:
ip addr show
   ping <gateway_ip>
0.2.4.2. Настройка менеджера пакетов для локального репозитория¶

Поскольку доступ в интернет отсутствует, настройте dnf на использование локального репозитория:

  1. Подключите локальный репозиторий (если он не был подключён автоматически):
    dnf config-manager --add-repo=file:///mnt/local_repo
    dnf makecache
    
  2. Проверьте доступные репозитории:
    dnf repolist
    
0.2.4.3. Установка пакета gdisk (если отсутствует)¶

В РЕД ОС 8 пакет gdisk обычно входит в состав базового репозитория. Установите его:

dnf install -y gdisk

Если пакет отсутствует в репозитории, установите его из заранее подготовленного RPM-файла:

dnf install -y /path/to/gdisk-*.rpm

Шаг 0.2.5: Работа с диском данных (3,5 ТБ) с использованием gdisk¶

Важно: Для дисков размером более 2 ТБ необходимо использовать GPT-разметку. Стандартная MBR-разметка не поддерживает диски свыше 2 ТБ. gdisk является специализированным инструментом для работы с GPT.

0.2.5.1. Проверка обнаруженных дисков¶
lsblk

Вы должны увидеть устройства, соответствующие добавленным виртуальным дискам:

  • /dev/sda — системный диск
  • /dev/sdb — диск данных, бэкапов и логов (3.5 + 1.4 + 0.3 ТБ)
0.2.5.2. Создание GPT-раздела STG на диске данных с помощью gdisk¶
  1. Запустите gdisk для диска /dev/sdb:
gdisk /dev/sdb

Последовательность действий в интерактивном режиме:

  • Создание новой GPT-таблицы (если на диске есть старая MBR-разметка):
    • Введите команду: o
    • Подтвердите создание новой GPT-таблицы: Y
  • Создание нового раздела:
    • Введите команду: n
    • Номер раздела (по умолчанию 1): нажмите Enter
    • Начальный сектор (по умолчанию): нажмите Enter
    • Конечный сектор (задать вручную): введите 1757812534 и нажмите Enter
    • Тип раздела (по умолчанию 8300 для Linux filesystem): нажмите Enter
  • Проверка созданного раздела:
    • Введите команду: p — вы увидите информацию о новом разделе
  • Запись изменений на диск:
    • Введите команду: w
    • Подтвердите запись: Y

Примечание: gdisk автоматически создаёт GPT-разметку, которая поддерживает диски объёмом более 2 ТБ.

  1. Смонтируйте раздел:
mkfs.xfs -f /dev/sdb1
mkdir -p /pgdata
mount /dev/sdb1 /pgdata
0.2.5.3. Создание разделов и файловых систем для остальных дисков¶

Для дисков менее 2 ТБ (WAL, бэкапы, логи) можно использовать как gdisk, так и fdisk. Рекомендуется использовать gdisk для единообразия.

  1. Создайте диск RAW (/dev/sdb2):
gdisk /dev/sdd
# o - n (конечный сектор 6835937534) - p - w
mkfs.xfs -f /dev/sdb2
mkdir -p /backup
mount /dev/sdb2 /backup
  1. Создайте диск бэкапов (/dev/sdb3):
gdisk /dev/sdd
# o - n (конечный сектор 9765625034) - p - w
mkfs.xfs -f /dev/sdb3
mkdir -p /backup
mount /dev/sdb3 /backup
  1. Создайте диск логов (/dev/sdb4):
gdisk /dev/sde
# o - n (конечный сектор 10485759966) - p - w
mkfs.xfs -f /dev/sdb4
mkdir -p /var/log/dlh
mount /dev/sdb4 /var/log/dlh
  1. Проверьте созданные разделы диска sdb:
# Последовательно введите команды
sudo gdisk /dev/sdb/
p

# Результат
Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048      1757812534   838.2 GiB   8300  Linux filesystem
   2      1757812736      6835937534   2.4 TiB     8300  Linux filesystem
   3      6835939328      9765625034   1.4 TiB     8300  Linux filesystem
   4      9765625856     10485759966   343.4 GiB   8300  Linux filesystem

Созданные тома:

Том Назначение Размер
sdb1 Данные слоя STG 0.9 Тб
sdb2 Данные слоя RAW 2.6 Тб
sdb3 Бэкап 1.4 Тб
sdb4 Логи 34 Гб = остаток от 4.9 Тб
0.2.5.4. Добавление в /etc/fstab для автоматического монтирования¶
  1. Добавьте записи в /etc/fstab:
nano /etc/fstab
  1. Добавьте строки:
/dev/sdb1  /pgdata       xfs  defaults  0 2
/dev/sdb3  /backup       xfs  defaults  0 2
/dev/sdb4  /var/log/dlh  xfs  defaults  0 2
  1. Проверьте монтирование:
mount -a
df -h

Шаг 0.2.6: Настройка LVM для будущих партиций (на диске данных)¶

Для упрощения управления дополнительными томами (партициями RAW-слоя) создайте LVM-группу на диске данных:

# Проверка существующих томов
lvdisplay

# Создание физического тома
sudo pvcreate /dev/sdb2

# Создание группы томов
sudo vgcreate vg_data /dev/sdb2

# Создание логических томов для будущих партиций (пример для первых 4 кварталов)
sudo lvcreate -L 800G -n lv_raw_q2_2026 vg_data
sudo lvcreate -L 800G -n lv_raw_q3_2026 vg_data
sudo lvcreate -L 800G -n lv_raw_q4_2026 vg_data

# Форматирование
sudo mkfs.xfs /dev/vg_data/lv_raw_q2_2026
sudo mkfs.xfs /dev/vg_data/lv_raw_q3_2026
sudo mkfs.xfs /dev/vg_data/lv_raw_q4_2026

# Создание точек монтирования
sudo mkdir -p /mnt/raw_q2_2026 /mnt/raw_q3_2026 /mnt/raw_q4_2026

# Добавление записей в `/etc/fstab` для автоматического монтирования
echo '/dev/vg_data/lv_raw_q2_2026 /mnt/raw_q2_2026 xfs defaults 0 0' | sudo tee -a /etc/fstab
echo '/dev/vg_data/lv_raw_q3_2026 /mnt/raw_q3_2026 xfs defaults 0 0' | sudo tee -a /etc/fstab
echo '/dev/vg_data/lv_raw_q4_2026 /mnt/raw_q4_2026 xfs defaults 0 0' | sudo tee -a /etc/fstab

# Назначение прав на каталоги для пользователя `postgres`
sudo chown postgres:postgres /mnt/raw_q2_2026 /mnt/raw_q3_2026 /mnt/raw_q4_2026
sudo chmod 700 /mnt/raw_q*

# Проверка всех смонтированных томов:
df -h /mnt/raw_q*

# Проверка существующих томов
lvdisplay

Шаг 0.2.7: Установка Open VM Tools (VMware Tools)¶

VMware рекомендует использовать Open VM Tools из репозитория ОС.

  1. Установка через локальный репозиторий:
dnf install -y open-vm-tools open-vm-tools-desktop
  1. Запуск службы:
systemctl enable vmtoolsd.service --now
systemctl status vmtoolsd.service
  1. Перезагрузка ВМ для применения изменений:
reboot

Шаг 0.2.8: Настройка безопасности (базовая)¶

  1. Настройка файрвола (firewalld):
# Открыть порт для SSH (если ещё не открыт)
firewall-cmd --add-service=ssh --permanent

# Открыть порт для PostgreSQL (для будущих подключений)
firewall-cmd --add-port=5432/tcp --permanent

# Перезагрузить правила
firewall-cmd --reload
  1. Отключение SELinux (опционально, для упрощения). В некоторых конфигурациях РЕД ОС может потребоваться отключение SELinux:
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config

Рекомендация: в продуктивных средах лучше настроить SELinux в режиме permissive для отладки, а затем переключить в enforcing.

  1. Настройка SSH для удалённого управления из Windows:
# Убедиться, что SSH-сервер запущен
systemctl enable sshd --now

# Разрешить вход по ключам (для PowerShell)
mkdir -p /root/.ssh
chmod 700 /root/.ssh

Итоговый чек-лист шага 0.2¶

Действие Статус
Подготовлены ISO-образы в локальном хранилище ☐
Создана виртуальная машина с 16 vCPU и 64 ГБ RAM ☐
Созданы отдельные виртуальные диски для данных, WAL, бэкапов, логов ☐
Установлена РЕД ОС 8 ☐
Настроен локальный репозиторий для dnf ☐
Установлен пакет gdisk ☐
С помощью gdisk создана GPT-разметка на диске данных (3,5 ТБ) ☐
Подключены и отформатированы дополнительные диски ☐
Настроено автоматическое монтирование в /etc/fstab ☐
Установлены Open VM Tools ☐
Настроен файрвол (открыты порты SSH и PostgreSQL) ☐
Создана LVM-группа vg_data и логические тома для партиций ☐
Проверена работоспособность системы (перезагрузка, подключение по SSH) ☐

Возможные проблемы и их решение¶

Проблема Решение
gdisk не установлен Установите из локального репозитория: dnf install -y gdisk. Если пакет отсутствует, установите из RPM-файла.
Диск не виден в системе Перезагрузите ВМ и проверьте lsblk. Убедитесь, что SCSI-контроллер настроен правильно.
Ошибка при создании GPT-разметки Убедитесь, что на диске нет активных разделов. При необходимости используйте gdisk для удаления существующей разметки (команда z для очистки).
Система не загружается после перезагрузки Проверьте порядок загрузки в BIOS/UEFI. Убедитесь, что загрузочный раздел правильно настроен.
Нет доступа по SSH Проверьте настройки файрвола: firewall-cmd --list-all.

Почему gdisk, а не fdisk?¶

Характеристика MBR (fdisk) (gdisk)
Максимальный размер диска 2 ТБ 2 ТБ (до 9,4 ЗБ)
Максимальное количество разделов 4 основных (или 3 основных + 1 расширенный) До 128
Поддержка UEFI Ограниченная Полная
Резервирование таблицы разделов Нет Да (копия в конце диска)

Использование gdisk — это стандартная мировая практика для работы с дисками объёмом более 2 ТБ.


Эта инструкция полностью учитывает работу в закрытом контуре без доступа в интернет, опирается на официальную документацию РЕД ОС и VMware, а также на лучшие мировые практики работы с большими дисками с использованием GPT-разметки.


Инструкция по шагу 0.3: Настройка сетевого взаимодействия¶

Ниже представлена детальная пошаговая инструкция по выполнению шага 0.3. Настройка сетевого взаимодействия из итоговой архитектуры Data Lakehouse (DLH). В рамках этого шага мы настроим статический IP-адрес, DNS-серверы, имя хоста, межсетевой экран (firewalld), службу синхронизации времени (Chrony) и SSH-доступ для удалённого управления.

Инструкция полностью адаптирована для закрытого контура (без доступа в интернет), предполагает использование только локального хранилища и учитывает лучшие мировые практики безопасности и администрирования.


Технологический стек шага 0.3¶

Компонент Технология / Версия
Операционная система РЕД ОС 8 (серверная редакция)
Управление сетью NetworkManager + nmcli
Межсетевой экран firewalld
Синхронизация времени chronyd (Chrony)
Удалённый доступ openssh-server
Разрешение имён systemd-resolved / /etc/resolv.conf

Пошаговая инструкция¶

Шаг 0.3.1: Вход в систему и проверка сетевых интерфейсов¶

  1. Войдите в систему под пользователем root или пользователем с правами sudo.

  2. Проверьте список сетевых интерфейсов и их текущие настройки:

    ip addr show
    

    или

    nmcli device status
    

  3. Определите имя сетевого интерфейса, который будет использоваться для подключения к корпоративной сети (обычно eth0 или ens192). В дальнейшем в командах мы будем использовать eth0 как пример — замените его на актуальное имя вашего интерфейса.


Шаг 0.3.2: Настройка статического IP-адреса¶

В закрытом контуре сервер должен иметь постоянный IP-адрес для гарантированной доступности. Настройка выполняется через NetworkManager с использованием утилиты nmcli.

  1. Проверьте текущее подключение:

    nmcli connection show
    

    Вы увидите список подключений. Обычно активное подключение называется eth0 или System eth0.

  2. Настройте статический IP-адрес, маску подсети и шлюз. Замените значения на актуальные для вашей сети:

    nmcli connection modify eth0 ipv4.method manual
    nmcli connection modify eth0 ipv4.addresses 192.168.1.100/24
    nmcli connection modify eth0 ipv4.gateway 192.168.1.1
    

  3. Отключите автоматическое получение DNS-адресов от DHCP и укажите статические DNS-серверы (в закрытом контуре это обычно внутренние DNS-серверы):

    nmcli connection modify eth0 ipv4.ignore-auto-dns true
    nmcli connection modify eth0 ipv4.dns "192.168.1.10 192.168.1.11"
    

  4. Укажите поисковый домен (если требуется):

    nmcli con mod eth0 ipv4.dns-search "your-company.local"
    

  5. Перезапустите сетевое подключение для применения настроек:

    nmcli con down eth0 && nmcli con up eth0
    

  6. Проверьте новые настройки:

    ip addr show eth0
    ping 192.168.1.1   # проверка доступности шлюза
    


Шаг 0.3.3: Настройка имени хоста (hostname)¶

Установите осмысленное имя хоста, которое будет использоваться для идентификации сервера в сети.

  1. Проверьте текущее имя хоста:

    hostname
    hostnamectl
    

  2. Установите новое имя хоста (например, dlh-pg-srv):

    hostnamectl set-hostname dlh-pg-srv.your-company.local
    

  3. Добавьте запись в файл /etc/hosts, чтобы имя хоста разрешалось в локальный IP-адрес:

    echo "192.168.1.100 dlh-pg-srv.your-company.local dlh-pg-srv" >> /etc/hosts
    

  4. Проверьте разрешение имени:

    ping dlh-pg-srv
    

Шаг 0.3.4: Настройка DNS-разрешения (дополнительно)¶

В РЕД ОС управление DNS может осуществляться через systemd-resolved или напрямую через /etc/resolv.conf. Если вы уже указали DNS-серверы через nmcli (шаг 0.3.2), они должны автоматически появиться в /etc/resolv.conf.

  1. Проверьте содержимое файла /etc/resolv.conf:

    cat /etc/resolv.conf
    

    В нём должны быть указаны ваши DNS-серверы и поисковый домен.

  2. Если настройки не применились, можно отредактировать файл вручную (но учтите, что NetworkManager может перезаписывать его):

    echo "nameserver 192.168.1.10" > /etc/resolv.conf
    echo "nameserver 192.168.1.11" >> /etc/resolv.conf
    echo "search your-company.local" >> /etc/resolv.conf
    

  3. Проверьте резолвинг внешних (внутри контура) имён:

    nslookup dlh-pg-srv
    


Шаг 0.3.5: Настройка межсетевого экрана (firewalld)¶

В целях безопасности необходимо открыть только те порты, которые действительно нужны для работы сервера. По умолчанию firewalld может быть уже установлен, но его необходимо настроить.

  1. Убедитесь, что firewalld установлен и запущен:

    dnf install -y firewalld
    systemctl enable firewalld --now
    systemctl status firewalld
    

  2. Проверьте текущую зону и активные правила:

    firewall-cmd --get-default-zone
    firewall-cmd --list-all
    

  3. Откройте порт для SSH (порт 22), если он ещё не открыт:

    firewall-cmd --add-service=ssh --permanent
    

  4. Откройте порт для PostgreSQL (порт 5432), который потребуется на следующих этапах для подключения извне (например, из Airflow):

    firewall-cmd --add-port=5432/tcp --permanent
    

  5. Если планируется использование веб-интерфейсов (например, Grafana на порту 3000 или Airflow на порту 8080), откройте соответствующие порты:

    firewall-cmd --add-port=3000/tcp --permanent
    firewall-cmd --add-port=8080/tcp --permanent
    

  6. Перезагрузите правила firewalld для применения постоянных изменений:

    firewall-cmd --reload
    

  7. Проверьте список открытых портов:

    firewall-cmd --list-all
    


Шаг 0.3.6: Настройка синхронизации времени (Chrony)¶

Точное время критически важно для корректной работы базы данных, логирования и ETL-процессов. В РЕД ОС по умолчанию используется Chrony.

  1. Проверьте статус службы Chrony:

    systemctl status chronyd.service
    

    Если служба не активна, запустите её:

    systemctl enable chronyd.service --now
    

  2. Настройте NTP-серверы. В закрытом контуре это должен быть корпоративный NTP-сервер. Отредактируйте файл конфигурации:

    nano /etc/chrony.conf
    

    Найдите и замените или добавьте строки с NTP-серверами (пример для корпоративного сервера):

    server ntp.your-company.local iburst
    

    Закомментируйте или удалите публичные серверы, если они есть.

  3. Перезапустите Chrony для применения изменений:

    systemctl restart chronyd.service
    

  4. Проверьте синхронизацию времени:

    chronyc tracking
    

    В выводе должно отображаться, что система синхронизирована с указанным NTP-сервером.

  5. Проверьте текущее системное время:

    date
    


Шаг 0.3.7: Настройка SSH-доступа для удалённого управления¶

SSH-сервер в РЕД ОС обычно установлен по умолчанию. Однако для повышения безопасности и удобства управления из Windows 11 через PowerShell рекомендуется выполнить дополнительные настройки.

  1. Убедитесь, что пакет openssh-server установлен:

    dnf install -y openssh-server
    

  2. Запустите SSH-сервер и добавьте его в автозагрузку:

    systemctl enable sshd --now
    systemctl status sshd
    

  3. Настройте SSH-сервер для повышения безопасности. Отредактируйте файл /etc/ssh/sshd_config:

    nano /etc/ssh/sshd_config
    

    Внесите следующие изменения (раскомментируйте или добавьте строки):

    Port 22
    PermitRootLogin no                # Запрет входа под root (рекомендуется)
    PasswordAuthentication yes        # Оставьте для первоначальной настройки, затем отключите
    PubkeyAuthentication yes          # Включите аутентификацию по ключам
    MaxAuthTries 3                    # Ограничьте число попыток входа
    

  4. Перезапустите SSH-сервер для применения изменений:

    systemctl restart sshd
    

  5. Настройте аутентификацию по SSH-ключам для беспарольного входа из Windows 11:

    • На машине с Windows 11 откройте PowerShell и сгенерируйте ключи (если ещё не сделали):

      ssh-keygen -t rsa -b 4096
      

    • Скопируйте публичный ключ на сервер РЕД ОС:

      type C:\Users\<Ваше_имя>\.ssh\id_rsa.pub | ssh admin@192.168.1.100 "cat >> ~/.ssh/authorized_keys"
      

    • На сервере установите правильные права доступа:

      chmod 700 ~/.ssh
      chmod 600 ~/.ssh/authorized_keys
      

  6. Проверьте подключение по SSH с Windows 11:

    ssh admin@192.168.1.100
    


Шаг 0.3.8: Проверка сетевой связанности и доступности сервисов¶

  1. Проверьте доступность сервера из сети:

    ping <ip-адрес_сервера>
    

  2. Проверьте доступность порта SSH (22) извне:

    telnet <ip-адрес_сервера> 22
    

  3. Проверьте разрешение DNS-имён:

    nslookup dlh-pg-srv
    

  4. Проверьте синхронизацию времени:

    timedatectl
    

  5. Проверьте, что firewalld не блокирует необходимые порты:

    firewall-cmd --list-all
    


Итоговый чек-лист шага 0.3¶

Действие Статус
Определено имя сетевого интерфейса ☐
Настроен статический IP-адрес, маска, шлюз ☐
Настроены DNS-серверы и поисковый домен ☐
Установлено имя хоста (hostname) ☐
Настроен и запущен firewalld ☐
Открыты порты: SSH (22), PostgreSQL (5432) и др. ☐
Настроена синхронизация времени (Chrony) с корпоративным NTP ☐
Настроен и запущен SSH-сервер ☐
Настроена аутентификация по SSH-ключам ☐
Проверена сетевая связанность (ping, DNS, порты) ☐

Возможные проблемы и их решение¶

Проблема Решение
nmcli не видит интерфейс Проверьте имя интерфейса: nmcli device status. Используйте актуальное имя в командах.
После перезагрузки сеть не поднимается Проверьте настройки в /etc/sysconfig/network-scripts/ifcfg-eth0. Убедитесь, что ONBOOT=yes.
firewalld не запускается Проверьте логи: journalctl -u firewalld. Убедитесь, что пакет установлен из локального репозитория.
Chrony не синхронизируется Проверьте доступность NTP-сервера: ping ntp.your-company.local. Проверьте конфигурацию в /etc/chrony.conf.
SSH-подключение отклоняется Проверьте настройки firewalld: firewall-cmd --list-all. Проверьте файл /etc/ssh/sshd_config.
Нет доступа к DNS-серверам Проверьте, что DNS-серверы доступны по сети. Временно используйте файл /etc/hosts для разрешения имён.

Эта инструкция полностью учитывает работу в закрытом контуре без доступа в интернет, опирается на официальную документацию РЕД ОС и лучшие мировые практики настройки сетевой подсистемы для высоконагруженных серверов баз данных.


Подробная инструкция по шагу 1.1: Установка PostgreSQL¶

Ниже представлена детальная пошаговая инструкция по выполнению шага 1.1 «Установка PostgreSQL» из итоговой архитектуры Data Lakehouse (DLH).

Инструкция полностью адаптирована для закрытого контура (без доступа в интернет), предполагает установку только из локального репозитория и учитывает, что на предыдущем шаге вы уже подготовили диск /dev/sdb для хранения данных.


1. Технологический стек шага 1.1¶

Компонент Технология / Версия
Операционная система РЕД ОС 8 (серверная редакция)
СУБД PostgreSQL 17 (доступен в локальном репозитории как postgresql17-server)
Менеджер пакетов dnf (работает с локальным репозиторием)
Служба systemd (управление сервисами)
Каталог данных /pgdata/17/data (на отдельном диске /dev/sdb)
Инструменты postgresql-17-setup, psql, pg_ctl

Важно: пакет postgresql17-contrib отсутствует в локальном репозитории. Это ограничение учтено — на данном этапе мы устанавливаем только серверную часть, которая уже содержит всё необходимое для базовой работы (включая поддержку типа JSONB).


2. Цель шага¶

Установить PostgreSQL 17 на сервер РЕД ОС, разместив кластер баз данных на отдельном диске /dev/sdb (смонтирован в /pgdata), и настроить автоматический запуск службы. Это создаст основу для всех последующих слоёв хранилища (STG, RAW и т.д.).


3. Пошаговое выполнение¶

Шаг 1.1.1: Вход в систему и проверка дисков¶

  1. Подключитесь к серверу по SSH или через локальную консоль.
  2. Убедитесь, что диск /dev/sdb1 размечен и смонтирован в /pgdata:
lsblk
   df -h /pgdata

Вы должны увидеть, что /dev/sdb1 (или аналогичный раздел) смонтирован в /pgdata.


Шаг 1.1.2: Установка PostgreSQL 17 из локального репозитория¶

Поскольку доступ в интернет отсутствует, установка выполняется через dnf из локального репозитория.

  1. Установите сервер PostgreSQL 17:
    sudo dnf install -y postgresql17-server
    

Обратите внимание: пакет postgresql17-contrib не устанавливаем, так как его нет в репозитории. Это не критично — все основные функции (включая JSONB) уже входят в серверный пакет.

  1. Проверьте, что установка прошла успешно:

    rpm -qa | grep postgresql17
    

    В выводе должна быть строка с postgresql17-server.


Шаг 1.1.3: Инициализация кластера баз данных на отдельном диске¶

По умолчанию кластер создаётся в /var/lib/pgsql/. Нам нужно разместить его на /pgdata.

  1. Создайте каталог для данных и назначьте владельца:

    sudo mkdir -p /pgdata/17/data
    sudo chown postgres:postgres /pgdata/17/data
    sudo chmod 700 /pgdata/17/data
    

  2. Выполните инициализацию кластера с указанием нового каталога:

    sudo /usr/pgsql-17/bin/postgresql-17-setup initdb
    

Если команда не сработает, можно использовать ручную инициализацию:

sudo -u postgres /usr/pgsql-17/bin/initdb -D /pgdata/17/data
  1. Проверьте, что каталог данных создан:

    ls -la /pgdata/17/data
    

    Вы должны увидеть файлы postgresql.conf, pg_hba.conf и другие.


Шаг 1.1.4: Настройка конфигурационных файлов¶

Чтобы PostgreSQL использовал новый каталог данных, укажем путь в конфигурации службы.

  1. Отредактируйте файл окружения для службы (если он существует):

    sudo nano /etc/sysconfig/postgresql-17
    

    Добавьте или раскомментируйте строку:

    PGDATA=/pgdata/17/data
    

  2. Если файла нет, создайте символическую ссылку (альтернативный способ):

    sudo systemctl stop postgresql-17
    sudo mv /var/lib/pgsql/17/data /var/lib/pgsql/17/data.bak
    sudo ln -s /pgdata/17/data /var/lib/pgsql/17/data
    sudo chown -h postgres:postgres /var/lib/pgsql/17/data
    

  3. Настройте производительность (под 64 ГБ RAM). Откройте postgresql.conf:

    sudo nano /pgdata/17/data/postgresql.conf
    

    Найдите и измените параметры:

    shared_buffers = 16GB
    work_mem = 128MB
    maintenance_work_mem = 2GB
    effective_cache_size = 48GB
    max_connections = 200
    

Это стандартная рекомендация для выделенных серверов с 64 ГБ RAM.


Шаг 1.1.5: Запуск службы и добавление в автозагрузку¶

  1. Запустите PostgreSQL и настройте автоматический старт:

    sudo systemctl enable postgresql-17 --now
    

  2. Проверьте статус службы:

    sudo systemctl status postgresql-17
    

    В выводе должно быть active (running).


Шаг 1.1.6: Настройка удалённого доступа (опционально)¶

Для подключения извне (например, из Airflow на следующих этапах) разрешите удалённые подключения.

  1. В файле postgresql.conf раскомментируйте и измените:

    listen_addresses = '*'
    

  2. В файле pg_hba.conf добавьте правило для удалённых подключений:

    sudo nano /pgdata/17/data/pg_hba.conf
    

    Добавьте строку (замените подсеть на вашу):

    host    all    all    192.168.1.0/24    scram-sha-256
    

  3. Перезапустите службу:

    sudo systemctl restart postgresql-17
    


Шаг 1.1.7: Установка пароля для пользователя postgres¶

Для безопасности и последующей работы задайте пароль суперпользователю:

sudo -u postgres psql -c "ALTER USER postgres WITH ENCRYPTED PASSWORD 'your_strong_password';"

Важно: замените 'your_strong_password' на надёжный пароль и сохраните его.


Шаг 1.1.8: Проверка работоспособности¶

  1. Подключитесь локально:

    sudo -u postgres psql
    
  2. Выполните команду для проверки версии:

    SELECT version();
    

    Ожидаемый вывод: PostgreSQL 17.x on x86_64-redhat-linux-gnu...

  3. Проверьте список баз данных:

    \l
    

    Должны отображаться служебные базы template0, template1, postgres.

  4. Выход из psql:

    \q
    

Шаг 1.1.9: Настройка файрвола (firewalld)¶

Откройте порт PostgreSQL (5432) для внешних подключений (если планируется):

sudo firewall-cmd --add-port=5432/tcp --permanent
sudo firewall-cmd --reload

4. Полный скрипт для автоматизации шага 1.1¶

Вы можете сохранить этот скрипт в файл install_postgres.sh и выполнить его от имени root:

#!/bin/bash
# Шаг 1.1: Установка PostgreSQL 17 на РЕД ОС (закрытый контур)

set -e

echo "=== 1. Проверка диска /pgdata ==="
df -h /pgdata || { echo "Ошибка: /pgdata не смонтирован!"; exit 1; }

echo "=== 2. Установка PostgreSQL 17 ==="
dnf install -y postgresql17-server

echo "=== 3. Создание каталога данных ==="
mkdir -p /pgdata/17/data
chown postgres:postgres /pgdata/17/data
chmod 700 /pgdata/17/data

echo "=== 4. Инициализация кластера ==="
/usr/pgsql-17/bin/postgresql-17-setup initdb

echo "=== 5. Настройка PGDATA ==="
echo "PGDATA=/pgdata/17/data" > /etc/sysconfig/postgresql-17

echo "=== 6. Настройка производительности ==="
cat >> /pgdata/17/data/postgresql.conf << EOF
shared_buffers = 16GB
work_mem = 128MB
maintenance_work_mem = 2GB
effective_cache_size = 48GB
max_connections = 200
EOF

echo "=== 7. Запуск службы ==="
systemctl enable postgresql-17 --now
systemctl status postgresql-17 --no-pager

echo "=== 8. Установка пароля для postgres ==="
sudo -u postgres psql -c "ALTER USER postgres WITH ENCRYPTED PASSWORD 'your_strong_password';"

echo "=== 9. Проверка ==="
sudo -u postgres psql -c "SELECT version();"

echo "=== Установка PostgreSQL 17 завершена ==="

5. Учёт ограничений проекта¶

Ограничение Как учтено
Нет доступа в интернет Все пакеты устанавливаются из локального репозитория через dnf.
Отсутствует postgresql17-contrib Устанавливается только серверный пакет, который уже включает все базовые возможности (включая JSONB). Дополнительные расширения не требуются.
Диск /dev/sdb уже размечен Мы используем его для хранения данных (/pgdata), не изменяя разметку.
Предыдущие шаги выполнены ВМ создана, РЕД ОС установлена, диск /dev/sdb подготовлен и смонтирован.

6. Лучшие мировые практики, применённые в шаге¶

  • Размещение данных на отдельном диске — повышает производительность и упрощает бэкапирование.
  • Настройка параметров памяти — оптимизация под 64 ГБ RAM для эффективной работы с большими объёмами данных.
  • Использование systemd — стандартный механизм управления службами в РЕД ОС.
  • Установка пароля для postgres — базовая мера безопасности.
  • Открытие только необходимых портов — минимизация поверхности атаки.

7. Связь с последующими шагами¶

  • Шаг 1.2 — на основе установленного PostgreSQL будет создан слой STG (таблица-песочница).
  • Шаг 1.3 — будет создан слой RAW с партиционированием по кварталам.
  • Шаг 1.5 — ETL-скрипт будет использовать установленный PostgreSQL для загрузки и трансформации данных.

8. Итоговый чек-лист шага 1.1¶

Действие Статус
Диск /pgdata проверен и доступен ☐
Пакет postgresql17-server установлен из локального репозитория ☐
Каталог /pgdata/17/data создан с правильными правами ☐
Кластер инициализирован ☐
Настроен путь PGDATA в /etc/sysconfig/postgresql-17 ☐
Настроены параметры производительности ☐
Служба запущена и добавлена в автозагрузку ☐
Установлен пароль для пользователя postgres ☐
Проверена работа psql ☐
(Опционально) Открыт порт 5432 в файрволе ☐

Если на каком-либо шаге возникнут трудности (например, с правами доступа или инициализацией), обратитесь к логам службы: sudo journalctl -u postgresql-17.


Следует ли в PostgreSQL сохранять формат названий таблиц и их полей из исходной БД MS SQL (например, Table1 вместо table_1)?¶

В PostgreSQL не следует сохранять формат названий таблиц и их полей из исходной БД MS SQL. Настоятельно рекомендуем использовать единый стиль именования snake_case (нижний регистр с подчёркиваниями) во всех слоях PostgreSQL, а оригинальные имена из MS SQL хранить в комментариях и документации.

Почему snake_case, а не оригинальный PascalCase или camelCase?¶

Критерий Исходный стиль (MS SQL) snake_case
Регистрозависимость В PostgreSQL без кавычек имена приводятся к нижнему регистру. Table1 → table1 (если не использовать кавычки). Это создаёт путаницу. Все имена однозначно приводятся к нижнему регистру, проблем с регистром нет.
Удобство запросов Приходится постоянно заключать имена в двойные кавычки, если хотите сохранить CamelCase. Это усложняет написание SQL. Имена пишутся просто, без кавычек.
Совместимость с инструментами Многие BI-инструменты и ORM ожидают snake_case. Лучшая совместимость.
Читаемость Table1, RowID – менее читаемы, чем table_1, row_id (особенно при длинных именах). Читаемость выше.
Связь с источником Исходные имена могут быть сохранены в метаданных (комментариях, словаре данных) для трассировки. Можно хранить оригинальное имя в комментарии к таблице или колонке.

Рекомендация по реализации¶

  1. На слоях STAGING и RAW – используйте snake_case, но добавьте комментарии к таблицам и колонкам с указанием оригинальных имён из MS SQL.
    Например:

    COMMENT ON TABLE stg.table_1 IS 'Оригинальная таблица: Table1 из MS SQL';
    COMMENT ON COLUMN stg.table_1.id IS 'Оригинальное поле: RowID';
    

    Это позволит разработчикам и аналитикам легко сопоставлять имена, не теряя связи с источником.

  2. На слоях ODS, DDS, CDM – также используйте snake_case, но здесь вы можете давать более осмысленные имена, отражающие бизнес-смысл (например, customer_id, order_date), уже не привязываясь строго к исходным именам.

  3. В ETL-скриптах – при выгрузке из MS SQL вы можете использовать алиасы для приведения имён к snake_case:

    SELECT RowID AS id, CreatedDate AS dt, ...
    

    Это позволит сразу загружать данные в столбцы с нужными именами.

Что делать, если аналитикам важно видеть оригинальные имена?¶

  • Создайте словарь данных (документацию или представления), где будет таблица соответствия: оригинальное имя → имя в DLH.
  • В BI-инструментах (например, Power BI) можно переименовывать поля на этапе визуализации, не меняя структуру хранилища.

Итог¶

Используйте snake_case во всех слоях PostgreSQL, а оригинальные имена из MS SQL храните в комментариях и документации. Это сэкономит вам множество часов отладки и упростит написание запросов. Сохранение оригинального стиля приведёт к постоянным проблемам с регистром и кавычками, что не стоит того ради «визуальной близости» к источнику.


Подробная инструкция по шагу 1.2: Создание слоя STG (таблица-песочница)¶

Технологический стек шага¶

Компонент Версия / Технология
Операционная система РЕД ОС 8 (серверная редакция)
СУБД PostgreSQL 17 (установлен из локального репозитория)
Клиентские инструменты psql (входит в состав сервера)
Схема stg (создаётся в базе данных dlh_db)
Таблица stg.table_1 – соответствует структуре исходной таблицы MS SQL
Расширения Не используются (пакет postgresql17-contrib отсутствует в репозитории, но он и не нужен)

Цель шага¶

Создать слой STG – временное хранилище для сырых данных, загружаемых из MS SQL Server. Таблица будет:

  • Полностью перезаписываться при каждой загрузке (через TRUNCATE + INSERT).
  • Не иметь индексов и ограничений для максимальной скорости записи.
  • Хранить данные только за последние 30 суток (ограничение будет реализовано на этапе ETL-скрипта, шаг 1.5).
  • Использовать схему stg для логической изоляции.

Предварительные условия¶

  • Виртуальная машина создана и настроена (шаг 0.2).
  • РЕД ОС установлена, сеть настроена (шаг 0.3).
  • Диск /dev/sdb размечен с помощью gdisk, отформатирован и смонтирован в /pgdata (шаг 0.2).
  • PostgreSQL 17 Server установлен, инициализирован и запущен (шаг 1.1). Служба работает, пароль для пользователя postgres задан.

Пошаговое выполнение¶

Шаг 1.2.1: Подключение к PostgreSQL¶

Войдите на сервер под пользователем root или через sudo, затем переключитесь на пользователя postgres:

sudo -i -u postgres

Выполните проверку, что кластер доступен:

psql -c "SELECT version();"

Ожидаемый вывод: PostgreSQL 17.x on x86_64-redhat-linux-gnu...


Шаг 1.2.2: Создание базы данных dlh_db¶

Если база данных ещё не создана, выполните:

CREATE DATABASE dlh_db
  OWNER postgres
  ENCODING 'UTF8'
  LC_COLLATE 'ru_RU.UTF-8'
  LC_CTYPE 'ru_RU.UTF-8'
  TEMPLATE template0;

Примечание: База будет физически размещена в каталоге PGDATA (мы настроили его на /pgdata на шаге 1.1). Это гарантирует, что все данные хранятся на выделенном диске.


Шаг 1.2.3: Создание пользователя для ETL-процессов¶

Для безопасности создадим отдельного пользователя с ограниченными правами (без права создавать объекты):

CREATE USER dlh_user WITH ENCRYPTED PASSWORD 'your_strong_password';

Важно: замените 'your_strong_password' на сложный пароль и сохраните его – он потребуется в скрипте загрузки (шаг 1.5).


Шаг 1.2.4: Создание схемы stg¶

Подключитесь к базе dlh_db и создайте схему:

\c dlh_db
CREATE SCHEMA IF NOT EXISTS stg AUTHORIZATION postgres;

Шаг 1.2.5: Создание таблицы stg.table_1¶

Структура таблицы полностью соответствует исходной таблице table_1 из MS SQL Server: | Колонка | Тип | Описание | |—|—|—| | id | VARCHAR(16) | 16-значный уникальный идентификатор (не NULL) | | dt | TIMESTAMP | дата и время создания записи в источнике (не NULL) | | json_data | JSONB | неструктурированные данные в формате JSON | | col4 | TEXT | дополнительное поле | | col5 | TEXT | дополнительное поле |

Для максимальной скорости вставки таблица создаётся без индексов, без первичного ключа и без ограничений (кроме NOT NULL для обязательных полей).

CREATE TABLE stg.table_1 (
    id          VARCHAR(16)   NOT NULL,
    dt          TIMESTAMP     NOT NULL,
    json_data   JSONB,
    col4        TEXT,
    col5        TEXT
);

Важно: тип JSONB поддерживается «из коробки» в PostgreSQL и не требует установки дополнительных расширений. Это особенно важно, так как пакет postgresql17-contrib отсутствует в локальном репозитории.


Шаг 1.2.6: Настройка прав доступа¶

Предоставьте пользователю dlh_user минимально необходимые права для загрузки данных:

-- Доступ к схеме
GRANT USAGE ON SCHEMA stg TO dlh_user;

-- Права на таблицу (вставка, очистка, чтение)
GRANT INSERT, TRUNCATE, SELECT ON stg.table_1 TO dlh_user;

Лучшая практика: Не давайте права на изменение структуры таблицы (ALTER, DROP) – это снижает риск случайных изменений.


Шаг 1.2.7: Дополнительные оптимизации для ускорения загрузки¶

Учитывая, что STG перезаписывается при каждой загрузке, рекомендуется:

  • Отключить автовакуум для таблицы (чтобы избежать лишней нагрузки):

    ALTER TABLE stg.table_1 SET (autovacuum_enabled = false);
    

  • Перевести таблицу в режим UNLOGGED – это ускоряет вставку за счёт отказа от записи в WAL. Поскольку данные в STG временные (последние 30 суток), потеря при сбое допустима. На этапе MVP (без WAL-архивации) это безопасно:

    ALTER TABLE stg.table_1 SET UNLOGGED;
    

Внимание: Если в будущем вы включите репликацию или WAL-архивацию, UNLOGGED-таблицы не будут реплицироваться. На этапе MVP это приемлемо.


Шаг 1.2.8: Проверка создания таблицы¶

Подключитесь к базе и выполните:

\c dlh_db
\d stg.table_1

Вывод должен показать структуру таблицы без индексов и ограничений.


Шаг 1.2.9: Тестовая вставка¶

Вставьте одну строку для проверки работоспособности:

INSERT INTO stg.table_1 (id, dt, json_data, col4, col5)
VALUES ('TEST000000000001', NOW(), '{"test": true}', 'sample', 'data');

Выполните выборку:

SELECT * FROM stg.table_1;

Убедитесь, что данные возвращаются корректно.


Полный SQL-скрипт для выполнения шага 1.2¶

Сохраните содержимое в файл create_staging.sql и выполните его через psql -U postgres -f create_staging.sql (или скопируйте и выполните в интерактивном режиме).

-- Создание базы данных (если не существует)
CREATE DATABASE dlh_db
  OWNER postgres
  ENCODING 'UTF8'
  LC_COLLATE 'ru_RU.UTF-8'
  LC_CTYPE 'ru_RU.UTF-8'
  TEMPLATE template0;

-- Подключение к базе
\c dlh_db

-- Создание пользователя для ETL
CREATE USER dlh_user WITH ENCRYPTED PASSWORD 'your_strong_password';

-- Создание схемы
CREATE SCHEMA IF NOT EXISTS stg AUTHORIZATION postgres;

-- Создание таблицы STG
CREATE TABLE stg.table_1 (
    id          VARCHAR(16)   NOT NULL,
    dt          TIMESTAMP     NOT NULL,
    json_data   JSONB,
    col4        TEXT,
    col5        TEXT
);

-- Отключение автовакуума (ускоряет загрузку)
ALTER TABLE stg.table_1 SET (autovacuum_enabled = false);

-- (Опционально) Сделать таблицу UNLOGGED для повышения скорости вставки
ALTER TABLE stg.table_1 SET UNLOGGED;

-- Настройка прав доступа
GRANT USAGE ON SCHEMA stg TO dlh_user;
GRANT INSERT, TRUNCATE, SELECT ON stg.table_1 TO dlh_user;

-- Проверка
INSERT INTO stg.table_1 (id, dt, json_data, col4, col5)
VALUES ('TEST000000000001', NOW(), '{"test": true}', 'sample', 'data');

SELECT * FROM stg.table_1;

Учёт ограничений проекта¶

Ограничение Как учтено
Нет доступа в интернет Все действия выполняются локально, без обращений к внешним репозиториям.
Отсутствует postgresql17-contrib Не используются расширения (тип JSONB доступен по умолчанию).
Диск /dev/sdb уже размечен Мы используем существующую разметку и монтирование, не изменяя её.
PostgreSQL 17 уже установлен Мы пропускаем этап установки и сразу переходим к настройке базы.
Использование gdisk не требуется На этом шаге мы не работаем с дисками.

Лучшие мировые практики, применённые в шаге¶

  • Разделение прав – создан отдельный пользователь для ETL, что повышает безопасность.
  • Отказ от индексов и ограничений – таблица STG служит только для временного хранения, индексы замедляют вставку.
  • Использование JSONB – эффективное хранение и возможность индексирования в будущем (если потребуется).
  • Настройка параметров хранения – отключение автовакуума и режим UNLOGGED снижают накладные расходы на этапе MVP.
  • Проверка работоспособности – тестовая вставка гарантирует, что таблица создана корректно.

Связь с последующими шагами¶

  • Шаг 1.3 – будет создан слой RAW с партиционированием по кварталам, используя ту же структуру, но с табличным пространством.
  • Шаг 1.4 – создание табличных пространств и подготовка LVM-томов для партиций (на этом шаге мы будем использовать gdisk для выделения томов, но диск уже размечен, поэтому мы создадим табличные пространства внутри /pgdata).
  • Шаг 1.5 – ETL-скрипт будет загружать данные в STG, затем переносить их в RAW.

Итоговый чек-лист шага 1.2¶

Действие Статус
База данных dlh_db создана ☐
Пользователь dlh_user создан ☐
Схема stg создана ☐
Таблица stg.table_1 создана без индексов и ограничений ☐
Автовакуум отключён ☐
(Опционально) Таблица переведена в UNLOGGED ☐
Права доступа настроены ☐
Проверочная вставка выполнена успешно ☐

Если на каком-либо шаге возникнут трудности (например, с правами доступа или паролями), обратитесь к логам PostgreSQL (/pgdata/17/data/log/ или через journalctl -u postgresql-17).


Подробная инструкция по шагу 1.3: Создание слоя RAW с партиционированием по кварталам¶

Технологический стек шага¶

Компонент Версия / Технология
Операционная система РЕД ОС 8 (серверная редакция)
СУБД PostgreSQL 17 (установлен из локального репозитория)
Клиентские инструменты psql (входит в состав сервера)
Управление томами LVM2 (lvcreate, vgcreate, lvdisplay)
Файловая система XFS
Схема raw
Таблица-родитель raw.table_1 с декларативным партиционированием по RANGE (dt)
Партиции Квартальные (Q2, Q3, Q4)
Табличные пространства ts_raw_q2_2026 … ts_raw_q4_2026

Цель шага¶

Создать слой RAW DATA LAKE – долгосрочное, неизменяемое хранилище сырых данных. Данные будут партиционироваться по кварталам (3 месяца) на основе поля dt (время создания записи в источнике). Каждая партиция физически размещается в отдельном табличном пространстве, которое соответствует отдельному LVM-тому (или каталогу на основном томе, если отдельный том не создан). В рамках данного шага мы:

  • Убедимся, что для каждого квартала существует отдельный том.
  • Создадим табличные пространства для всех кварталов.
  • Создадим схему raw и партиционированную таблицу-родитель.
  • Создадим партиции для Q2–Q4 2026 года.
  • Настроим права доступа для пользователя dlh_user.

Примечание: на предыдущем шаге (0.2) были созданы LVM-тома для Q2, Q3, Q4 в группе томов vg_data.


Предварительные условия¶

  • Диск /dev/sdb разделён на разделы, создана группа томов vg_data и логические тома для Q2, Q3, Q4 (см. шаг 0.2).
  • Каталоги /mnt/raw_q2_2026, /mnt/raw_q3_2026, /mnt/raw_q4_2026 созданы и смонтированы.
  • PostgreSQL 17 установлен, служба запущена, база dlh_db и слой stg созданы (шаги 1.1–1.2).
  • Пользователь dlh_user существует и имеет права на схему stg.

Пошаговое выполнение¶

Шаг 1.3.1: Создание слоя RAW¶

Для автоматизации создания слоя RAW «с нуля» рекомендуется использовать файлы create_raw.sh. Также рекомендуется использовать файл drop_raw.sh для автоматизации удаления слоя RAW в случае, например, потребности его пересоздания. Это полезно при первичном создании слоя и отладки его функционирования.

Шаг 1.3.2: Тестирование вставки (проверка маршрутизации)¶

Вставьте тестовую запись с датой, попадающей в Q2, и убедитесь, что она попала в нужную партицию:

INSERT INTO raw.table_1 (id, dt, json_data, col4, col5)
VALUES ('TEST_Q2_001', '2026-02-15 10:00:00', '{"test": "q2"}', 'test', 'data');

SELECT tableoid::regclass AS partition, * FROM raw.table_1 WHERE id = 'TEST_Q2_001';

Ожидаемый результат: partition = raw.table_1_q2_2026.


Учёт ограничений проекта¶

Ограничение Как учтено
Нет доступа в интернет Все действия выполняются локально, без внешних обращений.
Отсутствует postgresql17-contrib Расширения не используются; партиционирование и табличные пространства встроены в ядро PostgreSQL.
Использование gdisk На этом шаге работа с разделами диска не требуется (мы работаем на уровне LVM).

Лучшие мировые практики, применённые в шаге¶

  • Партиционирование по бизнес-времени – естественный способ управления большими объёмами данных, ускоряет запросы и упрощает архивацию.
  • Размещение партиций на отдельных томах – позволяет независимо управлять каждым томом (архивировать, отключать, переносить).
  • Отказ от индексов на этапе MVP – снижает нагрузку при загрузке; индексы можно добавить позже при необходимости.
  • Использование табличных пространств – обеспечивает гибкость и соответствие требованиям ТЗ.
  • Комментирование объектов – упрощает поддержку и трассировку данных.

Связь с последующими шагами¶

  • Шаг 1.4 (подготовка томов и табличных пространств) – в нашей реализации уже выполнен на шаге 0.2 и дополнен сейчас. При добавлении новых кварталов потребуется повторить создание томов и табличных пространств.
  • Шаг 1.5 (ETL-скрипт) – будет загружать данные из MS SQL в stg.table_1, затем выполнять проверку целостности и вставлять данные в raw.table_1 (автоматическое распределение по партициям).
  • Шаг 2.2 (переход на Airflow) – DAG будет использовать те же таблицы и партиции.

Итоговый чек-лист шага 1.3¶

Действие Статус
Запись в /etc/fstab добавлена для всех томов ☐
Каталоги /mnt/raw_q* принадлежат пользователю postgres ☐
Табличные пространства ts_raw_q* созданы ☐
Схема raw создана ☐
Таблица-родитель raw.table_1 создана с партиционированием по dt ☐
Созданы партиции для Q2–Q4 2026 с привязкой к табличным пространствам ☐
Права доступа для dlh_user настроены ☐
Добавлены комментарии (опционально) ☐
Тестовая вставка успешно маршрутизирована в нужную партицию ☐

Если на каком-либо шаге возникнут проблемы (например, с правами доступа к каталогам или недостатком места в группе томов), проверьте логи и состояние LVM.


Подробная инструкция по шагу 1.4: Управление жизненным циклом партиций (архивация и восстановление)¶

Технологический стек шага¶

Компонент Версия / Технология
Операционная система РЕД ОС 8 (серверная редакция)
СУБД PostgreSQL 17
Управление томами LVM2 (lvcreate, lvremove, mount, umount)
Скриптовый язык Bash
Инструменты psql, tar, gzip, logger

Цель шага¶

Разработать скрипты для полного удаления квартальной партиции (включая объект БД, табличное пространство, LVM-том и точку монтирования) и последующего восстановления из архива. Это необходимо для:

  • Освобождения места на диске при переходе к новым кварталам.
  • Возможности хранения архивов на съёмных носителях.
  • Обратного монтирования исторических данных для аналитики.
  • Создание параметризованного скрипта для создания новой квартальной партиции (для использования на следующих этапах, когда потребуется расширение).

Ключевое требование: после выполнения архивации с флагом --remove-lv в системе не должно оставаться следов партиции, которые могли бы вызвать ошибки при запросах к raw.table_1.


Предварительные условия¶

  • Шаг 1.3 выполнен: созданы табличные пространства ts_raw_q2_2026, ts_raw_q3_2026, ts_raw_q4_2026 и соответствующие партиции.
  • LVM-тома lv_raw_q2_2026, lv_raw_q3_2026, lv_raw_q4_2026 смонтированы в /mnt/raw_q2_2026, /mnt/raw_q3_2026, /mnt/raw_q4_2026.
  • PostgreSQL работает, база dlh_db и слой raw существуют.
  • Каталоги /backup и /var/log/dlh смонтированы.

Пошаговое выполнение¶

Шаг 1.4.1: Создание скрипта архивации (полное удаление)¶

Создадим скрипт /opt/dlh/scripts/archive_raw_partition.sh, который выполняет:

  1. Отключение партиции от родительской таблицы (DETACH PARTITION).
  2. Удаление самой партиции (DROP TABLE), чтобы она перестала существовать как объект.
  3. Удаление табличного пространства (DROP TABLESPACE).
  4. Отмонтирование тома.
  5. Создание сжатого архива содержимого тома (если том ещё существует).
  6. Удаление логического тома (если передан флаг --remove-lv).
  7. Удаление записи из /etc/fstab.
  8. Удаление точки монтирования (каталога).

Это гарантирует, что не остаётся ссылок на несуществующие файлы, и запрос SELECT * FROM raw.table_1 не вызовет ошибок.

#!/bin/bash
# archive_raw_partition.sh - полное удаление партиции и архивация тома
# Использование: ./archive_raw_partition.sh <quarter_name> [--remove-lv]
# Пример: ./archive_raw_partition.sh q2_2026 --remove-lv

set -e

QUARTER=$1
REMOVE_LV=$2
PARTITION_NAME="raw.table_1_${QUARTER}"
TABLESPACE_NAME="ts_raw_${QUARTER}"
MOUNT_POINT="/mnt/raw_${QUARTER}"
LV_NAME="lv_raw_${QUARTER}"
VG_NAME="vg_data"
ARCHIVE_DIR="/backup/raw_archives"
BACKUP_FILE="${ARCHIVE_DIR}/raw_${QUARTER}_$(date +%Y%m%d_%H%M%S).tar.gz"
LOG_FILE="/var/log/dlh/scripts/archive.log"

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

if [ -z "$QUARTER" ]; then
    log "Ошибка: укажите имя квартала (например, q2_2026)"
    exit 1
fi

log "Начало архивации квартала $QUARTER"

# 1. Отключение партиции от родительской таблицы
log "Отключение партиции $PARTITION_NAME..."
sudo -u postgres psql -d dlh_db -c "ALTER TABLE raw.table_1 DETACH PARTITION $PARTITION_NAME;" || log "Партиция уже отключена или не существует."

# 2. Удаление партиции как объекта
log "Удаление партиции $PARTITION_NAME..."
sudo -u postgres psql -d dlh_db -c "DROP TABLE IF EXISTS $PARTITION_NAME;" || log "Партиция уже удалена."

# 3. Удаление табличного пространства
log "Удаление табличного пространства $TABLESPACE_NAME..."
sudo -u postgres psql -d dlh_db -c "DROP TABLESPACE IF EXISTS $TABLESPACE_NAME;" || log "Табличное пространство уже удалено."

# 4. Отмонтирование тома
if mount | grep -q "$MOUNT_POINT"; then
    log "Отмонтирование $MOUNT_POINT..."
    sudo umount "$MOUNT_POINT" || { log "Ошибка отмонтирования"; exit 1; }
else
    log "Том уже отмонтирован."
fi

# 5. Создание архива (если том существует и есть данные)
if lvdisplay "/dev/$VG_NAME/$LV_NAME" >/dev/null 2>&1; then
    TEMP_MNT="/mnt/temp_${QUARTER}"
    sudo mkdir -p "$TEMP_MNT"
    sudo mount -o ro "/dev/$VG_NAME/$LV_NAME" "$TEMP_MNT" || { log "Ошибка монтирования для архивации"; exit 1; }
    sudo tar -czf "$BACKUP_FILE" -C "$TEMP_MNT" .
    sudo umount "$TEMP_MNT"
    sudo rmdir "$TEMP_MNT"
    log "Архив создан: $BACKUP_FILE"
else
    log "Логический том /dev/$VG_NAME/$LV_NAME не найден. Пропускаем архивацию."
fi

# 6. Удаление логического тома (если указан флаг --remove-lv)
if [ "$REMOVE_LV" == "--remove-lv" ]; then
    if lvdisplay "/dev/$VG_NAME/$LV_NAME" >/dev/null 2>&1; then
        log "Удаление логического тома /dev/$VG_NAME/$LV_NAME..."
        sudo lvremove -f "/dev/$VG_NAME/$LV_NAME" || { log "Ошибка удаления тома"; exit 1; }
    else
        log "Логический том уже удалён."
    fi
    # Удаление записи из /etc/fstab
    sudo sed -i "\|$MOUNT_POINT|d" /etc/fstab
    # Удаление точки монтирования
    if [ -d "$MOUNT_POINT" ]; then
        sudo rmdir "$MOUNT_POINT" || log "Не удалось удалить каталог $MOUNT_POINT (возможно, не пуст)."
    fi
fi

log "Архивация квартала $QUARTER завершена."

Сделайте скрипт исполняемым:

sudo chmod +x /opt/dlh/scripts/archive_raw_partition.sh

Шаг 1.4.2: Создание скрипта восстановления¶

Скрипт /opt/dlh/scripts/restore_raw_partition.sh выполняет:

  1. Создание LVM-тома (если отсутствует) указанного размера (например, 800 ГБ).
  2. Форматирование в XFS.
  3. Монтирование тома в каталог /mnt/raw_<квартал>.
  4. Распаковка архива в этот каталог.
  5. Установка прав для пользователя postgres.
  6. Создание табличного пространства.
  7. Создание партиции как части raw.table_1 с нужным диапазоном дат.
#!/bin/bash
# restore_raw_partition.sh - восстановление партиции из архива
# Использование: ./restore_raw_partition.sh <quarter_name> <archive_file>
# Пример: ./restore_raw_partition.sh q2_2026 /backup/raw_archives/raw_q2_2026_20250714_120000.tar.gz

set -e

QUARTER=$1
ARCHIVE_FILE=$2
if [ -z "$QUARTER" ] || [ -z "$ARCHIVE_FILE" ]; then
    echo "Ошибка: укажите имя квартала и путь к архиву"
    exit 1
fi

PARTITION_NAME="raw.table_1_${QUARTER}"
TABLESPACE_NAME="ts_raw_${QUARTER}"
MOUNT_POINT="/mnt/raw_${QUARTER}"
LV_NAME="lv_raw_${QUARTER}"
VG_NAME="vg_data"
LV_SIZE="800G"  # фиксированный размер, можно изменить
LOG_FILE="/var/log/dlh/scripts/restore.log"

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

if [ ! -f "$ARCHIVE_FILE" ]; then
    log "Ошибка: архив $ARCHIVE_FILE не найден"
    exit 1
fi

log "Начало восстановления квартала $QUARTER из $ARCHIVE_FILE"

# 1. Создание логического тома, если не существует
if lvdisplay "/dev/$VG_NAME/$LV_NAME" >/dev/null 2>&1; then
    log "Том /dev/$VG_NAME/$LV_NAME уже существует. Пропускаем создание."
else
    log "Создание логического тома $LV_NAME размером $LV_SIZE..."
    sudo lvcreate -L "$LV_SIZE" -n "$LV_NAME" "$VG_NAME" || { log "Ошибка создания тома"; exit 1; }
    sudo mkfs.xfs -f "/dev/$VG_NAME/$LV_NAME"
fi

# 2. Монтирование тома
sudo mkdir -p "$MOUNT_POINT"
if ! mount | grep -q "$MOUNT_POINT"; then
    sudo mount "/dev/$VG_NAME/$LV_NAME" "$MOUNT_POINT" || { log "Ошибка монтирования"; exit 1; }
    echo "/dev/$VG_NAME/$LV_NAME $MOUNT_POINT xfs defaults 0 0" | sudo tee -a /etc/fstab
fi

# 3. Распаковка архива
log "Распаковка архива $ARCHIVE_FILE в $MOUNT_POINT..."
sudo tar -xzf "$ARCHIVE_FILE" -C "$MOUNT_POINT" || { log "Ошибка распаковки"; exit 1; }

# 4. Установка прав
sudo chown -R postgres:postgres "$MOUNT_POINT"
sudo chmod 700 "$MOUNT_POINT"

# 5. Создание табличного пространства
sudo -u postgres psql -d dlh_db -c "CREATE TABLESPACE $TABLESPACE_NAME OWNER postgres LOCATION '$MOUNT_POINT';" || log "Табличное пространство уже существует."

# 6. Определение дат начала и конца квартала
YEAR=$(echo "$QUARTER" | grep -oP '(?<=q[1-4]_)\d{4}')
Q=$(echo "$QUARTER" | grep -oP 'q[1-4]' | tr -d 'q')
case $Q in
    1) START_DATE="${YEAR}-01-01"; END_DATE="${YEAR}-04-01" ;;
    2) START_DATE="${YEAR}-04-01"; END_DATE="${YEAR}-07-01" ;;
    3) START_DATE="${YEAR}-07-01"; END_DATE="${YEAR}-10-01" ;;
    4) START_DATE="${YEAR}-10-01"; END_DATE="$((YEAR+1))-01-01" ;;
    *) log "Ошибка: неверный квартал (поддерживаются 1-4)"; exit 1 ;;
esac

# 7. Создание партиции (заново)
log "Создание партиции $PARTITION_NAME для периода $START_DATE - $END_DATE"
sudo -u postgres psql -d dlh_db -c "
    CREATE TABLE $PARTITION_NAME PARTITION OF raw.table_1
    FOR VALUES FROM ('$START_DATE') TO ('$END_DATE')
    TABLESPACE $TABLESPACE_NAME;
" || log "Ошибка создания партиции (возможно, уже существует)."

log "Восстановление квартала $QUARTER завершено."

Сделайте скрипт исполняемым:

sudo chmod +x /opt/dlh/scripts/restore_raw_partition.sh

Шаг 1.4.3: Создание скрипта для автоматического создания новой партиции (для будущего использования)¶

Создадим скрипт /opt/dlh/scripts/create_new_partition.sh, который будет использоваться на этапе промышленного внедрения для добавления новых кварталов. В рамках MVP он не будет запускаться автоматически, но должен быть готов.

#!/bin/bash
# create_new_partition.sh - создание новой квартальной партиции (для использования в будущем)
# Использование: ./create_new_partition.sh <year> <quarter>
# Пример: ./create_new_partition.sh 2027 1


# 1. Остановка выполнения данного файла при любой ошибке
set -e


# 2. Базовые проверки

# 2.1. Проверка прав root
if [[ $EUID -ne 0 ]]; then
    log "Ошибка: скрипт должен быть запущен от root (sudo)."
    exit 1
fi

# 2.2. Проверка наличия параметров 
# года и квартала при запуске файла этого скрипта
if [ -z $1 ] || [ -z $2 ]; then
#if [ -z "$YEAR" ] || [ -z "$QUARTER" ]; then
    log "Ошибка: укажите год и квартал (например, 2027 1)"
    exit 1
fi


# 3. Получение параметров запуска и объявление 
# глобальных переменных этого скрипта

# 3.1. Базовые переменные с параметрами запуска данного скрипта
YEAR=$1
QUARTER=$2
# Преобразование квартала в нижний регистр и удаление пробелов
#Q_LOWER=$(echo "$QUARTER" | tr '[:upper:]' '[:lower:]' | tr -d ' ') # не требуется, если квартал обозначен только числом

# 3.2. Переменные с полными или частичными названиями и адресами
TABLE_NAME="table_1"
QUARTER_NAME="q${QUARTER}_${YEAR}"  # например, q1_2027
LV_NAME="lv_raw_${QUARTER_NAME}"
VG_NAME="vg_data"
LV_PATH="/dev/${VG_NAME}/${LV_NAME}"
MOUNT_POINT="/mnt/raw_${QUARTER_NAME}"
TABLESPACE_NAME="ts_raw_${QUARTER_NAME}"
PARTITION_NAME="raw.${TABLE_NAME}_${QUARTER_NAME}"
LOG_FILE="/var/log/dlh/scripts/create_partition.log"

# 3.3. Определение дат начала и конца квартала
log "Определение дат начала и конца квартала."
case $QUARTER in
    #Q1) START_DATE="${YEAR}-01-01"; END_DATE="${YEAR}-04-01" ;;
    #Q2) START_DATE="${YEAR}-04-01"; END_DATE="${YEAR}-07-01" ;;
    #Q3) START_DATE="${YEAR}-07-01"; END_DATE="${YEAR}-10-01" ;;
    #Q4) START_DATE="${YEAR}-10-01"; END_DATE="$((YEAR+1))-01-01" ;;
    1) START_DATE="${YEAR}-01-01"; END_DATE="${YEAR}-04-01" ;;
    2) START_DATE="${YEAR}-04-01"; END_DATE="${YEAR}-07-01" ;;
    3) START_DATE="${YEAR}-07-01"; END_DATE="${YEAR}-10-01" ;;
    4) START_DATE="${YEAR}-10-01"; END_DATE="$((YEAR+1))-01-01" ;;
    #*) log "Ошибка: неверный квартал. Используйте Q1, Q2, Q3, Q4"; exit 1 
    *) log "Ошибка: неверный квартал. Используйте 1, 2, 3, 4"; exit 1 ;;
esac
log "QUARTER_NAME=${QUARTER_NAME}, START_DATE=${START_DATE}, END_DATE=${END_DATE}"


# 4. Создание исходных функций

# 4.1. Функция логирования действий данного скрипта
log() {
    echo "$(data '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

# 4.2. Универсальная функция запуска SQL-скрипта
run_sql() {
    # Универсальная функция запуска SQL-скрипта
    # Использование: run_sql "CREATE TABLE %I (id serial)" table="may_table"
    
    local sql_command="$1"
    shift
    
    #  --- РЕАЛИЗАЦИЯ ЧЕРЕЗ СБОРКУ СТРОКИ В BASH (Самый надежный метод) ---
    
    for var in "$@"; do
    	local key="${var%%=*}"
    	local value="${var#*=}"
    	
    	# Простая замена %I на значение, обернутое  в двойные кавычки (стандарт SQL для идентификаторов)
    	# Это эмулирует поведение %I.
    	# Важно: Если в имени есть вдойные кавычки, нужна дополнительная обработка.
    	# Для базового скрипта этого достаточно.
    	#sql_command=$(echo "$sql_command" | sed "s|%I|\"$value\"|")
    	sql_command=$(echo "$sql_command" | sed "s|%I|$value|")
    	
    	# Если нужно подставить текст (не идентификатор), нужна другая метка, например %L
    	# В данном примере считаем, что все %I - это имена объектов.
    done
    
    log "Выполнено SQL: $sql_command"
    
    # Запуск через sudo -u postgres (локальный сокет, без пароля, безопасно для РЕД ОС)
    # -v ON_ERROR_STOP=1 гарантирует, что скрипт упадет при любой ошибке БД
    if sudo -u postgres psql -d dlh_db -v ON_ERROR_STOP=1 -c "$sql_command"; then
    	log "Успех: $sql_command"
    	return 0
    else
    	log "Ошибка выполнения SQL: $sql_command"
    	return 1
    fi
}


log "Начало создания партиции $QUARTER_NAME для периода $START_DATE - $END_DATE"


# 5. Остановка PostgreSQL (ы РЕД ОС обычно systemd)
log "Остановка службы ${POSTGRESQL_SERVICE_NAME}"
if systemctl stop "${POSTGRESQL_SERVICE_NAME}"; then
    log "Служба ${POSTGRESQL_SERVICE_NAME} успешно остановлена"
else
    log "Не удалось остановить службу ${POSTGRESQL_SERVICE_NAME}"
fi


# 6-12. Создание логических томов и точек монтирования
if true; then
    # 6. Создание логического тома для будущей партиции
    log "Создание логического тома ${LV_NAME} в группе ${VG_NAME} ( ${LV_PATH} ) для будущей партиции"
    if ! lvdisplay "${LV_PATH}" >/dev/null 2>&1; then
    	if lvcreate -L 800G -n "${LV_NAME}" "${VG_NAME}"; then
    		log "Создание логического тома ${LV_PATH} успешно завершено"
    	else
    		log "Логический том ${LV_PATH} не создан из-за ошибки"
    		exit 1
    	fi
    else
    	log "Логический том ${LV_PATH} уже существует"
    fi

    # 7. Форматирование логического тома
    log "Форматирование логического тома ${LV_PATH}"
    # Флаг -f (force) автоматически подтверждает 
    # для mkfs.xfs форматирование, даже, если данные
    if mkfs.xfs -f "${LV_PATH}"; then
    	log "Форматирование логического тома ${LV_PATH} успешно завершено"
    else
    	log "Форматирование логического тома ${LV_PATH} завершено c ошибкой"
    	exit 1
    fi

    # 8. Создание каталога в точке монтирования
    MOUNT_POINT="/mnt/raw_q${i}_${YEAR}"
    log "Создание каталога в точке монтирования ${MOUNT_POINT}"
    if ! [-d "${MOUNT_POINT}"]; then
    	if mkdir -p "${MOUNT_POINT}"; then
    		log "Создан каталог ${MOUNT_POINT}"
    	else
    		log "Не удалось создать каталог ${MOUNT_POINT}"
    		exit 1
    	fi
    else
    	ACTUAL_PATH=$(findmnt -no SOURCE -T "${MOUNT_POINT}" 2>/dev/null | tr -d '[:space:]')
    	if ["$LV_PATH" = "$ACTUAL_PATH"]; then
    		log "Каталог ${MOUNT_POINT} уже существует и смонтирован правильно с томом ${ACTUAL_PATH}"
    	else
    		log "Каталог ${MOUNT_POINT} уже существует, но ошибочно смонтирован с томом ${ACTUAL_PATH}"
    		exit 1
    	fi
    fi
    	
    # 9. Монтирование каталога с томом
    log "Монтирование каталога ${MOUNT_POINT} с томом ${LV_PATH}"
    if ! mountpoint -q "${MOUNT_POINT}"; then
    	if mount "${LV_PATH} ${MOUNT_POINT}"; then
    		log "Монтирование каталога ${MOUNT_POINT} с томом ${LV_PATH} успешно завершено"
    	else
    		log "Ошибка при монтировании каталога ${MOUNT_POINT} с томом ${LV_PATH}"
    	fi
    else
    	log "Каталог ${MOUNT_POINT} был смонтирован ранее"
    fi

    # 10. Добавление записей в `/etc/fstab` для автоматического монтирования
    log "Добавление записей в /etc/fstab для автоматического монтирования"
    if echo "${LV_PATH} ${MOUNT_POINT} xfs defaults 0 0" | tee -a /etc/fstab; then
    	log "Запись успешно добавлена в /etc/fstab"
    else
    	log "Ошибка добавления записи в /etc/fstab"
    	exit 1
    fi

    # 11. Назначение прав на каталог для пользователя `postgres`
    log "Назначение прав на каталог ${MOUNT_POINT} для пользователя postgres"
    if chown postgres:postgres "${MOUNT_POINT}"; then
    	log "Права на каталог ${MOUNT_POINT} для пользователя postgres успешно назначены"
    else
    	log "Ошибка при назначении прав на каталог ${MOUNT_POINT} для пользователя postgres"
    fi
    
    # 12. Назначение прав для всех пользователей на каталог
    log "Назначение прав на каталог ${MOUNT_POINT} для всех пользователей"
    if chmod 700 "${MOUNT_POINT}"; then
    	log "Права на каталог ${MOUNT_POINT} для всех пользователей успешно назначены"
    else
    	log "Ошибка при назначении прав на каталог ${MOUNT_POINT} для всех пользователей"
    fi

    # Проверка всех смонтированных томов:
    df -h /mnt/raw_q*

    # Проверка существующих томов
    lvdisplay
fi


# 13. Запуск PostgreSQL
log "Запуск службы ${POSTGRESQL_SERVICE_NAME}"
if systemctl start "${POSTGRESQL_SERVICE_NAME}"; then
    log "Служба ${POSTGRESQL_SERVICE_NAME} успешно запущена"
else
    log "Не удалось запустить службу ${POSTGRESQL_SERVICE_NAME}"
fi


# 14. Создание табличных пространств для каждого квартала
# true - если надо выполнить и false - если не надо
if true; then
    log "Создание табличного пространства ${TABLESPACE_NAME}"
    run_sql "CREATE TABLESPACE %I OWNER postgres LOCATION '%I';" space="$TABLESPACE_NAME" point="$MOUNT_POINT"
    
    log "Проверка списка табличных пространств"
    run_sql "\db+;"
fi


# 15. Создание партиции
if true; then
    log "Создание квартальной партиции ${PARTITION_NAME}"
    run_sql "CREATE TABLE IF NOT EXISTS %I PARTITION OF raw.%I
    FOR VALUES FROM ('%I') TO ('%I') TABLESPACE %I;" pdrtition="${PARTITION_NAME}" table="${TABLE_NAME}" start="${START_DATE}" end="${END_DATE}" space="${TABLESPACE_NAME}"


log "Партиция $PARTITION_NAME создана для периода $START_DATE - $END_DATE"

Сделайте скрипт исполняемым:

sudo chmod +x /opt/dlh/scripts/create_new_partition.sh

Шаг 1.4.4: Тестирование полного цикла¶

  1. Архивация с удалением тома (например, для Q2 2026):

    sudo /opt/dlh/scripts/archive_raw_partition.sh q2_2026 --remove-lv
    

    Проверьте:

    • Партиция raw.table_1_q2_2026 отсутствует (запрос \d raw.table_1_q2_2026 в psql выдаст ошибку).
    • Табличное пространство ts_raw_q2_2026 удалено.
    • Том /dev/vg_data/lv_raw_q2_2026 удалён (команда lvdisplay не показывает его).
    • Каталог /mnt/raw_q2_2026 удалён.
    • Запись из /etc/fstab удалена.
    • Архив создан в /backup/raw_archives/.
  2. Запрос к raw.table_1 не должен вызывать ошибок, так как партиция отключена и удалена:

    sudo -u postgres psql -d dlh_db -c "SELECT * FROM raw.table_1 LIMIT 1;"
    
  3. Восстановление:

    sudo /opt/dlh/scripts/restore_raw_partition.sh q2_2026 /backup/raw_archives/raw_q2_2026_*.tar.gz
    

    Проверьте:

    • Том создан, смонтирован, данные распакованы.
    • Табличное пространство создано.
    • Партиция создана и присоединена.
    • Запрос SELECT * FROM raw.table_1 WHERE dt BETWEEN '2026-04-01' AND '2026-06-30' возвращает данные.

Шаг 1.4.5: Настройка логов и ротации¶

Для всех скриптов логи пишутся в /var/log/dlh/scripts/. Настройте ротацию логов, чтобы избежать переполнения диска:

sudo nano /etc/logrotate.d/dlh-scripts

Добавьте:

/var/log/dlh/scripts/*.log {
    daily
    rotate 30
    compress
    missingok
    notifempty
    create 0640 root root
}

Учёт ограничений проекта¶

Ограничение Как учтено
Нет доступа в интернет Все команды и скрипты используют только локальные ресурсы.
Установка только из локального репозитория Не требуется дополнительное ПО.
Использование gdisk На этом шаге работа с разделами диска не требуется (используется LVM).
Ограничение на 3 тома (Q2-Q4 2026) Скрипты рассчитаны на управление этими тремя кварталами. Для добавления новых кварталов в будущем можно использовать те же скрипты, подставив другие имена.
Закрытый контур Скрипты не обращаются к внешним ресурсам.

Лучшие мировые практики¶

  • Полное удаление объектов БД перед удалением табличного пространства и тома – исключает ошибки «не удалось отправить файл».
  • Архивация в режиме read-only – гарантирует целостность архива.
  • Восстановление через создание заново – воссоздаёт все объекты в актуальном состоянии.
  • Логирование – упрощает отладку и аудит.
  • Параметризация скриптов – универсальность для любых кварталов.

Итоговый чек-лист шага 1.4¶

Действие Статус
Скрипт архивации создан и протестирован с флагом --remove-lv ☐
Скрипт восстановления создан и протестирован ☐
После архивации запрос SELECT к raw.table_1 не вызывает ошибок ☐
После восстановления данные доступны и партиция присоединена ☐
Логи скриптов ротируются ☐

Если на каком-либо шаге возникнут проблемы, проверьте логи в /var/log/dlh/scripts/.


Пояснения к коду файла archive_raw_partition.sh¶

Разберем archive_raw_partition.sh построчно: здесь много логики ветвлений, обработки состояний и «стоимости» ошибок (потеря данных, простой системы, некорректное состояние БД). Это скрипт с выраженным порядком операций: сначала БД, потом ФС, потом LVM — и на каждом шаге есть защита от лишних действий и фиксация результата.


#!/bin/bash

Указывает интерпретатор Bash. Без этой строки ОС не поймёт, как исполнять скрипт.

# archive_raw_partition.sh - полное удаление партиции и архивация тома

Комментарий: назначение скрипта. Для поддержки и передачи задачи другим это критично: сразу понятно, что он делает.

# Использование: ./archive_raw_partition.sh <quarter_name> [--remove-lv]
# Пример: ./archive_raw_partition.sh q2_2026 --remove-lv

Инструкция по запуску: обязательный аргумент — имя квартала, необязательный флаг --remove-lv для удаления логического тома после архивации. Это делает интерфейс предсказуемым и удобным для автоматизации.

set -e

Ключевая директива: при любой ошибке (ненулевой код возврата) скрипт немедленно завершается. Это защита от «полуочищенных» состояний: если, например, не удастся отмонтировать том, скрипт не пойдёт дальше и не удалит табличное пространство, оставив БД в несогласованном состоянии. С точки зрения процесса — это гарантия атомарности шагов.

QUARTER=$1
REMOVE_LV=$2
PARTITION_NAME="raw.table_1_${QUARTER}"
TABLESPACE_NAME="ts_raw_${QUARTER}"
MOUNT_POINT="/mnt/raw_${QUARTER}"
LV_NAME="lv_raw_${QUARTER}"
VG_NAME="vg_data"
ARCHIVE_DIR="/backup/raw_archives"
BACKUP_FILE="${ARCHIVE_DIR}/raw_${QUARTER}_$(date +%Y%m%d_%H%M%S).tar.gz"
LOG_FILE="/var/log/dlh/scripts/archive.log"

Инициализация переменных:

  • QUARTER, REMOVE_LV — аргументы;
  • PARTITION_NAME, TABLESPACE_NAME — имена объектов БД;
  • MOUNT_POINT, LV_NAME, VG_NAME — пути и имена для LVM/ФС;
  • ARCHIVE_DIR, BACKUP_FILE — куда писать архив (с временной меткой, чтобы не перезаписывать старые);
  • LOG_FILE — файл логов.

Вынесение в переменные делает скрипт гибким: если схема именования или пути изменятся, правки будут в одном месте.

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

Функция логирования: печатает сообщение с временной меткой в консоль и дописывает в лог. tee -a пишет одновременно в stdout и файл. Это нужно для мониторинга и аудита: ты видишь ход выполнения и имеешь запись для разбора инцидентов.

if [ -z "$QUARTER" ]; then
    log "Ошибка: укажите имя квартала (например, q2_2026)"
    exit 1
fi

Проверка: имя квартала обязательно. Если нет — логируем ошибку и завершаем скрипт с кодом 1 (ошибка). Это защита от случайного запуска без параметров.

log "Начало архивации квартала $QUARTER"

Фиксируем старт операции в логах. Это точка отсчёта для оценки длительности и диагностики.

# 1. Отключение партиции от родительской таблицы
log "Отключение партиции $PARTITION_NAME..."
sudo -u postgres psql -d dlh_db -c "ALTER TABLE raw.table_1 DETACH PARTITION $PARTITION_NAME;" || log "Партиция уже отключена или не существует."

Отсоединяем партицию от родительской таблицы. Это правильный порядок: сначала «вывести» партицию из логики БД, потом удалять. Если партиция уже отсоединена или не существует — ошибка игнорируется, в лог пишется предупреждение.

С точки зрения прикладной логики это «безопасное ослабление связей»: мы не пытаемся удалить то, что ещё участвует в структуре таблицы.

# 2. Удаление партиции как объекта
log "Удаление партиции $PARTITION_NAME..."
sudo -u postgres psql -d dlh_db -c "DROP TABLE IF EXISTS $PARTITION_NAME;" || log "Партиция уже удалена."

Удаляем саму таблицу-партицию. DROP TABLE IF EXISTS делает операцию идемпотентной: если её уже нет, ошибки не будет.

Это пример «мягкой» операции: скрипт не падает, если объект уже удалён.

# 3. Удаление табличного пространства
log "Удаление табличного пространства $TABLESPACE_NAME..."
sudo -u postgres psql -d dlh_db -c "DROP TABLESPACE IF EXISTS $TABLESPACE_NAME;" || log "Табличное пространство уже удалено."

Удаляем табличное пространство. Опять же, IF EXISTS защищает от ошибок, если оно уже отсутствует.

Важно: порядок действий (сначала партиция, потом табличное пространство) критичен: нельзя удалить табличное пространство, пока в нём есть объекты БД.

# 4. Отмонтирование тома
if mount | grep -q "$MOUNT_POINT"; then
    log "Отмонтирование $MOUNT_POINT..."
    sudo umount "$MOUNT_POINT" || { log "Ошибка отмонтирования"; exit 1; }
else
    log "Том уже отмонтирован."
fi

Проверяем, смонтирован ли том в точке MOUNT_POINT:

  • Если смонтирован — пытаемся отмонтировать через sudo umount. При ошибке — лог и выход.
  • Если не смонтирован — просто фиксируем это в логе.

Здесь важна атомарность: нельзя архивировать или удалять том, который ещё используется.

# 5. Создание архива (если том существует и есть данные)
if lvdisplay "/dev/$VG_NAME/$LV_NAME" >/dev/null 2>&1; then
    TEMP_MNT="/mnt/temp_${QUARTER}"
    sudo mkdir -p "$TEMP_MNT"
    sudo mount -o ro "/dev/$VG_NAME/$LV_NAME" "$TEMP_MNT" || { log "Ошибка монтирования для архивации"; exit 1; }
    sudo tar -czf "$BACKUP_FILE" -C "$TEMP_MNT" .
    sudo umount "$TEMP_MNT"
    sudo rmdir "$TEMP_MNT"
    log "Архив создан: $BACKUP_FILE"
else
    log "Логический том /dev/$VG_NAME/$LV_NAME не найден. Пропускаем архивацию."
fi

Если логический том существует — архивируем его:

  • Создаём временный каталог для монтирования.
  • Монтируем том в режиме «только чтение» (-o ro). Это критически важно: при архивации мы не должны случайно изменить данные.
  • Архивируем содержимое через tar -czf.
  • Отмонтируем и удаляем временный каталог («уборка» за собой).

Если тома нет — просто пишем в лог и пропускаем архивацию.

С прикладной точки зрения это «безопасная копия»: все операции над данными выполняются в режиме read-only, а временные ресурсы освобождаются.

# 6. Удаление логического тома (если указан флаг --remove-lv)
if [ "$REMOVE_LV" == "--remove-lv" ]; then
    if lvdisplay "/dev/$VG_NAME/$LV_NAME" >/dev/null 2>&1; then
        log "Удаление логического тома /dev/$VG_NAME/$LV_NAME..."
        sudo lvremove -f "/dev/$VG_NAME/$LV_NAME" || { log "Ошибка удаления тома"; exit 1; }
    else
        log "Логический том уже удалён."
    fi
    # Удаление записи из /etc/fstab
    sudo sed -i "\|$MOUNT_POINT|d" /etc/fstab
    # Удаление точки монтирования
    if [ -d "$MOUNT_POINT" ]; then
        sudo rmdir "$MOUNT_POINT" || log "Не удалось удалить каталог $MOUNT_POINT (возможно, не пуст)."
    fi
fi

Блок условного удаления:

  • Проверяем наличие тома.
  • Удаляем его через lvremove -f (без подтверждения). Это опасная операция: данные будут потеряны. Поэтому она вынесена за отдельный флаг.
  • Очищаем /etc/fstab от записи монтирования этого тома. Синтаксис \|...|d использует | как разделитель, чтобы избежать проблем, если в пути есть /.
  • Пытаемся удалить каталог точки монтирования через rmdir (удаляет только пустые каталоги). Если он не пуст — в лог пишется предупреждение, но скрипт не падает.

Это компромисс между «чистотой системы» и «надёжностью»: не блокировать завершение скрипта из‑за того, что каталог не пуст.

log "Архивация квартала $QUARTER завершена."

Финальная запись в лог: операция завершена. Это удобная точка для мониторинга: «всё прошло» или «остановилось раньше».


Что важно с точки зрения «процесса и результата»¶

  • Порядок операций: сначала БД (DETACH → DROP TABLE → DROP TABLESPACE), потом ФС (umount), потом LVM (архив → lvremove). Это предотвращает ошибки, когда объекты БД ссылаются на удалённые тома.
  • Надёжность: set -e, проверки на существование, логирование каждого шага.
  • Безопасность данных: монтирование в режиме «только чтение», удаление тома — только по явному флагу.
  • Идемпотентность: операции с БД используют IF EXISTS, проверки наличия тома и смонтированности делают скрипт безопасным для повторного запуска.
  • Прозрачность: все шаги фиксируются в логах с временем, что позволяет быстро понять, на каком этапе что пошло не так.
  • Математическая/алгоритмическая чистота: чёткие условия, ветвления и обработка состояний (том есть/нет, смонтирован/нет, объект БД существует/нет) делают логику предсказуемой и воспроизводимой.

Этот скрипт — хороший пример «алгоритма очистки и архивации» с явными предусловиями, проверками и защитой от частичных состояний. Каждый блок решает конкретную подзадачу, а вместе они дают воспроизводимый процесс.


Пояснения к коду файла restore_raw_partition.sh¶

Разберем restore_raw_partition.sh построчно: тут есть и чёткая алгоритмическая логика (ветвления, проверки состояний), и прикладная «экономика» операций (стоимость ошибки, атомарность шагов, воспроизводимость), и элементы программирования (нормализация данных, вычисление диапазонов).


#!/bin/bash

Указывает интерпретатор Bash. Без этой строки ОС не поймёт, как исполнять скрипт. Это базовый контракт запуска: «этот файл — bash‑скрипт».

# restore_raw_partition.sh - восстановление партиции из архива

Комментарий: назначение скрипта. Для поддержки и передачи задачи другим это критично: сразу понятно, что он делает.

# Использование: ./restore_raw_partition.sh <quarter_name> <archive_file>
# Пример: ./restore_raw_partition.sh q2_2026 /backup/raw_archives/raw_q2_2026_20250714_120000.tar.gz

Инструкция по запуску: два обязательных аргумента — имя квартала и путь к архиву. Это делает интерфейс предсказуемым и удобным для автоматизации.

set -e

Ключевая директива: при любой ошибке (ненулевой код возврата) скрипт немедленно завершается. Это защита от «полувосстановленных» состояний: если, например, не удастся создать LVM‑том, скрипт не пойдёт дальше и не попытается распаковать архив в никуда. С точки зрения процесса — это гарантия, что не будет «частичного результата», который сложно откатить.

QUARTER=$1
ARCHIVE_FILE=$2

Присваиваем переменные: QUARTER — первый аргумент (имя квартала), ARCHIVE_FILE — второй (путь к архиву). Это входные данные алгоритма.

if [ -z "$QUARTER" ] || [ -z "$ARCHIVE_FILE" ]; then
    echo "Ошибка: укажите имя квартала и путь к архиву"
    exit 1
fi

Проверка: оба аргумента должны быть переданы. Если нет — выводим ошибку и завершаем скрипт с кодом 1 (ошибка). Это защита от случайного запуска без параметров.

PARTITION_NAME="raw.table_1_${QUARTER}"
TABLESPACE_NAME="ts_raw_${QUARTER}"
MOUNT_POINT="/mnt/raw_${QUARTER}"
LV_NAME="lv_raw_${QUARTER}"
VG_NAME="vg_data"
LV_SIZE="800G"  # фиксированный размер, можно изменить
LOG_FILE="/var/log/dlh/scripts/restore.log"

Инициализация переменных:

  • PARTITION_NAME, TABLESPACE_NAME — имена объектов БД;
  • MOUNT_POINT, LV_NAME, VG_NAME — пути и имена для LVM/ФС;
  • LV_SIZE — размер тома (параметр, который можно вынести в аргументы или конфиг);
  • LOG_FILE — файл логов.

Вынесение в переменные делает скрипт гибким: если схема именования или пути изменятся, правки будут в одном месте. С прикладной точки зрения это «детерминированная генерация идентификаторов»: по имени квартала однозначно строятся все связанные имена.

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

Функция логирования: печатает сообщение с временной меткой в консоль и дописывает в лог. tee -a пишет одновременно в stdout и файл. Это нужно для мониторинга и аудита: ты видишь ход выполнения и имеешь запись для разбора инцидентов.

if [ ! -f "$ARCHIVE_FILE" ]; then
    log "Ошибка: архив $ARCHIVE_FILE не найден"
    exit 1
fi

Проверяем, что файл архива реально существует и это обычный файл. Если нет — логируем ошибку и завершаем скрипт. Это экономит время и защищает от бессмысленных шагов.

log "Начало восстановления квартала $QUARTER из $ARCHIVE_FILE"

Фиксируем старт операции в логах. Это точка отсчёта для оценки длительности и диагностики.

# 1. Создание логического тома, если не существует
if lvdisplay "/dev/$VG_NAME/$LV_NAME" >/dev/null 2>&1; then
    log "Том /dev/$VG_NAME/$LV_NAME уже существует. Пропускаем создание."
else
    log "Создание логического тома $LV_NAME размером $LV_SIZE..."
    sudo lvcreate -L "$LV_SIZE" -n "$LV_NAME" "$VG_NAME" || { log "Ошибка создания тома"; exit 1; }
    sudo mkfs.xfs -f "/dev/$VG_NAME/$LV_NAME"
fi

Логика: если том уже есть — ничего не делаем; если нет — создаём LVM‑том 800 ГБ и форматируем в XFS.

  • lvdisplay с перенаправлением вывода проверяет существование тома по коду возврата.
  • -f в mkfs.xfs форсирует создание ФС (удаляет существующую разметку). Это опасно: можно потерять данные. Но здесь это оправдано, потому что скрипт именно про «восстановление» и предполагает, что на этом месте сейчас не должно быть нужных данных.

С прикладной точки зрения это условный шаг: он делает скрипт идемпотентным для случая «том отсутствует» и безопасным (не создаёт лишнее, если уже есть).

# 2. Монтирование тома
sudo mkdir -p "$MOUNT_POINT"
if ! mount | grep -q "$MOUNT_POINT"; then
    sudo mount "/dev/$VG_NAME/$LV_NAME" "$MOUNT_POINT" || { log "Ошибка монтирования"; exit 1; }
    echo "/dev/$VG_NAME/$LV_NAME $MOUNT_POINT xfs defaults 0 0" | sudo tee -a /etc/fstab
fi

Сначала создаём каталог точки монтирования (если его нет). Затем проверяем, смонтирован ли том:

  • Если не смонтирован — монтируем и добавляем запись в /etc/fstab, чтобы она сохранилась после перезагрузки.
  • Если уже смонтирован — ничего не делаем.

Это типичный шаг «подготовить среду и зафиксировать её состояние».

# 3. Распаковка архива
log "Распаковка архива $ARCHIVE_FILE в $MOUNT_POINT..."
sudo tar -xzf "$ARCHIVE_FILE" -C "$MOUNT_POINT" || { log "Ошибка распаковки"; exit 1; }

Распаковываем архив в точку монтирования:

  • -x — извлечь;
  • -z — распаковать gzip;
  • -f — имя файла;
  • -C — сменить каталог перед распаковкой.

При ошибке — логируем и выходим. Это критично: если данные распаковались частично, лучше остановить процесс, чем продолжать поверх повреждённого состояния.

# 4. Установка прав
sudo chown -R postgres:postgres "$MOUNT_POINT"
sudo chmod 700 "$MOUNT_POINT"

Назначаем владельца postgres:postgres рекурсивно и ставим права 700 (только владелец может читать/писать/исполнять). Это важно для PostgreSQL: он не запустится или не сможет работать с табличным пространством, если права неверны.

Здесь видна прикладная логика: «восстановить данные» — это не только «скопировать файлы», но и «сделать их доступными для целевой системы» (в данном случае — для Postgres).

# 5. Создание табличного пространства
sudo -u postgres psql -d dlh_db -c "CREATE TABLESPACE $TABLESPACE_NAME OWNER postgres LOCATION '$MOUNT_POINT';" || log "Табличное пространство уже существует."

Пытаемся создать табличное пространство в Postgres. Если оно уже есть — ошибка игнорируется, а в лог пишется предупреждение. Это пример «мягкой» операции: скрипт не падает, если объект уже создан.

# 6. Определение дат начала и конца квартала
YEAR=$(echo "$QUARTER" | grep -oP '(?<=q[1-4]_)\d{4}')
Q=$(echo "$QUARTER" | grep -oP 'q[1-4]' | tr -d 'q')
case $Q in
    1) START_DATE="${YEAR}-01-01"; END_DATE="${YEAR}-04-01" ;;
    2) START_DATE="${YEAR}-04-01"; END_DATE="${YEAR}-07-01" ;;
    3) START_DATE="${YEAR}-07-01"; END_DATE="${YEAR}-10-01" ;;
    4) START_DATE="${YEAR}-10-01"; END_DATE="$((YEAR+1))-01-01" ;;
    *) log "Ошибка: неверный квартал (поддерживаются 1-4)"; exit 1 ;;
esac

Извлекаем год и номер квартала из имени (например, q2_2026 → 2026, 2):

  • grep -oP использует Perl‑совместимые регулярные выражения;
  • (?<=...) — lookbehind: взять цифры после qN_;
  • tr -d 'q' удаляет букву q, оставляя только цифру.

Затем через case вычисляем границы диапазона дат для партиционирования по номеру квартала. Это чистая логика диапазонов: каждому кварталу соответствует фиксированный интервал.

С точки зрения прикладной экономики/процессов: такие диапазоны — это «периоды учёта», и важно, чтобы они не пересекались и покрывали весь год.

# 7. Создание партиции (заново)
log "Создание партиции $PARTITION_NAME для периода $START_DATE - $END_DATE"
sudo -u postgres psql -d dlh_db -c "
    CREATE TABLE $PARTITION_NAME PARTITION OF raw.table_1
    FOR VALUES FROM ('$START_DATE') TO ('$END_DATE')
    TABLESPACE $TABLESPACE_NAME;
" || log "Ошибка создания партиции (возможно, уже существует)."

Создаём партицию таблицы raw.table_1 с нужным диапазоном дат и табличным пространством. Здесь нет IF NOT EXISTS, поэтому при повторном запуске будет ошибка (но скрипт не упадёт благодаря || log).

С точки зрения алгоритма это «финальный шаг интеграции»: данные на диске + права + табличное пространство + партиция в БД. Только после этого данные становятся доступны для запросов.

log "Восстановление квартала $QUARTER завершено."

Финальная запись в лог: операция завершена. Это удобная точка для мониторинга: «всё прошло» или «остановилось раньше».


Что важно с точки зрения «процесса и результата»¶

  • Надёжность: set -e, проверки аргументов и существования файлов, логирование каждого шага.
  • Безопасность: монтирование и работа с данными происходят после подготовки тома и прав; критические операции (пересоздание ФС) вынесены в логику «если не существует».
  • Идемпотентность: большинство шагов безопасно повторять (том, табличное пространство, fstab). Исключение — создание партиции без IF NOT EXISTS: при повторном запуске будет предупреждение, но скрипт не упадёт.
  • Интеграция с БД: восстановление не ограничивается файловой системой, а включает создание табличного пространства и партиций — это полный «контекст использования» данных.
  • Прозрачность: все шаги фиксируются в логах с временем, что позволяет быстро понять, на каком этапе что пошло не так.
  • Математическая/алгоритмическая чистота: чёткие условия, ветвления и обработка состояний (том есть/нет, смонтирован/нет, объект БД существует/нет) делают логику предсказуемой и воспроизводимой.

Этот скрипт — хороший пример «алгоритма восстановления» с нормализацией входных данных, вычислением диапазонов, проверками условий и идемпотентными операциями. Каждый блок решает конкретную подзадачу, а вместе они дают воспроизводимый процесс.


Пояснения к коду файла create_new_partition.sh¶

Разберем create_new_partition.sh построчно: тут много логики преобразования данных, ветвлений и «алгоритмической надёжности» — по сути, это скрипт-фабрика для квартальных партиций.

#!/bin/bash

Указывает интерпретатор Bash. Без этой строки ОС не поймёт, как исполнять скрипт.

# create_new_partition.sh - создание новой квартальной партиции (для использования в будущем)

Комментарий: назначение скрипта. Для поддержки и передачи задачи другим это критично: сразу понятно, что он делает.

# Использование: ./create_new_partition.sh <year> <quarter>
# Пример: ./create_new_partition.sh 2027 Q1

Инструкция по запуску: два обязательных аргумента — год и квартал. Это делает интерфейс предсказуемым и удобным для автоматизации.

set -e

Ключевая директива: при любой ошибке (ненулевой код возврата) скрипт немедленно завершается. Это защита от «полусозданных» состояний: если, например, не удастся создать LVM-том, скрипт не пойдёт дальше и не сделает «частичную» партицию. С точки зрения процесса — это гарантия атомарности шагов.

YEAR=$1
QUARTER=$2
LOG_FILE="/var/log/dlh/scripts/create_partition.log"

Присваиваем переменные: YEAR — первый аргумент, QUARTER — второй, LOG_FILE — путь к логу. Вынесение путей и имён в переменные делает скрипт гибким: если схема именования или каталоги изменятся, правки будут в одном месте.

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

Функция логирования: печатает сообщение с временной меткой в консоль и дописывает в лог. tee -a пишет одновременно в stdout и файл. Это нужно для мониторинга и аудита: ты видишь ход выполнения и имеешь запись для разбора инцидентов.

if [ -z "$YEAR" ] || [ -z "$QUARTER" ]; then
    log "Ошибка: укажите год и квартал (например, 2027 Q1)"
    exit 1
fi

Проверка: оба аргумента должны быть переданы. Если нет — логируем ошибку и завершаем скрипт с кодом 1 (ошибка). Это защита от случайного запуска без параметров.

# Преобразование квартала в нижний регистр и удаление пробелов
Q_LOWER=$(echo "$QUARTER" | tr '[:upper:]' '[:lower:]' | tr -d ' ')
QUARTER_NAME="q${Q_LOWER}_${YEAR}"  # например, q1_2027

Нормализация ввода:

  • tr '[:upper:]' '[:lower:]' — приводит к нижнему регистру (чтобы Q1, q1, Q 1 стали одинаковыми);
  • tr -d ' ' — удаляет пробелы;
  • QUARTER_NAME — формирует каноническое имя квартала (например, q1_2027).

С точки зрения прикладной математики это «нормализация входных данных»: приводим разные варианты к единому формату, чтобы дальше логика работала однозначно.

MOUNT_POINT="/mnt/raw_${QUARTER_NAME}"
LV_NAME="lv_raw_${QUARTER_NAME}"
VG_NAME="vg_data"
TABLESPACE_NAME="ts_raw_${QUARTER_NAME}"
PARTITION_NAME="raw.table_1_${QUARTER_NAME}"

Определение всех связанных имён и путей по шаблону. Это «детерминированная генерация идентификаторов»: по году и кварталу однозначно строятся имена тома, точки монтирования, табличного пространства и партиции. Такой подход делает скрипт воспроизводимым и предсказуемым.

# Определение дат начала и конца квартала
case $QUARTER in
    Q1) START_DATE="${YEAR}-01-01"; END_DATE="${YEAR}-04-01" ;;
    Q2) START_DATE="${YEAR}-04-01"; END_DATE="${YEAR}-07-01" ;;
    Q3) START_DATE="${YEAR}-07-01"; END_DATE="${YEAR}-10-01" ;;
    Q4) START_DATE="${YEAR}-10-01"; END_DATE="$((YEAR+1))-01-01" ;;
    *) log "Ошибка: неверный квартал. Используйте Q1, Q2, Q3, Q4"; exit 1 ;;
esac

Вычисляем границы диапазона дат для партиционирования по номеру квартала. Это чистая логика диапазонов: каждому кварталу соответствует фиксированный интервал.

Здесь видна прикладная логика «периодов учёта»: границы не пересекаются и покрывают весь год, а для Q4 корректно переносится год в END_DATE. С точки зрения экономики/учёта это важно: периоды должны быть смежными и не иметь разрывов.

log "Начало создания партиции $QUARTER_NAME для периода $START_DATE - $END_DATE"

Фиксируем старт операции в логах. Это точка отсчёта для оценки длительности и диагностики.

# Проверяем, существует ли уже том
if lvdisplay "/dev/$VG_NAME/$LV_NAME" >/dev/null 2>&1; then
    log "Том /dev/$VG_NAME/$LV_NAME уже существует. Пропускаем создание."
else
    log "Создание логического тома $LV_NAME размером 800G..."
    sudo lvcreate -L 800G -n "$LV_NAME" "$VG_NAME" || { log "Ошибка создания тома"; exit 1; }
    sudo mkfs.xfs -f "/dev/$VG_NAME/$LV_NAME"
fi

Логика: если том уже есть — ничего не делаем; если нет — создаём LVM-том 800 ГБ и форматируем в XFS.

  • lvdisplay с перенаправлением вывода проверяет существование тома по коду возврата.
  • -f в mkfs.xfs форсирует создание ФС (удаляет существующую разметку). Это опасно: можно потерять данные. Но здесь это оправдано, потому что скрипт именно про «создание новой партиции» и предполагает, что на этом месте сейчас не должно быть нужных данных.

С прикладной точки зрения это условный шаг: он делает скрипт идемпотентным для случая «том отсутствует» и безопасным (не создаёт лишнее, если уже есть).

# Создание точки монтирования
sudo mkdir -p "$MOUNT_POINT"

Создаёт каталог точки монтирования, если его нет. -p не выдаст ошибку, если каталог уже существует. Это стандартная практика подготовки среды.

# Монтирование, если не смонтировано
if ! mount | grep -q "$MOUNT_POINT"; then
    sudo mount "/dev/$VG_NAME/$LV_NAME" "$MOUNT_POINT" || { log "Ошибка монтирования"; exit 1; }
    echo "/dev/$VG_NAME/$LV_NAME $MOUNT_POINT xfs defaults 0 0" | sudo tee -a /etc/fstab
fi

Если точка не смонтирована — монтируем том и добавляем запись в /etc/fstab, чтобы она сохранялась после перезагрузки.

  • grep -q ищет строку монтирования; ! инвертирует условие.
  • Запись в fstab делает конфигурацию постоянной.

Это типичный шаг «подготовить среду и зафиксировать её состояние».

# Установка прав
sudo chown postgres:postgres "$MOUNT_POINT"
sudo chmod 700 "$MOUNT_POINT"

Назначаем владельца postgres:postgres и ставим права 700 (только владелец может читать/писать/исполнять). Это важно для PostgreSQL: он не запустится или не сможет работать с табличным пространством, если права неверны.

Здесь видна прикладная логика: «создать партицию» — это не только «создать таблицу в БД», но и «одготовить файловую систему и права доступа» для целевой системы (Postgres).

# Создание табличного пространства
sudo -u postgres psql -d dlh_db -c "CREATE TABLESPACE $TABLESPACE_NAME OWNER postgres LOCATION '$MOUNT_POINT';" || log "Ошибка создания табличного пространства (возможно, уже существует)."

Пытаемся создать табличное пространство в Postgres. Если оно уже есть — ошибка игнорируется, а в лог пишется предупреждение. Это пример «»мягкой» операции: скрипт не падает, если объект уже создан.

# Создание партиции
sudo -u postgres psql -d dlh_db -c "
    CREATE TABLE IF NOT EXISTS $PARTITION_NAME PARTITION OF raw.table_1
    FOR VALUES FROM ('$START_DATE') TO ('$END_DATE')
    TABLESPACE $TABLESPACE_NAME;
" || log "Ошибка создания партиции (возможно, уже существует)."

Создаём партицию таблицы raw.table_1 с нужным диапазоном дат и табличным пространством. CREATE TABLE IF NOT EXISTS делает операцию идемпотентной: если партиция уже есть, ничего не произойдёт.

Это ключевой прикладной шаг: создать структуру БД, чтобы будущие данные могли быть корректно распределены по партициям.

log "Партиция $PARTITION_NAME создана для периода $START_DATE - $END_DATE"

Финальная запись в лог: операция завершена. Это удобная точка для мониторинга: «всё прошло» или «остановилось раньше».

Что важно с точки зрения «процесса и результата»¶

  • Надёжность: set -e, проверки аргументов, логирование каждого шага.
  • Безопасность: подготовка прав и точек монтирования перед использованием в БД; критические операции (пересоздание ФС) вынесены в логику «если не существует».
  • Идемпотентность: скрипт можно запускать несколько раз — он не будет создавать лишнее и не сломает уже существующее.
  • Интеграция с БД: создание не только файловой структуры, но и табличного пространства, и партиции — это полный «контекст использования» данных.
  • Прозрачность: все шаги фиксируются в логах с временем, что позволяет быстро понять, на каком этапе что пошло не так.

Этот скрипт — хороший пример «алгоритма создания ресурса» с нормализацией входных данных, вычислением диапазонов, проверками условий и идемпотентными операциями. Каждый блок решает конкретную подзадачу, а вместе они дают воспроизводимый процесс.

AI-Ready платформа Data Lakehouse ELT PostgreSQL гибридное хранилище данных корпоративная информационная система практика

Предыдущая статьяКто управляет информацией, тот управляет миромСледующая статья Автоматизация создания слоя RAW гибридного хранилища данных

Рубрики

Метки

abc abcd AI-Ready платформа Bash Data Lakehouse ELT excel ms sql pandas PostgreSQL PowerShell Python RAW sql tessa VBA xyz анализ виртуальный помощник гибридное хранилище данных данные знания информационная система информация кластерный анализ комбинаторика корпоративная информационная система маркетинг математика медальон-архитектура модель предоставления прав мудрость о проекте оптимизация практика программное обеспечение пэст ролевая модель сеть; теория теория вероятностей тесса тест юмор языки программирования

Политика конфиденциальности

Продолжая использовать данный сайт вы подтверждаете свое согласие с условиями его политики конфиденциальности. Подробнее…




Администрация и владельцы данного информационного ресурса не несут ответственности за возможные последствия, связанные с использованием информации, размещенной на нем.


Все права защищены. При копировании материалов сайта обязательно указывать ссылку на © Microsegment.ru (2020-2026)