Перейти к содержанию

Версионирование данных параметров

Обзор

Механизм версионирования позволяет сохранять снимки данных параметров в определенные моменты времени и восстанавливать их при необходимости. Это критически важно для:

  • Создания точек восстановления перед рискованными операциями
  • Сравнения данных между разными периодами планирования
  • Хранения исторических срезов для аудита и анализа
  • Тестирования различных сценариев с возможностью отката

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

Архитектура механизма

Основные компоненты

1. Version (Версия)

Модель: /src/planiqum/core/parameters/models.py: Version

class Version(HierarchyItem):
    # Наследуется от HierarchyItem
    # Автоматически получает поля: shortname, description, is_active
    level = VersionHierarchy.get_version_level()  # version__version

Характеристики:

  • Version — это обычный элемент иерархии уровня version__version
  • Хранится в таблице core_parameter_version
  • Имеет shortname (имя версии, например "v1.0", "2024-Q1")
  • Может быть активной/неактивной (is_active)

2. Backup-параметр

Функция создания: /src/planiqum/core/parameters/scripts/backup.py: create_backup_parameter

Суть: Для каждого параметра с флагом has_backup=True создается отдельный параметр-дублер.

Структура backup-параметра:

Основной параметр "Sales Plan":
  Dimensions: [Product, Region, Date]
  Measures: [Quantity, Revenue]

Backup-параметр "Sales Plan (backup)":
  Dimensions: [Version, Product, Region, Date]  ← Version добавлен!
  Measures: [Quantity, Revenue]

Особенности:

  • Имя: "{original_name} (backup)" (суффикс из settings.PARAMETERS_BACKUP_SUFFIX)
  • Key: "{original_key}_backup"
  • Флаг: is_backup=True
  • Дополнительное измерение: Version (первое в списке dimensions)
  • Структура мер: Полностью копируется из исходного параметра
  • Fact-таблица: Отдельная таблица fact__{backup_key} с колонкой version

3. Fact-таблица backup-параметра

Структура:

CREATE TABLE fact__sales_plan_backup (
    id BIGSERIAL PRIMARY KEY,
    dim_version__abc123 BIGINT,          -- ID версии (ПЕРВАЯ dimension!)
    dim_product__def456 BIGINT,
    dim_region__ghi789 BIGINT,
    dim_date__jkl012 BIGINT,
    m_quantity__mno345 NUMERIC(20, 4),
    m_revenue__pqr678 NUMERIC(20, 4)
);

-- Уникальность: (version_id + все остальные dimensions)
CREATE UNIQUE INDEX idx_unique ON fact__sales_plan_backup 
    (dim_version__abc123, dim_product__def456, dim_region__ghi789, dim_date__jkl012);

Принципиальная схема

┌─────────────────────────────────────────────────────────────────┐
│                      ВЕРСИОНИРОВАНИЕ ДАННЫХ                      │
└─────────────────────────────────────────────────────────────────┘

┌──────────────────────┐         backup()          ┌───────────────────────┐
│  Основной параметр   │ ──────────────────────►   │  Backup-параметр      │
│  "Sales Plan"        │                           │  "Sales Plan (backup)"│
│                      │         restore()         │                       │
│  Dimensions:         │ ◄──────────────────────   │  Dimensions:          │
│  - Product           │                           │  - Version   ← NEW!   │
│  - Region            │                           │  - Product            │
│  - Date              │                           │  - Region             │
│                      │                           │  - Date               │
│  Measures:           │                           │                       │
│  - Quantity          │                           │  Measures:            │
│  - Revenue           │                           │  - Quantity           │
│                      │                           │  - Revenue            │
│  fact__sales_plan    │                           │  fact__sales_plan_bkp │
└──────────────────────┘                           └───────────────────────┘
         ▲                                                    │
         │                                                    │
         │                    Хранит все версии:             │
         │                    ┌─────────────────────────┐    │
         │                    │ Version = v1.0          │◄───┤
         │                    │ Version = v2.0          │◄───┤
         │                    │ Version = 2024-Q1       │◄───┤
         │                    │ ...                     │◄───┘
         │                    └─────────────────────────┘
         │
    Восстановление
    с фильтром по версии

