HTTP-прокси
Примечание
Для понимания изложенного в разделе материала требуется знание устройства HTTP протокола, принципов работы через HTTP, основ хранения данных в системе YTsaurus.
В данном разделе показан принцип работы с YTsaurus через HTTP-прокси — на примере запуска задачи Word Count.
Word Count — это типичная задача, которую считают в любой системе MapReduce. Общая схема следующая:
- По исходному тексту выполняется Map операция, выдающая для каждого слова пару
(<слово>, 1)
. - Результат сортируется по первой координате.
- По первой координате выполняется Reduce операция, суммирующая вторую координату. На выходе получается набор пар
(<слово>, <количество упоминаний слова>)
.
Полное описание всех доступных команд YTsaurus можно найти в разделе Команды.
Перед началом работы
-
Для доступа к системе YTsaurus вам понадобится токен. Как его получить — написано в разделе Аутентификация.
-
Примеры рассчитаны для запуска в командной оболочке Bash. У вас должна быть установлена утилита curl — она понадобится для работы с YTsaurus через HTTP. В некоторых примерах также будет использоваться утилита jq — чтобы сделать вывод команд более читабельным.
Установка переменных окружения
export YT_PROXY=<cluster-host>
export YT_HOME=//tmp/yt_examples
export YT_TOKEN=`cat ~/.yt/token`
Создание исходной таблицы
Прежде всего необходимо создать рабочий каталог, в котором будут размещаться данные для нашего примера. Для создания каталогов в Кипарисе используется команда create:
# Создание рабочего каталога
$ curl -v -X POST "http://$YT_PROXY/api/v4/create?path=$YT_HOME&type=map_node" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
# Для удобства восприятия в примерах удалена
# несущественная и дублирующаяся информация
< HTTP/1.1 200 OK
"0-3c35f-12f-8c397340"
Данные в системе YTsaurus хранятся в таблицах, поэтому необходимо создать таблицу:
# Создание таблицы — в ней будут храниться исходные данные
$ curl -v -X POST "http://$YT_PROXY/api/v4/create?path=$YT_HOME/input&type=table" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
< HTTP/1.1 200 OK
"0-6a7-191-8075d1f7"
В результате выполнения команд в ответ возвращаются идентификаторы (GUID) созданных объектов в Кипарисе: 0-3c35f-12f-8c397340
и 0-6a7-191-8075d1f7
.
Загрузка данных в таблицу
Теперь в таблицу необходимо загрузить данные. В качестве формата загружаемых данных будет использоваться tab-separated формат:
- строки таблицы отделяются друг от друга переводом строки (
\n
); - колонки отделяются друг от друга табуляцией (
\t
); - имя колонки и содержимое колонки отделяются друг от друга знаком равенства.
К примеру, строка: lineno=1\tsize=6\tvalue=foobar
описывает строку с колонками lineno, size и value со значениями, соответственно, 1, 6 и foobar. Символы табуляции экранируются.
Ниже приведён пример подготовки исходных данных:
# Загрузка текста
$ curl http://lib.ru/BULGAKOW/whtguard.txt | iconv -f koi8-r -t utf-8 > source.txt
# Приведение текста к нужному формату
$ cat source.txt | perl -e '
$n = 0;
while(<>) {
$n++; chomp; s/\t/\\t/g; print "lineno=$n\ttext=$_\n";
}
' > source.tsv
Для записи данных в таблицу используется команда write_table. Обратите внимание — запросы на загрузку данных выполняются не через обычную прокси YT_PROXY
, а через тяжёлую прокси:
# Получение тяжёлой прокси
$ HEAVY_YT_PROXY=$(curl -s -H "Accept: text/plain" "http://$YT_PROXY/hosts" | head -n1)
# Загрузка данных в таблицу
$ curl -v -X PUT "http://$HEAVY_YT_PROXY/api/v4/write_table?path=$YT_HOME/input" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN" \
-H "Content-Type: text/tab-separated-values" \
-H "Transfer-Encoding: chunked" \
-T source.tsv
< HTTP/1.1 200 OK
Примечание
Обратите внимание, что в данном запросе также явно указан формат входных данных — "text/tab-separated-values".
Чтобы удостовериться в том, что данные записаны, необходимо посмотреть на атрибуты таблицы:
# Получение атрибутов таблицы
$ curl -v -X GET "http://$YT_PROXY/api/v4/get?path=$YT_HOME/input/@" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN" | jq
< HTTP/1.1 200 OK
{
...
"uncompressed_data_size" : 993253,
"compressed_size" : 542240,
"compression_ratio" : 0.54592334480741566693,
"row_count" : 8488,
"sorted" : "false",
...
"channels" : [ ["lineno"], ["text"] ]
}
Примечание
Не стоит пугаться Null
-значений. Null
не значит, что соответствующих данных нет. Null
значит, что либо по данному ключу находится узел специального типа (например, таблица или файл), либо что данные лениво вычисляемые и за ними нужно обратиться явно.
Примечание
Чтобы загрузить данные в другом формате, например в JSON, необходимо добавить немного больше опций. Подробности можно прочитать в разделе Форматы.
Пример загрузки данных в формате JSON
$ cat test.json
{ "color": "красный", "value": "#f00" }
{ "color": "red", "value": "#f00" }
$ curl -X POST "http://$YT_PROXY/api/v4/create?path=//tmp/test-json&type=table" \
-H "Accept: application/json" \
-H "Authorization: OAuth $YT_TOKEN"
$ curl -X PUT "http://$HEAVY_YT_PROXY/api/v4/write_table?path=//tmp/test-json&encode_utf8=false"
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
-H "Content-Type: application/json"
-H "Transfer-Encoding: chunked"
-H "X-YT-Header-Format: <format=text>yson"
-H "X-YT-Input-Format: <encode_utf8=%false>json"
-T test.json
Получение тяжёлых прокси
При написании своего клиента пользователю необходимо эпизодически запрашивать список тяжёлых прокси через запрос /hosts
. В ответе возвращается упорядоченный по приоритету список тяжёлых прокси. Приоритет прокси определяется динамически и зависит от её загрузки (CPU+I/O). Хорошая стратегия — раз в минуту или раз в несколько запросов перезапрашивать список /hosts
и менять текущую прокси, к которой задаются запросы.
HEAVY_YT_PROXY=$(curl -s -H "Accept: text/plain" "http://$YT_PROXY/hosts" | head -n1)
Загрузка файла
Для запуска операции Map или Reduce потребуется загрузить в систему скрипт, который нужно будет исполнить. Скрипт для данной задачи размещен на GitHub. Пусть локально он сохранён в файл exec.py
.
Для загрузки файлов в систему YTsaurus используется команда write_file. Перед загрузкой необходимо создать узел типа «файл», аналогично созданию таблицы на предыдущем шаге:
# Создание узла с типом «файл»
$ curl -v -X POST "http://$YT_PROXY/api/v4/create?path=$YT_HOME/exec.py&type=file" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
< HTTP/1.1 200 OK
"0-efd-190-88fec34"
# Загрузка содержимого в файл
$ curl -v -X PUT "http://$HEAVY_YT_PROXY/api/v4/write_file?path=$YT_HOME/exec.py" \
-H "Transfer-Encoding: chunked" -H "Authorization: OAuth $YT_TOKEN" \
-H "Accept: application/json" \
-T exec.py
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
Для того чтобы у файла были права на исполнение, необходимо установить для него атрибут executable:
# Установка атрибутов
$ curl -v -X PUT "http://$YT_PROXY/api/v4/set?path=$YT_HOME/exec.py/@executable" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN" \
-H "Content-Type: application/json" \
--data-binary '"true"'
Для проверки корректности загруженного файла можно его прочитать:
# Чтение загруженного файла
$ curl -v -X GET "http://$HEAVY_YT_PROXY/api/v4/read_file?path=$YT_HOME/exec.py" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
< HTTP/1.1 200 OK
#!/usr/bin/python
import re
import sys
import itertools
import operator
...
Если необходимо прочесть диапазон байт из файла, можно использовать дополнительные параметры offset
и length
команды read_file
. Для разнообразия в примере ниже параметры передаются через заголовок X-YT-Parameters
:
# Чтение диапазона байт из файла
$ curl -v -X GET "http://$HEAVY_YT_PROXY/api/v4/read_file" \
-H "Authorization: OAuth $YT_TOKEN" \
-H "X-YT-Parameters: {\"path\": \"$YT_HOME/exec.py\", \"offset\": 11, \"length\": 6}"
< HTTP/1.1 200 OK
python
Запуск Map операции
Важный момент, который стоит учитывать: требование существования выходной таблицы до запуска операции. Создадим таблицу с помощью команды create:
# Создание выходной таблицы
$ curl -v -X POST "http://$YT_PROXY/api/v4/create?path=$YT_HOME/output_map&type=table" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
< HTTP/1.1 200 OK
"0-843-191-ae506abe"
Теперь можно запустить операцию Map. Для этого следует воспользоваться командой start_operation с параметром operation_type
, равным map
. Обратите внимание на спецификацию операции: запуск операции map
требует задания структурированного набора аргументов. В HTTP-интерфейсе системы кодировать аргументы можно как через query string (что и происходило в примерах выше, см. аргументы path и type), так и в теле запроса для POST-запросов. Пример описывает спецификацию для запуска в формате JSON:
# Запуск Map операции
$ cat <<END | curl -v -X POST "http://$YT_PROXY/api/v4/start_operation" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @-
{
"operation_type": "map",
"spec" : {
"mapper" : {
"command" : "python exec.py map",
"file_paths" : [ "$YT_HOME/exec.py" ],
"format" : "dsv"
},
"input_table_paths" : [ "$YT_HOME/input" ],
"output_table_paths" : [ "$YT_HOME/output_map" ]
}
}
END
< HTTP/1.1 200 OK
"381eb9-8eee9ec2-70a8370d-ea39f666"
Примечание
В /api/v3 операции запускаются несколько иначе.
Примечание
В отличие от операций set
и write_file
, возвращаемый GUID операции необходим для отслеживания состояния операции. Успешный ответ HTTP-интерфейса о запуске операции говорит только о том, что операция была запущена, но не о том, что она отработала корректно.
Посмотреть статус операции можно через веб-интерфейс или с помощью вызова get_operation
:
# Просмотр статуса операции
$ curl -v -X GET "http://$YT_PROXY/api/v4/get_operation?operation_id=381eb9-8eee9ec2-70a8370d-ea39f666" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
< HTTP/1.1 200 OK
{
"operation_type" : "map",
"progress" : {
"jobs" : { "total" : 1, "pending" : 0, "running" : 0, "completed" : 1, "failed" : 0 },
"chunks" : { "total" : 1,"running" : 0, "completed" : 1, "pending" : 0, "failed" : 0 },
...
},
"state" : "completed"
}
Чтобы убедиться, что операция отработала корректно, можно прочитать результат её работы:
# Чтение данных
$ curl -v -X GET "http://$HEAVY_YT_PROXY/api/v4/read_table?path=$YT_HOME/output_map" \
-H "Accept: text/tab-separated-values" -H "Authorization: OAuth $YT_TOKEN" \
| head
< HTTP/1.1 202 Accepted
word=html count=1
word=head count=1
word=title count=1
word=михаил count=1
word=булгаков count=1
word=белая count=1
word=гвардия count=1
word=title count=1
word=head count=1
word=body count=1
Запуск сортировки
Сортировка запускается командой start_operation с параметром operation_type
, равным sort
. Входная спецификация для Sort немного похожа на спецификации для Map и Reduce.
# Создание выходной таблицы
$ curl -v -X POST "http://$YT_PROXY/api/v4/create?path=$YT_HOME/output_sort&type=table" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
< HTTP/1.1 200 OK
"0-95d-191-8901298a"
# Запуск сортировки
$ cat <<END | curl -v -X POST "http://$YT_PROXY/api/v4/start_operation" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @-
{
"operation_type": "sort",
"spec" : {
"input_table_paths" : [ "$YT_HOME/output_map" ],
"output_table_path" : "$YT_HOME/output_sort",
"sort_by" : [ "word" ]
}
}
END
< HTTP/1.1 200 OK
"640310-da8d54f1-6eded631-31c91e76"
Отслеживать процесс выполнения операции можно через веб-интерфейс.
Запуск Reduce операции
Запуск Reduce операции выполняется с помощью команды start_operation с параметром operation_type
, равным reduce
:
# Создание выходной таблицы
$ curl -v -X POST "http://$YT_PROXY/api/v4/create?path=$YT_HOME/output_reduce&type=table" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
< HTTP/1.1 200 OK
"0-95f-191-ba3a4055"
# Запуск Reduce операции
$ cat <<END | curl -v -X POST "http://$YT_PROXY/api/v4/start_operation" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @-
{
"operation_type": "reduce",
"spec" : {
"reducer" : {
"command" : "python exec.py reduce",
"file_paths" : [ "$YT_HOME/exec.py" ],
"format" : "dsv"
},
"input_table_paths" : [ "$YT_HOME/output_sort" ],
"output_table_paths" : [ "$YT_HOME/output_reduce" ],
"sort_by" : [ "word" ],
"reduce_by" : [ "word" ]
}
}
END
< HTTP/1.1 200 OK
"658ad8-edf7650f-182e4fd0-a1a0fd03"
Чтение результата
После окончания операции получим подсчитанную статистику по частоте использования слов в «Белой гвардии». Посмотрим на результат её работы:
# Чтение результата
$ curl -v -X GET "http://$HEAVY_YT_PROXY/api/v4/read_table?path=$YT_HOME/output_reduce" \
-H "Accept: text/tab-separated-values" -H "Authorization: OAuth $YT_TOKEN" \
| head
< HTTP/1.1 200 OK
count=1 word=0
count=1 word=04
count=1 word=05
count=4 word=1
count=2 word=10
count=4 word=11
count=4 word=12
count=4 word=13
count=3 word=14
count=2 word=15
Запуск операций в /api/v3
Важно
Версия /api/v3 является устаревшей — больше она не развивается. Используйте версию /api/v4.
В /api/v3 все операции обработки данных (Map, Reduce, Sort и так далее) запускались не командой start_operation
, а с помощью одноимённых команд — map, reduce, sort. Например, так выглядит запуск Map операции:
# Запуск Map операции в /api/v3
$ cat <<END | curl -v -X POST "http://$YT_PROXY/api/v3/map" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @-
{
"spec" : {
"mapper" : {
"command" : "python exec.py map",
"file_paths" : [ "$YT_HOME/exec.py" ],
"format" : "dsv"
},
"input_table_paths" : [ "$YT_HOME/input" ],
"output_table_paths" : [ "$YT_HOME/output_map" ]
}
}
END