【问题标题】:Amazon Redshift - Pivot Large JSON ArraysAmazon Redshift - 透视大型 JSON 数组
【发布时间】:2019-06-05 07:30:39
【问题描述】:

我有一个优化问题。

我有一个表,其中包含大约 15MB 的 JSON 存储为 VARCHAR(65535) 的行。每个 JSON 字符串都是一个任意大小的数组。

  • 95% 包含 16 个或更少的元素
  • 最长(迄今为止)包含 67 个元素
  • 硬限制是 512 个元素(在 64kB 之前不够大)

任务很简单,旋转每个数组,使每个元素都有自己的行。

 id | json
----+---------------------------------------------
 01 | [{"something":"here"}, {"fu":"bar"}]

=>

 id | element_id | json
----+------------+---------------------------------
 01 |     1      | {"something":"here"}
 01 |     2      | {"fu":"bar"}

在没有任何类型的表值函数(用户定义或其他方式)的情况下,我通过加入一个数字表来进行旋转。

SELECT
    src.id,
    pvt.element_id,
    json_extract_array_element_text(
        src.json,
        pvt.element_id
    )
        AS json
FROM
    source_table         AS src
INNER JOIN
    numbers_table        AS pvt(element_id)
       ON pvt.element_id < json_array_length(src.json)

numbers 表有 512 行 (0..511),结果是正确的。

经过的时间太可怕了。它与分布或排序顺序或编码无关。这与 (我相信) redshift 的具体化有关。

处理 15MB 的 JSON 文本所需的工作内存为 7.5GB。

  • 15MB * numbers 中的 512 行 = 7.5GB

如果我只在numbers 中放入 128 行,那么所需的工作内存会减少 4 倍,并且经过的时间同样会减少 (不是 4 倍,真正的查询会做其他工作,它仍在写入相同数量的结果数据等).

那么,我想知道,添加这个怎么样?

WHERE
    pvt.element_id < (SELECT MAX(json_array_length(src.json)) FROM source_table)


不需要改变工作记忆,经过的时间略有增加(实际上是一个有成本但没有好处的 WHERE 子句)


我尝试制作一个 CTE 来创建 512 个号码的列表,但没有帮助。我尝试制作一个 CTE 来创建数字列表,并使用 WHERE 子句来限制大小,但这没有帮助 (实际上 Redshift 似乎已经使用 512 行实现了,然后应用了 WHERE 子句)。


我目前的工作是为数字创建一个临时表,受 WHERE 子句的限制。在我的示例集中,这意味着我得到了一个包含 67 行的表,而不是 512 行。

这仍然不是很好,因为具有 67 个元素的 ONE 行支配了经过的时间(每一行,无论有多少元素,在应用 ON pvt.element_id &lt; json_array_length(src.json) 之前被复制 67 次)


我接下来的工作将分两步进行。

  1. 同上,但表格只有 16 行,并且仅适用于包含 16 个或更少元素的行

  2. 如上,使用动态混合数字表,并且仅适用于超过 16 个元素的行


问题:有人有更好的想法吗?

【问题讨论】:

  • Redshift 支持横向连接吗?
  • @a_horse_with_no_name - 没有类似于 LATERAL、APPLY、表值函数等的东西。
  • 您应该在红移之外执行此操作。通过 export->process->import 或在数据到达 redshift 之前对其进行处理。
  • @JonScott - 是的,我同意。这就是中期计划(改变会产生更广泛的影响,所以不在近期计划中,特别是因为我已经“优化”到几乎可以容忍的时间尺度)。我要特别问的是,一旦数据在 Redshift 上,是否有办法更有效地做到这一点。

标签: sql json pivot amazon-redshift explode


【解决方案1】:

也许如果您避免将 JSON 解析和解释为 JSON,而是将其作为文本处理,它可以更快地工作。如果您确定 JSON 值的结构(我猜您是因为原始查询不会产生 JSON 解析错误),您可以尝试使用 split_part 函数而不是 json_extract_array_element_text

如果您的元素不包含逗号,您可以使用:

split_part(src.json,',',pvt.element_id)

如果您的元素包含逗号,您可以使用

split_part(src.json,'},{',pvt.element_id)

