外部函数最佳实践¶
本主题介绍了最佳实践,这些最佳实践将提高效率并防止在远程服务未设计为与 Snowflake 兼容时可能发生的意外结果。
您可以在以下文档中找到其他最佳实践:
优化 Azure 函数的性能和可靠性 (https://docs.microsoft.com/en-us/azure/azure-functions/functions-best-practices)
(尽管这是 Microsoft Azure 文档,但其中的许多建议适用于任何云平台上的远程服务。
本主题内容:
使用远程服务的批次 API (如果可用)¶
某些远程服务同时提供批次模式和单行模式。如果使用外部函数的查询预期会发送多个行,则 Snowflake 建议使用远程服务的批次模式来提高性能。
在以下情况下,此规则不一定适用:
每一行的大小都非常大(例如,数百千字节或更大)。
如果批量接收行而不是单个接收行,则远程服务处理行的方式会有所不同。(有关详细信息,请参阅 一次处理一行。)
一次处理一行¶
为了最大程度地减少网络开销,Snowflake 通常会按批次处理要发送到远程服务的行。批次数和每个批次的大小可能会有所不同。
此外,批次的顺序可能不同,批次中的行的顺序也可能不同。即使查询包含 ORDER BY 子句,通常也会在调用外部函数后再应用 ORDER BY。
由于无法保证批次大小和行顺序,因此编写依赖该批次或以前批次中的任何其他行来返回值的函数可能会产生不确定的结果。
Snowflake 强烈建议远程服务独立处理每个行。每个输入行的返回值应仅依赖于该输入行,而不依赖于其他输入行。(例如,目前外部函数不支持 窗口函数。)
另请注意,由于无法保证批次大小,因此对批次进行计数没有意义。
另请参阅 确保外部函数无状态。
请勿假定远程服务只传递每行一次¶
如果 Snowflake 调用远程服务,且远程服务收到请求并返回结果,但 Snowflake 由于临时网络问题未收到结果,则 Snowflake 可能会重复该请求。如果 Snowflake 重试,远程服务可能会看到两次(或更多次)相同的行。
这可能会产生预料之外的影响。例如,由于远程服务可能会多次调用同一个值,因此分配唯一 IDs 的远程服务在这些 IDs 的序列中可能存在缺失。在某些情况下,可以通过跟踪请求标头 sf-external-function-query-batch-id
字段中的批次 ID 来确定以前是否处理过特定批次的行。从而减少此类影响。当 Snowflake 重试对特定批次的请求时,Snowflake 将使用之前用于同一批次的相同批次 ID。
Snowflake 会在收到以下错误时重试:
所有暂时性网络传输错误。
所有状态代码为 429 的失败请求。
所有状态代码为 5XX 的失败请求。
请求将重试,直到达到总计重试超时。用户无法配置总计重试超时。Snowflake 将来可能会调整此限制。
如果达到总计重试超时而未成功重试,则查询将失败。
如果外部函数调用在远程服务生效时超时,并且 Snowflake 和远程服务之间的所有元素似乎都生效,则可以尝试较小的批次大小,看看是否能减少超时错误。
要了解如何设置最大批次大小,请参阅 CREATE EXTERNAL FUNCTION。
确保外部函数无状态¶
通常,外部函数(包括远程服务)应避免存储状态信息,包括以下两种状态:
内部状态(远程服务在内部存储的状态)。
外部状态(存储在远程服务外部的状态,例如,发送到和/或从本身保留状态的远程服务读取的状态信息)。
如果远程服务更改状态信息,然后使用该信息来影响将来的输出,则函数可能会返回与预期不同的值。
例如,假设一个包含内部计数器的简单远程服务,并返回自远程服务首次启动以来接收的行数。如果存在临时网络问题,并且 Snowflake 使用相同的数据重复请求,则远程服务将对重新发送的行进行两次(或更多)计数。
有关涉及外部状态的示例,请参阅 避免产生副作用。
在极少数情况下,函数不是无状态的,调用方的文档应明确说明该函数不是无状态的,并且应将该函数标记为可变。
如果远程服务 异步 处理请求,则远程服务作者必须编写远程服务以临时存储和管理某些状态。例如,远程服务必须存储 HTTP POST 请求的批次 ID,以便在相同批次 ID 收到 HTTP GET 时,远程服务可以在仍在处理指定批次时返回 HTTP 代码 202。
注意,查询可能由于各种原因而中止,这意味着无法保证在远程服务会在完成结果生成后前往最终 GET。存储异步请求状态的远程服务最终应当超时,并清理相应的内部状态。最佳超时将来可能会更改,但目前 Snowflake 建议保留有关异步请求的信息至少 10 分钟(最好保留 12 小时),然后再将其删除。
避免产生副作用¶
外部功能(包括远程服务)应避免产生副作用,例如更改外部状态(存储在远程服务外部的信息)。
例如,远程服务向治理机构报告超出范围的值会被视为副作用。
副作用可能很有用,但调用外部函数的副作用并不总是可预测的。例如,假设您调用了一个远程服务,用来分析匿名健康记录并返回诊断。再假设,如果诊断是患者患有传染性疾病,则将诊断报告给统计该疾病病例数的机构。这是一个有用的副作用。但是,该副作用容易受到以下问题的影响:
如果外部函数调用位于回滚的事务中,将不会回滚副作用。
如果在同一行中多次调用远程服务(例如,由于临时网络故障和重试),则副作用可能会多次发生。例如,受感染的患者可能会在统计信息中被计算两次。
在某些情况下,行数还可能会被低估而不是高估。
在极少数情况下,函数有副作用,调用方的文档应明确说明副作用的内容,并且应该将函数标记为可变。
将函数分类为可变或不可变¶
函数可以分类为可变函数或不可变函数。(CREATE EXTERNAL FUNCTION 语句允许用户指定函数是可变的还是不可变的。)
要使外部函数被视为不可变,函数应满足以下条件:
如果给定的输入值相同,则函数返回的输出值相同。(例如,当给定相同的输入时,SQRT 函数返回相同的输出,但在给定相同的输入时,CURRENT_TIMESTAMP 函数不一定返回相同的输出。)
函数没有副作用。(有关详细信息,请参阅 避免产生副作用。)
如果函数满足这两个条件,则 Snowflake 可以使用某些类型的优化来减少发送到远程服务的行数或批次数。(这些优化可能会随着时间的推移而演化,此处不作详细介绍。)
Snowflake 无法检测或强制执行不可变性或影响不可变性的因素(例如副作用)。远程服务的作者应记录远程服务是否满足标记为不可变的标准。如果远程服务存在副作用,则调用该远程服务的外部函数应标记为可变,即使函数调用为相同的输入值返回相同的输出值也一样。如果不确定远程服务是否不可变,则调用该远程服务的任何外部函数都应标记为可变。
考虑超时错误¶
外部函数调用涉及 Snowflake、远程服务、代理服务以及服务链中可能的其他元素。这些元素都不知道特定的函数调用应当花费多长时间,因此没有元素确切知道何时停止等待并返回超时错误。每个步骤可能都有自身独立的超时时长。有关超时和重试的更多信息,请参阅 考虑超时错误 和 重试。
最小化延迟¶
为了最大程度地减少延迟并提高外部函数调用的性能,Snowflake 建议在可行的情况下执行以下操作:
将 API 网关与调用最频繁(或数据量最大)的 Snowflake 实例放置在同一云平台和区域中。
如果您编写了远程服务(而不是使用现有服务),请将该远程服务部署在调用该远程服务的同一云平台和区域中。
发送尽可能少的数据。例如,如果远程服务将检查输入值并仅对其中的子集进行操作,比起将所有行发送到远程服务并进行筛选,筛选 SQL 并仅将相关行发送到远程服务通常更为有效。
再举一个例子,如果要处理包含大型半结构化数据值的列,并且远程服务将仅对每个数据值中的一小部分进行操作,相比发送整个列并让远程服务在处理之前提取小部分,使用 Snowflake SQL 提取相关部分并仅发送该部分通常更为有效。
逐步开发和测试外部函数¶
Snowflake 建议在使用 Snowflake 进行测试之前,先在不使用 Snowflake 的情况下进行测试。
在开发外部函数的早期阶段,请使用云平台代理服务控制台(例如 Amazon API Gateway 控制台)和远程服务开发控制台(例如 AWS Lambda 控制台)来帮助开发和测试代理服务和远程服务。
例如,如果您开发了一个 Lambda 函数,您可能希望通过 Lambda 控制台对其进行广泛测试,然后再通过从 Snowflake 调用函数来进行测试。
通过代理服务控制台和远程服务控制台进行测试通常具有以下优点:
可以使诊断问题更容易,因为寻找问题原因的地方更少。
查看数据有效负载可能会提供有用的调试信息。Snowflake 不会在错误消息中显示数据有效负载的任何部分;尽管这样做会增强安全性,但可能会减慢调试速度。
Snowflake 会自动重试 HTTP 5xx 错误,在某些情况下,这可能会使调试速度变慢或使调试变得更加困难。
除了云平台 credit 外,通过 Snowflake 进行测试还会消耗 Snowflake credit。
当然,在未使用 Snowflake 的情况下尽可能多地测试远程服务和代理服务后,您应该使用 Snowflake 测试它们。使用 Snowflake 进行测试的优点包括:
您正在测试外部函数涉及的所有步骤。
使用 Snowflake 表作为数据源可以轻松使用大量数据进行测试,从而获得外部函数性能的真实估算值。
请考虑以下测试用例:
NULL 值。
“空”值(例如,空字符串、空半结构化数据类型)。
很长的 VARCHAR 和 BINARY 值(如果合适)。
将远程服务设为异步¶
如果您正在编写远程服务,并且您的远程服务可能不会在预期的超时时间内返回结果,请考虑将远程服务设置为 异步。有关详细信息,请参阅 异步与同步远程服务。
确保外部函数的实参与远程服务解析的实参相对应¶
将实参传递到外部函数或从外部函数传递实参时,请确保数据类型是合适的。如果发送的值不适合要接收的数据类型,则该值可能会截断或损坏,还可能导致远程服务调用失败。
例如,由于某些 Snowflake SQL 数值数据类型可以存储比常用 JavaScript 数据类型更大的值,因此从 JSON 中进行大量反序列化在 JavaScript 中特别敏感。
如果要更改远程服务实参的数量、数据类型或顺序,请记得对外部函数进行相应的更改。目前,ALTER FUNCTION 命令没有更改实参的选项,因此您必须删除并重新创建外部函数才能更改实参。