HTTP-прокси

В данном разделе содержится демонстрация работы с YTsaurus через HTTP-прокси, на примере запуска задачи Word Count.

Полное описание всех команд YTsaurus можно найти в разделе Команды.

Примечание

Для понимания изложенного в разделе материала требуется знание устройства HTTP протокола, принципов работы через HTTP, основ хранения данных в системе YTsaurus.

Подготовка

Пример Word Count — это типичная задача, которую считают в любой системе MapReduce. Общая схема следующая:

  1. По исходному тексту выполняется Map операция, выдающая по каждому слову пару (слово, 1);
  2. Результат сортируется по первой координате;
  3. По первой координате выполняется 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