Metabase数据库列值缓存的分析与改进
Posted 不舍昼夜夫
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Metabase数据库列值缓存的分析与改进相关的知识,希望对你有一定的参考价值。
1. 背景
Metabase是业界非常易用的开源BI工具,它屏蔽数据库很多细节,使得非技术人员可以无门槛的进行数据分析。此外,Metabase在用户使用体验上做了很多优化,比如缓存数据库列值,提升列值过滤响应速度。但是对于大型分布式数据库,Metabase的定时缓存列值存在一些问题。本文将对Metabase数据库列值缓存的机制原理进行分析,并提出一种可行的改进方案。
2. 数据库列值缓存分析
2.1 调度配置
Metabase在创建数据库连接时,默认开启数据库表信息和列值自动同步。表信息的同步周期默认为小时,列值的缓存同步时间默认为每天的24点。对于连接多个数据库的使用场景,需要自定义同步时间,避免所有同步集中在24点启动。开启下图中“这是一个大型数据库,让我选择Metabase同步和扫描的时间”选项,可以自定义同步时间。
开启后,在调度页签,可以指标调度时间。如下图所示:
同任务的调度信息如下图所示:
2.2 元数据存储
Metabase的元数据存储在mysql中,和列值缓存相关的有四张表,分别是metabase_database、metabase_table、metabase_field和metabase_fieldvalues。
2.2.1 metabase_database
metabase_database表中保存数据库的信息,数据库表信息同步调度时间保存在metadata_sync_schedule字段中,列值缓存调度时间保存在cache_field_values_schedule字段中。表结构如下:
CREATE TABLE `metabase_database` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`name` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL,
`description` text COLLATE utf8mb4_unicode_ci,
`details` text COLLATE utf8mb4_unicode_ci,
`engine` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL,
`is_sample` bit(1) NOT NULL DEFAULT b'0',
`is_full_sync` bit(1) NOT NULL DEFAULT b'1',
`points_of_interest` text COLLATE utf8mb4_unicode_ci,
`caveats` text COLLATE utf8mb4_unicode_ci,
`metadata_sync_schedule` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0 50 * * * ? *' COMMENT 'The cron schedule string for when this database should undergo the metadata sync process (and analysis for new fields).',
`cache_field_values_schedule` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0 50 0 * * ? *' COMMENT 'The cron schedule string for when FieldValues for eligible Fields should be updated.',
`timezone` varchar(254) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Timezone identifier for the database, set by the sync process',
`is_on_demand` bit(1) NOT NULL DEFAULT b'0' COMMENT 'Whether we should do On-Demand caching of FieldValues for this DB. This means FieldValues are updated when their Field is used in a Dashboard or Card param.',
`options` text COLLATE utf8mb4_unicode_ci COMMENT 'Serialized JSON containing various options like QB behavior.',
`auto_run_queries` bit(1) NOT NULL DEFAULT b'1' COMMENT 'Whether to automatically run queries when doing simple filtering and summarizing in the Query Builder.',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
2.2.2 metabase_table
metabase_table表中保存着数据库的表信息,Metabase有专门的定时任务更新这张表,默认每小时更新一次,也支持手动同步。表结构如下:
CREATE TABLE `metabase_table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`name` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL,
`description` text COLLATE utf8mb4_unicode_ci,
`entity_name` varchar(254) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`entity_type` varchar(254) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`active` bit(1) NOT NULL,
`db_id` int(11) NOT NULL,
`display_name` varchar(254) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`visibility_type` varchar(254) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`schema` varchar(254) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`points_of_interest` text COLLATE utf8mb4_unicode_ci,
`caveats` text COLLATE utf8mb4_unicode_ci,
`show_in_getting_started` bit(1) NOT NULL DEFAULT b'0',
`field_order` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'database',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_uniq_table_db_id_schema_name` (`db_id`,`schema`,`name`),
KEY `idx_table_db_id` (`db_id`),
KEY `idx_metabase_table_show_in_getting_started` (`show_in_getting_started`),
KEY `idx_metabase_table_db_id_schema` (`db_id`,`schema`),
CONSTRAINT `fk_table_ref_database_id` FOREIGN KEY (`db_id`) REFERENCES `metabase_database` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=235 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
2.2.3 metabase_field
metabase_field表中保存数据库表的所有列信息,和metabase_table表一样,有专门的定时任务更新这张表,默认每小时更新一次,支持手动同步。表结构如下:
CREATE TABLE `metabase_field` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`name` varchar(254) COLLATE utf8mb4_unicode_ci NOT NULL,
`base_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`special_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`active` bit(1) NOT NULL DEFAULT b'1',
`description` text COLLATE utf8mb4_unicode_ci,
`preview_display` bit(1) NOT NULL DEFAULT b'1',
`position` int(11) NOT NULL DEFAULT '0',
`table_id` int(11) NOT NULL,
`parent_id` int(11) DEFAULT NULL,
`display_name` varchar(254) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`visibility_type` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'normal',
`fk_target_field_id` int(11) DEFAULT NULL,
`last_analyzed` datetime DEFAULT NULL,
`points_of_interest` text COLLATE utf8mb4_unicode_ci,
`caveats` text COLLATE utf8mb4_unicode_ci,
`fingerprint` text COLLATE utf8mb4_unicode_ci COMMENT 'Serialized JSON containing non-identifying information about this Field, such as min, max, and percent JSON. Used for classification.',
`fingerprint_version` int(11) NOT NULL DEFAULT '0' COMMENT 'The version of the fingerprint for this Field. Used so we can keep track of which Fields need to be analyzed again when new things are added to fingerprints.',
`database_type` text COLLATE utf8mb4_unicode_ci,
`has_field_values` text COLLATE utf8mb4_unicode_ci COMMENT 'Whether we have FieldValues ("list"), should ad-hoc search ("search"), disable entirely ("none"), or infer dynamically (null)"',
`settings` text COLLATE utf8mb4_unicode_ci COMMENT 'Serialized JSON FE-specific settings like formatting, etc. Scope of what is stored here may increase in future.',
`database_position` int(11) NOT NULL DEFAULT '0',
`custom_position` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_uniq_field_table_id_parent_id_name` (`table_id`,`parent_id`,`name`),
KEY `idx_field_table_id` (`table_id`),
KEY `idx_field_parent_id` (`parent_id`),
CONSTRAINT `fk_field_parent_ref_field_id` FOREIGN KEY (`parent_id`) REFERENCES `metabase_field` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_field_ref_table_id` FOREIGN KEY (`table_id`) REFERENCES `metabase_table` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=5169 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
has_field_values字段值为search、list和auto-list时,Metabase会定时缓存该列的值。该列的值通过页面可以配置,配置页面如下图所示:
当选择“搜索框“和”一个所有值的列表“,Metabase会缓存该列的值。
2.2.4 metabase_fieldvalues
列值缓存保存在metabase_fieldvalues表中,列值处理成JSON格式,保存到该表的values字段中。表结构如下:
CREATE TABLE `metabase_fieldvalues` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_at` datetime NOT NULL,
`updated_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
`values` mediumtext COLLATE utf8mb4_unicode_ci,
`human_readable_values` text COLLATE utf8mb4_unicode_ci,
`field_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_fieldvalues_field_id` (`field_id`),
CONSTRAINT `fk_fieldvalues_ref_field_id` FOREIGN KEY (`field_id`) REFERENCES `metabase_field` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=2389 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
2.3 使用场景
列值缓存是通过提高查询速度,来优化用户使用体验。列值缓存使用场景主要有过滤器和Dashboard的筛选。
2.3.1 过滤器
如下图所示,在某列上选择”过滤器“,下面会列出该列的所有值,供用户选择。
如果没有缓存,用户点击时,查询后台数据库,会因查询延迟影响用户体验。
2.3.2 Dashboard筛选
Metabase配置的Dashboard页面添加筛选时,点列出筛选列表,会查询列值缓存。如下图所示:
3. 存在问题
Metabase通过定时任务查询数据库缓存列值的方法,提升了用户体验。但这种方式对于大型的分布式数据库,比如ClickHouse,会存在以下问题。
3.1 数据库负载高
缓存列值的定时任务,多线程并发查询列值,查询的SQL语句形式如下:
select system, count(*) as num from database.data_table group by system order by num;
这种查询会扫描全表,如果表中数据量比较大的话,查询的耗时在分钟级。并发查询,会导致数据库服务节点因负载过高而宕机。
3.2 列值无法保存
查询的列值Metabase会处理成JSON字符串保存到metabase_fieldvalues表的values字段。values字段在MySQL中的数据类型是text,text类型最大可存储64KB,对于大型分布式数据库,很多表的列值会超过64KB。这些大于64KB的列就无法缓存了。
4. 改进方案
虽然大型分布式数据库在Metabase中的缓存列值存在以上问题,但不能把列值缓存关闭。关闭列值缓存不仅影响用户使用体验,Dashboard的筛选功能没有列值将无法使用。因此需要对列值缓存进行改进。直接修改Metabase源码会影响后续版本升级,我们采取自研外部组件的方式改进。
4.1 统一时间字段
Metabase列值缓存定时任务,采取的是并发全表扫描的查询方式。该方式会导致数据库节点宕机,而在我们的业务场景,不需要扫描全表,查询最近两天的数据,即可满足业务需求。不同的表,因时间字段名各不相同,导致无法统一按时间字段过滤查询的SQL。因此需要统一数据表的时间字段。数据库时间字段统一命名为fdate,这样就可以统一按时间过滤查询的SQL。按时间过滤查询最近两天的数据,可以大幅减少扫描的数据量。结合控制并发查询请求数,就可以解决数据库节点宕机的问题。
4.2 列值采样
一些大型分布式表,列值的数据在百万级别。所有的列值保存到MySQL的text字段,会超出最大存储限制。在不修改Metabase元数据表结构的前提下,采取对列值采样,采样后的列值控制在5万以内,确保text类型的字段可以存储。
4.3 定时任务
把Metabase中列值缓存同步任务停止,开发外面独立列值缓存同步程序。将查询列值按Metabase定义的格式,写入指定MySQL表中。在用户使用体验上,和Metabase自己缓存列值一样。
5. 总结与思考
Metabase主要功能是为用户提供交互分析的Web页面,为了系统功能的完整性,其内部开发一些诸如数据同步的定时任务。对于小型的数据库,这种架构完成可以胜任。但对于大型的分布式数据库,这种架构就显得捉襟见肘。将这些定时任务拆分成独立的程序,是一种不错的改进Metabase思路。
以上是关于Metabase数据库列值缓存的分析与改进的主要内容,如果未能解决你的问题,请参考以下文章