另外,join 条件中带有ON pvt.element_id &lt; json_array_length(src.json) 的部分仍然存在,因此为了完全避免 JSON 解析,您可以尝试交叉连接,然后过滤掉非空值。

【讨论】:

  • 所有方法产生相同数量的工作内存。问题是针对数字表的连接以产生不同数量的输出行。这不是标量操作。
  • 您可以使用 Python 生成 SQL 代码,就像您硬编码所有数字并复制/粘贴那个丑陋的查询一样
  • 你的意思是{QueryWhereElements&lt;=1} UNION ALL {QueryWhereElements&lt;=2} UNION ALL {etc, etc}?这实际上比我们已经拥有的更糟糕。
  • 是的,json_extract_array_element_text(src.json, X) 其中 X 是生成/硬编码的,不需要加入。差多少?
  • 我已经更彻底地浏览了您的 cmets,您对物化的考虑是有道理的。您可以尝试在表格中添加一列并使用json_array_length(src.json) 的结果填充它吗?那么您可以在连接条件中使用该列,并可能避免运行时的冗余实现
【解决方案2】:

请考虑将 JSON 声明为外部表。然后,您可以使用 Redshift Spectrum 的嵌套数据语法来访问这些值就好像它们是行一样。

这里有一个快速教程:"Tutorial: Querying Nested Data with Amazon Redshift Spectrum"

简单示例:

{ "id": 1
 ,"name":   { "given":"John", "family":"Smith" }
 ,"orders": [ {"price": 100.50, "quantity": 9 }
             ,{"price":  99.12, "quantity": 2 } 
            ]
}

CREATE EXTERNAL TABLE spectrum.nested_tutorial
     (id      int
     ,name    struct<given:varchar(20), family:varchar(20)>
     ,orders  array<struct<price:double precision, quantity:double precision>>
     ) 
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://my-files/temp/nested_data/nested_tutorial/'
;

SELECT c.id
      ,c.name.given
      ,c.name.family
      ,o.price
      ,o.quantity
FROM spectrum.nested_tutorial c
LEFT JOIN c.orders o ON true
;

 id | given | family | price | quantity
----+-------+--------+-------+----------
  1 | John  | Smith  | 100.5 |        9
  1 | John  | Smith  | 99.12 |        2

【讨论】:

  • 好答案 - 我喜欢这种方法。在加载到 redshift 之前,您可能会将这些数据放在 s3 上!
【解决方案3】:

无论是数据格式还是您希望执行的任务,都不是 Amazon Redshift 的理想选择。

Amazon Redshift 作为数据仓库非常出色,能够对数十亿行进行查询。但是,将数据存储为 JSON 并不理想,因为 Redshift 在处理存储在 JSON 中的字段时无法使用其所有功能(例如,分发键、排序键、区域映射、并行处理)。

如果将数据存储为以下格式,您的 Redshift 集群的效率会更高:

 id | element_id | key        | value
----+------------+---------------------
 01 |     1      | something  | here
 01 |     2      | fu         | bar

至于如何最好地将现有 JSON 数据转换为单独的行,我坦率地建议这是在 Redshift 之外 完成,然后通过 COPY 命令加载到表中。一个小的 Python 脚本可以更有效地转换在 Redshift 中的 numbers 表上尝试奇怪 JOIN 的数据。

【讨论】:

  • 这本质上是为仓库提供数据的 ETL 过程的一部分。其最终结果是 JSON 在任何地方都不存在。事情的那一面很好。目前,摄取所用时间的比例为 85% 的 JSON 旋转,以及 15% 的其他 ETL。所以我要攻击 85%。
  • 在将 JSON 加载到 Redshift 之前,建议将 JSON 转换为普通字段作为 ETL 过程的一部分。
  • 从中期来看,这将在 Redshift 之外完成,但在短期内这不是一个选项 (外部处理与物联网设备的事件流传递的数据无关. 有时 VARCHAR(65535) 有一个整数,有时它有一个 JSON 字符串。这是一个 ####ing 混乱,将作为我的下一个项目完全替换,但现在它是它的样子)这就是为什么我要专门询问有关优化 SQL 的问题,因为“今天”是唯一可用的选项。
猜你喜欢
  • 2014-01-06
  • 2022-01-15
  • 1970-01-01
  • 1970-01-01
  • 2022-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-07
相关资源
最近更新 更多