Создание версий

Через Django Admin

URL: /admin/parameters/version/add/

Поля:

  • shortname — имя версии (обязательное, уникальное)
  • description — описание версии
  • is_active — флаг активности

Пример:

Shortname: v1.0
Description: Первая версия бюджета 2024
Is active: ✓

Через API

Endpoint: POST /core/parameters/api/versions/

ViewSet: /src/planiqum/core/parameters/views.py: VersionViewSet

Запрос:

curl -X POST http://localhost:8000/core/parameters/api/versions/ \
  -H "Content-Type: application/json" \
  -d '{
    "name": "v1.0",
    "description": "Первая версия бюджета 2024"
  }'

Ответ:

{
  "id": 123,
  "name": "v1.0",
  "description": "Первая версия бюджета 2024",
  "level": 45,
  "is_active": true
}

Через Python API

from planiqum.core.parameters.models import Version
from planiqum.core.parameters.libs.backup import VersionHierarchy

# Способ 1: Прямое создание
version = Version.objects.create(
    shortname="v1.0",
    description="Первая версия бюджета 2024",
    is_active=True
)

# Способ 2: Через HierarchyItem (с явным указанием level)
from planiqum.core.hierarchy.models import Item as HierarchyItem

version_level = VersionHierarchy.get_version_level()
version = HierarchyItem.objects.create(
    level=version_level,
    shortname="v2.0",
    description="Вторая версия бюджета 2024"
)

Важно: При создании Version автоматически выполняются скрипты с триггером on_version_created (если они зарегистрированы).

Создание backup-параметров

Автоматическое создание

Backup-параметр создается автоматически при установке флага has_backup=True и синхронизации параметра.

Процесс:

  1. Установить флаг в админке:
  2. Открыть параметр в Django Admin
  3. Установить has_backup = True
  4. Сохранить параметр

  5. Синхронизировать структуру:

  6. Нажать кнопку "Sync" в админке
  7. Или вызвать parameter.sync() программно

  8. Результат:

  9. Создан параметр "{name} (backup)"
  10. Создана fact-таблица с dimension Version
  11. Структура мер скопирована

Программное создание

from planiqum.core.parameters.models import Parameter
from planiqum.core.parameters.scripts.backup import create_backup_parameter

# Получить основной параметр
parameter = Parameter.objects.get(key="sales_plan")

# Установить флаг
parameter.has_backup = True
parameter.save()

# Создать backup-параметр
backup_param = create_backup_parameter(parameter)

# Синхронизировать структуру (создать таблицы)
backup_param.sync()

Проверка существования backup-параметра

parameter = Parameter.objects.get(key="sales_plan")

# Получить backup-параметр (или None)
backup_param = parameter.get_backup_parameter()

if backup_param:
    print(f"Backup-параметр существует: {backup_param.shortname}")
else:
    print("Backup-параметр не создан")

Сохранение данных в версию

Функция backup

Функция: /src/planiqum/core/parameters/scripts/backup.py: backup

Назначение: Копирует данные из основных параметров в backup-параметры с привязкой к версии.

Параметры функции

def backup(settings, user=None):
    """
    Сохранение данных параметров в версию.

    :param settings: Словарь с настройками
        {
            "version": int,              # ID версии (обязательно)
            "parameters": [int, ...],    # Список ID параметров (обязательно)
            "filter": dict или None,     # Фильтр для отбора данных (опционально)
            "overwrite": bool            # Перезаписать существующие данные версии
        }
    :param user: Объект User, выполняющий операцию
    """

Использование фильтров при сохранении

Критически важно: Параметр filter определяет, какие именно данные будут сохранены в версию.

Примеры использования фильтров:

Пример 1: Сохранить все данные параметра

from planiqum.core.parameters.scripts.backup import backup
from planiqum.core.hierarchy.models import Item as HierarchyItem
from planiqum.core.parameters.models import Parameter

version = HierarchyItem.objects.get(shortname="v1.0")
parameter = Parameter.objects.get(key="sales_plan")

backup(
    settings={
        "version": version.id,
        "parameters": [parameter.id],
        "filter": None,  # Нет фильтра = все данные
        "overwrite": True
    },
    user=request.user
)

