本文从部署、建模、导入、查询和监控五个模块介绍StarRocks的最佳使用方法。
部署
容量规划
【建议】做容量规划
为了实现集群高可用,建议集群最低3个节点,FE和BE分开部署也可以混合部署。
单节点配置要求:
BE推荐16核64GB内存以上,FE推荐8核16GB内存以上。
磁盘可以使用HDD或者SSD。
CPU必须支持AVX2指令集, cat /proc/cpuinfo |grep avx2 确认有输出即可,如果没有支持,建议更换机器,StarRocks的向量化技术需要CPU指令集支持才能发挥更好的效果。
网络需要万兆网卡和万兆交换机。
假定内存、磁盘都不会拖后腿的情况下,分析/查询的性能瓶颈在 CPU 的处理能力。所以通过对 CPU 的算力要求,来预估集群的数量。
集群需要的总 CPU 资源:e_core = scan_rows / cal_rows / e_rt * e_qps
| 变量名 | 变量含义 | 样例 |
|---|---|---|
| e_core | 预计要使用的CPU核数(vCPU) | |
| vCPU总数(逻辑处理器) = Socket数(CPU个数)x Core数(内核)x Thread数(超线程) | 540c | |
| scan_rows | 线上典型场景中的数据扫描量 | 3000万 |
| e_qps | 预期线上QPS | 180qps |
| e_rt | 预期线上响应时间 | 300ms |
| cal_rows | StarRocks针对SQL场景的计算能力 | 3000万/s |
场景样例:
数据量:事实表一年 3.6亿行数据,大约 100万行/天;
典型查询场景:一个月的事实表数据( 3000万 )和比较小的维度表(万级别)做关联,再进行 group by、sum 等聚合计算;
期望:响应时间在 300ms 以内,业务的峰值 QPS 达 180 左右。
估算解释:
StarRocks 的处理能力在“单核 1000万~1亿/秒”,此场景有「多表 join」和「group by」以及一些表达式函数,相对复杂,所以按照「 3000万/s 的计算能力」估算,需要 3 个 vCPU:3000万 / 3000万/s / 300ms = 3c。
并发峰值为180qps,因此需要 3 * 180 = 540c,即总共需要 540 个 vCPU。按单台物理机48虚拟核(vCPU)算,理论计算大约需要12台物理机。
实际 POC 过程中,用3台物理机16虚拟核进行压力测试,能够在40qps下满足300-500ms的响应时间。最终,线上确定用7台48虚拟核的物理机。 所以,还是建议用户要根据实际的业务场景做一下POC测试。
综上:根据POC的测试结果,建议用户搭建3个FE节点每个节点16核64GB内存、7个BE节点每个节点48核152GB内存。
其他说明:
计算业务越复杂、处理中的一行的列数量越多越复杂,每秒能处理的行数就会越少;
计算中「条件过滤」的效果越好(能过滤掉很多数据),则能处理的行数就会越多(因为内部有一些索引结构,能更快地帮助处理数据);
不同「表模型」会对处理能力有很大影响,上面是按照「明细模型」估算。其他模型,内部会有一些特殊处理,真实的数据量行数会和用户理解的数据量行数有一些差异;同时,分区/分桶,也会对查询性能有很大影响;(我们有其他相关文档来指导用户如何使用以达到最佳性能)
对于一些需要扫描大量数据的场景,磁盘的性能也会影响处理能力。需要时,可以使用SSD来加速。
基础环境配置
【必须】关注端口信息、swap关闭、overcommit设置为1、ulimit配置合理
StarRocks 为不同的服务使用特定的端口,TBDS中默认使用以下端口
FE的端口:
8030:FE HTTP Server 端口(http_port)
9020:FE Thrift Server 端口(rpc_port)
9030:FE MySQL Server 端口(query_port)
9010:FE 内部通讯端口(edit_log_port)
6090:FE 云原生元数据服务 RPC 监听端口(cloud_native_meta_port)
BE的端口:
9060:BE Thrift Server 端口(be_port)
8040:BE HTTP Server 端口(be_http_port)
9050:BE 心跳服务端口(heartbeat_service_port)
8060:BE bRPC 端口(brpc_port)
9070:BE 和 CN 的额外 Agent 服务端口。(starlet_port)
Broker的端口:
8000:Broker 上的 thrift server 端口,用于接收 FE 或 BE 的请求内存设置
Memory Overcommit
Memory Overcommit 允许操作系统将额外的内存资源分配给进程。建议您启用 Memory Overcommit。
修改配置文件。
cat >> /etc/sysctl.conf << EOF
vm.overcommit_memory=1
EOF
使修改生效。
sysctl -p
- Swap Space
建议您禁用 Swap Space。
检查并禁用 Swap Space 操作步骤如下:
- 关闭 Swap Space。
swapoff /
swapoff -a - 从/etc/fstab 文件中删除 Swap Space 信息。
/ swap swap defaults 0 0 - 确认 Swap Space 已关闭。
free -m
- ulimit 设置
如果最大文件描述符和最大用户进程的值设置得过小,StarRocks 运行可能会出现问题。TBDS出厂配置已经将最大文件描述符和最大用户进程数调大,通过命令可以查看:
cat >> /etc/security/limits.conf
机器配置
FE节点
【建议】 8C32GB
【必须】数据盘>=200GB,建议 SSD
BE节点
【建议】CPU:内存比,1:4,生产最小配置必须是 8C32GB+
【建议】单节点磁盘容量建议10TB,数据盘建议最大单盘2TB,建议SSD或者NVMe(如果是HDD,建议吞吐>150MB/s,IOPS>500)
【建议】集群中节点同构 (机器规格一样,避免木桶效应)
部署方案
【必须】生产环境必须最小集群规模 3FE+3BE(建议FE和BE独立部署),如果混合部署, 必须配置be.conf 中的mem_limit 为减去其他服务后剩余内存量,例如机器内存40G,上面已经部署了FE,理论上限会用8G,那么配置下mem_limit=30G (40-8-2),2g是给系统预留
【必须】生产必须 FE 高可用部署 ,1 Leader + 2 Follower,如果需要提高读并发,可以扩容Observer节点
【必须】生产必须使用负载均衡器连接集群进行读写,一般常用Nginx、Haproxy、F5等
建模
建表规范
- 仅支持UTF8编码
- 不支持修改表中的列名(即将支持)
- VARCHAR最大长度1048576
- KEY列不能使用FLOAT、DOUBLE类型
- 数据目录名、数据库名、表名、视图名、用户名、角色名 大小写敏感 ,列名和分区名 大小写不敏感
- 主键模型中,主键长度不超过128字节
模型选择
- 如果想要保留明细,建议使用明细模型
- 如果有明确主键,主键非空,写少读多,非主键列要利用索引,建议使用主键模型
- 如果有明确主键,主键可能为空,写多读少,建议使用更新模型
- 如果只想保留聚合数据,建议使用聚合模型
排序列和前缀索引选择
DUPLICATE KEY、AGGREGATE KEY、UNIQUE KEY中指定的列,3.0版本以前,主键模型中排序列通过PRIMARY KEY指定,3.0版本起,主键模型中排序列通过ORDER BY指定。
前缀索引是在排序列基础上引入的稀疏索引,进一步提升查询效率,全部加载在内存中
- 经常作为查询条件的列,建议选为排序列,例如经常用user_id过滤,where user_id=234,可以把user_id放在第一列
- 排序列建议选择3-5列,过多会增大排序开销,降低导入效率
- 前缀索引不超过36字节,不能超过3列,遇到varchar会截断,前缀索引中不能包含 float 或 double 类型的列
因此可以结合实际业务查询场景,在确定 key 列以及字段顺序时,要充分考虑前缀索引带来的优势。尽可能将经常需要查询的key列字段,放置在前面,字段数据类型尽量选择 date 日期类型或者 int 等整数类型。
举例:
CREATE TABLE site_access(
site_id BIGINT DEFAULT '10',
city_code INT,
site_name VARCHAR(50),
pv BIGINT DEFAULT '0'
)
DUPLICATE KEY(site_id,city_code,site_name)
DISTRIBUTED BY HASH(site_id);
在 site_access 表中,前缀索引为 site_id( 8 Bytes ) + city_code( 4 Bytes ) + site_name(前 24 Bytes)
- 如果查询条件只包含 site_id 和 city_code 两列,如下所示,则可以大幅减少查询过程中需要扫描的数据行:
select sum(pv) from site_access where site_id = 123 and city_code = 2;
- 如果查询条件只包含 site_id 一列,如下所示,可以定位到只包含 site_id 的数据行:
select sum(pv) from site_access where site_id = 123;
- 如果查询条件只包含 city_code 一列,如下所示,则需要扫描所有数据行,排序效果大打折扣:
select sum(pv) from site_access where city_code = 2;
- 如果 site_id和city_code联合查询和单独city_code的查询占比不相上下,可以考虑创建同步物化视图调整列顺序来达到查询性能提升,物化视图中的city_code放到第一列
create materialized view site_access_city_code_mv as
select city_code, site_id, site_name, pv
from site_access;
Bad case:
CREATE TABLE site_access_bad(
site_name VARCHAR(20),
site_id BIGINT DEFAULT '10',
city_code INT,
pv BIGINT DEFAULT '0'
)
PRIMARY KEY(site_id)
DISTRIBUTED BY HASH(site_id)
ORDER BY(site_id,city_code);
在 site_access_bad 表中,前缀索引只有 site_name
分区选择
【建议】值不会变化的时间列经常用于where过滤,使用该列创建分区
【建议】有数据淘汰需求的场景建议选择动态分区
【必须】数据更新有明显的冷热特征的,必须创建分区,例如经常更新最近一周的数据,可以按天分区
【必须】单个分区数据量不要超过100GB
【必须】超过50G或者5KW的表建议创建分区
【建议】按需创建分区,不要提前创建大量空分区,避免元数据太多占用fe的内存
当前支持时间类型(Range分区、表达式分区)、字符串(List分区)、数字(Range分区、List分区)
默认最大支持1024个分区,可以通过参数调整,不过一般情况下不需要调整
分桶选择
生产必须使用 3 副本
分桶个数判断
【必须】单个桶按照1GB预估,原始数据按照10GB(导入starrocks后,压缩比7:1~10:1)预估
当按照以上策略估算出来的分桶个数小于be个数的时候,最终分桶个数以be个数为准,例如6个be节点,按照1GB每个桶预估分桶个数为1,最终分桶个数取6
【必须】非分区表不要使用动态分桶,按照实际数据量估算分桶个数
【必须】如果分区表的各个分区的数据差异很大,建议不要使用动态分桶策略
分桶裁剪和数据倾斜如何抉择?
【建议】如果分桶列是where中经常用到的列,且分桶列的重复度比较低(例如用户id、事务id等),则可以利用该列作为分桶列
【建议】如果查询条件中有city_id和site_id,city_id取值只有几十,如果仅仅使用city_id分桶,则可能出现某些桶的数据量会比较大,出现数据倾斜,这个时候可以考虑使用city_id和site_id联合作为分桶字段,不过这样做的缺点是如果查询条件中只有city_id的时候,没办法利用分桶裁剪
【建议】如果没有合适的字段作为分桶字段打散数据,可以利用random分桶,不过这样的话没办法利用分桶裁剪的属性
【必须】2个或多个超过KW行以上的表join,建议使用colocate
字段类型
【建议】不要使用NULL属性
【必须】时间类型和数字类型的列选择正确的类型,否则计算的开销会比较大,例如时间类型的数据“2024-01-01 00:00:00”不要使用VARCHAR存储,这样没办法利用到starrocks内部的zonemap索引,没办法加速过滤
索引选择
bitmap索引
适合基数在10000-100000左右的列
适合等值条件 (=) 查询或 [NOT] IN 范围查询的列
不支持为 FLOAT、DOUBLE、BOOLEAN 和 DECIMAL 类型的列创建 Bitmap 索引
城市、性别这些基数在255以下的列不需要创建bitmap索引,因为starrocks内部有低基数字典,会针对这些case自动创建低基数字典用于加速
明细模型和主键模型,所有列可以创建bitmap索引,聚合模型和更新模型,只有Key列支持创建bitmap索引
bloomfilter索引
适合基数在100000+的列,列的重复度很低
适合 in 和 = 过滤条件的查询
不支持为 TINYINT、FLOAT、DOUBLE 和 DECIMAL 类型的列创建 Bloom filter 索引
主键模型和明细模型中所有列都可以创建 Bloom filter 索引;聚合模型和更新模型中,只有维度列(即 Key 列)支持创建 Bloom filter 索引
导入
使用建议
【必须】生产禁止使用insert into values() 导入数据
【必须】建议导入批次间隔5s+,也就是攒批写入,尤其是实时场景
【建议】主键模型更新场景,建议开启索引落盘,磁盘强制SSD、NVMe或者更高性能的磁盘。
【建议】比较多ETL(insert into select)的场景,建议开启spill落盘功能,避免内存超过限制
数据生命周期
【建议】使用truncate删除数据,不要使用delete
【必须】完整的UPDATE语法只能用于3.0版本以后的主键模型,禁止高并发 update,建议每次update操作需要间隔分钟以上
【必须】如果使用DELETE删除数据,需要带上where条件,并且禁止并发执行delete,例如要删除id=1,2,3,4,……1000,禁止delete xxx from tbl1 where id=1这样的语句执行1000条,建议delete xxx from tbl1 where id in (1,2,3…,1000)
【必须】drop操作默认会进入FE 回收站,默认保留86400(s),也就是1天(这个期间可以RECOVER恢复,避免误操作),受参数catalog_trash_expire_second控制,超过1天后会进入BE的trash目录,默认保留259200(s),也就是3天(2.5.17,3.0.9,3.1.6之后默认值改为了86400,也就是1天),受参数trash_file_expire_time_sec控制,如果drop后需要尽快释放磁盘,可以调小fe和be的trash保留时间
查询
高并发场景
【建议】尽可能利用分区分桶裁剪,具体参考上文的分区和分桶选择部分
【必须】调大客户的并发限制,可以设置为1000,默认100,SET PROPERTY FOR ‘jack’ ‘max_user_connections’ = ‘1000’;
【必须】开启page cache、query cache
数据精度
【必须】如果需要精确结果的,强制使用decimal类型,不要使用float、double类型
SQL查询
【必须】避免SELECT *,建议指定需要查询的列,例如select col0,col1 from tb1
【必须】避免全表扫描,建议增加过滤的谓词,例如SELECT col0,col1 from tb1 WHERE id=123,select col0,col1 from tb1 where dt>‘2024-01-01’
【必须】避免大数据量的下载,如果要使用,强制使用分页,SELECT col0,col1,col2,…,col50 from tb order by id limit 0,50000
【必须】分页操作需要加上ORDER BY,要不然是无序的
【建议】避免使用一些不必要的函数或者表达式,比如:
- 谓词中含CAST, 可以移除
-- bad case
select l_tax
from lineitem
where cast(l_shipdate as varchar) > substr('1990-01-02 12:30:31',1,10);
-- good case
select l_tax
from lineitem
where l_shipdate > '1990-01-02';
- 过度使用函数处理表达式
-- bad case
select count(1)
from lineitem
where l_shipdate >= regexp_extract("TIME:1996-01-02 20:00:00", "(\\d{4}-\\d{2}-\\d{2})", 1);
-- good case
select count(1)
from lineitem
where l_shipdate >= "1996-01-02"
-- bad case
select count(1)
from lineitem
where DATE_FORMAT(l_shipdate,'%Y-%m-%d') >= "1996-01-02"
-- good case
select count(1)
from lineitem
where l_shipdate >= "1996-01-02"
- JOIN
【必须】关联的字段类型匹配,虽然starrocks已经在内部做了隐式转换来达到最优的性能,不过建议大家使用类型一致的字段join,避免使用float、double类型join,可能会导致结果不准确
【必须】关联字段建议不要使用函数或者表达式,例如 JOIN on DATE_FORMAT(tb1.col1,’%Y-%m-%d’)=DATE_FORMAT(tb2.col1,’%Y-%m-%d’)
【必须】2个或多个KW行以上的表JOIN,推荐colocate join
【建议】避免笛卡尔积,查询多个表需要指定连接条件
-- bad case
SELECT * FROM table1, table2;
-- good case
SELECT * FROM table1, table2
ON table1.column1 = table2.column1;
- 正确关联子查询 * 在子查询中,确保外部查询和子查询之间的列有明确的关联
-- bad case
SELECT * FROM table1
WHERE column1 IN (SELECT column2 FROM table2);
-- good case
SELECT * FROM table1
WHERE column1 IN (SELECT column2 FROM table2 WHERE table1.column3 = table2.column3);
- 使用AND条件而不是OR
-- bad case
SELECT *
FROM table1
JOIN table2
WHERE (table1.column1 = table2.column1 OR table1.column2 = table2.column2);
-- good case
SELECT *
FROM table1
JOIN table2
ON table1.column1 = table2.column1 AND table1.column2 = table2.column2;
使用物化视图加速查询
- 精确去重
以下示例基于一张广告业务相关的明细表 advertiser_view_record ,其中记录了点击日期 click_time 、广告代码 advertiser 、点击渠道 channel 以及点击用户 ID user_id 。
CREATE TABLE advertiser_view_record(
click_time DATE,
advertiser VARCHAR(10),
channel VARCHAR(10),
user_id INT
) distributed BY hash(click_time);
该场景需要频繁使用如下语句查询点击广告的 UV。
SELECT advertiser, channel, count(distinct user_id)
FROM advertiser_view_recordGROUP
BY advertiser, channel;
如需实现精确去重查询加速,您可以基于该明细表创建一张物化视图,并使用 bitmap_union() 函数预先聚合数据。
CREATE MATERIALIZED VIEW advertiser_uv ASSELECT advertiser, channel, bitmap_union(to_bitmap(user_id))
FROM advertiser_view_record
GROUP BY advertiser, channel;
物化视图创建完成后,后续查询语句中的子查询 count(distinct user_id) 会被自动改写为 bitmap_union_count (to_bitmap(user_id)) 以便查询命中物化视图。
- 异步物化视图最多支持3层嵌套
利用cache 加速查询
【建议】page cache,建议开启,可以加速数据扫描场景,如果内存有冗余,可以尽可能调大限制,默认是mem_limit*20%
【建议】query cache,建议开启,可以加速单表或多表JOIN的聚合场景
查询中不能包含 rand 、 random 、 uuid 和 sleep 等不确定性 (Nondeterministic) 函数
【建议】data cache,用于存算分离和湖分析场景,建议这两个场景下默认开启
元数据周期性后台刷新
元数据周期性后台刷新方案是 StarRocks 用于加速检索 Iceberg Catalog 中元数据的策略
【建议】可以通过系统变量 plan_mode 调整 Iceberg Catalog 元数据检索方案
元数据缓存周期性刷新与元数据自动异步更新策略配合使用,可以进一步加快数据访问速度,降低从外部数据源读取数据的压力,提升查询性能
监控
慢查询分析
【必须】通过审计插件把fe.audit.log的数据导入一个表方便进行分析慢查询。
监控告警
【建议】登录TM页面->集群服务可以查看监控信息。
原生监控指标也可以到以下页面查看:
FE监控指标页面:http://${fe_leader_ip}:8030/metrics
BE监控页面:http://${be_ip}:8040/metrics
大查询定位
大查询包括扫描大量数据或占用过多 CPU 和内存资源的查询。如果不施加限制,大查询很容易耗尽集群资源并导致系统过载。为了解决这个问题,StarRocks 提供了一系列措施来监控和管理大查询,防止大查询独占集群资源
处理 StarRocks 大查询的总体思路如下:
- 通过资源组和查询队列对大查询设置自动预防措施
- 实时监控大查询,并及时终止绕过预防措施的大查询
- 分析审计日志和大查询日志,研究大查询的模式,优化先前设置的预防机制参数
具体定位方法:
查看当前FE上正在运行的查询
SQL命令: show proc '/current_queries'
返回结果包括以下几列:
- QueryId
- ConnectionId
- Database:当前查询的DB
- User:用户
- ScanBytes:当前已扫描的数据量,单位Bytes
- ProcessRow:当前已扫描的数据行数
- CPUCostSeconds:当前查询已使用的CPU时间,单位秒。此为多个线程累加的CPU时间,举个例子,如果有两个线程分别占用1秒和2秒的CPU时间,那么累加起来的CPU时间为3秒
- MemoryUsageBytes:当前占用的内存。如果查询涉及到多个BE节点,此值即为该查询在所有BE节点上占用的内存之和
- ExecTime:查询从发起到现在的时长,单位为毫秒
mysql> show proc '/current_queries';
+-------------------------------------+-------------+------------+-----+----------+----------------+---------------+------------------+----------+
| QueryId | ConnectionId| Database | User| ScanBytes| ProcessRows | CPUCostSeconds| MemoryUsageBytes |ExecTime |
+-------------------------------------+-------------+------------+-----+----------+----------------+---------------+------------------+----------+
| 7c56495f-ae8b-11ed-8ebf-00163e00accc| 4 | tpcds_100g | root| 37.88 MB | 1075769 Rows | 11.13 Seconds | 146.70 MB | 3804 |
| 7d543160-ae8b-11ed-8ebf-00163e00accc| 6 | tpcds_100g | root| 13.02 GB | 487873176 Rows | 81.23 Seconds | 6.37 GB | 2090 |
+-------------------------------------+-------------+------------+-----+----------+----------------+---------------+------------------+----------+
2 rows in set (0.01 sec)
查看某个查询在每个BE节点上的资源消耗
SQL命令: show proc '/current_queries/${query_id}/hosts'
返回结果有多行,每行描述该查询在对应BE节点上的执行信息,包括以下几列:
- Host:BE节点信息
- ScanBytes:已经扫描的数据量,单位Bytes
- ScanRows:已经扫描的数据行数
- CPUCostSeconds:已使用的CPU时间
- MemUsageBytes:当前占用的内存
mysql> show proc '/current_queries/7c56495f-ae8b-11ed-8ebf-00163e00accc/hosts';
+-------------------+----------+------------+---------------+--------------+
| Host | ScanBytes| ScanRows | CpuCostSeconds| MemUsageBytes|
+-------------------+----------+------------+---------------+--------------+
| 172.26.34.185:8060| 11.61 MB | 356252 Rows| 52.93 Seconds | 51.14 MB |
| 172.26.34.186:8060| 14.66 MB | 362646 Rows| 52.89 Seconds | 50.44 MB |
| 172.26.34.187:8060| 11.60 MB | 356871 Rows| 52.91 Seconds | 48.95 MB |
+-------------------+----------+------------+---------------+--------------+
3 rows in set (0.00 sec)
大查询业务重保
StarRocks 提供了两种预防工具处理大查询——资源组和查询队列。核心业务可以使用资源组来过滤并熔断大查询。而查询队列可以帮助您在系统达到并发阈值或资源限制时对新查询请求进行排队,从而防止系统过载。
【建议】利用资源隔离大查询熔断,小查询保底
# shortquery_group 资源组用于核心业务重保
CREATE RESOURCE GROUP shortquery_group
TO
(user='rg1_user1', role='rg1_role1', db='db1', query_type in ('select'), source_ip='192.168.x.x/24'),
WITH (
'type' = 'short_query',
'cpu_core_limit' = '10',
'mem_limit' = '20%');
# bigquery_group 用于大查询熔断,避免大查询将集群资源打满
CREATE RESOURCE GROUP bigquery_group
TO
(user='rg1_user2', role='rg1_role1', query_type in ('select')),
WITH (
"type" = 'normal',
'cpu_core_limit' = '10',
'mem_limit' = '20%',
'big_query_cpu_second_limit' = '100',
'big_query_scan_rows_limit' = '100000',
'big_query_mem_limit' = '1073741824');