go mod download тормозит: GOPROXY, GOSUMDB и кэш модулей
Go скачивает зависимости не так, как npm или pip. Между вашей машиной и GitHub почти всегда стоит прокси модулей, а каждую новую версию дополнительно сверяет база контрольных сумм. Когда понятно, кто за что отвечает, медленный go mod download обычно чинится парой переменных окружения.
Как Go получает модуль
Порядок источников задаёт переменная GOPROXY. Значение по умолчанию:
GOPROXY=https://proxy.golang.org,directСначала Go обращается к публичному прокси proxy.golang.org. Если модуля там нет, переходит к direct, то есть забирает код напрямую из системы контроля версий, чаще всего через git. Прокси отдаёт готовые zip-архивы конкретной версии, поэтому через него обычно быстрее, чем через клонирование репозитория.
Режиму direct нужен установленный git и доступ к каждому хостингу кода. Для модулей с собственными доменами, например golang.org/x/..., Go сначала запрашивает HTML-страницу по пути модуля и ищет в ней адрес настоящего репозитория. Это ещё один запрос, который может тормозить.
Вторая переменная, GOSUMDB, по умолчанию равна sum.golang.org. Это база контрольных сумм: Go проверяет, что скачанный архив совпадает с тем, что получили все остальные. Проверенные суммы записываются в go.sum, и дальше сверка идёт уже с этим файлом, без обращения к базе.
Текущие значения посмотрите одной командой:
go env GOPROXY GOSUMDB GOPRIVATE GOMODCACHE GOFLAGSНайдите, где теряется время
Флаг -x печатает каждый сетевой запрос и каждую команду git. Чтобы Go скачал всё заново, но основной кэш остался нетронутым, запустите загрузку с временным каталогом кэша:
GOMODCACHE=$(mktemp -d) go mod download -x 2>&1 | tee download.logВ логе появятся строки # get https://proxy.golang.org/..., а после ответа тот же адрес с кодом ответа и временем запроса в секундах. Сразу видно, какой модуль тормозит и с какого хоста он идёт. Если вместо # get вы видите вызовы git, модуль качается напрямую из репозитория, мимо прокси.
Без аргументов go mod download скачивает все зависимости основного модуля. Чтобы проверить один проблемный модуль, передайте его явно: go mod download -x github.com/acme/lib@v1.4.0. Флаг -json печатает по объекту на каждый модуль, и у неудачных загрузок там есть поле Error с текстом ошибки. Это удобно разбирать в скриптах CI.
Учтите, что go mod tidy учитывает все платформы и теги сборки, а также тесты, поэтому может скачать больше модулей, чем обычный go build. Если медленно идёт именно tidy, это не обязательно сеть: ему просто нужно больше.
Типичные ошибки
| Ошибка | Что значит | Что делать |
|---|---|---|
| dial tcp ...: i/o timeout | нет соединения с прокси или он не ответил вовремя | проверить сеть и DNS, добавить запасной источник в GOPROXY |
| 410 Gone от sum.golang.org | публичная база сумм не знает модуль, чаще всего он приватный | добавить путь модуля в GOPRIVATE |
| terminal prompts disabled | git не может спросить логин для закрытого репозитория | настроить для git доступ по SSH или через .netrc |
| SECURITY ERROR, checksum mismatch | архив не совпал с go.sum или с базой сумм | не отключать проверку, а выяснить причину: часто автор перевыпустил тег |
Ошибка с приватным модулем выглядит длинно, но узнаётся по ключевым словам:
verifying github.com/acme/private@v1.2.0: github.com/acme/private@v1.2.0: reading https://sum.golang.org/lookup/github.com/acme/private@v1.2.0: 410 Gone
server response: not found: github.com/acme/private@v1.2.0: invalid version: ...
fatal: could not read Username for 'https://github.com': terminal prompts disabledЗдесь не виновата сеть: публичная база сумм пыталась найти модуль, к которому у неё нет доступа.
Если в ошибке есть lookup proxy.golang.org и no such host, не работает разрешение имён, и начинать нужно с DNS, а не с настроек Go. Если в компании выход в интернет идёт через HTTP-прокси, Go берёт его из стандартных переменных HTTPS_PROXY и NO_PROXY. Без них запросы уходят напрямую и висят до таймаута.
Приватные модули: GOPRIVATE
Публичный прокси и база сумм не видят ваши внутренние репозитории и не должны их видеть: сам запрос раскрывает путь модуля. Перечислите приватные префиксы в GOPRIVATE:
go env -w GOPRIVATE=github.com/acme,git.example.comGOPRIVATE служит значением по умолчанию для двух других переменных. GONOPROXY определяет, какие модули качать напрямую, минуя прокси. GONOSUMDB определяет, какие модули не сверять с публичной базой. Обычно хватает одной GOPRIVATE. Отдельно две другие задают, когда внутренний прокси умеет отдавать приватные модули и ходить к нему всё же нужно.
Чтобы git не спрашивал пароль для закрытых репозиториев на HTTPS, переключите нужный хост на SSH:
git config --global url."git@github.com:acme/".insteadOf "https://github.com/acme/"Не ставьте GOSUMDB=off ради скорости. Сверка с базой нужна один раз на каждую новую версию модуля, дальше Go сверяется с go.sum. Отключив её, вы теряете защиту от подменённых версий во всех модулях сразу.
Настройка GOPROXY
Переменные удобно задавать через go env -w: значение сохраняется в пользовательском файле настроек Go, путь к нему покажет go env GOENV, и действует во всех терминалах. Вернуть значение по умолчанию: go env -u GOPROXY.
Элементы списка в GOPROXY разделяются запятой или вертикальной чертой, и это разные режимы:
- Запятая: Go переходит к следующему источнику только при ответе 404 или 410, то есть когда модуля в этом прокси нет.
- Вертикальная черта: Go переходит к следующему источнику при любой ошибке, включая таймауты и обрывы соединения.
# внутренний прокси, затем публичный, затем напрямую
go env -w GOPROXY=https://goproxy.example.com,https://proxy.golang.org,direct
# при сбое прокси сразу пробовать прямую загрузку
go env -w GOPROXY='https://proxy.golang.org|direct'Публичный прокси скачивает модули по требованию и кэширует их. Только что опубликованная версия может появиться в ответах @latest не сразу. Если нужна она, запросите её явно: go get example.com/lib@v1.5.0. По словам FAQ прокси, после явного запроса версия обычно доступна в течение минуты.
Второй вариант спасает, когда прокси временно недоступен. Но прямая загрузка через git обычно медленнее и требует доступа к каждому хостингу кода, поэтому это запасной путь, а не основной.
Кэш модулей
Скачанные модули лежат в GOMODCACHE, по умолчанию это $GOPATH/pkg/mod, обычно ~/go/pkg/mod. Версия, которая уже есть в кэше, из сети больше не запрашивается. Каталоги в кэше создаются только для чтения, поэтому обычный rm -rf спотыкается о права. Очищайте кэш командой go clean -modcache. Если каталог нужно удалять обычными средствами, например при очистке агента CI, добавьте -modcacherw в GOFLAGS: тогда новые каталоги будут создаваться с правом записи.
Кэш стоит сохранять между сборками:
- GitHub Actions:
actions/setup-goв свежих версиях по умолчанию кэширует модули и кэш сборки, ключ строится поgo.sum. - Docker: копируйте
go.modиgo.sumотдельно и скачивайте зависимости до копирования кода, с кэшем BuildKit.
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download
COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go build -o /app ./cmd/appПути /go/pkg/mod и /root/.cache/go-build верны для официального образа golang при сборке от root. Модули при таком подходе не попадают в слои образа, поэтому кэш подключается и к шагу сборки. Даже без BuildKit отдельный шаг с go.mod и go.sum полезен: пока эти файлы не менялись, Docker берёт слой с зависимостями из кэша.
Вендоринг: зависимости внутри репозитория
Команда go mod vendor копирует все нужные пакеты в каталог vendor проекта. Если в go.mod указана версия Go 1.14 или новее и каталог vendor существует, Go по умолчанию собирает проект из него и за модулями в сеть не ходит. Это удобно для CI в изолированной сети и для полностью воспроизводимых сборок, ценой размера репозитория.
Чтобы на время проигнорировать vendor и разрешить Go обновлять go.mod, используйте -mod=mod. Через GOFLAGS флаг применяется ко всем командам:
GOFLAGS=-mod=mod go build ./...
go mod vendor # обновите vendor после изменения зависимостейВ CI легко проверить, что каталог vendor не отстал от go.mod: выполните go mod vendor и затем git diff --exit-code. Если разница есть, сборка упадёт и подскажет закоммитить обновление.
Свой прокси и автозагрузка тулчейна
Если сборок много, поднимите кэширующий прокси модулей внутри сети. Распространённые варианты: открытый Athens, а также Go-репозитории в Nexus и Artifactory. Прокси один раз скачивает версию модуля и дальше раздаёт её всем машинам. Клиентам достаточно поставить его первым в GOPROXY.
Для машины без интернета есть простой приём: каталог cache/download внутри кэша модулей устроен так же, как прокси. Скопируйте его с машины, где всё уже скачано, и укажите путь к копии со схемой file://, например GOPROXY=file:///opt/gomod/download. Документация Go так же предлагает использовать и собственный кэш: GOPROXY=file://$(go env GOMODCACHE)/cache/download.
Ещё один источник долгих загрузок появился в Go 1.21. Если go.mod требует версию Go новее установленной, команда go сама скачивает нужный тулчейн через тот же GOPROXY. В логе это строка вида go: downloading go1.23.4 (linux/amd64). Запретить автозагрузку можно через GOTOOLCHAIN=local. Тогда при несовпадении версий сборка завершится ошибкой, и Go нужно обновить вручную.
Если не помогает
Когда -x показывает, что медленно отвечает именно proxy.golang.org или хостинг кода, а внутреннего прокси нет, проблема в маршруте до этих серверов. Если проблема в маршруте до репозитория, помогает защищённое подключение через ближайшую к нему локацию.
Коротко
- Проверьте
go env GOPROXY GOSUMDB GOPRIVATE. - Диагностируйте
go mod download -xс временнымGOMODCACHE. - Приватные пути пропишите в
GOPRIVATE, проверку сумм для публичных модулей не отключайте. - Для устойчивости к таймаутам прокси используйте
|вместо,вGOPROXY. - Сохраняйте кэш модулей в CI и в Docker через
--mount=type=cache. - Для команды поднимите Athens или Go-репозиторий в Nexus или Artifactory.
- Помните про автозагрузку тулчейна:
GOTOOLCHAIN=localеё отключает.