在 Redshift 中取消嵌套 json 会导致查询计划中出现嵌套循环
Posted
技术标签:
【中文标题】在 Redshift 中取消嵌套 json 会导致查询计划中出现嵌套循环【英文标题】:Unnesting a json in Redshift causing nested loop in the query plan 【发布时间】:2021-07-17 08:46:43 【问题描述】:我的表格中有一个名为“数据”的列,其中包含 JSON,如下所示:
"tt":"452.95","records":["r":"IN184366","t":"812812819910","s":"129.37","d":"982.7","c":"83","r":"IN183714","t":"8028028029093","s":"33.9","d":"892","c":"38"]
我已经编写了一个代码来将其取消嵌套到单独的列中,例如 tr,r,s。 下面是代码
with raw as (
SELECT json_extract_path_text(B.Data, 'records', true) as items
FROM tableB as B where B.date::timestamp between
to_timestamp('2019-01-01 00:00:00','YYYY-MM-DD HH24:MA:SS') AND
to_timestamp('2022-12-31 23:59:59','YYYY-MM-DD HH24:MA:SS')
UNION ALL
SELECT json_extract_path_text(C.Data, 'records', true) as items
FROM tableC as C where C.date-5 between
to_timestamp('2019-01-01 00:00:00','YYYY-MM-DD HH24:MA:SS') AND
to_timestamp('2022-12-31 23:59:59','YYYY-MM-DD HH24:MA:SS')
),
numbers as (
SELECT ROW_NUMBER() OVER (ORDER BY TRUE)::integer- 1 as ordinal
FROM <any_random_table> limit 1000
),
joined as (
select raw.*,
json_array_length(orders.items, true) as number_of_items,
json_extract_array_element_text(
raw.items,
numbers.ordinal::int,
true
) as item
from raw
cross join numbers
where numbers.ordinal <
json_array_length(raw.items, true)
),
parsed as (
SELECT J.*,
json_extract_path_text(J.item, 'tr',true) as tr,
json_extract_path_text(J.item, 'r',true) as r,
json_extract_path_text(J.item, 's',true)::float8 as s
from joined J
)
select * from parsed
上面的代码在有少量记录时工作,但这需要一天多的时间才能运行,并且 CPU 利用率(红移)达到 100%,如果我输入日期,甚至使用的磁盘空间也达到 100%在过去两年等之间。或者如果记录数量很大。
任何人都可以建议任何替代方法来取消嵌套 JSON 对象,例如 redshift 中的上述对象。
我的查询计划是:
查询计划中的嵌套循环联接 - 查看联接谓词以避免笛卡尔积
目标:在不使用任何交叉连接的情况下取消嵌套 输入:具有 JSON 的数据列
"tt":"452.95","records":["r":"IN184366","t":"812812819910","s":"129.37","d":"982.7","c":"83","r":"IN183714","t":"8028028029093","s":"33.9","d":"892","c":"38"]
输出应该是例如 上述 json 中的 tr,r,s 列
【问题讨论】:
我不清楚你想做什么。给出了一个示例输入,尽管它不容易阅读 - 一行 - 并且未定义输出(“将其取消嵌套到单独的列中,如 tr,r,s”)。我必须破译代码才能弄清楚你的意图,这是一项昂贵且耗时的任务。 @MaxGanzII 编辑了问题 我们能否在不使用任何交叉连接或笛卡尔积和查询的情况下在 redshift 中取消嵌套 JSON 以降低时间和空间复杂度? 【参考方案1】:您想取消嵌套存储在 json 数组中的最多 1000 条 json 记录,但嵌套循环连接耗时太长。
根本问题可能是您的数据模型。您已将结构化记录(称为“记录”)存储在半结构化文本元素 (json) 中,存储在结构化列式数据库的列中。您想对这些未描述的隐藏记录执行一些操作,但问题就在这里。列式数据库针对执行以读取为中心的分析查询进行了优化,但您需要将这些 json 内部记录扩展为 Redshift 行(记录),这基本上是一种写入操作。这不利于数据库的优化。
与集群上的磁盘存储相比,这种扩展数据的大小也很大,这就是磁盘被填满的原因。您的 CPU 可能正在旋转解包 json 并管理过载的磁盘和内存容量。在磁盘填满的边缘,Redshift 转变为以牺牲执行速度为代价优化磁盘空间利用率的模式。如果您可以避免这种影响,更大的集群可能会给您带来更快的执行速度,但这会花费您可能没有预算的资金。不是一个理想的解决方案。
可以提高查询速度的一个方面是不携带所有数据。您在整个查询中保留 raw.* 和 J.* ,但不清楚您是否需要这些。由于问题的一部分是执行期间的数据大小,并且此执行包括循环连接,因此您通过携带所有这些数据(包括原始 jsons)使执行变得更加困难。
摆脱这种情况的最佳方法是更改数据模型并将这些 json 内部记录扩展为摄取时的 Redshift 记录。 Json 数据适用于很少使用的信息或仅在数据较小的查询结束时才需要的信息。对于如此大量的数据,在查询的输入端需要扩展的 json 对 Redshift 中的 json 来说并不是一个好的用例。 json 中的这些“记录”中的每一个都是记录,如果您需要将它们作为查询输入工作,则需要按原样存储。
现在您想知道在您的情况下是否有一些巧妙的方法可以解决此问题,答案是“不太可能但可能”。您能描述一下您是如何在查询中使用最终值(t、r 和 s)的吗?如果您只是使用此数据的某些方面(最大值或总和或......),那么可能有一种方法可以在没有大型嵌套循环连接的情况下获得答案。但是,如果您需要所有值,则没有其他方法可以获取这些 AFAIK。对数据过程中接下来发生的事情的描述可能会带来这样的机会。
【讨论】:
太棒了!谢谢@Bill Weiner,这很有见地。但是,不幸的是,我需要所有这些列(t,r,s,tr)而没有任何聚合。关于我们如何在 REDSHIFT 中更改数据模型以在不使用任何其他外部脚本语言(如 python、javascript 等)的情况下获取内部记录的任何想法。如何使用 redshift更改数据模型> 在您的示例行中有两条嵌入记录 - r = IN184366 和 r = IN183714。您想将这些作为 2 个 Redshift 行存储在 tableB 和 tableC 中,是的,原始行的其他值将被重复。这些新表 tableB_new 和 tableC_new 将成为基于 json 值进行分析的源表。要制作这些表,您将拥有与您发布的内容一样的 SQL,但您将在数据进入时逐步执行转换。每条记录只需要经历一次这种昂贵的扩展处理。这种摄取处理可以在 Redshift 内部完成,没问题。 嗨,@Bill Weiner 我的数据有 300,000 行我运行了上述查询并对另一个外部表执行了 SELECT INTO 操作。我花了 5m 9s 完成了执行。但是,如果我想通过使用 SELECT -select r,t,tr from parsed 来获取这些记录中的每一个。 OMG,那是令人难以置信的时间和内存昂贵。即使 8 小时后我也没有得到输出。谢谢比尔,它现在可以工作了!! :) 对历史数据执行此操作后,您无需再次执行此操作。如果您需要分块处理这些历史数据(按年份?)。您只需要在数据进入时取消嵌套数据并将其添加(或更新插入)到大表中。做一次昂贵的部分,然后使用未嵌套的数据进行分析。 “在磁盘填满的边缘,Redshift 切换到以牺牲执行速度为代价优化磁盘空间利用率的模式。” - 这对我来说是新的,非常有趣 - 你能详细说明一下吗,@BillWeiner?以上是关于在 Redshift 中取消嵌套 json 会导致查询计划中出现嵌套循环的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Amazon Redshift 的列中取消嵌套/展开/展平逗号分隔值?
Redshift Postgresql - 如何解析嵌套的 JSON