time - cover

В этой статье мы расскажем о том, как GitLab выявил и устранил «бутылочное горлышко» производительности в 15-летней функции Git, что повысило эффективность, обеспечив возможность применения более надёжных стратегий резервного копирования и снижения рисков.

Резервные копии репозиториев — важнейший компонент надёжной любой стратегии восстановления после сбоев. Однако с увеличением размеров репозиториев процесс создания надёжных бэкапов становится всё сложнее. Для резервного копирования нашего собственного репозитория Rails нам требовалось 48 часов. Это заставило нас искать невозможные компромиссы между частотой резервного копирования и производительностью системы. Мы хотели найти собственное внутреннее решение для наших клиентов и пользователей.

В конечном итоге, мы нашли источник проблемы в 15-летней функции Git со сложностью O(N²) и устранили его, внеся изменения в алгоритм, что экспоненциально уменьшило время резервного копирования. В результате мы обеспечили снижение затрат, уменьшение рисков и возможность создания стратегий резервного копирования, которые хорошо масштабируются месте с нашей кодовой базой.

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

Резервное копирование в крупных масштабах

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

  • Длительные процессы бэкапа: создание резервной копии крупного репозитория может занимать до нескольких часов, из-за чего планирование регулярных бэкапов становится проблематичным.

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

  • Окна резервного копирования: для команд, чьи рабочие процессы происходят в режиме 24/7, такие длительные операции усложняют поиск подходящих окон технического обслуживания.

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

  • Состояния гонки: так как для создания бэкапа требуется много времени, репозиторий за этот период может сильно измениться, что потенциально может создать недопустимый бэкап или прервать процесс резервного копирования из-за отсутствия требуемых объектов.

Эти трудности могут препятствовать частоте или полноте резервного копирования, что неприемлемо при обеспечении защиты данных. Длительные окна бэкапов могут заставить клиентов искать обходные пути. Кто-то из них воспользуется внешним инструментарием, другие же снизят частоту резервного копирования, что потенциально может привести к несогласованности стратегий защиты данных в организациях.

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

Технические трудности

В основе функциональности резервного копирования репозиториев GitLab лежит команда git bundle create, создающая полный снэпшот репозитория, в том числе всех объектов и ссылок (веток и тэгов). Этот бандл используется как точка восстановления для воссоздания точной копии состояния репозитория.

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

Анализ первопричин

Чтобы выявить первопричину этого узкого места, мы проанализировали flame-график команды во время её выполнения.

Flame graph showing command during execution

На flame-графике показан путь выполнения кода программы через трассировку её стека. Каждая строка соответствует функции кода, а ширина строки определяет, сколько времени команда потратила на выполнение внутри этой функции.

При изучении flame-графика команды git bundle create, запущенной для репозитория с 10 тысячами ссылок, оказалось, что приблизительно 80% времени выполнения потребляла функция object_array_remove_duplicates(). Эта функция была добавлена в Git в коммите b2a6d1c686 («bundle: allow the same ref to be given more than once, 2009-01-17»).

Чтобы понять это изменение, важно знать, что git bundle create позволяет пользователем указывать, какие ссылки будут включены в бандл. Для полных бандлов репозиториев флаг --all упаковывает все ссылки.

Коммит решал проблему, возникавшую, когда пользователи указывали в командной строке дублирующиеся ссылки. Например, git bundle create main.bundle main main создаст бандл без правильной обработки дублированной ссылки на main. При развёртывании этого бандла в репозитории Git он может поломаться, потому что попытается выполнить запись одной и той же ссылки дважды. Код, позволяющий избежать дублирования, использует вложенные циклы for для итеративного обхода всех ссылок и выявления дубликатов. Этот алгоритм O(N²) стал серьёзным узким местом производительности в репозиториях с большим количеством ссылок, занимая большую долю времени обработки.

Решение: от O(N²) к эффективному mapping

Для устранения этой проблемы производительности мы внесли в Git upstream-исправление, заменяющее вложенные циклы структурой данных map. В map добавляется каждая ссылка, и это гарантирует, что для обработки останется только одна копия каждой ссылки.

Это изменение существенно повысило производительность git bundle create и обеспечило гораздо более высокую масштабируемость в репозиториях с большим количеством ссылок. Тестирование бенчмарка репозитория с 10 тысячами ссылок продемонстрировало повышение производительности в шесть раз.