Пример 2: Сохранить только данные по определенному продукту

from planiqum.core.filters.libs.filter import Filter

# Получить уровень и элемент иерархии
product_level = Level.objects.get(key="product__product")
iphone_product = Item.objects.get(shortname="iPhone 15", level=product_level)

# Создать фильтр
filter_dict = {
    "level": product_level.id,
    "dimension": "product",
    "elements": [{"id": iphone_product.id, "level": product_level.id}]
}

backup(
    settings={
        "version": version.id,
        "parameters": [parameter.id],
        "filter": [filter_dict],  # Только данные по iPhone 15
        "overwrite": True
    },
    user=request.user
)

Пример 3: Сохранить данные за определенный период

# Фильтр по датам (2024 год)
filter_dict = {
    "level": date_level.id,
    "dimension": "date",
    "elements": [
        {"id": item.id, "level": date_level.id}
        for item in Item.objects.filter(
            level=date_level,
            shortname__startswith="2024"
        )
    ]
}

backup(
    settings={
        "version": version.id,
        "parameters": [parameter.id],
        "filter": [filter_dict],  # Только 2024 год
        "overwrite": True
    },
    user=request.user
)

Пример 4: Сохранить данные с комплексным фильтром

# Комбинированный фильтр: iPhone 15 за 2024 год в регионе Европа
filter_data = [
    {
        "level": product_level.id,
        "dimension": "product",
        "elements": [{"id": iphone_product.id, "level": product_level.id}]
    },
    {
        "level": date_level.id,
        "dimension": "date",
        "elements": [
            {"id": item.id, "level": date_level.id}
            for item in date_2024_items
        ]
    },
    {
        "level": region_level.id,
        "dimension": "region",
        "elements": [{"id": europe_region.id, "level": region_level.id}]
    }
]

backup(
    settings={
        "version": version.id,
        "parameters": [parameter.id],
        "filter": filter_data,  # Комплексный фильтр
        "overwrite": True
    },
    user=request.user
)

Алгоритм работы функции backup

  1. Валидация и подготовка:
  2. Проверка обязательных параметров (version, parameters)
  3. Получение объектов Version и Parameter из БД
  4. Парсинг фильтра через класс Filter
  5. Применение пользовательского фильтра (если user задан)

  6. Для каждого параметра:

a. Создание/получение backup-параметра:

backup_param = create_backup_parameter(source_param)

b. Создание ревизии (опционально):

if backup_param.supports_revisions():
    revision = Revision.objects.create(created_by=user, type=2)

c. Подготовка маппинга колонок:

# Dimensions: одинаковые имена (кроме version)
dimension_columns = [
    (dim.fact_field_name(), dim.fact_field_name())
    for dim in source_param.dimensions.all()
]

# Measures: маппинг по short_key
measure_columns = [
    (source_measure.fact_field_name(), backup_measure.fact_field_name())
    for source_measure, backup_measure in zip(...)
]

d. Определение unique_columns:

# Все dimension-колонки backup, включая version!
unique_columns = [
    dim.fact_field_name() 
    for dim in backup_param.dimensions.all()
]

e. Добавление version_id в default_values:

version_dim_field = get_version_dimension_field(backup_param)
default_values = {version_dim_field: version.id}

f. Вызов низкоуровневой функции переноса:

transfer_table_data(
    source_table=source_param.fact_table_name(),
    target_table=backup_param.fact_table_name(),
    columns=dimension_columns + measure_columns,
    source_filter=filter_,  # ← Фильтр применяется здесь!
    source_parameter=source_param,
    unique_columns=unique_columns,
    default_values=default_values,
    flush=True,
    revision=revision,
    user=user
)

  1. Результат:
  2. Данные скопированы в backup-таблицу
  3. К каждой записи добавлен version_id
  4. Фильтр определил, какие данные скопированы
  5. Старые данные этой версии удалены (flush=True)

Скрипт для пользователей

Скрипт: /src/planiqum/core/parameters/scripts/backup.py: save_data_to_backup

Декоратор: @pq_script(label="00.3.1 Версионность - сохранение данных в версию")

