fix(stand): снят несуществующий бюджет памяти, нодам ClickHouse — 4 ГиБ
Зачем Стенд упирался в память ноды ClickHouse: пробник валился на CREATE TABLE ON CLUSTER, вместе с ним краснели make smoke и make smoke-guards. Причина не та, что предполагал #21: дело не в заводских кэшах, а в коробке на гигабайт. Около 550 МиБ RSS праздной ноды — страницы её собственного бинарника, и на работу оставалось около 350 МиБ, которые пробник добирал за сессию. Заодно выяснилось, откуда взялся предел 3,4 ГБ. Это была оценка расхода из спеки, посчитанная по стенду-предшественнику до первой сборки v2 и превращённая в жёсткий порог проверки. Порог стал критерием приёмки каждого этапа и дальше блокировал бы любой рост стенда на этапах 2-9. Что - ADR 0004: бюджета памяти у стенда нет, есть требование к машине — около 8 ГБ, доступных Docker. Ресурсный довод ADR 0001 отозван, сами решения в силе. - Нодам ClickHouse 4 ГиБ вместо гигабайта. Остальные лимиты не тронуты: ни один из них ни разу не сработал, а снять их скопом — то же изменение без свидетельств, каким они были выставлены. - Из make smoke убрана проверка суммарного потребления. Она мерила docker stats вместе со страничным кэшем, то есть отвечала на вопрос «сколько файлов стенд потрогал», и с появлением настоящих данных краснела бы на здоровом стенде. Вместе с ней убрана привязанная к её сообщению проверка docs-guards. - Взамен smoke спрашивает у Docker, не убивало ли ядро долгоживущий контейнер за память и не включалась ли политика перезапуска. Порога у проверки нет: убитый контейнер Docker поднимает сам, и без этого вопроса стенд отрапортует «всё хорошо» о ноде, которая умирала. - README и раздел «Ресурсный бюджет» спеки переписаны с предела на требование к машине; README объясняет менти, что такое «память, доступная Docker». Проверка make config-test; make up; make smoke — 25 из 25; make smoke-cluster — 8 из 8; make smoke-guards — 3 из 3, включая шаг «после восстановления стенд проходит make smoke», который падал 31 июля. На живом стенде с новой коробкой: max_server_memory_usage = 3,60 ГиБ, в журнале ноды «Lowered mark cache size to 2.00 GiB because the system has limited RAM». Семантика счётчиков Docker снята отдельными контейнерами: ручной restart оставляет RestartCount = 0, убийство за память даёт OOMKilled = true и растущий счётчик, убийство не за память OOMKilled не поднимает. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+30
-28
@@ -590,41 +590,43 @@ check_superset() {
|
||||
fi
|
||||
}
|
||||
|
||||
check_memory_budget() {
|
||||
local -a ids=()
|
||||
# Контейнер, убитый ядром за нехватку памяти, Docker поднимает сам, и через
|
||||
# полминуты его проверка состояния снова зелёная: о смерти она не расскажет.
|
||||
# Поэтому спрашиваем у Docker два факта — убивало ли контейнер ядро и включалась
|
||||
# ли политика перезапуска. Ручной `docker compose restart` счётчик не трогает,
|
||||
# так что документированный перезапуск нод проверку не роняет. Порога здесь
|
||||
# нет: это «да или нет», а не бюджет памяти (ADR 0004).
|
||||
check_containers_survived() {
|
||||
local container_id
|
||||
local service
|
||||
local total_mib
|
||||
local state
|
||||
local hurt=0
|
||||
|
||||
sleep 20
|
||||
for service in "${LONG_LIVED_SERVICES[@]}"; do
|
||||
container_id="$(compose ps --status running --quiet "$service" 2>/dev/null || true)"
|
||||
container_id="$(compose ps --all --quiet "$service" 2>/dev/null || true)"
|
||||
if [[ -z "$container_id" || "$container_id" == *$'\n'* ]]; then
|
||||
fail "не удалось получить работающий контейнер ${service} для измерения памяти"
|
||||
fail "не удалось получить контейнер ${service} для проверки перезапусков"
|
||||
return
|
||||
fi
|
||||
ids+=("$container_id")
|
||||
state="$(docker inspect --format '{{.State.OOMKilled}}/{{.RestartCount}}' "$container_id" 2>/dev/null || true)"
|
||||
case "$state" in
|
||||
false/0) ;;
|
||||
true/*)
|
||||
fail "контейнер ${service} был убит из-за нехватки памяти"
|
||||
hurt=1
|
||||
;;
|
||||
false/*)
|
||||
fail "контейнер ${service} перезапускался, счётчик Docker — ${state#*/}"
|
||||
hurt=1
|
||||
;;
|
||||
*)
|
||||
fail "Docker не рассказал о состоянии контейнера ${service}"
|
||||
hurt=1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
if ! total_mib="$(docker stats --no-stream --format '{{.MemUsage}}' "${ids[@]}" 2>/dev/null | awk -v expected="${#ids[@]}" '
|
||||
$1 ~ /GiB$/ {sub(/GiB$/, "", $1); total += $1 * 1024; next}
|
||||
$1 ~ /MiB$/ {sub(/MiB$/, "", $1); total += $1; next}
|
||||
$1 ~ /KiB$/ {sub(/KiB$/, "", $1); total += $1 / 1024; next}
|
||||
$1 ~ /GB$/ {sub(/GB$/, "", $1); total += $1 * 1000 / 1.048576; next}
|
||||
$1 ~ /MB$/ {sub(/MB$/, "", $1); total += $1 / 1.048576; next}
|
||||
$1 ~ /kB$/ {sub(/kB$/, "", $1); total += $1 / 1048.576; next}
|
||||
{invalid = 1}
|
||||
END {
|
||||
if (invalid || NR != expected) exit 1
|
||||
printf "%.1f", total
|
||||
}
|
||||
')"; then
|
||||
fail 'Docker не вернул полное измерение памяти стенда'
|
||||
return
|
||||
fi
|
||||
if awk -v total="$total_mib" 'BEGIN {exit !(total <= 3242.5)}'; then
|
||||
pass "стенд занимает ${total_mib} MiB после 20 секунд покоя, порог 3,4 ГБ не превышен"
|
||||
else
|
||||
fail "стенд занимает ${total_mib:-неизвестно} MiB после 20 секунд покоя, это больше 3,4 ГБ"
|
||||
if [[ "$hurt" -eq 0 ]]; then
|
||||
pass 'ни один долгоживущий контейнер не был убит по памяти и не перезапускался сам'
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -638,7 +640,7 @@ if check_host_dependencies; then
|
||||
check_grafana_datasource
|
||||
check_airflow
|
||||
check_superset
|
||||
check_memory_budget
|
||||
check_containers_survived
|
||||
else
|
||||
fail 'проверки контейнеров, Kafka, Airflow, Superset, Prometheus и Grafana пропущены без зависимостей машины'
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user