Benchmark 1: bundle (refcount = 100000, revision = master)
  Time (mean ± σ): 	14.653 s ±  0.203 s	[User: 13.940 s, System: 0.762 s]
  Range (min … max):   14.237 s … 14.920 s	10 runs

Benchmark 2: bundle (refcount = 100000, revision = HEAD)
  Time (mean ± σ):  	2.394 s ±  0.023 s	[User: 1.684 s, System: 0.798 s]
  Range (min … max):	2.364 s …  2.425 s	10 runs

Summary
  bundle (refcount = 100000, revision = HEAD) ran
	6.12 ± 0.10 times faster than bundle (refcount = 100000, revision = master)

Патч был принят и смерджен в upstream Git. Внутри GitLab мы сразу выполнили обратное портирование исправления, чтобы наши клиенты могли воспользоваться им, не ожидая следующего релиза Git.

Результат: значительное снижение времени создания бэкапов

Рост производительности, вызванный этим улучшением, вполне можно назвать революционным:

  • С 48 часов до 41 минуты: создание бэкапа самого нашего крупного репозитория (gitlab-org/gitlab) теперь занимает всего 1,4% от изначального времени.

  • Стабильная производительность: это улучшение надёжно масштабируется для репозиториев любого размера.

  • Эффективное использование ресурсов: мы существенно снизили нагрузку на серверы во время операций резервного копирования.

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

Что это значит для клиентов GitLab

Это улучшение приносит непосредственные и осязаемые преимущества в планировании резервного копирования репозиториев и восстановления после аварий:

  • Более совершенные стратегии создания бэкапов

    • Энтерпрайз-команды могут выполнять ежедневные ночные операции резервного копирования, не влияющие на процессы разработки и не требующие долгих окон для создания бэкапов.

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

  • Повышение бесперебойности работы

    • Благодаря тому, что время создания бэкапов сократилось с дней до минут, организации существенно минимизировали их recovery point objective (RPO). Это приводит к снижению рисков для бизнеса — в случае аварий клиентам потенциально нужно будет восстанавливать работу за считанные часы, а не за дни.

  • Снижение эксплуатационных затрат

    • Сниженное потребление ресурсов серверов и уменьшение окон технического обслуживания.

    • Благодаря уменьшению окон резервного копирования снижаются траты вычислительных ресурсов, особенно в облачных средах, где увеличение времени обработки напрямую отражается в повышении счетов на оплату.

  • Инфраструктура, обеспечивающая возможность будущего роста

    • Рост объёмов репозиториев теперь не заставляет делать сложный выбор между частотой создания бэкапов и производительностью системы.

    • С расширением кодовой базы может параллельно масштабироваться и стратегия резервного копирования.

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

Начиная с релиза GitLab 18.0 все клиенты GitLab, вне зависимости от уровня лицензии, могут использовать эти преимущества для планирования и реализации своей стратегии резервного копирования. Дополнительных изменений в конфигурации не требуется.

