Как стать автором
Поиск
Написать публикацию
Обновить
570.65
Сбер
Технологии, меняющие мир

Укрощаем мажорные обновления: сценарий обновления системных данных каталога без лишней боли

Уровень сложностиСредний
Время на прочтение15 мин
Количество просмотров1.2K

В мире разработки и эксплуатации ПО мажорные обновления — это всегда стресс. Независимо от того, насколько хорошо вы тестируете изменения, всегда есть риск, что что-то пойдёт не так. Особенно это касается обновлений, которые затрагивают пользовательские данные. В какой-то момент мы задумались о том, как нам минимизировать риски и сделать обновления более предсказуемыми.

Меня зовут Кристина Демидович, я DevOps‑инженер в СберТехе, занимаюсь автоматизацией в команде СУБД Pangolin — это целевая СУБД в Сбере и не только. Я расскажу о нашем подходе к обновлению СУБД Pangolin, который позволил нам превратить часть мажорных обновлений в обновление данных системного каталога — что проще, удобнее и занимает вдвое меньше времени.

Надеюсь, наш опыт будет полезен тем, кто занимается автоматизацией и имеет дело с обновлением сложных систем.

Когда нужны мажорные обновления

Традиционно мажорное обновление предполагает полную миграцию данных. Как правило, это полный фарш: полный бэкап, инициализация новой БД и её настройка, подготовка конфигурационных файлов. Миграция с помощью pg_upgrade, после которого необходим запуск сборки новой статистики (vacuumdb). Перенос данных с помощью rsync, обновление версий расширений и так далее. Всё это необходимо выполнить единовременно. В общем, у мажорных обновлений есть ряд существенных недостатков:

  • они требуют много времени и ресурсов;

  • есть риски возникновения сбоев, которые могут привести к потере информации, ввиду того, что мы работаем с пользовательскими данными;

  • сложность откатов.

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

  • изменение мажорной версии базового PostgreSQL;

  • изменение логического или физического формата данных;

  • потеря обратной совместимости с предыдущими версиями функциональностей;

  • изменение системного каталога.

В первых трёх случаях обойтись без мажорных обновлений нельзя, и это обоснованно. А вот на изменение системного каталога мы посмотрели под другим углом. Делать мажорное обновление в этом сценарии неоправданно долго и ресурсозатратно, даже если изменения незначительны — меняются только системные данные, но не сами физические объекты системного каталога (таблицы, столбцы, индексы, ключи и т. д.). 

Нам надоело это терпеть — и мы дали жизнь новому способу обновления.

Работу разделили на два этапа:

  1. Разработка инструмента для обновления данных системного каталога.

  2. Автоматизация процесса обновления.

Как устроена работа инструмента

Подробно о том, как устроен этот инструмент, расскажет мой коллега Николай Литковец — он уже почти дописал свою статью на Хабр. А я расскажу вкратце и перейду к автоматизации обновления.

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

Механизм включает в себя добавление новых объектов в системный каталог и изменение существующих объектов. Всё начинается с создания дампа pg_catalog (это происходит до обновления), чтобы проверить согласованность исходных данных системных каталогов. С помощью дополнительной утилиты update_catalog_version меняется версия каталога. После этого каталоги до пользовательских табличных пространств переименовываются в соответствии с новой версией системного каталога.

Далее загружаем SQL-скрипты на master-хост, для всех баз данных, включая template0 и template1. Проходит это в две итерации. Сначала скрипты запускают с функцией ROLLBACK, чтобы проверить возможность их загрузки. Если тестовая загрузка прошла успешно, то скрипты запускают повторно без ROLLBACK.

После успешной загрузки всех SQL-скриптов во все БД создаётся повторный дамп pg_catalog для проверки консистентности обновлённых данных системного каталога.

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

В случае кластерной конфигурации на replica-хост данные доезжают с помощью репликации.

Как устроена работа скриптов автоматизации

Следующий шаг — автоматизация запуска. Мы реализовали её через набор ролей Ansible, которые выполняют различные задачи: установку, обновление и настройку компонентов продукта. Простота управления заключается в разделении ролей по функциональным областям и компонентам продукта.

Вот как выглядит структура нашего проекта:

