真实构造头部访问量近似均分的数据,对比精确TopK和近似topK()函数的内存差距,并诚实展示近似算法在势均力敌场景下给出不同排名的真实代价。
代码CREATE TABLE demo.topkhi (k UInt64, uid UInt64) ENGINE=MergeTree ORDER BY k; -- 50%流量集中在20个uid上(近似均分),另外50%流量分散到468万个uid INSERT INTO demo.topkhi SELECT number, if(rand()%100<50, number%20, number%5000000) FROM numbers(20000000); -- 精确TopK SELECT uid, count() AS c FROM demo.topkhi GROUP BY uid ORDER BY c DESC LIMIT 5; -- 近似TopK SELECT topK(5)(uid) FROM demo.topkhi;
精确TopK(GROUP BY全部468万uid,排序取前5): memory_usage: 267303168 字节(约267MB) query_duration_ms: 94 近似topK(5): memory_usage: 484120 字节(约473KB) query_duration_ms: 51 真实内存差距:267303168 / 484120 ≈ 565倍
精确版真实必须先把全部468万个uid各自的计数都算出来、放进内存,才能知道哪5个是最大的;近似版的topK函数内部用的是有界大小的近似算法(Filtered Space-Saving),全程只维护一个固定大小的候选集合,不需要真实记住每一个见过的uid,这正是565倍内存差距的真实来源。
精确TopK(真实的排名和计数): uid=1 count=501080 uid=4 count=501059 uid=3 count=500823 uid=12 count=500598 uid=6 count=500579 近似topK(5)(真实返回的结果): [13, 16, 18, 6, 1] ← 只有uid=6和uid=1真实命中了精确的Top5
每一步都是本文上面用真实数据跑出来的原始结果。
GROUP BY+ORDER BY+LIMIT 与 topK()