2025, Oct 15 14:18

Lambda UDF в Redshift: как исправить ошибку Invalid External Function Response и вернуть корректный JSON

Как исправить Invalid External Function Response в Amazon Redshift при вызове Lambda UDF: правильный JSON-ответ с ключом results и совпадением длины массивов.

Запуск Lambda UDF из Amazon Redshift и получение ошибки «Invalid External Function Response» — частая ловушка. Код в Lambda выполняется, логи печатаются, но Redshift отказывается разбирать ответ. Почти всегда причина в форме и типе возвращаемых данных, а не в бизнес-логике.

Постановка задачи

Рассмотрим минимальный обработчик, который возвращает константу, и внешнюю функцию, объявленную в Redshift. Наивная реализация может выглядеть так.

def handle(event_obj, ctx_obj):
    print("Got payload:", event_obj)

    num = 42
    print("Sending value:", num, "kind:", type(num))

    payload = [[num]]
    print("Payload to send back:", payload)

    return payload

И внешняя функция в Redshift:

CREATE OR REPLACE EXTERNAL FUNCTION demo_fn()
RETURNS INT
VOLATILE
LAMBDA 'arn:aws:.........'
IAM_ROLE 'arn:aws:iam::........';

Вызов SELECT demo_fn(); приводит к ошибке:

ERROR: Invalid External Function Response
Detail:
Cannot parse External Function response

Что на самом деле не так

Lambda UDF в Redshift должны возвращать строку JSON строго определённой структуры. На верхнем уровне должен быть поле results, а его значением — массив с ровно одним элементом на каждую входную строку, которую Redshift группирует в event["arguments"]. Если обработчик возвращает голое значение, список Python или что‑то иное, отличное от JSON‑строки вида {"results": [...]}, Redshift не сможет это разобрать и выдаст ошибку «Cannot parse External Function response».

Иначе говоря, контракт жёсткий: верните json.dumps({"results": [...]}) так, чтобы длина массива совпадала с len(event["arguments"]). Даже у функции без аргументов Redshift присылает строки в event["arguments"], например [ [] ] при выполнении SELECT demo_fn(); — значит, в results должен быть один элемент для этой единственной строки.

Решение

Возвращайте JSON‑строку с ожидаемой обёрткой и следите, чтобы количество выходных значений совпадало с числом входов, которые прислал Redshift.

import json

def handle(event_obj, ctx_obj):
    # Redshift группирует строки в event_obj["arguments"]
    items = event_obj["arguments"]          # например, [ [] ] при SELECT demo_fn();
    outputs = [42] * len(items)              # по одному выходному значению на каждую входную строку
    return json.dumps({"results": outputs})

Этот минимальный обработчик удовлетворяет контракту для UDF без аргументов, возвращающей INT. SQL‑определение менять не нужно, если объявленный тип совпадает со значениями, которые вы кладёте в results.

Почему это важно

Ошибка не про права или рантайм; дело в контракте ответа. Если вернуть корректно сформатированную JSON‑строку с верхнеуровневым ключом results, не будет сбоя «Cannot parse External Function response». А согласование длины массива results с event["arguments"] обеспечивает сопоставление «одна входная строка — одно скалярное значение на выходе». Наконец, объявленный в SQL тип возврата должен соответствовать элементам в results; например, при RETURNS INT элементы обязаны быть целыми числами.

Итоги

Связывая Redshift с Lambda, держите в фокусе оболочку ответа. Всегда сериализуйте данные через json.dumps, помещайте значения под ключом results и выдавайте ровно одно значение на каждую строку во входном event["arguments"]. Держите SQL‑тип возврата согласованным с этими значениями. Тогда SELECT demo_fn(); вернёт ожидаемое 42 без ошибок разбора.

Статья основана на вопросе на StackOverflow от Stephen Saidani и ответе от Pradipta Dash.