Deep Engineering

MEASUREMENT

bench/shutdown/practice.py

The script that produced the numbers in the article, and the record of the run. The file is read from the repository at build time — this is the code that was run, not a copy of it.

Cited in
/en/interview/sre/graceful-shutdown
How to run it
python3 bench/shutdown/drain.py    > bench/shutdown/runs/drain.txt
python3 bench/shutdown/practice.py > bench/shutdown/runs/practice.txt

The run below is recorded in Russian. It is a lab record, kept in the language it was written in; the numbers, the tables and the code read the same either way.

Record of the run

Замеры для урока «Graceful shutdown»

Файл Что делает
drain.py настоящий сервер на loopback под нагрузкой из двадцати клиентов: сколько запросов обрывается при немедленном выходе по SIGTERM и при дренаже, и сколько занимает сама остановка
practice.py ответы к задачам урока: число оборванных запросов в обоих режимах, код выхода и время остановки

Запуск из корня репозитория:

python3 bench/shutdown/drain.py    > bench/shutdown/runs/drain.txt
python3 bench/shutdown/practice.py > bench/shutdown/runs/practice.txt

Что воспроизводимо

Соотношение: без дренажа обрываются все запросы, что были в работе; с дренажем — ни одного. И то, что цена дренажа равна остатку уже начатой работы, а не льготному сроку.

Не воспроизводимо: миллисекунды остановки и выдержка обработки — её задаёт сам скрипт (300 мс).

Требования к среде

Только loopback. Сервер живёт в отдельном процессе, чтобы ему можно было послать сигнал так, как это делает оркестратор.

Числа сняты на CPython 3.11.15, Linux 6.18.44.

Script

53 lines
"""Практика к уроку про graceful shutdown: три ответа и одна цена.

ЗАЧЕМ ОТДЕЛЬНЫЙ ФАЙЛ. Задачи урока проверяются сборкой
(scripts/validate-practice.mjs): показанный читателю код обязан построчно быть
в скрипте, верный вариант — дословно встречаться в записи прогона, а
заявленное число — печататься самой программой.

ЧТО ЗДЕСЬ ПРОВЕРЯЕТСЯ. Одна и та же остановка под нагрузкой в двух режимах:
выход по `SIGTERM` немедленно и выход после дренажа. Задача — про то, сколько
запросов при этом обрывается, и про цену дренажа в миллисекундах.

Сервер и клиенты берутся из `drain.py`, чтобы задача и разбор урока стояли на
одном коде.

ЗАПУСК: python3 bench/shutdown/practice.py
Вывод: runs/practice.txt
"""

import os
import sys

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from drain import CLIENTS, WORK_MS, run  # noqa: E402


def main() -> None:
    print(f"Python {sys.version.split()[0]} · Linux {os.uname().release} · loopback")
    print()

    # --- Часть 1: три ответа о поведении --------------------------------
    abrupt = run("abrupt")
    drained = run("drain")
    print(abrupt["broken"])
    print(drained["broken"])
    print(abrupt["exit"])

    # --- Часть 2: сколько стоит дренаж -----------------------------------
    #
    # ПОЧЕМУ ЭТО И ЕСТЬ ЦЕНА. Дренаж не «замедляет остановку вообще» — он
    # ждёт ровно остаток работы, которая уже начата. Сигнал приходит на
    # середине трёхсотмиллисекундной обработки, поэтому ожидание получается
    # около половины от неё, а не льготный срок целиком.
    print()
    print(f"concurrent requests                  {CLIENTS}")
    print(f"work per request, ms                 {WORK_MS}")
    print(f"stop time, abrupt exit, ms           {abrupt['stop_ms']:.1f}")
    print(f"stop time, with draining, ms         {drained['stop_ms']:.1f}")


if __name__ == "__main__":
    main()