HTTP-прокси
В данном разделе содержится демонстрация работы с YTsaurus через HTTP-прокси, на примере запуска задачи Word Count.
Полное описание всех команд YTsaurus можно найти в разделе Команды.
Примечание
Для понимания изложенного в разделе материала требуется знание устройства HTTP протокола, принципов работы через HTTP, основ хранения данных в системе YTsaurus.
Подготовка
Пример Word Count — это типичная задача, которую считают в любой системе MapReduce. Общая схема следующая:
- По исходному тексту выполняется Map операция, выдающая по каждому слову пару
(слово, 1)
. - Результат сортируется по первой координате.
- По первой координате выполняется Reduce операция, суммирующая вторую координату. На выходе получается набор пар
(слово, количество упоминаний слов)
.
Далее в тексте примера используется утилита curl для работы через HTTP. Для удобства записи были выставлены переменные окружения, представленные ниже. Подробнее о получении токена можно прочитать в разделе Аутентификация.
Установка переменных окружения:
$ export YT_PROXY=cluster-name
$ export YT_TOKEN=`cat ~/.yt/token`
$ export YT_HOME=//tmp/yt_examples
Для большей наглядности и читаемости примеров в них удалена несущественная и дублирующаяся информация.
Загрузка данных
Данные в системе YTsaurus хранятся в таблицах, поэтому необходимо создать таблицу. Таблица содержит две колонки: номер строки и содержимое строки. Для создания таблицы используется команда create.
Создание каталога и таблицы:
# Создание рабочего каталога
$ curl -v -X POST "http://$YT_PROXY/api/v3/create?path=$YT_HOME&type=map_node" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
> POST /api/v3/create?path=//tmp/yt_examples&type=map_node HTTP/1.1
< HTTP/1.1 200 OK
"0-3c35f-12f-8c397340"
# Создание таблицы для исходных данных
$ curl -v -X POST "http://$YT_PROXY/api/v3/create?path=$YT_HOME/input&type=table" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
> POST /api/v3/create?path=//tmp/yt_examples/input&type=table HTTP/1.1
< HTTP/1.1 200 OK
"0-6a7-191-8075d1f7"
В результате выполнения команд в ответ возвращаются идентификаторы (GUID) созданных объектов в Кипарисе: 0-3c35f-12f-8c397340
и 0-6a7-191-8075d1f7
.
Теперь в таблицу необходимо загрузить данные, для этого используется команда write_table. В качестве формата данных используется tab-separated формат:
- строки таблицы отделяются друг от друга переводом строки (
\n
); - колонки отделяются друг от друга табуляцией (
\t
); - имя колонки и содержимое колонки отделяются друг от друга знаком равенства.
К примеру, строка: lineno=1\tsize=6\tvalue=foobar
описывает строку с колонками lineno, size и value со значениями, соответственно, 1, 6 и foobar. Символы табуляции экранируются.
Загрузка данных в таблицу:
# Скачивание текста
$ wget -O - http://lib.ru/BULGAKOW/whtguard.txt | iconv -f cp1251 -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
$ HEAVY_YT_PROXY=$(curl -s -H "Accept: text/plain" "http://$YT_PROXY/hosts" | head -n1)
$ curl -v -L -X PUT "http://$HEAVY_YT_PROXY/api/v3/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
> PUT /api/v3/write_table?path=//tmp/yt_examples/input HTTP/1.1
> Expect: 100-continue
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
Примечание
Обратите внимание, что в данном запросе также явно указан формат входных данных — "text/tab-separated-values".
Примечание
При написании своего клиента пользователю необходимо эпизодически запрашивать список тяжелых прокси через запрос /hosts
. В ответе возвращается упорядоченный по приоритету список тяжелых прокси. Приоритет прокси определяется динамически и зависит от ее загрузки (CPU+I/O). Хорошая стратегия — раз в минуту или раз в несколько запросов перезапрашивать список /hosts
и менять текущую прокси, к которой задаются запросы.
Получение списка тяжелых прокси:
$ curl -v -X GET "http://$YT_PROXY/hosts"
> GET /hosts HTTP/1.1
< HTTP/1.1 200 OK
< Content-Type: application/json
<
["n0008-sas.cluster-name","n0025-sas.cluster-name",...]
Чтобы удостовериться в том, что данные записаны, необходимо посмотреть на атрибуты таблицы.
Получение атрибутов таблицы:
$ curl -v -X GET "http://$YT_PROXY/api/v3/get?path=$YT_HOME/input/@" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
> GET /api/v3/get?path=//tmp/yt_examples/input/@ HTTP/1.1
< 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/v3/create?path=//tmp/test-json&type=table" \
-H "Accept: application/json" \
-H "Authorization: OAuth $YT_TOKEN"
$ curl -L -X PUT "http://$HEAVY_YT_PROXY/api/v3/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
Загрузка файлов
Для запуска операции Map или Reduce потребуется загрузить в систему скрипт, который нужно будет исполнить. Скрипт для данной задачи размещен на GitHub (пусть локально он сохранен в файл exec.py
). Для загрузки файлов в систему YTsaurus используется команда write_file. Перед загрузкой необходимо создать узел типа «файл», аналогично созданию таблицы на предыдущем шаге.
Загрузка файлов:
$ curl -v -X POST "http://$YT_PROXY/api/v3/create?path=$YT_HOME/exec.py&type=file" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
> POST /api/v3/create?path=//tmp/yt_examples/exec.py&type=file HTTP/1.1
< HTTP/1.1 200 OK
"0-efd-190-88fec34"
$ curl -v -L -X PUT "http://$YT_PROXY/api/v3/write_file?path=$YT_HOME/exec.py" \
-H "Transfer-Encoding: chunked" -H "Authorization: OAuth $YT_TOKEN" \
-T exec.py
> PUT /api/v3/write_file?path=//tmp/yt_examples/exec.py HTTP/1.1
> Expect: 100-continue
< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
Для того, чтобы у файла были права на исполнение, необходимо установить для него атрибут executable.
Установка атрибутов:
$ curl -v -L -X PUT "http://$YT_PROXY/api/v3/set?path=$YT_HOME/exec.py/@executable" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN" \
-H "Content-Type: application/json" \
--data-binary '"true"'
> PUT /api/v3/set?path=//tmp/yt_examples/exec.py/@executable HTTP/1.1
< HTTP/1.1 200 OK
Для проверки корректности загруженного файла можно его прочитать.
Чтение загруженного файла:
$ curl -v -L -X GET "http://$YT_HEAVY_PROXY/api/v3/read_file?path=$YT_HOME/exec.py" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
> GET /api/v3/read_file?path=//tmp/yt_examples/exec.py HTTP/1.1
< 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 -L -X GET "http://$YT_HEAVY_PROXY/api/v3/read_file" \
-H "Authorization: OAuth $YT_TOKEN" \
-H "X-YT-Parameters: {\"path\": \"$YT_HOME/exec.py\", \"offset\": 11, \"length\": 6}"
> GET /api/v3/read_file HTTP/1.1
< HTTP/1.1 200 OK
python
Запуск Map операции
В системе YTsaurus для запуска Map операции существует одноименная команда. Важный момент, который стоит учитывать, — требование существования выходной таблицы до запуска операции. Таблицу можно создать с помощью команды create.
Создание выходной таблицы:
$ curl -v -X POST "http://$YT_PROXY/api/v3/create?path=$YT_HOME/output_map&type=table" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
> POST /api/v3/create?path=//tmp/yt_examples/output_map&type=table HTTP/1.1
< HTTP/1.1 200 OK
"0-843-191-ae506abe"
Теперь можно запустить операцию Map. Обратим внимание на ее спецификацию: видно, что запуск команды map требует задания структурированного набора аргументов. В HTTP-интерфейсе системы кодировать аргументы можно как через query string (что и происходило в примерах выше, см. аргументы path и type), так и в теле запроса для POST-запросов. Пример описывает спецификацию для запуска в формате JSON.
Запуск Map операции:
$ 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
> POST /api/v3/map HTTP/1.1
< HTTP/1.1 200 OK
"381eb9-8eee9ec2-70a8370d-ea39f666"
Примечание
В отличие от операций set
и write_file
, возвращаемый GUID операции необходим для отслеживания состояния операции. Успешный ответ HTTP-интерфейса о запуске операции говорит только о том, что операция была запущена, но не о том, что она отработала корректно.
Посмотреть статус операции можно через веб-интерфейс или с помощью вызова get_operation
.
Просмотр статус операции:
$ curl -v -X GET "http://$YT_PROXY/api/v3/get_operation?operation_id=381eb9-8eee9ec2-70a8370d-ea39f666" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
> GET /api/v3/get_operation?operation_id=381eb9-8eee9ec2-70a8370d-ea39f666 HTTP/1.1
< 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 -L -X GET "http://$YT_HEAVY_PROXY/api/v3/read_table?path=$YT_HOME/output_map" \
-H "Accept: text/tab-separated-values" -H "Authorization: OAuth $YT_TOKEN" \
| head
> GET /api/v3/read?path=//tmp/yt_examples/output_map HTTP/1.1
< 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
Запуск сортировки
Сортировка запускается командой sort, которая по входной спецификации немного похожа на Map и Reduce.
Запуск сортировки:
# Создание выходной таблицы
$ curl -v -X POST "http://$YT_PROXY/api/v3/create?path=$YT_HOME/output_sort&type=table" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
> POST /api/v3/create?path=//tmp/yt_examples/output_map&type=table HTTP/1.1
< HTTP/1.1 200 OK
"0-95d-191-8901298a"
# Запуск сортировки
$ cat <<END | curl -v -X POST "http://$YT_PROXY/api/v3/sort" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @-
{
"spec" : {
"input_table_paths" : [ "$YT_HOME/output_map" ],
"output_table_path" : "$YT_HOME/output_sort",
"sort_by" : [ "word" ]
}
}
END
> POST /api/v3/sort HTTP/1.1
< HTTP/1.1 200 OK
"640310-da8d54f1-6eded631-31c91e76"
За процессом выполнения операции можно следить через веб-интерфейс.
Запуск Reduce операции
Запуск Reduce операции выполняется с помощью одноименной команды.
Запуск Reduce операции:
# Создание выходной таблицы
$ curl -v -X POST "http://$YT_PROXY/api/v3/create?path=$YT_HOME/output_reduce&type=table" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN"
> POST /api/v3/create?path=//tmp/yt_examples/output_reduce&type=table HTTP/1.1
< HTTP/1.1 200 OK
"0-95f-191-ba3a4055"
# Запуск reduce
$ cat <<END | curl -v -X POST "http://$YT_PROXY/api/v3/reduce" \
-H "Accept: application/json" -H "Authorization: OAuth $YT_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @-
{
"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
> POST /api/v3/reduce HTTP/1.1
< HTTP/1.1 200 OK
"658ad8-edf7650f-182e4fd0-a1a0fd03"
Чтение результата
После окончания операции получим подсчитанную статистика по частоте использования слов в "Белой гвардии".
Посмотрим результат ее работы.
Чтение результата:
$ curl -v -L -X GET "http://$YT_HEAVY_PROXY/api/v3/read_table?path=$YT_HOME/output_reduce" \
-H "Accept: text/tab-separated-values" -H "Authorization: OAuth $YT_TOKEN" \
| head
> GET /api/v3/read_table?path=//tmp/yt_examples/output_reduce HTTP/1.1
< 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