installer
│
├── collections
│
├── files
│
├── group_vars
│
├── inventories
│
├── roles
│   ├── pangolin_auth_reencrypt    	# Роль для настройки инструмента перешифрования
│   ├── pangolin_backup_tools      	# Роль для инструмента резервного копирования
│   ├── pangolin_certs_rotate      	# Роль для настройки ротации сертификатов
│   ├── pangolin_checks            	# Роль с комплексом проверок перед установкой или обновлением
│   ├── pangolin_dbms              	# Роль для установки, обновления и настройки СУБД
│   ├── pangolin_manager           	# Роль для настройки инструмента кластеризации
│   ├── pangolin_pooler            	# Роль для настройки пулеров 
│   ├── configure       	        # Роль для конфигурирования СУБД
│   ├── clean       	            # Роль для очистки стенда  
│   ├── common       	            # Роль с общими функциями 
│   ├── recovery       	            # Роль для восстановления
│   └── и тд.
│
├── templates
│
├── playbook_install.yaml     		# Плейбук для установки продукта
├── playbook_minor_update.yaml		# Плейбук для минорных обновлений
├── playbook_major_update.yaml		# Плейбук для мажорных обновлений
└── playbook_scouting.yaml    		# Плейбук для проверки готовности стенда к обновлению  

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

installer
│
├── roles
│   ├── pangolin_dbms 
│   │    ├── tasks
│   │    │    ├── backup.yml
│   │    │    ├── install.yml
│   │    │    ├── main.yml
│   │    │    ├── revert_minor.yml
│   │    │    ├── revert_major.yml
│   │    │    ├── update_major.yml
│   │    │    ├── update_minor.yml
│   │    │    └── и тд.

При подготовке новой модели обновления нам было важно следующее:

  • не затратить много времени на реализацию;

  • сократить время обновления;

  • обеспечить процесс восстановления;

  • сократить требования к процессу обновления (х2 места для бэкапа).

Мы хотели не радикально поменять механизм скриптов, а интегрировать в него новые процессы. Внедрение нового процесса в существующий механизм минорного обновления позволило ускорить разработку и тестирование за счёт того, что он был успешно протестирован и внедрён ранее. Такое обновление мы назвали обновлением данных системного каталога.

Сейчас наши плейбуки работают преимущественно с монолитными системами, хотя у нас есть планы по переходу на компонентную архитектуру. Но это уже другая история. А пока рассмотрим исходный плейбук для минорного обновления, чтобы лучше понять, как оно работает.

####################################################   standalone  #####################################################
- hosts: master
  roles:
    - { role: pangolin_checks }
    - { role: pangolin_license,            operation_type: install }
    - { role: pangolin_auth_reencrypt,     operation_type: install_and_update }
    - { role: pangolin_certs_rotate,       operation_type: install_and_update }
    - { role: pangolin_security_utilities, operation_type: install_and_update }
    - { role: pangolin_manager,            operation_type: update }
    - { role: pangolin_dbms,               operation_type: update_minor }
    - { role: pangolin_pooler,             operation_type: update }
    - { role: pangolin_diagnostic_tool,    operation_type: install_and_update }
    - { role: finally,                     operation_type: switch_to_original_configs }
    - { role: configure,                   operation_type: configure_for_update }
    - { role: finally,                     operation_type: finish_update }
    - { role: pangolin_backup_tools,       operation_type: install_and_update }
  tags: standalone  

######################################################   cluster  ######################################################
- hosts: master:replica:arbiter
  roles:
    - { role: pangolin_checks,             when: not update_errors.aggregate }
    - { role: pangolin_license,            when: not update_errors.aggregate,                       operation_type: install }
    - { role: pangolin_auth_reencrypt,     when: not update_errors.aggregate,                       operation_type: install_and_update }
    - { role: pangolin_certs_rotate,       when: not update_errors.aggregate,                       operation_type: install_and_update }
    - { role: pangolin_security_utilities, when: not update_errors.aggregate,                       operation_type: install_and_update }
    - { role: common,                                                                               operation_type: update_errors_sync_to_hosts }
  tags: cluster
- hosts: master:replica
  serial: 1
  roles:
    - { role: common,                                                                               operation_type: update_errors_sync_to_hosts }
    - { role: pangolin_manager,            when: patroni and not update_errors.aggregate,           operation_type: update }
  tags: cluster