Комментарии (27)


  1. nin-jin
    10.06.2025 07:31

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


    1. M_AJ
      10.06.2025 07:31

      Так вроде общеизвестно, что Торвальдс написал первую версию Git меньше чем за неделю.


  1. vadimr
    10.06.2025 07:31

    Не проще бэкапить диск виртуальной машины git? Кстати на файловой системе с cow это работает мгновенно.


    1. BadNickname
      10.06.2025 07:31

      Cнапшоты cow - это не бэкапы.


      1. vadimr
        10.06.2025 07:31

        Снапшоты – конечно, не бэкапы, но мгновенно скопированный файл с образом диска можно затем бэкапить в своё удовольствие.

        virsh suspend git
        cp --reflink /vm/git-disk.raw /vm/tmp/git-disk.raw
        virsh resume git
        tar -cvf - /vm/tmp/git-disk.raw > /dev/st0
        rm /vm/tmp/git-disk.raw

        Примерно так.

        Ну если очень хочется, то можно shutdown делать.


        1. 13werwolf13
          10.06.2025 07:31

          тогда уж `btrfs send | btrfs recive` ну или zfs кому как больше нравится, в случае проблем с основным сервером с такого "бекапа" можно загрузиться сразу а не долго восстанавливать.


          1. zatorax
            10.06.2025 07:31

            Плюс за zfs. Читаю и вижу сильных программистов но очень слабых админов


            1. BadNickname
              10.06.2025 07:31

              Админство вымирает как явление, увы.


        1. navion
          10.06.2025 07:31

          Простой сервиса без гарантии консистентности и без возможности развернуть бекап на другом инстансе.


          1. vadimr
            10.06.2025 07:31

            Бекап в данном случае – это виртуальная машина, как же без гарантии? Простой составляет десяток секунд.

            Авторы, со своей стороны, предлагают простой, исчисляемый часами.


            1. BadNickname
              10.06.2025 07:31

              Ох, давайте не будем погружаться в дебри дикой дичи.

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

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


              1. vadimr
                10.06.2025 07:31

                Битая база данных гораздо скорее приведёт к тыкве вместо логического бэкапа.


                1. BadNickname
                  10.06.2025 07:31

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


                  1. vadimr
                    10.06.2025 07:31

                    Если удастся узнать. А не так, что молча сделается пустой или полупустой бекап.

                    Конечно, по-хорошему надо разворачивать обратно и тестировать, но...


              1. falcon4fun
                10.06.2025 07:31

                Про application aware бэкапы вы видимо не слышали, да? :)

                Ну это раз. Два: диск успешно пристегивается к любой виртуалке или вся ВМка развворачивается в том же Виме методом Instant Recovery. Проверить работоспособность ВМки - примерно пару минут, не рекаверя ее физически :) Про всякие SureBackup и прочие штуки я уж промолчу.

                Как в целом и про то, что если ц вас там кривые метаданные и битое фс дерево - это уже ваши проблемы: не стоит дергать из розетки ни вмку, ни серв, ни айскази/фц сторадж. Вопрос битой ФС не решается 3-2-1 правилом как бэ в целом. Задача другая :DDD

                Ах да. Пре-бэкап и пост-бэкап скрипты забанили что ли уже? Хоть обпроверяй все возможные статусы всего и вся.


            1. navion
              10.06.2025 07:31

              У авторов даже до фикса бекап происходил онлайн, но с RPO в 48 часов из-за длительности процесса. Плюс у них работает репликация через кластер gitaly и бекап нужен на случай удаления базы стажером повреждения логической структуры базы.


              1. vadimr
                10.06.2025 07:31

                В статье написано совсем другое (раздел "Резервное копирование в крупных масштабах").


                1. navion
                  10.06.2025 07:31

                  Можно цитату? Я не вижу там фразы про недоступность сервиса пользователям.


                  1. vadimr
                    10.06.2025 07:31

                    А как вы интерпретируете это?

                    • Окна резервного копирования: для команд, чьи рабочие процессы происходят в режиме 24/7, такие длительные операции усложняют поиск подходящих окон технического обслуживания.


                    1. navion
                      10.06.2025 07:31

                      Админам трудно найти окно для обслуживания репозитория, так как всё время работает бекап.


        1. BadNickname
          10.06.2025 07:31

          Вот смотрю я на эту конструкцию, и мне сразу вспоминается анекдот про взвод солдат, танк, гусеницу и фею.


        1. aamonster
          10.06.2025 07:31

          Снэпшоты прекрасны для консистентного бэкапа без остановки мира, факт.


          1. AlexGluck
            10.06.2025 07:31

            Некоторый софт такого не позволяет(


            1. aamonster
              10.06.2025 07:31

              Ну да, но если можно остановить хоть на секунду для создания снэпшота – этого хватит. Куда лучше, чем ждать, пока всё сбэкапится.


  1. OldFisher
    10.06.2025 07:31

    Не сочтите занудством, но переход от O(N^2) даже к O(1) не станет экспоненциальным снижением сложности.


  1. domix32
    10.06.2025 07:31

    Убрали дедупликаю по строкам, добавили проверку в сете. Отличная история для accidentally quadratic


  1. aamonster
    10.06.2025 07:31

    функции Git со сложностью O(N²) и устранили его, внеся изменения в алгоритм, что экспоненциально уменьшило время резервного копирования

    Кажется, пресс-релиз доверили писать человеку, не знающему математики...