Kusto查询性能波动及缓存、扫描统计相关技术咨询
表结构
.create table iot (ts: datetime, dim1: string, dim2: string, value: real)
查询性能波动现象
查询逻辑为获取不同维度组合的最新数据点并关联为单行,核心代码如下:
let q1 = iot | where dim1 == 'X' and dim2 == 'Y' | top 1 by ts | project NameXY = 'XY', ValueXY = value, tsXY = ts, binding = 1; let q2 = ...; let q3 = ...; ... q1 | join q2 on binding | join q3 on binding
注:已知存在更高效的实现方式,此为纯理论问题探讨。
该查询执行时间波动极大,从0.07秒到1分13秒不等(期间子查询数量及维度条件无变化)。通过.show queries对比两次执行的统计数据:
快速执行实例(0.07秒)统计
"CacheStatistics": { "Memory": { "Hits": 0, "Misses": 0 }, "Disk": { "Hits": 0, "Misses": 0 }, "Shards": { "Hot": { "HitBytes": 51980752, "MissBytes": 0, "RetrieveBytes": 0 }, "Cold": { "HitBytes": 0, "MissBytes": 0, "RetrieveBytes": 0 }, "BypassBytes": 0 } } "ScannedExtentsStatistics": { "MinDataScannedTime": "2022-10-05T00:41:28.2285664Z", "MaxDataScannedTime": "2022-11-16T10:43:41.8025338Z", "TotalExtentsCount": 195, "ScannedExtentsCount": 168, "TotalRowsCount": 1037201352, "ScannedRowsCount": 660165419 }
慢速执行实例(1分13秒)统计
"CacheStatistics": { "Memory": { "Hits": 0, "Misses": 0 }, "Disk": { "Hits": 0, "MissBytes": 0, "RetrieveBytes": 0 }, "Shards": { "Hot": { "HitBytes": 106393759, "MissBytes": 0, "RetrieveBytes": 0 }, "Cold": { "HitBytes": 179996560, "MissBytes": 0, "RetrieveBytes": 0 }, "BypassBytes": 0 } } "ScannedExtentsStatistics": { "MinDataScannedTime": "2022-10-05T00:41:28.2285664Z", "MaxDataScannedTime": "2022-11-15T20:44:31.0044810Z", "TotalExtentsCount": 192, "ScannedExtentsCount": 168, "TotalRowsCount": 1036292805, "ScannedRowsCount": 659257190 }
可见慢查询读取了约170MB冷分片数据,但该数据量不应导致超1分钟的耗时。
环境参数
- SKU:Standard_D12_v2 x 2
- 缓存周期:30天
- 近期缓存利用率:不足1%
- 表总行数:3.45亿
- 总扩展数:64
- 查询维度均包含近期数据(数分钟至数小时前)
技术疑问解答
1. 为何不同执行实例读取的冷分片字节数存在差异?查询数据在缓存与磁盘的分布保持稳定。
Kusto的分片冷热状态并非完全静态,即使缓存分布看似稳定,仍可能存在以下情况:
- 集群后台的缓存淘汰或预热操作可能在查询间隙调整了分片的冷热标记,导致两次查询命中的分片集合有差异;
- 分片的冷热判定基于访问频率,若其他查询在两次目标查询之间访问了部分冷分片,可能导致这些分片临时被标记为热,反之热分片也可能因闲置被降级为冷;
- 统计数据中的
HitBytes是实际读取的分片数据量,即使是同一分片,若查询执行时的并行度不同,或分片数据的压缩状态临时变化,也可能导致读取字节数存在细微差异。
2. 查询引擎是否无法优先查询缓存(仅在未找到所需top行数时再查询磁盘)?
当前Kusto的top查询逻辑并非按“缓存优先,磁盘兜底”的顺序执行。对于带过滤条件的top by ts查询,引擎会先根据分片的MaxCreatedOn/MinCreatedOn元数据筛选出可能包含目标数据的分片,然后并行扫描这些分片——无论分片处于热缓存还是冷磁盘状态。
也就是说,引擎不会先扫描缓存中的分片、确认是否找到足够的top数据后再去扫描磁盘分片,而是一次性调度所有符合条件的分片扫描任务,这就导致即使缓存中已经有最新数据,仍可能触发冷分片的读取,进而影响查询耗时。
3. MinDataScannedTime是什么日期?来源何处?其接近所有扩展的最小MinCreatedOn时间但并不完全相等。
MinDataScannedTime是本次查询扫描到的所有数据行中,最小的ts字段值(即数据本身的业务时间戳),而非扩展的MinCreatedOn(扩展被写入集群的时间)。两者接近但不等的原因是:
- 扩展的
MinCreatedOn是扩展被写入集群的时间,而数据行的ts是业务侧生成的时间戳,通常业务时间会略早于扩展创建时间; - 若某个扩展中包含的所有数据行的
ts都大于其他扩展的ts,则扫描后的MinDataScannedTime会大于该扩展的MinCreatedOn,最终导致整体的MinDataScannedTime与所有扩展的最小MinCreatedOn不完全一致。
4. 统计中的总行数与扩展数为表中数据的3倍(因3个查询),但无法理解扫描数的由来:尝试过滤MinCreatedOn大于上述日期的扩展,未得到56个扩展(168/3)。
统计中的TotalExtentsCount和TotalRowsCount是所有子查询(q1、q2、q3)扫描的扩展和行数的总和,因此呈现为单表数据的3倍是符合预期的。
而ScannedExtentsCount=168不等于3×56的原因在于:
- 不同子查询的过滤条件(dim1/dim2的组合)可能重叠扫描部分扩展,即同一个扩展可能被多个子查询扫描,导致总扫描扩展数并非简单的单查询扫描数×3;
- 扩展的
MinCreatedOn和数据的ts并非完全对应,过滤MinCreatedOn大于MinDataScannedTime的扩展,可能排除了部分实际包含符合条件数据的扩展,导致计算出的扩展数与实际扫描数不符; - Kusto的查询优化器可能会根据元数据筛选出“可能包含目标数据”的扩展,这些扩展的范围可能略大于你手动过滤的结果,比如某些扩展的
MinCreatedOn早于MinDataScannedTime,但其中仍存在符合查询条件的最新数据行,因此会被纳入扫描范围。
内容的提问来源于stack exchange,提问作者greatvovan