- hosts: replica:master
  serial: 1
  roles:
    - { role: common,                                                                               operation_type: update_errors_sync_to_hosts }
    - { role: pangolin_dbms,               when: not update_errors.aggregate,                       operation_type: update_minor }
    - { role: common,                                                                               operation_type: update_errors_sync_to_hosts }
    - { role: pangolin_pooler,             when: pgbouncer and not update_errors.aggregate,         operation_type: update }
  tags: cluster
- hosts: master:replica
  roles:
    - { role: common,                                                                               operation_type: update_errors_sync_to_hosts }
    - { role: pangolin_diagnostic_tool,    when: not update_errors.aggregate,                       operation_type: install_and_update }
    - { role: common,                                                                               operation_type: update_errors_sync_to_hosts }
    - { role: finally,                     when: not update_errors.aggregate,                       operation_type: switch_to_original_configs }
    - { role: common,                                                                               operation_type: update_errors_sync_to_hosts }
  tags: cluster
- hosts: master:replica:arbiter
  roles:
    - { role: common,                                                                               operation_type: update_errors_sync_to_hosts }
    - { role: configure,                   when: not update_errors.aggregate,                       operation_type: configure_for_update }
    - { role: common,                                                                               operation_type: update_errors_sync_to_hosts }
    - { role: finally,                     when: not update_errors.aggregate,                       operation_type: finish_update }
    - { role: common,                                                                               operation_type: update_errors_sync_to_hosts }
  tags: cluster
- hosts: master:replica
  roles:
    - { role: pangolin_backup_tools,       when: not update_errors.aggregate,                       operation_type: install_and_update }
    - { role: common,                                                                               operation_type: update_errors_sync_to_hosts }
  tags: cluster  

######################################################   recovery  #####################################################
- hosts: master:replica:arbiter
  roles:
    - { role: recovery, when: handle_update_errors and update_errors.aggregate, recovery_type: pangolin_checks }
    - { role: recovery, when: handle_update_errors and update_errors.aggregate, recovery_type: pangolin_backup_tools, vars: { revert_pgbackup: false } }
  tags: standalone,cluster
- hosts: master:replica:arbiter
  serial: 1
  roles:
    - { role: recovery, when: handle_update_errors and update_errors.aggregate, recovery_type: pangolin_manager }
  tags: standalone,cluster
- hosts: master
  roles:
    - { role: recovery, when: handle_update_errors and update_errors.aggregate, recovery_type: pangolin_dbms }
    - { role: recovery, when: handle_update_errors and update_errors.aggregate, recovery_type: pangolin_pooler }
  tags: standalone,cluster
- hosts: replica
  roles:
    - { role: recovery, when: handle_update_errors and update_errors.aggregate, recovery_type: pangolin_dbms }
    - { role: recovery, when: handle_update_errors and update_errors.aggregate, recovery_type: pangolin_pooler }
  tags: cluster
- hosts: replica:master:arbiter
  roles:
    - { role: recovery, when: handle_update_errors and update_errors.aggregate, recovery_type: pangolin_security_utilities }
    - { role: recovery, when: handle_update_errors and update_errors.aggregate, recovery_type: pangolin_diagnostic_tool }
    - { role: recovery, when: handle_update_errors and update_errors.aggregate, recovery_type: pangolin_auth_reencrypt }
    - { role: recovery, when: handle_update_errors and update_errors.aggregate, recovery_type: pangolin_certs_rotate }
    - { role: recovery, when: handle_update_errors and update_errors.aggregate, recovery_type: finally }
  tags: standalone,cluster
- hosts: master:replica
  roles:
    - { role: pangolin_backup_tools, operation_type: return_original_scripts }
  tags: standalone,cluster

Плейбук состоит из трёх основных разделов:

  • обновление standalone-архитектур;

  • обновление кластерных архитектур;

  • откат в случае возникновения проблем.

Сосредоточимся на обновлении СУБД Pangolin. В случае кластерной конфигурации мы обеспечиваем отсутствие простоя с помощью последовательного обновления хостов.

