【问题标题】:BigQuery JavaScript UDF process - per row or per processing node?BigQuery JavaScript UDF 进程 - 每行或每个处理节点?
【发布时间】:2020-04-13 06:05:38
【问题描述】:

我正在考虑使用 BigQuery 的 JavaScript UDF 作为新数据架构中的关键组件。它将用于在逻辑上处理加载到主表中的每一行,并在定期和临时聚合查询期间处理每一行。

为了同样的目的使用 SQL UDF 似乎是不可行的,因为每一行都代表一个复杂的对象,并且在 SQL 中实现业务逻辑,包括解析复杂的文本字段之类的事情,很快就会变得丑陋。

我刚刚在Optimizing query computation 文档页面中阅读了以下内容:

最佳实践:避免使用 JavaScript 用户定义函数。请改用本机 UDF。

调用 JavaScript UDF 需要实例化子进程。 启动此过程并运行 UDF 会直接影响查询 表现。如果可能,请改用本机 (SQL) UDF。

我理解为什么每个处理节点都需要一个新进程,并且我知道 JS 倾向于以每进程单线程的方式部署(尽管如今 v8 确实支持多线程)。但是我不清楚一旦 JS 运行时进程启动,它是否可以在对同一函数的调用之间被重用(例如,用于处理同一处理节点上的不同行)。重复使用的数量可能会显着影响成本。我的表并没有那么大(几千万到几亿行),但是这里还是需要有一个更好的理解。

我找不到任何权威来源。有没有人分析过使用 JavaScript UDF 对每个处理的行的实际影响,在执行时间和成本方面?

【问题讨论】:

  • 如果 ad-hoc 聚合查询可以用 SQL 表达它们的逻辑,并且所有数据(形成一行)只能使用 SQL 选择,那么解析复杂的文本字段可以使用 JS 作为 post -处理步骤。此方法由 project 演示。我是作者。例如,这个function 用三点替换长字符串的尾部。您显然可以应用更复杂的逻辑并在后端而不是在客户端或两者上执行这些操作。

标签: performance google-bigquery user-defined-functions


【解决方案1】:

如果没有记录,那么这是一个可能会改变的实现细节。但是让我们测试一下:

CREATE TEMP FUNCTION randomThis(views INT64)
RETURNS FLOAT64
LANGUAGE js AS """
  if (typeof variable === 'undefined') {
     variable = Math.random()
  }
  return variable
""";

SELECT randomThis(views), COUNT(*) c
FROM (
  SELECT views
  FROM `fh-bigquery.wikipedia_v3.pageviews_2019` 
  LIMIT 10000000
)
GROUP BY 1
ORDER BY 2 DESC

我期待一千万个不同的数字,或者少数几个,但我只得到一个:同一个进程被重复使用一千万次,并且在调用之间保留变量。

这甚至在我达到 1 亿时发生,这表明并行性受到一个 JS VM 的限制。

同样,这些是可能会改变的实现细节。但是,尽管它保持这种状态,但您可以充分利用它。

【讨论】:

  • 在下面的另一个答案中发表了评论
【解决方案2】:

我期待一千万个不同的数字,或者少数,但我只有一个

那是因为您不允许多次调用 Math.random

变量在调用之间保留

由于在全局范围内定义的变量。

换句话说,您的代码明确允许 Math.random 仅执行一次(通过在全局范围内隐式定义变量)。

如果你试试这个:

CREATE TEMP FUNCTION randomThis(seed INT64)
RETURNS FLOAT64
LANGUAGE js AS """
  let ret = undefined
  if (ret === undefined) {
     ret = Math.random()
  }
  return ret
""";

SELECT randomThis(size), COUNT(*) c
FROM (
  SELECT repository_size as size
  FROM `my-internal-dataset.sample-github-table` 
  LIMIT 10000000
)
GROUP BY 1
ORDER BY 2 DESC

然后你会得到很多行。而现在它确实需要更长的时间来执行,可能是因为单个 VM 成为了瓶颈。

使用另一个数据集来降低查询成本。

结论:
1.每个查询有一个VM(或者可能是一个容器)来支持JS UDF。这符合文档中提到的单个子流程(“调用 JavaScript UDF 需要实例化子流程”)。
2. 如果您可以应用一次执行模式(使用某种缓存或编码技术,如记忆化)并编写类似于上一个答案的 UDF,那么 JS UDF 的绝对存在对您的查询的影响有限。
3.如果您必须像this answer那样编写JS UDF,那么即使对于简单的JS代码,查询执行时间也会飙升,对您的查询的影响会变得非常显着。所以在这种情况下,最好还是呆在外面。

【讨论】:

  • In other words your code explicitly permits Math.random to be executed once only (by implictly defining the variable at the global scope) - 这正是我想要测试的 - 行为是否会在全局范围内起作用(单个重用的 VM),或者是否有许多 VM 会参与调用
  • @FelipeHoffa 考虑到 JavaScript UDF 和 UDF 导致实例化的子进程之间的一对一关系(doco 说 “调用 JavaScript UDF 需要实例化子进程” 明确没有多个子进程)假设单个子进程意味着单个 VM 是合理的。但我同意通过额外测试确认它是件好事。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多