… Мы разрабатывали Gardenscapes. В нём всё ещё оставались следы старого Gardenscapes под Windows. Он даже был не Match-3, а Hidden Object. И никто даже и представить не мог высот, которых достигнет игра.
И вот в один прекрасный день…
Как всё начиналось
При обращении к репозиторию мы увидели следующее сообщение:
«This repository has been disabled. Access to this repository has been disabled by GitHub staff due to excessive use of resources, in violation of our Terms of Service. Please contact support to restore access to this repository. Read here to learn more about decreasing the size of your repository.»
Как вы уже поняли, мы используем github в качестве хостинга репозиториев git. И вот, внезапно и без объявления войны, github заблокировал наш репозиторий за превышение максимально допустимого размера. Точная цифра у них на сайте не была приведена. На момент блокировки размер папки .git был равен примерно 25 Гб. (Примечание 2020: сейчас лимиты стали побольше, и на сайте github явно указано, что размер репозитория не должен превышать 100 Гб).
Как же мы сумели сделать такой большой репозиторий? Причина понятна: мы храним в нём бинарные файлы. Везде написано, что так делать не рекомендуется, но нам так гораздо проще. Мы хотим, чтобы игра запускалась из репозитория сразу, без дополнительных усилий. Поэтому мы коммитим в репозиторий графику и другие игровые ресурсы.
Но это ещё полбеды. Важный урок, который мы извлекли из всей этой истории: никогда
Борьба за историю
Итак, ни у кого ничего не работает. Мы сказали команде, что день придётся поработать локально, но не очень стараться, а то потом конфликты разгребать (все очень расстроились и сразу ушли пить чай). И стали думать, что делать. Понятно, что нужен новый репозиторий, но что туда закоммитить? Простой способ — текущее состояние всех веток. Но нам так не понравилось, потому что будет утрачена история изменений, всеми любимая команда git blame сломается, и всё пойдёт кувырком. Поэтому мы решили сделать так: стереть историю бинарных файлов, а историю текстовых файлов сохранить.
Шаг 1. Удаляем историю бинарных файлов
У нас была полная локальная копия репозитория. Первым делом мы нашли отличную утилиту BFG Repo-Cleaner. Она очень простая и одновременно очень быстрая, и название хорошее.
Примерный сценарий выполнения:
java -jar bfg.jar bfg --delete-files *.{pvrtc,webp,png,jpeg,fla,swl,swf,pbi,bin,mask,ods,ogv,ogg,ttf,mp4} path_to_repository
В параметрах — все расширения бинарных файлов, которые мы смогли придумать. Из всех на свете коммитов удалятся сведения о файлах с этими расширениями. Утилита умная и при удалении истории файла оставляет его самую последнюю версию. Вдобавок эта последняя версия будет включена в самый последний коммит ветки. Мы хотели ещё удалить историю exe- и dll-файлов, но утилита выдавала ошибку. Видимо, по каким-то причинам обработка в виде *.exe запрещена. При этом если явно указать файл, например, gardenscapes.exe, то всё работает. (Примечание 2020: возможно, баг уже исправлен).
Шаг 2. Сжимаем репозиторий
После выполнения первого шага размер репозитория всё ещё большой. Причина этому — особенности работы git. Мы удалили только ссылки на файлы, а сами файлы остались.
Чтобы файлы удалились физически, нужно выполнить команду git gc, а именно:
git reflog expire --expire=now --all
и потом:
git gc --prune=now --aggressive
Именно такая последовательность команд рекомендована автором утилиты. Вот gc действительно долго работает. Кроме того, при настройках репозитория по умолчанию клиенту git не хватает памяти, чтобы завершить операцию, и нужны некоторые пляски с бубном. (Примечание 2020: тогда у нас была 32-битная версия git. Скорее всего, в 64-битной версии этих проблем уже нет).
Шаг 3. Записываем коммиты в новый репозиторий
Это оказалось самой интересной частью квеста.
Чтобы понимать дальнейшее, нужно представлять, как работает git. Подробнее почитать про git можно много где, включая наш блог:
Итак, у нас локально есть очень-очень много коммитов, эти коммиты правильные, то есть без истории бинарных файлов. Казалось бы, достаточно выполнить git push и всё отработает само. Но нет!
Если выполнить просто команду git push -u master, то git бодро начинает процесс загрузки данных на сервер, но вылетает с ошибкой примерно на отметке в 2 Гб. Значит, так много коммитов за 1 раз залить не получится. Будем кушать слона по частям. Мы прикинули, что 2 000 коммитов наверняка поместятся в 2 Гб. Общий объём нашего репозитория тогда составлял примерно 20 000 коммитов, распределённых между 4 ветками: master-v101-v102-v103. (Примечание 2020: эх, юность! С тех пор всё стало куда серьёзнее. Коммитов в этом репозитории уже более 100 000, а релизных веток — несколько десятков. При этом мы всё ещё укладываемся в ограничения Github )
Первым делом считаем число коммитов в ветках при помощи команды:
git rev-list --count <branch-name>
К примеру, в ветке master оказалось примерно 10 000 коммитов. Теперь мы можем воспользоваться расширенным синтаксисом команды git push, а именно:
git push -u origin HEAD~8000:refs/origin/master
HEAD~8000:refs/origin/master — это так называемый refspec. Левая часть говорит, что нужно взять коммиты вплоть до коммита, отстоящего от HEAD на 8 000, то есть как раз примерно 2 000 коммитов. А правая часть — что нужно их запушить в удалённую ветку master. Здесь нужен именно полный путь к ветке refs/origin/master.
После этого ветки master пока ещё нет, и, например, git fetch скачать её не сможет. Что неудивительно — ведь коммита, который бы указывал на её HEAD, ещё не существует. Тем не менее, повторив команду git push HEAD~8000:refs/origin/master, мы увидели ответ, что эти коммиты на сервере уже есть, и, значит, работа всё-таки выполнена.
Далее мы подумали, что процесс понятен и оставшуюся часть работы можно поручить скрипту. Последний коммит будет очень большой, так как в него попадут все бинарные файлы. Поэтому на всякий случай последние 10 коммитов заливаем отдельно. Скрипт получился такой:
git push origin HEAD~6000:refs/origin/master
git push origin HEAD~5000:refs/origin/master
git push origin HEAD~4000:refs/origin/master
git push origin HEAD~3000:refs/origin/master
git push origin HEAD~2000:refs/origin/master
git push origin HEAD~1000:refs/origin/master
git push origin HEAD~10:refs/origin/master
git push origin master
git checkout v101
git push -u origin HEAD~1000:refs/origin/v101
git push origin HEAD~10:refs/origin/v101
git push origin v101
git checkout v102
… и т.д.
То есть мы последовательно записываем на сервер все наши ветки, по 2 000 коммитов за один push, и последние 10 коммитов отдельно.
Времени вся эта история заняла немало, и часы показывали уже ближе к 12 ночи. Так что скрипт мы оставили работать на ночь, произнесли полагающиеся молитвы Ктулху (Примечание 2020: тогда он был ещё относительно популярен) и пошли по домам.
Финал. Хэппи-энд
Утром, открыв репозиторий на сайте github, мы убедились, что скрипт отработал успешно и все коммиты и ветки на месте.
В итоге: размер репозитория (папка .git) уменьшен с 25 Гб до 7.5 Гб. При этом вся важная история коммитов — всё, кроме бинарных файлов — сохранена. Гейм-дизайнеры выпили больше чая, чем обычно. Программисты получили незабываемый экспириенс. И срочно начали думать, а как бы так сделать, чтобы не надо было коммитить исполняемый файл в репозиторий, но работать при этом было бы удобно.
scarab
Ставить под угрозу разработку крупного проекта путём отдачи репозитория сторонней компании, которая может всё без предупреждения отключить только потому, что левая пятка зачесалась — бесценно, чёрт побери.
Мне кажется, главный урок, который следовало/следует усвоить из этой истории — никогда не отдавать ничего критичного неконтролируемым тобой людям.
И свой репозиторий на своём сервере уж точно обошёлся бы дешевле, чем день простоя команды.
s_shestakov Автор
Максимально несовременная точка зрения в эпоху, когда «все крутится на Амазоне».
А свои репозитории на своих серверах за все эти годы точно обошлись бы сильно дороже, чем день простоя. Притом, что свои сервера совсем не гарантируют отсутствие простоев.
scarab
Ну да. Тогда как Ваша «максимально современная» точка зрения привела к дню простоя, причём неконтролируемого и это стало поводом для статьи на хабре.
Ну банально — разбирательства с гитхабом могли затянуться на неопределённый срок. Здесь на Хабре достаточно примеров, как гитхаб блокировал репозитории просто потому что «они так решили». А ещё у них может случиться любая авария, утечка данных или они просто могут закрыться.
При этом шансов отсудить у гитхаба фактическую стоимость простоя бизнеса, в общем-то, не существует. Это же касается и Амазона «на котором всё крутится» и много кого ещё.
При этом имеется вполне достаточно локальных инструментов — тот же Gitlab, Phabricator, Gitea и т.д., с которыми вы не будете ограничены ни смехотворными 100гб на репозиторий, сможете делать бэкапы по любой удобной вам схеме, можете держать хоть кластер dev-серверов.
А также вы сможете в любой момент проверить фактическую работоспособность этих бэкапов, а в случае сбоя — у вас будут ваши люди, которые будут чинить этот сбой, а не безликий баннер на сайте «у нас технические проблемы, но мы уже знаем и наши инженеры работают над этим».
novoselov
Кто мешает сделать зеркало на свои сервера?
GitHub берет на себя инфраструктуру и обслуживание, а бекапы никто не отменял.
s_shestakov Автор
А смысл? git устроен так, что по сути у каждого разработчика на компьютере полный бекап. Хостингов миллион, не понравился один — переедем на другой.
Все же сейчас облака — это стандарт де факто, строить свои датацентры желающих мало.
Ну и 100Гб на репозиторий — это вообще то много, git на такое на самом деле не особо рассчитан. То есть мы объективно работали неправильно, и этот случай помог нам пересмотреть свои подходы.
Vitaly83vvp
В том то и дело, все полагаются на облачные технологии, на их надёжность и забывают, что они, по сути, просто большое количество серверов, которые тоже могу ломаться.
Я, например, один из рабочих продуктов, разметил в Gilab, Github, Bitbucket и локальном репозитории. Это даёт мне возможность работать с продуктом из разных мест. Я просто читаю все репозитории и пишу тоже во все. Если один из ресурсов недоступен, то на него изменения прилетят в следующий раз. Во всех местах хранятся все ветки и все коммиты. В любой момент можно залить на очередной репозиторий и в нём окажется все, что есть в остальных.
На счёт бинарных данных, хорошо заметили. В проекте они тоже есть, но обновляются крайне редко. Хотя, урок для себя из статьи я вынес по данному моменту.
rg_software
Не знаете, к чему придраться — назовите противную точку зрения «несовременной». Я пришёл к диаметрально противоположному выводу: всё перетаскивать «на Амазон» в конечном итоге дороже, да и возиться с кучей поставщиков услуг надоедает — везде свои заморочки. А нужно многое: файловое хранилище, репозитории, билд-машина, хранилище документации, баг-трекер, какие-то собственные инструменты в виде веб-приложений, которые нужно расшарить на команду.
Поставил простой обычный NAS от Synology, держу всё это дело там и вообще ни о каких «планах», ограничениях, подписках и прочей ерунде не думаю.
s_shestakov Автор
Всю инфраструктуру для сотен разработчиков из десятков городов на NAS? И туда же еще и production с миллионами игроков давайте закинем. Нет уж, мы лучше повозимся с поставщиками.
Да, когда-то и у нас было что-то подобное. Просто для разных масштабов нужны разные решения.
rg_software
Аргумент вида «соломенное чучело»: поздравляю, achievement unlocked. Я не предлагал вам засовывать на NAS базу данных всех игроков.
У меня NAS для разработчиков, а для пользователей, разумеется, обычная production база данных в дата-центре. А вы попробуйте покритиковать исходную идею. Да, NAS для разработчиков из десятков городов. А в чём, собственно, проблема? Назовите хотя бы две штуки.
s_shestakov Автор
Я не буду ввязываться в дискуссию. Мне достаточно аргумента про «бутылочное горлышко». Отключили интернет или (надолго) электричество там, где стоит NAS — и все встало. Можно, конечно, ставить генераторы и резервные каналы связи. Только нужно ли? Мы хотим доступности всех сервисов для разработки на том же уровне, что и production для игроков. И не хотим заморачиваться с обеспечением этого уровня сами. Если вы хотите — ваше решение, каждому свое.
И при любых обстоятельствах, все наше добро на один NAS ну вот никак не поместится.
rg_software
Разумеется. Ведь достаточно обозвать оппонента «ретроградом», а потом ещё и приписать ему то, что он не говорил.
Ну, ваша статья ровно об этом: «отключили интернет или электричество» или «закрыли доступ к репозиторию» — одного уровня проблемы. А они случаются и на вашей стороне, на стороне поставщиков — это всё неизбежно, и сложно сказать, что чаще бывает.
Да, пожалуй, ваши объёмы превосходят мои фантазии. Ну что ж.
mickvav
Что-то мне подсказывает, что при их масштабах бедствия, обычный NAS уже тянет за собой необоснованно большие риски. И хочется уже делать как-то более по уму — хотя бы пару серверов, мастер-слейв с переключением, нормальный мониторинг этого добра, и прочие элементы собственной инфраструктуры. Не очень понимаю, что помешало это поднять.
s_shestakov Автор
Это же «что-то» должно вам подсказывать, что при наших масштабах бедствия «пара серверов» — это самое начало истории. У нас были сервера, их было гораздо больше, чем пара. Постепенно выпиливаем. В какой-то момент роста облака становятся и проще, и дешевле, и доступнее.
Серверная конечна по определению. И есть 3 бесконечные вещи — Вселенная, человеческая глупость, Амазон (С).
mickvav
На следующем витке вы считаете экономику амазона и начинаете задумываться, не пора ли строить свой дата-центр :). Пока годовой счет от амазона меньше зарплаты 10-ти инженеров — туда даже смотреть не нужно.
mickvav
Ну и кстати облако облаку рознь. Вы можете там инстансы гонять и по сути это будет почти своя инфраструктура, за которую вы «всего лишь» раза в два переплачиваете, в обмен на гибкость и отсутствие головняка с физическим масштабированием. А можете платить за «сервис» — когда в расчете на гигабайт/гигабит/гигафлопс вы переплачиваете раз в 20, но еще с вас снимают головняк по логическому масштабированию, безопасности и обеспечивают какой-то уровень SLA. В конечном итоге все сводится к тому, кто заплатит инженерам за чертову магию :)
s_shestakov Автор
Мы делаем и так и так. Иногда «гоняем инстансы», иногда сервис под ключ.
Низконагруженное выгоднее брать под ключ. Там получится «раз в 20 дороже» — $100 вместо $5, но зато все из коробки.
Высоконагруженное обычно лучше запилить свое, получится и дешевле, и можем сделать более кастомно. Там еще очень распространенный кейс: пользователи проснулись, надо в 3 раза больше серверов. Уснули — в 3 раза меньше. Или какой-то экшен в игре, то же самое. В Амазоне мы можем так сделать, у себя пришлось бы держать запас под максимальную нагрузку.
aamonster
git же. «Отдача» на чужой сервер ничего особо не меняет – это просто +1 точка, где лежат исходники. И просто восстановить работу можно было очень быстро – разослав письмо с адресом локального репозитория и инструкцией по переключению.
scarab
Так тем более непонятно, почему это стало такой проблемой. Или они настолько слепо верили в непогрешимость облаков, что даже не озаботились поддержкой локального зеркала?
aamonster
А что о ней заботиться? У каждого разработчика своё локальное зеркало есть, залить на общедоступный сервер недолго.
Подождём ответа от s_shestakov, думаю, что просто решили воспользоваться моментом и привести репу в порядок.
scarab
Возможно :)
В этом случае ещё более доставляет заголовок статьи «была чуть не сорвана разработка Gardenscapes». Скандалы, интриги, расследования :)
А по факту просто мануал «как вычистить г… но из репы».
s_shestakov Автор
Так да ;) Понятно, что угрозы потери данных не было, git же. А заголовок — нарочно, чтобы завлечь любопытных. Теперь модно же так, а-ля желтая пресса.
aamonster
Сделайте игру про разработку игры. С событиями типа «репозиторий заблокирован, что делать? Поднять локальный или вычистить мусор?»
s_shestakov Автор
Каждый день в нее играем. Особенно увлекательное событие: «У нас баг, что делать? Добавить костылек или делаем рефакторинг?»
aamonster
Ну вот не в виде летсплея, как здесь, выложить, а с эффектом погружения :-)
s_shestakov Автор
Так приходите к нам на работу;) Еще и денег заплатят за успешную игру. Эффект погружения гарантирую.
klirichek
Может просто при таком размере каждый разработчик уже не держал у себя полный клон?
Можно же фетчем забрать только нужную ветку, минуя всю историю целиком.