在Heap中,我们依靠PostgreSQL支撑大多数后端繁重的任务,我们存储每个事件为一个hstore blob,我们为每个跟踪的用户维护一个已完成事件的PostgreSQL数组,并将这些事件按时间排序。 Hstore能够让我们以灵活的方式附加属性到事件中,而且事件数组赋予了我们强大的性能,特别是对于漏斗查询,在这些查询中我们计算不同转化渠道步骤间的输出。
在这篇文章中,我们看看那些意外接受大量输入的PostgreSQL函数,然后以高效,惯用的方式重写它。
你的第一反应可能是将PostgreSQL中的数组看做像C语言中对等的类似物。你之前可能用过变换阵列位置或切片来操纵数据。不过要小心,在PostgreSQL中不要有这样的想法,特别是数组类型是变长的时,比如JSON、文本或是hstore。如果你通过位置来访问PostgreSQL数组,你会进入一个意想不到的性能暴跌的境地。
这种情况几星期前在Heap出现了。我们在Heap为每个跟踪用户维护一个事件数组,在这个数组中我们用一个hstore datum代表每个事件。我们有一个导入管道来追加新事件到对应的数组。为了使这一导入管道是幂等的,我们给每个事件设定一个event_id,我们通过一个功能函数重复运行我们的事件数组。如果我们要更新附加到事件的属性的话,我们只需使用相同的event_id转储一个新的事件到管道中。
所以,我们需要一个功能函数来处理hstores数组,并且,如果两个事件具有相同的event_id时应该使用数组中最近出现的那个。刚开始尝试这个函数是这样写的:
-- This is slow, and you don't want to use it! -- -- Filter an array of events such that there is only one event with each event_id. -- When more than one event with the same event_id is present, take the latest one. CREATE OR REPLACE FUNCTION dedupe_events_1(events HSTORE[]) RETURNS HSTORE[] AS $$ SELECT array_agg(event) FROM ( -- Filter for rank = 1, i.e. select the latest event for any collisions on event_id. SELECT event FROM ( -- Rank elements with the same event_id by position in the array, descending.
这个查询在拥有2.4GHz的i7CPU及16GB Ram的macbook pro上测得,运行脚本为:https://gist.github.com/drob/9180760。
在这边究竟发生了什么呢"htmlcode">
{“event_id=>1,data=>foo”, “event_id=>2,data=>bar”, “event_id=>3,data=>baz”}
相反的是
{[pointer], [pointer], [pointer]}
对于那些长度不一的变量, 举个例子. hstores, json blobs, varchars,或者是 text fields, PostgreSQL 必须去找到每一个变量的长度. 对于evaluateevents[2], PostgreSQL 解析从左侧读取的事件直到读取到第二次读取的数据. 然后就是 forevents[3], 她再一次的从第一个索引处开始扫描,直到读到第三次的数据! 所以, evaluatingevents[sub]是 O(sub), 并且 evaluatingevents[sub]对于在数组中的每一个索引都是 O(N2), N是数组的长度.
PostgreSQL能得到更加恰当的解析结果, 它可以在这样的情况下分析该数组一次. 真正的答案是可变长度的元素与指针来实现,以数组的值, 以至于,我们总能够处理 evaluateevents[i]在不变的时间内.
即便如此,我们也不应该让PostgreSQL来处理,因为这不是一个地道的查询。除了generate_subscripts我们可以用unnest,它解析数组并返回一组条目。这样一来,我们就不需要在数组中显式加入索引了。
-- Filter an array of events such that there is only one event with each event_id. -- When more than one event with the same event_id, is present, take the latest one. CREATE OR REPLACE FUNCTION dedupe_events_2(events HSTORE[]) RETURNS HSTORE[] AS $$ SELECT array_agg(event) FROM ( -- Filter for rank = 1, i.e. select the latest event for any collisions on event_id. SELECT event FROM ( -- Rank elements with the same event_id by position in the array, descending. SELECT event, row_number AS index, rank() OVER (PARTITION BY (event -> 'event_id')::BIGINT ORDER BY row_number DESC) FROM ( -- Use unnest instead of generate_subscripts to turn an array into a set. SELECT event, row_number() OVER (ORDER BY event -> 'time') FROM unnest(events) AS event ) unnested_data ) deduped_events WHERE rank = 1 ORDER BY index ASC ) to_agg; $$ LANGUAGE SQL IMMUTABLE;
结果是有效的,它花费的时间跟输入数组的大小呈线性关系。对于100K个元素的输入它需要大约半秒,而之前的实现需要40秒。
这实现了我们的需求:
- 一次解析数组,不需要unnest。
- 按event_id划分。
- 对每个event_id采用最新出现的。
- 按输入索引排序。
教训:如果你需要访问PostgreSQL数组的特定位置,考虑使用unnest代替。
SELECT events[sub] AS event, sub, rank() OVER (PARTITION BY (events[sub] -> 'event_id')::BIGINT ORDER BY sub DESC) FROM generate_subscripts(events, 1) AS sub ) deduped_events WHERE rank = 1 ORDER BY sub ASC ) to_agg; $$ LANGUAGE SQL IMMUTABLE;
这样奏效,但大输入是性能下降了。这是二次的,在输入数组有100K各元素时它需要大约40秒!
这个查询在拥有2.4GHz的i7CPU及16GB Ram的macbook pro上测得,运行脚本为:https://gist.github.com/drob/9180760。
在这边究竟发生了什么呢"htmlcode">
{“event_id=>1,data=>foo”, “event_id=>2,data=>bar”, “event_id=>3,data=>baz”}
相反的是
{[pointer], [pointer], [pointer]}
对于那些长度不一的变量, 举个例子. hstores, json blobs, varchars,或者是 text fields, PostgreSQL 必须去找到每一个变量的长度. 对于evaluateevents[2], PostgreSQL 解析从左侧读取的事件直到读取到第二次读取的数据. 然后就是 forevents[3], 她再一次的从第一个索引处开始扫描,直到读到第三次的数据! 所以, evaluatingevents[sub]是 O(sub), 并且 evaluatingevents[sub]对于在数组中的每一个索引都是 O(N2), N是数组的长度.
PostgreSQL能得到更加恰当的解析结果, 它可以在这样的情况下分析该数组一次. 真正的答案是可变长度的元素与指针来实现,以数组的值, 以至于,我们总能够处理 evaluateevents[i]在不变的时间内.
即便如此,我们也不应该让PostgreSQL来处理,因为这不是一个地道的查询。除了generate_subscripts我们可以用unnest,它解析数组并返回一组条目。这样一来,我们就不需要在数组中显式加入索引了。
-- Filter an array of events such that there is only one event with each event_id. -- When more than one event with the same event_id, is present, take the latest one. CREATE OR REPLACE FUNCTION dedupe_events_2(events HSTORE[]) RETURNS HSTORE[] AS $$ SELECT array_agg(event) FROM ( -- Filter for rank = 1, i.e. select the latest event for any collisions on event_id. SELECT event FROM ( -- Rank elements with the same event_id by position in the array, descending. SELECT event, row_number AS index, rank() OVER (PARTITION BY (event -> 'event_id')::BIGINT ORDER BY row_number DESC) FROM ( -- Use unnest instead of generate_subscripts to turn an array into a set. SELECT event, row_number() OVER (ORDER BY event -> 'time') FROM unnest(events) AS event ) unnested_data ) deduped_events WHERE rank = 1 ORDER BY index ASC ) to_agg; $$ LANGUAGE SQL IMMUTABLE;
结果是有效的,它花费的时间跟输入数组的大小呈线性关系。对于100K个元素的输入它需要大约半秒,而之前的实现需要40秒。
这实现了我们的需求:
- 一次解析数组,不需要unnest。
- 按event_id划分。
- 对每个event_id采用最新出现的。
- 按输入索引排序。
教训:如果你需要访问PostgreSQL数组的特定位置,考虑使用unnest代替。
PostgreSQL
RTX 5090要首发 性能要翻倍!三星展示GDDR7显存
三星在GTC上展示了专为下一代游戏GPU设计的GDDR7内存。
首次推出的GDDR7内存模块密度为16GB,每个模块容量为2GB。其速度预设为32 Gbps(PAM3),但也可以降至28 Gbps,以提高产量和初始阶段的整体性能和成本效益。
据三星表示,GDDR7内存的能效将提高20%,同时工作电压仅为1.1V,低于标准的1.2V。通过采用更新的封装材料和优化的电路设计,使得在高速运行时的发热量降低,GDDR7的热阻比GDDR6降低了70%。
更新动态
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]