【问题标题】:On-demand Median Aggregation on a Large Dataset大型数据集上的按需中值聚合
【发布时间】:2017-12-20 22:57:42
【问题描述】:

TLDR:我需要对 web 应用程序的大型数据集进行多个中值聚合,但性能很差。对于这个用例,我的查询是否可以改进/是否有比 AWS Redshift 更好的数据库?

我正在开展一个团队项目,该项目涉及通过我们的网络应用程序按需聚合大型数据集以进行可视化。我们使用的 Amazon Redshift 加载了近 1,000,000,000 行、按日期排列的 dist-key(我们拥有从 2014 年至今的数据,每天摄取 900,000 个数据点)和按唯一 ID 排列的排序键。唯一 id 可能与其他唯一 id 具有一对多的关系,对于这种关系,“多”关系可以被认为是 id 的“孩子”。

出于保密考虑,请考虑这样的表结构

TABLE NAME: meal_nutrition
DISTKEY(date),
SORTKEY(patient_id),
patient_name varchar,
calories integer,
fat integer,
carbohydrates integer,
protein integer,
cholesterol integer,
sodium integer,
calories integer

TABLE NAME: patient_hierarchy
DISTKEY(date date),
SORTKEY(patient_id integer),
parent_id integer,
child_id integer,
distance integer

把这个想象成一个有医生等级的世界。患者被封装为实际患者和医生本身,医生可以是其他医生的患者。医生可以随时转让患者/医生的所有权,所以层次结构不断变化。

     DOCTOR (id: 1)
      /         \
PATIENT(id: 2) DOCTOR (id: 3)
              /        \      \
       P (id: 4)    D (id: 8) D(id: 20)
                     /  \     / \ / \ \
            ................

我们遇到问题的一个可视化(由于性能)是时间序列图,它显示了默认日期范围必须为 1 年的多个指标的每日中位数。所以在这个例子中,我们想要一个病人/医生和他们的“孩子”消耗的所有膳食的脂肪、碳水化合物和蛋白质的中位数,给定一个病人 ID。使用的查询是:

SELECT patient_name,
    date,
    max(median_fats),
    max(median_carbs),
    max(median_proteins)
FROM (SELECT mn.date date,
    ph.patient_name patient_name,
    MEDIAN(fats) over (PARTITION BY date) AS median_fats,
    MEDIAN(carbohydrates) over (PARTITION BY date) AS median_carbs,
    MEDIAN(proteins) over (PARTITION BY date) AS median_proteins
        FROM meal_nutrition mn
        JOIN patient_hierarchy ph
        ON (mn.patient_id = ph.child_id)
        WHERE ph.date = (SELECT max(date) FROM patient_hierarchy)
        AND ph.parent_id = ?
        AND date >= '2016-12-17' and date <= '2017-12-17'
)
GROUP BY date, patient_name

此查询中最繁重的操作是对每个中位数的排序(每个都需要对 ~200,000,000 行进行排序),但我们无法避免这种情况。因此,此查询需要大约 30 秒才能完成,这意味着用户体验不佳。我正在做的查询可以改进吗?这种用例有更好的数据库吗?谢谢!

【问题讨论】:

标签: postgresql bigdata amazon-redshift rdbms datastore


【解决方案1】:

正如 cmets 中所说,数据的排序/分发非常重要。如果您只获得患者层次结构的一个日期切片,则您使用的所有数据都在一个节点上,并按日期分布。最好按meal_nutrition.patient_idpatient_hierarchy.child_id 分发,这样连接的数据可能位于同一个节点上,并分别按date,patient_iddate,child_id 对表进行排序,这样您就可以有效地找到必要的日期切片/范围,然后高效地寻找病人。

至于查询本身,您可以尝试一些选项:

1)这样的近似中位数:

SELECT mn.date date,
ph.patient_name patient_name,
APPROXIMATE PERCENTILE_DISC (0.5) WITHIN GROUP (ORDER BY fats) AS median_fats
FROM meal_nutrition mn
JOIN patient_hierarchy ph
ON (mn.patient_id = ph.child_id)
WHERE ph.date = (SELECT max(date) FROM patient_hierarchy)
AND ph.parent_id = ?
AND date >= '2016-12-17' and date <= '2017-12-17'
GROUP BY 1,2

注意:如果超出内存堆栈,这可能不起作用。此外,每个子查询必须只有一个这样的函数,因此您不能在同一个子查询中获取脂肪、碳水化合物和蛋白质,但您可以分别计算它们然后加入。如果这可行,那么您可以通过对几个 ID 运行 30s 语句并比较结果来测试准确性。

2) 分箱。首先按每个值分组,或者设置合理的 bin,然后找到分布中间的 group/bin。那将是你的中位数。一个变量示例是:

WITH
groups as (
    SELECT mn.date date,
    ph.patient_name patient_name,
    fats,
    count(1)
    FROM meal_nutrition mn
    JOIN patient_hierarchy ph
    ON (mn.patient_id = ph.child_id)
    WHERE ph.date = (SELECT max(date) FROM patient_hierarchy)
    AND ph.parent_id = ?
    AND date >= '2016-12-17' and date <= '2017-12-17'
    GROUP BY 1,2,3
)
,running_groups as (
    SELECT *
    ,sum(count) over (partition by date, patient_name order by fats rows between unlimited preceding and current row) as running_total
    ,sum(count) (partition by date, patient_name) as total
    FROM groups
)
,distance_from_median as (
    SELECT *
    ,row_number() over (partition by date, patient_name order by abs(0.5-(1.0*running_total/total))) as distance_from_median
    FROM running_groups
)
SELECT
date,
patient_name,
fats
WHERE distance_from_median=1

这可能会允许在每个单独的节点上对值进行分组,并且使用 bin 的后续操作将更轻,并避免对原始集进行排序。同样,您必须进行基准测试。您拥有的唯一值越少,您的性能增益就越高,因为您将在大量原始值中拥有少量垃圾箱,并且排序会便宜得多。结果是准确的,除了具有偶数个不同值的选项(对于1,2,3,4,它将返回 2,而不是 2.5),但是如果它很关键,可以通过添加另一层来解决。主要问题是该方法本身是否能显着提高性能。

3) 实现每个日期/患者 ID 的计算。如果您唯一的参数是耐心,并且您总是计算去年的中位数,您可以在一夜之间将查询运行到汇总表中并查询那个。即使 (1) 或 (2) 有助于优化性能也更好。您还可以在物化后将汇总表复制到 Postgres 实例并将其用作应用程序的后端,您将获得更好的 ping(Redshift 适合物化大量数据,但不适用于 Web 应用程序后端)。它伴随着维护数据传输作业的成本,因此如果物化/优化做得足够好,您可以将其留在 Redshift 中。

如果您尝试任何建议的选项,我真的很想获得反馈,这是 Redshift 的一个很好的用例。

【讨论】:

    猜你喜欢
    • 2020-11-28
    • 1970-01-01
    • 2018-09-30
    • 1970-01-01
    • 2020-02-13
    • 1970-01-01
    • 2018-03-07
    • 1970-01-01
    • 2011-07-25
    相关资源
    最近更新 更多