Использование через UI: - Доступен в меню "Routines" - Позволяет выбрать версию, параметры и фильтр через графический интерфейс - Вызывает функцию backup() с подготовленными параметрами

Восстановление данных из версии

Функция restore

Функция: /src/planiqum/core/parameters/scripts/backup.py: restore

Назначение: Восстанавливает данные из backup-параметров в основные параметры.

Параметры функции

def restore(settings, user=None):
    """
    Восстановление данных параметров из версии.

    :param settings: Словарь с настройками
        {
            "version": int или str,      # ID или shortname версии (обязательно)
            "parameters": [int, ...],    # Список ID параметров (обязательно)
            "filter": dict или None      # Фильтр для отбора данных (опционально)
        }
    :param user: Объект User, выполняющий операцию
    """

Использование фильтров при восстановлении

Критически важно: Параметр filter определяет, какие именно данные будут восстановлены из версии.

Важное отличие от backup: К переданному фильтру автоматически добавляется фильтр по версии!

# Переданный фильтр
user_filter = Filter(settings.get("filter"))

# Автоматически добавляется фильтр по версии
version_filter = Filter([{
    "level": version_level.id,
    "dimension": "version",
    "elements": [{"id": version.id, "level": version_level.id}]
}])

# Итоговый фильтр
final_filter = user_filter + version_filter  # Оба фильтра применяются!

Примеры использования фильтров:

Пример 1: Восстановить все данные версии

from planiqum.core.parameters.scripts.backup import restore

restore(
    settings={
        "version": "v1.0",  # Можно передать shortname
        "parameters": [parameter.id],
        "filter": None  # Нет дополнительного фильтра = все данные версии
    },
    user=request.user
)

Что произойдет: - Применится автоматический фильтр: version = "v1.0" - Будут восстановлены все данные этой версии

Пример 2: Восстановить только данные по определенному продукту

# Фильтр по продукту
filter_dict = [{
    "level": product_level.id,
    "dimension": "product",
    "elements": [{"id": iphone_product.id, "level": product_level.id}]
}]

restore(
    settings={
        "version": "v1.0",
        "parameters": [parameter.id],
        "filter": filter_dict  # Дополнительный фильтр
    },
    user=request.user
)

Что произойдет: - Применятся оба фильтра: version = "v1.0" AND product = "iPhone 15" - Будут восстановлены только данные по iPhone 15 из версии v1.0

Пример 3: Восстановить данные за период

# Фильтр по датам
filter_dict = [{
    "level": date_level.id,
    "dimension": "date",
    "elements": [{"id": item.id, "level": date_level.id} for item in q1_2024]
}]

restore(
    settings={
        "version": version_id,
        "parameters": [parameter.id],
        "filter": filter_dict
    },
    user=request.user
)

Что произойдет: - Применятся оба фильтра: version = ID AND date IN (Q1 2024) - Будут восстановлены только данные Q1 2024 из указанной версии

Алгоритм работы функции restore

  1. Валидация и подготовка:
  2. Проверка обязательных параметров
  3. Парсинг версии (по ID или shortname)
  4. Парсинг параметров
  5. Создание пользовательского фильтра

  6. Добавление version_filter:

    version_filter = __get_version_filter(version)
    filter_.add_filter(version_filter)  # ← Ключевой момент!
    

  7. Добавление пользовательского фильтра (если есть):

    if user:
        user_filter = user.profile.get_view_filter()
        filter_.add_filter(user_filter)
    

  8. Создание ревизии для восстановления:

    revision = Revision.objects.create(created_by=user, type=2)
    

  9. Использование FactManager:

    fm = FactManager(revision=revision, filter_=filter_, user=user)
    
    for target_param in parameters:
        # Получить backup-параметр
        source_param = target_param.get_backup_parameter()
    
        # Получить данные с применением фильтров
        target_facts = fm.get_facts(target_param, filter_=filter_)
        source_facts = fm.get_facts(source_param, filter_=filter_)
    
        # Вычислить изменения
        changes = get_facts_transfer_changes(source_facts, target_facts)
    
        # Добавить изменения
        for c in changes:
            fm.add_changes(c)
    
    # Сохранить все изменения
    fm.write(flush=False)
    

  10. Результат:

  11. Данные из backup-таблицы (с фильтром по версии!) восстановлены в основную таблицу
  12. Применены дополнительные фильтры (если указаны)
  13. Создана ревизия с информацией об изменениях