- hosts: replica:master
  serial: 1
  roles:

    - { role: common,                                                                               operation_type: update_errors_sync_to_hosts }
    - { role: pangolin_dbms,               when: not update_errors.aggregate,                       operation_type: update_minor }

При обновлении данных системного каталога БД нам требуется обеспечить полную недоступность СУБД для пользователей. Во время снятия дампов системных таблиц (например, pg_class, pg_type, pg_proc и других), которые содержат важную информацию о структуре данных, любые изменения этих таблиц могут привести к несоответствию между состоянием БД на момент начала обновления и по его окончании. Это может создать проблемы при восстановлении данных или привести к ошибкам в работе самой СУБД после обновления. Чтобы исключить эти риски, организован полный downtime — период, когда БД недоступна для всех операций записи и чтения со стороны пользователей.

Это ключевое отличие потребовало переработки плейбука, и теперь он выглядит так:

- hosts: master:replica
  roles:
    - { role: common,                                                                                     operation_type: update_errors_sync_to_hosts }
    - { role: pangolin_dbms,   when: pg_inplace_upgrade | d(false) and not update_errors.aggregate,       operation_type: update_minor_shutdown }
    - { role: common,                                                                                     operation_type: update_errors_sync_to_hosts }
  tags: cluster

- hosts: replica:master
  serial: 1
  roles:
    - { role: common,                                                                                     operation_type: update_errors_sync_to_hosts }
    - { role: pangolin_dbms,   when: not pg_inplace_upgrade | d(false) and not update_errors.aggregate,   operation_type: update_minor }
    - { role: common,                                                                                     operation_type: update_errors_sync_to_hosts }
    - { role: pangolin_pooler, when: pgbouncer and not update_errors.aggregate,                           operation_type: update }
  tags: cluster

Путь, по которому идёт обновление, определяется параметром pg_inplace_upgrade. Его значение вычисляется запуском скрипта inplace_upgrade.sh в режиме info. Запуск выполняется после всех проверок готовности стенда к обновлению в рамках роли pangolin_checks, причём одновременно на всех хостах. В коде это выглядит примерно так:

- name: execute utility with key info
  ansible.builtin.expect:
	chdir: "{{ pangolin_ansible_cache }}/pg_inplace_upgrade"
	command: "./inplace_upgrade.sh info \
              -n {{ current_pangolin_dbms_version }} \
              -N {{ version_components.pangolin_dbms }} \
              -s {{ pangolin_ansible_cache }}/pg_inplace_upgrade \
              -B {{ pg_inplace_upgrade_work_dir }}/pg_inplace_upgrade/backup \
              -d {{ _PGDATA | d(PGDATA) }} \
              -t {{ _PGHOME | d(PGHOME) }}/bin \
              -T {{ _PGHOME_CLIENT | d(PGHOME_CLIENT) }}/bin \
              -l {{ pg_inplace_upgrade_work_dir }}/pg_inplace_upgrade/log \
              -m {{ pg_inplace_upgrade_work_dir }}/pg_inplace_upgrade/dump \
              -p {{ ports.pg }} \
              -h {{ ansible_default_ipv4.address }} \
              -u {{ connect_user }} \
              -D \
              -b postgres \
              {% if inventory_hostname == 'replica' %}-r{% endif %} \
              {% if skip_test %}-k{% endif %}"
	responses:
    	(?im)password*: "{{ postgres_db_pass }}"
	timeout: 1000000
    environment:
    	- PG_LICENSE_PATH: "{{ license.path }}"
        - "{{ locale_lang }}"
    vars:
    	ansible_python_interpreter: '{{ python.postgresql_venv }}/bin/python3'
    register: status_work_pg_inplace_upgrade
    no_log: "{{ nolog }}"
    ignore_errors: true
    become_user: postgres

- name: save state execute utility
  ansible.builtin.set_fact:
  	pg_inplace_upgrade: "{{ (not status_work_pg_inplace_upgrade.rc) | ternary(true, false) }}"

Рассмотрим роль pangolin_dbms — именно здесь произошли изменения. Скрипты по обновлению и восстановлению данных системного каталога мы разместили в отдельных файлах — update_minor_shutdown.yml и revert_minor_shutdown.yml соответственно, чтобы не пересекаться с существующим процессом. Структура роли приобрела вид:

