分类
Articles

KYLIN查询响应慢问题分析

本文记录生产环境一次Kylin偶尔查询缓慢问题以及问题分析。

问题描述

业务反馈查询缓慢,通过日志查看确实有查询慢的现象。像下面的查询,总共扫描了11行数据,数据量121bytes,一般来说查询时间不应该超过一秒,但实际上查询却耗费了108秒,查询缓慢现象很明显。

==========================[QUERY]===============================
Query Id: 4f10b2e7-9109-4afa-a594-bd665c0d429e
SQL: select sum(search_times) as "搜索量" ,sum(down_times) as "下载量"  from xxx  where  dayno >= '20180707' and dayno <= '20180717'  order by "搜索量"  desc limit 10000
User: ADMIN
Success: true
Duration: 108.857
Project: easydata
Realization Names: [CUBE[name=xxxxx]]
Cuboid Ids: [1048576]
Total scan count: 11
Total scan bytes: 121
Result row count: 1
Accept Partial: true
Is Partial Result: false
Hit Exception Cache: false
Storage cache used: false
Is Query Push-Down: false
Is Prepare: false
Trace URL: null
Message: null
==========================[QUERY]===============================

问题分析

对于Java进程的卡顿问题,首先想到的是是否是GC停顿导致的。下图是“jstat -gcutil PID 1000 300”命令的输出结果:

从图中可以看出JVM的OLD区一直保持93%的使用率,空闲空间比例很低。图中可以看出发生了一次Full GC,Full GC花费时间是29秒,并且Full GC过后,OLD区域并没有释放内存。至此我们可以得出部分结论:JVM中有大量的数据在较长时间内占用内存空间,由于内存紧张,容易导致频繁的Minor GC和Full GC,并且GC的时间很长。通过kylin进程的GC日志也能证明以上结论,很明显这种情况是导致查询延迟的原因之一。

接下来我们需要知道是什么数据长时间占用这么大量的JVM内存。方式很简单,首先dump进程的内存,然后通过MAT进行分析。通过MAT的内存泄漏概览页面,发现有一个18.6GB的对象,看来它就是罪魁祸首。

接下来跟进该对象的引用关系,定位到这个18.6GB的对象是“DictionaryManager”里的“dictCache”字段,通过引用关系可知,它是一个“LocalCache”类,可以简单看做是一个Map类。下图是对象引用关系图:

我们在跟进kylin代码,看看“DictionaryManager.dictCache”里存放的是什么。

@see DictionaryManager 

public class DictionaryManager {
    ...
    private LoadingCache<String, DictionaryInfo> dictCache; // resource

    private DictionaryManager(KylinConfig config) {
        this.config = config;
        this.dictCache = CacheBuilder.newBuilder()//
                .softValues()//
                .removalListener(new RemovalListener<String, DictionaryInfo>() {
                    ...
                })//
                .maximumSize(config.getCachedDictMaxEntrySize())//
                .expireAfterWrite(1, TimeUnit.DAYS).build(new CacheLoader<String, DictionaryInfo>() {
                    @Override
                    ...
                    }
                });
    }

    private DictionaryInfo saveNewDict(DictionaryInfo newDictInfo) throws IOException {
        save(newDictInfo);
        dictCache.put(newDictInfo.getResourcePath(), newDictInfo);
        return newDictInfo;
    }

    ...
}

从上边摘录的代码可以看出,“DictionaryManager.dictCache”是一个有一天过期时间的LocalCache,里边存放的是cube的字典信息。这里解释下kylin中字典是什么,在kylin中对于每一个维度,在Hbase中并不存储维度的每个实际值,而是将每个维度值编码为一个整数,而维度值与编码后的整数值的映射关系就为字典。

我们在回到MAT分析页面,看看到底是什么字典占用了这么多内存。

可以看到占用内存的主要是“aaa”的字典信息。接下来在到kylin元数据目录统计下字典情况:

du -shm * | sort -n -r | head  -n5
21531   aaa
3010    bbb
2955    ccc
1792    ddd
1314    eee

“aaa”的字典总共占用21GB的磁盘空间,相比其它cube大太多。进一步统计得出是“URL”这个字段太大(21G)。并且从Hive表里查出“URL”字段一天的基数集大小为758w,严重超过了kylin的正常区间。

解决方案

优化”aaa”,减少维度的基数大小。

本作品采用 知识共享署名 4.0 国际许可协议 进行许可, 转载时请注明原文链接。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注