Скрипт для пользователей

Скрипт: /src/planiqum/core/parameters/scripts/backup.py: restore_data_from_backup

Декоратор: @pq_script(label="00.3.2 Версионность - восстановление данных из версии")

Использование через UI: - Доступен в меню "Routines" - Позволяет выбрать версию, параметры и фильтр через графический интерфейс - Вызывает функцию restore() с подготовленными параметрами

Внутренние механизмы

Функция transfer_table_data

Функция: /src/planiqum/core/libs/db.py: transfer_table_data

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

Параметры:

def transfer_table_data(
    source_table: Union[str, Table],          # Исходная таблица
    target_table: Union[str, Table],          # Целевая таблица
    columns: List[Union[str, Tuple[str, str]]],  # Список колонок для переноса
    source_filter: Optional[Filter] = None,   # Фильтр для source
    source_parameter: Optional[Parameter] = None,  # Параметр source
    unique_columns: Optional[List[str]] = None,    # Колонки для ON CONFLICT
    default_values: Optional[Dict[str, Any]] = None,  # Значения по умолчанию
    flush: bool = False,                      # Удалить старые данные
    flush_filter: Optional[Filter] = None,    # Фильтр для flush
    flush_parameter: Optional[Parameter] = None,   # Параметр для flush
    revision: Optional[Revision] = None,      # Ревизия (опционально)
    user: Optional[User] = None               # Пользователь
) -> None:

Алгоритм:

  1. Нормализация параметров:
  2. Преобразование columns в пары (source_col, target_col)
  3. Нормализация unique_columns

  4. Создание SELECT запроса:

    select_query = Query.from_(source_table).select(*source_cols)
    
    # Применение фильтра
    if source_filter:
        select_query = apply_to_query(select_query, source_filter, source_parameter)
    

  5. Создание временной таблицы:

    temp_table = create_table_as(query=select_query, temporary=True)
    

  6. Добавление default_values:

    for col, value in default_values.items():
        execute_query(f"ALTER TABLE {temp_table} ADD COLUMN {col} BIGINT")
        execute_query(f"UPDATE {temp_table} SET {col} = {value}")
    

  7. Создание индекса:

    execute_query(f"CREATE INDEX ON {temp_table} ({', '.join(unique_cols)})")
    

  8. Flush (удаление старых данных):

    if flush:
        # Удалить из target записи, которых нет в temp_table
        DELETE FROM target_table
        WHERE NOT EXISTS (
            SELECT 1 FROM temp_table
            WHERE target.col1 = temp.col1 AND target.col2 = temp.col2 ...
        )
    

  9. INSERT с ON CONFLICT:

    INSERT INTO target_table (col1, col2, ...)
    SELECT col1, col2, ... FROM temp_table
    ON CONFLICT (unique_col1, unique_col2, ...)
    DO UPDATE SET
        measure1 = EXCLUDED.measure1,
        measure2 = EXCLUDED.measure2
    

  10. Очистка:

    execute_query(f"DROP TABLE IF EXISTS {temp_table}")
    

Функция get_facts_transfer_changes

Функция: /src/planiqum/core/parameters/scripts/backup.py: get_facts_transfer_changes

Назначение: Вычисляет разницу между двумя DataFrames (source и target) и формирует список изменений для FactManager.

Алгоритм:

  1. Сравнивает DataFrames по ключевым полям (dimensions)
  2. Находит новые записи (есть в source, нет в target)
  3. Находит измененные записи (значения мер отличаются)
  4. Формирует список изменений в формате FactManager

Возвращает: Список объектов изменений для FactManager.add_changes()

Примеры использования

Пример 1: Создание точки восстановления перед массовой корректировкой

from planiqum.core.parameters.models import Parameter, Version
from planiqum.core.parameters.scripts.backup import backup, restore

