BigQuery UDF 内存在多行上超出错误,但在单行上工作正常
Posted
技术标签:
【中文标题】BigQuery UDF 内存在多行上超出错误,但在单行上工作正常【英文标题】:BigQuery UDF memory exceeded error on multiple rows but works fine on single row 【发布时间】:2016-02-20 02:17:45 【问题描述】:我正在编写一个 UDF 来处理 Google Analytics 数据,并在我尝试处理多行时收到“UDF 内存不足”错误消息。我下载了原始数据并找到了最大的记录,并尝试在其上运行我的 UDF 查询,并成功。一些行有多达 500 个嵌套命中,命中记录的大小(到目前为止,原始 GA 数据的每一行的最大组成部分)似乎确实会影响我在收到错误之前可以处理多少行.
例如查询
select
user.ga_user_id,
ga_session_id,
...
from
temp_ga_processing(
select
fullVisitorId,
visitNumber,
...
from [79689075.ga_sessions_20160201] limit 100)
返回错误,但是
from [79689075.ga_sessions_20160201] where totals.hits = 500 limit 1)
没有。
我的印象是任何内存限制都是每行的?我尝试了几种技术,例如在emit(return_dict);
之前设置row = null;
(其中return_dict 是处理后的数据)但无济于事。
UDF 本身并没有做任何花哨的事情;我会把它贴在这里,但它的长度约为 45 kB。它基本上做了很多事情:
function temp_ga_processing(row, emit)
topic_id = -1;
hit_numbers = [];
first_page_load_hits = [];
return_dict = ;
return_dict["user"] = ;
return_dict["user"]["ga_user_id"] = row.fullVisitorId;
return_dict["ga_session_id"] = row.fullVisitorId.concat("-".concat(row.visitNumber));
for(i=0;i<row.hits.length;i++)
hit_dict = ;
hit_dict["page"] = ;
hit_dict["time"] = row.hits[i].time;
hit_dict["type"] = row.hits[i].type;
hit_dict["page"]["engaged_10s"] = false;
hit_dict["page"]["engaged_30s"] = false;
hit_dict["page"]["engaged_60s"] = false;
add_hit = true;
for(j=0;j<row.hits[i].customMetrics.length;j++)
if(row.hits[i].customDimensions[j] != null)
if(row.hits[i].customMetrics[j]["index"] == 3)
metrics = "video_play_time": row.hits[i].customMetrics[j]["value"];
hit_dict["metrics"] = metrics;
metrics = null;
row.hits[i].customDimensions[j] = null;
hit_dict["topic"] = ;
hit_dict["doctor"] = ;
hit_dict["doctor_location"] = ;
hit_dict["content"] = ;
if(row.hits[i].customDimensions != null)
for(j=0;j<row.hits[i].customDimensions.length;j++)
if(row.hits[i].customDimensions[j] != null)
if(row.hits[i].customDimensions[j]["index"] == 1)
hit_dict["topic"] = "name": row.hits[i].customDimensions[j]["value"];
row.hits[i].customDimensions[j] = null;
continue;
if(row.hits[i].customDimensions[j]["index"] == 3)
if(row.hits[i].customDimensions[j]["value"].search("doctor") > -1)
return_dict["logged_in_as_doctor"] = true;
// and so on...
if(row.hits[i]["eventInfo"]["eventCategory"] == "page load time" && row.hits[i]["eventInfo"]["eventLabel"].search("OUTLIER") == -1)
elre = /(?:onLoad|pl|page):(\d+)/.exec(row.hits[i]["eventInfo"]["eventLabel"]);
if(elre != null)
if(parseInt(elre[0].split(":")[1]) <= 60000)
first_page_load_hits.push(parseFloat(row.hits[i].hitNumber));
if(hit_dict["page"]["page_load"] == null)
hit_dict["page"]["page_load"] = ;
hit_dict["page"]["page_load"]["sample"] = 1;
page_load_time_re = /(?:onLoad|pl|page):(\d+)/.exec(row.hits[i]["eventInfo"]["eventLabel"]);
if(page_load_time_re != null)
hit_dict["page"]["page_load"]["page_load_time"] = parseFloat(page_load_time_re[0].split(':')[1])/1000;
// and so on...
row = null;
emit return_dict;
作业ID是realself-main:bquijob_4c30bd3d_152fbfcd7fd
【问题讨论】:
1. javascript 处理环境对每个查询的可用内存有限。累积过多本地状态的 UDF 查询可能会因内存耗尽而失败; 2. 处理单行时,UDF 输出的数据量应在 5 Mb 左右或更少; 3. 您可能需要提供您的 UDF 代码 @MikhailBerlyant 是的,我知道这些事情(在我开始写这篇文章之前,我曾多次阅读 Google 的 UDF 页面)。我在上面提到过,我在最大的一行上进行了尝试,这表明我每行返回 @Grayson :我不得不从示例中修改您的代码以使其运行(我无权访问您的 GCS 存储桶中的代码) - 但是这个示例确实适用于我在飞行中的更改。当更改在生产中时,我会在这里再次更新。 【参考方案1】:2016 年 8 月更新:我们推出了一项更新,允许 JavaScript 工作线程使用两倍的 RAM。我们将继续监视因 JS OOM 失败的作业,看看是否需要增加更多;同时,如果您还有其他工作因 OOM 失败,请告诉我们。谢谢!
更新:这个问题与我们对 UDF 代码大小的限制有关。看起来 V8 的 UDF 代码的优化+重新编译过程生成的数据段大于我们的限制,但这仅在 UDF 运行“足够”行数时发生。本周我将与 V8 团队会面,以进一步深入了解细节。
@Grayson - 我能够成功地在整个 20160201 表上运行你的工作;查询需要 1-2 分钟才能执行。您能否确认这对您有效?
我们收到了一些类似问题的报告,这些问题似乎与已处理的 # 行有关。很抱歉给您带来麻烦;我将对我们的 JavaScript 运行时进行一些分析,以尝试找出内存是否泄漏以及在何处泄漏。请继续关注分析。
与此同时,如果您能够隔离导致错误的任何特定行,那也将非常有帮助。
【讨论】:
太好了,谢谢。失败的并不是任何特定的行(实际上只是任意行的组合)。您是否大致知道用于 JS 运行时的分析和剖析的 ETA 是多少? 这是我三月份调查的首选。 我从 3 月 1 日起一直不在办公室,昨天刚回来。现在调查这个! 我们的一个 UDF 也有同样的问题,并且还在我们能找到的最大记录上对其进行了测试,并且运行良好。在大量记录(~ 10 毫米记录)上运行时失败 @thomaspark “UDF 内存不足”错误真的很奇怪。我们使用相同的 UDF 运行相同的查询,有时会成功,有时会失败。我们还尝试在桌子上零散地运行失败的 UDF,首先在桌子的前半部分,然后在后半部分,并且它有效。我们还会在不使用时立即删除每个变量;使用更少的变量;一切都无济于事......顺便说一句,我们在一个有 3,500 行和 6.6MB 的表上运行它。该函数通过一个 JSON 字段并找到最小/最大值。没什么特别的【参考方案2】:如果 UDF 有很多 if/then 级别,那么它会在除非常小的数据集之外的任何数据集上失败,例如: 如果 () .... if() .........if () 等等
我们必须追踪并删除最深的 if/then 语句。
但是,这还不够。此外,当您将数据传递到 UDF 时,对所有变量运行“GROUP EACH BY”。这将迫使 BQ 将输出发送给多个“工人”。否则也会失败。
我已经在这个烦人的错误上浪费了 3 天的时间。啊。
【讨论】:
坚持住,我可能会为您解决问题。如果您可以为失败的查询粘贴作业 ID,我会看到它适用于我的更新。【参考方案3】:我喜欢在 BigQuery 中解析我的日志的概念,但我也遇到了同样的问题,我明白了
错误:查询执行期间资源超出。
作业 ID 是 bigquery-looker:bquijob_260be029_153dd96cfdb,如果这有帮助的话。
我编写了一个非常基本的解析器,它进行简单的匹配并返回行。在 10K 行数据集上运行良好,但在尝试针对 3M 行日志文件运行时资源不足。
有什么解决方法的建议吗?
这里是javascript代码。
function parseLogRow(row, emit)
r = (row.logrow ? row.logrow : "") + (typeof row.l2 !== "undefined" ? " " + row.l2 : "") + (row.l3 ? " " + row.l3 : "")
ts = null
category = null
user = null
message = null
db = null
found = false
if (r)
m = r.match(/^(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d (\+|\-)\d\d\d\d) \[([^|]*)\|([^|]*)\|([^\]]*)\] :: (.*)/ )
if( m)
ts = new Date(m[1])/1000
category = m[3] || null
user = m[4] || null
db = m[5] || null
message = m[6] || null
found = true
else
message = r
found = false
emit(
ts: ts,
category: category,
user: user,
db: db,
message: message,
found: found
);
bigquery.defineFunction(
'parseLogRow', // Name of the function exported to SQL
['logrow',"l2","l3"], // Names of input columns
[
'name': 'ts', 'type': 'timestamp', // Output schema
'name': 'category', 'type': 'string',
'name': 'user', 'type': 'string',
'name': 'db', 'type': 'string',
'name': 'message', 'type': 'string',
'name': 'found', 'type': 'boolean',
],
parseLogRow // Reference to JavaScript UDF
);
【讨论】:
你应该将此作为新问题发布,否则我不知道如何回答这个问题!当你这样做时,我还建议提供日志行和预期输出的示例 我为您提供了潜在的解决方法。如果您将发布您的问题 - 我将能够分享它以上是关于BigQuery UDF 内存在多行上超出错误,但在单行上工作正常的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript 内存不足错误:超出 UDF 线程内存限制-Snowflake
Bigquery UDF 重复查询。错误:标量子查询不能超过一列