installer
│
├── roles
│   ├── pangolin_dbms 
│   │    ├── tasks
│   │    │    ├── backup.yml
│   │    │    ├── install.yml
│   │    │    ├── main.yml
│   │    │    ├── revert_minor_shutdown.yml
│   │    │    ├── revert_minor.yml
│   │    │    ├── revert_major.yml
│   │    │    ├── update_major.yml
│   │    │    ├── update_minor.yml
│   │    │    ├── update_minor_shutdown.yml
│   │    │    └── и тд.

Обновление

Схематично последовательность действий выглядит так:

Этап, отмеченный жёлтым цветом, играет важную роль. Он включает в себя:

  • бэкап системных файлов для возможности восстановления в случае ошибки во время работы скрипта inplace_upgrade.sh;

  • изменение версии системного каталога;

  • запуск узла БД;

  • снятие SQL-дампа исходного системного каталога для проверки целостности его системных данных;

  • обновление и добавление новых системных объектов;

  • снятие SQL-дампа обновлённого системного каталога для последующей проверки целостности его системных данных;

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

На этом этапе ключевой аспект — резервное копирование только системных файлов. Это отличается от полного резервного копирования всех данных, которое происходит при мажорном обновлении и может занимать длительное время на большой БД. 

Создание бэкапа определено первым шагом не случайно. На этом этапе обновления могут возникнуть сценарии, требующие последующего восстановления. В зависимости от кода состояния скрипта inplace_upgrade.sh, контролируется дальнейшая работа скриптов автоматизации. Рассмотрим возможные сценарии и их обработку:

  • Некорректное обновление без возможности автоматического восстановления (код: 1). Транзакция по изменению данных системного каталога была запущена, но при выполнении скрипта произошла критическая ошибка. Сообщение скриптов автоматизации:

    FAIL__В процессе обновления данных системного каталога возникли ошибки: {}. Процесс дальнейшего обновления остановлен. Автоматическое восстановление невозможно. Необходимо произвести анализ состояния стенда вручную и восстановить его к исходному состоянию или дообновить.__FAIL

  • Некорректное обновление с возможностью автоматического восстановления (код: 4). Транзакция по изменению данных системного каталога была запущена, но в дальнейшей работе скрипта произошла ошибка. Сообщение скриптов автоматизации:

    FAIL__В процессе обновления данных системного каталога возникли ошибки: {}. Процесс дальнейшего обновления остановлен. Будет запущено автоматическое восстановление.__FAIL

  • Некорректное обновление с возможностью автоматического восстановления (альтернативный сценарий) (код: 5). Транзакция по изменению данных системного каталога не была запущена, и данные в системном каталоге остались в исходном состоянии. Сообщение скриптов автоматизации:

    FAIL__В процессе обновления данных системного каталога возникли ошибки: {}. Процесс дальнейшего обновления остановлен. Будет запущено автоматическое восстановление.__FAIL

  • Корректное обновление (код: 0). Работа скрипта завершилась без ошибок. Сообщение скриптов автоматизации:

    INFO__Процесс обновления данных системного каталога завершился успешно.__INFO

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

Восстановление

Параметр pg_inplace_upgrade управляет тем, как будет выполняться восстановление. Мы организовали плейбук следующим образом:

- hosts: master:replica
  roles:
    - { role: recovery, when: pg_inplace_upgrade | d(false) and handle_update_errors and update_errors.aggregate, recovery_type: pangolin_dbms }
  tags: standalone,cluster

- hosts: master:replica:arbiter
  serial: 1
  roles:
    - { role: recovery, when: handle_update_errors and update_errors.aggregate, recovery_type: pangolin_manager }
  tags: standalone,cluster

- hosts: master
  roles:
    - { role: recovery, when: not pg_inplace_upgrade | d(false) and handle_update_errors and update_errors.aggregate, recovery_type: pangolin_dbms }
    - { role: recovery, when: handle_update_errors and update_errors.aggregate, recovery_type: pangolin_pooler }
  tags: standalone,cluster

- hosts: replica
  roles:
    - { role: recovery, when: not pg_inplace_upgrade | d(false) and handle_update_errors and update_errors.aggregate, recovery_type: pangolin_dbms }
    - { role: recovery, when: handle_update_errors and update_errors.aggregate, recovery_type: pangolin_pooler }
  tags: cluster