# 1. Создать версию
version_before = Version.objects.create(
    shortname="before_correction_2024_03_15",
    description="Состояние перед корректировкой продаж"
)

# 2. Сохранить текущее состояние
parameter = Parameter.objects.get(key="sales_plan")
backup(
    settings={
        "version": version_before.id,
        "parameters": [parameter.id],
        "filter": None,  # Все данные
        "overwrite": True
    },
    user=request.user
)

# 3. Выполнить корректировку
# ... массовое изменение данных ...

# 4. Если что-то пошло не так — откатить
restore(
    settings={
        "version": version_before.id,
        "parameters": [parameter.id],
        "filter": None
    },
    user=request.user
)

Пример 2: Сравнение сценариев

# Сохранить оптимистичный сценарий
version_optimistic = Version.objects.create(shortname="scenario_optimistic")
backup(
    settings={
        "version": version_optimistic.id,
        "parameters": [parameter.id],
        "filter": None
    },
    user=user
)

# Изменить данные для пессимистичного сценария
# ... изменения ...

# Сохранить пессимистичный сценарий
version_pessimistic = Version.objects.create(shortname="scenario_pessimistic")
backup(
    settings={
        "version": version_pessimistic.id,
        "parameters": [parameter.id],
        "filter": None
    },
    user=user
)

# Переключаться между сценариями:
restore(settings={"version": "scenario_optimistic", "parameters": [parameter.id]}, user=user)
restore(settings={"version": "scenario_pessimistic", "parameters": [parameter.id]}, user=user)

Пример 3: Версионирование по продуктам

from planiqum.core.hierarchy.models import Level, Item

# Получить продукты
product_level = Level.objects.get(key="product__product")
iphone = Item.objects.get(shortname="iPhone 15", level=product_level)
samsung = Item.objects.get(shortname="Samsung S24", level=product_level)

version = Version.objects.create(shortname="products_snapshot_2024_q1")

# Сохранить только данные по iPhone
backup(
    settings={
        "version": version.id,
        "parameters": [parameter.id],
        "filter": [{
            "level": product_level.id,
            "dimension": "product",
            "elements": [{"id": iphone.id, "level": product_level.id}]
        }]
    },
    user=user
)

# Сохранить только данные по Samsung (в ту же версию!)
backup(
    settings={
        "version": version.id,
        "parameters": [parameter.id],
        "filter": [{
            "level": product_level.id,
            "dimension": "product",
            "elements": [{"id": samsung.id, "level": product_level.id}]
        }]
    },
    user=user
)

# Теперь в версии есть данные по обоим продуктам
# Восстановить только iPhone:
restore(
    settings={
        "version": version.id,
        "parameters": [parameter.id],
        "filter": [{
            "level": product_level.id,
            "dimension": "product",
            "elements": [{"id": iphone.id, "level": product_level.id}]
        }]
    },
    user=user
)

API для работы с версиями

REST API эндпоинты

Список версий

GET /core/parameters/api/versions/

Параметры запроса: - shortname — фильтр по имени

Ответ:

[
  {
    "id": 1,
    "name": "v1.0",
    "description": "Первая версия",
    "is_active": true,
    "level": 45
  },
  {
    "id": 2,
    "name": "v2.0",
    "description": "Вторая версия",
    "is_active": false,
    "level": 45
  }
]

Создание версии

POST /core/parameters/api/versions/
Content-Type: application/json

{
  "name": "v3.0",
  "description": "Третья версия"
}

Получение версии

GET /core/parameters/api/versions/{id}/

Обновление версии

PATCH /core/parameters/api/versions/{id}/
Content-Type: application/json

{
  "description": "Обновленное описание",
  "is_active": false
}

Удаление версии

DELETE /core/parameters/api/versions/{id}/

Важно: При удалении версии не удаляются данные из backup-таблиц! Они остаются со ссылками на несуществующую версию. Рекомендуется сначала очистить данные версии, а потом удалять саму версию.

Python API

Работа с версиями

from planiqum.core.parameters.models import Version
from planiqum.core.hierarchy.models import Item as HierarchyItem

# Создать версию
version = Version.objects.create(shortname="v1.0", description="...")

