Создание веб-сервиса
В данном разделе описан пример создания простого веб-сервиса с использованием динамических таблиц.
В рамках примера будет построен бекэнд для небольшого сервиса хранения и показа комментариев. Можно рассматривать его как упрощенный аналог Reddit.
Для этого будет разработан stateless сервис c HTTP-интерфейсом, работающий поверх консистентного, отказоустойчивого и масштабируемого хранилища, в котором будут находиться топики, комментарии, лайки.
Используемые технологии и инструменты:
- Исходный код сервиса на Python 3.4 и выше.
- В качестве хранилища будут использоваться реплицированные динамические таблицы YTsaurus в режиме синхронной репликации. Работа с динамическими таблицами реализована через RPC прокси.
- В качестве основы для HTTP API будет использоваться фреймворк Flask.
Примечание
Данный пример не предусматривает разработку таких компонент как: авторизация, rate limiting, сервисные мониторинги.
Описываемый пример предназначен в первую очередь для знакомства с принципами использования и возможностями динамических таблиц YTsaurus, а не для разработки production high-load веб-сервисов на Python.
Чтобы скачать примеры кода, перейдите по ссылке.
Проектирование
Требования к сервису:
- Все комментарии, хранимые в сервисе, должны относиться к одному из топиков.
- Комментарии внутри топика должны быть организованы в дерево, у каждого комментария, кроме корневого комментария топика, должен быть один родитель.
- Каждый комментарий должен характеризоваться строкой
parent_path
. Она представляет собой путь из идентификаторов предков данного комментария в дереве. - Для каждого комментария должно храниться имя пользователя, количество просмотров, время создания.
- API должно позволять:
- создавать, редактировать и удалять комментарии;
- выбирать все комментарии по топику или поддерево комментариев внутри топика;
- выбирать последние комментарии, оставленные данным пользователем;
- выбирать последние добавленные топики.
Ожидаемые характеристики:
- до 50 000 комментариев внутри топика;
- типичный размер комментария — не больше десятков тысяч символов;
- по нагрузке (RPS) и объему данных сервис должен горизонтально масштабироваться.
Методы API
В случае POST-запросов параметры запросов передаются в теле запроса в виде JSON-документа. В GET-запросах — в URL. Во всех случаях результат запроса или ошибка приходит в теле ответа в виде JSON-документа. Возможные HTTP-коды ответа:
HTTP код | Описание |
---|---|
200 | GET/POST запрос был успешно выполнен. |
201 | Значение было успешно добавлено. |
400 | Некорректный запрос. Например, не заданы некоторые обязательные параметры. |
404 | Объект или объекты по заданному критерию не найдены. Например, попытка добавить комментарий в несуществующий топик. |
409 | Конфликт при внесении изменений в динамическую таблицу. |
503 | В данный момент сервис не может обработать запрос. Например, мета-кластер обновляется и/или динамические таблицы отмонтированы. |
524 | Превышен таймаут при работе с динамическими таблицами. |
В случае любой ошибки с кодами 4xx и 5xx в теле ответа приходит JSON-документ вида {"error" : "message"}
.
Запись данных — добавление, изменение, удаление комментария — будет реализовано через POST
, чтение — через GET
. В качестве идентификатора комментария используется guid
— такой подход позволит обеспечивать равномерную нагрузку на шарды динамических таблиц.
Сигнатуры вызовов
-
POST: /post_comment
— добавление комментария.Имя параметра Тип Обязательный параметр Описание topic_id
guid
Нет Идентификатор топика, если не задан — создается новый топик. parent_path
string
Нет Путь к комментарию-родителю. Может быть не задан, если не указан topic_id
.content
string
Да Содержимое комментария. user
string
Да Имя пользователя.
В случае успешного выполнения возвращается код 201 и документ вида {"comment_id" : "<guid>", "new_topic" : True | False}
;
-
POST: /edit_comment
— редактирование комментария.Имя параметра Тип Обязательный параметр Описание topic_id
guid
Да Идентификатор топика. parent_path
string
Да Путь к комментарию. content
string
Да Новое содержимое комментария. В случае успешного выполнения возвращается код 200 и пустой документ.
-
POST: /delete_comment
— удаление комментария. При этом из базы данных комментарий в действительности не удаляется, чтобы не нарушать древовидную структуру комментариев. Вместо этого в параметрах комментария выставляется специальный флаг о том, что он удален, после чего комментарий больше нельзя редактировать.Имя параметра Тип Обязательный параметр Описание topic_id
guid
Да Идентификатор топика. parent_path
string
Да Путь к комментарию. В случае успешного выполнения возвращается код 200 и пустой документ.
-
GET: /topic_comments
— получение комментария в топике. Для каждого комментария хранится счетчик просмотров, который увеличивается на единицу каждый раз при запросе комментариев топика.Имя параметра Тип Обязательный параметр Описание topic_id
guid
Да Идентификатор топика. parent_path
string
Нет Путь к корневому комментарию — может быть указан, если требуется получить только поддерево комментариев по заданному топику. В случае успешного выполнения возвращается код 200 и документ со списком комментариев следующего вида:
[ { "comment_id":"<id>", "parent_id":"<id>", "content":"<large_text>", "user":"<user_name>", "view_count":"<N>", "update_time":"<UTC_time>", "deleted":"True|False" } ] [{"comment_id" : "<id>", "parent_id" : "<id>", "content" : "<large_text>", "user" : "<user_name>", "view_count" : <N>, "update_time" : "<UTC_time>", "deleted" : True|False}]
Возвращаемые комментарии сортируются по
parent_path
, поэтому ответы на комментарий оказываются расположены сразу после него и при этом упорядочиваются по времени создания.
-
GET: /last_user_comments
— получить список комментариев пользователя, отсортированный в обратном хронологическом порядке. Предполагается, что в веб-интерфейсе сервиса будет страница, где пользователь сможет посмотреть все свои комментарии.Имя параметра Тип Обязательный параметр Описание user
string
Да Имя пользователя. limit
int
Нет Ограничение числа возвращаемых комментариев. from_time
UTC_time
Нет Возвращать только комментарии с update_time
больше данного параметра. Специально используется параметрfrom_time
вместоoffset
, потому что язык запросов YTsaurus не поддерживает ключевое словоOFFSET
в запросах.В случае успешного выполнения возвращается код 200 и документ со списком комментариев следующего вида:
[{"comment_id" : "<id>", "topic_id" : "<id>", "content" : "<large_text>", "user" : <user_name>, "view_count" : <N>, "update_time" : <UTC_time>}]
-
GET: /last_topics
— получить топики, в которых недавно обновлялись комментарии. Топиком является корневой комментарий. Предполагается, что данный запрос будет использоваться главной страницей сервиса, список будет общим для всех пользователей. Необходима поддержка пагинации.Имя параметра Тип Обязательный параметр Описание limit
int
Нет Ограничение на число возвращаемых топиков. from_time
UTC_time
Нет Возвращать только комментарии с update_time
больше данного параметра. Параметрfrom_time
используется вместоoffset
намеренно, потому что язык запросов YTsaurus не поддерживает ключевое словоOFFSET
в запросах.В случае успешного выполнения возвращается код 200 и документ со списком комментариев следующего вида:
[{"topic_id" : "<id>", "content" : "<large_text>", "user" = <user_name>, "view_count" : <N>, "update_time" : <UTC_time>}]
Описание таблиц с данными
Нужно определить необходимый набор таблиц и их схему. При проектировании схемы стоит учитывать возможности и ограничения динамических таблиц:
- В каждой динамической таблице есть уникальный первичный ключ. По первичному ключу можно быстро получать записи вызовом команды
lookup
. - Динамические таблицы поддерживают транзакции — в том числе между разными таблицами в модели
snapshot isolation
. - Динамические таблицы не поддерживают вторичные индексы. Эффективный поиск или фильтрация возможны только по префиксу первичного ключа с помощью механизма вывода диапазонов. В противном случае запрос может выродиться в full scan таблицы.
- Горизонтальное масштабирование динамических таблиц осуществляется посредством шардирования. Шард таблицы в системе называется
tablet
. Он задается непрерывным диапазоном значений первичного ключа. Чтобы шардирование было эффективно, необходимо чтобы чтение и запись были равномерно распределены между таблетами. - Вторичные индексы можно эмулировать, создавая дополнительные таблицы по соответствующему первичному ключу. В таком случае запись в основную таблицу и таблицу-индекс необходимо осуществлять в одной транзакции.
Необходима основная таблица, в которой будет храниться контент — topic_comments
. Запросы на чтение к такой таблице в основном будут ограничены одним топиком, поэтому рекомендуется сделать id топика первой компонентой ключа. В качестве id топика можно выдавать последовательные целые числа или формировать их на основе текущего времени. Однако это будет означать, что при создании новых топиков им будут выдаваться очень близкие номера, а поскольку новые комментарии чаще появляются в новых топиках, вся нагрузка на запись будет идти в один таблет.
Поэтому, чтобы обеспечить равномерную загрузку таблетов при масштабировании таблицы, в качестве id топика используется случайно сформированные guid. В качестве id комментариев в топике используются их номера в порядке создания — это может приводить к появлению близких ключей, но если размеры топиков будут не слишком велики, шардирования по guid топиков должно оказаться достаточно.
Также в ключевой колонке parent_path
хранится путь к комментарию в топике: для корневого комментария — его id, а parent_path
каждого следующего комментария получается из parent_path
его родителя путём дописывания к нему /
и id родителя. Например, parent_path
комментария с идентификатором #id1
, написанного в ответ на корневой комментарий с идентификатором 0
, записывается как 0/#id1
. Такая организация пути позволяет быстро отфильтровать комментарии в поддереве при вызове метода topic_comments
: если parent_path
корневого комментария в поддереве — строка S
, то parent_path
любого его потомка будет иметь вид S/#id1/.../#idN
. Он будет содержать S
в качестве префикса. Поэтому, если лексикографически упорядочить комментарии в топике по parent_path
, любому поддереву будет соответствовать непрерывный блок комментариев, начинающийся с корневого комментария поддерева. Таким образом, назначение parent_path
ключевой колонкой позволяет не выполнять full scan таблицы для выбора комментариев в поддереве.
Дополнительно для каждого комментария хранится id непосредственного комментария-родителя. Это нужно, чтобы при отображении комментариев их можно было собрать в дерево. Кроме того, в таблице topic_comments
хранится счетчик просмотров комментария, для которого используется механизм агрегирующих колонок. Такой механизм позволяет выполнять инкремент и декремент значения колонки без чтения ее предыдущей версии.
topic_comments
Таблица Имя колонки | Тип | Ключевая колонка | Описание |
---|---|---|---|
topic_id |
guid |
Да | Guid топика. |
parent_path |
string |
Да | Путь к комментарию. |
comment_id |
uint64 |
Нет | Id комментария. Его порядковый номер при добавлении в топик. |
parent_id |
uint64 |
Нет | Id комментария, в ответ на который был написан данный комментарий. Для корневых комментариев совпадает с comment_id . |
user |
string |
Нет | Логин пользователя, оставившего комментарий. |
create_time |
uint64 |
Нет | Время создания комментария в POSIX Time. |
update_time |
uint64 |
Нет | Время последнего обновления комментария в POSIX Time. |
content |
string |
Нет | Текст комментария. |
views_count |
int64 |
Нет | Количество просмотров, агрегирующая колонка. |
deleted |
boolean |
Нет | Флаг того, что комментарий был удалён. |
Чтобы выбирать записи в вызове /last_user_comments
, таблица topic_comments
не подходит. Комментарии пользователя могут находиться в разных топиках, а значит, для выполнения подобного запроса потребуется full scan таблицы. YTsaurus не поддерживает вторичные индексы, поэтому их также не получится использовать.
Поэтому необходима вторая, вспомогательная таблица user_comments
, где в качестве первой компоненты первичного ключа используется имя пользователя. Из соображений равномерного распределения нагрузки при будущем шардировании таблицы, стоит добавить в начало ключа вычислимую колонку — хеш от user_name
. Для хеширования используется функция farm_hash, указываемая в поле expression
в схеме таблицы (смотрите код создания таблиц).
user_comments
Таблица Имя колонки | Тип | Ключевая колонка | Описание |
---|---|---|---|
hash(user_name) |
uint64 |
Да | Вычислимая колонка. |
user_name |
string |
Да | Логин пользователя, оставившего комментарий. |
topic_id |
string |
Да | Guid топика. |
parent_path |
string |
Да | Путь к комментарию. |
update_time |
uint64 |
Нет | Время последнего обновления комментария в POSIX Time. |
Необходимо решить, как эффективно обрабатывать вызов /last_topics
. Простой способ — сделать запросы к таблице topic_comments
с группировкой по topic_id
. Но такой подход потребует повторно выполнить full scan самой большой таблицы. Поэтому имеет смысл завести еще одну небольшую таблицу topics
, где по topic_id
будет храниться время последнего обновления топика — то есть любого его комментария.
Несмотря на то, что данная таблица будет просматриваться целиком, ее размер гораздо меньше. Это позволит применить ряд оптимизаций. Например, загрузить таблицу целиком в память или увеличить параллельность выполнения запроса с помощью увеличения количества таблетов. Более того, ответ на такой запрос можно кэшировать, поскольку он будет одинаковым для всех пользователей. Также в данной таблице будет храниться количество комментариев в топике, чтобы при добавлении нового комментария можно было получить его идентификатор.
topic
Таблица Имя колонки | Тип | Ключевая колонка | Описание |
---|---|---|---|
topic_id |
string |
Да | Guid топика. |
update_time |
uint64 |
Нет | Время последнего обновления комментария в топике. |
comment_count |
uint64 |
Нет | Количество комментариев в топике, включая удаленные. |
Создание и монтирование таблиц
В данном примере использована синхронная репликация с одной синхронной и одной асинхронной репликой и автоматическим переключением режима реплик. Данный режим обеспечивает самые строгие гарантии консистентности — как у нереплицированных динамических таблиц и не требует ручного переключения режима реплик.
Создание реплицированной таблицы состоит из следующих шагов:
- Создание специального объекта типа
replicated_table
на мета-кластере. - Создание таблицы-реплики на других кластерах и соответствующие им объекты типа
table_replica
на мета-кластере.
Все таблицы необходимо создавать с одинаковой схемой. Для хранения данных используется медиум ssd_blobs
. Выбор медиума SSD обусловлен необходимыми таймингами и потоком записи в мета-кластер. Сначала все реплики будут созданы в режиме async
. После монтирования таблиц синхронная реплика будет выбрана автоматически.
Описанные выше действия можно выполнять как через CLI — через команды yt create
и yt set
, так и через Python wrapper.
Для данного примера написан скрипт создания необходимых таблиц, который считывает необходимые параметры в аргументах командной строки. Это позволяет использовать скрипт как в production, так и в testing окружении. Параметры скрипта:
meta_cluster
— кластер с реплицированной таблицей.replica_clusters
— кластеры с репликами.path
— путь к рабочей директории проекта.force
— флаг, используемый для того, чтобы пересоздать таблицы, если они уже существуют. Без него изменения не будут внесены.
Пример запуска скрипта после сборки через ya make
:
./create_tables --path //path/to/directory/ --meta_cluster <meta_cluster_name> --replica_clusters <replica1-cluster-name> <replica2-cluster-name> --force
Квоты на сервис
Создайте выделенный tablet_cell_bundle и аккаунт, в котором будет квота на SSD. Для обеспечения отказоустойчивости сервиса, стоит иметь 3 кластера: мета-кластер и 2 кластера-реплики. Но в учебных целях можно все операции можно сделать на одном кластере. Он будет выступать мета-кластером и двумя репликами (синхронной и асинхронной).
Разработка кода сервиса
Установка необходимых пакетов
Установите пакеты Flask и WTForms. WTForms будет использоваться для валидации параметров.
Команды установки:
sudo pip install flask
sudo pip install wtforms
Определение необходимых функций
Для передачи параметров программе будут использоваться переменные окружения. В дальнейшем такой подход позволит легко переносить приложение из testing окружения в production.
Необходимые переменные окружения:
CLUSTER
— имя кластера, на котором хранятся реплицированные таблицы.TABLE_PATH
— путь к директории с таблицами.
Установка необходимых переменных окружения:
export CLUSTER=<cluster-name>
export TABLE_PATH=//path/to/directory
Создайте функцию для добавления информации о комментариях во все таблицы. Ее также можно использовать при редактировании комментариев и при добавлении комментария для примера.
find_comment
Реализация вызова Функция для поиска комментариев в таблице принимает значения topic_id
и parent_path
в качестве аргументов. Это позволяет быстро находить комментарий с помощью метода lookup_rows
, поскольку известны значения всех ключевых колонок таблицы topic_comments
.
Пример получения информации про последние добавленные комментарии:
# -*- coding: utf-8 -*-
import yt.wrapper as yt
import json
import os
def find_comment(topic_id, parent_path, client, table_path):
# В lookup_rows требуется передать значения всех ключевых колонок
return list(client.lookup_rows(
"{}/topic_comments".format(table_path),
[{"topic_id": topic_id, "parent_path": parent_path}],
))
def main():
table_path = os.environ["TABLE_PATH"]
<cluster-name> = os.environ["CLUSTER"]
client = yt.YtClient(cluster_name, config={"backend": "rpc"})
comment_info = find_comment(
topic_id="1dd64501-4131025-562332a3-40507acc",
parent_path="0",
client=client, table_path=table_path,
)
print(json.dumps(comment_info, indent=4))
if __name__ == "__main__":
main()
"""
Вывод программы:
[
{
"update_time": 100000,
"views_count": 0,
"parent_id": 0,
"deleted": false,
"comment_id": 0,
"creation_time": 100000,
"content": "Some comment text",
"parent_path": "0",
"user": "abc",
"topic_id": "1dd64501-4131025-562332a3-40507acc"
}
]
"""
post_comment
Реализация вызова Первая реализация вызова post_comment
представляет собой функцию, принимающую все параметры в аргументах и возвращающую JSON-строку в качестве ответа. Запросы к динамическим таблицам собраны в одну транзакцию. Добавлена обработка исключений при обращении к системе YTsaurus.
# -*- coding: utf-8 -*-
import yt.wrapper as yt
import os
import json
import time
from datetime import datetime
# Вспомогательная функция для получения количества комментариев в топике из таблицы topic_id
# В случае отсутствия заданного топика возвращается None
def get_topic_size(client, table_path, topic_id):
topic_info = list(client.lookup_rows(
"{}/topics".format(table_path), [{"topic_id": topic_id}],
))
if not topic_info:
return None
assert(len(topic_info) == 1)
return topic_info[0]["comment_count"]
def post_comment(client, table_path, user, content, topic_id=None, parent_id=None):
# Необходимо обрабатывать исключение YtResponseError, возникающее если выполнить операцию не удается
try:
# Добавление комментария включает в себя несколько запросов разных типов,
# поэтому требуется собрать их в одну транзакцию, чтобы обеспечить атомарность
with client.Transaction(type="tablet"):
new_topic = not topic_id
if new_topic:
topic_id = yt.common.generate_uuid()
comment_id = 0
parent_id = 0
parent_path = "0"
else:
# Поле comment_id задается равным порядковому номеру комментария в топике
# Этот номер совпадает с текущим размером топика
comment_id = get_topic_size(topic_id)
if not comment_id:
return json.dumps({"error": "There is no topic with id {}".format(topic_id)})
parent_info = find_comment(topic_id, parent_path, client, table_path)
if not parent_info:
return json.dumps({"error" : "There is no comment {} in topic {}".format(parent_id, topic_id)})
parent_id = parent_info[0]["comment_id"]
parent_path = "{}/{}".format(parent_path, comment_id)
creation_time = int(time.mktime(datetime.now().timetuple()))
insert_comments([{
"topic_id": topic_id,
"comment_id": comment_id,
"parent_id": parent_id,
"parent_path": parent_path,
"user": user,
"creation_time": creation_time,
"update_time": creation_time,
"content": content,
"views_count": 0,
"deleted": False,
}], client, table_path)
result = {"comment_id" : comment_id, "new_topic" : new_topic, "parent_path": parent_path}
if new_topic:
result["topic_id"] = topic_id
return json.dumps(result)
except yt.YtResponseError as error:
# У yt.YtResponseError определен метод __str__, возвращающий подробное сообщение об ошибке
json.dumps({"error" : str(error)})
user_comments
Реализация вызова Функция включает в себя только один запрос select_rows
. В качестве основной таблицы используется user_comments
, поскольку она отсортирована по полю user
, и из неё эффективнее всего извлекать комментарии заданного пользователя. К данной таблице с помощью JOIN
подключается дополнительная таблица topic_comments
, чтобы получить полную информацию о комментариях. При этом в секции ON
необходимо явно задать и topic_id
, и parent_path
, чтобы данные из дополнительной таблицы можно было эффективно получить по ключу. В секциях WHERE
и LIMIT
задаются необходимые фильтры, в секции ORDER BY
— порядок, чтобы сперва возвращались самые новые комментарии.
# -*- coding: utf-8 -*-
import yt.wrapper as yt
import os
import json
def get_last_user_comments(client, table_path, user, limit=10, from_time=0):
try:
# В качестве основной таблицы используется user_comments, позволяющая фильтровать записи по полю user
# Через join подключается дополнительная таблица topic_comments,
# которая используется для получения полной информации про комментарий
comments_info = list(client.select_rows(
"""
topic_comments.topic_id as topic_id,
topic_comments.comment_id as comment_id,
topic_comments.content as content,
topic_comments.user as user,
topic_comments.views_count as views_count,
topic_comments.update_time as update_time
from [{0}/user_comments] as user_comments join [{0}/topic_comments] as topic_comments
on (user_comments.topic_id, user_comments.parent_path) =
(topic_comments.topic_id, topic_comments.parent_path)
where user_comments.user = '{1}' and user_comments.update_time >= {2}
order by user_comments.update_time desc
limit {3}""".format(table_path, user, from_time, limit)
))
return json.dumps(comments_info, indent=4)
except yt.YtResponseError as error:
return json.dumps({"error" : str(error)})
Запуск приложения. Приложение создаётся через Blueprint application factory. Для передачи параметров client
и table_path
используется специальный объект Flask.g
, в котором настраивается контекст приложения. Передача настроек для подключения выполняется через переменные окружения HOST
и PORT
. По умолчанию используется http://127.0.0.1:5000/. Так же настраивается логирование: в файл comment_service.log
производится запись одного сообщения в лог в начале выполнения запроса и еще одного при его завершении. В файл driver.log
перенаправляются отладочные логи системы YTsaurus.
Для каждого запроса требуется создавать отдельный клиент YTsaurus, чтобы в дальнейшем было возможно обрабатывать запросы параллельно. При этом для каждого клиента создается собственная версия драйвера, что приводит к дополнительному расходу ресурсов. Чтобы избежать данной проблемы, создается один общий объект драйвера при инициализации приложения, после чего данный объект подключается к каждому создаваемому клиенту путем выставления в клиенте соответствующей опции.
Для валидации параметров запроса используются формы WTForms. Для каждого метода требуется создать отдельный класс-форму, в которой указываются параметры запроса, их тип и т.п.
Предполагается что код сохранён в файле run_application.py
.
export CLUSTER=<cluster-name>
export TABLE_PATH=//path/to/directory
export HOST=127.0.0.1
export PORT=5000
python run_application.py
Примеры запросов
Для отправки запросов используется curl
, для форматирования JSON вывода утилита jq
.
Листинг 22 — Установка утилит
sudo apt-get install curl
sudo apt-get install jq
Листинг 23 — Создание топиков с несколькими комментариями
# Первый топик:
curl - s - X
POST - d
'user=abc&content=comment1' 'http://127.0.0.1:5000/post_comment/' | jq.
{
"comment_id": 0,
"new_topic": true,
"parent_path": "0",
"topic_id": "d178dfb1-b721a596-4358abc9-ed93ae6b"
}
curl - s - X
POST - d
'user=abc&content=comment2&topic_id=d178dfb1-b721a596-4358abc9-ed93ae6b&parent_path=0' 'http://127.0.0.1:5000/post_comment/' | jq.
{
"comment_id": 1,
"new_topic": false,
"parent_path": "0/1"
}
curl - s - X
POST - d
'user=def&content=comment3&topic_id=d178dfb1-b721a596-4358abc9-ed93ae6b&parent_path=0' 'http://127.0.0.1:5000/post_comment/' | jq.
{
"comment_id": 2,
"new_topic": false,
"parent_path": "0/2"
}
# Второй топик:
curl - s - X
POST - d
'user=def&content=comment4' 'http://127.0.0.1:5000/post_comment/' | jq.
{
"comment_id": 0,
"new_topic": true,
"parent_path": "0",
"topic_id": "d9de3eac-fa020dab-4299d3b5-cb5fd5b8"
}
curl - s - X
POST - d
'user=abc&content=comment5&topic_id=d9de3eac-fa020dab-4299d3b5-cb5fd5b8&parent_path=0' 'http://127.0.0.1:5000/post_comment/' | jq.
{
"comment_id": 1,
"new_topic": false,
"parent_path": "0/1"
}
Листинг 24 — Редактирование комментария
# Редактирование второго комментария
curl - s - X
POST - d
'topic_id=d178dfb1-b721a596-4358abc9-ed93ae6b&content=new_comment2&parent_path=0/1' 'http://127.0.0.1:5000/edit_comment/' | jq.
Листинг 25 — Удаление комментария
# Удаление четвертого комментария (корневого во втором топике). Пятый комментарий при этом не удалится.
curl - s - X
POST - d
'topic_id=d9de3eac-fa020dab-4299d3b5-cb5fd5b8&parent_path=0' 'http://127.0.0.1:5000/delete_comment/' | jq.
Листинг 26 — Вывод комментариев
# Вывод двух последних комментариев пользователя abc
curl - s - H @ headers
'http://127.0.0.1:5000/user_comments/?user=abc&limit=2' | jq.
[
{
"comment_id": 1,
"content": "new_comment2",
"topic_id": "d178dfb1-b721a596-4358abc9-ed93ae6b",
"update_time": 1564581207,
"user": "abc",
"views_count": 0
},
{
"comment_id": 1,
"content": "comment5",
"topic_id": "d9de3eac-fa020dab-4299d3b5-cb5fd5b8",
"update_time": 1564581173,
"user": "abc",
"views_count": 0
}
]
Листинг 27 — Вывод поддерева комментариев
# Вывод всех комментариев в первом топике в поддереве второго комментария (куда входит только данный комментарий)
curl - s - H @ headers
'http://127.0.0.1:5000/topic_comments/?topic_id=d178dfb1-b721a596-4358abc9-ed93ae6b&parent_path=0/1' | jq.
[
{
"comment_id": 1,
"content": "new_comment2",
"creation_time": 1564581113,
"deleted": false,
"parent_id": 0,
"update_time": 1564581207,
"user": "abc",
"views_count": 1
}
]
Листинг 28 — Вывод списка топиков
# Вывод всех последних топиков
curl - s - H @ headers
'http://127.0.0.1:5000/last_topics/' | jq.
[
{
"content": "comment4",
"topic_id": "d9de3eac-fa020dab-4299d3b5-cb5fd5b8",
"update_time": 1564582660,
"user": "abc",
"views_count": 0
},
{
"content": "comment1",
"topic_id": "d178dfb1-b721a596-4358abc9-ed93ae6b",
"update_time": 1564581207,
"user": "abc",
"views_count": 0
}
]
Листинг 29 — Примеры неудачных запросов
# Случай указания неполного набора аргументов: не указан topic_id в запросе к topic_comments
curl - s - H @ headers
'http://127.0.0.1:5000/topic_comments/?parent_path=0' | jq.
{
"error": "Parameter topic_id must be specified"
}
Листинг 30 — Пример сообщения об ошибке
# В случае когда невозможно выполнить запрос к системе YTsaurus, вернётся сообщение об ошибке вида:
{
"error": "Received driver response with error\n Internal RPC call failed\n Error getting mount info for _home/dev/username/comment_service/user_comments\n Error communicating with master\n Error resolving path #f0b5-5c916-3f401a9-dda0ef6f\n No such object f0b5-5c916-3f401a9-dda0ef6f\n\n***** Details:\nReceived driver response with error \n origin user_host.domain.com in 2018-09-28T10:33:17.618617Z\nInternal RPC call failed \n origin node001.cluster.domain.com in 2018-09-28T10:33:17.601953Z (pid 745355, tid 4359a68cbe5897dd, fid fffee7436fa6bd03) \n service ApiService \n request_id 3dc-764489c-69ebdf66-942f638f \n connection_id 7-e996d4e8-7b3a20cb-a9093d41 \n address node001.cluster.domain.com:9013 \n realm_id 0-0-0-0 \n method SelectRows\nError getting mount info for _home/dev/username/comment_service/user_comments \n origin node001.cluster.domain.com in 2018-09-28T10:33:17.601569Z (pid 745355, tid 372673d539e8466f, fid fffee7436e8d8609)\nError communicating with master \n origin node001.cluster.domain.com in 2018-09-28T10:33:17.601413Z (pid 745355, tid 372673d539e8466f, fid fffee7436e8d8609)\nError resolving path #f0b5-5c916-3f401a9-dda0ef6f \n code 500 \n origin m01.cluster.domain.com in 2018-09-28T10:33:17.602199Z (pid 471427, tid e8efa5c24fc65652, fid fffe806472536cdb) \n method GetMountInfo\nNo such object f0b5-5c916-3f401a9-dda0ef6f \n code 500 \n origin m01.cluster.domain.com in 2018-09-28T10:33:17.602147Z (pid 471427, tid e8efa5c24fc65652, fid fffe806472536cdb)\n"
}
# Для того, чтобы вывести ошибку в более читаемом виде, можно заменить "jq ." на "jq -r .error"
Received driver response with error
Internal RPC call failed
Error getting mount info for //home/dev/username/comment_service/user_comments
Error communicating with master
Error resolving path #f0b5-5c916-3f401a9-dda0ef6f
No such object f0b5-5c916-3f401a9-dda0ef6f
***** Details:
Received driver response with error
origin user_host.domain.com in 2018-09-28T10:33:31.449413Z
Internal RPC call failed
origin node001.cluster.domain.com in 2018-09-28T10:33:31.434137Z (pid 745355, tid 4359a68cbe5897dd, fid fffee743122257c3)
service ApiService
request_id 3df-add3dc38-3fd547ae-d9ab851f
connection_id 7-e996d4e8-7b3a20cb-a9093d41
address node001.cluster.domain.com:9013
realm_id 0-0-0-0
method SelectRows
Error getting mount info for //home/dev/username/comment_service/user_comments
origin node001.cluster.domain.com in 2018-09-28T10:33:17.601569Z (pid 745355, tid 372673d539e8466f, fid fffee7436e8d8609)
Error communicating with master
origin node001.cluster.domain.com in 2018-09-28T10:33:17.601413Z (pid 745355, tid 372673d539e8466f, fid fffee7436e8d8609)
Error resolving path #f0b5-5c916-3f401a9-dda0ef6f
code 500
origin m01.cluster.domain.com in 2018-09-28T10:33:17.602199Z (pid 471427, tid e8efa5c24fc65652, fid fffe806472536cdb)
method GetMountInfo
No such object f0b5-5c916-3f401a9-dda0ef6f
code 500
origin m01.cluster.domain.com in 2018-09-28T10:33:17.602147Z (pid 471427, tid e8efa5c24fc65652, fid fffe806472536cdb)