Диагностика медленных джобов

В данном разделе приведено описание диагностики медленных джобов с использованием инструмента Job Shell.

Время от времени может случаться так, что один или несколько джобов операции выполняются долго и без видимого прогресса. Чтобы разобраться в причине, можно воспользоваться инструментом Job Shell. С помощью данного инструмента можно открыть консоль непосредственно в окружении джоба от имени того же пользователя, от которого выполняется джоб. После этого можно исследовать работу джоба с помощью вызовов gdb или strace.

Запуск Job Shell

Для запуска Job Shell необходимо выполнить команду yt --proxy <cluster name> run-job-shell <job id>, подставив вместо <...> имя кластера и идентификатор джоба. Идентификатор джоба можно узнать на странице операции, на вкладке Jobs.

Подробное описание запуска Job Shell:

$ yt --proxy cluster-name run-job-shell fcebe6b9-e06ab2e1-3fe0384-d0b66453
Use ^F to terminate shell.

Job environment:
TMPDIR=/yt/disk2/cluster-data/slots/11/sandbox
PYTHONPATH=/yt/disk2/cluster-data/slots/11/sandbox
PYTHONUSERBASE=/yt/disk2/cluster-data/slots/11/sandbox/.python-site-packages
PYTHON_EGG_CACHE=/yt/disk2/cluster-data/slots/11/sandbox/.python-eggs
HOME=/yt/disk2/cluster-data/slots/11/sandbox
YT_OPERATION_ID=78b048b5-8055fabe-3fe03e8-d5b74b2a
YT_JOB_INDEX=120
YT_JOB_ID=fcebe6b9-e06ab2e1-3fe0384-d0b66453
YT_START_ROW_INDEX=202133578

UID          PID    PPID  C STIME TTY          TIME CMD
19911     257386  239675  0 02:24 pts/1    00:00:00 /bin/bash
19911     257397  257386  0 02:24 pts/1    00:00:00  \_ ps -fu 19911 --forest
19911     240043  239675  0 02:21 ?        00:00:00 /bin/bash -c :; ./my_mapper
19911     240073  240043 91 02:21 ?        00:02:37  \_ ./my_mapper

yt_slot_11@n1757-sas:~$

В выводе команды отображена различная информация о джобе:

  • путь к рабочей директории джоба — /yt/disk2/cluster-data/slots/11/sandbox;
  • имя пользователя, от которого запущен джоб — yt_slot_11;
  • список процессов, которые выполняются от имени текущего пользователя.

Первый и второй процессы из приведённого примера относятся к Job Shell, третий процесс — сессия командной оболочки bash, в которой запускается исследуемый джоб, четвёртый — это непосредственно пользовательский процесс.

Анализ пользовательского процесса

В первую очередь следует запустить команду top -p <PID>, указав в качестве аргумента идентификатор пользовательского процесса. Ниже приведен пример и результат вызова:

yt_slot_11@n1757-sas:~$ top -p 240073
top - 02:30:52 up 6 days,  7:11,  0 users,  load average: 41.26, 37.59, 39.23
Tasks:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
Cpu(s): 48.9%us,  5.6%sy,  0.0%ni, 31.1%id, 14.3%wa,  0.0%hi,  0.2%si,  0.0%st
Mem:  132001796k total, 130937776k used,  1064020k free,   296488k buffers
Swap:        0k total,        0k used,        0k free, 107424768k cached

    PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 240073 yt_slot_  20   0  4164  632  552 R   98  0.0   8:08.05 my_mapper

По использованию CPU (48.9%) в приведенном примере видно, что пользовательский процесс выполняет вычисления и, вероятно, причина медленной работы джоба в пользовательском коде. Следует подключиться к процессу джоба с помощью gdb и попытаться понять, что он в данный момент делает.

Если потребление CPU близко к нулю, то это скорее всего означает, что процесс завис на системном вызове. Стоит вызвать strace на пользовательский процесс.
Пример вызова:

