Данные проекта
Нода Данные проекта читает, сохраняет и удаляет записи, занимает ключи, обрабатывает списки и запрашивает данные проекта. Что такое хранилище, коллекции и срок хранения -- в разделе Данные.
Назначение
- Хранить состояние между запусками: статусы, настройки, курсоры выгрузки.
- Кэшировать ответы внешних API.
- Читать и сохранять списки записей.
- Выбирать записи по условиям и отсекать повторную обработку событий с помощью «Занять ключ».
Операции
| Операция | Что делает |
|---|---|
| «Прочитать» | Читает запись по ключу; если её нет, ветка «Ошибка». |
| «Сохранить» | Сохраняет или заменяет запись по ключу. |
| «Занять ключ» | Проверяет и занимает ключ одним действием, сохраняя значение. |
| «Удалить» | Удаляет запись по ключу. |
| «Список записей» | Возвращает страницу записей, ключей или количество. |
| «Запрос» | Возвращает результат выборки по условиям. |
| «Прочитать несколько» | Читает записи по списку ключей. |
| «Сохранить несколько» | Сохраняет список элементов как записи. |
| «Занять несколько ключей» | Занимает ключи из списка и отделяет новые от уже занятых. |
Первые четыре операции работают с одной записью; остальные обрабатывают список или страницу и не используются внутри ForEach.
Общие поля
Коллекция
Обязательное имя набора записей: буквы (в том числе кириллица), цифры, _ и -, без пробелов, не более 255 символов. Допускается переменная, например {{input.tenant}}_orders; после подстановки действуют те же правила.
Ключ
Ключ обязателен для операций с одной записью. Он может содержать не более 512 символов; пробелы в начале и конце отбрасываются, регистр учитывается. Каждая переменная должна разрешаться в текст или число, иначе нода перейдёт в ветку «Ошибка». Для объекта или списка используйте поле-идентификатор ({{input.order.id}}) или фильтр | string.
Значение
Используется в операциях сохранения и занятия ключа.
| Содержимое | Что сохраняется |
|---|---|
Одна переменная, например {{node_2.data}} | Значение без преобразования: объект, список, число и строка остаются своими типами. |
JSON, например {"status":"new","attempts":0} | Значение JSON. В JSON-шаблоне текстовые переменные заключайте в кавычки, а объекты, списки, числа, true/false и null подставляйте как JSON. |
| Обычный текст | Строка; если результат подстановки является корректным JSON, он сохраняется как значение JSON. |
| Пустое поле | Пустая строка. |
Если поле состоит из переменной с отсутствующим значением или null, нода перейдёт в ветку «Ошибка». Отсутствующая переменная также приводит к ошибке внутри текста; null внутри текста подставляется как None, а в JSON -- как null. Чтобы число, записанное в переменную-строку, осталось строкой, используйте только переменную ({{input.phone}}), а не текст с ней.
Размер значения ограничен -- см. Ограничения.
Срок хранения, дней
Поле операций, сохраняющих данные. Допустимо целое число от 1 до 365 дней, по умолчанию -- 180. Срок отсчитывается от последнего сохранения: сохранение продлевает его, чтение -- нет. Истёкшая запись сразу недоступна для чтения, а её ключ можно занять снова.
Прочитать
Читает запись по коллекции и ключу. Поля: «Коллекция», «Ключ».
Основные поля результата:
{
"found": true,
"value": { "status": "paid" },
"claimedBy": "exec_a3B9kLm2:node_2",
"createdAt": "2026-09-28T10:15:04.512000+00:00",
"expiresAt": "2027-03-27T10:15:04.512000+00:00"
}
Если записи нет или срок истёк, нода переходит в ветку «Ошибка» с errorType: "NotFound" и found: false. Отличайте это от других ошибок по errorType.
Сохранить
Сохраняет значение по ключу и заменяет прежнюю запись целиком. Поля: «Коллекция», «Ключ», «Значение», «Срок хранения, дней».
Результат (основные поля):
{"key":"order-1042","value":{"status":"paid"},"claimedBy":"exec_a3B9kLm2:node_5"}
«Сохранить» также становится владельцем ключа (claimedBy); последующая попытка «Занять ключ» другим владельцем вернёт claimed = false.
Занять ключ
«Занять ключ» атомарно проверяет наличие записи и, если ключ свободен или запись истекла, создаёт запись с указанным значением. Проверка и создание происходят как одно действие: при одновременном занятии ключа только один вызов получит claimed = true.
Поля: «Коллекция», «Ключ», «Значение», «Срок хранения, дней».
| Ситуация | Результат |
|---|---|
| Записи нет или её срок истёк | claimed = true; сохраняются заданное значение и срок. |
| Ключ занят другим запуском того же или другого процесса, другой нодой в том же запуске, другой итерацией ForEach или агентом | claimed = false; существующая запись не меняется и возвращается в результате. |
| Та же нода повторно выполняется в том же запуске | claimed = true; значение и срок перезаписываются. |
Запись, созданная «Сохранить», «Сохранить несколько» или инструментом агента, тоже занимает ключ. При claimed = false результат содержит существующие value, claimedBy, createdAt и expiresAt; claimedBy для процесса имеет вид ID запуска:ID ноды, для агента -- ID его запуска. Срок хранения этой записи не продлевается.
{"claimed":false,"value":{"status":"processing"},"claimedBy":"exec_Zq81xYt0:node_2","createdAt":"2026-09-28T09:58:40Z","expiresAt":"2027-03-27T09:58:40Z"}
Оба значения claimed ведут в выход «Успех». После ноды проверяйте node_2.claimed == true в If/Else и обрабатывайте событие только при true.
Выбирайте стабильный идентификатор события во внешней системе, а не время, случайное значение или ID запуска. Если идентификаторы из разных источников могут совпасть, добавьте префикс источника; для разных действий над одним событием используйте разные коллекции или префиксы (invoice-…, sms-…). Срок хранения выбирайте больше периода, в течение которого отправитель может повторить событие. Ключи сравниваются точно; переменная ключа не должна быть пустой или null, иначе нода перейдёт в ветку «Ошибка», а не займёт общий ключ None.
Пример обработки вебхука:
Если обработка завершается ошибкой после занятия, ключ остаётся занят. Освобождайте его «Удалить» только если внешнее действие ещё не произошло; ошибку процесса можно обработать через обработчик ошибок. Рекомендуемый вариант -- сохранить при занятии {"status":"processing"}, а после обработки заменить на {"status":"done"}; в ветке claimed = false проверяйте node_2.value.status.
При TimeoutError или DatastoreUnavailable ключ мог успеть заняться, поэтому в ветке «Ошибка» событие не обрабатывайте. При очень плотной одновременной доставке также возможен DatastoreConflict.
Агенты используют общие с процессами ключи; каждый вызов инструмента агента считается отдельным владельцем -- см. инструменты хранилища агентов.
Удалить
Удаляет запись по ключу. Поля: «Коллекция», «Ключ». Операция завершается через «Успех», даже если записи нет:
{"deleted":false}
Если запись существовала, deleted: true; если записи не было или срок истёк, deleted: false. После удаления ключ можно занять снова.
Список записей
Читает страницу коллекции, упорядоченную по ключам как по строкам: например, order-10 идёт раньше order-9. Для числового порядка дополняйте номера нулями.
Поля
| Поле | Значение |
|---|---|
| «Начало ключа» | Необязательный префикс ключей. |
| «Что вернуть» | «Записи», «Только ключи» или «Только количество». |
| «Записей на странице» | От 1 до 1000, по умолчанию 100. |
| «Курсор страницы» | Пустой курсор начинает чтение с первой страницы; для следующей передайте nextCursor. |
| «Если страница не поместилась» | Ошибка или возврат записей, которые поместились. |
В «Начале ключа» и «Курсоре страницы» допускаются переменные. Если префикс состоит только из переменной без значения или равной null, читается вся коллекция; пустая переменная внутри текста приводит к «Ошибке».
Основные поля результата: items содержит записи или ключи, returned -- число элементов на странице, nextCursor -- курсор следующей страницы или null. В режиме «Только количество» возвращается count; complete: false означает, что число приблизительное.
Передавайте nextCursor в следующий вызов и продолжайте, пока он не станет null. Пустая или null переменная-курсор начинает чтение с первой страницы, поэтому проверяйте nextCursor != null; курсор от другой коллекции даёт InvalidCursor. Записи, добавленные во время обхода, попадут в выдачу, если их ключ больше последнего прочитанного.
Результат ограничен примерно 1 000 000 символов; записи никогда не обрезаются. Поле «Если страница не поместилась» задаёт, что делать при превышении: перейти в ветку «Ошибка» с ResultTooLarge (записи страницы не возвращаются -- уменьшите «Записей на странице» и прочитайте ту же страницу заново) или вернуть поместившиеся записи с complete: false и nextCursor.
Запрос
«Запрос» проверяет наличие, считает, выбирает записи по условиям или подводит итоги по группам. Конструктор и типы условий описаны в разделе Запросы к коллекции.
В значениях условий можно использовать переменные; также переменные допускаются в «Начале ключа», «Курсоре страницы» и «Записей на странице». Если переменная в префиксе или курсоре отсутствует, пуста или равна null, нода переходит в ветку «Ошибка» (в отличие от «Списка записей»). Если коллекция задана переменной, конструктор показывает поля всех коллекций проекта; отсутствие заданного поля в выбранной коллекции приводит к ошибке. Запрос должен завершиться за 5 секунд (QueryTimeout); запрос, которому пришлось бы прочитать слишком много записей, не выполняется (ScanTooLarge).
| Режим | Поля результата |
|---|---|
| «Проверка наличия» | exists |
| «Количество» | count |
| «Ключи» | keys, nextCursor |
| «Записи» | items, nextCursor |
| «Итоги по группам» | groups с key, count и metrics (fn, field, value) |
Ключи в результате запроса находятся в keys, а не в items.
Прочитать несколько
Читает до 500 ключей за вызов. Поля: «Коллекция», «Ключи».
В «Ключах» можно задать переменную со списком строк или чисел, JSON-массив либо указать по одному ключу в строке. Список ключей из «Списка записей» в режиме «Только ключи» находится в items. Повторы читаются один раз; пустой элемент, null, объект или более 500 ключей приводят к «Ошибке».
Результат содержит items с найденными записями, found с их количеством и missing с отсутствующими или истёкшими ключами. Отсутствующие записи не переводят операцию в ветку «Ошибка»; если весь ответ не помещается, возвращается ResultTooLarge.
Сохранить несколько
Сохраняет до 1000 элементов за вызов. Поля: «Коллекция», «Элементы», «Шаблон ключа», «Шаблон значения», «Срок хранения, дней», «Продолжать при ошибках».
«Элементы» задаются переменной со списком или JSON-массивом; пустой список допустим. В шаблоне ключа _item -- текущий элемент, _index -- его номер с нуля. Если «Шаблон значения» пуст, сохраняется сам элемент; иначе шаблон понимается по тем же правилам, что поле «Значение».
Все элементы проверяются до записи. Если ключи повторяются, весь вызов завершается с InvalidKey и ничего не сохраняется. При выключенном «Продолжать при ошибках» вызов останавливается на первой ошибке с BulkWriteFailed, но записи, сохранённые до неё, остаются сохранёнными. При включённом параметре операция завершается через «Успех»: written -- сохранённые записи, failed и failures -- неудачи, complete -- сохранены ли все записи.
Занять несколько ключей
Занимает ключ для каждого элемента списка по тем же правилам, что и «Занять ключ». Поля: «Коллекция», «Элементы», «Шаблон ключа», «Шаблон значения», «Срок хранения, дней», «Продолжать при ошибках».
Результат содержит claimed -- ключи, занятые этим вызовом, и duplicates -- уже занятые ключи с claimedBy и createdAt. Если в списке есть повторяющиеся ключи, весь вызов завершается с InvalidKey, и ни один ключ не занимается. При выключенном «Продолжать при ошибках» вызов останавливается на первой ошибке с BulkClaimFailed; ключи, занятые до неё, остаются занятыми и перечислены в claimed. При включённом параметре операция продолжается и возвращает «Успех» с claimed, duplicates, failed и failures.
Например, после получения массива HTTP-событий можно занять их ключи одной операцией, а затем передать claimed в ForEach для обработки новых событий.
Ошибки
Для ветвления используйте errorType, а не текст error.
errorType | Значение |
|---|---|
NotFound | Запись отсутствует или истекла (операция «Прочитать»). |
UnresolvedVariable | Переменная не найдена, пуста или равна null. |
InvalidKey | Недопустимы коллекция, ключ (в том числе пустой после подстановки), шаблон или элементы ключей; повтор ключа в пакетной операции. |
ConfigurationError, InvalidTtl | Не заполнены обязательные поля, неверна настройка операции или срок хранения. |
ValueTooLarge, TooManyItems, QuotaExceeded | Превышен лимит значения, списка или хранилища проекта; см. Ограничения. |
ResultTooLarge, PageTooLarge | Результат или страница не помещается в ответ. |
InvalidCursor | Курсор повреждён или относится к другой коллекции. |
BulkWriteFailed, BulkClaimFailed | Пакетная операция остановилась на ошибке при выключенном «Продолжать при ошибках». |
TimeoutError | Операция не завершилась за отведённое время. |
DatastoreUnavailable, DatastoreConflict, DatastoreError | Ошибка или конфликт хранилища. |
DatastoreReadOnly, DatastoreDisabled | Сохранение для проекта отключено или хранилище не подключено в этой установке. |
QueryTimeout, ScanTooLarge, UnknownField, InvalidFilter | Ошибка выполнения или настройки «Запроса». |
Подключайте выход «Ошибка»: иначе при ошибке процесс остановится на этой ноде. Автоматические повторы для ноды по умолчанию отключены -- что делать при ошибке, решает эта ветка.
Как использовать результат
Результат доступен по ID ноды, например node_2:
| Выражение | Значение |
|---|---|
{{node_2.value}}, {{node_2.value.status}} | Значение записи и его поля. |
{{node_2.claimed}} | Результат «Занять ключ» или список занятых ключей в «Занять несколько ключей». |
{{node_2.items}} | Записи или ключи страницы («Список записей», «Запрос» в режиме «Записи») или найденные записи «Прочитать несколько». |
{{node_2.missing}} | Ненайденные ключи «Прочитать несколько». |
{{node_2.nextCursor}} | Курсор следующей страницы. |
{{node_2.errorType}} | Тип ошибки в ветке «Ошибка». |
В условиях If/Else фигурные скобки не нужны, например node_2.claimed == true.
Внутри ForEach
В ForEach доступны только операции с одной записью: «Прочитать», «Сохранить», «Занять ключ» и «Удалить». Ключ «Сохранить» и «Занять ключ» должен зависеть от элемента -- ссылаться на {{_loop_item...}} или {{_loop_index}}, например order-{{_loop_item.id}}, иначе все элементы попадут в одну запись. Каждая итерация -- отдельный владелец ключа: если два элемента дают один ключ, claimed = true получит только одна из итераций. Ошибка отдельной итерации даёт null в results и сведения об ошибке в errors.
Подключения
- Вход: один вход.
- Выход «Успех»: операция выполнена. Сюда же относятся
claimed = false,deleted = falseи ненайденные ключи в «Прочитать несколько» -- это не ошибки. - Выход «Ошибка»: операция завершилась с ошибкой.
Примеры использования
Кэш ответа API на сутки
Прочитать: коллекция weather_cache, ключ {{input.city}}
Успех → использовать node_2.value
Ошибка → HTTP-запрос → Сохранить ответ с тем же ключом, срок 1 день
Выгрузка коллекции страницами
В первый запуск курсора нет: «Прочитать» уходит в ветку «Ошибка», курсор без значения -- и читается первая страница.
Сохранение массива из API
Сохранить несколько: элементы {{node_2.data.items}}
Шаблон ключа: sku-{{_item.sku}}
Шаблон значения: пусто (сохранить элемент целиком)