Посмотрим на процесс восстановления на схеме:

Как можно заметить, по существующим исходам работы inplace_upgrade.sh может быть два пути восстановления:

  • восстановление версии СУБД с запуском восстановления файлов системного каталога из бэкапа;

  • восстановление версии СУБД без запуска восстановления файлов системного каталога из бэкапа.

Восстановление версии СУБД с запуском восстановления файлов системного каталога из бэкапа предполагает возврат бинарных файлов к предыдущей версии СУБД, а также использование ранее созданной резервной копии данных для восстановления в исходное состояние данных системного каталога. Этот метод используется только если при обновлении были затронуты данные системного каталога. Восстановление из бэкапа происходит с помощью запуска скрипта inplace_upgrade.sh в режиме reset.

Так выглядит запуск:

- name: execute utility with key reset
  ansible.builtin.expect:
	chdir: "{{ pangolin_ansible_cache }}/pg_inplace_upgrade"
	command: "./inplace_upgrade.sh reset \
              -n {{ current_pangolin_dbms_version }} \
              -N {{ version_components.pangolin_dbms }} \
              -s {{ pangolin_ansible_cache }}/pg_inplace_upgrade \
              -B {{ pg_inplace_upgrade_work_dir }}/pg_inplace_upgrade/backup \
              -d {{ _PGDATA | d(PGDATA) }} \
              -t {{ _PGHOME | d(PGHOME) }}/bin \
              -T {{ _PGHOME_CLIENT | d(PGHOME_CLIENT) }}/bin \
              -l {{ pg_inplace_upgrade_work_dir }}/pg_inplace_upgrade/log \
              -m {{ pg_inplace_upgrade_work_dir }}/pg_inplace_upgrade/dump \
              -p {{ ports.pg }} \
              -h {{ ansible_default_ipv4.address }} \
              -u {{ connect_user }} \
              -D \
              -b postgres \
              {% if inventory_hostname == 'replica' %}-r{% endif %} \
              {% if skip_test %}-k{% endif %}"
	responses:
    	(?im)password*: "{{ postgres_db_pass }}"
	timeout: 1000000
    environment:
    	- PG_LICENSE_PATH: "{{ license.path }}"
        - "{{ locale_lang }}"
    vars:
    	ansible_python_interpreter: '{{ python.postgresql_venv }}/bin/python3'
    register: status_work_pg_inplace_upgrade
    no_log: "{{ nolog }}"
    ignore_errors: true
    become_user: postgres

И восстановление версии СУБД без запуска восстановления файлов из бэкапа. Если данные системного каталога остались нетронутыми, достаточно вернуть бинарные файлы СУБД к предыдущей версии, не нужно восстанавливать файлы из бэкапа.

Результаты. Что насчёт скорости обновления

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

  • Мажорное обновление. Общее время обновления всех компонентов продукта в конфигурации cluster объёмом 100 ГБ — 65 минут.

  • Минорное обновление. Общее время обновления всех компонентов продукта в конфигурации cluster объёмом 100 ГБ — 25 минут.

  • Обновление данных системного каталога. Общее время обновления всех компонентов продукта в конфигурации cluster объёмом 100 ГБ — 23 минуты.

Мы внедрили обновление данных системных каталогов в версии 6.4.0. В результате этого текущее состояние наших версий выглядит так: 6.1.0 — 6.2.0 — 6.3.0 — 6.4.0 — 6.5.0 — 6.6.0 — 7.1.0. Изменение минорной версии означает обновление данных системных каталогов, а изменение мажорной версии — мажорное обновление. Если бы мы не внедрили этот подход, то наше состояние могло бы быть таким: 6.1.0 — 6.2.0 — 6.3.0 — 7.1.0 — 8.1.0 — 9.1.0 — 10.1.0.

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

Буду рада услышать ваш опыт организации автоматизации в комментариях. Если у вас есть вопросы или предложения по темам для обсуждения, дайте знать!

Теги:
Хабы:
Всего голосов 20: ↑20 и ↓0+27
Комментарии2

Информация

Сайт
www.sber.ru
Дата регистрации
Дата основания
Численность
свыше 10 000 человек
Местоположение
Россия