diff --git a/docs/en/01-quickstart.md b/docs/en/01-quickstart.md index 90565769..f8847548 100644 --- a/docs/en/01-quickstart.md +++ b/docs/en/01-quickstart.md @@ -186,7 +186,7 @@ services: seq-ui: image: ghcr.io/ozontech/seq-ui:latest volumes: - - ${PWD}/config.seq-ui.yaml:/seq-ui-server/config.yaml + - ${PWD}/config.seq-ui.yaml:/seq-ui/config.yaml ports: - "5555:5555" # Default HTTP port - "5556:5556" # Default gRPC port diff --git a/docs/en/10-public-api.md b/docs/en/10-public-api.md index 8a33d285..3c0361ab 100644 --- a/docs/en/10-public-api.md +++ b/docs/en/10-public-api.md @@ -21,13 +21,13 @@ seq-db is compatible with Elasticsearch bulk API Returns a hardcoded ES response that specifies the Elasticsearch version used. This feature allows the usage of seq-db as an Elasticsearch output for logstash, filebeat, and other log shippers. -#### Example request: +Example request: ```bash curl -X GET http://localhost:9002/ ``` -#### Example successful response: +Example successful response: ```json { @@ -42,7 +42,7 @@ curl -X GET http://localhost:9002/ Receives body, parses it to docs and metas and writes to stores via internal API. -#### Example request: +Example request: ```bash curl -X POST http://localhost:9002/_bulk -d ' @@ -69,7 +69,7 @@ curl -X POST http://localhost:9002/_bulk -d ' ' ``` -#### Example successful response: +Example successful response: ```json { @@ -112,7 +112,7 @@ curl -v -X POST http://localhost:9002/_bulk -d ' We get -``` +```text processing doc: unexpected end of string near `", "request_time": "123` ^ ``` @@ -734,3 +734,271 @@ Example successful response: } } ``` + +## Async search gRPC API + +### `/StartAsyncSearch` + +Start an async search. + +Returns an ID that can later be used to fetch results, cancel, or delete the search. + +Similar to [`/ComplexSearch`](#complexsearch), but with additional fields: `retention` and `with_docs`. +`retention` determines how long the data will be available. +`with_docs` determines whether document IDs that match the query are found and saved. +If `with_docs` is true, `with_total` is automatically set to true. + +Example request: + +```bash +grpcurl -plaintext -d ' +{ + "retention": "3600s", + "query": { + "from": "2025-07-01T05:20:00Z", + "to": "2025-09-01T05:21:00Z", + "query": "message:error | fields level, message" + }, + "hist": { + "interval": "1ms" + }, + "aggs": [ + { + "group_by": "k8s_pod", + "field": "request_time", + "func": "AGG_FUNC_QUANTILE", + "quantiles": [ + 0.2, + 0.8, + 0.95 + ] + } + ], + "with_docs": true +}' localhost:9004 seqproxyapi.v1.SeqProxyApi/StartAsyncSearch +``` + +Example successful response: + +```json +{ + "search_id": "c28c97d1-117a-45dc-a0cf-d080f22b2a10" +} +``` + +### `/FetchAsyncSearchResult` + +Fetch async search result. + +Returns execution result based on a search parameters from [`/StartAsyncSearch`](#startasyncsearch) request. +A partial response can be returned if the search is not yet complete. The `status` field indicates the current status. +Available statuses: `AsyncSearchStatusInProgress`, `AsyncSearchStatusDone`, `AsyncSearchStatusCanceled`, `AsyncSearchStatusError`. + +Example request: + +```bash +grpcurl -plaintext -d ' +{ + "search_id": "c28c97d1-117a-45dc-a0cf-d080f22b2a10", + "size": 2, + "offset": 0, + "order": 0 +}' localhost:9004 seqproxyapi.v1.SeqProxyApi/FetchAsyncSearchResult +``` + +Example successful response: + +```json +{ + "status": "AsyncSearchStatusDone", + "request": { + "retention": "3600s", + "query": { + "query": "message:peggy | fields level, message", + "from": "2025-08-01T05:20:00Z", + "to": "2025-09-01T05:21:00Z" + }, + "hist": { + "interval": "1ms" + }, + "aggs": [ + { + "group_by": "k8s_pod", + "field": "request_time", + "func": "AGG_FUNC_QUANTILE", + "quantiles": [ + 0.2, + 0.8, + 0.95 + ] + } + ], + "with_docs": true + }, + "response": { + "total": 12, + "docs": [ + { + "id": "c09e878998010000-9102a3cb83f156f2", + "data": { + "message": "some error", + "level": 3 + }, + "time": "2025-08-08T11:53:43.36Z" + }, + { + "id": "c09e878998010000-91026cf9cf3386e3", + "data": { + "message": "error 2", + "level": 3 + }, + "time": "2025-08-08T11:53:43.36Z" + } + ], + "aggs": [ + { + "buckets": [ + { + "docCount": "6", + "key": "seq-proxy", + "value": 6, + "quantiles": [ + 6, + 8, + 9 + ] + } + ] + } + ], + "hist": { + "buckets": [ + { + "docCount": "1", + "ts": "2024-12-23T18:00:36.357Z" + }, + { + "docCount": "4", + "ts": "2024-12-23T18:23:41.349Z" + } + ] + } + }, + "progress": 1, + "disk_usage": "537", + "started_at": "2025-08-08T11:53:52.542336Z", + "expires_at": "2025-08-08T12:53:52.542336Z" +} +``` + +> `response.docs` and `response.total` fields will be saved and returned only if `with_docs` is true in the [`/StartAsyncSearch`](#startasyncsearch) request + +### `/GetAsyncSearchesList` + +Get async searches list. + +Returns list of available async searches filtered out based on fields from request. +Empty request `{}` returns all available at the moment searches. + +Example request: + +```bash +grpcurl -plaintext -d ' +{ + "status": null, + "size": 2, + "offset": 0, + "ids": ["c28c97d1-117a-45dc-a0cf-d080f22b2a10", "8e68d3d7-8e85-44a9-9b09-8de248a3b414"] +}' localhost:9004 seqproxyapi.v1.SeqProxyApi/GetAsyncSearchesList +``` + +Example successful response: + +```json +{ + "searches": [ + { + "searchId": "c28c97d1-117a-45dc-a0cf-d080f22b2a10", + "status": "AsyncSearchStatusInProgress", + "request": { + "retention": "3600s", + "query": { + "query": "message:error | fields level, message", + "from": "2025-07-01T05:20:00Z", + "to": "2025-08-01T05:21:00Z" + }, + "aggs": [], + "with_docs": true + }, + "startedAt": "2025-08-08T11:49:20.707200Z", + "expiresAt": "2025-08-08T12:49:20.707200Z", + "progress": 0.89, + "diskUsage": "282" + }, + { + "searchId": "8e68d3d7-8e85-44a9-9b09-8de248a3b414", + "status": "AsyncSearchStatusDone", + "request": { + "retention": "3600s", + "query": { + "query": "message:error", + "from": "2025-07-01T05:20:00Z", + "to": "2025-08-01T05:21:00Z" + }, + "aggs": [], + "with_docs": false + }, + "startedAt": "2025-08-08T11:48:49.488244Z", + "expiresAt": "2025-08-08T12:48:49.488244Z", + "progress": 1, + "diskUsage": "283" + } + ] +} +``` + +### `/CancelAsyncSearch` + +Cancel async search. + +Cancels async search. Canceled search's execution will be stopped. Can't cancel finished request. + +Example request: + +```bash +grpcurl -plaintext -d ' +{ + "search_id": "c28c97d1-117a-45dc-a0cf-d080f22b2a10" +}' localhost:9004 seqproxyapi.v1.SeqProxyApi/CancelAsyncSearch +``` + +Example successful response: + +```json +{} +``` + +Empty response is successful. + +### `/DeleteAsyncSearch` + +Delete async search. + +Marks async search as expired, the data will be removed the next maintenance iteration. + +Example request: + +```bash +grpcurl -plaintext -d ' +{ + "search_id": "c28c97d1-117a-45dc-a0cf-d080f22b2a10" +}' localhost:9004 seqproxyapi.v1.SeqProxyApi/DeleteAsyncSearch +``` + +Example successful response: + +```json +{} +``` + +Empty response is successful. diff --git a/docs/en/12-async-search.md b/docs/en/12-async-search.md new file mode 100644 index 00000000..34e16d6a --- /dev/null +++ b/docs/en/12-async-search.md @@ -0,0 +1,31 @@ +--- +id: async-search +--- + +# Async search + +Async searches provide the ability to run search requests in the background. + +This ability is especially valuable when executing long-running queries that take longer to complete. +Usually this queries are aggregations and histograms. +Async searches can also be used to search documents using `with_docs` field in the [`StartAsyncSearch`](10-public-api.md#startasyncsearch) request, though primary use case is aggregations. + +Read [API docs](10-public-api.md#async-search-grpc-api) for more info about the public API. + +Async search data is persisted on disk for the specified `retention` time. +Minimum retention is 5 minutes and maximum is 30 days. +Retention is set by the `retention` field in the [`StartAsyncSearch`](10-public-api.md#startasyncsearch) request. +Data is deleted after the retention period expires. + +When data size exceeds the limits, read-only mode is enabled: new async searches are rejected, and unfinished searches are suspended until disk space is freed by deleting older searches by retention. + +## Configuration + +Configuration parameters are: + +* `data_dir` [string] - directory that contains async search data. By default, it is a subdirectory of `config.storage.data_dir`. +* `concurrency` [int] - number of concurrent async search executions. +* `max_total_size` [bytes] - maximum total size of all async searches data per store. +* `max_size_per_request` [bytes] - maximum total size of a single async search data per store. + +Configuration parameters are part of `async_search` object in the config file. diff --git a/docs/ru/01-quickstart.md b/docs/ru/01-quickstart.md index 6c9fce9d..3264c4ee 100644 --- a/docs/ru/01-quickstart.md +++ b/docs/ru/01-quickstart.md @@ -186,7 +186,7 @@ services: seq-ui: image: ghcr.io/ozontech/seq-ui:v0.37.0 volumes: - - ${PWD}/config.seq-ui.yaml:/seq-ui-server/config.yaml + - ${PWD}/config.seq-ui.yaml:/seq-ui/config.yaml ports: - "5555:5555" # Default HTTP port - "5556:5556" # Default gRPC port diff --git a/docs/ru/10-public-api.md b/docs/ru/10-public-api.md index c9bf733f..8dc6c2e3 100644 --- a/docs/ru/10-public-api.md +++ b/docs/ru/10-public-api.md @@ -21,13 +21,13 @@ seq-db is compatible with Elasticsearch bulk API Returns a hardcoded ES response that specifies the Elasticsearch version used. This feature allows the usage of seq-db as an Elasticsearch output for logstash, filebeat, and other log shippers. -#### Example request: +Example request: ```bash curl -X GET http://localhost:9002/ ``` -#### Example successful response: +Example successful response: ```json { @@ -42,7 +42,7 @@ curl -X GET http://localhost:9002/ Receives body, parses it to docs and metas and writes to stores via internal API. -#### Example request: +Example request: ```bash curl -X POST http://localhost:9002/_bulk -d ' @@ -69,7 +69,7 @@ curl -X POST http://localhost:9002/_bulk -d ' ' ``` -#### Example successful response: +Example successful response: ```json { @@ -112,7 +112,7 @@ curl -v -X POST http://localhost:9002/_bulk -d ' We get -``` +```text processing doc: unexpected end of string near `", "request_time": "123` ^ ``` @@ -186,8 +186,7 @@ we get - через отдельный gRPC обработчик: [`GetAggregation`](#getaggregation) - вместе с поиском и гистограммами: [`ComplexSearch`](#complexsearch) -> В примерах используется API `GetAggregation`, -которая по структуре запроса и ответа совпадает с `ComplexSearch`. +> В примерах используется API `GetAggregation`, которая по структуре запроса и ответа совпадает с `ComplexSearch`. Поддерживаемые функции агрегации: @@ -347,8 +346,7 @@ we get ##### QUANTILE -> Функцию агрегации QUANTILE также можно применять с группировкой по полю, -используя поле group_by. +> Функцию агрегации QUANTILE также можно применять с группировкой по полю, используя поле group_by. **Запрос:** @@ -736,3 +734,271 @@ Example successful response: } } ``` + +## Async search gRPC API + +### `/StartAsyncSearch` + +Начать асинхронный поиск. + +Возвращает идентификатор, с помощью которого можно получить результат выполнения поиска, отменить и удалить поиск. + +Запрос похож на [`/ComplexSearch`](#complexsearch), но есть дополнительные поля: +`retention` - определяет, как долго будут доступны данные поиска. +И `with_docs` - определяет, будут ли найдены и сохранены идентификаторы документов, удовлетворяющих поисковому запросу. +`with_total` будет автоматически установлен, если в запросе указан `with_docs: true` + +Пример запроса: + +```bash +grpcurl -plaintext -d ' +{ + "retention": "3600s", + "query": { + "from": "2025-07-01T05:20:00Z", + "to": "2025-09-01T05:21:00Z", + "query": "message:error | fields level, message" + }, + "hist": { + "interval": "1ms" + }, + "aggs": [ + { + "group_by": "k8s_pod", + "field": "request_time", + "func": "AGG_FUNC_QUANTILE", + "quantiles": [ + 0.2, + 0.8, + 0.95 + ] + } + ], + "with_docs": true +}' localhost:9004 seqproxyapi.v1.SeqProxyApi/StartAsyncSearch +``` + +Пример успешного ответа: + +```json +{ + "search_id": "c28c97d1-117a-45dc-a0cf-d080f22b2a10" +} +``` + +### `/FetchAsyncSearchResult` + +Получить результат выполнения асинхронного поиска. + +Возвращает результат выполнения, соответствующий поисковому запросу [`/StartAsyncSearch`](#startasyncsearch). +Может быть возвращен результат частичного выполнения, если поиск еще не завершен. Поле `status` показывает текущий статус поиска. +Возможные статусы: `AsyncSearchStatusInProgress`, `AsyncSearchStatusDone`, `AsyncSearchStatusCanceled`, `AsyncSearchStatusError`. + +Пример запроса: + +```bash +grpcurl -plaintext -d ' +{ + "search_id": "c28c97d1-117a-45dc-a0cf-d080f22b2a10", + "size": 2, + "offset": 0, + "order": 0 +}' localhost:9004 seqproxyapi.v1.SeqProxyApi/FetchAsyncSearchResult +``` + +Пример успешного ответа: + +```json +{ + "status": "AsyncSearchStatusDone", + "request": { + "retention": "3600s", + "query": { + "query": "message:peggy | fields level, message", + "from": "2025-08-01T05:20:00Z", + "to": "2025-09-01T05:21:00Z" + }, + "hist": { + "interval": "1ms" + }, + "aggs": [ + { + "group_by": "k8s_pod", + "field": "request_time", + "func": "AGG_FUNC_QUANTILE", + "quantiles": [ + 0.2, + 0.8, + 0.95 + ] + } + ], + "with_docs": true + }, + "response": { + "total": 12, + "docs": [ + { + "id": "c09e878998010000-9102a3cb83f156f2", + "data": { + "message": "some error", + "level": 3 + }, + "time": "2025-08-08T11:53:43.36Z" + }, + { + "id": "c09e878998010000-91026cf9cf3386e3", + "data": { + "message": "error 2", + "level": 3 + }, + "time": "2025-08-08T11:53:43.36Z" + } + ], + "aggs": [ + { + "buckets": [ + { + "docCount": "6", + "key": "seq-proxy", + "value": 6, + "quantiles": [ + 6, + 8, + 9 + ] + } + ] + } + ], + "hist": { + "buckets": [ + { + "docCount": "1", + "ts": "2024-12-23T18:00:36.357Z" + }, + { + "docCount": "4", + "ts": "2024-12-23T18:23:41.349Z" + } + ] + } + }, + "progress": 1, + "disk_usage": "537", + "started_at": "2025-08-08T11:53:52.542336Z", + "expires_at": "2025-08-08T12:53:52.542336Z" +} +``` + +> Поля `response.docs` и `response.total` будут сохранены и возвращены только если в запросе [`/StartAsyncSearch`](#startasyncsearch) было указано `with_docs: true` + +### `/GetAsyncSearchesList` + +Получить список асинхронных поисков. + +Возвращает список асинхронных поисков, отфильтрованный в соответствии с полями запроса. +Пустой запрос вернет все доступные поиски. + +Пример запроса: + +```bash +grpcurl -plaintext -d ' +{ + "status": null, + "size": 2, + "offset": 0, + "ids": ["c28c97d1-117a-45dc-a0cf-d080f22b2a10", "8e68d3d7-8e85-44a9-9b09-8de248a3b414"] +}' localhost:9004 seqproxyapi.v1.SeqProxyApi/GetAsyncSearchesList +``` + +Пример успешного ответа: + +```json +{ + "searches": [ + { + "searchId": "c28c97d1-117a-45dc-a0cf-d080f22b2a10", + "status": "AsyncSearchStatusInProgress", + "request": { + "retention": "3600s", + "query": { + "query": "message:error | fields level, message", + "from": "2025-07-01T05:20:00Z", + "to": "2025-08-01T05:21:00Z" + }, + "aggs": [], + "with_docs": true + }, + "startedAt": "2025-08-08T11:49:20.707200Z", + "expiresAt": "2025-08-08T12:49:20.707200Z", + "progress": 0.89, + "diskUsage": "282" + }, + { + "searchId": "8e68d3d7-8e85-44a9-9b09-8de248a3b414", + "status": "AsyncSearchStatusDone", + "request": { + "retention": "3600s", + "query": { + "query": "message:error", + "from": "2025-07-01T05:20:00Z", + "to": "2025-08-01T05:21:00Z" + }, + "aggs": [], + "with_docs": false + }, + "startedAt": "2025-08-08T11:48:49.488244Z", + "expiresAt": "2025-08-08T12:48:49.488244Z", + "progress": 1, + "diskUsage": "283" + } + ] +} +``` + +### `/CancelAsyncSearch` + +Отменить асинхронный поиск. + +Отменяет асинхронный поиск. Выполнение отмененного поиска будет остановлено. Нельзя отменить завершенный поиск. + +Пример запроса: + +```bash +grpcurl -plaintext -d ' +{ + "search_id": "c28c97d1-117a-45dc-a0cf-d080f22b2a10" +}' localhost:9004 seqproxyapi.v1.SeqProxyApi/CancelAsyncSearch +``` + +Пример успешного ответа: + +```json +{} +``` + +В случае успеха будет возвращен пустой ответ. + +### `/DeleteAsyncSearch` + +Удалить асинхронный поиск. + +Помечает поиск истекшим. Данные поиска будут удалены при следующей итерации обслуживания. + +Пример запроса: + +```bash +grpcurl -plaintext -d ' +{ + "search_id": "c28c97d1-117a-45dc-a0cf-d080f22b2a10" +}' localhost:9004 seqproxyapi.v1.SeqProxyApi/DeleteAsyncSearch +``` + +Пример успешного ответа: + +```json +{} +``` + +В случае успеха будет возвращен пустой ответ. diff --git a/docs/ru/12-async-search.md b/docs/ru/12-async-search.md new file mode 100644 index 00000000..9ef5bb34 --- /dev/null +++ b/docs/ru/12-async-search.md @@ -0,0 +1,29 @@ +--- +id: async-search +--- + +# Асинхронный поиск + +Асинхронные поиски позволяют запустить поиск в фоновом режиме. + +Это особенно полезно, когда нужно выполнить поиск, время выполнения которого достаточно большое: обычно это агрегации и гистограммы. +С помощью асинхронного поиска можно искать и документы, указав поле `with_docs` в запросе [`StartAsyncSearch`](10-public-api.md#startasyncsearch), но все-таки основной способ использования это гистограммы. + +Больше информации о публичном API асинхронных поисков можно найти в [документации](10-public-api.md#async-search-grpc-api) + +Данные асинхронных поисков сохраняются на диск на время, указанное в поле `retention` запроса [`StartAsyncSearch`](10-public-api.md#startasyncsearch) +Минимальное время жизни поиска - 5 минут, маскимальное - 30 дней. +Данные поиска будут удалены после того, как выйдет время его жизни. + +Если размер данных превышает лимиты, то включается режим чтения: запрещено создавать новые асинхронные поиски, выполнение существующих незавершенных поисков будет заморожено до момента, когда появится свободное место после удаления данных истекших поисков. + +## Конфигурация + +Есть следующие параметры конфигурации: + +* `data_dir` [string] - определяет директорию, в которой будут лежать данные асинхронных поисков. По умолчанию равен поддиректории в `config.storage.data_dir`. +* `concurrency` [int] - определяет максимальное количество одновременно выполняемых асинхронных поисков. +* `max_total_size` [bytes] - определяет максимальный размер данных всех асинхронных поисков на одном сторе. +* `max_size_per_request` [bytes] - определяет максимальный размер данных одного поиска на одном сторе. + +Параметры конфигурации содержатся внутри объекта `async_search` в конфигурационном файле.