# Получить версию по shortname
version = HierarchyItem.objects.get(
    level__key="version__version",
    shortname="v1.0"
)

# Получить все версии
versions = HierarchyItem.objects.filter(level__key="version__version")

# Активировать/деактивировать
version.is_active = True
version.save()

# Удалить версию
version.delete()

Работа с backup-параметрами

from planiqum.core.parameters.models import Parameter

# Проверить, есть ли backup
parameter = Parameter.objects.get(key="sales_plan")
has_backup = parameter.has_backup

# Получить backup-параметр
backup_param = parameter.get_backup_parameter()

# Получить основной параметр из backup
if backup_param.is_backup:
    regular_param = backup_param.get_regular_parameter()

# Имя backup-параметра
backup_name = parameter.get_backup_name()  # "Sales Plan (backup)"
backup_key = parameter.get_backup_key()    # "sales_plan_backup"

Лучшие практики

1. Именование версий

Рекомендации:

  • Используйте семантическое версионирование: v1.0, v2.0, v2.1
  • Или даты: 2024-Q1, 2024-03-15
  • Или описательные имена: before_correction, approved_budget
  • Избегайте спецсимволов и пробелов

Хорошо:

v1.0
2024-Q1-baseline
approved-budget-march
before-mass-correction

Плохо:

Версия 1 (черновик)!!!
test123
temp

2. Использование описаний

Всегда заполняйте поле description — это поможет понять назначение версии спустя время:

Version.objects.create(
    shortname="v1.0",
    description="Утвержденный бюджет 2024, версия после согласования с финансовым директором 15.03.2024"
)

3. Фильтры при сохранении

  • Сохраняйте только нужные данные — не создавайте огромные версии без необходимости
  • Используйте фильтры для версионирования по регионам, продуктам, периодам
  • Комбинируйте фильтры для точного отбора данных

4. Очистка старых версий

Периодически удаляйте неиспользуемые версии и их данные:

from planiqum.core.parameters.models import Parameter

# Удалить данные версии из backup-таблицы
backup_param = parameter.get_backup_parameter()
fm = FactManager()
facts = fm.get_facts(
    backup_param,
    filter_=Filter([{
        "level": version_level.id,
        "dimension": "version",
        "elements": [{"id": old_version.id, "level": version_level.id}]
    }])
)
# Удалить факты через FactManager или напрямую SQL

# Удалить версию
old_version.delete()

5. Тестирование перед восстановлением

Перед восстановлением критичных данных:

  1. Создайте точку восстановления текущего состояния
  2. Проверьте содержимое версии (выгрузите данные в Excel)
  3. Восстановите на тестовой среде
  4. Только потом восстанавливайте на продакшене

6. Мониторинг размера backup-таблиц

-- Размер backup-таблиц
SELECT 
    schemaname,
    tablename,
    pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
WHERE tablename LIKE 'fact__%_backup'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;

Ограничения и особенности

1. Backup-таблица растет с каждой версией

Каждая новая версия добавляет данные в backup-таблицу. Если создать 100 версий, размер таблицы увеличится в ~100 раз.

Решение: Регулярная очистка старых версий.

2. Version dimension всегда первая

В backup-параметре dimension Version добавляется первой. Это влияет на порядок колонок в таблице.

Важно: Не изменяйте порядок dimensions в backup-параметре вручную!

3. Синхронизация структуры

При изменении структуры основного параметра (добавление/удаление мер или dimensions) необходимо пересоздать backup-параметр:

parameter.save()
parameter.sync()  # Автоматически обновит backup-параметр

4. Производительность при большом объеме данных

Операции backup/restore для больших параметров (миллионы записей) могут занимать значительное время.

Оптимизация: - Используйте фильтры для уменьшения объема данных - Выполняйте операции в фоновом режиме (через Celery) - Создавайте индексы на dimension-колонках

5. Транзакции и откаты

Операции backup/restore выполняются в транзакциях. При ошибке изменения откатываются автоматически.

6. Ревизии при восстановлении

При восстановлении создается ревизия (если параметр поддерживает ревизии). Это позволяет отследить, кто и когда восстанавливал данные.

Связанные материалы