C git'ом кстати тоже есть проблема, он хорошо подходит для сорцов, а вот что делать с гигабайтными текстурами или файлами модели по 100мб, или луашником/json'ом уровня размером под 20-30 мб текста? Тут либо держишь 2 cvs - одну для сорцов, вторую для контента, либо пишешь своё решение.
Большие файлы стало можно использовать после появления Git LFS.
Для совсем мелких проектов подходит почти всё, что угодно.
Проблемы начинаются когда увеличивается количество хотелок:
Хочется, чтобы в выводе сборки можно было узнать, какие тесты попадали и как?
Хочется иметь хоть какую-то статистику по тестам (тут с ностальгией вспоминаю TeamCity);
Хочется иметь разбивку по этам сборки, чтобы не нужно было искать ошибку в одном гигантском логе;
Хочется иметь нотификации авторам о падении тестов;
Хочется иметь интеграцию с системой контроля версий;
Хочется иметь возможность как-то управлять секретами;
Хочется иметь возможность делать цепочки сборок;
И т.д. и т.п.
При этом основные проблемы визуализации возникают в негативном сценарии: пока всё работает, оно никому не интересно. А вот возможность по выводу понять, что и где пошло не так, очень полезна.
Проблему безопасности можно частично решить через вынос общих запчастей в отдельную "доверенную" Jenkins Shared Library. Тогда на её код в src не будут распространяться ограничения песочницы, хотя на vars - всё ещё будут.
На счет плагинов я с Вами согланен: в Jenkins безумное количество legacy и очень разное качество кода от плагина к плагину.
Ну и отдельная проблема в том, что там модульность доведена до абсурда: обычно функционал затрагивает сразу несколько плагинов и не всегда очевидно, какой именно за какую часть фичи отвечает.
Если сборка совсем типовая, то всю эту сложность можно попытаться спрятать, но что-то мне подсказывает, что под капотом standardDeclarativePipelineTemplate творится жесть.
Ну и декларативный pipeline в Jenkins то еще поделие: это де-факто инструкция императивного pipeline, то есть всё равно нельзя сказать, что сделает pipeline не выполнив его.
Из-за этого, в частности:
вечно поломанная визуализация сборки;
объявления параметров и опций в pipeline влияет на следующую, а не на текущую сборку.
С моей точки зрения, SQL СУБД должна соответствовать хоть одному SQL-стандарту. Должен быть не только похожий синтаксис, но и поведение.
К примеру, запрос:
UPDATE foo SET a = b, b = a WHERE id = 42
должен внутри выражений брать значения исходного кортежа, то есть поменять значения полей a и bместами. В MySQL же в обе колонки попадёт исходное значение b.
Про особенное виденье уровней изоляции транзакций уже было выше.
Все эти особенности в MySQL документированы и переведены в категорию фичей, но от этого как-то не легче.
Вы не представляете масштаб проблемы: распил проекта на маленькие модули требует годы.
Даже если предположить, что проект волшебным образом распадётся на сотни маленьких слабо связанных модулей, то их всё равно нужно будет чем-то собирать.
К тому же сокращение времени на итерацию в CI делает рефакторинг заметно комфортнее.
Я так и не смог понять, как подружить IDE и Bazel...
Собственно из-за этого и пошли по пути, когда BUILD-файлы генерируются на базе исходного кода и go.mod-файлов, максимально повторяя go build-сборку.
На машинках разработчиков Bazel используется только для создания генерируемых .go-файлов, которые потом раскладываются в рабочей копии. В результате для большинства, кроме этого вызова генератора, Bazel никак не используется.
Эта схема оказалась на удивление удачной:
разработчикам не нужно воевать с Bazel;
IDE работает как работало;
генераторы получают возможность генерировать зависящие от компиляции данные (в нашем случае mock-и);
если нужно, разработчик может использовать Bazel как для сборки, так и для запроса зависимостей через bazel query.
40 минут это был средний результат инкрементальной сборки на 7-ми машинках в параллель. По нашим прикидкам, на одной машинке с холодным кэшем должно было быть часов 15. В основном это время уходило на линковку тестовых бинарей: каждый пакет с тестами собирается в отдельный исполняемый файл и на это уходит чудовищное количество времени и дискового пространства. На втором месте собственно исполнение тестов.
Об этом я писал здесь: https://habr.com/ru/company/joom/blog/718340/ Основная цель: сокращение времени сборки в несколько раз (до миграции среднее время на сборку было порядка 40 минут, после 12 минут). При этом удалось оторвать кучу самопальных костылей и, в целом, сборка стала гораздо надёжнее.
Но для параллельного запуска тесты должны быть сами к этому готовы. Если они, к примеру, используют общую базу данных на машине разработчика, то будут проблемы.
Ну и переход на Bazel довольно трудоёмок. Самые вкусные плюшки в виде общего кэша и сборочной фермы, мы так и не смогли получить на машинках разработчиков: для этого нужен очень толстый канал до фермы.
В результате, к примеру, зачастую для проверки компилируемости проще и быстрее закинуть ветку на CI.
В нашем случае просто проверка "а код вообще компилируется?" занимала более получаса. Мы уже успели навернуть распараллеливание тестов, инкрементальный запуск тестов в зависимости от изменённых тестов, распил самих тестов и время запуска на CI всё еще было очень печальным.
Вопрос в этой ситуации был в том, развивать собственный инструментарий или заменить его на что-то готовое. После некоторого исследования пришли к выводу, что затащить Bazel будет более разумным.
Я изначально то же ждал от go.work совершенно другого поведения: он меня интересовал в контексте работы с несколькими go.mod в рамках монорепозитория.
Но он сделан для другого:
решение проблемы с IDE, которые используют gopls для интеграции, например VS Code (в этом случае при работе с несколькими go.mod надо открывать по одному экземпляру IDE на каждый go.mod);
решение проблемы работы с несколькими связанным go.mod в разных репозиториях.
Если для Вас эти сценарии не актуальны, то эта фича действительно выглядит предельно странно.
В go.work явно перечисляются используемые go.mod-ы. Если директория с go.mod там не указана, то GoLang будет её игнорировать и запустить из неё без ключа -workfile=off не получится. Будет ошибка вида:
go: no modules were found in the current workspace; see 'go help work'
Тут не задан самый главный вопрос: "Зачем нужна эта картина? Какая задача решается?"
Большие файлы стало можно использовать после появления Git LFS.
А проблему взаимодействия Git с не-программистами мы решали реализацией Subversion API поверх Git-репозитория: https://habr.com/ru/companies/vk/articles/241095/
Для совсем мелких проектов подходит почти всё, что угодно.
Проблемы начинаются когда увеличивается количество хотелок:
Хочется, чтобы в выводе сборки можно было узнать, какие тесты попадали и как?
Хочется иметь хоть какую-то статистику по тестам (тут с ностальгией вспоминаю TeamCity);
Хочется иметь разбивку по этам сборки, чтобы не нужно было искать ошибку в одном гигантском логе;
Хочется иметь нотификации авторам о падении тестов;
Хочется иметь интеграцию с системой контроля версий;
Хочется иметь возможность как-то управлять секретами;
Хочется иметь возможность делать цепочки сборок;
И т.д. и т.п.
При этом основные проблемы визуализации возникают в негативном сценарии: пока всё работает, оно никому не интересно. А вот возможность по выводу понять, что и где пошло не так, очень полезна.
Проблему безопасности можно частично решить через вынос общих запчастей в отдельную "доверенную" Jenkins Shared Library. Тогда на её код в
src
не будут распространяться ограничения песочницы, хотя наvars
- всё ещё будут.На счет плагинов я с Вами согланен: в Jenkins безумное количество legacy и очень разное качество кода от плагина к плагину.
Ну и отдельная проблема в том, что там модульность доведена до абсурда: обычно функционал затрагивает сразу несколько плагинов и не всегда очевидно, какой именно за какую часть фичи отвечает.
Если сборка совсем типовая, то всю эту сложность можно попытаться спрятать, но что-то мне подсказывает, что под капотом
standardDeclarativePipelineTemplate
творится жесть.Ну и декларативный pipeline в Jenkins то еще поделие: это де-факто инструкция императивного pipeline, то есть всё равно нельзя сказать, что сделает pipeline не выполнив его.
Из-за этого, в частности:
вечно поломанная визуализация сборки;
объявления параметров и опций в pipeline влияет на следующую, а не на текущую сборку.
Когда речь заходит про CI, я всё время вспоминаю цитату из Симпсонов:
> Check out The Willie World News! I reviewed the new tractors! They're all shite!
Выбор CI выглядит как поиск наименее плохого варианта :(
Я не знаю СУБД, которые полностью поддерживающая весь ansi.
Но UPDATE входит в Core SQL Features под номером E101-03. Всё-таки весь стандарт и его Core SQL часть несколько разные вещи :)
С моей точки зрения, SQL СУБД должна соответствовать хоть одному SQL-стандарту.
Должен быть не только похожий синтаксис, но и поведение.
К примеру, запрос:
должен внутри выражений брать значения исходного кортежа, то есть поменять значения полей
a
иb
местами. В MySQL же в обе колонки попадёт исходное значениеb
.Про особенное виденье уровней изоляции транзакций уже было выше.
Все эти особенности в MySQL документированы и переведены в категорию фичей, но от этого как-то не легче.
MySQL - это минное поле. У неё куча очень странных особенностей, которые могут вставить нож в спину в самый неожиданный момент.
Из особо прекрасного: https://www.percona.com/blog/what-if-mysqls-repeatable-reads-cause-you-to-lose-money/
Ну и, строго говоря, MySQL не является SQL СУБД.
Вы не представляете масштаб проблемы: распил проекта на маленькие модули требует годы.
Даже если предположить, что проект волшебным образом распадётся на сотни маленьких слабо связанных модулей, то их всё равно нужно будет чем-то собирать.
К тому же сокращение времени на итерацию в CI делает рефакторинг заметно комфортнее.
Распилить проект и уменьшить связанность кода это благая цель.
Но в данном случае:
уменьшение связанности никак не конфликтует с оптимизацией инструментария сборки;
уменьшение связанности кода никак нельзя назвать "простым" процессом.
"Монолит" это что?
Я так и не смог понять, как подружить IDE и Bazel...
Собственно из-за этого и пошли по пути, когда BUILD-файлы генерируются на базе исходного кода и go.mod-файлов, максимально повторяя
go build
-сборку.На машинках разработчиков Bazel используется только для создания генерируемых .go-файлов, которые потом раскладываются в рабочей копии. В результате для большинства, кроме этого вызова генератора, Bazel никак не используется.
Эта схема оказалась на удивление удачной:
разработчикам не нужно воевать с Bazel;
IDE работает как работало;
генераторы получают возможность генерировать зависящие от компиляции данные (в нашем случае mock-и);
если нужно, разработчик может использовать Bazel как для сборки, так и для запроса зависимостей через
bazel query
.А вот тезис с поддельным GOROOT я не понял...
40 минут это был средний результат инкрементальной сборки на 7-ми машинках в параллель. По нашим прикидкам, на одной машинке с холодным кэшем должно было быть часов 15.
В основном это время уходило на линковку тестовых бинарей: каждый пакет с тестами собирается в отдельный исполняемый файл и на это уходит чудовищное количество времени и дискового пространства.
На втором месте собственно исполнение тестов.
Об этом я писал здесь: https://habr.com/ru/company/joom/blog/718340/
Основная цель: сокращение времени сборки в несколько раз (до миграции среднее время на сборку было порядка 40 минут, после 12 минут).
При этом удалось оторвать кучу самопальных костылей и, в целом, сборка стала гораздо надёжнее.
Гранулярность Bazel-а при сборке Go-проектов - пакет.
То есть, если в пакете поменялся один файл, то пересобрать придётся весь пакет и, скорее всего, всё от чего он зависит.
Здесь, как мне кажется, Bazel может дать ускорение только за счет следующих моментов:
не нужно будет каждый раз пересобирать зависимости данного пакета;
если пакет не менялся, то его тесты могут быть закэшированны;
можно распилить тесты на https://bazel.build/reference/be/common-definitions#test.shard_count (грубо говоря, будет параллельно вызываться один и тот же исполняемый файл с разными аргументами, каждый раз выполняя часть тестов пакета);
Но для параллельного запуска тесты должны быть сами к этому готовы. Если они, к примеру, используют общую базу данных на машине разработчика, то будут проблемы.
Ну и переход на Bazel довольно трудоёмок. Самые вкусные плюшки в виде общего кэша и сборочной фермы, мы так и не смогли получить на машинках разработчиков: для этого нужен очень толстый канал до фермы.
В результате, к примеру, зачастую для проверки компилируемости проще и быстрее закинуть ветку на CI.
В нашем случае просто проверка "а код вообще компилируется?" занимала более получаса.
Мы уже успели навернуть распараллеливание тестов, инкрементальный запуск тестов в зависимости от изменённых тестов, распил самих тестов и время запуска на CI всё еще было очень печальным.
Вопрос в этой ситуации был в том, развивать собственный инструментарий или заменить его на что-то готовое. После некоторого исследования пришли к выводу, что затащить Bazel будет более разумным.
На сколько я понимаю, Bazel имеет следующие пенальти:
время анализа;
создание песочницы на каждый шаг;
запись и чтение из кэша. В итоге совсем чистая сборка с холодным кэшем действительно идёт чуть дольше. Но цифры сопоставимые.
Особый плюс Bazel-я проявляется при использовании общего кэша и фермы для сборки.
С кросс-компиляцией действительно всё плохо, но если не используется CGO, то тут спасает кросс-компиляция в самом Go.
Ну и интересно, когда именно Вы стокнулись с этими проблемами и какую ферму/общий кэш использовали?
Я изначально то же ждал от
go.work
совершенно другого поведения: он меня интересовал в контексте работы с несколькимиgo.mod
в рамках монорепозитория.Но он сделан для другого:
решение проблемы с IDE, которые используют
gopls
для интеграции, например VS Code (в этом случае при работе с несколькимиgo.mod
надо открывать по одному экземпляру IDE на каждыйgo.mod
);решение проблемы работы с несколькими связанным
go.mod
в разных репозиториях.Если для Вас эти сценарии не актуальны, то эта фича действительно выглядит предельно странно.
В
go.work
явно перечисляются используемыеgo.mod
-ы.Если директория с
go.mod
там не указана, то GoLang будет её игнорировать и запустить из неё без ключа-workfile=off
не получится. Будет ошибка вида:go: no modules were found in the current workspace; see 'go help work'