Здесь представлены файлы bash для автоматизации создания слоя сырых данных RAW с архивируемыми партициями на отдельных отмонтируемых сегментах диска минимального жизнеспособного (MVP) гибридного хранилища данных (Data Lakehouse) на основе PostgreSQL.
Скрипт файла create_raw.sh для создания слоя RAW¶
Файл create_raw.sh — предназначен для автоматизации создания «с нуля» слоя RAW минимального жизнеспособного гибридного хранилища данных. Это полезно при первичном создании слоя и отладки его функционирования. Обязательно сделать этот файл исполняемым с помощью команды: sudo chmod +x /opt/dlh/scripts/create_raw.sh. После этого его можно будет использовать с помощью команды ./create_raw.sh.
#!/bin/bash
# create_raw.sh - создание слоя RAW "с нуля"
# обязательно сделать исполняемым: sudo chmod +x /opt/dlh/scripts/create_raw.sh
# Использование: ./create_raw.sh
# Пример: ./create_raw.sh
# 1. Остановка выполнения данного файла при любой ошибке
set -e
# 2. Базовые проверки
# 2.1. Проверка прав root
if [[ $EUID -ne 0 ]]; then
log "Ошибка: скрипт должен быть запущен от root (sudo)."
exit 1
fi
# 3. Получение параметров и объявление
# глобальных переменных этого скрипта
# 3.1. Базовые переменные с параметрами запуска данного скрипта
REMOVE_LV=$1
# 3.2. Переменные с полными или частичными названиями и адресами
TABLE_NAME="table_1"
RAW_TABLE_NAME="raw.${TABLE_NAME}"
VG_NAME="vg_data"
YEAR="2026"
LOG_FILE="/var/log/dlh/scripts/archive.log"
POSTGRESQL_SERVICE_NAME="postgresql-17"
# 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 "Начало создания слоя RAW"
# 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. Создание логических томов и точек монтирования
if true; then
# 6.1. Создание физического тома
log "Создание физического тома /dev/sdb2"
if ! sudo pvcreate /dev/sdb2; then
log "Не удалось создать физический том /dev/sdb2"
fi
# 6.2. Создание группы томов
log "Создание группы томов vg_data в /dev/sdb2"
if ! sudo vgcreate vg_data /dev/sdb2; then
log "Не удалось создать группу томов vg_data в /dev/sdb2"
fi
for i in {2..4}; do
# 6.3. Создание логического тома для будущей партиции
LV_NAME="lv_raw_q${i}_${YEAR}"
LV_PATH="/dev/${VG_NAME}/${LV_NAME}"
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
# 6.4. Форматирование логического тома
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
# 6.5. Создание каталога в точке монтирования
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
# 6.6. Монтирование каталога с томом
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
# 6.7. Добавление записей в `/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
# 6.8. Назначение прав на каталог для пользователя `postgres`
log "Назначение прав на каталог ${MOUNT_POINT} для пользователя postgres"
if chown postgres:postgres "${MOUNT_POINT}"; then
log "Права на каталог ${MOUNT_POINT} для пользователя postgres успешно назначены"
else
log "Ошибка при назначении прав на каталог ${MOUNT_POINT} для пользователя postgres"
fi
# 6.9. Назначение прав для всех пользователей на каталог
log "Назначение прав на каталог ${MOUNT_POINT} для всех пользователей"
if chmod 700 "${MOUNT_POINT}"; then
log "Права на каталог ${MOUNT_POINT} для всех пользователей успешно назначены"
else
log "Ошибка при назначении прав на каталог ${MOUNT_POINT} для всех пользователей"
fi
done
# Проверки
log "Использование дискового пространства для всех точек монтированрия"
df -h /mnt/raw_q*
log "Краткий список физифческих томов (PV), которые участвуют в LVM"
sudo pvs
log "Краткий список групп томов (VG) и сводная статистика по ним"
sudo vgs
log "Список логических томов (LV) с базовой информацией"
sudo lvs
log "Подробная информация обо всех логических томах (LV)"
sudo lvdisplay
log "Где физически лежат логические тома (LV), какая на них ФС и куда они смонтированы"
sudo lsblk -f
fi
# 7. Запуск PostgreSQL
log "Запуск службы ${POSTGRESQL_SERVICE_NAME}"
if systemctl start "${POSTGRESQL_SERVICE_NAME}"; then
log "Служба ${POSTGRESQL_SERVICE_NAME} успешно запущена"
else
log "Не удалось запустить службу ${POSTGRESQL_SERVICE_NAME}"
fi
# 8. Создание табличных пространств для каждого квартала
# true - если надо выполнить и false - если не надо
if true; then
for i in {2..4}; do
TABLESPACE_NAME="ts_raw_q${i}_${YEAR}"
MOUNT_POINT="/mnt/raw_q${i}_${YEAR}"
log "Создание табличного пространства ${TABLESPACE_NAME}"
run_sql "CREATE TABLESPACE %I OWNER postgres LOCATION '%I';" space="$TABLESPACE_NAME" point="$MOUNT_POINT"
done
log "Проверка списка табличных пространств"
run_sql "\db+;"
fi
# 9. Создание схемы raw
if true; then
log "Создание схемы raw"
run_sql "CREATE SCHEMA IF NOT EXISTS raw AUTHORIZATION postgres;"
fi
# 10. Создание партиционированной таблицы-родителя
# (дублирует stg.action_history, но партируется по полю modified)
if true; then
log "Создание партиционированной таблицы-родителя (дублирует stg.${TABLE_NAME}, но партируется по полю modified)"
run_sql "
CREATE TABLE IF NOT EXISTS ${RAW_TABLE_NAME} (
id UUID NOT NULL,
row_id UUID NOT NULL,
action_id SMALLINT NOT NULL,
type_id UUID NOT NULL,
type_caption VARCHAR(128) NOT NULL,
digest VARCHAR(128),
modified TIMESTAMP WITHOUT TIME ZONE NOT NULL,
modified_by_id UUID NOT NULL,
modified_by_name VARCHAR(128) NOT NULL,
session_id UUID NOT NULL,
request JSONB NOT NULL,
application_id UUID
) PARTITION BY NAME (modified);
"
fi
# 11. Создание квартальных партиций
# 11.1. Определение дат начала и конца квартала
# 11.2.
if true; then
log "Создание квартальных партиций"
for i in {2..4}; do
QUARTER_NAME="q${i}_${YEAR}" # например, q1_2027
PARTITION_NAME="raw.${TABLE_NAME}_${QUARTER_NAME}"
TABLESPACE_NAME="ts_raw_q${i}_${YEAR}"
log "Определение дат начала и конца квартала ${QUARTER_NAME}"
case $i 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, 2, 3, 4"; exit 1 ;;
esac
log "QUARTER_NAME=${QUARTER_NAME}, START_DATE=${START_DATE}, END_DATE=${END_DATE}"
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}"
done
log "Проверка созданных партиций"
run_sql "
SELECT
schemaname,
tablename,
tablespace
FROM pg_tables
WHERE schemaname = 'raw' AND tablename LIKE '${TABLE_NAME}%'
ORDER BY tablename;
"
fi
# 12. Настройка прав доступа для dlh_user
if true; then
log "Настройка прав доступа для dlh_user"
run_sql "GRANT USAGE ON SCHEMA raw TO dlh_user;"
run_sql "GRANT INSERT, SELECT ON ${RAW_TABLE_NAME} TO dlh_user;"
log "Опционально права на создание партиций в будущем"
run_sql "GRANT CREATE ON SCHEMA raw TO dlh_user;"
fi
# 13. Добавление комментариев для трассировки
if true; then
log "Добавление комментариев для трассировки"
run_sql "
COMMENT ON TABLE ${RAW_TABLE_NAME} IS 'Оригинальная таблица: ActionHistory из MS SQL TESSA. Партиционирование по modified (кварталы)';
COMMENT ON COLUMN ${RAW_TABLE_NAME}.id IS 'Опригинальное поле: ID';
COMMENT ON COLUMN ${RAW_TABLE_NAME}.row_id IS 'Опригинальное поле: RowID';
COMMENT ON COLUMN ${RAW_TABLE_NAME}.action_id IS 'Опригинальное поле: ActionID';
COMMENT ON COLUMN ${RAW_TABLE_NAME}.type_id IS 'Опригинальное поле: TypeID';
COMMENT ON COLUMN ${RAW_TABLE_NAME}.type_caption IS 'Опригинальное поле: TypeCaption';
COMMENT ON COLUMN ${RAW_TABLE_NAME}.digest IS 'Опригинальное поле: Digest';
COMMENT ON COLUMN ${RAW_TABLE_NAME}.modified IS 'Опригинальное поле: Modified';
COMMENT ON COLUMN ${RAW_TABLE_NAME}.modified_by_id IS 'Опригинальное поле: ModifiedByID';
COMMENT ON COLUMN ${RAW_TABLE_NAME}.modified_by_name IS 'Опригинальное поле: ModifiedByName';
COMMENT ON COLUMN ${RAW_TABLE_NAME}.session_id IS 'Опригинальное поле: SessionID';
COMMENT ON COLUMN ${RAW_TABLE_NAME}.request IS 'Опригинальное поле: Request';
COMMENT ON COLUMN ${RAW_TABLE_NAME}.application_id IS 'Опригинальное поле: ApplicationID';
"
fi
log "Процедура создания слоя RAW завершена успешно"
Скрипт файла drop_raw.sh для удаления слоя RAW¶
Файл drop_raw.sh — предназначен для автоматизации удаления слоя RAW. Это полезно, как и в случае использования файла create_raw.sh, при первичном создании слоя и отладки его функционирования. Обязательно сделать этот файл исполняемым с пмощью команды: sudo chmod +x /opt/dlh/scripts/drop_raw.sh. После этого его можно будет использовать с помощью команды ./drop_raw.sh --remove-lv.
#!/bin/bash
# drop_raw.sh - полное удаление слоя RAW
# обязательно сделать исполняемым: sudo chmod +x /opt/dlh/scripts/drop_raw.sh
# Использование: ./drop_raw.sh
# Пример: ./drop_raw.sh --remove-lv
# 1. Остановка выполнения данного файла при любой ошибке
set -e
# 2. Базовые проверки
# 2.1. Проверка прав root
if [[ $EUID -ne 0 ]]; then
log "Ошибка: скрипт должен быть запущен от root (sudo)."
exit 1
fi
# 3. Получение параметров и объявление
# глобальных переменных этого скрипта
# 3.1. Базовые переменные с параметрами запуска данного скрипта
REMOVE_LV=$1
# 3.2. Переменные с полными или частичными названиями и адресами
TABLE_NAME="action_history"
RAW_TABLE_NAME="raw.${TABLE_NAME}"
VG_NAME="vg_data"
YEAR="2026"
LOG_FILE="/var/log/dlh/scripts/archive.log"
POSTGRESQL_SERVICE_NAME="postgresql-17"
# 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 "Начало удаления слоя RAW"
# 5. Запуск PostgreSQL
log "Запуск службы ${POSTGRESQL_SERVICE_NAME}"
if systemctl start "${POSTGRESQL_SERVICE_NAME}"; then
log "Служба ${POSTGRESQL_SERVICE_NAME} успешно запущена"
else
log "Не удалось запустить службу ${POSTGRESQL_SERVICE_NAME}"
fi
# 6. Отсоединение всех партиций от родительской таблицы (данные остаются в партиции)
# true - если надо выполнить и false - если не надо
if true; then
for i in {2..4}; do
PARTITION_NAME="${RAW_TABLE_NAME}_q${i}_${YEAR}"
log "Отключение партиции ${PARTITION_NAME}"
run_sql "ALTER TABLE %I DETACH PARTITION %I;" table="$RAW_TABLE_NAME" part="$PARTITION_NAME"
done
fi
# 7. Последовательное удаление партиций и таблицы (это удалит и файл с диска, если он был битым, почистит каталог)
if true; then
for i in {2..4}; do
PARTITION_NAME="${RAW_TABLE_NAME}_q${i}_${YEAR}"
log "Удаление партиции ${PARTITION_NAME}"
run_sql "DROP TABLE IF EXISTS %I;" table="$PARTITION_NAME"
done
log "Удаление таблицы ${RAW_TABLE_NAME}"
run_sql "DROP TABLE IF EXISTS %I;" table="$RAW_TABLE_NAME"
fi
# 8. Удаление основной таблицы слоя RAW (это удалит и файл с диска, если он был битым, почистит каталог)
if true; then
log "Удаление таблицы ${RAW_TABLE_NAME}"
run_sql "DROP TABLE IF EXISTS %I;" table="$RAW_TABLE_NAME"
fi
# 9. Удаление табличных пространств
if true; then
for i in {2..4}; do
TABLESPACE_NAME="ts_raw_q${i}_${YEAR}"
log "Удаление табличного пространства ${TABLESPACE_NAME}"
run_sql "DROP TABLESPACE IF EXISTS %I;" tablespace="$TABLESPACE_NAME"
done
fi
# 10. Удаление схемы RAW
if true; then
log "Удаление схемы RAW"
run_sql "DROP SCHEMA IF EXISTS raw CASCADE;"
fi
log "Начало процедуры зачистки томов и точек монтирования"
# 11. Остановка PostgreSQL (ы РЕД ОС обычно systemd)
log "Остановка службы ${POSTGRESQL_SERVICE_NAME}"
if systemctl stop "${POSTGRESQL_SERVICE_NAME}"; then
log "Служба ${POSTGRESQL_SERVICE_NAME} успешно остановлена"
else
log "Не удалось остановить службу ${POSTGRESQL_SERVICE_NAME}"
fi
# 12. Размонтирование и удаление логических томов,
# пустой папки в точке монтирования (если указан флаг --remove-lv)
if [ "$REMOVE_LV" == "--remove-lv" ]; then
for i in {2..4}; do
MOUNT_POINT="/mnt/raw_q${i}_${YEAR}"
LV_NAME="lv_raw_q${i}_${YEAR}"
# 12.1. Размонтирование логического тома
if true; then
log "Размонтирование логического тома ${MOUNT_POINT}"
if mountpoint -q "${MOUNT_POINT}"; then
if ! umount "${MOUNT_POINT}"; then
log "Том ${MOUNT_POINT} не размонтирован"
exit 1
else
log "Том ${MOUNT_POINT} успешно размонтирован"
fi
else
log "Том ${MOUNT_POINT} не смонтирован"
fi
fi
# 12.2. Удаление логического тома
if true; then
LV_PATH="/dev/${VG_NAME}/${LV_NAME}"
log "Удаление логического тома ${LV_PATH}"
if lvdisplay "${LV_PATH}" >/dev/null 2>&1; then
if lvremove -y "${LV_PATH}"; then
log "Логический том ${LV_PATH} успешно удален"
else
log "Ошибка удаления логического тома ${LV_PATH}"
exit 1
fi
else
log "Логический том ${LV_PATH} не найден"
fi
fi
# 12.3. Удаление записи о точке монтирования из /etc/fstab
if true; then
log "Удаление записи о точке монтирования ${MOUNT_POINT} из /etc/fstab"
sed -i "\|${MOUNT_POINT}|d" /etc/fstab
log "Запись о точке монтирования ${MOUNT_POINT} удалена из /etc/fstab"
fi
# 12.4. Удаление пустой папки в точке монтирования
if true; then
log "Удаление пустой папки в точке монтирования ${MOUNT_POINT}"
if [-d "${MOUNT_POINT}"]; then
if [-z "$(ls -A "${MOUNT_POINT}")"; then
if ! rmdir "${MOUNT_POINT}"; then
log "Не удалось удалить каталог ${MOUNT_POINT}"
exit 1
fi
else
log "ВНИМАНИЕ: Папка ${MOUNT_POINT} не удалена, т.к. она не пуста! Содержание: $(ls -A "${MOUNT_POINT}")"
exit 1
fi
else
log "Папка ${MOUNT_POINT} не найдена"
fi
fi
done
# 12.5. Удаление группы томов
log "Удаление группы томов vg_data в /dev/sdb2"
if ! sudo vgremove vg_data; then
log "Не удалось удалить группу томов vg_data в /dev/sdb2"
fi
# 12.6. Удаление физического тома
log "Удаление физического тома /dev/sdb2"
if ! sudo pvremove /dev/sdb2; then
log "Не удалось удалить физический том /dev/sdb2"
fi
# Проверки
log "Использование дискового пространства для всех точек монтированрия"
df -h /mnt/raw_q*
log "Краткий список физифческих томов (PV), которые участвуют в LVM"
sudo pvs
log "Краткий список групп томов (VG) и сводная статистика по ним"
sudo vgs
log "Список логических томов (LV) с базовой информацией"
sudo lvs
log "Подробная информация обо всех логических томах (LV)"
sudo lvdisplay
log "Где физически лежат логические тома (LV), какая на них ФС и куда они смонтированы"
sudo lsblk -f
fi
# 13. Запуск PostgreSQL
log "Запуск службы ${POSTGRESQL_SERVICE_NAME}"
if systemctl start "${POSTGRESQL_SERVICE_NAME}"; then
log "Служба ${POSTGRESQL_SERVICE_NAME} успешно запущена"
else
log "Не удалось запустить службу ${POSTGRESQL_SERVICE_NAME}"
fi
log "Процедура удаления слоя RAW завершена успешно"