yt_slot_11@n1757-sas:~$ strace -p 240073
Process 240073 attached - interrupt to quit
write(4

Если процесс постоянно производит системные вызовы (а не висит на каком-то одном), то будьте готовы, что указанная выше команда выдаст на консоль сразу поток информации. В такой ситуации может быть полезен вариант strace -p <PID> 2>&1 | head, позволяющий увидеть лишь один вызов.

Также если ваш код многопоточный, то может быть полезно добавить опцию -f, позволяющую перехватывать системные вызовы изо всех потоков.

Если процесс завис на системном вызове read или write, стоит обратить внимание на значение первого аргумента, который обозначает файловый дескриптор потока. Нулевое значение аргумента для системного вызова read и небольшое целое число для системного вызова write (1, 4, 7), означает, что по каким-то причинам служебный процесс YTsaurus Job Proxy не отдаёт или не принимает очередную порцию данных в направлении к пользовательскому процессу или от него. В таком случае следует сообщить о проблеме администратору системы, приложив детали о проведенной вами диагностике. Подробнее о проведении диагностики можно прочитать в разделе How To Report.

В случае зависаний на системных вызовах futex, connect, open, stat и др. причина не связана с работой Job Proxy и системы YTsaurus в целом. Проблему необходимо искать в пользовательском коде. Стоит воспользоваться gdb, чтобы понять чем вызвана блокировка. Например, если пользовательский код многопоточный и в нём по каким-то причинам возникает блокировка, то вывод strace может выглядеть так:

yt_slot_11@n1757-sas:~$ strace -p 240073 2>&1 | head
Process 240073 attached - interrupt to quit
futex(0x7ffff79b3e00, FUTEX_WAIT, 2, NULL

Возможна ситуация, когда пользовательский процесс не потребляет CPU, а strace не показывает никаких системных вызовов. Часто это означает, что код использует memory mapping и производит чтение (или запись) из файла, находящегося на диске. При этом происходит major page fault и процесс ожидает, пока операционная система загрузит данные с диска в память.

Внимание

Использование memory mapping в джобах категорически не рекомендуется, а джобы, совершающие такие действия, сильно нагружают диски случайными чтениями и записями. Такие джобы также можно распознать по высоким значениям статистик /user_job/block_io/[io_read,io_write]. Помните, что обычный жесткий диск способен совершить не более 100 операций со случайным доступом в секунду, в то время как на узле кластера одновременно работают несколько десятков джобов.

Частным случаем большого memory mapped файла, читаемого в джобах, является исполняемый файл операции и его зависимости, загружаемые динамическим линковщиком во время запуска операции. Код загрузчика написан так, что возникающие при этом page faults обычно не составляют проблемы. Стоит помнить, что уменьшение размеров исполняемого файла и его зависимостей всегда положительно сказывается на производительности. В частности, с точки зрения производительности лучше использовать исполняемые файлы без отладочных символов.

Именованные Job Shells

Иногда пользовательские процессы могут создавать внутри джобов свои подконтейнеры.

Чтобы иметь возможность подключаться и отлаживать джобы во вложенных контейнерах, в YT есть специальные именованные Job Shells. Их можно указать в спеке операции с привязкой к конкретному контейнеру.

Для этого в спеке операции можно указать job_shells:

spec = {
    "job_shells" = [
        {
            "name" = "inner";
            "subcontainer" = "/N";
            "owners" = ["user1", "user2", "admin"];
        };
        {
            "name" = "default";
            "subcontainer" = "";
            "owners" = ["admin"];
        };
    ];
};

Использование именованных Job Shells практически ничем не отличается от обычных. Если нужно подключиться к конкретному Job Shell, нужно указать параметр --shell-name:

$ yt --proxy cluster-name run-job-shell fcebe6b9-e06ab2e1-3fe0384-d0b66453 --shell-name N

По умолчанию запускается Job Shell с именем default.

Права на запуск Job Shell

Для запуска Job Shell нужно иметь права на чтение (read) и управление (manage) операцией.

Для этого при запуске операции в спецификации нужно указывать ACL, описывающий пользователей и группы, которые могут выполнять действия над операцией и джобами.
По умолчанию список ACL пустой, права на операцию выдаются только пользователю, запустившему операцию, а также администраторам системы YTsaurus.

Пример настройки ACL в Python:

spec={"acl": [{"permissions": ["read", "manage"], "subjects": ["user1", "user2"], "action": "allow"}]}

Пользователь из переменных окружения YT_TOKEN/YT_TOKEN_PATH (authenticated_user) добавляется автоматически.

Для изменения прав доступа к уже работающей операции нужно использовать команду update_operation_parameters с новым ACL.

Чтобы иметь доступ до именованных Job Shells, нужно также указать им правильные настройки owners. owners – это упрощенная версия acl, который представляет собой список субъектов (пользователей или групп), которым будет разрешен доступ до Job Shell.

"owners" = ["user1", "user2"];

Если вам захочется поменять owners какого-то Job Shell уже запущенной операции, можно воспользоваться командой update_operation_parameters, указав новый список owners конкретного Job Shell:

PARAMETERS {"operation_id" = "<op-id>"; "parameters" = {"options_per_job_shell" = {"shell_name": {"owners": ["user3"]}}}}