[ {"url": "https://pingcap.com/cases-cn/user-case-xiaomi/", "title": "TiDB 在小米的应用实践", "content": " 作者:张良,小米 DBA 负责人;潘友飞,小米 DBA;王必文,小米开发工程师。 一、应用场景介绍 MIUI 是小米公司旗下基于 Android 系统深度优化、定制、开发的第三方手机操作系统,也是小米的第一个产品。MIUI 在 Android 系统基础上,针对中国用户进行了深度定制,在此之上孕育出了一系列的应用,比如主题商店、小米音乐、应用商店、小米阅读等。图 1 MIUI Android 系统界面图目前 TiDB 主要应用在: 小米手机桌面负一屏的快递业务 商业广告交易平台素材抽审平台 这两个业务场景每天读写量均达到上亿级,上线之后,整个服务稳定运行;接下来我们计划逐步上线更多的业务场景,小米阅读目前正在积极的针对订单系统做迁移测试。二、TiDB 特点 TiDB 结合了传统的 RDBMS 和 NoSQL 的最佳特性,兼容 MySQL 协议,支持无限的水平扩展,具备强一致性和高可用性。具有如下的特性: 高度兼容 MySQL,大多数情况下无需修改代码即可从 MySQL 轻松迁移至 TiDB,即使已经分库分表的 MySQL 集群亦可通过 TiDB 提供的迁移工具进行实时迁移。 水平弹性扩展,通过简单地增加新节点即可实现 TiDB 的水平扩展,按需扩展吞吐或存储,轻松应对高并发、海量数据场景。 分布式事务,TiDB 100% 支持标准的 ACID 事务。 真正金融级高可用,相比于传统主从(M-S)复制方案,基于 Raft 的多数派选举协议可以提供金融级的 100% 数据强一致性保证,且在不丢失大多数副本的前提下,可以实现故障的自动恢复(auto-failover),无需人工介入。 TiDB 的架构及原理在 官网 里有详细介绍,这里不再赘述。图 2 TiDB 基础架构图三、背景 跟绝大数互联网公司一样,小米关系型存储数据库首选 MySQL,单机 2.6T 磁盘。由于小米手机销量的快速上升和 MIUI 负一屏用户量的快速增加,导致负一屏快递业务数据的数据量增长非常快, 每天的读写量级均分别达到上亿级别,数据快速增长导致单机出现瓶颈,比如性能明显下降、可用存储空间不断降低、大表 DDL 无法执行等,不得不面临数据库扩展的问题。 比如,我们有一个业务场景(智能终端),需要定时从几千万级的智能终端高频的向数据库写入各种监控及采集数据,MySQL 基于 Binlog 的单线程复制模式,很容易造成从库延迟,并且堆积越来越严重。对于 MySQL 来讲,最直接的方案就是采用分库分表的水平扩展方式,综合来看并不是最优的方案,比如对于业务来讲,对业务代码的侵入性较大;对于 DBA 来讲提升管理成本,后续需要不断的拆分扩容,即使有中间件也有一定的局限性。 同样是上面的智能终端业务场景,从业务需求看,需要从多个业务维度进行查询,并且业务维度可能随时进行扩展,分表的方案基本不能满足业务的需求。了解到 TiDB 特点之后,DBA 与业务开发沟通确认当前 MySQL 的使用方式,并与 TiDB 的兼容性做了详细对比,经过业务压测之后,根据压测的结果,决定尝试将数据存储从 MySQL 迁移到 TiDB。经过几个月的线上考验,TiDB 的表现达到预期。四、兼容性对比 TiDB 支持包括跨行事务、JOIN、子查询在内的绝大多数 MySQL 的语法,可以直接使用 MySQL 客户端连接;对于已用 MySQL 的业务来讲,基本可以无缝切换到 TiDB。二者简单对比如下几方面: 功能支持 TiDB 尚不支持如下几项: 增加、删除主键 非 UTF8 字符集 视图(即将支持)、存储过程、触发器、部分内置函数 Event 全文索引、空间索引 默认设置 字符集、排序规则、sql_mode、lower_case_table_names 几项默认值不同。 事务 TiDB 使用乐观事务模型,提交后注意检查返回值。 TiDB 限制单个事务大小,保持事务尽可能的小。 TiDB 支持绝大多数的 Online DDL。 另,一些 MySQL 语法在 TiDB 中可以解析通过,不会产生任何作用,例如: create table 语句中 engine、partition 选项都是在解析后忽略。 详细信息可以访问官网:https://pingcap.com/docs-cn/sql/mysql-compatibility/ 。 五、压测 5.1 目的 通过压测 TiDB 了解一下其 OLTP 性能,看是否满足业务要求。5.2 机器配置 组件 实例数量 CPU 型号 内存 磁盘 版本 操作系统 TiDB 3 Intel® Xeon® CPU E5-2620 v3 @ 2.40GHz 128G SSD Raid 5 2.0.3 CentOS Linux release 7.3.1611 PD 3 Intel® Xeon® CPU E5-2620 v3 @ 2.40GHz 128G SSD Raid 5 2.0.3 CentOS Linux release 7.3.1611 TiKV 4 Intel® Xeon® CPU E5-2620 v3 @ 2.40GHz 128G SSD Raid 5 2.0.3 CentOS Linux release 7.3.1611 5.3 压测内容以及结果 5.3.1 标准 Select 压测 Threads QPS Latency (avg / .95 / max) 8 12650.81 0.63 / 0.90 / 15.62 16 21956.21 0.73 / 1.50 / 15.71 32 31534.8 1.01 / 2.61 / 25.16 64 38217 1.67 / 5.37 / 49.80 128 39943.05 3.20 / 8.43 / 58.60 256 40920.64 6.25 / 13.70 / 95.13 图 3 标准 Select 压测图5.3.2 标准 OLTP 压测 Threads TPS QPS Latency (avg / .95 / max) 8 428.9 8578.09 18.65 / 21.89 / 116.06 16 731.67 14633.35 21.86 / 25.28 / 120.59 32 1006.43 20128.59 31.79 / 38.25 / 334.92 64 1155.44 23108.9 55.38 / 71.83 / 367.53 128 1121.55 22431 114.12 / 161.51 / 459.03 256 941.26 18825.1 271.94 / 369.77 / 572.88 图 4 标准 OLTP 压测图5.3.3 标准 Insert 压测 Threads QPS Latency (avg / .95 / max) 8 3625.75 2.20 / 2.71 / 337.94 16 6527.24 2.45 / 3.55 / 160.84 32 10307.66 3.10 / 4.91 / 332.41 64 13662.83 4.68 / 7.84 / 467.56 128 15100.44 8.47 / 16.41 / 278.23 256 17286.86 14.81 / 25.74 / 3146.52 图 5 标准 Insert 压测图通过压测发现 TiDB 稳定性上与预期稍有差别,不过压测的 Load 会明显高于生产中的业务 Load,参考低 Threads 时 TiDB 的表现,基本可以满足业务对 DB 的性能要求,决定灰度一部分 MySQL 从库读流量体验一下实际效果。六、迁移过程 整个迁移分为 2 大块:数据迁移、流量迁移。6.1 数据迁移 数据迁移分为增量数据、存量数据两部分。 对于存量数据,可以使用逻辑备份、导入的方式,除了传统的逻辑导入外,官方还提供一款物理导入的工具 TiDB Lightning。 对于增量备份可以使用 TiDB 提供的 Syncer (新版已经更名为 DM - Data Migration)来保证数据同步。 Syncer 结构如图 6,主要依靠各种 Rule 来实现不同的过滤、合并效果,一个同步源对应一个 Syncer 进程,同步 Sharding 数据时则要多个 Syncer 进程。图 6 Syncer 结构图使用 Syncer 需要注意: 做好同步前检查,包含 server-id、log_bin、binlog_format 是否为 ROW、binlog_row_image 是否为 FULL、同步相关用户权限、Binlog 信息等。 使用严格数据检查模式,数据不合法则会停止。数据迁移之前最好针对数据、表结构做检查。 做好监控,TiDB 提供现成的监控方案。 对于已经分片的表同步到同一个 TiDB 集群,要做好预先检查。确认同步场景是否可以用 route-rules 表达,检查分表的唯一键、主键在数据合并后是否冲突等。 6.2 流量迁移 流量切换到 TiDB 分为两部分:读、写流量迁移。每次切换保证灰度过程,观察周期为 1~2 周,做好回滚措施。 读流量切换到 TiDB,这个过程中回滚比较简单,灰度无问题,则全量切换。 再将写入切换到 TiDB,需要考虑好数据回滚方案或者采用双写的方式(需要断掉 Syncer)。 七、集群状况 7.1 配置 集群配置采用官方推荐的 7 节点配置,3 个 TiDB 节点,3 个 PD 节点,4 个 TiKV 节点,其中每个 TiDB 与 PD 为一组,共用一台物理机。后续随着业务增长或者新业务接入,再按需添加 TiKV 节点。7.2 监控 监控采用了 TiDB 的提供的监控方案,并且也接入了公司开源的 Falcon,目前整个集群运行比较稳定,监控如图 7。 图 7 监控图八、遇到的问题、原因及解决办法 问题 原因及解决办法 在一个 DDL 里不能对多个列或者多个索引做操作。 ADD/DROP INDEX/COLUMN 操作目前不支持同时创建或删除多个索引或列,需要拆分单独执行,官方表示 3.0 版本有计划改进。 部分操作符查询优化器支持不够好,比如 or 操作符会使用 TableScan,改写成 union all 可避免。 官方表示目前使用 or 操作符确实在执行计划上有可能不准确,已经在改进计划中,后续 3.0 版本会有优化。 重启一个 PD 节点的时候,业务能捕捉到 PD 不可用的异常,会报 PD server timeout 。 因为重启的是 Leader 节点,所以重启之前需要手动切换 Leader,然后进行重启。官方建议这里可以通过重启前做 Leader 迁移来减缓,另外后续 TiDB 也会对网络通讯相关参数进行梳理和优化。 建表语句执行速度相比 MySQL 较慢 多台 TiDB 的时候,Owner 和接收 create table 语句的 TiDB Server 不在一台 Server 上时,可能比 MySQL 慢一些,每次操作耗时在 0.5s 左右,官方表示会在后续的版本中不断完善。 pd-ctl 命令行参数解析严格,多一个空格会提示语法错误。 官方表示低版本中可能会有这个问题,在 2.0.8 及以上版本已经改进。 tikv-ctl 命令手动 compact region 失败。 在低版本中通常是因为 tikv-ctl 与集群版本不一致导致的,需要更换版本一致的 tikv-ctl,官方表示在 2.1 中已经修复。 大表建索引时对业务有影响 官方建议在业务低峰期操作,在 2.1 版本中已经增加了操作优先级以及并发读的控制,情况有改善。 存储空间放大问题 该问题属于 RocksDB,RocksDB 的空间放大系数最理想的值为 1.111,官方建议在某些场景下通过 TiKV 开启 RocksDB 的 dynamic-level-bytes 以减少空间放大。 九、后续和展望 目前 TiDB 在小米主要提供 OLTP 服务,小米手机负一屏快递业务为使用 TiDB 做了一个良好的开端,而后商业广告也有接入,2 个业务均已上线数月,TiDB 的稳定性经受住了考验,带来了很棒的体验,对于后续大体的规划如下: MIUI 生态业务中存在大量的类似场景的业务,后续将会与业务开发积极沟通,从 MySQL 迁移到 TiDB。 针对某些业务场景,以资源合理利用为目标,推出归档集群,利用 Syncer 实现数据归档的功能。 数据分析,结合 TiDB 提供的工具,将支持离线、实时数据分析支持。 将 TiDB 的监控融合到小米公司开源的监控系统 Falcon 中。 十、致谢 非常感谢 TiDB 官方在迁移及业务上线期间给予我们的支持,为每一个 TiDB 人专业的精神、及时负责的响应点赞。"}, {"url": "https://pingcap.com/cases-cn/user-case-xishanju/", "title": "TiDB 在西山居实时舆情监控系统中的应用", "content": " 作者介绍:邹学,舆情监控系统技术负责人,珠海金山网络游戏科技有限公司(西山居)数据中心架构师,2015 年加入西山居,具有 10 年游戏行业软件开发经验,主要参与了公司的游戏网关设计,数据分析框架底层架构建设等,现专注于实时计算、爬虫、分布式系统方向。 公司简介 西山居创建 1995 年初夏,在美丽的海滨小城珠海,西山居工作室孕育而生,一群西山居居士们十年如一日尅勊业业的奋斗。”创造快乐,传递快乐!” 一直是西山居居士们的创作宗旨。西山居以领先的技术作为坚实的基础以独特的本土化产品为玩家提供时尚化服务。在未来,西山居仍以娱乐软件为主导产品,不断进行研发和市场活动,逐步发展成为国内最优秀的集制作、发行于一体的数字化互动娱乐公司。业务背景 由于公司产品的社交属性都非常强,对相关舆情进行分析与了解就显得很有必要,在此背景下,舆情监控系统应运而生。该系统利用算法组提供的分词算法,对文本进行解析与分类,打上各类标记后再通过计算产生中间结果。舆情系统直接查询这些中间结果,产生各类报表与趋势图,为及时掌握各类舆情趋势提供便利。用户可以自由组合舆情关注点,从而对平台有很严格的实时交互性查询要求,是典型的实时 HTAP 类业务。存储技术选型 舆情系统之前我们曾经实现过一个客服系统,这个系统要求能实时查询,但面对是海量的玩家行为记录。在当时情况下(2016 年),可以选择的对象只有 MyCAT 这类数据库中间件,通过综合压力测试后,我们选定了 KingShard 这一款由公司前同事开发的中间件,KingShard 虽然没有 MyCAT 丰富的周边功能,但它在满足我们业务需求的核心功能上有更快的表现。但正因为有了这一次中间件的使用,我们对中间件有了比较全面的了解,它们在查询优化上有着天生的弱点,无法满足更复杂的查询或者表现极不友好,为此我们还不得不砍掉了客服系统的部分业务功能,所以在那时我已开始寻找更优的技术方案,其中分布式数据库是我们考察的重点方向。BigTable、GFS、MapReduce 是谷歌在分布式存储与查询领域的探索成果,他们没有公开具体实现代码,但却发布了相应论文,对分布式文件系统、大数据挖掘和 NoSQL 发展起了重大促进作用。开源界根据这一成果开发出对应产品是 HBase、HDFS、Hadoop,这三个产品红极一时,相关周边产品更是百花齐放,很多细分领域都同时出现了多个产品竞争,让整个生态非常繁荣但也变得复杂,提高了我们的学习与使用成本。那么,在一些领域中有没有更加简单、直接、具有较强融合能力的解决方案呢?此时距谷歌这三篇论文发表已近 10 年,谷歌内部早已在尝试融合 NoSQL 和 SQL,并对它们进行了多次更新换代,Spanner、F1 两篇论文便是谷歌在这一方向的探索成果。开源分布式数据库 TiDB 便是受论文启发而设计的 HTAP (Hybrid Transactional and Analytical Processing) 数据库,结合了传统的 RDBMS 和 NoSQL 的最佳特性,兼容 MySQL,具有支持分布式事务、无限的水平扩展、数据强一致性保证等核心 NewSQL 特性。当时,舆情系统接入的第一个游戏平均每天入库数据量就已达到 8500 万条,并且还需要支持各种实时交互性查询,显然中间件已不能满足要求,传统的关系型数据库则更加不可能了。考虑到以后还会有其它游戏接入,我们果断选择了分布式数据库。 随着互联网经济的发展,数据量跟并发数也在飞速增长,单机数据库已越来越不能满足要求了,为此谷歌、阿里等大厂都有了自研的分布式数据库,但都没有开源,而 MySQL 的 MGR 及相关功能进展的步子太小,TiDB 的出现很好的弥补了市场空白,成为我们的唯一选择。服务器配置 舆情系统是内部孵化项目,服务器具体如下:新购物理机器 6 台:旧物理机 4 台:我们将对资源使用相对较小的 PD、监控服务分别放在旧物理机上,TiDB、TiKV 和 TiSpark 则分配在新机器上,详细如下:其中每个 TiKV 分配 CPU 10C / 内存 64G / 硬盘 2T,每个 TiSpark 分配 CPU 20C / 内存 64G。在资源有限情况下,结合数据量及舆情系统的 AP 业务属性,我们设计了这样相对复杂的架构,目的是为了充分利用好服务器资源,让它们能承担更极限的压力,事后有多次历史数据的导入也证明了我们这样设计的必要性,多谢 TiDB 的兄弟全程耐心指导及帮助。项目开发过程 得出中间结果是一个非常大的计算过程,所以我们使用了 TiSpark。TiSpark 是 PingCAP 为解决用户复杂 OLAP 需求而推出的产品,它是将 Spark SQL 直接运行在分布式存储引擎 TiKV 上的 OLAP 解决方案。有了 TiSpark 我们可以方便地使用 Spark,而不需要再单独搭建一套 Spark 集群。从 TiDB 的 1.0 RC 3 版本开始,我们就在金山云上搭建集群来试用与压测。期间经历了多次版本热更,集群也一直很稳定,功能与性能越来越强,所以在舆情系统开始开发时我们果断使用了 TiDB。并且 TiDB 有强烈的市场需求,他们的版本更新非常迅速,在试用期间时发现了一些功能不能满足需要,往往在下一个版本就解决了,这让人非常惊叹。当前版本未加入实时计算业务,再加上使用了 TiSpark,所以整个架构相对简单,详细如下图:项目上线及使用情况 舆情系统目前总数据量数 T,已正式上线三个月,期间从未出现过异常,系统平稳、使用效果也非常好。现在每天原始文本数据在 2500 万条以上,通过算法分词后产生的中间结果则每天有 6000 万条左右(日均入库 8500 万条),高峰时段的平均 QPS 在 3K 以上,闲时的平均 QPS 为 300 多一点。根据这样的量级,在一开始评估时设定的目标是:支持最近一个星期的实时交互性查询,但现在已经远远超过我们的预期。目前所有一个月内的时间跨度查询都在 1 秒左右完成,个别复杂的 3 个月的实时交互性查询则需要 2 秒多一点。可以说 TiDB 给我们的体验远超预期,这样的数据量级及响应,单机版数据库是不可能达到要求的。"}, {"url": "https://pingcap.com/cases-cn/user-case-beijing-bank/", "title": "北京银行企业级 NewSQL 数据库赋能金融科技建设", "content": " 作者介绍于振华,北京银行软件开发部,核心系统架构管理,长期从事银行核心系统研发、规划,当前主要研发方向集中在构建先进、高效、面向OLTP的银行交易系统,提升银行信息系统服务能力。张小龙,北京银行软件开发部,核心系统架构设计,长期从事银行核心系统对公业务、中间业务模型研发、规划,软件项目管理。参与构建新型面向OLTP的银行交易系统架构设计。 近年来,国家不断提高对信息技术安全可控的战略要求,银行业希望在快速发展业务的同时不断降低经营成本。这不仅促使商业银行积极提升自主掌控能力,也促使商业银行对基础软件的服务能力、软硬件升级成本控制提出新的要求。与此同时,面对互联网金融带来的交易复杂度及交易频次的大幅提升,商业银行信息系统采用的传统数据库一体化解决方案,在应对此类场景时遇到了明显的性能瓶颈,而提升系统性能只靠替换式的硬件升级,成本昂贵。在这种背景下,引入一种高性能、可弹性扩展、能够支持OLTP场景的数据库成为我行系统建设的优先选择方案。一、 分布式数据库的价值与应用场景 分布式事务数据库采用多种模式实现数据的分散存储,将数据库压力分散到不同服务器上。与集中式数据库相比,分布式数据库可以均衡交易负载,并采用高并发的架构提升系统的交易处理能力,而其统一的资源管理机制也使得数据库的性能扩展不再是设备的替换式升级,而是通过增加存储或计算节点来实现弹性升级,极大地节约了升级成本。虽然分布式事务数据库在互联网应用场景下的探索取得了良好的成效和大量的实战经验,积累了很多成熟的技术,但相比互联网企业,金融行业对风险控制的要求更高,所以在面对高复杂度交易场景、业务实时一致性等方面的需求时,需要更为完善的技术方案支持。目前绝大部分分布式数据库解决方案都是基于 MySQL 主从复制结合分库分表中间件方式进行改造和集成,无法提供商业银行交易场景中的强一致性和完整的分布式事务要求,对业务和应用有侵入性,需要做一定的技术调整和事务妥协,并且此类架构离银行业务场景中的高可用和多中心容灾及多活的高级别安全要求也有一定距离。所以,我行在选型前先确定了六个需要特别关注的特性:ACID 特性、横向扩展能力、可用性、可维护性、透明性、兼容性。需要特别说明的是透明性和兼容性,区域银行等体量的金融机构相比互联网企业来说科技资源有限,所以希望新的分布式数据库对架构、开发、运维的影响能够降到最低,同时能够支持传统系统的迁移。新一代分布式 NewSQL 数据库对应用透明,像一个单机数据库一样使用,支持水平扩展的同时保证分布式事务和数据的强一致性,从而避免传统分库分表、事务补偿等方案对上层应用及业务流程的影响,另一方面如果能兼容传统单机数据库,传统应用平移时不需要人工改写代码,就能极大减少迁移成本。二、具有北京银行特色的选型方案 由于金融行业对风险控制的严格要求,以及在交易复杂度、业务实时一致性等方面诉求不同于互联网企业。所以,我行对于分布式数据库的选择也比较谨慎,利用两轮专项POC评测来探索分布式数据库的适用场景及性能指标,稳步推进由传统数据库向分布式数据库的迁移。在第一轮 POC 测试中,主要进行了多场景的性能测试。由于 Sysbench 等开源测试工具对 OLTP 的性能测试存在较大的局限性,于是我行提出了“标准化交易组”的概念,用银行真实交易逻辑,模拟多表跨节点事务,最大程度的还原银行实际应用场景,检验数据库产品的实际交易性能。第二轮 POC 测试关注更为全面的数据库产品特性。在当前数据库主流评测指标的基础上,结合银行的关注要点,我行自主提出了一套“分布式事务数据库评测指标”(见图),将分布式事务数据库能力进行了分解,形成具体的指标项,使得评测标准更加标准化,评测结果更加客观。图1:分布式事务数据库评测指标选型过程中,从多维度考察了多家厂商的产品,包括 TPS、QPS 等性能指标,和算法性能、可靠性、安全备份、数据库兼容性、产品化程度等功能指标。同时,我行也得到了 Intel 实验室的大力支持,提供最新架构的计算和存储设备进行对比测试。结合两轮 POC 结果,TiDB 分布式数据库产品表现出了架构的先进性和高效的性能,水平扩展能力、交易处理能力和功能指标均符合我行对分布式数据库产品的要求。其采用的 Raft 算法保证了数据的强一致性,同时可以实现两地三中心多活的部署方式,以上特性在应用中具备较大优势。除了优秀的开源社区环境,其背后的团队在开发支持、技术培训、运维服务、成本控制等方面也表现出了优秀的素质。三、NewSQL 数据库平台的建设进展 我行在进行分布式事务数据库选型之初,就将目标定为可以承载银行核心系统与核心业务,所以选型过程和应用迁移都是基于这一目标,在数据库投产后将首先应用于互联网支付业务,之后迁移部分核心系统功能模块,并进一步扩展到其他场景的使用。其他感兴趣的用户也可以从非核心业务用起,或先作为备份数据系统。为了更好满足应用端的需求以及业务的扩展,对业务的交易量和数据量进行了预估。结合预估结果以及行内系统建设要求,北京银行率先采用了两地三中心五副本的高可用部署架构方案,支持同城两中心多活,并具备服务器级、机柜级、数据中心级容灾能力。随着业务不断发展,客户数量、账户数量、业务交易量都会上升,这对我行信息系统的数据存储能力、运算能力等方面提出了更高的要求。我行也对系统架构进行了长远规划,利用分布式 NewSQL 数据库集群的横向水平扩展能力,通过增加存储或计算节点来实现弹性升级,节约成本与实施难度。2018 年 3 月 22 日,北京银行分布式 NewSQL 数据库集群正式投产,成为国内首家采用同类方案应用于核心交易场景的银行。在数据库投产后,将进行生产环境多活和灾备的验证,并开始应用切换。四、对开源软件的一些理解 银行在开展技术能力转型建设的过程中,必然会应用越来越多的开源技术。开源软件是当前软件发展的趋势,互联网企业的大规模应用和快速迭代使开源软件成为先进技术事实上的代表。传统银行业使用开源软件的初衷是希望快速获得互联网企业同样的能力,但是否存在困难与阻碍呢?第一、大部分银行的科技资源状况使之不具备源代码级的掌控能力和基于开源组件的架构设计能力。大多选择采用由国外社区控制的软件或是直接购买国内互联网公司封装好的全家桶解决方案,很难做到真正意义的自主、安全、可控。第二、开源软件变化快、分支多、依赖“试错”的创新,跟银行追求稳健、长期的内部机制存在差异甚至冲突,反映在选型、测试、变更、运维等各个环节。第三、开源软件的极客思维更多面向开发者,而非使用者,灾备、监控、审计等企业级功能经常落后于核心功能,在培训、ISV 持、维保服务上跟传统企业的需求还有差距。所以银行业采用开源软件并取得成功的成本可能会比原有模式更高。值得欣慰的是,随着多年的技术积累,国内越来越多的类似 PingCAP 这样专注于底层核心基础软件研发的团队开始崭露头角,通过全球开源协作的方式极大的提升软件的迭代速度和成熟度,且愿意倾听传统行业的客户需求,有一颗做好产品与服务的诚心。不同于部分银行在新兴业务上采用互联网公司提供的整体外包解决方案,北京银行寻求自主可控能力,主动在模式和管理上创新,与互联网思维和技术不断切磋、碰撞、融合。通过研究、评测、应用、部署等工作,在实践中做到了自主掌控。双方在合作中互惠互利,利用双方优势,实现了信息系统服务能力的快速提升,打造出具有北京银行特色的创新驱动力。五、结语 今后我行会尝试将更多高频高并发、对可扩展性和可用性有较高要求的业务场景迁移到分布式系统上。充分发挥分布式数据库的优势,探索和开辟创新发展的新路径。同时也希望我行在分布式数据库建设过程中的经验可以分享给更多的金融机构。借此北京银行愿与各同业机构和互联网企业携手并进,为推动银行数据库应用升级贡献自己的一份力量!"}, {"url": "https://pingcap.com/cases-cn/user-case-ekingtech/", "title": "TiDB 在海航易建科技与香港航空研发收益支持系统过程中的实践", "content": " 背景介绍 收益支持系统(Revenue Support System,简称 RSS)是海航易建科技与香港航空共同研发的基于大数据实时分析处理的航空业务支持和决策系统。RSS 的目标在于根据顾客需求进行市场细分和定价,在科学分析的基础上通过价格和座位库存控制手段平衡需求和供给的关系,将产品销售给合适的旅客,其核心价值在于支撑和帮助航空公司业务人员和决策者进行业务管理和科学决策。 RSS 在航空公司角色和定位,决定了该系统对 OLAP 和 OLTP 的操作在准确性和实时性方面具有很高的要求,并且由于航空公司每天产生海量的订票、值机、离港和财务数据,使得要求系统在数据存储方面要有很好的水平扩展能力。前期方案 前期我们主要使用 MySQL 数据库,但是单表记录大于 2000 万行时,现有的业务报表查询和导出操作明显变慢,通过各种 sql 调优和代码优化手段,也无法继续满足服务等级协议,只能通过分库分表来解决,但是这会增加的后续业务逻辑开发复杂度与数据库运维困难。后来,随着业务的深入和数据的积累,代理人在全球各个全球分销系统(Global Distribution System,GDS)中的订座数据数据(Marketing Information Data Tapes,MIDT)就近2年的数据就超过 3.8 亿行,后续会同步近 10 年的数据,初步预估单表数据量将突破10亿条数据,并且后续每年的正常量可能会突破 2 亿条,如果继续使用 MySQL,必然面临着更细粒度分库、分表的难题,而且当前业界很多分表分库的中间件对 OLAP 支持的并不完美,而且很难满足复杂的 OLAP 需求,并且需要进行分表策略的额外配置。这样必然加大了开发和运维的难度和降低了开发的灵活性。在这个过程中,我们曾经使用 HDFS + Hive + Spark + Kylin 作为大数据解决方案,但是这个方案对于实时的OLTP却满足不了。为了满足两者的需求,我们需要把一份大数据存储两份,MySQL + 分表中间件做 OLTP 操作,HDFS + Hive + Spark + Kylin 做 OLAP 分析。茅塞顿开 在业务遇到不可妥协的技术瓶颈后,我们重新评估业务模型,发现对于数据库的选型必须满足: 支持业务弹性的水平扩容与缩容; 支持 MySQL 便捷稳定的迁移,不影响线上业务; 支持 SQL 和复杂的查询,尽量少的改动代码; 满足业务故障自恢复的高可用,且易维护; 强一致的分布式事务处理; 为了解决上述问题,我们发现了 TiDB、CockroachDB 与 oceanbase 这三款分布式的数据库。由于 CockroachDB 是支持 Postgresql 数据库,我们是 MySQL 的协议,oceanbase 发布在阿里云,我们的数据属于收益核心数据需要发布在集团内部,这样一来 TiDB 成了我们选择。TiDB 数据库完美的支持了 MySQL 的 SQL 语法,它可是让我们不改变平时用 MySQL 的操作习惯。并且能够很好的满足我们的 OLTP 需求和复杂的 OLAP 的需求。另外 TiSpark 是建立在 Spark 引擎之上的,Spark 在机器学习领域上还是比较成熟的。考虑到收益系统未来会涉及到一些预测分析,会需要用到机器学习。综合这些考虑,TiDB + TiSpark 成为了我们首选的技术解决方案。后续我们了解到饿了么的80%的核心流程都跑在tidb的高性能集群上面,还有新一代的互联网代表企业摩拜、今日头条,以及机票行业的qunar都有tidb的深度应用。TiDB简介 TiDB项目在世界上最流行的开源代码托管平台 GitHub 上共计已获得 10000+ Star,项目集合了 150 多位来自全球的 Contributors (代码贡献者)。TiDB是 PingCAP 公司受 Google Spanner / F1论文启发而设计的开源分布式 NewSQL 数据库。TiDB 具备如下NewSQL 核心特性: SQL 支持(TiDB 是 MySQL 兼容的) 水平线性弹性扩展 分布式事务 跨数据中心数据强一致性保证 故障自恢复的高可用 TiDB 适用于 100% 的 OLTP 场景和 80% 的 OLAP 场景。对业务没有任何侵入性,能优雅的替换传统的数据库中间件、数据库分库分表等 Sharding 方案。同时它也让开发运维人员不用关注数据库 Scale 的细节问题,专注于业务开发,极大的提升研发的生产力。参考:https://pingcap.github.io/docs-cn/overviewTiDB的架构 TiDB 集群主要分为三个组件:TiDB Server、TiKVServer、PD Server,整体实现架构如下:TiDB 整体架构图TiDB Server TiDB Server 主要负责接收 SQL 请求,处理 SQL 相关的逻辑,并通过 PD 找到存储计算所需数据的 TiKV 地址,与 TiKV 交互获取数据,最终返回结果。 TiDB Server 是无状态的,其本身并不存储数据,只负责计算,可以无限水平扩展,可以通过负载均衡组件(如 LVS、HAProxy 或 F5)对外提供统一的接入地址。PD Server PlacementDriver (简称 PD) 是整个集群的管理模块,其主要工作有三个: 存储集群的元信息(某个 Key 存储在哪个 TiKV 节点); 对TiKV 集群进行调度和负载均衡(如数据迁移、Raft group leader 迁移等); 分配全局唯一且递增的事务 ID; TiKV Server TiKV Server 负责存储数据,从外部看 TiKV 是一个分布式的提供事务的 Key-Value 存储引擎。存储数据的基本单位是 Region,每个 Region 负责存储一个 Key Range 的数据,每个 TiKV 节点会负责多个 Region 。TiKV 使用 Raft 协议做复制,保持数据的一致性和容灾。副本以 Region 为单位进行管理,不同节点上的多个 Region 构成一个 Raft Group,互为副本。数据在多个 TiKV 之间的负载均衡由 PD 调度,以 Region 为单位进行调度。参考:https://pingcap.github.io/docs-cn/overviewTiDB在易建 RSS 系统的架构如下:RSS 系统的架构图我们的 TiDB 集群共有 5 台高性能的海航云服务器来提供服务,经过测试 write 性能可以稳定的做到 1 万 TPS,同时,read 性能可以做到 0.5 万 TPS,后续升级到 SSD 硬盘将能够提高更高的读写性能。使用了 TiDB 后,我们不需要再考虑分表分库的问题,因为数据在一起,也不用考虑数据同步的问题。近 10 亿行数据都可以复用以前的 MySQL 代码,进行 OLAP 分析也比上一代收益系统提速近 20 倍,同时,免去了数据同步的可能存在的问题。而且也能很好的满足我们 OLTP 操作的需求,懂得 MySQL 的开发人员都可以轻松的进行大数据开发,没有学习门槛,既节省了开发成本,又降低了数据运维成本。后记 部署 TiDB 近 3 个月来,收益系统的数据量已经增长近 1 倍达到 5 亿的数据量,近 10 张千万级别的中间表数据,期间我们做过 TiDB 的扩容与版本升级,这些操作对业务来讲都是完全透明的,而且扩容与升级简单。我们可以更加专注业务程序的开发与优化,无需了解数据库分片的规则,对于快速变化的业务来说是非常重要的。同时,由于 TiSpark 的整合可以再同一个集群里面分析海量的数据并将结果无缝共享,对于核心业务的应用的开发来讲是非常方便的,由于能够快速响应业务的需求对于当前激烈竞争的航空业来说,提升了航空公司自身的核心竞争力。"}, {"url": "https://pingcap.com/cases-cn/user-case-beikejinfu/", "title": "贝壳金服 TiDB 在线跨机房迁移实践", "content": " 作者介绍:李振环,贝壳金服数据基础架构负责人,目前负责数据平台和企业级数据仓库开发。 一、公司介绍 贝壳金服是专注居住场景的金融科技服务商,起步于 2006 年成立的链家金融事业部,并于 2017 年 3 月正式独立运营。贝壳金服聚焦于居住场景,在租赁、买卖、家装、安居等场景中为用户提供定制化的居住金融服务。贝壳金服以独家大数据与场景风控能力见长,致力于解决居住金融需求,以 Fintech 驱动产业升级,让每个家庭都能享受高品质的居住生活。截至 2018 年底,贝壳金服业务已覆盖全国 90 多个城市及地区,为超过 130 万用户提供了金融服务。二、项目背景 贝壳金服数据中台使用 TiDB 和 TiSpark 平台,基于 Syncer 将业务数据实时从 MySQL 备库抽取到 TiDB 中,并通过 TiSpark 对 TiDB 中的数据进行数据分析处理,供上游业务消费,现已服务于 70 多名数据开发人员。现有集群已经使用 100 多个 Syncer 同步上游 MySQL 数据,目前已经达到 4.7TB 热数据,上百张离线和实时报表。由于机房调整,数据中台也需要同步迁移到新机房,结合 TiDB 的特性,我们探索了一种在线不停机迁移机房的方式。TiDB 是一个分布式 NewSQL 数据库。它支持水平弹性扩展、ACID 事务、MySQL 语法,具有数据强一致的高可用特性,是一个不仅适合 OLTP 场景还适合 OLAP 场景的混合数据库。而 TiSpark 是为解决较重的 OLAP 需求而推出的产品。它借助 Spark 平台,同时融合 TiKV 分布式集群的优势,和 TiDB 一起为用户一站式解决 HTAP 的业务需求。TiSpark 依赖于 TiKV 集群和 PD 组件,使用同一个数据源,减少对于 ETL 工具的维护,并且可以使用 Spark 进行复杂查询计算。图 1 贝壳金服整体数据架构图三、业务类型 由于原有 MySQL 数据库提供服务非常吃力,使用 100 多个 Syncer 同步上游的 MySQL 数据,而 TiDB 作为一个数据中台,主要使用 TiSpark 在做查询。四、集群规模 组件 数量 TiDB 4 TiKV 9*3(物理机*实例数) PD 3 数据量 4.7 TB+ 图 2 集群拓扑图五、迁移实践 5.1 迁移业务挑战 本次数据迁移主要有两个关键因素: 尽可能减少对业务的影响,业务方希望服务不能中断超过 1 小时。 由于跨机房网络带宽有限,并且与线上业务共用跨机房网络专线,在迁移过程中需要能控制迁移速度,在白天线上业务繁忙时以较慢的速度迁移,等到晚上业务空闲时加快迁移速度。另外网络运维同事如果发现流量过载,为了不影响其他业务正常网络资源使用,可能随时限制正在迁移的网络带宽,切断迁移网络连接,因此迁移方案必须要支持“断点续传功能”。 5.2 迁移方案设计 本次迁移最初设想了三套方案(如下),最终通过技术考察和验证,采用了技术上更有优势的第三套方案。方案一:只使用 Syncer 在新机房同步 ODS(Operational Data Store 操作性数据存储)数据,然后从 ODS 数据再次全部计算 DW 和 ADM 层等数据。此方案需要迁移的数据最小,但是只从 ODS 计算出所有的其它数据是不现实的。其中很多拉链表(拉链表是数据仓库的数据模型设计中常用的数据模式,该模型记录历史,通常记录一个事务从开始,一直到当前状态的所有变化的信息)的数据都是基于历史时间点计算出来的结果,由于 TiDB 目前版本刚刚开始支持部分分区表功能,不能马上用于生产。并且历史数据没有分区备份,历史的拉链数据无法真实还原。另外此方案业务迁移成本也很高,两边需要不断校准数据,查漏补缺,重新计算所有非 ODS 层数据计算量也过大,导致迁移时间和大量人力投入。方案二:在某个时间点将老机房数据 Dump 出来,全量导入到新机房。之后再开启 TiDB 集群的 Binlog 增量同步老集群数据,待新集群慢慢追上老集群之后再迁移业务。这个方案中 Dump 数据无法实现单库 Dump。因为 Dump 出来的 Position 值不一样,而且有的表没有主键,多次导入会导致数据重复。因此全量 Dump 所有数据是一个很“重”的操作,Dump 后的大文件传输也存在无法断点续传的问题。具体存在问题如下: 锁问题:全量备份时,需要对库进行加锁操作,如果数据量很大,备份时间较长,可能会影响业务。 备份失败可能性较高:如果数据量较大,比如对 2T 的数据进行备份,可能会达到 3h 以上的备份时间,如果备份失败,那么这次备份就相当于不可用。 Binlog 增量同步延迟问题:如果上游 MySQL 压力较大,或者跨机房的网络带宽成为了瓶颈,那么增量同步可能追不上,Binlog 同步无法控制速度,断点续传也需要人工参与。 最终数据校验任务较重:数据迁移完成之后,需要对上下游数据进行校验,最简单的方式是业务校验和对比上下游标的行数。或者使用 pt-toolkit 工具进行数据校验。 停业务风险:在机房迁移完成后,业务需要停止,等待同步和数据校验完成才可以启动。 方案三:采用 TiDB 原生的 Raft 三副本机制自动同步数据。在新机房添加 TiKV 节点,待数据均衡之后再下线老机房 TiKV 节点。老机房 TiKV 下线完成则表示数据迁移完成。此方案操作简单,业务影响在分钟级别。网络层面可以通过 PD 调度参数控制 TiKV 迁移速度,Raft 副本迁移如果网络中断会自动重新传输。具体优点如下: 迁移数据期间不会影响线上业务,整个迁移过程都是在线提供服务的。 迁移效率非常高。一个机房内部 balance 3T 的数据只需要 10 小时左右,跨机房迁移一般受限于网络。 容错性高,没有很多的人工干预,集群高可用依然保留。 5.3 机房迁移实施过程 操作描述:图 3 配置防火墙,将两个机房所需端口开通。 新机房扩容 3+ 个 TiKV,3 个 PD,2+ 个 TiDB。 执行下线 TiKV 命令,一次性下线所有旧机房的 TiKV。 PD Leader 手动切换到新机房,业务迁移到新机房,等待 TiKV balance 完成之后,下线旧机房的 TiKV、PD 和 TiDB。 整个过程人为操作较少,耗时较长的只有 TiKV balance 数据的过程,可以调整调度并发度来加速整个过程。注意事项: 新机房的 TiKV 节点尽量要多于旧机房,否则在下线过程中,可能由于集群 TiKV 实例数比以前要少,导致 TiKV 压力较大。 跨机房迁移,网络延迟不能高于 3ms。 TiKV 下线过程中, Region Leader(s) 会逐渐迁移到新机房,这时业务已经可以并行的迁移,将压力转移到新机房去。 5.4 在 TiDB 中的二次开发 Syncer 二次开发:在贝壳金服,有 100 多个 Syncer 实时同步线上数据,由于 TiDB 语法与 MySQL 语法不是 100% 兼容,特别是上游修改 DDL 操作,比如从 INT 改成 VARCHAR,会导致 Syncer 同步失败。在贝壳金服实战中,优化了失败恢复工作,监控程序会监控失败原因并自动化恢复 Syncer 错误。 TiSpark 二次开发:TiSpark 无法实现 TiDB 数据插入和删除。贝壳金服基于 TiSpark 二次开发封装了 TiSpark,因此可以实现 TiSpark 直接原生 SparkSQL 执行 Insert 、Create 操作。实现新增 executeTidbSQL 实现 delete、update、drop 操作。增加 TiSpark View 的功能,弥补现阶段 TiDB 不支持 View 的问题。 TiSpark 权限控制:TiDB 和 TiSpark 都无法实现基于组和大量用户的权限控制。贝壳金服基于 Catalyst 自研了一套兼容 TiSpark SQL 和 TiDB SQL 的 SQL 解析引擎,并基于此引擎之上开发权限验证、权限申请、权限审批、数据发现等功能。 趟过的坑 Region 过多:由于 TiDB 目前版本暂不支持 Partition 功能,我们的 job 都是需要支持可以重复跑,因此有一些业务会直接先 drop table 然后再创建 table。默认情况下每次创建 table 都会申请一套 Region,导致现在单台 TiKV Region 数过载。 DDL 排队执行:有一次对一个 2 亿级别的大表添加索引,希望提高基于时间查询的效率,结果导致集群业务中所有 drop table 、create table 相关 job 全部卡住。最终了解到 DDL 是串行化操作。Add index 大操作让其他 DDL 操作 pending,手动 kill add index 操作后集群恢复。目前 TiDB 2.1 版本已经将添加索引操作和其他的 DDL 操作分开,这个问题已经解决。 Syncer 恢复自动化:TiDB 现在对某些 alter column sql(字段从 INT 改为 VARCHAR 的跨类型修改操作)依然不兼容,因此在上游执行不兼容 SQL 之后,Syncer 同步会失败。修复过程需要使用到 Syncer 同步 position,DB name,table name。获取这些信息之后可以一个 shell 自动恢复 Syncer 同步,但是上面的三个信息输出不够友好,需要人为查看才能获取到。如果在失败 Syncer 日志中可以格式化输出以上三个信息,Syncer 恢复可以更自动化。目前新版本的 Syncer 已经解决这个问题。 Decimal Hash Join 结果不正确:在使用两个 Decimal 字段做表 join 时,发现使用 limit 可以查询出来数据,不 limit 返回无结果。查看执行计划发现 limit 之后改变了执行计划,将 HashLeftJoin 改为了 IndexJoin。调查之后发现 Decimal 在计算 hash 值时返回结果不正确,导致相同 Decimal 无法 join 上。可以使用 hint 强制使用 IndexJoin 来解决此问题。目前 TiDB 2.0.11 及以上版本已经解决了这个问题。 列式存储:由于现在 TiDB 是行存,即使是 TiSpark 读取 TiDB 一个字段也会在底层取出此记录所有值,导致性能问题。在 OLAP 大宽表场景中使用列式存储性能会显著提升。 后续计划 机房顺利迁移完成后,后续计划升级到 TiDB 3.0,利用 TiDB 3.0 产品路线图中提供的新功能来优化和提升使用效果: 开启 Region merge 功能,自动在后台合并空 Region 从而减少 Region 的数量。 使用 3.0 所提供的视图 View 和分区 Partition 功能。 尝试 PingCAP 新一代的列计算/存储引擎 TiFlash ,提升 OLAP 宽表查询性能。 此外,在应用 TiDB 支持业务的过程中,贝壳金服的技术团队也通过自身对数据中台的业务理解和技术实践,打磨出了以下配套工具及平台: 基于 TiDB 的数据发布平台 基于 TiDB 的元数据管理平台 支持 TiSpark+TiDB 的权限管理系统 基于 Flink + TiDB 的在线 SQL 流式处理平台 在上面这些技术成果的基础上,贝壳金服的技术团队正在做未来的数据中台技术栈演进规划,即基于 TiDB + Azkaban + 自研的数据质量平台。"}, {"url": "https://pingcap.com/cases-cn/user-case-meituandianping/", "title": "美团点评携手 PingCAP 开启新一代数据库深度实践之旅", "content": " 作者介绍:赵应钢,美团点评研究员;李坤,美团点评数据库专家;朴昌俊,美团点评数据库专家。 一、背景和现状 在美团,基于 MySQL 构建的传统关系型数据库服务已经难于支撑公司业务的爆发式增长,促使我们去探索更合理的数据存储方案和实践新的运维方式。随着近一两年来分布式数据库大放异彩,美团 DBA 团队联合架构存储团队,于 2018 年初启动了分布式数据库项目。图 1 美团点评产品展示图立项之初,我们进行了大量解决方案的对比,深入了解了业界多种 scale-out、scale-up 方案,考虑到技术架构的前瞻性、发展潜力、社区活跃度、以及服务本身与 MySQL 的兼容性,最终敲定了基于 TiDB 数据库进行二次开发的整体方案,并与 PingCAP 官方和开源社区进行深入合作的开发模式。美团业务线众多,我们根据业务特点及重要程度逐步推进上线,到截稿为止,已经上线 10 个集群,近 200 个物理节点,大部分是 OLTP 类型的应用,除了上线初期遇到了一些小问题,目前均已稳定运行。初期上线的集群,已经分别服务于配送、出行、闪付、酒旅等业务。TiDB 架构分层清晰,服务平稳流畅,但在美团当前的数据量规模和已有稳定的存储体系的基础上,推广新的存储服务体系,需要对周边工具和系统进行一系列改造和适配,从初期探索到整合落地需要走很远的路。下面从几个方面分别介绍: 一是从 0 到 1 的突破,重点考虑做哪些事情; 二是如何规划实施不同业务场景的接入和已有业务的迁移; 三是上线后遇到的一些典型问题介绍; 四是后续规划和对未来的展望。 二、前期调研测试 2.1 对 TiDB 的定位 我们对于 TiDB 的定位,前期在于重点解决 MySQL 的单机性能和容量无法线性和灵活扩展的问题,与 MySQL 形成互补。业界分布式方案很多,我们为何选择了 TiDB 呢?考虑到公司业务规模的快速增长,以及公司内关系数据库以 MySQL 为主的现状,因此我们在调研阶段,对以下技术特性进行了重点考虑: 协议兼容 MySQL:这个是必要项。 可在线扩展:数据通常要有分片,分片要支持分裂和自动迁移,并且迁移过程要尽量对业务无感知。 强一致的分布式事务:事务可以跨分片、跨节点执行,并且强一致。 支持二级索引:为兼容 MySQL 的业务,这个是必须的。 性能:MySQL 的业务特性,高并发的 OLTP 性能必须满足。 跨机房服务:需要保证任何一个机房宕机,服务能自动切换。 跨机房双写:支持跨机房双写是数据库领域一大难题,是我们对分布式数据库的一个重要期待,也是美团下一阶段重要的需求。 业界的一些传统方案虽然支持分片,但无法自动分裂、迁移,不支持分布式事务,还有一些在传统 MySQL 上开发一致性协议的方案,但它无法实现线性扩展,最终我们选择了与我们的需求最为接近的 TiDB。与 MySQL 语法和特性高度兼容,具有灵活的在线扩容缩容特性,支持 ACID 的强一致性事务,可以跨机房部署实现跨机房容灾,支持多节点写入,对业务又能像单机 MySQL 一样使用。2.2 测试 针对官方声称的以上优点,我们进行了大量的研究、测试和验证。首先,我们需要知道扩容、Region 分裂转移的细节、Schema 到 kv 的映射、分布式事务的实现原理。而 TiDB 的方案,参考了较多的 Google 论文,我们进行了阅读,这有助于我们理解 TiDB 的存储结构、事务算法、安全性等,包括: Spanner: Google’s Globally-Distributed Database Large-scale Incremental Processing Using Distributed Transactions and Notifications In Search of an Understandable Consensus Algorithm Online, Asynchronous Schema Change in F1 我们也进行了常规的性能和功能测试,用来与 MySQL 的指标进行对比,其中一个比较特别的测试,是证明 3 副本跨机房部署,确实能保证每个机房分布一个副本,从而保证任何一个机房宕机不会导致丢失超过半数副本。从以下几个点进行测试: Raft 扩容时是否支持 learner 节点,从而保证单机房宕机不会丢失 2⁄3 的副本。 TiKV 上的标签优先级是否可靠,保证当机房的机器不平均时,能否保证每个机房的副本数依然是绝对平均的。 实际测试,单机房宕机,TiDB 在高并发下,QPS、响应时间、报错数量,以及最终数据是否有丢失。 手动 Balance 一个 Region 到其他机房,是否会自动回来。 从测试结果来看,一切都符合预期。三、存储生态建设 美团的产品线丰富,业务体量大,业务对在线存储的服务质量要求也非常高。因此,从早期做好服务体系的规划非常重要。下面从业务接入层、监控报警、服务部署,来分别介绍一下我们所做的工作。3.1 业务接入层 当前 MySQL 的业务接入方式主要有两种,DNS 接入和 Zebra 客户端接入。在前期调研阶段,我们选择了 DNS + 负载均衡组件的接入方式,TiDB-Server 节点宕机,15s 可以被负载均衡识别到,简单有效。业务架构如图 2。图 2 业务架构图后面我们会逐渐过渡到当前大量使用的 Zebra 接入方式来访问 TiDB,从而保持与访问 MySQL 的方式一致,一方面减少业务改造的成本,另一方面尽量实现从 MySQL 到 TiDB 的透明迁移。3.2 监控报警 美团目前使用 Mt-Falcon 平台负责监控报警,通过在 Mt-Falcon 上配置不同的插件,可以实现对多种组件的自定义监控。另外也会结合 Puppet 识别不同用户的权限、文件的下发。这样,只要我们编写好插件脚本、需要的文件,装机和权限控制就可以完成了。监控架构如图 3。图 3 监控架构图而 TiDB 有丰富的监控指标,使用流行的 Prometheus + Grafana,一套集群有 700+ 的 Metric。从官方的架构图可以看出,每个组件会推送自己的 Metric 给 PushGateWay,Prometheus 会直接到 PushGateWay 去抓数据。由于我们需要组件收敛,原生的 TiDB 每个集群一套 Prometheus 的方式不利于监控的汇总、分析、配置,而报警已经在 Mt-Falcon 上实现的比较好了,在 AlertManager 上再造一个也没有必要。因此我们需要想办法把监控和报警汇总到 Mt-Falcon 上面,有如下几种方式: 方案一:修改源代码,将 Metric 直接推送到 Falcon,由于 Metric 散落在代码的不同位置,而且 TiDB 代码迭代太快,把精力消耗在不停调整监控埋点上不太合适。 方案二:在 PushGateWay 是汇总后的,可以直接抓取,但 PushGateWay 是个单点,不好维护。 方案三:通过各个组件(TiDB、PD、TiKV)的本地 API 直接抓取,优点是组件宕机不会影响其他组件,实现也比较简单。 我们最终选择了方案三。该方案的难点是需要把 Prometheus 的数据格式转化为 Mt-Falcon 可识别的格式,因为 Prometheus 支持 Counter、Gauge、Histogram、Summary 四种数据类型,而 Mt-Falcon 只支持基本的 Counter 和 Gauge,同时 Mt-Falcon 的计算表达式比较少,因此需要在监控脚本中进行转换和计算。3.3 批量部署 TiDB 使用 Ansible 实现自动化部署。迭代快,是 TiDB 的一个特点,有问题快速解决,但也造成 Ansible 工程、TiDB 版本更新过快,我们对 Ansible 的改动,也只会增加新的代码,不会改动已有的代码。因此线上可能同时需要部署、维护多个版本的集群。如果每个集群一个 Ansible 目录,造成空间的浪费。我们采用的维护方式是,在中控机中,每个版本一个 Ansible 目录,每个版本中通过不同 inventory 文件来维护。这里需要跟 PingCAP 提出的是,Ansible 只考虑了单集群部署,大量部署会有些麻烦,像一些依赖的配置文件,都不能根据集群单独配置(咨询官方得知,PingCAP 目前正在基于 Cloud TiDB 打造一站式 HTAP 平台,会提供批量部署、多租户等功能,能比较好的解决这个问题)。3.4 自动化运维平台 随着线上集群数量的增加,打造运维平台提上了日程,而美团对 TiDB 和 MySQL 的使用方式基本相同,因此 MySQL 平台上具有的大部分组件,TiDB 平台也需要建设。典型的底层组件和方案:SQL 审核模块、DTS、数据备份方案等。自动化运维平台展示如图 4。图 4 自动化运维平台展示图3.5 上下游异构数据同步 TiDB 是在线存储体系中的一环,它同时也需要融入到公司现有的数据流中,因此需要一些工具来做衔接。PingCAP 官方标配了相关的组件。公司目前 MySQL 和 Hive 结合的比较重,而 TiDB 要代替 MySQL 的部分功能,需要解决 2 个问题: MySQL to TiDB MySQL 到 TiDB 的迁移,需要解决数据迁移以及增量的实时同步,也就是 DTS,Mydumper + Loader 解决存量数据的同步,官方提供了 DM 工具可以很好的解决增量同步问题。 MySQL 大量使用了自增 ID 作为主键。分库分表 MySQL 合并到 TiDB 时,需要解决自增 ID 冲突的问题。这个通过在 TiDB 端去掉自增 ID 建立自己的唯一主键来解决。新版 DM 也提供分表合并过程主键自动处理的功能。 Hive to TiDB & TiDB to Hive Hive to TiDB 比较好解决,这体现了 TiDB 和 MySQL 高度兼容的好处,insert 语句可以不用调整,基于 Hive to MySQL 简单改造即可。 TiDB to Hive 则需要基于官方 Pump + Drainer 组件,Drainer 可以消费到 Kafka、MySQL、TiDB,我们初步考虑用下图 5 中的方案通过使用 Drainer 的 Kafka 输出模式同步到 Hive。 图 5 TiDB to Hive 方案图四、线上使用磨合 对于初期上线的业务,我们比较谨慎,基本的原则是:离线业务 -> 非核心业务 -> 核心业务。TiDB 已经发布两年多,且前期经历了大量的测试,我们也深入了解了其它公司的测试和使用情况,可以预期的是 TiDB 上线会比较稳定,但依然遇到了一些小问题。总体来看,在安全性、数据一致性等关键点上没有出现问题。其他一些性能抖动问题,参数调优的问题,也都得到了快速妥善的解决。这里给 PingCAP 的同学点个大大的赞,问题响应速度非常快,与我们内部研发的合作也非常融洽。4.1 写入量大、读 QPS 高的离线业务 我们上线的最大的一个业务,每天有数百 G 的写入量,前期遇到了较多的问题,我们重点说说。业务场景: 稳定的写入,每个事务操作 100~200 行不等,每秒 6w 的数据写入。 每天的写入量超过 500G,以后会逐步提量到每天 3T。 每 15 分钟的定时读 job,5000 QPS(高频量小)。 不定时的查询(低频量大)。 之前使用 MySQL 作为存储,但 MySQL 到达了容量和性能瓶颈,而业务的容量未来会 10 倍的增长。初期调研测试了 ClickHouse,满足了容量的需求,测试发现运行低频 SQL 没有问题,但高频 SQL 的大并发查询无法满足需求,只在 ClickHouse 跑全量的低频 SQL 又会 overkill,最终选择使用 TiDB。测试期间模拟写入了一天的真实数据,非常稳定,高频低频两种查询也都满足需求,定向优化后 OLAP 的 SQL 比 MySQL 性能提高四倍。但上线后,陆续发现了一些问题,典型的如下:4.1.1 TiKV 发生 Write Stall TiKV 底层有 2 个 RocksDB 作为存储。新写的数据写入 L0 层,当 RocksDB 的 L0 层数量达到一定数量,就会发生减速,更高则发生 Stall,用来自我保护。TiKV 的默认配置: level0-slowdown-writes-trigger = 20 level0-stop-writes-trigger = 36 遇到过的,发生 L0 文件过多可能的原因有 2 个: 写入量大,Compact 完不成。 Snapshot 一直创建不完,导致堆积的副本一下释放,rocksdb-raft 创建大量的 L0 文件,监控展示如图 6。 图 6 TiKV 发生 Write Stall 监控展示图我们通过以下措施,解决了 Write Stall 的问题: 减缓 Raft Log Compact 频率(增大 raft-log-gc-size-limit、raft-log-gc-count-limit) 加快 Snapshot 速度(整体性能、包括硬件性能) max-sub-compactions 调整为 3 max-background-jobs 调整为 12 level 0 的 3 个 Trigger 调整为 16、32、64 4.1.2 Delete 大量数据,GC 跟不上 现在 TiDB 的 GC 对于每个 kv-instance 是单线程的,当业务删除数据的量非常大时,会导致 GC 速度较慢,很可能 GC 的速度跟不上写入。目前可以通过增多 TiKV 个数来解决,长期需要靠 GC 改为多线程执行,官方对此已经实现,即将发布。4.1.3 Insert 响应时间越来越慢 业务上线初期,insert 的响应时间 80 线(Duration 80 By Instance)在 20ms 左右,随着运行时间增加,发现响应时间逐步增加到 200ms+。期间排查了多种可能原因,定位在由于 Region 数量快速上涨,Raftstore 里面要做的事情变多了,而它又是单线程工作,每个 Region 定期都要 heartbeat,带来了性能消耗。tikv-raft propose wait duration 指标持续增长。解决问题的办法: 临时解决 增加 Heartbeat 的周期,从 1s 改为 2s,效果比较明显,监控展示如图 7。 图 7 insert 响应时间优化前后对比图 彻底解决 需要减少 Region 个数,Merge 掉空 Region,官方在 2.1 版本中已经实现了 Region Merge 功能,我们在升级到 2.1 后,得到了彻底解决。 另外,等待 Raftstore 改为多线程,能进一步优化。(官方回复相关开发已基本接近尾声,将于 2.1 的下一个版本发布。) 4.1.4 Truncate Table 空间无法完全回收 DBA Truncate 一张大表后,发现 2 个现象,一是空间回收较慢,二是最终也没有完全回收。 由于底层 RocksDB 的机制,很多数据落在 level 6 上,有可能清不掉。这个需要打开 cdynamic-level-bytes 会优化 Compaction 的策略,提高 Compact 回收空间的速度。 由于 Truncate 使用 delete_files_in_range 接口,发给 TiKV 去删 SST 文件,这里只删除不相交的部分,而之前判断是否相交的粒度是 Region,因此导致了大量 SST 无法及时删除掉。 考虑 Region 独立 SST 可以解决交叉问题,但是随之带来的是磁盘占用问题和 Split 延时问题。 考虑使用 RocksDB 的 DeleteRange 接口,但需要等该接口稳定。 目前最新的 2.1 版本优化为直接使用 DeleteFilesInRange 接口删除整个表占用的空间,然后清理少量残留数据,已经解决。 4.1.5 开启 Region Merge 功能 为了解决 region 过多的问题,我们在升级 2.1 版本后,开启了 region merge 功能,但是 TiDB 的响应时间 80 线(Duration 80 By Instance)依然没有恢复到当初,保持在 50ms 左右,排查发现 KV 层返回的响应时间还很快,和最初接近,那么就定位了问题出现在 TiDB 层。研发人员和 PingCAP 定位在产生执行计划时行为和 2.0 版本不一致了,目前已经优化。4.2 在线 OLTP,对响应时间敏感的业务 除了分析查询量大的离线业务场景,美团还有很多分库分表的场景,虽然业界有很多分库分表的方案,解决了单机性能、存储瓶颈,但是对于业务还是有些不友好的地方: 业务无法友好的执行分布式事务。 跨库的查询,需要在中间层上组合,是比较重的方案。 单库如果容量不足,需要再次拆分,无论怎样做,都很痛苦。 业务需要关注数据分布的规则,即使用了中间层,业务心里还是没底。 因此很多分库分表的业务,以及即将无法在单机承载而正在设计分库分表方案的业务,主动找到了我们,这和我们对于 TiDB 的定位是相符的。这些业务的特点是 SQL 语句小而频繁,对一致性要求高,通常部分数据有时间属性。在测试及上线后也遇到了一些问题,不过目前基本都有了解决办法。4.2.1 SQL 执行超时后,JDBC 报错 业务偶尔报出 privilege check fail。是由于业务在 JDBC 设置了 QueryTimeout,SQL 运行超过这个时间,会发行一个 “kill query” 命令,而 TiDB 执行这个命令需要 Super 权限,业务是没有权限的。其实 kill 自己的查询,并不需要额外的权限,目前已经解决了这个问题,不再需要 Super 权限,已在 2.0.5 上线。4.2.2 执行计划偶尔不准 TiDB 的物理优化阶段需要依靠统计信息。在 2.0 版本统计信息的收集从手动执行,优化为在达到一定条件时可以自动触发: 数据修改比例达到 tidb_auto_analyze_ratio 表一分钟没有变更(目前版本已经去掉这个条件) 但是在没有达到这些条件之前统计信息是不准的,这样就会导致物理优化出现偏差,在测试阶段(2.0 版本)就出现了这样一个案例:业务数据是有时间属性的,业务的查询有 2 个条件,比如:时间+商家 ID,但每天上午统计信息可能不准,当天的数据已经有了,但统计信息认为没有。这时优化器就会建议使用时间列的索引,但实际上商家 ID 列的索引更优化。这个问题可以通过增加 Hint 解决。在 2.1 版本对统计信息和执行计划的计算做了大量的优化,也稳定了基于 Query Feedback 更新统计信息,也用于更新直方图和 Count-Min Sketch,非常期待 2.1 的 GA。五、总结展望 经过前期的测试、各方的沟通协调,以及近半年对 TiDB 的使用,我们看好 TiDB 的发展,也对未来基于 TiDB 的合作充满信心。接下来,我们会加速推进 TiDB 在更多业务系统中的使用, …"}, {"url": "https://pingcap.com/cases-cn/user-case-weiruida/", "title": "TiDB 在威锐达 WindRDS 远程诊断及运维中心的应用", "content": " 作者介绍:郭凯乐,应用软件工程师,从公司成立入职工作至今共 6 年半时间,起初主要负责公司的应用系统的服务器端程序的设计开发,对于公司的核心业务及系统架构非常熟悉。2015 年到 2016 年,主持开发了基于规则的智能诊断系统(专家系统),该系统的开发,使自身对于专家系统有了深刻的了解和认识,并积累了丰富的经验,成为该领域的资深工程师。2016 年第至今,参与公司大数据平台项目的研发,该项目主要是围绕着大数据、工业物联网及分布式系统进行一些方法、中间件及解决方案的一些研究,而作者本身参与该项目关于数据的接入及治理方法及方案的研究工作,过程中对于数据接入融合及数据治理所面临的问题、痛点深有体会,积累了丰富经验及心得。 公司简介 西安锐益达风电技术有限公司成立于 2012 年 1 月 4 日,是一家专业化的工业测量仪器系统、机电产品和计算机软件研发、设计和制造公司,是北京威锐达测控系统有限公司在西安成立的全资子公司。依托大学的科研实力,矢志不渝地从事仪器仪表及测量系统的研究和应用开发,积累了丰富的专业知识和实践经验,具备自主开发高端仪器系统和工程实施的完整技术能力。为了适应我国大型风电运营商设备维护管理的需求,破解风电监测技术难题,经过多年艰苦研发,研制了一种具有完全自主知识产权的网络化、模块化、集成化的风电机组状态监测与故障诊断系统,为风电机组全生命周期的运行维护管理提供一套完整的解决方案。业务描述 威锐达 WindRDS 远程诊断与运维中心,是以设备健康监测为核心,实现企业设备全生命周期的健康监测和基于状态的预知性设备运营维护的管理平台。本平台以多维、丰富的数据为基础,结合传统的诊断分析方法,并充分发挥利用大数据智能化的技术手段,快速及时的发现、分析定位设备运转及企业运维过程中的问题,并以流程化、自动化的软件系统辅助用户高效的跟踪、处理问题,目标提升企业设备运维管理的能力,节约运维成本,为企业创造价值。图 1:WindRDS 系统交互图痛点、选型指标 痛点 WindRDS 的数据平台,对于数据的存储当前选用流行的 MySQL 数据库,面对每年 T 级的数据增长量,以及随着数据量的快速增长导致访问性能的急剧下降,目前也只是通过传统的分表、分库等解决方案进行优化,但性能提升未达到预期,且后续维护升级复杂麻烦,不能很好的满足存储和性能弹性水平扩展的需求。 本项目同时具有 OLTP 和 OLAP 应用需求,也曾设计构建混合型的数据存储方案(MySQL+ HDFS + Hive + Kylin + HBase + Spark),功能上可同时满足 OLTP 和 OLAP 应用需求,但问题也很明显,如: 要满足一定程度的实时在线分析,还需要做一些数据迁移同步工作,需要开发实时同步 ETL 中间件,实时从存储事务数据的关系数据库向存储面向分析的 Hive、HBase 数据库同步数据,实时性及可靠性不能保证; 对于基于 SQL 数据访问的应用程序的切换到该数据平台构成很大挑战,应用程序的数据访问层都需要进行修改适配,工作量大,切换成本高; 对于面向大数据的的分布式数据库产品(Hive、HBase 等)投入成本高且维护复杂,容易出错,可维护性差。 选型指标 支持容量及性能的水平弹性扩缩; 支持对使用 MySQL 协议的应用程序的便捷稳定的迁移,无需修改程序; 满足业务故障自恢复的高可用,且易维护; 强一致的分布式事务处理; 支持 Spark,可支撑机器学习应用; 集群状态可视化监控,方便运行维护。 我们大部分应用程序数据访问用的是 MySQL 的协议,TiDB 数据库完美的支持了 MySQL 的 SQL 语法,我们现有的应用程序几乎不用做任何修改,就可直接切换到 TiDB 上使用,并且能够很好的满足我们的 OLTP 需求和复杂 OLAP 的需求。另外,TiSpark 是建立在 Spark 引擎之上的,Spark 在机器学习领域上还是比较成熟的。考虑到未来我们的平台也会用到机器学习的一些业务应用,综合上述方面,TiDB + TiSpark 成为了我们首选的技术解决方案。TiDB 上线前测试 TiDB 在我司的数据中心部署的应用情况如下:部署架构 改造之前,主要用 MySQL 多实例的方式承载 WindRDS 所有的业务数据存储和应用,随着数据增长,存储容量接近单机的磁盘极限,单机的磁盘 IO 繁忙且易阻塞,查询性能难以满足业务增长的需求。数据量大了以后,传统的 MySQL 水平扩展能力弱,性能和稳定性容易产生问题,现有传统关系数据库已不能满足业务的扩展和应用,已成为制约业务发展的瓶颈。而为了满足大数据可视化 BI 分析、机器学习的 OLAP 场景,选用了多种数据中间件产品 HBase、Hive、Kylin 及 Spark 进行组合,形成一个复杂的多种数据中间件产品混合型集群,一定程度满足了 OLAP 的需求,但不同的产品之间存在资源争抢和制约,集群非常难于维护,非一步到位的最佳方案。图 2:改造前 WindRDS 系统架构改造之后,TiDB + TiSpark 的解决方案,解决了之前方案的不足,系统数据中间件产品种类简化,OLTP + OLAP 一揽子解决方案,系统数据存储和查询计算集群结构简单,较少人工参与系统节点维护,降低运维复杂度,是一个比较理想的解决方案。图 3:改造后 WindRDS 部署架构测试集群配置 TiDB 测试集群总体配置如下: 类型 配置 节点数 TiDB 12C 32G 2 PD 16C 16G 3 TiKV 16C 32G 2T(SSD) 5 TiSpark 测试集群总体配置如下: 类型 配置 节点数 TiSpark_master 4C 8G 1 TiSpark_slave 9C 15G 7 测试数据查询性能对比 我们使用 TiDB 1.0 版本搭建测试集群,然后我们进行了简单的查询性能测试,我们对 WindRDS 的 5 种类型的数据进行查询测试,从业务应用中选择了针对每种数据类型的耗时、复杂的关联 SQL 语句,分别在 MySQL 上和 TiDB 上进行执行,多次执行取平均值,如下图所示,明显的,TiDB 的响应时间要小于 MySQL,可见 TiDB 的查询性在我们业务模型中表现明显优于 MySQL 。图 4:测试数据关键操作对比 MySQL vs TiDB图 5:测试数据关键操作 MySQL vs TiDB 耗时对比 (越低越好)TiDB 上线 从 1 月初测试环境搭建完成到上线,TiDB 稳定运行四个多月,平均 QPS 稳定在数千。TiDB 在性能、可用性、稳定性上完全超出了我们的预期。测试及上线过程中的一些问题 由于前期我们对 TiDB 的了解还不深,在此迁移期间碰到的一些兼容性的问题,简单列举如下: 比如 TiDB 的自增 ID 的机制; 表外键级联机制; 排序的时候需要使用字段名等。 以上问题咨询 TiDB 的工程师后,很快的得到了解决,非常感谢 TiDB 团队的支持以及快速响应。另外,在使用 TiDB 1.0 版本的过程中我们也遇到过如下问题: 集群中某个 TiKV 节点的 SSD 满了,但是集群不认为满了,继续要求该节点写入数据,导致进程宕机。 集群中任何一个节点 IO 能力下降,都会导致整个集群若依赖他的操作都受到影响,因此,该分布式的数据库等组件,虽然提高了性能和扩展性,但是维护也一样比较棘手,任何瓶颈,都有可能拉低整个集群的性能。 以上问题再升级到 TiDB 2.0 版本后解决,咨询 TiDB 官方团队答复如下: 第一个问题,在 TiDB 2.0 版本有对应的优化,TiDB 在空间不足时会根据剩余空间进行调度,降低此问题发生的概率。 第二个问题,TiDB 2.0 版本会充分考虑机器负载,响应时间等维度进行调度,尽可能避免单点成为整个系统的瓶颈。 后续和展望 我们对 TiDB 越来越了解,后续我们计划对 TiDB 进行大规模推广使用,具体包括: 公司后续关于风电领域大数据中心的开发建设,考虑选型 TiDB 作为数据存储,并推荐给我们的合作客户。 公司 WindRDS、WindCMS 等既有应用系统将考虑逐步切换到 TiDB 上来。 WindRDS 后续关于大数据多维度可视化分析、专家系统及机器学习等应用功能的开发,对于数据的存储和查询应用,计划选用 TiDB + TiSpark 进行底层中间件的支持。 最终通过 TiDB 形成一个同时兼容分析型和事务型(HTAP)的统一数据库平台解决方案。"}, {"url": "https://pingcap.com/cases-cn/user-case-ping++/", "title": "TiDB 在 Ping++ 金融聚合支付业务中的实践", "content": " 作者:宋涛,Ping++ DBA Ping++ 介绍 Ping++ 是国内领先的支付解决方案 SaaS 服务商。自 2014 年正式推出聚合支付产品,Ping++ 便凭借“7 行代码接入支付”的极致产品体验获得了广大企业客户的认可。如今,Ping++ 在持续拓展泛支付领域的服务范围,旗下拥有聚合支付、账户系统、商户系统三大核心产品,已累计为近 25000 家企业客户解决支付难题,遍布零售、电商、企业服务、O2O、游戏、直播、教育、旅游、交通、金融、房产等等 70 多个细分领域。Ping++ 连续两年入选毕马威中国领先金融科技 50 强,并于 2017 成功上榜 CB Insights 全球 Fintech 250 强。从支付接入、交易处理、业务分析到业务运营,Ping++ 以定制化全流程的解决方案来帮助企业应对在商业变现环节可能面临的诸多问题。TiDB 在 Ping++ 的应用场景 - 数据仓库整合优化 Ping++ 数据支撑系统主要由流计算类、报表统计类、日志类、数据挖掘类组成。其中报表统计类对应的数据仓库系统,承载着数亿交易数据的实时汇总、分析统计、流水下载等重要业务:随着业务和需求的扩展,数仓系统历经了多次发展迭代过程: 由于业务需求中关联维度大部分是灵活多变的,所以起初直接沿用了关系型数据库 RDS 作为数据支撑,数据由自研的数据订阅平台从 OLTP 系统订阅而来。 随着业务扩大,过大的单表已不足以支撑复杂的查询场景,因此引入了两个方案同时提供数据服务:ADS,阿里云的 OLAP 解决方案,用来解决复杂关系型多维分析场景。ES,用分布式解决海量数据的搜索场景。 以上两个方案基本满足业务需求,但是都仍存在一些问题: ADS:一是数据服务稳定性,阿里云官方会不定期进行版本升级,升级过程会导致数据数小时滞后,实时业务根本无法保证。二是扩容成本,ADS 为按计算核数付费,如果扩容就必须购买对应的核数,成本不是那么灵活可控。 ES:单业务搜索能力较强,但是不适合对复杂多变的场景查询。且研发运维代价相对较高,没有关系型数据库兼容各类新业务的优势。 所以需要做出进一步的迭代整合,我们属于金融数据类业务,重要性安全性不能忽视、性能也得要有保障,经过我们漫长的调研过程,最终,由 PingCAP 研发的 TiDB 数据库成为我们的目标选型。TiDB 具备的以下核心特征是我们选择其作为实时数仓的主要原因: 高度兼容 MySQL 语法; 水平弹性扩展能力强; 海量数据的处理性能; 故障自恢复的高可用服务; 金融安全级别的架构体系。 并追踪形成了以下数据支撑系统架构:新的方案给我们的业务和管理带来了以下的提升和改变: 兼容:整合了现有多个数据源,对新业务上线可快速响应; 性能:提供了可靠的交易分析场景性能; 稳定:更高的稳定性,方便集群运维; 成本:资源成本和运维成本都有所降低。 TiDB 架构解析及上线情况 TiDB 是 PingCAP 公司受 Google Spanner / F1 论文启发而设计的开源分布式 NewSQL 数据库。从下图 Google Spanner 的理念模型可以看出,其设想出数据库系统把数据分片并分布到多个物理 Zone 中、由 Placement Driver 进行数据片调度、借助 TrueTime 服务实现原子模式变更事务,从而对外 Clients 可以提供一致性的事务服务。因此,一个真正全球性的 OLTP & OLAP 数据库系统是可以实现的。我们再通过下图分析 TiDB 整体架构:可以看出 TiDB 是 Spanner 理念的一个完美实践,一个 TiDB 集群由 TiDB、PD、TiKV 三个组件构成。 TiKV Server:负责数据存储,是一个提供事务的分布式 Key-Value 存储引擎; PD Server:负责管理调度,如数据和 TiKV 位置的路由信息维护、TiKV 数据均衡等; TiDB Server:负责 SQL 逻辑,通过 PD 寻址到实际数据的 TiKV 位置,进行 SQL 操作。 生产集群部署情况:现已稳定运行数月,对应的复杂报表分析性能得到了大幅提升,替换 ADS、ES 后降低了大量运维成本。TiDB 在 Ping++ 的未来规划 TiSpark 的体验TiSpark 是将 Spark SQL 直接运行在分布式存储引擎 TiKV 上的 OLAP 解决方案。下一步将结合 TiSpark 评估更加复杂、更高性能要求的场景中。 OLTP 场景目前数仓 TiDB 的数据是由订阅平台订阅 RDS、DRDS 数据而来,系统复杂度较高。TiDB 具备了出色的分布式事务能力,完全达到了 HTAP 的级别。TiKV 基于 Raft 协议做复制,保证多副本数据的一致性,可以秒杀当前主流的 MyCat、DRDS 分布式架构。且数据库的可用性更高,比如我们对生产 TiDB 集群所有主机升级过磁盘(Case 记录),涉及到各个节点的数据迁移、重启,但做到了相关业务零感知,且操作简单,过程可控,这在传统数据库架构里是无法轻易实现的。我们计划让 TiDB 逐渐承载一些 OLTP 业务。 对 TiDB 的建议及官方回复 DDL 优化:目前 TiDB 实现了无阻塞的 online DDL,但在实际使用中发现,DDL 时生成大量 index KV,会引起当前主机负载上升,会对当前集群增加一定的性能风险。其实大部分情况下对大表 DDL 并不是很频繁,且时效要求并不是特别强烈,考虑安全性。建议优化点: 是否可以通过将源码中固定数值的 defaultTaskHandleCnt、defaultWorkers 变量做成配置项解决; 是否可以像 pt-osc 工具的一样增加 DDL 过程中暂停功能。 DML 优化:业务端难免会有使用不当的 sql 出现,如导致全表扫描,这种情况可能会使整个集群性能会受到影响,对于这种情况,是否能增加一个自我保护机制,如资源隔离、熔断之类的策略。 针对以上问题,我们也咨询了 TiDB 官方技术人员,官方的回复如下: 正在优化 Add Index 操作的流程,降低 Add Index 操作的优先级,优先保证在线业务的操作稳定进行。 计划在 1.2 版本中增加动态调节 Add Index 操作并发度的功能。 计划在后续版本中增加 DDL 暂停功能。 对于全表扫描,默认采用低优先级,尽量减少对于点查的影响。后续计划引入 User 级别的优先级,将不同用户的 Query 的优先级分开,减少离线业务对在线业务的影响。 最后,特此感谢 PingCAP 所有团队成员对 Ping++ 上线 TiDB 各方面的支持!"}, {"url": "https://pingcap.com/cases-cn/user-case-youzu/", "title": "TiDB 在游族网络平台部的深度应用", "content": " 公司介绍 游族网络股份有限公司(SZ.002174)成立于 2009 年,是全球领先的互动娱乐供应商。公司以“大数据”、“全球化”、“精品化”为战略方向,立足全球化游戏研发与发行,知名 IP 管理,大数据与智能化,泛娱乐产业投资四大业务板块全面发展。背景 2017 年初的时候,游族的用户中心体系面临迭代和重构,当时数据库有数亿多的核心数据,通过 hash key 分为了 1024 张表在 64 个数据库中来存储,使用自研的代码框架来进行对应 hash key 的 seek 操作。这时,非 hash key 的查询、DDL 变更等业务需求,分表分库逻辑代码框架的局限,让研发和运维都面临较高的数据库使用成本,数据库不能灵活高效的支撑业务需求。图 1:分库分表方案架构图为了解决上述问题,游族的技术团队急需一套同时满足如下的条件的数据库分布式集群: 能够提供实时的 OLTP 的一致性数据存储服务; 弹性的分布式架构; 配套的监控备份方案; 稳定的高可用性; 较低的迁移重构成本。 前期选择 最开始先考察了几个方案,但都有相对来说的不足: 方案一,将整个分表分库逻辑剥离到开源分表分库中间件上: 基于 2PC 的 XA 弱事务的一致性保证不尽如人意; 高可用架构更加复杂,单分片的局部不可用会对全局产生影响; 备份恢复的复杂度高; 这些方案引入了新的 sharding key 和 join key 的设计问题,整体的迁移难度不降反升。 方案二,官方的 MySQL cluster 集群: ndb 引擎不同于 InnoDB,需要修改表引擎,且实际使用效果未知; ndb 的高可用有脑裂风险; 监控备份的方案需要另作整理; 国内生产用例不多,资料缺乏,非企业版运维流程复杂。 探索 机缘巧合下,与 TiDB 的技术团队做了一次技术交流,了解到 TiDB 是 PingCAP 受 Google Spanner 的论文启发设计而来的开源分布式 NewSQL 数据库,具备如下 NewSQL 特性: SQL支持 (TiDB 是 MySQL 兼容的); 水平线性弹性扩展; 分布式事务; 跨数据中心数据强一致性保证; 故障自恢复的高可用; 海量数据高并发写入及实时查询(HTAP 混合负载)。 上述的特点非常契合目前我们在数据库应用设计上碰到的问题,于是在与 TiDB 的技术团队沟通了解后,就开始着手安排部署和测试 TiDB: TiDB 的备份目前采用的是逻辑备份,官方提供了一套基于 mydumper 和 myloader 的工具套件; 监控用的是应用内置上报 Prometheus 的方案,可以写脚本与自有的监控系统对接; 有状态的 KV 层采用的是 Raft 协议来保证,Leader 的选举机制满足了故障自恢复的需求; KV 层的 Region 分裂保证了集群无感知的扩展。 测试之后发现数据库运维中比较重要的几项都已经闭环的解决了,实测结论是: TiDB 是分布式结构,有网络以及多副本开销,导致 Sysbench OLTP 测试中 TiDB 单 server 的读性能不如 MySQL,但写优于 MySQL,且弹性扩展能力评估后可以满足业务的峰值需求; TiDB 的 OLAP 能力在大数据量下远优于 MySQL,且能看到持续的大幅提升; 由于 TiDB-Server 是无状态的,后续可以添加 Load Balance 来扩展 Server 层的支撑。 持续引入 在性能和需求满足前提下,就开始着手业务层的接入和改造: MySQL 协议的兼容这时候极大的降低了迁移到 TiDB 的成本,官方的 TiDB 同步 MySQL 的 Syncer 工具也给了接入和改造有力的支持,部分迁移在业务层就是一次 MySQL 的主从切换。于是,用户积分系统的扩展便不再采用分表分库的方案,分表逻辑回归到多个独立的单表,数亿的数据在 OLTP 的业务场景下表现十分出色,没有 sharding key 的约束后,整个使用逻辑在上层看来和 MySQL 单表没有不同,更加灵活的索引也提供了一部分低开销的 OLAP 的查询能力,整体的迁移改造流程比较顺利,业务契合度很高。随着上述系统的成功应用后,后面符合场景的 OLTP 项目也逐渐开始使用 TiDB: 登录态系统:原先的在 MySQL 采用 Replace 保留最后一条数据,迁移到 TiDB 的模式后,由于表的伸缩能力获得了很大的提升,故将 Replace 改为了 Insert 保留了所有的登录情况,单表数据量十亿以上,业务上支持了更多维度的记录,没有碰到性能和扩展性问题; 礼包码系统:礼包码的主流程为复杂度 O(1) 的 hash seek OLTP业务,经过 TiDB 的改造后,将原来的 100 个表的分表模式集中成单表管理,单表数据预计会达到 20 亿+; 用户轨迹项目:数据库弹性能力增长后的新需求,一些重要的用户行为数据的记录。 同时,在 kv 存储层没有瓶颈的时候,采用复用了集群的 kv 层的策略,在无状态的 Server 层做了业务隔离,间接的提升了整个集群的使用率,类似一个 DBaaS 的服务(图 2)。图 2:多套业务系统 TiDB 部署图RC2.2 -> GA1.0 -> GA1.1 从 RC2.2 版本到 GA1.0,游族平台部的生产环境已经有 3 套 TiDB 集群在运行,共计支撑了 6 个 OLTP 业务的稳定运行快一年的时间。 期间 PingCAP 团队提供了非常多的技术支持:集群部署策略、BUG 响应和修复、升级方案协助、迁移工具支持等,厂商和开源社区都非常活跃。从 RC2.2 开始,从性能优化到细小的 SQL 兼容性 BUG 修复,每个版本都能看到开发团队对 roadmap 的细致规划和态度。TiDB 也在厂商和社区打磨下,性能和稳定性逐渐提升,兼容性也越来越好。现在官方已经发布 GA1.1 的 alpha 版本,重构了 TiDB 的优化器,我们也在第一时间做了详细的测试,目前大部分 OLAP 性能比 GA1.0 要提升 1~2 倍,不得不感慨 TiDB 的演进速度,也期待 1.1 的正式发布。计划和期望 我们与 TiDB 团队的沟通中了解到补充 TiDB-Server 的重 OLAP 需求的 TiSpark 计算层已经比较成熟了,后面计划分析型的需求也会尝试使用 TiSpark 来进行计算。同时我们与 TiDB 团队在交流的时候也得知分区表,视图等功能都已经在计划中,后续 TiDB 的数据存储方式将会越来越灵活。经过内部的实际使用后,后续已经有数个符合业务场景在评估或计划使用 TiDB 作为 OLTP 存储层的支撑。TiDB 给了大库大表业务的一个全新的选择方向,相信 TiDB 以后能在更多的业务选型和设计方案上给出新的方向和思路。 作者:陶政,游族网络平台部 MySQL DBA 负责人。曾任同程旅游系统架构组 DBA,现负责游族网络数据库整体的运维规划和设计。熟悉各类业务的数据库设计、缓存设计、离线数据分析等解决方案。 "}, {"url": "https://pingcap.com/cases-cn/user-case-iqiyi/", "title": "TiDB 在爱奇艺的应用及实践", "content": " 作者:朱博帅,爱奇艺资深数据库架构师 背景介绍 爱奇艺,中国高品质视频娱乐服务提供者,2010 年 4 月 22 日正式上线,推崇品质、青春、时尚的品牌内涵如今已深入人心,网罗了全球广大的年轻用户群体,积极推动产品、技术、内容、营销等全方位创新。企业愿景为做一家以科技创新为驱动的伟大娱乐公司。我们在前沿技术领域也保持一定的关注度。随着公司业务的快速发展,原来普遍使用的 MySQL 集群遇到了很多瓶颈,比如单机 MySQL 实例支撑的数据量有限,只能通过不停删除较旧的数据来维持数据库的运转。同时单表的数据行数不断增大导致查询速度变慢。急需一种可扩展、高可用同时又兼容 MySQL 访问方式的数据库来支撑业务的高速发展。我司从 2017 年年中开始调研 TiDB,并且在数据库云部门内部系统中使用了 TiDB 集群。从今年 TiDB 推出 2.0 之后,TiDB 愈发成熟,稳定性与查询效率都有很大提升。今年陆续接入了边控中心、视频转码、用户登录信息等几个业务,这几个业务背景和接入方式如下详述。项目介绍 1. 边控中心 边控中心存储的是机器的安全统计信息,包括根据 DC、IP、PORT 等不同维度统计的流量信息。上层业务会不定期做统计查询,其业务页面如下:图 1 边控中心上层业务页面(一)图 2 边控中心上层业务页面(二)在选型过程中,也考虑过时序型数据库 Apache Druid(http://druid.io),但是 Druid 聚合查询不够灵活,最终放弃 Druid 选择了 TiDB 数据库。TiDB 几乎完全兼容 MySQL 的访问协议,可以使用现有的 MySQL 连接池组件访问 TiDB,业务迁移成本低,开发效率高。边控中心是爱奇艺第一个在线业务使用 TiDB 的项目,所以我们制定了详细的上线计划。 第一,部署单独的 TiDB 集群。然后,为了数据安全,部署了 TokuDB 集群,用作 TiDB 集群的备份数据库。 第二,我们通过 TiDB-Binlog 将 TiDB 集群的数据变更实时同步到 TokuDB 集群中,作为 TiDB 的灾备方案。 第三,前端部署了自研的负载均衡中间件,以最大化利用多个 TiDB 节点的计算能力,同时保证 TiDB 节点的高可用。 第四,部署 Prometheus 和 Grafana 监控 TiDB 集群健康状况,同时 Grafana 接入了公司的告警平台,会及时将告警信息通过短信、邮件等方式通知到运维值班同事。 边控中心上线过程中,也遇到了一些问题,都比较顺利的解决了: 最常见的问题是连接超时。经过仔细排查发现是统计信息严重过时导致执行计划无法选择最优解造成的,这个问题其实是关系型数据库普遍存在的问题,普遍的解决思路是手工进行统计信息收集,或者通过 hint 的方式来固定执行计划,这两种方式对使用者都有一定的要求,而最新版本的 TiDB 完善了统计信息收集策略,比如增加了自动分析功能,目前这个问题已经解决。 还遇到了加索引时间较长的问题。这一方面是由于 DDL 执行信息更新不及时,造成查询 DDL 进度时看到的是滞后的信息。另一方面是由于有时会存在 size 过大的 Region,导致加索引时间变长。这个问题反馈给 PingCAP 官方后得到比较积极的响应,大 Region 已经通过增加 Batch split 等功能在新版的 TiDB 中修复了。 边控中心上线以后,在不中断业务的情况下成功做过版本升级,修改 TiKV 节点内部参数等操作,基本对业务没有影响。在升级 TiKV 节点过程中会有少量报错,如“region is unvailable[try again later]、tikv server timeout”等。这是由于缓存信息滞后性造成的,在分布式系统中是不可避免的,只要业务端有重试机制就不会造成影响。边控中心上线以后,数据量一直在稳定增长,但查询性能保持稳定,响应时间基本保持不变,能做到这点殊为不易,我个人的理解,这个主要依赖 TiDB 底层自动分片的策略,TiDB 会根据表数据量,按照等长大小的策略(默认 96M)自动分裂出一个分片,然后通过一系列复杂的调度算法打散到各个分布式存储节点上,对一个特定的查询,不管原表数据量多大,都能通过很快定位到某一个具体分片进行数据查询,保证了查询响应时间的稳定。边控中心数据量增长情况如下:图 3 边控中心数据量增长情况TiDB 底层自动分片策略:图 4 TiDB 底层自动分片策略2. 视频转码 视频转码业务是很早就接入 TiDB 集群的一个业务。视频转码数据库中主要存储的是转码生产过程中的历史数据,这些数据在生产完成后还需要进一步分析处理,使用 MySQL 集群时因为容量问题只好保留最近几个月的数据,较早的数据都会删掉,失去了做分析处理的机会。针对业务痛点,在 2017 年年底部署了 TiDB 独立集群,并全量+增量导入数据,保证原有 MySQL 集群和新建 TiDB 集群的数据一致性。在全量同步数据过程中,起初采用 Mydumper+Loader 方式。Loader 是 PingCAP 开发的全量导入工具,但是导入过程总遇到导入过慢的问题,为解决这个问题,PingCAP 研发了 TiDB-Lightning 作为全量同步工具。TiDB-Lightning 会直接将导出的数据直接转化为 sst 格式的文件导入到 TiKV 节点中,极大的提高了效率,1T 数据量在 5-6 个小时内就能完成,在稳定运行一段时间后将流量迁移到了 TiDB 集群,并且扩充了业务功能,迄今稳定运行。TiDB-Lightning 实现架构图:图 5 TiDB-Lightning 实现架构图3. 用户登录信息用户登录信息项目的数据量一直在稳定增长,MySQL 主备集群在不久的将来不能满足存储容量的需求。另外,由于单表数据量巨大,不得不在业务上进行分表处理,业务数据访问层代码变得复杂和缺乏扩展性。在迁移到 TiDB 后,直接去掉了分库分表,简化了业务的使用方式。另外,在 MySQL 中存在双散表并进行双写。在 TiDB 中进一步合并成了一种表,利用 TiDB 的索引支持多种快速查询,进一步简化了业务代码。在部署增量同步的过程中使用了官方的 Syncer 工具。Syncer 支持通过通配符的方式将多源多表数据汇聚到一个表当中,是个实用的功能,大大简化了我们的增量同步工作。目前的 Syncer 工具还不支持在 Grafana 中展示实时延迟信息,这对同步延迟敏感的业务是个缺点,据官方的消息称已经在改进中,同时 PingCAP 他们重构了整个 Syncer,能自动处理分表主键冲突,多源同时 DDL 自动过滤等功能,总之通过这套工具,可以快速部署 TiDB “实时”同步多个 MySQL,数据迁移体验超赞。图 6 Syncer 架构在我们公司业务对数据库高可用有两个需求:一个是机房宕机了,服务仍然可用。另一个是,多机房的业务,提供本机房的只读从库,提升响应速度。针对这些不同的需求,TiDB 集群采用了多机房部署的方式,保证其中任一个机房不可用时仍然正常对外提供服务(如下图)。对每个 TiKV 节点设置 label 后,TiDB 集群在每个机房都有一份数据的副本,PD 集群会自动调度到合适的副本进行读操作,也可以满足第二个要求。为了满足迁移过程中的高可用性,会在流量迁移完成后部署 TiDB 到 MySQL 的实时同步。Drainer 支持指定同步开始的时间戳,有力支持了反向同步功能。图 7 TiDB 集群多机房部署形式在整个运维过程中,PingCAP 的小伙伴们提供了及时的帮助,帮助我们定位问题并提出建议,保证了业务的有序运行。在此表示由衷的感谢!适用范围 在实践过程中感受到 TiDB 解决的痛点主要是横向扩展和高可用。单机数据库支撑的数据量有限,如果采用分库分表 + proxy 的方式,无论 proxy 是在客户端还是服务端都增加了运维的成本。同时 proxy 的查询效率在很多场景下不能满足要求。另外,proxy 对事务的支持都比较弱,不能满足数据强一致性的要求。TiDB 可以直接替代 proxy+MySQL 的架构,服务高可用性、数据高可用性、横向扩展性都由 TiDB 集群完成,完美解决了数据量增量情况下出现的很多问题。而且,TiDB 在数据量越大的情况下性能表现得比 MySQL 越惊艳。一些改进建议 统计信息的收集期望更加的智能化,选择更好的时机自动完成而且不影响线上业务。 OLTP 和 OLAP 混合场景下相互间的隔离和尽量互不影响还有许多工作值得推进。 一些外围工具还需要提供高可用特性。 未来计划 我司仍有其它业务在接入 TiDB 服务,目前正在评估测试中。一些业务场景是 OLTP+OLAP 混合的场景,TiSpark 正好可以大展身手。目前在测试集群发现 TiSpark 查询时对 OLTP 业务的影响还是比较大的,必须限制 TiSpark 对 TiDB 集群造成的压力。还部署了单独 TiDB 集群做 OLAP 场景的测试,对内部参数做了针对性的优化。未来计划会继续加大对 TiDB 的投入,贡献一些 PR 到社区,其中很大的一部分工作是增强 TiDB-Binlog 的功能,和现有的一些数据同步组件整合起来,支持 TiDB 到 Kudu、HBase 等的同步。"}, {"url": "https://pingcap.com/cases-cn/user-case-wanda/", "title": "TiDB 帮助万达网络科技集团实现高性能高质量的实时风控平台", "content": " 作者:陈新江,万达网络科技集团大数据中心 万达网络科技集团 是中国唯一的实业+互联网大型开放型平台公司,拥有飞凡信息、快钱支付、征信、网络信贷、大数据等公司,运用大数据、云计算、人工智能、场景应用等技术为实体产业实现数字化升级,为消费者提供生活圈的全新消费服务。万达网络科技集团的技术团队,建设和维护着一套实时风控平台。这套实时风控平台,承担着各种关键交易的在线风控数据的写入和查询服务。实时风控平台后端的数据库系统在高性能,可靠性,可扩展性上有很高的要求,并且需要满足如下核心功能和业务要求: 风控相关业务数据实时入库 实时风控规则计算 通过 BI 工具分析风控历史数据 ETL 入库到 Hadoop 数据仓库 应用开发侧需要兼容 MySQL,降低应用改造门槛 为实现上述业务目标,万达网络科技集团的技术团队在实时风控数据库选型的早期阶段,首先选择了 MySQL Galera Cluster 作为数据库集群的技术架构。这套 MySQL 数据库架构通过不同于 MySQL 主流复制技术的复制机制,实现在多个 MySQL 节点间建立强同步关系,实现数据的副本和高可用。但经过业务实践,发现这套方案有诸多问题,其中比较突出的有以下几点: MySQL Galera Cluster 自身的强同步机制以大幅度降低集群整体性能为代价,集群整体性能比单节点 MySQL 还差。所以不能很好的满足“风控相关业务数据实时入库”的业务需求。 同时,MySQL Galera Cluster 的 JOIN 支持非常弱,不足以支持 BI 相关的复杂分析。 集群整体性能的短板加上对 JOIN 支持的薄弱,使得要在业务上实现大并发高性能的风控规则计算变的很困难。 万达的技术团队还考察了市场上用的比较多的 MySQL 主从复制以及通过 MySQL Proxy 中间件实现分库分表的方案。但这些方案,无论是高可用安全性,强一致性,还是对业务应用所需要的复杂事务/JOIN 操作以及横向扩展能力上,都无法满足实时风控平台的业务要求。这些问题集中反映在以下几个方面: 基于 MySQL 主从复制方式的高可用方案,容易出现诸如接入层脑裂和数据不一致的风险。 基于 MySQL Proxy 中间件的方案,缺少对分库分表后的跨库跨表的分布式事务支持以及对复杂 JOIN 的良好支持,因此也无法满足业务上风控规则实时计算和复杂查询的需求以及对业务团队的 BI 需求的支持。 基于 MySQL Proxy 中间件的方案需要业务代码的开发妥协,需要显式设计和指定分库分表的切分规则和路由配置,开发改造和运维成本显著增高。 在实时风控平台的高并发高性能的对外服务过程中,在线灵活扩容的相关工作在 MySQL Proxy 中间件架构中无法高效和可靠的实施。 最终万达的技术团队,通过评估验证,选择了 TiDB 帮助他们实现一个高性能,高可靠性和高扩展能力的实时风控平台后台数据库系统。TiDB 产品和技术方案对业务需求的支持和助力效果,集中表现在: 借助 TiDB 的分布式计算和存储引擎,集群对外服务的处理能力大大增强,高并发实时的风控规则计算能够轻松的处理完,相比较原来的 MySQL Galera Cluster 方案,单位处理性能提升了数倍。并且数据库集群获得了线性提升和扩展的能力。 集群整体 QPS(万级起)和 Latency (毫秒级) 对风控的实时性要求做出了技术保证。 无需考虑分库分表,对业务应用透明无侵入,应用开发和维护变得直观且简单。业务相关数据量规模和请求即便高速增长,也无需担心应用的复杂调整和运维的风险。 TiDB 针对分布式事务和强一致性的完善设计以及对各种 JOIN 模式的支持,使得实时风控类和 BI 分析类的业务应用能够高效运行。 这套实时风控平台,借助于 TiDB 的可靠性架构和高性能分布式处理能力,在业务生产环境已经稳定运行超过半年,期间经历过环境问题导致的故障,经历过诸如“618”高并发、大流量活动的严格考验。万达网络科技集团大数据中心技术专家陈新江表示:”TiDB 的表现让万达的技术团队有了信心,接下来将在 TiDB 的基础上,根据业务特点,拓展应用规模,增加诸如 TiSpark 复杂计算组件,整合 CDC 工具以提升 ETL 实时性以及增强 TiDB 运维管理能力等多项架构和技术演进工作,继续在万达的核心业务架构中发挥重要作用。”"}, {"url": "https://pingcap.com/cases-cn/user-case-gaea-ad/", "title": "盖娅互娱 | 日均数据量千万级,MySQL、TiDB 两种存储方案的落地对比", "content": " 作者简介:刘玄,盖娅互娱数据平台高级开发工程师,主要负责实时数据业务和数据流方向。毕业于湖南大学软件工程系,曾任百度高级运维工程师,负责大搜建库运维。 背景介绍 盖娅广告匹配系统(GaeaAD)用于支撑盖娅互娱全平台实时广告投放系统,需要将广告数据和游戏 SDK 上报的信息进行近实时匹配,本质上来说需要实时的根据各个渠道的广告投放与相应渠道带来的游戏玩家数据进行计算,实现广告转化效果分钟级别的展现及优化。初期的 MySQL 存储方案 在系统设计之初,基于对数据量的预估以及简化实现方案考虑,我们选用了高可用的 MySQL RDS 存储方案,当时的匹配逻辑主要通过 SQL 语句来实现,包含了很多联表查询和聚合操作。当数据量在千万级别左右,系统运行良好,基本响应还在一分钟内。图 1 MySQL RDS 存储方案架构图遭遇瓶颈,寻找解决方案 然而随着业务的发展,越来越多游戏的接入,盖娅广告系统系统接收数据很快突破千万/日,高峰期每次参与匹配的数据量更是需要翻几个番,数据库成为了业务的瓶颈。由于此时,整个技术架构出现了一些问题:1. 单次匹配耗时已从原本的 10 秒左右增加到 2 分钟以上,最慢的聚合查询甚至达到 20 分钟,时效性受到严重挑战。而且 MySQL 的问题是查询的时间随着数据量的增长而增长,以至于数据量越大的情况下查询越慢。2. 随着历史数据的积累,单表数据很快达到亿级别,此时单表的读写压力已经接近极限。3. 由于第一点提到的查询性能问题以及单机的容量限制,需要定时删除数据,对于一些时间跨度较长的业务查询需求没法满足。根据数据量的增长情况来看,分布式数据库会是很好的解决方案。首先考虑的是业务的垂直及水平拆分或者基于 MySQL 的数据库中间件方案和一些主流的 NoSQL 方案。但是仔细评估后,最先排除掉的是业务水平拆分的方案,因为业务逻辑中包含大量的关联查询和子查询,如果拆表后这些查询逻辑就没有办法透明的兼容,而且是比较核心的业务系统,时间精力的关系也不允许整体做大的重构。中间件的问题和分库分表的问题类似,虽然解决了大容量存储和实时写入的问题,但是查询的灵活度受限,而且多个 MySQL 实例的维护成本也需要考虑。第二个方案就是采用 NoSQL,因为此系统需要接收业务端并发的实时写入和实时查询,所以使用类似 Greenplum,Hive 或者 SparkSQL 这样的系统不太合适,因为这几个系统并不是针对实时写入设计的, MongoDB 的问题是文档型的查询访问接口对业务的修改太大,而且 MongoDB 是否能满足在这么大数据量下高效的聚合分析可能是一个问题。所以很明显,我们当时的诉求就是能有一款数据库既能像 MySQL 一样便于使用,最好能让业务几乎不用做任何修改,又能满足分布式的存储需求,还要保证很高的复杂查询性能。当时调研了一下社区的分布式数据库解决方案,找到了 TiDB 这个项目,因为协议层兼容 MySQL,而且对于复杂查询的支持不错,业务代码完全不用修改直接就能使用,使迁移使用成本降到极低。技术转身,使用 TiDB 在部署测试的过程中,我们使用 TiDB 提供的 Syncer 工具将 TiDB 作为 MySQL Slave 接在原业务的 MySQL 主库后边观察,确保读写的兼容性以及稳定性,经过一段时间观察后,确认读写没有任何问题,业务层的读请求切换至 TiDB,随后把写的流量也切换至 TiDB 集群,完成平滑的上线。图 2 TiDB 方案架构图GaeaAD 系统从 2016 年 10 月上线以来,已经稳定运行了一季度多,结合实际的使用体验,我们总结了 TiDB 带来的收益,主要有以下几点: 用 3 个节点组成的 TiDB 集群替换了原先的高可用 MySQL RDS 后,同样数据量级下,单次匹配平均耗时从 2 分钟以上降到了 30 秒左右,后续随着 TiDB 工程师的持续优化,达到了10 秒左右。另外,我们发现,TiDB 在数据规模越大的情况下,对比 MySQL 的优势就越明显,应该是 TiDB 自研的分布式 SQL 优化器带来的优势。不过在数据量比较轻量的情况下,因内部通信成本,优势相比 MySQL 并不明显。 图 3 TiDB 与 MySQL 在不同数据量下的查询时间对比 TiDB 支持自动 Sharding,业务端不用切表操作,TiDB 也不需要像传统的数据库中间件产品设定 Sharding key 或者分区表什么的,底层的存储会自动根据数据的分布,均匀的分散在集群中,存储空间和性能可以通过增加机器实现快速的水平扩展,极大地降低了运维成本。 TiDB 支持在线不中断的滚动升级,至今直接在线升级已有 10 余次左右,没出现过一起导致线上服务中断的情况,在可用性上体验不错。 TiDB 支持和 MySQL 的互备,这个功能很好的解决了我们业务迁移时候的过渡问题。 当前我们正在着手把 storm 集群上的 BI 系统的实时计算业务的数据存储系统从 MongoDB 替换成 TiDB(因 MongoDB 的使用门槛相对较高,运维成本大,查询方式不如传统的 SQL 灵活),后续也计划把实时性要求高、数据存储量大且存储周期较长的业务都迁移到 TiDB 上来,看上去是一个比较合适的场景。TiDB 工程师点评 盖娅的业务使用 TiDB 做了如下优化:1. 支持更多表达式下推,充分利用 TiKV 多实例的计算资源,加快计算速度;同时也尽可能将不需要用到的数据过滤掉,减小网络传输。2. TiDB 默认支持 HashJoin,将算子尽可能并行化,能够利用整个集群的计算资源。3. TiDB 采用流水线的方式读取数据,并且优化过 IndexScan 算子,降低整个流程的启动时间。"}, {"url": "https://pingcap.com/cases-cn/user-case-mobike-2/", "title": "TiDB 在摩拜单车的深度实践及应用", "content": " 作者介绍:吕磊,摩拜单车高级 DBA。 一、业务场景 摩拜单车 2017 年开始将 TiDB 尝试应用到实际业务当中,根据业务的不断发展,TiDB 版本快速迭代,我们将 TiDB 在摩拜单车的使用场景逐渐分为了三个等级: P0 级核心业务:线上核心业务,必须单业务单集群,不允许多个业务共享集群性能,跨 AZ 部署,具有异地灾备能力。 P1 级在线业务:线上业务,在不影响主流程的前提下,可以允许多个业务共享一套 TiDB 集群。 离线业务集群:非线上业务,对实时性要求不高,可以忍受分钟级别的数据延迟。 本文会选择三个场景,给大家简单介绍一下 TiDB 在摩拜单车的使用姿势、遇到的问题以及解决方案。二、订单集群(P0 级业务) 订单业务是公司的 P0 级核心业务,以前的 Sharding 方案已经无法继续支撑摩拜快速增长的订单量,单库容量上限、数据分布不均等问题愈发明显,尤其是订单合库,单表已经是百亿级别,TiDB 作为 Sharding 方案的一个替代方案,不仅完美解决了上面的问题,还能为业务提供多维度的查询。2.1 订单 TiDB 集群的两地三中心部署架构 图 1 两地三中心部署架构图整个集群部署在三个机房,同城 A、同城 B、异地 C。由于异地机房的网络延迟较高,设计原则是尽量使 PD Leader 和 TiKV Region Leader 选在同城机房(Raft 协议只有 Leader 节点对外提供服务),我们的解决方案如下: PD 通过 Leader priority 将三个 PD server 优先级分别设置为 5 5 3。 将跨机房的 TiKV 实例通过 label 划分 AZ,保证 Region 的三副本不会落在同一个 AZ 内。 通过 label-property reject-leader 限制异地机房的 Region Leader,保证绝大部分情况下 Region 的 Leader 节点会选在同城机房 A、B。 2.2 订单集群的迁移过程以及业务接入拓扑 图 2 订单集群的迁移过程以及业务接入拓扑图为了方便描述,图中 Sharding-JDBC 部分称为老 Sharding 集群,DBProxy 部分称为新 Sharding 集群。 新 Sharding 集群按照 order_id 取模通过 DBproxy 写入各分表,解决数据分布不均、热点等问题。 将老 Sharding 集群的数据通过使用 DRC(摩拜自研的开源异构数据同步工具 Gravity)全量+增量同步到新 Sharding 集群,并将增量数据进行打标,反向同步链路忽略带标记的流量,避免循环复制。 为支持上线过程中业务回滚至老 Sharding 集群,需要将新 Sharding 集群上的增量数据同步回老 Sharding 集群,由于写回老 Sharding 集群需要耦合业务逻辑,因此 DRC(Gravity)负责订阅 DBProxy-Sharding 集群的增量数放入 Kafka,由业务方开发一个消费 Kafka 的服务将数据写入到老 Sharding 集群。 新的 TiDB 集群作为订单合库,使用 DRC(Gravity)从新 Sharding 集群同步数据到 TiDB 中。 新方案中 DBProxy 集群负责 order_id 的读写流量,TiDB 合库作为 readonly 负责其他多维度的查询。 2.3 使用 TiDB 遇到的一些问题 2.3.1 上线初期新集群流量灰度到 20% 的时候,发现 TiDB coprocessor 非常高,日志出现大量 server is busy 错误。问题分析: 订单数据单表超过 100 亿行,每次查询涉及的数据分散在 1000+ 个 Region 上,根据 index 构造的 handle 去读表数据的时候需要往这些 Region 上发送很多 distsql 请求,进而导致 coprocessor 上 gRPC 的 QPS 上升。 TiDB 的执行引擎是以 Volcano 模型运行,所有的物理 Executor 构成一个树状结构,每一层通过调用下一层的 Next/NextChunk() 方法获取结果。Chunk 是内存中存储内部数据的一种数据结构,用于减小内存分配开销、降低内存占用以及实现内存使用量统计/控制,TiDB 2.0 中使用的执行框架会不断调用 Child 的 NextChunk 函数,获取一个 Chunk 的数据。每次函数调用返回一批数据,数据量由一个叫 tidb_max_chunk_size 的 session 变量来控制,默认是 1024 行。订单表的特性,由于数据分散,实际上单个 Region 上需要访问的数据并不多。所以这个场景 Chunk size 直接按照默认配置(1024)显然是不合适的。 解决方案: 升级到 2.1 GA 版本以后,这个参数变成了一个全局可调的参数,并且默认值改成了 32,这样内存使用更加高效、合理,该问题得到解决。 2.3.2 数据全量导入 TiDB 时,由于 TiDB 会默认使用一个隐式的自增 rowid,大量 INSERT 时把数据集中写入单个 Region,造成写入热点。解决方案: 通过设置 SHARD_ROW_ID_BITS,可以把 rowid 打散写入多个不同的 Region,缓解写入热点问题:ALTER TABLE table_name SHARD_ROW_ID_BITS = 8;。 2.3.3 异地机房由于网络延迟相对比较高,设计中赋予它的主要职责是灾备,并不提供服务。曾经出现过一次大约持续 10s 的网络抖动,TiDB 端发现大量的 no Leader 日志,Region follower 节点出现网络隔离情况,隔离节点 term 自增,重新接入集群时候会导致 Region 重新选主,较长时间的网络波动,会让上面的选主发生多次,而选主过程中无法提供正常服务,最后可能导致雪崩。问题分析: Raft 算法中一个 Follower 出现网络隔离的场景,如下图所示。 图 3 Raft 算法中,Follower 出现网络隔离的场景图 Follower C 在 election timeout 没收到心跳之后,会发起选举,并转换为 Candidate 角色。 每次发起选举时都会把 term 加 1,由于网络隔离,选举失败的 C 节点 term 会不断增大。 在网络恢复后,这个节点的 term 会传播到集群的其他节点,导致重新选主,由于 C 节点的日志数据实际上不是最新的,并不会成为 Leader,整个集群的秩序被这个网络隔离过的 C 节点扰乱,这显然是不合理的。 解决方案: TiDB 2.1 GA 版本引入了 Raft PreVote 机制,该问题得到解决。 在 PreVote 算法中,Candidate 首先要确认自己能赢得集群中大多数节点的投票,才会把自己的 term 增加,然后发起真正的投票,其他节点同意发起重新选举的条件更严格,必须同时满足 : 没有收到 Leader 的心跳,至少有一次选举超时。 Candidate 日志足够新。PreVote 算法的引入,网络隔离节点由于无法获得大部分节点的许可,因此无法增加 term,重新加入集群时不会导致重新选主。 三、在线业务集群(P1 级业务) 在线业务集群,承载了用户余额变更、我的消息、用户生命周期、信用分等 P1 级业务,数据规模和访问量都在可控范围内。产出的 TiDB Binlog 可以通过 Gravity 以增量形式同步给大数据团队,通过分析模型计算出用户新的信用分定期写回 TiDB 集群。图 4 在线业务集群拓扑图四、数据沙盒集群(离线业务) 数据沙盒,属于离线业务集群,是摩拜单车的一个数据聚合集群。目前运行着近百个 TiKV 实例,承载了 60 多 TB 数据,由公司自研的 Gravity 数据复制中心将线上数据库实时汇总到 TiDB 供离线查询使用,同时集群也承载了一些内部的离线业务、数据报表等应用。目前集群的总写入 TPS 平均在 1-2w/s,QPS 峰值 9w/s+,集群性能比较稳定。该集群的设计优势有如下几点: 可供开发人员安全的查询线上数据。 特殊场景下的跨库联表 SQL。 大数据团队的数据抽取、离线分析、BI 报表。 可以随时按需增加索引,满足多维度的复杂查询。 离线业务可以直接将流量指向沙盒集群,不会对线上数据库造成额外负担。 分库分表的数据聚合。 数据归档、灾备。 图 5 数据沙盒集群拓扑图4.1 遇到过的一些问题和解决方案 4.1.1 TiDB server oom 重启很多使用过 TiDB 的朋友可能都遇到过这一问题,当 TiDB 在遇到超大请求时会一直申请内存导致 oom, 偶尔因为一条简单的查询语句导致整个内存被撑爆,影响集群的总体稳定性。虽然 TiDB 本身有 oom action 这个参数,但是我们实际配置过并没有效果。于是我们选择了一个折中的方案,也是目前 TiDB 比较推荐的方案:单台物理机部署多个 TiDB 实例,通过端口进行区分,给不稳定查询的端口设置内存限制(如图 5 中间部分的 TiDBcluster1 和 TiDBcluster2)。例:[tidb_servers] tidb-01-A ansible_host=$ip_address deploy_dir=/$deploydir1 tidb_port=$tidb_port1 tidb_status_port=$status_port1 tidb-01-B ansible_host=$ip_address deploy_dir=/$deploydir2 tidb_port=$tidb_port2 tidb_status_port=$status_port2 MemoryLimit=20G 实际上 tidb-01-A、tidb-01-B 部署在同一台物理机,tidb-01-B 内存超过阈值会被系统自动重启,不影响 tidb-01-A。TiDB 在 2.1 版本后引入新的参数 tidb_mem_quota_query,可以设置查询语句的内存使用阈值,目前 TiDB 已经可以部分解决上述问题。4.1.2 TiDB-Binlog 组件的效率问题大家平时关注比较多的是如何从 MySQL 迁移到 TiDB,但当业务真正迁移到 TiDB 上以后,TiDB 的 Binlog 就开始变得重要起来。TiDB-Binlog 模块,包含 Pump&Drainer 两个组件。TiDB 开启 Binlog 后,将产生的 Binlog 通过 Pump 组件实时写入本地磁盘,再异步发送到 Kafka,Drainer 将 Kafka 中的 Binlog 进行归并排序,再转换成固定格式输出到下游。使用过程中我们碰到了几个问题: Pump 发送到 Kafka 的速度跟不上 Binlog 产生的速度。 Drainer 处理 Kafka 数据的速度太慢,导致延时过高。 单机部署多 TiDB 实例,不支持多 Pump。 其实前两个问题都是读写 Kafka 时产生的,Pump&Drainer 按照顺序、单 partition 分别进行读&写,速度瓶颈非常明显,后期增大了 Pump 发送的 batch size,加快了写 Kafka 的速度。但同时又遇到一些新的问题: 当源端 Binlog 消息积压太多,一次往 Kafka 发送过大消息,导致 Kafka oom。 当 Pump 高速大批写入 Kafka 的时候,发现 Drainer 不工作,无法读取 Kafka 数据。 和 PingCAP 工程师一起排查,最终发现这是属于 sarama 本身的一个 bug,sarama 对数据写入没有阈值限制,但是读取却设置了阈值:https://github.com/Shopify/sarama/blob/master/real_decoder.go#L88。最后的解决方案是给 Pump 和 Drainer 增加参数 Kafka-max-message 来限制消息大小。单机部署多 TiDB 实例,不支持多 Pump,也通过更新 ansible 脚本得到了解决,将 Pump.service 以及和 TiDB 的对应关系改成 Pump-8250.service,以端口区分。针对以上问题,PingCAP 公司对 TiDB-Binlog 进行了重构,新版本的 TiDB-Binlog 不再使用 Kafka 存储 binlog。Pump 以及 Drainer 的功能也有所调整,Pump 形成一个集群,可以水平扩容来均匀承担业务压力。另外,原 Drainer 的 binlog 排序逻辑移到 Pump 来做,以此来提高整体的同步性能。4.1.3 监控问题当前的 TiDB 监控架构中,TiKV 依赖 Pushgateway 拉取监控数据到 Prometheus,当 TiKV 实例数量越来越多,达到 Pushgateway 的内存限制 2GB 进程会进入假死状态,Grafana 监控就会变成下图的断点样子:图 6 监控拓扑图图 7 监控展示图目前临时处理方案是部署多套 Pushgateway,将 TiKV 的监控信息指向不同的 Pushgateway 节点来分担流量。这个问题的最终还是要用 TiDB 的新版本(2.1.3 以上的版本已经支持),Prometheus 能够直接拉取 TiKV 的监控信息,取消对 Pushgateway 的依赖。4.2 数据复制中心 Gravity (DRC) 下面简单介绍一下摩拜单车自研的数据复制组件 Gravity(DRC)。Gravity 是摩拜单车数据库团队自研的一套数据复制组件,目前已经稳定支撑了公司数百条同步通道,TPS 50000/s,80 线延迟小于 50ms,具有如下特点: 多数据源(MySQL, MongoDB, TiDB, PostgreSQL)。 支持异构(不同的库、表、字段之间同步),支持分库分表到合表的同步。 支持双活&多活,复制过程将流量打标,避免循环复制。 管理节点高可用,故障恢复不会丢失数据。 支持 filter plugin(语句过滤,类型过滤,column 过滤等多维度的过滤)。 支持传输过程进行数据转换。 一键全量 + 增量迁移数据。 轻量级,稳定高效,容易部署。 支持基于 Kubernetes 的 PaaS 平台,简化运维任务。 使用场景: 大数据总线:发送 MySQL Binlog,Mongo Oplog,TiDB Binlog 的增量数据到 Kafka 供下游消费。 单向数据同步:MySQL → MySQL&TiDB 的全量、增量同步。 双向数据同步:MySQL ↔ MySQL 的双向增量同步,同步过程中可以防止循环复制。 分库分表到合库的同步:MySQL 分库分表 → 合库的同步,可以指定源表和目标表的对应关系。 数据清洗:同步过程中,可通过 filter plugin 将数据自定义转换。 数据归档:MySQL→ 归档库,同步链路中过滤掉 delete 语句。 Gravity 的设计初衷是要将多种数据源联合到一起,互相打通,让业务设计上更灵活,数据复制、数据转换变的更容易,能够帮助大家更容易的将业务平滑迁移到 TiDB 上面。该项目 已经在 GitHub 开源,欢迎大家交流使用。五、总结 TiDB 的出现,不仅弥补了 MySQL 单机容量上限、传统 Sharding 方案查询维度单一等缺点,而且其计算存储分离的架构设计让集群水平扩展变得更容易。业务可以更专注于研发而不必担心复杂的维护成本。未来,摩拜单车还会继续尝试将更多的核心业务迁移到 TiDB 上,让 TiDB 发挥更大价值,也祝愿 TiDB 发展的越来越好。"}, {"url": "https://pingcap.com/cases-cn/user-case-zhuanzhuan/", "title": "TiDB 分布式数据库在转转公司的应用实践", "content": " 作者:孙玄,转转公司首席架构师;陈东,转转公司资深工程师;冀浩东,转转公司资深 DBA。 公司及业务架构介绍 转转二手交易网 —— 把家里不用的东西卖了变成钱,一个帮你赚钱的网站。由腾讯与 58 集团共同投资。为海量用户提供一个有担保、便捷的二手交易平台。转转是 2015 年 11 月 12 日正式推出的 APP,遵循“用户第一”的核心价值观,以“让资源重新配置,让人与人更信任”为企业愿景,提倡真实个人交易。转转二手交易涵盖手机、3C 数码、母婴用品等三十余个品类。在系统设计上,转转整体架构采用微服务架构,首先按照业务领域模型垂直拆分成用户、商品、交易、搜索、推荐微服务。对每一个功能单元(商品等),继续进行水平拆分,分为商品网关层、商品业务逻辑层、商品数据访问层、商品 DB / Cache,如下图所示: 项目背景 1. 面临的问题 转转后端业务现阶段主要使用 MySQL 数据库存储数据,还有少部分业务使用 MongoDB。虽然目前情况下使用这两种存储基本可以满足我们的需求,但随着业务的增长,公司的数据规模逐渐变大,为了应对大数据量下业务服务访问的性能问题,MySQL 数据库常用的分库、分表方案会随着 MySQL Sharding(分片)的增多,业务访问数据库逻辑会越来越复杂。而且对于某些有多维度查询需求的表,我们总需要引入额外的存储或牺牲性能来满足我们的查询需求,这样会使业务逻辑越来越重,不利于产品的快速迭代。从数据库运维角度讲,大数据量的情况下,MySQL 数据库在每次 DDL 都会对运维人员造成很大的工作量,当节点故障后,由于数据量较大,恢复时间较长。但这种 M - S 架构只能通过主从切换并且需要额外的高可用组件来保障高可用,同时在切换过程由于需要确定主库状态、新主库选举、新路由下发等原因,还是会存在短暂的业务访问中断的情况。 综上所述,我们面临的主要问题可归纳为: 数据量大,如何快速水平扩展存储; 大数据量下,如何快速 DDL; 分库分表造成业务逻辑非常复杂; 常规 MySQL 主从故障转移会导致业务访问短暂不可用。 2. 为什么选择 TiDB 针对上章提到的问题,转转基础架构部和 DBA 团队考虑转转业务数据增速,定位简化业务团队数据库使用方案,更好的助力业务发展,决定启动新型存储服务(NewSQL)的选型调研工作。 TiDB 数据库,结合了关系库与 KV 存储的优点,对于使用方,完全可以当做 MySQL 来用,而且不用考虑数据量大了后的分库分表以及为了支持分库分表后的多维度查询而建立的 Mapping 表,可以把精力全部放在业务需求上。所以我们把 TiDB 作为选型的首选对象展开了测试和试用。TiDB 测试 1. 功能测试 TiDB 支持绝大多数 MySQL 语法,业务可以将基于 MySQL 的开发,无缝迁移至 TiDB。不过目前 TiDB 不支持部分 MySQL 特性,如:存储过程、自定义函数、触发器等。2. TiDB 压力测试 通过测试工具模拟不同的场景的请求,对 TiDB 数据库进行压力测试,通过压力测试结果的对比,可以提供 RD 使用 TiDB 的合适业务场景以及 TiDB 的使用建议。 此次压力测试,总共使用 6 台物理服务器,其中 3 台 CPU 密集型服务器,用于启动 TiDB - Server、PD 服务;另外 3 台为 IO / CPU 密集型的PCIE 服务器,用于启动 TiKV 服务。 使用 sysbench - 1.0.11 测试数据大小为 200G 的 TiDB 集群,在不同场景下 TiDB 的响应时间(95th per):3. 结果整理 顺序扫描的效率是比较高的,连续的行大概率会存储在同一台机器的邻近位置,每次批量的读取和写入的效率会高; 控制并发运行的线程数,会减少请求响应时间,提高数据库的处理性能。 4. 场景建议 适合线上业务混合读写场景; 适合顺序写的场景,比如:数据归档、操作日志、摊销流水。 5. TiDB 预上线 将 TiDB 挂载到线上 MySQL,作为 MySQL 从库同步线上数据,然后业务将部分线上读流量切换到 TiDB,可以对 TiDB 集群是否满足业务访问做好预判。业务接入 1. 迁移过程 我们第一个接入 TiDB 的业务线是转转消息服务。消息作为转转最重要的基础服务之一,是保证平台上买卖双方有效沟通、促进交易达成的重要组件,其数据量和访问量都非常大。起初我们使用的是 MySQL 数据库,对其所有的业务都做了库的垂直拆分以及表的水平拆分。目前线上有几十 TB 的数据,记录数据达到了几百亿。虽对 MySQL 做了分库分表,但实例已经开始又有偶发的性能问题,需要马上对数据进行二次拆分,而二次拆分的执行成本也比较高,这也是我们首先迁移消息数据库的原因之一。消息服务有几个核心业务表:联系人列表、消息表、系统消息表等等。联系人列表作为整个消息系统的枢纽,承载着巨大的访问压力。业务场景相对其他表最复杂的,也是这个表的实例出现了性能问题,所以我们决定先迁移联系人列表。整个迁移过程分三步:测试(判断 TiDB 是否满足业务场景,性能是否 OK)、同步数据、切流量。(1)测试:首先我们模拟线上的数据和请求对“联系人列表”做了大量功能和性能的验证,而且还将线上的数据和流量引到线下,对数据库做了真实流量的验证,测试结果证明 TiDB 完全满足消息业务的需求。引流工作,我们是通过转转自研的消息队列,将线上数据库的流量引一份到测试环境。测试环境消费消息队列的数据,转换成数据库访问请求发送到 TiDB 测试集群。通过分析线上和测试环境两个数据访问模块的日志可以初步判断 TiDB 数据库是否可以正常处理业务请求。当然仅仅这样是不够的,DBA 同学还需要校验 TiDB 数据的正确性(是否与线上 MySQL 库一致)。验证思路是抽样验证 MySQL 库表记录和 TiDB 的记录 Checksum 值是否一致。(2)同步数据:DBA 同学部署 TiDB 集群作为 MySQL 实例的从库,将 MySQL 实例中的联系人列表(单实例分了 1024 个表)的数据同步到 TiDB 的一张大表中。(3)切流量:切流量分为三步,每两步之间都有一周左右的观察期。 第一步将读流量灰度切到 TiDB 上; 第二步断开 TiDB 与 MySQL 的主从同步,业务开双写(同时写 MySQL 和 TiDB,保证两库数据一致)确保业务流量可以随时回滚到 MySQL; 第三步停止 MySQL 写入,到此业务流量完全切换到 TiDB 数据库上。 迁移过程中最重要的点就是确保两个数据库数据一致,这样读写流量随时可以切回 MySQL,业务逻辑不受任何影响。数据库双写的方案与上文提到的引流测试类似,使用消息队列引一份写入流量,TiDB 访问模块消费消息队列数据,写库。但仅仅这样是不能保证两个库数据一致的,因为这个方案无法保证两个写库操作的原子性。所以我们需要一个更严谨的方案,转转的消息队列还提供了事务消息的支持,可以保证本地操作和发送消息的原子性。利用这一特性再加上异步补偿策略(离线扫描日志,如果有失败的写入请求,修正数据)保证每个消息都被成功消费且两个库每次写入结果都是一致的,从而保证了 MySQL 与 TiDB 两个库的数据一致。2. 遇到问题 按照上述的方案,我们已经将消息所有的业务都切到 TiDB 数据库上。迁移过程中也不都是顺风顺水,也遇到了问题,过程中也得到了 TiDB 官方团队的大力支持。这里主要介绍两个问题:(1)TiDB 作为分布式存储,其锁机制和 MySQL 有很大不同。我们有一个并发量很大,可能同时更新一条记录的场景,我们用了 MySQL 的唯一索引保证了某个 Key 值的唯一性,但如果业务请求使用默认值就会大量命中唯一索引,会造成 N 多请求都去更新统一同一条记录。在 MySQL 场景下,没有性能问题,所以业务上也没做优化。但当我们用这个场景测试 TiDB 时,发现 TiDB 处理不太好,由于其使用的乐观锁,数据库输出大量的重试的日志。业务出现几十秒的请求延迟,造成队列中大量请求被抛弃。PingCAP 的同学建议调整 retry_limit 但也没有完全生效(该 BUG 已经在 2.0 RC 5 已经修复),最后业务进行优化(过滤使用默认值的请求)后问题得到解决。(2)第二个问题是运维方面的,DBA 同学按照使用 MySQL 的运维经验,对一个上近 T 的表做了 Truncate操作,操作后,起初数据库表现正常,但几分钟后,开始出现超时,TiKV 负载变高。最后请教 PingCAP 同学分析,定位是操作触发了频繁回收 Region 的 BUG(该 BUG TiDB 2.0 版本已经修复)。线上效果对比 1. 队列等待情况对比 使用 TiDB 数据库,业务模块队列请求数基本保持 1 个,MySQL 会有较大抖动。2. 请求延迟情况对比 使用 TiDB 数据库,整体响应延时非常稳定,不受业务流量高峰影响,但 MySQL 波动很大。 另外在扩展性方面,我们可以通过无缝扩展 TiDB 和 TiKV 实例提升系统的吞吐量,这个特性 MySQL 是不具备的。3. 业务延迟和错误量对比 接入 TiDB 数据库后业务逻辑层服务接口耗时稳定无抖动,且没有发生丢弃的情况(上图错误大多由数据访问层服务队列堆积发生请求丢弃造成)。TiDB 线上规模及后续规划 目前转转线上已经接入消息、风控两套 OLTP 以及一套风控 OLAP 集群。 集群架构如下:目前转转线上 TiDB 集群的总容量几百 TB,线上 TiDB 表现很稳定,我们会继续接入更多的业务(留言,评论、搜索、商品、交易等等)。1. 后续规划 多个正在开发的新业务在开发和测试环境中使用 TiDB,线上会直接使用 TiDB; 转转核心的留言、评论、搜索、商品、交易订单库计划迁移到 TiDB,已经开始梳理业务,准备展开测试; 计划在后续 TiDB 的使用中,TiKV 服务器池化,按需分配 TiKV 节点。 2. TiDB 使用成果 利用 TiDB 水平扩展特性,避免分库分表带来的问题,使得业务快速迭代; TiDB 兼容 MySQL 语法和协议,按照目前线上 MySQL 使用规范,可以无缝的迁移过去,无需 RD 做调整,符合预期; 在数据量较大的情况下,TiDB 响应较快,优于 MySQL; 集群出现故障对用户无感知; TiDB 自带了完善的监控系统,使得运维成本大大降低。 "}, {"url": "https://pingcap.com/cases-cn/user-case-funyours-japan/", "title": "TiDB 在株式会社 FUNYOURS JAPAN 的应用", "content": " 作者:张明塘,FUNYOURS JAPAN 运营系统工程師 背景 株式会社 FUNYOURS JAPAN 自 2014 在日本成立以来,营运多款颇受好评的页游跟手游,如:剣戟のソティラス、九十九姬 等,对于营运游戏来说,能够了解游戏中的玩家在做什么,喜欢的偏好是什么,关卡的设计是否平衡,都是相当重要的,所以随着营运时间的增长,资料库数据在亿笔以上也是寻常的。所以我们的技术单位也一直不断在评估市面上的各种资料库以及如何改进目前现有系统与架构,近年来最热门的资料库系统可以说是 NoSQL 了,不论 MongoDB,Cassandra,Redis,HBase 等等都占有一片天,具有读写快速,容易扩展等特性。经过初步了解后,采用 NoSQL 方式,需要对于目前的资料储存架构整个重新设计,并且需要配合采用的该套 NoSQL 资料库进行业务改造设计,那么该采用哪一套 NoSQL 资料库又是一个需要慎重考虑的课题。先回过头来看当前最需要处理改进的项目:1. 储存空间扩展不易2. 单台资料库效能有限初期方案 在处理储存空间不足的部分,一开始我们先采用了 MySQL innoDB 提供的压缩表格格式,对于需要时常读写更新的部分使用了 8K page size,过往的日志部分采用 4K page size,效果非常令人满意,释放了大量的储存空间,并且对于效能来说没有造成可察觉的影响。这部分网路上的测试比较多,就不在此多做说明。但是很快的压缩表格节省的空间毕竟是有限的,接下来只能增加 volume 容量以及将没有需要更新的过往日志移动到其他资料库上,虽然造成维护工作跟时间的繁复与负担,但是问题解决了。基于 MySQL 资料库架构单台的性能限制上,我们采用了多组的资料库伺服器,来满足所需的效能。当然不同组之间资料是不共通的,也就是无法直接使用 SQL 来做跨组间的操作,需要额外的程式来作业。而当然为了大量的资料存取上的效能,分表分库对表格进行 partition 这些作业都少不了。初识 TiDB 使用 NoSQL 式资料库看似可以完美的提供出一个解法,但需要付出的成本也是高昂的。于是我们把眼光落到了 MySQL Cluster 上,这时看到了 Google 发布 Cloud Spanner beta 的新闻,NewSQL?这是什么? 很快的引起了我们浓厚的兴趣,然后经过多方调研,我们发现了 TiDB:一个开源在 GitHub 上的 NewSQL 资料库。官方也持续不断发布了很多相关的文章,随着对 TiDB 的认识,认为对于目前现况是很合适的最佳化方案,相容于 MySQL,高可用性,容易水平扩展。在可行性评估与测试的时候,一开始采用了 TiKV 3 台搭配 PD 3 台,TiDB 2 台混搭 PD 的架构,使用了文件建议的 ansible 安装,这时遇到两个困难,第一个是在 ansible 检查机器效能的时候会因为硬碟读写效能而无法安装。由于是使用云端机器,所以对硬体方面没有太大的弹性,只好自己手动去修改脚本才能顺利安装。第二个也是在 ansible 里面会检查 ntp 同步服务是否启动,但是 centos7 预设的时间同步服务是 chrony,所以也是修改了脚本(后来的版本有提供 flag 能切换,也有自动安装 ntp 的选项),总之是顺利安装了。这时因为 PingCAP 才刚发布了 ansible 安装的方式,所以文件对于水平扩展部分,如新增 TiKV、 PD 、TiDB 机器,或者移除机器,官方 doc 没有详细说明,于是就写了封 mail 联系 PingCAP,发完信出去吃午餐回来,官方已经回复并且邀请加入 wechat,提供更即时的沟通跟支援,实在是很令人惊艳。备份与还原的机制,TiDB 在这部分提供了一个性能比官方的 mysqldump 更快的方案- mydumper/loader,这里我们一开始使用 GitHub 上的 source 自己 build,但是有点问题,跟官方交流后,才知道原来 tidb-enterprise-tools 这个工具包里面已经有提供了。mydumper 能够使用正则表达式去挑选出想要的 database 跟 table 备份,对于原本架构就会分库分表的设计,更添加了不少方便,备份出来的档案会全部放在一个资料夹内,使用 loader 就可以将这个备份的资料再次进入 DB。但是采用 TiDB 不就是为了使用同一张表的便利性吗?当巨量的数据都在同一个表内的时候,虽然 mydumper/loader 的效能很好,由于必需全量备份的关系,还是需要一点时间,因为 TiDB 也支援 mysqldump,所以如果需要进行增量备份,也可以采用 mysqldump 搭配 where 条件式来进行。因为需要对于不同的服务进行权限的管制,所以也一并测试了 TiDB 的帐号权限机制,那时还是 pre-GA 版本,根据文件上赋予模糊匹配会无法获得权限,必须要完全匹配才能正常取得;另外是在使用 revoke 回收权限方面会没有正确收回权限。但是在回报 PingCAP 后,很快的在 GA 版中就修复了。上线 TiDB 初期上线采用了 4 core cpu、记忆体 32 GB 作为 TiKV,8 core cpu、记忆体 16 GB 作为 TiDB/PD,3 台 TiKV、3 台 PD 、2 台 TiDB 跟 PD 混搭的方式。透过 prometheus 观察,发现 loading 都集中在同一台 TiKV 上,且 loadaverage 在高峰期间会冲到 7 以上,初步判断可能是规格不够,于是决定将 TiKV 都提升到 16 core 、24 GB 记忆体。因为线上正在举办活动,所以不希望停机,采用先增加三台 TiKV 机器同步后,再移除三台原本 TiKV 的方式进行,也特别感谢 PingCAP 在置换机器中间,一直在线上支援,过程中很平顺的完成了切换机器。机器提高规格后,高峰期的 loadaverage 下降到 4,但是还是会集中在其中某一台 TiKV 上不会分散到三台,在 PingCAP 的协助分析下,判断出可能是业务行为中的 select count(1) 这个 SQL 太过频繁,涉及该业务数据一直存取在同 1 个 region,通过尝试在文件上的提高开发度的方式,还是无法解决(最新的 v1.1 版有在对 count(*) 进行最佳化),最后结合数据特性对业务行为进行了变更,loadavg 几乎都是保持在 1 以下。比较原本架构与 TiDB 架构,原本架构上是采用多组 DB 的方式来让使用者分布在不同组 DB 上面,来达到所需的效能。但是当其中某几组负荷较大时,其他组 DB 并无法协助分担负荷。采用 TiDB 的架构后,在机器的使用上更有效率,并且在使用后台查找分析资料时,原本的架构下,只要时间一拉长到一个月以上,就会对该组 DB 的效能造成影响,而在 TiDB 的架构下,并不会有这样的问题。现在运营上最直接的效益就是硬体成本的节约,原架构下每一组 DB 规格都必须符合尖峰期间的运作。但是在 TiDB 的架构下,将全部的机器整合成一体后,只要全部机器加总起来的效能能够达到尖峰期间即可,在监控上搭配 Prometheus/Grafana 的视觉化系统与能弹性的自订规则的警示,也免去了原本使用 snmp 自建监视系统的成本。另外由于降低了撰写程式的复杂度,当运营企划人员提出新的想得知的分析资料时,能够更快的从资料库中取出,而可以有更多的时间来应对与分析使用者偏好。未来计划 目前正在评估 TiSpark 的使用,未来计划将后台分析资料部份,改采用 TiSpark。因为 TiSpark 可以直接操作 TiKV,也能够应用 Spark 提供的许多现成的函式库来对收集到的 log 做数据分析。预期利用 Spark 的机器学习来初步判断系统内的每个功能是否正常运作,并提出警示,例如当使用者的登入频率异常时等,来协助人工监控游戏运行状态。"}, {"url": "https://pingcap.com/cases-cn/user-case-360/", "title": "TiDB 在 360 金融贷款实时风控场景应用", "content": " 作者:金中浩,360 金融 / 数据智能部 / 部门总监 背景 近几年来基于互联网渠道的现金贷业务发展十分迅猛,无论是新兴的互联网企业还是传统的金融机构,都想在这个领域快速占领市场,攫取客户。然而在线贷款业务与其他互联网业务有着明显的不同,源自金融的基因决定了重视风险的必要性,这不仅关系到产品的收益,也直接影响了产品是否可以成功。将业务推到线上意味着无法准确的获取客户信息,只能通过有限的渠道验证客户的真实性和偿还能力,极大的增加了风险成本。如果申请步骤过于繁琐则降低了用户体验,不利于产品的推广和客户的使用。因此对于互联网贷款风控的一项挑战就是能够在尽可能短的时间内,有限数据的情况下,给出明确的风险判断。应用 建立风险策略的过程中,使用各种风险变量以及相关的衍生变量,通过专家模型进行评分,是一种较为典型的方法。实际应用中,我们发现除了已经被广泛使用的消费行为数据,基本收入数据等,基于特定维度的用户间社交关系也是比较有效的模型变量。在使用这些变量的过程中,我们面临最直接的问题是数据量。如果考虑将用户手机通讯录中出现的电话号码作为一项关系关联的形式,假设每位用户通讯录中联系人的个数平均为 100 个,那 100 万个注册用户就有对应大约 1 亿个联系人。事实上,在系统上线大约 1 年不到的时间内,我们几张存储社交关系的表已经达到了大约 50 亿左右的规模。相对于数据存储,变量的衍生加工和查询匹配是个更加有挑战性的工作。一个人的社交关系是个很典型的「图」数据结构。而很多专家模型中的规则是需要匹配某个用户 3 层以上关系的,最简单的就是匹配用户通过联系人关系,跃进 3 层后,命中系统黑名单的人数。我们还是按照平均 100 个联系人来估算,跃进 3 层后,需要匹配的关联人数为 100 * 100 * 100,即 100 万。而类似计算量的规则不在少数,需要调用这些计算规则的业务场景也较为频繁,同时对响应时间的要求也高。V1.0 版本的解决方案 在评估阶段,我们考虑了几种方案,各有利弊。首先被淘汰的是使用 MySQL 的解决方案。使用关系型数据库的优势是在查询方面的便捷性。在开发效率上,SQL 是开发人员和数据分析人员的必备技能,能够较快的在功能上实现需求。但是在数据存储和计算层面,MySQL 的表现则差强人意。在面对大数据量时,MySQL 能采取的水平扩展策略无非是分库分表,这样的后果就是查询逻辑变的非常复杂,不易维护,且性能下降的较为严重。另一个方案是把 HBase 作为数据存储的解决方案。它的优点很明显,可以水平扩展,数据量不再是瓶颈。但是它的缺点也同样明显,即对开发人员不友好,查询的 API 功能性较差,只能通过 key 来获取单条数据,或是通过 scan API 来批量读取。更关键的是 HBase 对图这样的数据结构支持的不好,只能通过使用 tall table 和存储冗余数据的形式来模拟。第三个方案是使用纯粹的图数据库。首先我们考察了开源的 Titan,发现这个项目已经废弃了,主力团队貌似研发了一个新的商业图数据库,并成立了公司。而且 Titan 的存储引擎也是使用了 HBase 和 Cassandra(根据需求两者选一),性能并不能满足我们的要求。接着我们考察了两款开源的商业产品 Neo4j 和 OrientDB。他们两者都提供了免费的社区版本,当然在功能上比商业版少了些。其中 Neo4j 的社区版不支持 HA,只能在单机上运行。而 OrientDB 的数据版支持 HA 和 Sharding。在编程接口上两者都支持各种主流的编程语言。Neo4j 提供了自家独创的,基于模式匹配的查询语言 cypher。OrientDB 则提供了类 SQL 的语法 API,可谓各有所长。最终上线的方案是混用了 HBase 和 Neo4j 两种存储。HBase 使用了 9 台 32G 内存,16 核的服务器集群,主要负责存储业务对象的基本信息,和第一层的关联信息。Neo4j 则负责图数据结构的存储,使用了单台 256G 内存 2T SSD 的服务器。上线后,相关实时分析接口的 TPS 大约为 300,90% 的相应时间保持在 200ms。部分表的数据量保持在 3000 万 ~ 6 亿的规模,部分核心表大约在 30 亿左右。V2.0 版本 - 引入 TiDB 和优化 系统上线后总体较为稳定,但是仍然存在一些亟需解决的问题。Neo4j 作为存储图数据的系统,在社区版本的功能上只能支持单节点,无法进行水平扩展,虽然现阶段来看无论是性能上还是功能上都可以满足业务的需求,但是可以预见在不久的将来就会有瓶颈。而 HBase 的缺点在于数据结构过于简单,无法给 OLAP 的系统和分析人员提供易用的数据接口,只能通过批量的 ETL 来同步数据至数据仓库,实时性较弱。在和 PingCAP 的技术团队进行交流后,了解到了 TiDB 这个由国人自己研发的分布式数据库。TiDB 中吸引我们的特点有很多,其中能帮助我们解决现有问题的主要集中于两点。其一是能够在近似无限的水平扩展的同时保证事务特性。这样不仅避免了分库,分表的繁琐,也让海量数据能够以关系模型进行存储变为可能。其次是能够高度兼容 MySQL 协议,不仅为开发人员及数据人员都提供了良好的应用接口。基于这些特性,我们发现线上 OLTP 和 OLAP 的界限已经非常模糊,在某些业务场景已经可以完全的融为一体。与 PingCAP 的技术同事沟通后,我们很快的设计了一套新的技术方案(V2.0)。为了考虑到技术方案迁移的稳定性,我们先使用 Kafka 作为一条数据旁路,将所有的基础数据在 TiDB 集群中存储一份。同时将 Neo4j 中大约 70 亿个 vertex 和相关 edge 数据迁出,移入 TiDB 中存储。然后我们基于关系模型的 SQL 接口实现了功能所需的部分图算法,包括最短路径,多节点连通性检查等。虽然在实现过程中要比使用 Neo4j 工作量多一些,但是在性能上,特别是吞吐量上有不少提升。原先 Neo4j 的事务模型较为笨重,在更新 vertex 时较多,且并发量大的时候很容易造成长时间的事务锁,严重降低系统的吞吐性能。最终上线时,我们为 TiDB 部署了 9 台服务器的集群。其中 3 台作为 PD 和 TiDB 的服务器,6 台作为 TiKV 的存储服务器。在运行一段时间后,除了一些业务逻辑上的 bug,表现一直很稳定,从未出过一次问题。而且随着业务量的增大,TPS 指标 也提升至 5000 左右,整个数据库平台的峰值计算能力提升了10 倍左右,但是平台整体的吞吐量和响应时间都没有特别的抖动,一直稳定在可接受范围内。对于风险分析人员,最大的提升就是就是可以用他们熟悉的 SQL 工具直接连接生产的 TiDB 库进行分析工作。不仅实时性大大增加,工作效率得到了质的提升,也省却了部分 ETL 的工作。Next Step 在对 TiDB 有了实际的认识和应用经验后,我们计划使用 TiDB 来取代 HBase,存储用户风险模型的相关数据。同时尝试在 TiDB 中慢慢迁入 Neo4j 的数据,最终回到关系模型的架构下,只是我们手中不再是日渐老去的 MySQL,而是新一代的分布式数据库 TiDB。"}, {"url": "https://pingcap.com/cases-cn/user-case-fengchao/", "title": "TiDB at 丰巢:尝鲜分布式数据库", "content": " 随着丰巢业务系统快速增长,其核心系统的数据量,早就跨越了亿级别,而且每年增量仍然在飞速发展。整个核心系统随着数据量的压力增长,不但系统架构复杂度急剧增长,数据架构更加复杂,传统的单节点数据库,已经日渐不能满足丰巢的需求,当单表数量上亿的时候,Oracle 还能勉强抗住,而 MySQL 到单表千万级别的时候就难以支撑,需要进行分表分库。为此,一款高性能的分布式数据库,日渐成为刚需。思考 在互联网公司业务量增大之后,并行扩展是最常用、最简单、最实时的手段。例如负载均衡设备拆流量,让海量流量变成每个机器可以承受的少量流量,并且通过集群等方式支撑起来整个业务。于是当数据库扛不住的时候也进行拆分。但有状态数据和无状态数据不同,当数据进行拆分的时候,会发生数据分区,而整个系统又要高可用状态下进行,于是数据的一致性变成了牺牲品,大量的核对工具在系统之间跑着保证着最终的一致性。在业务上,可能业务同学经常会遇到分过库的同学说,这个需求做不了,那个需求做不了,如果有 sql 经验的业务同学可能会有疑问不就是一条 sql 的事情么,其实这就是分库分表后遗症。为此,我们需要有个数据库帮我们解决以上问题,它的特性应该是: 数据强一致:支持完整的 ACID; 不分表分库:无论多少数据我们只管插入不需要关心啥时候扩容,会不会有瓶颈; 数据高可用:当我们某台数据库的少部分机器磁盘或者其他挂了的时候,我们业务上可以无感知,甚至某个城市机房发生灾难的时候还可以持续提供服务,数据不丢失; 复杂 SQL 功能:基本上单库的 SQL,都可以在这个数据库上运行,不需要修改或者些许修改; 高性能:在满足高 QPS 的同时,保证比较低的延时。 选型 根据以上期望进行分析,我们分析了目前市面上存在的 NewSQL 分布式数据库,列表如下:在综合考虑了开源协议,成熟度,可控度,性能,服务支撑等综合因素之后,我们选择了 TiDB,它主要优势如下: 高度兼容 MySQL 大多数情况下,无需修改代码即可从 MySQL 轻松迁移至 TiDB,分库分表后的 MySQL 集群亦可通过 TiDB 工具进行实时迁移。  水平弹性扩展 通过简单地增加新节点即可实现 TiDB 的水平扩展,按需扩展吞吐或存储,轻松松应对高并发、海量数据场景。 分布式事务  TiDB 100% 支持标准的 ACID 事务。 金融级别的高可用性 相比于传统主从(M-S)复制方案,基于 Raft 的多数派选举协议可以提供金融级的 100% 数据强一致性保证,且在不丢失大多数副本的前提下,可以实现故障的自动恢复(auto-failover),无需人工介入。基于如上的原因,我们选择了 TiDB,作为丰巢的核心系统的分布式数据库,来取代 Oracle 和 MySQL。评估 1. 性能测试 TiDB 的基准测试,使用的工具是 sysbanch 进行测试,使用了 8 张基础数据为一千万的表,分别测试了 insert,select,oltp 和 delete 脚本得到数据如下,查询的 QPS 达到了惊人的 14 万每秒,而插入也稳定在 1 万 4 每秒。核心服务器配置:测试结果:通过~2. 功能测试 通过~接入 因为是核心系统,安全起见,我们采取了多种方案保证验证项目接入的可靠性,保证不影响业务。1. 项目选择 在寻找第一个接入项目的时候,我们以下面 4 个特征,进行了选择:最终,我们选择了推送服务。因为推送服务是丰巢用来发送取件通知的核心服务,量非常大,但逻辑简单,而且有备选外部推送方案,所以即便万一出现问题,而不会影响用户。2. 代码修改 因为 TiDB 是完全兼容 MySQL 语法的,所以在这个项目的接入过程中,我们对代码的修改是很细微的。SQL 基本零改动,主要是外围代码,包括: 异步接口修改,数据异步化入库 同步接口修改,实现异常熔断 停止内嵌数据迁移代码 以上三点,保证了整个系统在不强依赖于数据库,并且能在高并发的情况下通过异步落库保护数据库不被压垮,并且在数据库发生问题的时候,核心业务可以正常进行下去。效果 1. 查询能力 接入 TiDB 之后,原先按照时间维度来拆分的十几个分表,变成了一张大表。最明显的变化,是在大数据量下,数据查询能力有了显著的提升。2. 监控能力 TiDB 拥有很完善的监控平台,可以直观的看到容量,以及节点状态:还能了解每个节点负载和 sql 执行的延时:当然还能了解所在机器上的位置,CPU 内存等负载情况:网络状态也能清晰的监控到:所有这些能让团队能分析出来有问题的 sql,以及数据库本身的问题。小结 TiDB 的接入过程,整体还是非常顺利的,由于之前做了很多接入的保障工作,当天切换流量到 TiDB 的过程只用了 10 分钟的时间,在此也要感谢 TiDB 对于 MySQL 语法的兼容性的支持,以及 PingCAP 提供的各种有用的工具。到目前为止,系统的稳定运行了一个多月,很好的满足了丰巢的业务需求。TiDB 的改造完成之后,丰巢推送服务对大部分消息进行了落地和查询,截止目前为止,推送服务最大的日落地量已经达到了 5 千万,而如果现在推送服务还使用的还是 MySQL 的方案,就需要上各种的分库分表方案,很多细致的业务就无法或者难以开展。此次 TiDB 的改造,只是丰巢对于分布式数据技术探索的一小步,未来丰巢会将更多的分布式技术,引入到更多的业务系统,打造更加极致的产品和服务。"}, {"url": "https://pingcap.com/cases-cn/user-case-shopee/", "title": "TiDB 助力东南亚领先电商 Shopee 业务升级", "content": " 作者介绍:刘春辉,Shopee DBA;洪超,Shopee DBA。 一、业务场景 Shopee 是东南亚和台湾地区领先的电子商务平台,覆盖新加坡、马来西亚、菲律宾、印度尼西亚、泰国、越南和台湾等七个市场。Shopee 母公司 Sea 为首家在纽约证券交易所上市的东南亚互联网企业。2015 年底上线以来,Shopee 业务规模迅速扩张,逐步成长为区域内发展最为迅猛的电商平台之一: 截止 2018 年第三季度 Shopee APP 总下载量达到 1.95 亿次,平台卖家数量超过 700 万。 2018 年第一季度和第二季度 GMV 分别为 19 亿美金和 22 亿美金,2018 上半年的 GMV 已达到 2017 全年水平。2018 年第三季度 GMV 达到了创纪录的 27 亿美元, 较 2017 年同期年增长率为 153%。 2018 年双 11 促销日,Shopee 单日订单超过 1100 万,是 2017 年双 11 的 4.5 倍;刚刚过去的双 12 促销日再创新高,实现单日 1200 万订单。 图 1 Shopee 电商平台展示图我们从 2018 年初开始调研 TiDB,6 月份上线了第一个 TiDB 集群。到目前为止我们已经有两个集群、60 多个节点在线运行,主要用于以下 Shopee 业务领域: 风控系统:风控日志数据库是我们最早上线的一个 TiDB 集群,稍后详细展开。 审计日志系统:审计日志数据库存储每一个电商订单的支付和物流等状态变化日志。 本文将重点展开风控日志数据库选型和上线的过程,后面也会约略提及上线后系统扩容和性能监控状况。二、选型:MySQL 分库分表 vs TiDB 图 2 风控日志收集和处理示意图风控系统基于大量历史订单以及用户行为日志,以实时和离线两种方式识别平台上的异常行为和欺诈交易。它的重要数据源之一是各种用户行为日志数据。最初我们将其存储于 MySQL 数据库,并按照 USER_ID 把数据均分为 100 个表。随着 Shopee 用户活跃度见长,数据体积开始疯长,到 2017 年底磁盘空间显得十分捉襟见肘了。作为应急措施,我们启用了 InnoDB 表透明压缩将数据体积减半;同时,我们把 MySQL 服务器磁盘空间从 2.5TB 升级到了 6TB。这两个措施为后续迁移 MySQL 数据到 TiDB 多争取了几个月时间。关于水平扩容的实现方案,当时内部有两种意见:MySQL 分库分表和直接采用 TiDB。1. MySQL 分库分表 基本思路:按照 USER_ID 重新均分数据(Re-sharding),从现有的 100 个表增加到1000 个甚至 10000 个表,然后将其分散到若干组 MySQL 数据库。 优点:继续使用 MySQL 数据库 ,不论开发团队还是 DBA 团队都驾轻就熟。 缺点:业务代码复杂度高。Shopee 内部若干个系统都在使用该数据库,同时我们还在使用 Golang 和 Python 两种编程语言,每一个系统都要改动代码以支持新的分库分表规则。 2. 直接采用 TiDB 基本思路:把数据从 MySQL 搬迁至 TiDB,把 100 个表合并为一个表。 优点:数据库结构和业务逻辑都得以大幅简化。TiDB 会自动实现数据分片,无须客户端手动分表;支持弹性水平扩容,数据量变大之后可以通过添加新的 TiKV 节点实现水平扩展。理想状况下,我们可以把 TiDB 当做一个「无限大的 MySQL」来用,这一点对我们极具诱惑力。 缺点:TiDB 作为新组件首次引入 Shopee 线上系统,我们要做好「踩坑」的准备。 最后,我们决定采用 TiDB 方案,在 Shopee 内部做「第一个吃螃蟹的人」。风控日志数据库以服务离线系统为主,只有少许在线查询;这个特点使得它适合作为第一个迁移到 TiDB 的数据库。三、上线:先双写,后切换 我们的上线步骤大致如下: 应用程序开启双写:日志数据会同时写入 MySQL 和 TiDB。 搬迁旧数据:把旧数据从 MySQL 搬到 TiDB,并完成校验确保新旧数据一致。 迁移只读流量:应用程序把只读流量从 MySQL 逐步迁移至 TiDB(如图 3 所示)。 停止双写:迁移过程至此结束。 图 3 迁移过程图:保持双写,逐步从读 MySQL 改为读 TiDB双写方式使得我们可以把整个切换过程拖长至几个月时间。这期间开发团队和 DBA 团队有机会逐步熟悉新的 TiDB 集群,并充分对比新旧数据库的表现。理论上,在双写停掉之前,若新的 TiDB 集群遭遇短时间内无法修复的问题,则应用程序有可能快速回退到 MySQL。除此之外,采用双写方式也让我们有了重构数据库设计的机会。这一次我们就借机按照用户所属地区把风控日志数据分别存入了七个不同的逻辑数据库:rc_sg,rc_my,rc_ph,…,rc_tw。Shopee 用户分布于七个不同地区。迁移到 TiDB 之前,所有日志数据共存于同一个逻辑数据库。按照地区分别存储使得我们能够更为方便地为每个地区的日志定制不同的数据结构。四、硬件配置和水平扩容 上线之初我们一共从 MySQL 迁移了大约 4TB 数据到 TiDB 上。当时 TiDB 由 14 个节点构成,包括 3 个 PD 节点,3 个 SQL 节点和 8 个 TiKV 节点。服务器硬件配置如下: TiKV 节点 CPU: 2 * Intel® Xeon® CPU E5-2640 v4 @ 2.40GHz, 40 cores 内存: 192GB 磁盘: 4 * 960GB Read Intensive SAS SSD Raid 5 网卡: 2 * 10gbps NIC Bonding PD 节点和 SQL 节点 CPU: 2 * Intel® Xeon® CPU E5-2640 v4 @ 2.40GHz, 40 cores 内存: 64GB 磁盘: 2 * 960GB Read Intensive SAS SSD Raid 1 网卡: 2 * 10gbps NIC Bonding 截至目前,系统已经平稳运行了六个多月,数据量增长至 35TB(如图 4 所示),经历了两次扩容后现在集群共包含 42 个节点。图 4 风控日志 TiDB 数据库存储容量和使用状况性能 图 5 风控日志 TiDB 数据库 QPS Total 曲线风控日志数据库的日常 QPS(如图 5 所示)一般低于每秒 20K,在最近的双 12 促销日我们看到峰值一度攀升到了每秒 100K 以上。尽管数据量较之 6 个月前涨了 8 倍,目前整个集群的查询响应质量仍然良好,大部分时间 pct99 响应时间(如图 6 所示)都小于 60ms。对于以大型复杂 SQL 查询为主的风控系统而言,这个级别的响应时间已经足够好了。图 6 风控日志 TiDB 数据库两天 pct99 查询响应时间曲线五、问题和对策 TiDB 的字符串匹配区分大小写(Case Sensitive)。目前尚不支持 Case Insensitive 方式。应用程序做了适配以实现 Case Insensitive 方式的字符串匹配。 TiDB 对于 MySQL 用户授权 SQL 语法的兼容支持尚不完善。例如,目前不支持 SHOW CREATE USER 语法,有时候不得不读取系统表(mysql.user)来查看一个数据库账户的基本信息。 添加 TiKV 节点后需要较长时间才能完成数据再平衡。据我们观察,1TB 数据大约需要 24 个小时才能完成拷贝。因此促销前我们会提前几天扩容和观察数据平衡状况。 TiDB v1.x 版本以 region 数目为准在各个 TiKV 节点之间平衡数据。不过每个 region 的大小其实不太一致。这个问题导致不同 TiKV 节点的磁盘空间使用率存在明显差异。据说新的 TiDB v2.x 对此已经做了优化,我们未来会尝试在线验证一下。 TiDB v1.x 版本需要定期手动执行 Analyze Table 以确保元信息准确。PingCAP 的同学告诉我们说:当 (Modify_count / Row_count) 大于 0.3 就要手动 Analyze Table 了。v2.x 版本已经支持自动更新元数据了。我们后续会考虑升级到新版本。 mysql> show stats_meta where db_name = 'aaa_db' G *************************** 1. row *************************** Db_name: aaa_db Table_name: xxx_tab Update_time: 2018-12-16 23:49:02 Modify_count: 166545248 Row_count: 8568560708 1 row in set (0.00 sec) 六、未来规划 过去一年亲密接触之下,我们对 TiDB 的未来充满信心,相信 TiDB 会成为 Shopee 数据库未来实现弹性水平扩容和分布式事务的关键组件。当前我们正在努力让更多 Shopee 业务使用 TiDB。我们规划把 Shopee 数据从 MySQL 迁移到 TiDB 上的路线是「先 Non-transactional Data(非交易型数据),后 Transactional Data(交易型数据)」。目前线上运行的集群都属于 Non-transactional Data,他们的特点是数据量超大(TB 级别),写入过程中基本不牵涉数据库事务。接下来我们会探索如何把一些 Transactional Data 迁移到 TiDB 上。MySQL Replica 是另一个工作重点。MySQL Replica 指的是把 TiDB 作为 MySQL 的从库,实现从 MySQL 到 TiDB 实时复制数据。我们最近把订单数据从 MySQL 实时复制到 TiDB。后续来自 BI 系统以及部分对数据实时性要求不那么高的只读查询就可以尝试改为从 TiDB 读取数据了。这一类查询的特点是全表扫描或者扫描整个索引的现象较多,跑在 TiDB 可能比 MySQL 更快。当然,BI 系统也可以借助 TiSpark 绕过 SQL 层直接读取 TiKV 以提升性能。目前我们基于物理机运行 TiDB 集群,DBA 日常要耗费不少精力去照顾这些服务器的硬件、网络和 OS。我们有计划把 TiDB 搬到 Shopee 内部的容器平台上,并构建一套工具实现自助式资源申请和配置管理,以期把 DBA 从日常运维的琐碎中解放出来。七、致谢 感谢 PingCAP 的同学一年来对我们的帮助和支持。每一次我们在微信群里提问,都能快速获得回应。官方架构师同学还不辞辛劳定期和我们跟进,详细了解项目进度和难点,总是能给出非常棒的建议。PingCAP 的文档非常棒,结构层次完整清晰,细节翔实,英文文档也非常扎实。一路跟着读下来,受益良多。TiDB 选择了 Golang 和 RocksDB,并坚信 SSD 会在数据库领域取代传统机械硬盘。这些也是 Shopee 技术团队的共识。过去几年间我们陆续把这些技术引入了公司的技术栈,在一线做开发和运维的同学相信都能真切体会到它们为 Shopee 带来的改变。"}, {"url": "https://pingcap.com/cases-cn/user-case-yiguo/", "title": "TiDB / TiSpark 在易果集团实时数仓中的创新实践", "content": " 作者简介:罗瑞星,曾就职于前程无忧,参加过 Elasticsearch 官方文档中文翻译工作,现就职于易果集团,担任资深大数据工程师,负责易果集团数据分析架构设计等工作。 项目背景 目前企业大多数的数据分析场景的解决方案底层都是围绕 Hadoop 大数据生态展开的,常见的如 HDFS + Hive + Spark + Presto + Kylin,在易果集团,我们初期也是采取这种思路,但是随着业务规模的快速增长和需求的不断变化,一些实时或者准实时的需求变得越来越多,这类业务除了有实时的 OLTP 需求,还伴随着一些有一定复杂度的 OLAP 的需求,单纯地使用 Hadoop 已经无法满足需求。现有的准实时系统运行在 SQL Server 之上,通过开发人员编写和维护相应的存储过程来实现。由于数据量不大,SQL Server 能够满足需求,但是随着业务的发展,数据量随之增长,SQL Server 越来越不能满足需求,当数据量到达一定的阶段,性能便会出现拐点。这个时候,这套方案已完全无法支撑业务,不得不重新设计新的方案。选型评估 在评估初期,Greenplum、Kudu、TiDB 都进入了我们的视野,对于新的实时系统,我们有主要考虑点: 首先,系统既要满足 OLAP 还要满足 OLTP 的基本需求; 其次,新系统要尽量降低业务的使用要求; 最后,新系统最好能够与现有的 Hadoop 体系相结合。 Greenplum 是一套基于 PostgreSQL 分析为主的 MPP 引擎,大多用在并发度不高的离线分析场景,但在 OLTP 方面,我们的初步测试发现其对比 TiDB 的性能差很多。再说说 Kudu。Kudu 是 CDH 2015年发布的一套介于 Hbase 和 HDFS 中间的一套存储系统,目前在国内主要是小米公司应用的较多,在测试中,我们发现其在 OLTP 表现大致与 TiDB 相当,但是一些中等数据量下,其分析性能相比 TiDB 有一定差距。另外我们的查询目前主要以 Presto 为主,Presto 对接 Kudu 和 PostgreSQL 都是需要考虑兼容性的问题,而 TiDB 兼容 MySQL 协议,在应用初期可以直接使用 Presto-MySQL 进行统一查询,下一步再考虑专门开发 Presto-TiDB。另外,我们希望未来的实时系统和离线系统能够通用,一套代码在两个系统中都能够完全兼容,目前 Tispark 和 SparkSQL 已经很大程度上实现了这点,这支持我们在以后离线上的小时级任务可以直接切换到 TiDB上,在 TiDB 上实现实时业务的同时,如果有 T+1 的需求也能够直接指 HDFS 即可,不用二次开发,这是 Kudu 和 GP 暂时实现不了的。最后,TiSpark 是建立在 Spark 引擎之上,Spark 在机器学习领域里有诸如 Mllib 等诸多成熟的项目,对比 GP 和 Kudu,算法工程师们使用 TiSpark 去操作 TiDB 的门槛非常低,同时也会大大提升算法工程师们的效率。经过综合的考虑,我们最终决定使用 TiDB 作为新的实时系统。同时,目前 TiDB 的社区活跃度非常好,这也是我们考虑的一个很重要的方面。TiDB 简介 在这里介绍一下 TiDB 的相关特性:TiDB 是基于 Google Spanner/F1 论文启发开源的一套 NewSQL 数据库,它具备如下 NewSQL 核心特性: SQL支持 (TiDB 是 MySQL 兼容的) 水平线性弹性扩展 分布式事务 数据强一致性保证 故障自恢复的高可用 同时,TiDB 还有一套丰富的生态工具,例如:快速部署的 TiDB-Ansible、无缝迁移 MySQL 的 Syncer、异构数据迁移工具 Wormhole、以及 TiDB-Binlog、Backup & Recovery 等。SQL Server 迁移到 TiDB 由于我们公司的架构是 .NET + SQL Server 架构,所以我们无法像大多数公司一样去使用 MySQL Binlog 去做数据同步,当然也就无法使用 TiDB 官方提供的 Syncer 工具了。因此我们采用了 Flume + Kafka 的架构,我们自己开发了基于 Flume 的 SQL Server Source 去实时监控 SQL Server 数据变化,进行捕捉并写入 Kafka 中,同时,我们使用 Spark Streaming 去读取 Kafka 中的数据并写入 TiDB,同时我们将之前 SQL Server 的存储过程改造成定时调度的 MySQL 脚本。TiDB 前期测试 在测试初期,我们采用 TiDB 的版本为 RC4,在测试过程中曾经在同时对一张表进行读写时,出现 Region is stale 的错误,在 GitHub 上提出 Issue 后,TiDB 官方很快在 Pre-GA 版本中进行了修复。在测试环境,我们是手动通过二进制包的形式来部署 TiDB ,虽然比较简单,但是当 TiDB 发布 GA 版本之后,版本升级却是一个比较大的问题,由于早期没有使用 TiDB-ansible 安装,官方制作的升级脚本无法使用,而手动进行滚动升级等操作非常麻烦。由于当时是测试环境,在听取了 TiDB 官方的建议之后,我们重新利用 TiDB 官方提供的 TiDB-ansible 部署了 TiDB 的 GA 版本。只需要下载官方提供的包,修改相应的配置,就能完成安装和部署。官方也提供了升级脚本,能够在相邻的 TiDB 版本之前完成无缝滚动升级。同时 TiDB-ansible 默认会提供 Prometheus + Grafana 的监控安装,官方提供了非常丰富完善的 Grafana 模板,省去了运维很多监控配置的工作量,借着 TiDB 部署监控的契机,我们也完成了诸如 Redis,RabbitMQ,Elasticsearch 等很多应用程序的监控由 Zabbix 往 Prometheus 的迁移。这里需要注意的是,如果是用官方提供的部署工具部署 Prometheus 和 Grafana,在执行官方的停止脚本时切记跳过相应的组件,以免干扰其他程序的监控。TiDB 上线过程 在 10 月中旬,随着新机器的采购到位,我们正式将 TiDB 部署到生产环境进行测试,整个架构为 3 台机器,3TiKV+3PD+2TiDB 的架构。在生产环境中的大数据量场景下,遇到了一些新的问题。首先遇到的问题是 OLTP 方面,Spark Streaming 程序设置的 5 秒一个窗口,当 5 秒之内不能处理完当前批次的数据,就会产生延迟,同时 Streaming 在这个批次结束后会马上启动下一个批次,但是随着时间的积累,延迟的数据就会越来越多,最后甚至延迟了 8 小时之久;另一方面,由于我们使用的是机械硬盘,因此写入的效率十分不稳定,这也是造成写入延迟的一个很主要的因素。出现问题之后我们立即与 TiDB 官方取得联系,确认 TiDB 整体架构主要基于 SSD 存储性能之上进行设计的。我们将 3 台机器的硬盘都换成了 SSD;与此同时,我们的工程师也开发了相应的同步程序来替代 Spark Streaming,随着硬件的更新以及程序的替换,写入方面逐渐稳定,程序运行的方式也和 Streaming 程序类似,多程序同时指定一个 Kafka 的 Group ID,同时连接不同机器的 TiDB 以达到写入效率最大化,同时也实现了 HA,保证了即使一个进程挂掉也不影响整体数据的写入。在 OLTP 优化结束之后,随之而来的是分析方面的需求。由于我们对 TiDB 的定位是实时数据仓库,这样就会像 Hadoop 一样存在很多 ETL 的流程,在 Hadoop 的流程中,以 T+1 为主的任务占据了绝大多数,而这些任务普遍在凌晨启动执行,因此只能用于对时间延迟比较大的场景,对实时性要求比较高的场景则不适合,而 TiDB 则能很好的满足实时或者准实时的需求,在我们的业务场景下,很多任务以 5-10 分钟为执行周期,因此,必须确保任务的执行时长在间隔周期内完成。我们取了两个在 SQL Server 上跑的比较慢的重要脚本做了迁移,相比于 SQL Server/MySQL 迁移至 Hadoop,从 SQL Server 迁移至 TiDB 的改动非常小,SQL Server 的 Merge 操作在 TiDB 里也通过 replace into 能够完成,其余一些 SQL Server 的特性,也能够通过 TiDB 的多行事务得以实现,在这一方面,TiDB 的 GA 版本已经做的非常完善,高度兼容 MySQL,因此迁移的成本非常小,从而使我们能够将大部分精力放在了调优方面。在脚本迁移完毕之后,一些简单的脚本能够在秒级完成达到了我们的预期。但是一些复杂的脚本的表现在初期并没表现出优势,一些脚本与 SQL Server 持平甚至更慢,其中最大的脚本 SQL 代码量一共 1000 多行,涉及将近 20 张中间表。在之前的 SQL Server 上,随着数据量慢慢增大,每天的执行时长逐渐由 1-2 分钟增长到 5-6 分钟甚至更久,在双11当天凌晨,随着单量的涌入和其他任务的干扰延迟到 20 分钟甚至以上。在迁移至 TiDB 初期,在半天的数据量下 TiDB 的执行时长大致为 15 分钟左右,与 SQL Server 大致相同,但是并不能满足我们的预期。我们参考了 TiDB 的相关文档对查询参数做了一些调优,几个重要参数为:tidb_distsql_scan_concurrency,tidb_index_serial_scan_concurrency,tidb_index_join_batch_size(TiDB 提供了很好的并行计算能力)。经过验证,调整参数后,一些 SQL 能够缩短一倍的执行时间,但这里依旧不能完全满足我们的需求。引入 TiSpark 随后,我们把目光转向了 TiDB 的一个子项目 TiSpark,用官网的介绍来讲 TiSpark 就是借助 Spark 平台,同时融合 TiKV 分布式集群的优势,和 TiDB 一起解决 HTAP 的需求。TiDB-ansible 中也带有 TiSpark 的配置,由于我们已经拥有了 Spark 集群,所以直接在现有的 Spark 集群中集成了 TiSpark。虽然该项目开发不久,但是经过测试,收益非常明显。TiSpark 的配置非常简单,只需要把 TiSprak 相关的 jar 包放入 Spark 集群中的 jars 文件夹中就能引入 TiSpark,同时官方也提供了 3 个脚本,其中两个是启动和停止 TiSpark 的 Thrift Server,另一个是提供的 TiSpark 的 cli 客户端,这样我们就能像使用 Hive 一样使用 TiSpark 去做查询。在初步使用之后,我们发现一些诸如 select count(*) from table 等 SQL 相比于 TiDB 有非常明显的提升,一些简单的 OLAP 的查询基本上都能够在 5 秒之内返回结果。经过初步测试,大致在 OLAP 的结论如下:一些简单的查询 SQL,在数据量百万级左右,TiDB 的执行效率可能会比 TiSpark 更好,在数据量增多之后 TiSpark 的执行效率会超过 TiDB,当然这也看 TiKV 的配置、表结构等。在 TiSpark 的使用过程中,我们发现 TiSpark 的查询结果在百万级时,执行时间都非常稳定,而 TiDB 的查询时间则会随着数据量的增长而增长(经过与 TiDB 官方沟通,这个情况主要是因为没有比较好的索引进行数据筛选)。针对我们的订单表做测试,在数据量为近百万级时,TiDB 的执行时间为 2 秒左右,TiSpark 的执行时间为 7 秒;当数据量增长为近千万级时,TiDB 的执行时间大致为 12 秒(不考虑缓存),TiSpark 依旧为 7 秒,非常稳定。因此,我们决定将一些复杂的 ETL 脚本用 TiSpark 来实现,对上述的复杂脚本进行分析后,我们发现,大多数脚本中间表很多,在 SQL Server 中是通过 SQL Server 内存表实现,而迁移至 TiDB,每张中间表都要删除和插入落地,这些开销大大增加了执行时长(据官方答复 TiDB 很快也会支持 View、内存表)。在有了 TiSpark 之后,我们便利用 TiSpark 将中间表缓存为 Spark 的内存表,只需要将最后的数据落地回 TiDB,再执行 Merge 操作即可,这样省掉了很多中间数据的落地,大大节省了很多脚本执行的时间。在查询速度解决之后,我们发现脚本中会有很多针对中间表 update 和 delete 的语句。目前 TiSpark 暂时不支持 update 和 delete 的操作(和 TiSpark 作者沟通,后续会考虑支持这两个操作),我们便尝试了两种方案,一部分执行类似于 Hive,采用 insert into 一张新表的方式来解决;另外一部分,我们引入了 Spark 中的 Snappydata 作为一部分内存表存储,在 Snappydata 中进行 update 和 delete,以达到想要的目的。因为都是 Spark 的项目,因此在融合两个项目的时候还是比较轻松的。最后,关于实时的调度工具,目前我们是和离线调度一起进行调度,这也带来了一些问题,每次脚本都会初始化一些 Spark 参数等,这也相当耗时。在未来,我们打算采用 Spark Streaming 作为调度工具,每次执行完成之后记录时间戳,Spark Streaming 只需监控时间戳变化即可,能够避免多次初始化的耗时,通过 Spark 监控,我们也能够清楚的看到任务的延迟和一些状态,这一部分将在未来进行测试。TiDB 官方支持 在迁移过程中,我们得到了 TiDB 官方很好的支持,其中也包括 TiSpark 相关的技术负责人,一些 TiSpark 的 Corner Case 及使用问题,我们都会在群里抛出,TiDB 的官方人员会非常及时的帮助我们解决问题,在官方支持下,我们迁移至 TiSpark 的过程很顺利,没有受到什么太大的技术阻碍。实时数仓 TiDB / TiSpark 在迁移完成之后,其中一条复杂的 SQL,一共 Join 了 12 张表(最大表数量亿级,部分表百万级),在平时小批量的情况下,执行时间会在 5 分钟左右,我们也拿了双 11 全量的数据进行了测试,执行时间在 9 分钟以上,而采用了 TiSpark 的方式去执行,双 11 全量的数据也仅仅花了 1 分钟,性能提升了 9 倍。整个大脚本在 SQL Server 上运行双 11 的全量数据以前至少要消耗 30 分钟,利用 TiDB 去执行大致需要 20 分钟左右,利用 TiSpark 只需要 8 分钟左右,相对 SQL Server 性能提升 4 倍,也就是说,每年数据量最高峰的处理能力达到了分钟级,很好的满足了我们的需求。最后,不管是用 TiDB 还是用 TiSpark 都会有一部分中间表以及与原表进行 Merge 的操作,这里由于 TiDB 对事务进行的限制,我们也采用以万条为单批次进行批量的插入和 Merge,既避免了超过事务的报错又符合 TiDB 的设计理念,能够达到最佳实践。有了 TiSpark 这个项目,TiDB 与 Hadoop 的生态体系得到进一步的融合,在没有 TiSpark 之前,我们的系统设计如下:可以发现,实时数仓与 T+1 异步数仓是两个相对独立的系统,并没有任何交集,我们需要进行数据实时的同步,同时也会在夜晚做一次异步同步,不管是 Datax 还是 Sqoop 读取关系型数据库的效率都远远达不到 TiSpark 的速度,而在有了 TiSpark 之后,我们可以对 T+1 异步数仓进行整合,于是我们的架构进化为如下:这样就能够利用 TiSpark 将 TiDB 和 Hadoop 很好的串联起来,互为补充,TiDB 的功能也由单纯的实时数仓变成能够提供如下几个功能混合数据库: 实时数仓,上游 OLTP 的数据通过 TiDB 实时写入,下游 OLAP 的业务通过 TiDB / TiSpark 实时分析。 T+1 的抽取能够从 TiDB 中利用 TiSpark 进行抽取。 TiSpark 速度远远超过 Datax 和 Sqoop 读取关系型数据库的速度; 抽取工具也不用维护多个系统库,只需要维护一个 TiDB 即可,大大方便了业务的统一使用,还节省了多次维护成本。 TiDB 天然分布式的设计也保证了系统的稳定、高可用。 TiDB 分布式特性可以很好的平衡热点数据,可以用它作为业务库热点数据的一个备份库,或者直接迁入 TiDB 。 上面这三点也是我们今后去努力的方向,由此可见,TiSpark 不仅对于 ETL 脚本起到了很重要的作用,在我们今后的架构中也起到了举足轻重的作用,为我们创建一个实时的统一的混合数据库提供了可能。与此同时,我们也得到 TiDB 官方人员的确认,TiDB 将于近期支持视图、分区表,并会持续增强 SQL 优化器,同时也会提供一款名为 TiDB Wormhole 的异构平台数据实时迁移工具来便捷的支持用户的多元化迁移需求。我们也计划将更多的产品线逐步迁入 TiDB。总结 同时解决 OLAP 和 OLTP 是一件相当困难的事情,TiDB 和 TiSpark 虽然推出不久,但是已经满足很多应用场景,同时在易用性和技术支持上也非常值得称赞,相信 TiDB 一定能够在越来越多的企业中得到广泛应用。"}, {"url": "https://pingcap.com/cases-cn/user-case-lianghuapai/", "title": "TiDB 在量化派风控系统中的应用", "content": " 作者:朱劲松,量化派研发中心系统架构师,主要参与了基础组件开发、API Gateway 等项目,现在致力于公司风控系统相关业务的架构设计和研发。 一、公司简介 量化派(QuantGroup)创办于 2014 年,是数据驱动的科技公司,是国家高新技术企业。量化派以「MOVE THE WORLD WITH DATA, ENLIGHTEN LIFE WITH AI」(数据驱动世界,智能点亮生活)为愿景,利用人工智能、机器学习、大数据技术。为金融、电商、旅游、出行、汽车供应链等多个领域的合作伙伴提供定制化的策略和模型,帮助提升行业效率。量化派已与国内外超过 300 家机构和公司达成深度合作,致力于打造更加有活力的共赢生态,推动经济的可持续发展。我司从 2017 年年中开始调研 TiDB,并在用户行为数据分析系统中搭建 TiDB 集群进行数据存储,经过一年多的应用和研究,积累了丰富的经验。同时,TiDB 官方推出 2.0 GA 版本,TiDB 愈发成熟,稳定性和查询效率等方面都有很大提升。我们于 2018 年 7 月部署 TiDB 2.0.5 版本,尝试将其应用于风控业务中。风控系统主要是在用户申请放款时,根据风控规则结合模型和用户特征进行实时计算并返回放款结果。二、业务背景 风控系统中用到的数据主要可以分为两部分: 一类是原始数据,用于分析用户当前的特征指标。 一类是快照数据,用于计算历史指定时间点的特征指标,供模型训练使用。 原始数据主要分为三种: 产生自公司内各个产品线的业务系统数据。 爬虫组提供的用户联系人、运营商、消费记录等数据。 经过处理后的用户特征数据。 由于我们的风控策略中用到了大量的模型,包括神经网络模型,评分模型等,这些模型的训练需要依靠大量的历史订单以及相关的用户特征,为了训练出更多精准、优秀的模型,就需要更多维度的特征,此时特征的准确性就直接影响了模型的训练结果,为此我们在回溯每一个订单的用户在指定时间的特征表现时,就需要用到数据快照。我们可以通过拉链表的方式来实现数据快照功能,简单说就是在每张表中增加三个字段,分别是new_id、start_time、end_time,每一次记录的更新都会产生一条新的数据,同时变更原有记录的end_time,以记录数据的变更历史。通过上面的介绍可以看到,业务数据和爬虫数据本身数据量就很大,再加上需要产生对应的拉链数据,数据量更是成倍增长。假设每条数据自创建后仅变更一次,那拉链表的数据量就已经是原始表的两倍了,而实际生产环境下数据的变更远不止一次。通过上述的介绍,我们总结风控系统下的数据存储需求应满足以下几点: 业务数据。 业务数据拉链表。 爬虫数据,如联系人信息、运营商数据,消费记录等。 爬虫数据拉链表。 其他数据,如预处理数据等。 三、当前方案 以前方案主要是采用 HBase 进行数据存储。它的水平扩展很好的解决了数据量大的问题。但是在实际使用中,也存在着比较明显的问题,最明显的就是查询的 API 功能性较弱,只能通过 Key 来获取单条数据,或是通过 Scan API 来批量读取,这无疑在特征回溯时增加了额外的开发成本,无法实现代码复用。在实时计算场景中,为了降低开发成本,对于业务数据的获取则是通过访问线上系统的 MySQL 从库来进行查询;爬虫数据由于统一存放在 HBase 中,计算时需要将用到的数据全量拉取在内存中再进行计算。在回溯场景中,针对业务特征回溯,通过查询订单时间之前的数据进行特征计算,这种方式对于已经变更的数据是无能为力的,只能通过 HBase 里的数据快照来实现,但无形增加了很多的开发工作。3.1 TiDB 为我们打开一片新视野 通过上面的介绍,我们知道要构建一个风控系统的实时数仓环境,需要满足下面几个特性: 高可用,提供健壮、稳定的服务。 支持水平弹性扩展,满足日益增长的数据需求。 性能好,支持高并发。 响应快。 支持标准 SQL,最好是 MySQL 语法和 MySQL 协议,避免回溯时的额外开发。 可以发现,TiDB 完美契合我们的每个需求。经过 TiDB 在用户行为数据分析系统中的长期使用,我们已经积累了一定的经验,在此过程中 TiDB 官方也给予了长期的技术支持,遇到的问题在沟通时也能够及时的反馈,而且还与我司技术人员进行过多次技术交流及线下分享,在此我们深表感谢。伴随着风控系统需求的持续增长,我们对整体架构进行了新一轮的优化,新的数据接入及存储架构如图 1。图 1 优化后的架构图通过图 1 可以看到,线上业务系统产生的数据统一存放在 MySQL 中,将这些孤立的数据归集在 TiDB 中,能够提供基于 SQL 的查询服务。通过 binlog 的方式直接从 MySQL 实例进行接入,接入后的数据以两种不同的形式分别存放: 一种是去分库分表后的源数据,降低了实时特征计算的实现及维护成本。 另一种是以拉链数据形式存储实现数据快照功能。 经过调研,针对第一种场景,可以通过阿里的 otter 或者 TiDB 周边工具 Syncer 来快速实现,但对于第二个需求都没有现成的成熟解决方案。最终,我们基于阿里的 canal 进行客户端的定制化开发,分别按照不同的需求拼装合并 SQL 并写入到不同的 TiDB 集群中;同时还可以按需将部分表的数据进行组装并发送至 Kafka,用于准实时分析场景。对于来自爬虫组的数据,我们采用直接消费 Kafka 的方式组装 SQL 写入到 TiDB 即可。在实际是使用中,通过索引等优化,TiDB 完全可以支持线上实时查询的业务需求;在特征回溯时只需要通过增加查询条件就可以获得指定时间的特征结果,大大降低了开发成本。3.2 遇到的问题 风控业务中用户特征提取的 SQL 相对都比较复杂,在实际使用中,存在部分 SQL 执行时间比在 MySQL 中耗时高。通过 explain 我们发现,他并没有使用我们创建的索引,而是进行了全表扫描,在进一步分析后还发现 explain 的结果是不确定的。经过与 TiDB 官方技术人员的沟通,我们进行了删除类似索引、analyze table 等操作,发现问题仍然存在。通过图 2 可以看到完全相同的 SQL 语句,其执行结果的差异性。最后按官方建议,我们采用添加 use index 的方式使其强制走索引,执行时间由 4 分钟变成了 < 1s,暂时解决了业务上的需求。图 2 explain 示意图同时 TiDB 技术人员也收集相关信息反馈给了研发人员。在整个问题的处理过程中,TiDB 的技术人员给予了高度的配合和及时的反馈,同时也表现出了很强的专业性,大大减少了问题排查的时间,我们非常感谢。四、展望 目前我们已经搭建两个 TiDB 集群,几十个物理节点,百亿级特征数据,受益于 TiDB 的高可用构架,上线以来一直稳定运行。如上,TiDB 在我们风控业务中的应用才只是开始,部分业务的迁移还有待进一步验证,但是 TiDB 给我们带来的好处不言而喻,为我们在数据存储和数据分析上打开了一片新视野。后续我们会继续加大对 TiDB 的投入,使其更好地服务于在线分析和离线分析等各个场景。我们也希望进一步增加与 PingCAP 团队的交流与合作,进行更深入的应用和研究,为 TiDB 的发展贡献一份力量。"}, {"url": "https://pingcap.com/cases-cn/user-case-zhuanzhuan-2/", "title": "TiDB 在转转的业务实战", "content": " 作者:陈维,转转优品技术部 RD。 开篇 世界级的开源分布式数据库 TiDB 自 2016 年 12 月正式发布第一个版本以来,业内诸多公司逐步引入使用,并取得广泛认可。对于互联网公司,数据存储的重要性不言而喻。在 NewSQL 数据库出现之前,一般采用单机数据库(比如 MySQL)作为存储,随着数据量的增加,“分库分表”是早晚面临的问题,即使有诸如 MyCat、ShardingJDBC 等优秀的中间件,“分库分表”还是给 RD 和 DBA 带来较高的成本;NewSQL 数据库出现后,由于它不仅有 NoSQL 对海量数据的管理存储能力、还支持传统关系数据库的 ACID 和 SQL,所以对业务开发来说,存储问题已经变得更加简单友好,进而可以更专注于业务本身。而 TiDB,正是 NewSQL 的一个杰出代表!站在业务开发的视角,TiDB 最吸引人的几大特性是: 支持 MySQL 协议(开发接入成本低); 100% 支持事务(数据一致性实现简单、可靠); 无限水平拓展(不必考虑分库分表)。 基于这几大特性,TiDB 在业务开发中是值得推广和实践的,但是,它毕竟不是传统的关系型数据库,以致我们对关系型数据库的一些使用经验和积累,在 TiDB 中是存在差异的,现主要阐述“事务”和“查询”两方面的差异。TiDB 事务和 MySQL 事务的差异 MySQL 事务和 TiDB 事务对比 在 TiDB 中执行的事务 b,返回影响条数是 1(认为已经修改成功),但是提交后查询,status 却不是事务 b 修改的值,而是事务 a 修改的值。可见,MySQL 事务和 TiDB 事务存在这样的差异:MySQL 事务中,可以通过影响条数,作为写入(或修改)是否成功的依据;而在 TiDB 中,这却是不可行的!作为开发者我们需要考虑下面的问题: 同步 RPC 调用中,如果需要严格依赖影响条数以确认返回值,那将如何是好? 多表操作中,如果需要严格依赖某个主表数据更新结果,作为是否更新(或写入)其他表的判断依据,那又将如何是好? 原因分析及解决方案 对于 MySQL,当更新某条记录时,会先获取该记录对应的行级锁(排他锁),获取成功则进行后续的事务操作,获取失败则阻塞等待。对于 TiDB,使用 Percolator 事务模型:可以理解为乐观锁实现,事务开启、事务中都不会加锁,而是在提交时才加锁。参见 这篇文章(TiDB 事务算法)。其简要流程如下:在事务提交的 PreWrite 阶段,当“锁检查”失败时:如果开启冲突重试,事务提交将会进行重试;如果未开启冲突重试,将会抛出写入冲突异常。可见,对于 MySQL,由于在写入操作时加上了排他锁,变相将并行事务从逻辑上串行化;而对于 TiDB,属于乐观锁模型,在事务提交时才加锁,并使用事务开启时获取的“全局时间戳”作为“锁检查”的依据。所以,在业务层面避免 TiDB 事务差异的本质在于避免锁冲突,即,当前事务执行时,不产生别的事务时间戳(无其他事务并行)。处理方式为事务串行化。TiDB 事务串行化 在业务层,可以借助分布式锁,实现串行化处理,如下:基于 Spring 和分布式锁的事务管理器拓展 在 Spring 生态下,spring-tx 中定义了统一的事务管理器接口:PlatformTransactionManager,其中有获取事务(getTransaction)、提交(commit)、回滚(rollback)三个基本方法;使用装饰器模式,事务串行化组件可做如下设计:其中,关键点有: 超时时间:为避免死锁,锁必须有超时时间;为避免锁超时导致事务并行,事务必须有超时时间,而且锁超时时间必须大于事务超时时间(时间差最好在秒级)。 加锁时机:TiDB 中“锁检查”的依据是事务开启时获取的“全局时间戳”,所以加锁时机必须在事务开启前。 事务模板接口设计 隐藏复杂的事务重写逻辑,暴露简单友好的 API:TiDB 查询和 MySQL 的差异 在 TiDB 使用过程中,偶尔会有这样的情况,某几个字段建立了索引,但是查询过程还是很慢,甚至不经过索引检索。索引混淆型(举例) 表结构:CREATE TABLE `t_test` ( `id` bigint(20) NOT NULL DEFAULT '0' COMMENT '主键id', `a` int(11) NOT NULL DEFAULT '0' COMMENT 'a', `b` int(11) NOT NULL DEFAULT '0' COMMENT 'b', `c` int(11) NOT NULL DEFAULT '0' COMMENT 'c', PRIMARY KEY (`id`), KEY `idx_a_b` (`a`,`b`), KEY `idx_c` (`c`) ) ENGINE=InnoDB; 查询:如果需要查询 (a=1 且 b=1)或 c=2 的数据,在 MySQL 中,sql 可以写为:SELECT id from t_test where (a=1 and b=1) or (c=2);,MySQL 做查询优化时,会检索到 idx_a_b 和 idx_c 两个索引;但是在 TiDB(v2.0.8-9)中,这个 sql 会成为一个慢 SQL,需要改写为:SELECT id from t_test where (a=1 and b=1) UNION SELECT id from t_test where (c=2); 小结:导致该问题的原因,可以理解为 TiDB 的 sql 解析还有优化空间。冷热数据型(举例) 表结构:CREATE TABLE `t_job_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id', `job_code` varchar(255) NOT NULL DEFAULT '' COMMENT '任务code', `record_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '记录id', `status` tinyint(3) NOT NULL DEFAULT '0' COMMENT '执行状态:0 待处理', `execute_time` bigint(20) NOT NULL DEFAULT '0' COMMENT '执行时间(毫秒)', PRIMARY KEY (`id`), KEY `idx_status_execute_time` (`status`,`execute_time`), KEY `idx_record_id` (`record_id`) ) ENGINE=InnoDB COMMENT='异步任务job' 数据说明:a. 冷数据,status=1 的数据(已经处理过的数据);b. 热数据,status=0 并且 execute_time<= 当前时间 的数据。慢查询:对于热数据,数据量一般不大,但是查询频度很高,假设当前(毫秒级)时间为:1546361579646,则在 MySQL 中,查询 sql 为:SELECT * FROM t_job_record where status=0 and execute_time<= 1546361579646 这个在 MySQL 中很高效的查询,在 TiDB 中虽然也可从索引检索,但其耗时却不尽人意(百万级数据量,耗时百毫秒级)。原因分析:在 TiDB 中,底层索引结构为 LSM-Tree,如下图:当从内存级的 C0 层查询不到数据时,会逐层扫描硬盘中各层;且 merge 操作为异步操作,索引数据更新会存在一定的延迟,可能存在无效索引。由于逐层扫描和异步 merge,使得查询效率较低。优化方式:尽可能缩小过滤范围,比如结合异步 job 获取记录频率,在保证不遗漏数据的前提下,合理设置 execute_time 筛选区间,例如 1 小时,sql 改写为:SELECT * FROM t_job_record where status=0 and execute_time>1546357979646 and execute_time<= 1546361579646 优化效果:耗时 10 毫秒级别(以下)。关于查询的启发 在基于 TiDB 的业务开发中,先摒弃传统关系型数据库带来的对 sql 先入为主的理解或经验,谨慎设计每一个 sql,如 DBA 所提倡:设计 sql 时务必关注执行计划,必要时请教 DBA。和 MySQL 相比,TiDB 的底层存储和结构决定了其特殊性和差异性;但是,TiDB 支持 MySQL 协议,它们也存在一些共同之处,比如在 TiDB 中使用“预编译”和“批处理”,同样可以获得一定的性能提升。服务端预编译 在 MySQL 中,可以使用 PREPARE stmt_name FROM preparable_stm 对 sql 语句进行预编译,然后使用 EXECUTE stmt_name [USING @var_name [, @var_name] ...] 执行预编译语句。如此,同一 sql 的多次操作,可以获得比常规 sql 更高的性能。mysql-jdbc 源码中,实现了标准的 Statement 和 PreparedStatement 的同时,还有一个ServerPreparedStatement 实现,ServerPreparedStatement 属于PreparedStatement的拓展,三者对比如下:容易发现,PreparedStatement 和 Statement 的区别主要区别在于参数处理,而对于发送数据包,调用服务端的处理逻辑是一样(或类似)的;经测试,二者速度相当。其实,PreparedStatement 并不是服务端预处理的;ServerPreparedStatement 才是真正的服务端预处理,速度也较 PreparedStatement 快;其使用场景一般是:频繁的数据库访问,sql 数量有限(有缓存淘汰策略,使用不宜会导致两次 IO)。批处理 对于多条数据写入,常用 sql 为 insert … values (…),(…);而对于多条数据更新,亦可以使用 update … case … when… then… end 来减少 IO 次数。但它们都有一个特点,数据条数越多,sql 越加复杂,sql 解析成本也更高,耗时增长可能高于线性增长。而批处理,可以复用一条简单 sql,实现批量数据的写入或更新,为系统带来更低、更稳定的耗时。对于批处理,作为客户端,java.sql.Statement 主要定义了两个接口方法,addBatch 和 executeBatch 来支持批处理。批处理的简要流程说明如下:经业务中实践,使用批处理方式的写入(或更新),比常规 insert … values(…),(…)(或 update … case … when… then… end)性能更稳定,耗时也更低。"}, {"url": "https://pingcap.com/cases-cn/user-case-tongcheng/", "title": "支撑百亿级应用的 NewSQL——TiDB 在同程旅游的应用", "content": " 作者:瞿锴,同程网资深 DBA。 项目背景 初次接触 TiDB,是通过同程网首席架构师王晓波先生的分享,当时同程网正在使开发和数据库全面往开源方向转型,由于业务需要,很多在线业务数据量和访问量都非常的大,而 MySQL 无法满足大数据量下的复杂查询需求,为了使数据库分片对开发透明,同程自研了 DBrouter。但分片后的合并、实时汇总统计及全量数据的监控仍然是困扰我们的一个难点。一直没有特别好的办法解决。急速增长的业务 2016 年国庆前,同程的票务项目(微信九宫格中的火车票、机票等票务业务背后是同程在提供)由于流量激增,订单库压力越来越大,同时相关业务需求也在增加,开发不断的在订单库上新增各种查询,例如为了及时定位异常而增加的限定各类条件的分钟级订单量监控(每分钟执行根据不同的条件进行汇总的订单量)。这样的功能越来越多,同时订单库总大小数 T 左右。对此,公司内部决定将票务订单库进行分片来降低单库压力,应对即将到来的国庆高峰订单爆发。引入 TiDB 经过评估,发现公司自研的分片可以满足绝大多数的查询需求,但是部分复杂条件的查询将会影响整个分片集群的性能,少量的全片扫描 SQL 经常会占用 80% 以上的 IO 资源,导致其他的查询性能下降。这时,刚好我们的首席架构师提议,使用 TiDB 试试,经过中间件组和 DBA 组的配合测试,我们尝试将 TiDB 作为所有数据的集合库提供复杂查询,分片集群则提供简单查询,同时由于 TiDB 高度兼容 MySQL 的连接协议,我们基于 PingCAP 提供的数据同步工具 Syncer 进行了二次开发,可以自定义库名和表名(后来同 TiDB 工程师交流,他们最新的 Wormhole & Syncer 也都已经支持了自定义选项),同时新增了同步状态监控,如 TPS、延迟等,如果出现异常,会通过微信告警。从 MySQL 将数据实时同步到 TiDB 来确保数据的一致。确定方案后,我们连夜安排压测同事和开发同事协作,紧急测试,发现这套分片集群 + TiDB 的方案能够满足我们的功能和性能方面的需求,于是迅速调整了该项目的架构,我们将数千个 MySQL 分片汇总到一个 TiDB 集群,保障了 2016 年国庆的高峰平稳渡过。当时的流量达到了我们平时流量的 2 倍,然而并没有出现异常。该实时同步查询系统架构如下所示:在该项目实施成功后,我们加深了对于 TiDB 的使用。并根据 PingCAP 的建议和协助部署了各类监控。同时,为了更好的关注数据库的情况,第一时间发现异常,我们将 TiDB 的异常报警接入了公司的监控系统和自愈系统。当发生异常的时候,监控系统会第一时间发现,然后自愈系统会依据提前制定的愈合逻辑处理对应异常,在第一时间恢复应用的可用。更大规模的使用 业务上线以后,我们很快又迁移了机票业务实时同步业务到 TiDB。至本文截稿时,在同程内部,目前共有数套 TiDB 集群,部署服务器数量近百台,总数据量数十 TB。其中最大的一个集群 10 多个数据节点,近十 TB 数据,数据量过百亿,支撑了每天过亿的访问,并提供千万级别的数据监控服务,平均 QPS 在 5000,高峰 QPS 过万。同时,由于 TiDB 的易用性(高度兼容 MySQL 协议和标准的 SQL 语法),我们目前已将 TiDB 作为一个很重要的数据库部署方案,在项目启动时就会考虑是否可以在初期就开始使用。在持续一年多的使用中,我们与 PingCAP 工程师一直保持着沟通和交流,互相之间也经常会进行一些技术和使用方面的沟通。目前最新版的 TiDB 我们也积极与 PingCAP 一起进行测试和问题反馈,他们也非常及时的给予我们反馈并很快的 fix 掉一些 BUG。展望 现在公司内部越来越多的开发在联系 DBA 咨询 TiDB 的信息,我们给他们的反馈就是:这是一个高度兼容 MySQL 协议和语法的数据库,非常简单易用,基本上看下相关文档就可以上手。你们在用的时候就可以当它就是一个 MySQL 来使用,只是它能存放的数据量远远超过 MySQL。而对于 DBA 来讲,这就是一个自带高可用和可动态扩容的数据库,对外是个 MySQL,对内是个分布式数据库。业务侧的开发人员基本没有学习成本,DBA 维护起来也和 MySQL 有很多相似点,系统生态非常好。可以预见,随着项目继续以及新项目建设,TiDB 的实例数和机器数又会继续以较快的速度增长,目前线上用的版本还不是最新的版本,正在做升级到 1.05 的准备工作。我们预计 2018 年底,TiDB 的集群数很快就会有 20 套,机器数数百台,这给开发和运维都带来了一定的挑战。如果我们仍然按照目前的方式建设和运维 TiDB 集群,可能就要面临增加相关人力的处境。我们一直在寻找多 TiDB 集群的便捷管理方案,这时一篇文章引起了我们的注意——《Cloud+TiDB 技术解读》。我们迅速和 TiDB 工程师取得联系,了解到 TiDB 最新的 DBaaS 方案基于 K8S 来自动管理和调度多个 TiDB 实例,这和我们目前大量 docker 化业务和数据库的战略方向是一致的。通过 TiDB-Operator 使可以自动化部署和管理 TiDB 及周边工具,自动化部署这些应用以及使后端获得故障转移能力,这样可以大大降低运维成本,同时提供丰富的接口方便后续对其进行扩展。我们计划 2018 年开始和 PingCAP 合作尝试引入 TiDB DBaaS 方案。另外,我们通过同 PingCAP 工程师的深度交流,了解到了 TiDB 的子项目 TiSpark,后续计划引入 TiSpark 来对数据进行实时分析、实时数仓等工作的尝试,让技术对业务产生更大的价值。"}, {"url": "https://pingcap.com/cases-cn/user-case-telaidian/", "title": "TiDB 在特来电的实践", "content": " 作者介绍:潘博存,特来电大数据技术研发部架构师,具有 10 多年平台软件设计开发经验,现专注于大数据领域快速读写方向。 背景介绍 特来电新能源有限公司是创业板第一股特锐德(300001)的全资子公司,主要从事新能源汽车充电网的建设、运营及互联网的增值服务。特来电颠覆了传统充电桩的模式,世界首创了电动汽车群智能充电系统,获得 336 项技术专利,以“无桩充电、无电插头、群管群控、模块结构、主动防护、柔性充电”的特点引领世界新能源汽车充电的发展,系统的鉴定结论为:“产品世界首创、技术水平国际领先。主动柔性充电对电池寿命可以延长 30% 左右,电池充电的安全性可以提升 100 倍以上。”特来电采用互联网思维,依靠国际领先的汽车群智能充电技术和系统,创新电动汽车充电商业模式,建设全国最大的汽车充电网,通过大系统卖电、大平台卖车、大共享租车、大数据修车、大支付金融、大客户电商,打造让客户满意、政府放心的中国最大汽车充电网生态公司,引领充电网、车联网、互联网“三网融合”的新能源互联网。为什么研究 TiDB 特来电大数据平台通过开源与自研相结合的方式,目前已经上线多套集群满足不同的业务需求。目前在大数据存储和计算方面主要使用了 HBase、Elasticsearch、Druid、Spark、Flink。大数据技术可谓是百花齐放、百家争鸣,不同的技术都有针对性的场景。结合实际情况,选择合适的技术不是一件容易的事情。随着接入大数据平台的核心业务的增加,我们在 OLAP 上主要遇到以下痛点问题: 随着基于大数据分析计算的深入应用,使用 SQL 进行分析的需求越来越旺盛,但目前已经上线的大数据集群(HBase、Elasticsearch、Druid、Spark、Flink)对 SQL 的支持度都比较弱。 目前进入大数据集群的数据主要以宽表方式进行,导致在数据归集和后期基础数据放生变化时应用成本较高。 数据仓库业务有些还是基于复杂的 T+1 模式的 ETL 过程,延时较高,不能实时的反映业务变化。 由于每个大数据集群主要针对特定的场景,数据重复存储的情况较多,这就造成了存储成本的增加,同时也会导致数据的不一致性。 目前进入 HDFS / Druid / ES 的数据,在历史数据更新时,成本较高,灵活性降低。 大数据技术发展迅速,我们也一直希望采用新的技术可以解决我们以上问题,我们关注到目前 NewSQL 技术已经有落地产品,并且不少企业在使用,所以决定在我们平台内尝试引入 NewSQL 技术解决我们的痛点问题。我们先了解一下 NewSQL。图 1 数据库发展史如图 1 所示,数据库的发展经历了 RDBMS、NoSQL 以及现在的 NewSQL,每种不同的技术都有对应的产品,每种数据库的技术背后,都有典型的理论支撑。2003 年 Google GFS 开创了分布式文件系统、2006 年的 BigTable 论文催生了 Hadoop 生态,在 2012 年的 Spanner 和 2013 年的 F1 论文发表后,被业界认为指明了未来关系型数据库的发展。随着大数据技术的发展,实际上 SQL 和 NoSQL 的界限逐渐模糊,比如现在 HBase 之上有 Phoenix,HiveSQL,SparkSQL 等,也有一些观点认为 NewSQL = SQL + NoSQL。不同的技术都有各自的最佳适应场景,Spanner 和 F1 被认为是第一个 NewSQL 在生产环境提供服务的分布式系统技术,基于该理念的开源产品主要为 CockroachDB、TiDB。结合社区活跃度以及相关案例、技术支持,我们决定 NewSQL 技术上引入 TiDB。TiDB 介绍 TiDB 是 PingCAP 公司受 Google Spanner / F1 论文启发而设计的开源分布式 HTAP 数据库,结合了传统的 RDBMS 和 NoSQL 的最佳特性。TiDB 兼容 MySQL,支持无限的水平扩展,具备强一致性和高可用性。图 2 TiDB 架构图TiDB 具有以下核心特性: 高度兼容 MySQL —— 无需修改代码即可从 MySQL 轻松迁移至 TiDB 水平弹性扩展 —— 轻松应对高并发、海量数据场景 分布式事务 —— TiDB 100% 支持标准的 ACID 事务 高可用 —— 基于 Raft 的多数派选举协议可以提供金融级的 100% 数据强一致性保证 一站式 HTAP 解决方案 —— 一份存储同时处理 OLTP & OLAP,无需传统繁琐的 ETL 过程 其中涉及到的分布式存储和分布式计算,大家可以参考 TiDB 的官方网站,在这里就不再进行论述。在处理大型复杂的计算时,PingCAP 结合上图说的 TiKV 以及目前大数据生态的 Spark,提供了另外一个开源产品 TiSpark。不得不说这是一个巧妙的设计,充分利用了现在企业已有的 Spark 集群的资源,不需要另外再新建集群。TiSpark 架构以及核心原理简单描述如下:图 3 TiSpark 架构图TiSpark 深度整合了 Spark Catalyst 引擎,可以对计算提供精确的控制,使 Spark 能够高效的读取 TiKV 中的数据,提供索引支持以实现高速的点查。通过多种计算下推减少 Spark SQL 需要处理的数据大小,以加速查询;利用 TiDB 的内建的统计信息选择更优的查询计划。从数据集群的角度看,TiSpark + TiDB 可以让用户无需进行脆弱和难以维护的 ETL,直接在同一个平台进行事务和分析两种工作,简化了系统架构和运维。除此之外,用户借助 TiSpark 项目可以在 TiDB 上使用 Spark 生态圈提供的多种工具进行数据处理。例如使用 TiSpark 进行数据分析和 ETL;使用 TiKV 作为机器学习的数据源;借助调度系统产生定时报表等等。目前的应用情况 由于很多用户已经部署了生产系统,我们没有在测试上再次投入比较大的精力,经过了简单的性能测试以后,搭建了我们的第一个 TiDB 集群,尝试在我们的业务上进行使用。目前主要用于我们的离线计算,以及部分即系查询场景,后续根据使用情况,逐渐调整我们的集群规模以及增加我们的线上应用。1. 目前的集群配置图 4 集群配置清单2. 规划的应用架构图 5 引入 TiDB 以后的应用架构图基于 TiDB 我们规划了完整的数据流处理逻辑,从数据接入到数据展现,由于 TiDB 高度兼容 MySQL,因此在数据源接入和 UI 展现就有很多成熟的工具可以使用,比如 Flume、Grafana、Saiku 等。3. 应用简介a. 充电功率的分时统计每个用户使用特来电的充电桩进行充电时,车辆的 BMS 数据、充电桩数据、环境温度等数据是实时的保存到大数据库中。我们基于采集的用户充电数据,需要按照一定的时间展示全国的充电功率 比如展示过去一天,全国的充电功率变化曲线,每隔 15 分钟或者 30 分钟进行一次汇总。随着我们业务规模的增加,此场景的计算也逐步进行了更新换代。图 6 充电功率的分时统计目前我们单表数据量接近 20 亿,每天的增量接近 800 万左右。使用 TiDB 后,在进行离线计算分析时,我们的业务逻辑转成了直接在我们的离线计算平台通过 SQL 的方式进行定义和维护,极大的提高了维护效率,同时计算速度也得到了大幅提升。b. 充电过程分析上面我们讲了,我们已经有了充电过程中的宝贵的海量数据,如何让数据发挥价值,我们基于充电数据进行充电过程的分析就是其中的一个方式,比如分析不同的车型在不同的环境(环境温度、电池特性)下,充电的最大电压和电流的变化情况,以及我们充电桩的需求功率满足度等。图 7 充电过程分析针对海量的历史数据计算我们使用了 TiSpark 进行计算,直接使用了我们现有的 Spark 集群,在使用 Spark 进行计算时,一开始由于不熟悉 TiSpark,分配的资源比较少,耗时多一些。后来和 TiDB 技术人员交流了解到最佳实践,提升配置和调整部分参数后,性能提升不少。这个场景中我们充分利用了 TiDB 和 TiSpark 进行协同工作,满足了我们的业务需求。总结及问题 1. 最佳应用场景结合我们的线上验证,我们认为使用 TiDB,主要有以下几个优势: SQL 支持度相对于现有的集群支持度较好,灵活性和功能性大大增强。 可以进行表之间的 join 运算,降低了构造宽边的复杂度以及因此带来的维护成本。 历史数据方便修改。 高度兼容 MySQL 生态下对应的成熟软件较多(开发工具、展现、数据接入)。 基于索引的 SQL 性能在离线计算上基本可以满足我们需求,在即席查询上最适合海量数据下进行多维度的精确查询,类似与“万里挑一”的场景。 使用 TiSpark 进行复杂的离线计算,充分利用了现有的集群,数据存储做到了一份,同时也降低了运维成本。 2. 目前的定位结合我们的实际现状,现阶段我们主要用于进行离线计算和部分即席查询的场景,后期随着应用的深入,我们逐步考虑增加更多的应用以及部分 OLTP 场景。"}, {"url": "https://pingcap.com/cases-cn/user-case-toutiao/", "title": "TiDB 在今日头条的实践", "content": " 本文整理自今日头条数据库中间件/分布式数据库负责人吴镝(知乎 ID:吴镝)在 TiDB DevCon2018 上的分享内容。 TiDB 主要应用在今日头条核心 OLTP 系统 - 对象存储系统中,存储其中一部分元数据,支持头条图片和视频相关业务,比如抖音等。如今(数据截至发文),TiDB 支撑着今日头条 OLTP 系统里 QPS 比较高的场景:集群容量约几十 T,日常 QPS 峰值会达到几十万。为什么我们需要用 TiDB 今日头条内部有一些业务数据量非常大,之前用的 MySQL 的单机盘是大概 2.8T 的 SSD 盘。我们做对象存储。因为头条不但做视频,还做图片,这些视频和图片当中基本上都是用我们自研的 S3 存储系统,这种存储系统需要一个元数据,比如一个图片存下来,它存在 S3 系统的哪个机器、哪个文件、哪个偏移里面的数据,还有比如一个大的视频,S3 会把它切成很多小的视频片段,每一个分片的位置,都会存在元数据里面。用 TiDB 之前,元数据是存在 MySQL 里的一个 2.8TB 的盘,因为增长的特别快,所以导致磁盘不够用,只能用分库分表的方案。我们以前用的的分库分表方案是 MyCAT。但用这个方案的过程中我们有遇到了一些问题,比如丢数据。某一个数据我 commit 了之后,最后发现这个数据丢了。再就是连接的问题,目前头条做分片是大概固定分 100 个片。如果你的业务是需要分库分表,那你这边搞 101 个分片,这样有些业务,他用了一个分片键,用分片键来做查询,那可能中间件只用一个连接就可以找到相关数据。但有些业务,确实有不带分片键的请求。会导致 select 语句过来的时候,下面会建 101 个对后端的连接,也就是说,因为有连接的限制,有一个没有带分片键的这种请求过来之后, MyCAT 可以启 101 个连接到后面的每一个 MySQL 库。那这样的话,有时候我给它 5 万个连接,他一下子就把一百万用掉了。这样会导致它在非分片键的 select 请求,它连接速度消耗非常快,经常在业务这边会抛出说,连接数不够。头条的数据库主要用的是 MySQL 和 MongoDB,相对比较单一,所我们也想多尝试一些其他的数据库。主要使用场景 目前,TiDB 主要在以下两个场景下使用: 首先是 OLTP 的场景,也就是大数据量的场景,我们不仅仅是考虑到延时,而是考虑到数据量单机装不下,需要扩展性; 还有 OLAP 场景,有些用户,他用的是 Hive 或者 Tableau,然后用的过程中发现,因为后面都是接 MySQL,做一些 OLAP 的方式查询就比较慢。后来公司在推广 TiDB,所以就接了一些 OLAP 的业务场景。 头条的自研对象存储系统元数据量非常大,而且增长非常快。以其中最大的一个集群举例:该集群有两种方式,一是分片信息最早是用 MySQL 的。如果想用 TiDB 的话,可能先得把 TiDB 做了 MySQL 的备,用 TiDB 提供的 syncer 来同步数据,有些读请求我们可以从 MySQL 上切到 TiDB 上来。我们用了一段时间,觉得 TiDB 其实挺稳定的。然后,公司会有这种需求,比如说突然接了一个元旦的活动,这个时候上传的图片就比较多,数据增长的就太大了,这种活动中 S3 系统压力比较大。我们 MySQL 的单盘基本上稳定的在 2.0TB 以上(盘总计 2.8TB),对此我们就只能删数据(一些很老的数据),跟业务部门沟通说,这个数据不要了,从 MySQL 的单盘里删掉,通过这种方式来支撑。但即使这么做,单盘还是扛不住现在数据增长的需求。然后当时就想干脆激进点,把一些写进来后立即就读、并且以后都不会读的一些流量切到 TiDB 里。因为 S3 存储分很多 bucket ,做活动的人就去新建一些 bucket, 这些 bucket 的元数据就直接存在 TiDB 里面,就不存 MySQL 了。这两个 case,就是目前在头条的 OLAP 和 OLTP 里数据流量最大、QPS 最大的一个场景。集群部署状态 关于部署,我们把 TiDB 和 PD 部在一起,都是 3 个。TiKV 我们一共是用了几十台的机器。CPU 是 40 个虚拟的 CPU,256G 的内存。目前平均值 QPS 在十几万,用了 3 个 TiDB,3 个 TiDB 总的连接数加起来大概 14K,然后 Latency 的 pct99 小于 60ms。这其实都属于挺高峰时期的数据了,做活动的时候 QPS 会达到几十万。与 MySQL 的延时对比 在使用 TiDB 过程中,我们也比较了一下 TiDB 和 MySQL 的延时:第一条线就是 MySQL 的延时,pct99 的,下面的黑线是 TiDB 的延时。可以看到,在 MySQL 的数据量非常大的情况下,TiDB 是明显 Latency 更优的,虽然说它用的机器会稍微多点。一些使用中的吐槽和经验 使用的过程中我们也碰到了一些槽点,这些槽点 TiDB 现在的版本已经基本都解决了。第一个就是直方图。大家知道基于 CBO 的这种优化器,肯定要用一些统计信息,TiDB 在之前的版本里对直方图的统计信息的更新没有做到很及时,导致我拿了一个 SQL 选执行计划的时候我会选错。比如说我可以选一个索引,但是实际上,因为这个更新信息不实时,所以它可能会做全表扫描。大家以后发现这种你可以用 explain 这个命令看执行计划,如果有这样的问题就可以用 analyze 这个命令,他可以把一张表统计信息给更新,更新之后再一次执行 SQL 语句,你会发现他的执行计划已经变了。第二个就是 raft leader。因为大家都知道,每个 region 是一个 raft ,TiDB 有一个监控指标,给出每个机器上有多少个 raft leader。当我们数据量跑到 10TB+,大概 20TB 的时候,会发现这个 raft leader 频繁掉线。掉线的原因主要是由于做 region 迁移的时候,比如你后边做迁移或者做负载均衡,会把 RocksDB 里面一个 range 的数据发到你要迁移的目标机器上面去。发过去了之后,目标端相当于要把 SST 文件 load 到 RocksDB 里,这个过程中,由于 RocksDB 实现的一个问题,导致把 SST 加到 RocksDB 的里面去的这个过程花费了大概 30 到 40 秒,正常情况下可能就毫秒级或者 1 秒。RocksDB 实现 ingest file 的时候,它打开了一些其实不需要打开的文件。因为 LevelDB、RocksDB 有很多层,把一个 file 给 ingest 进去的时候其实你要和一些 overlap 的数据做合并,因为它的实现问题,导致有一些没有必要去 touch 的 SST 它都会去 touch,会产生大量 IO 。因为我们数据量比较大, SST 就非常多,所以在数据量非常大的情况下就会踩到这个坑。然后,RocksDB ingest 一个文件时间过长,导致 Raft 的心跳就断了。因为 Raft 协议要维持你的 lease,你要发心跳包,这个时候心跳包都给堵在后面,因为前面 ingest file 时间太长了。然后 Raft leader 就掉,掉了以后很多读写请求就会有问题。第三个是大量的短链接。我们的业务使用数据库的时候,经常建了非常多短链接。因为大部分业务都是不大会使用数据库的,它也不知道要设置连接池,idle connection 这种东西。所以经常用完一个连接后就关掉。这种大量的短链接最后打到 TiDB,TiDB 连接建立了之后要去查一个 System 的变量,这些变量在 TiDB 里面是存在某几个 TiKV 实例里面的,那如果有大量短链接,这些短链接一上来,就会去查这些系统变量,刚好这些系统变量就聚在几台机器上面,导致说这几台机器就负载特别大。然后就会报警读请求堆积。TiKV 使用的是线程模型,请求过来之后,丢到队列里面去。然后线程再拿出来处理。现在 PingCAP 也在做优化,把这些 Cache 在 TiDB 这个进程里面。第四点,严格来说这不算是 TiKV 的问题,算是 prometheus 的客户端有问题。我们当时遇到这么一个情况:部署 prometheus 的这个机器宕掉了,重启之后,我们会发现很多 TiKV 的监控信息都没有上报。后来查的时候发现压根 TiKV 这台机器都没有到 prometheus 这台机器的连接。所以我们就觉得 prometheus 这边客户端实现有问题。第五个问题就是 Row id 的打散。这个问题正好是我们这边碰到的一个性能上的问题。因为 TiDB 存储数据是这么存的:我要插入一行数据,他会有两行,第一行是索引,索引是 Key ,然后 value 是 row id;第二行是 row id 是 Key,value 是整行的数据,相当于第二行有点像聚集索引这种东西。但是这个聚集索引的 Key 是 row id。原来的版本实现上是说这个 row id 是个递增了,所以这种就导致不管你插入什么数据,这个 row id 都是递增的,因为 row id 一递增,这些数据都会打到一个 TiKV 的一个 region 上面。因为我的 TiKV 是一个有序的 Map,所以说 row id 如果递增的话,肯定大家插入的时候都是打到一个 TiKV 上面。我们当时业务的压力比较大,导致客户发现他把这个业务的机器实例数给扩容上去之后,会发现这个 insert 的 TPS 大概也就在两万,一行大概就一百多个字节吧,你再怎么加他上不去了,也就是说 insert 的这个 QPS 上不去了。这一点 TiDB 新版本的方案就是,row id 不是单调递增,而是把 row id 打的很散,这种方案性能会比较好,没有热点。最后这个问题,因为 TiDB 这种事务模型,是需要拿一个事务版本,这个事务版本在 TiDB 里面是一个时间戳,并且这个时间戳是由 PD 这个组件来管理的。相当于每一个事务基本上连上来之后,它都要去访问 PD 这个组件拿时间戳。其实做 rpc 的时候拿时间戳延迟不会太长,也就是个位数毫秒级。但因为 TiDB 是 Go 写的,有调度开销。从 PD 拿回来一堆时间戳的 goroutine 把这堆时间戳发放给执行事务的一堆 goroutine 很慢,在链接数和压力都比较大的时候,大概有 30 毫秒左右的延时。可能调 rpc 的时候也就大概需要 1 毫秒,不到 2 毫秒。但由于 Go 的开销,能把这个延时翻几倍。以上这些讲的都是 TiDB 在头条做 OLTP 的场景下,碰到的一些主要的问题,这些问题大部分现在已经修复。头条在 OLAP 上的一些应用 在 OLAP 的场景下内容就比较少了。前面的一些业务喜欢用 tableau 这种客户端后面连接 MySQL,这就太慢了。可以用 syncer 把一些数据从 MySQL 同步到 TiDB。这就可能碰到一个问题:我们公司有一个组件,是会把 Hive 的数据批量的同步到 MySQL 的一个工具,很多做数据分析的同学就会把 Hive 里的数据同步到 TiDB。但是这个工具产生的事务非常大,而 TiDB 本身对事务的大小是有一个限制的。此时,把下面这两个配置项打开之后,TiDB 内部会把这种大的事务切成很多小的事务,就没有这个问题: set @@tidb_batch_insert =ON set @@tidb_batch_delete = ON 有事务大小的限制主要在于 TiKV 的实现用了一致性协议。对于任何一个分布式数据库,如果你要用一致性协议去做这种复制,肯定要避免非常大的事务。所以这个问题不是 TiDB 的问题。基本上,每个想要做分布式数据库的肯定都会碰到这么一个问题。在 OLAP 场景下,大家对数据的事务性要求没那么高,所以把这个配置项打开没什么问题。这就是头条在 OLAP 上的一些应用:比如说 ugc 点击量,app crash 的需求是客户端请求挂掉之后,要打一个 log 在 TiDB 的集群里面。druid 这个 OLAP 这个引擎,他会有 MySQL 的数据做元数据,有些人就把这个元数据存在 TiDB 上了,还有一些问答业务,也是把一些相关的数据放到 TiDB 上。"}, {"url": "https://pingcap.com/cases-cn/user-case-mobike/", "title": "TiDB 在摩拜单车在线数据业务的应用和实践", "content": " 作者:丁宬杰 / 胡明,Mobike 技术研发部基础平台中心 背景 摩拜单车于 2015 年 1 月成立,2016 年 4 月 22 日地球日当天正式推出智能共享单车服务,截至 2017 年 11 月中旬,已先后进入国内外超过 180 个城市,运营着超过 700 万辆摩拜单车,为全球超过 2 亿用户提供着智能出行服务,日订单量超过 3000 万,成为全球最大的智能共享单车运营平台和移动物联网平台。摩拜每天产生的骑行数据超过 30TB,在全球拥有最为全面的骑行大数据,飞速增长的业务使摩拜面临数据库扩展与运维的巨大挑战。面对飞速增长的并发数与数据量,单机数据库终将因无法支撑业务压力而罢工。在摩拜正式上线以来,我们就在不断思考数据库扩展和运维的未来,近年来业内对数据库进行扩展的常见的方案是通过中间件把数据库表进行水平拆分,将表内数据按照规则拆分到多个物理数据库中。使用这样的中间件方案,在数据库扩容时需要先停下业务,再重构代码,之后进行数据迁移,对于摩拜这样与时间赛跑的创业公司来讲代价巨大,中间件方案对业务过强的侵入性,不支持跨分片的分布式事务,无法保证强一致性事务的特性都使我们望而却步。摩拜单车于 2017 年初开始使用 TiDB,从最早的 RC3、RC4、PreGA、到现在的 1.0 正式版,一步步见证了 TiDB 的成熟和稳定。目前支撑着摩拜内部的实时分析和部分线上业务,同时正在规划迁移更多的线上业务至 TiDB。目前,TiDB 在摩拜部署了数套集群,近百个节点,承载着数十 TB 的各类数据。TiDB 在摩拜的角色和主要应用场景 在摩拜,TiDB 是一个核心的数据交易与存储支撑平台,引入它的主要目的是用来解决海量数据的在线存储、大规模实时数据分析和处理。在我们看来,TiDB 的好处主要有: 弹性扩容。具有 NoSQL 类似的扩容能力,在数据量和访问流量持续增长的情况下能够通过水平扩容提高系统的业务支撑能力,并且响应延迟稳定; 简单易用。兼容 MySQL 协议,基本上开箱即用,完全不用担心传统分库分表方案带来的心智负担和复杂的维护成本,而且用户界面友好,常规的技术技术人员都可以很高地进行维护和管理; 响应及时。因为和 PingCAP 团队有非常深入的合作关系,所以有任何问题都可以第一时间和 PingCAP 团队直接沟通交流,遇到问题都能很快的处理和解决。 下面介绍 TiDB 的应用场景:场景一:开关锁日志成功率统计 开关锁成功率是摩拜业务监控的重点指标之一。在每次开、关锁过程中,用户和锁信息会在关键业务节点产生海量日志,通过对线上日志的汇总分析,我们把用户的行为规整为人和车两个维度,通过分布式、持久化消息队列,导入并存放到 TiDB 里。在此过程中,通过对不同的实体添加不同的标签,我们就能方便地按照地域、应用版本、终端类型、用户、自行车等不同的维度,分别统计各个类别的开锁成功率。按照我们的估计,这个业务一年的量在数百亿,所以使用单机的 MySQL 库需要频繁的进行归档,特别是遇到单机数据库瓶颈的情况下,扩容更是带来了非常大的挑战,这在我们有限的人力情况下,完全是个灾难。所以要支撑整个 Mobike 的后端数据库,我们必须要寻找简单易用的方案,极大地减少在单个业务上的人力成本开销。其次,根据我们之前使用分库分表的经验,对于这类需要频繁更新表结构进行 DDL 操作的业务,一旦数据量过大,很很容易出现数据库假死的情况,不仅影响服务的可用性,更严重的是很可能导致数据不一致的情况出现。最后,我们希望不管今后的业务量如何激增,业务需求如何变化,都可以保持业务逻辑可以很方便地升级支持。在方案设计时,我们进行考察了 MySQL 分库分表的方案和 TiDB 方案的对比。我们先估计了可能的情况: 新业务上线,在线变动肯定是经常发生的; 尽可能存长时间的数据,以备进行统计比较; 数据要支持经常性的关联查询,支撑运营组的临时需求; 要能支撑业务的快速增长或者一些特殊活动造成的临时流量。 考虑到这些情况,MySQL 分库分表的方案就出现了一些问题,首先频繁变动表结构就比较麻烦,而 TiDB 可以进行在线 DDL。数据生命期比较长,可以设计之初做一个比较大的集群,但是弹性就比较差,针对这个问题,TiDB 可以根据需要,弹性的增加或者减少节点,这样的灵活性是 MySQL 分库分表没有的。另外,数据要支持频繁的复杂关联查询,MySQL 分库分表方案完全没办法做到这一点,而这恰恰是 TiDB 的优势,通过以上的对比分析,我们选择了 TiDB 作为开关锁日志成功率统计项目的支撑数据库。目前,大致可以将到端到端的延时控制在分钟级,即,若有开锁成功率下降,监控端可即时感知,此外,还能通过后台按用户和车查询单次故障骑行事件,帮助运维人员快速定位出故障的具体位置。场景二:实时数据分析 数据分析场景中,TiDB 可以从线上所有的 MySQL 实例中实时同步各类数据,通过 TiDB 周边工具 Syncer 导入到 TiDB 进行存储。这个业务的需求很简单,我们线上有数十个 MySQL 集群,有的是分库分表的,有的是独立的实例。这些孤立的数据要进行归集以便供业务方进行数据分析。我们一开始计划把这些库同步到 Hive 中,考察了两种方式,一种是每日全量同步,这么做,对线上的库压力以及 Hive 的资源开销都会越来越大。另一种是增量同步,这种方式非常复杂,因为 HDFS 不支持 update,需要把每日增量的部分和之前的部分做 merge 计算,这种方法的优点是在数据量比较大的情况下,增量同步对比全量同步要更快、更节省空间,缺点是占用相当一部分 Hadoop 平台的计算资源,影响系统稳定性。TiDB 本身有很多不错的工具,可以和 MySQL 的生态方便的连接到一起。这里我们主要使用了 TiDB 的 syncer 工具,这个工具可以方便的把 MySQL 实例或者 MySQL 分库分表的集群都同步到 TiDB 集群。因为 TiDB 本身可以 update,所以不存在 Hive 里的那些问题。同时有 TiSpark 项目,数据进入 TiDB 以后,可以直接通过 Spark 进行非常复杂的 OLAP 查询。有了这套系统,运营部门提出的一些复杂在线需求,都能够快速简洁的完成交付,这些在 Hadoop 平台上是无法提供这样的实时性的。目前,该集群拥有数十个节点,存储容量数十 T,受益于 TiDB 天然的高可用构架,该系统运行稳定,日后集群规模日益变大也仅需简单增加 x86 服务器即可扩展。后台开发、运维、业务方等都可以利用 TiDB 的数据聚合能力汇总数据,进行数据的汇总和分析。场景三:实时在线 OLTP 业务 如前所述,对比传统的分库分表方案,TiDB 的灵活性和可扩展性在实时在线业务上优势更加明显。根据我们的测试,TiDB 在数据量超过 5 千万时,对比 MySQL 优势较大,同时协议层高度兼容 MySQL,几乎不用修改业务代码就能直接使用,所以 TiDB 集群对于数据量大的实时在线业务非常适合。目前,摩拜主要上线了两套在线 OLTP 业务,分别是摩豆信用分业务和摩豆商城业务。摩豆信用分业务 摩拜单车信用分业务与用户骑行相关,用户扫码开锁时先行查询用户信用积分判断是否符合骑行条件,待骑行完成后,系统会根据用户行为进行信用分评估并进行修改。当单车无法骑行,上报故障核实有效后增加信用分、举报违停核实有效后降低信用分;但是如果不遵守使用规范,则会扣除相应的信用分;例如用户将自行车停在禁停区域内,系统就会扣除该用户的部分信用分作为惩罚,并存档该违停记录。当用户的信用分低于 80 分时,骑行费用将会大幅上升。摩豆商城业务(APP 中的摩拜成就馆) 魔豆商城业务即摩拜成就馆,用户的每一次骑行结束后,系统会根据骑行信息赠送数量不等的省时币、环保币、健康币作为积分,通过积累这些积分可以在摩拜成就馆内兑换相应积分的实物礼品。这些业务的共同特点: 7 * 24 * 365 在线,需要系统非常健壮,在任何状况下保证稳定运行; 数据不希望删除,希望能一直保存全量数据; 平时高峰期并发就非常大,搞活动的时候并发会有几倍的增长; 即便有业务变更,业务也不能暂停。 由于是典型 OLTP 场景,可选项并不多,而且数据量增长极快,这些数据库的数据在一年内轻松达到数百亿量级。这些场景在我们有了 TiDB 的使用经验以后,发现 TiDB 的所有特性都非常契合这种海量高并发的 OLTP 场景。TiDB 的容量/并发可随意扩展的特性不在赘述,支持在线 DDL 这个特性特别适合这些业务,有需要业务更改不会阻塞业务,这是我们业务快速迭代比较需要的特性。目前,这两个在线 OLTP 集群拥有数十个节点,百亿级数据,上线以后非常稳定,PingCAP 客户支持团队也协助我们进行该集群的日常运维工作。场景四:违章停车记录/开锁短信库等日志归集库 相对于传统的针对不同的业务分别部署 MySQL 集群的方案,TiDB 在可扩展性和在线跨库分析方面有较大优势。在部署 TiDB 之前,摩拜面对新增的业务需要对其进行单独的规划和设计,并根据业务的数据量,增速以及并发量设计 MySQL 的分库分表方案,这些重复的预先设计工作在所难免。另一方面,不同业务之间往往是有关联的,当运营部门需要不同业务的汇总数据时,就变得异常麻烦,需要新建一个临时的数据汇总中心,例如新建一套 MySQL 分库分表的集群,或者临时向大数据组申请 Hive 的空间,这都让数据提供的工作变得麻烦。有了 TiDB 以后,此类业务的开发和数据提供都变得非常简单,每次新需求下来,只需要按照新增数据量增加 TiKV 节点的数量即可,因为整个集群变得比较大,并发承载能力非常强,基本不需要考虑并发承载能力。特别的好处是,因为这些业务有相关性的业务,放在一个独立的数据库中,运营需要提供某几类某段时间的数据时就变得极为方便。基于 TiSpark 项目,Spark 集群可以直接读取 TiDB 集群的数据,在一些运营需要实时数据提供的场景,不再需要按照原有的提供数据到大数据平台,设计 ETL 方案,运营再去大数据部门沟通运算逻辑。而是直接在 TiDB 现有数据的基础上,直接提出复杂的分析需求,设计 Spark 程序进行在线的直接分析即可。这样做,我们非常容易就可以实现一些实时状态的分析需求,让数据除了完成自己的工作,还能更好的辅助运营团队。使用过程中遇到的问题和优化 在说优化问题之前,先看 TiDB 的架构图,整个系统大致分为几个部分。其中: PD 是整个集群的管理模块,负责:元信息管理、集群调度和分配全局递增非连续ID。 TiDB,是客户端接入层,负责 SQL 解析、执行计划优化,通过 PD 定位存储计算所需数据的 TiKV 地址。 TiKV,是数据的存储层,底层是基于 RocksDB 的 KV 引擎,并在其上分别封装 MVCC 和 Raft 协议,保证数据的安全、一致。 TiSpark,是 Spark 接入层,负责把 Spark 和 TiKV 连接到一起,在执行非常重的 OLAP 业务时可以利用到 Spark 集群的优势。 在使用过程中,遇到过不少问题,但是在我方和 PingCAP 技术团队的充分交流和协作下,都得到了比较完善的解决,下面挑选最为重要的资源隔离与优化展开。TiKV 中数据存储的基本单位是 Region,每个 Region 都会按顺序存储一部分信息。当一个 Region 包含多个表的数据,或一台机器上有多个 Region 同时为热点数据时,就容易产生资源瓶颈。PD 在设计之初考虑了这方面的问题(专门设计了 HotRegionBalance),但是,它的调度粒度是单个 Region,并且,整个调度基于这样的假设:即每个 Region 的资源消耗对等,不同 Region 之间没有关联,同时尽量保持 Region 均摊在所有 Store。但当一个集群同时承载多个库,或一个库中包含多个表时,发生资源瓶颈的概率会明显提升。针对这个问题,我们和 PingCAP 技术团队合作,对 TiDB 做了以下优化。优化一:基于 Table 的分裂 这个修改的目的是解决小表数据的相互影响的问题。当有新表数据插入某一 Region 时,TiKV 会根据当前 Region 的 Key Range 计算出 TableID,如果发现插入的 Key 不在这个 KeyRange 中,会对这个 Region 提前分裂,这就保证了每个 Region 只包含一个表的数据。优化二:表级别的资源隔离 与此同时,我们在 PD 增加了 TableID 和 Namespace 之间的映射关系以及 NameSpace 和 TiKV Store 的映射关系,通过把上述关系持久化到 eEtcd 里,保证该映射关系的安全。当数据插入时,可以在 TiDB 层面拿到 TableID,进而从 PD 找出目标 Region 所在的 TiKV,保证新插入的数据不会放到其他 TiKV。另外,我们还与 PingCAP 团队共同开发实现了一个 NameSpace 调度器,把未规整的 Region 调度回它应在的 TiKV 里,进而在表级别保证数据不会相互干扰。优化三:管理工具 最后的问题是管理 NameSpace 的问题。好在 TiDB 在早期设计时保留了足够的灵活性,通过 TiDB 原有接口,我们只需要调用相关 API 即能通过表名拿到 TableID。同时我们在 PD 的命令行管理台 pc-ctl 中增加了 HTTP 接口,管理确认 Table Name 和 TableID 之间的对应关系。后记 部署 TiDB 近一年来,摩拜单车经历了用户数量近十倍,日骑行数据数十倍的增长,依靠 TiDB 在线扩容的能力,我们完成了多次数据库扩容与服务器更换,而且这些操作对业务是完全透明的,我们可以更专注于业务程序的开发与优化,而无须了解数据库的分片规则,对于快速成长的初创公司,这有着很强的借鉴意义。另外深度参与 TiDB 的开发并和开源社区紧密的互动,也使我们获得了很多有益的反馈,极大降低了代码维护成本。未来,我们会联合 PingCAP 进一步丰富多集群的管理工具,进行更深入的研究和开发,持续提升 TiDB 的性能,将 TiDB 应用到更多的业务中。"}, {"url": "https://pingcap.com/cases-cn/user-case-qunar/", "title": "Qunar 高速发展下数据库的创新与发展 - TiDB 篇", "content": " 作者:蒲聪,去哪儿平台事业部 DBA,拥有近 6 年的 MySQL 和 HBase 数据库运维管理经验,2014 年 6 月加入去哪儿网,工作期间负责支付平台事业部的 MySQL 和 HBase 整体运维工作,从无到有建立去哪儿网 HBase 运维体系,在 MySQL 和 Hbase 数据库上有丰富的架构、调优和故障处理等经验。目前专注于分布式数据库领域的研究和实践工作。 目前互联网公司数据存储主要依赖于 MySQL 为代表的关系型数据库,但是随着业务量的增长,使用场景更加多样,传统的关系型数据库不能很好的满足业务需求,主要是在两个维度:一是随着数据量爆炸式增长,性能急剧下降,而且很难在单机内存储;二是一些场景下业务对响应速度要求较高,数据库无法及时返回结果,而传统的 memcached 缓存又存在无法持久化数据,存储容量受限于内存等问题。针对这两个问题,去哪儿的 DBA 团队分别调研了 TiDB 和 InnoDB memcached 以满足业务需求,为用户提供更多的选择方案。接下来的文章系列,我们将陆续为大家带来 TiDB 和 InnoDB memcached 在去哪儿的调研和实践,本篇文章先介绍 TiDB。分布式数据库诞生背景 随着互联网的飞速发展,业务量可能在短短的时间内爆发式地增长,对应的数据量可能快速地从几百 GB 涨到几百个 TB,传统的单机数据库提供的服务,在系统的可扩展性、性价比方面已经不再适用。随着业界相关分布式数据库论文的发布,分布式数据库应运而生,可以预见分布式数据库必将成为海量数据处理技术发展的又一个核心。目前业界最流行的分布式数据库有两类,一个是以 Google Spanner 为代表,一个是以 AWS Auraro 为代表。 Spanner 是 shared nothing 的架构,内部维护了自动分片、分布式事务、弹性扩展能力,数据存储还是需要 sharding,plan 计算也需要涉及多台机器,也就涉及了分布式计算和分布式事务。主要产品代表为 TiDB、CockroachDB、OceanBase 等。 Auraro 主要思想是计算和存储分离架构,使用共享存储技术,这样就提高了容灾和总容量的扩展。但是在协议层,只要是不涉及到存储的部分,本质还是单机实例的 MySQL,不涉及分布式存储和分布式计算,这样就和 MySQL 兼容性非常高。主要产品代表为 PolarDB。去哪儿数据存储方案现状 在去哪儿的 DBA 团队,主要有三种数据存储方案,分别是 MySQL、Redis 和 HBase。MySQL 是去哪儿的最主要的数据存储方案,绝大部分核心的数据都存储在 MySQL 中。MySQL 的优点不用多说,缺点是没法做到水平扩展。MySQL 要想能做到水平扩展,唯一的方法就业务层的分库分表或者使用中间件等方案。因此几年前就出现了各大公司重复造轮子,不断涌现出中间层分库分表解决方案,比如百度的 DDBS,淘宝的 TDDL,360 的 Atlas 等。但是,这些中间层方案也有很大局限性,执行计划不是最优,分布式事务,跨节点 join,扩容复杂(这个心酸 DBA 应该相当清楚)等。Redis 主要作为缓存使用,针对读访问延时要求高的业务,使用场景有限。 HBase 因其分布式的特点,可以通过 RS 的增加线性提升系统的吞吐,HDFS 具有水平扩展容量的能力,主要用来进行大数据量的存储,如日志、历史数据、订单快照等。HBase 底层存储是 LSM-Tree 的数据结构,与 B+ Tree 比,LSM-Tree 牺牲了部分读性能,用来大幅提升写性能。 但在实际运维的过程中,HBase 也暴露了一些缺点:  HBase 能提供很好地写入性能,但读性能就差很多,一是跟本身 LSM-Tree 数据结构有关。二是 HBase 因为要存储大容量,我们采用的是 SAS 盘,用 SSD 成本代价太大。三是跟 HBase 自身架构有关,多次 RPC、JVM GC 和 HDFS 稳定性因素都会影响读取性能。  HBase 属于 NoSQL,不支持 SQL,对于使用惯了关系 SQL 的人来说很不方便,另外在运维定位问题的时候也增加了不少难度。比如在运维 MySQL 过程中,可以很快通过慢查询日志就识别出哪些 SQL 执行效率低,快速定位问题 SQL。而在 HBase 运维中,就很难直接识别出客户端怎样的操作很慢,为此我们还在源码中增加了慢查询 Patch,但终归没有直接识别 SQL 来的方便。  HBase 的软件栈是 Java,JVM 的 GC 是个很头疼的问题,在运维过程中多次出现 RegionServer 因为 GC 挂掉的情况,另外很难通过优化来消除访问延时毛刺,给运维造成了很大的困扰。此外,HBase 在编程语言支持访问对 Java 友好,但是其他语言的访问需要通过 Thrift,有些限制。  架构设计上,HBase 本身不存储数据,HBase 通过 RPC 跟底层的 HDFS 进行交互,增加了一次 RPC,多层的架构也多了维护成本。另外 ZooKeeper 的高可用也至关重要,它的不可用也就直接造成了所有 HBase 的不可用。  HBase 不支持跨行事务。HBase 是 BigTable 的开源实现,BigTable 出现之后,Google 的内部就不停有团队基于 BigTable 做分布式事务,所以诞生了一个产品叫 MegaStore,虽然延时很大,但禁不住好用,所以用的人也很多,BigTable 的作者也后悔当初没有在 BigTable 中加入跨行事务。没有了跨行事务,也就没法实现二级索引,用户只能根据设计 rowkey 索引来进行查询,去哪儿不少业务开发问过我 HBase 是否支持二级索引,结果也不得不因为这个限制放弃了使用 HBase。业界很多公司也在 HBase 上实现二级索引,比如小米、华为等,比较出名的是 Phoenix。我们当初也调研了 Phoenix 来支持二级索引,但在 HBase 本身层次已经很多,Phoenix 无疑又多了一层,在运维的过程中也感觉坑很多 , 此外何时用 Phoenix 何时用 HBase API 的问题也给我们造成很多困扰,最终放弃了这个方案。 综上所述,为了解决现有数据存储方案所遇到的问题,去哪儿 DBA 团队从 2017 年上半年开始调研分布式数据库,在分布式数据库的产品选择上,综合各个方面因素考虑,最终选择了 TiDB,具体原因不在这里细说了,下面开始具体聊聊 TiDB。TiDB 架构设计 如下图,TiDB 架构主要分为三个组件: TiDB Server:负责接收 SQL 请求,处理 SQL 相关逻辑,通过 PD 找到所需数据的 TiKV 地址。TiDB Server 是无状态的,本身不存储数据,只负责计算,可以无限水平扩展。TiDB 节点可以通过负载均衡组件 (如 LVS、HAProxy 或者 F5) 对外提供统一入口,我个人觉得如果要能实现像 Elasticsearch 那样自己的客户端就更好。 PD Server:Placement Driver (简称 PD) 是整个集群的管理模块,其主要工作有三个: 存储集群元信息(某个 Key 存储在哪个 TiKV 节点)。 对 TiKV 集群进行调度和负载均衡(数据迁移、Raft group leader 迁移等)。 分配全局唯一且递增的事务 ID。 TiKV Server:TiKV 负责存储数据,存储数据基本单位是 Region,每个 TiKV 节点负责管理多个 Region。TiKV 使用 Raft 协议做复制,保证数据一致性和容灾。数据在多个 TiKV 之间的负载均衡由 PD 调度,以 Region 为单位调度。 TiDB 最核心的特性是水平扩展和高可用。 水平扩展:TiDB Server 负责处理 SQL 请求,可以通过添加 TiDB Server 节点来扩展计算能力,以提供更高的吞吐。TiKV 负责存储数据,随着数据量的增长,可以部署更多的 TiKV Server 节点来解决数据 Scale 的问题。 高可用:因 TiDB 是无状态的,可以部署多个 TiDB 节点,前端通过负载均衡组件对外提供服务,这样可以保证单点失效不影响服务。而 PD 和 TiKV 都是通过 Raft 协议来保证数据的一致性和可用性。 TiDB 原理与实现 TiDB 架构是 SQL 层和 KV 存储层分离,相当于 innodb 插件存储引擎与 MySQL 的关系。从下图可以看出整个系统是高度分层的,最底层选用了当前比较流行的存储引擎 RocksDB,RockDB 性能很好但是是单机的,为了保证高可用所以写多份(一般为 3 份),上层使用 Raft 协议来保证单机失效后数据不丢失不出错。保证有了比较安全的 KV 存储的基础上再去构建多版本,再去构建分布式事务,这样就构成了存储层 TiKV。有了 TiKV,TiDB 层只需要实现 SQL 层,再加上 MySQL 协议的支持,应用程序就能像访问 MySQL 那样去访问 TiDB 了。这里还有个非常重要的概念叫做 Region。MySQL 分库分表是将大的数据分成一张一张小表然后分散在多个集群的多台机器上,以实现水平扩展。同理,分布式数据库为了实现水平扩展,就需要对大的数据集进行分片,一个分片也就成为了一个 Region。数据分片有两个典型的方案:一是按照 Key 来做 Hash,同样 Hash 值的 Key 在同一个 Region 上,二是 Range,某一段连续的 Key 在同一个 Region 上,两种分片各有优劣,TiKV 选择了 Range partition。TiKV 以 Region 作为最小调度单位,分散在各个节点上,实现负载均衡。另外 TiKV 以 Region 为单位做数据复制,也就是一个 Region 保留多个副本,副本之间通过 Raft 来保持数据的一致。每个 Region 的所有副本组成一个 Raft Group, 整个系统可以看到很多这样的 Raft groups。最后简单说一下调度。 TiKV 节点会定期向 PD 汇报节点的整体信息,每个 Region Raft Group 的 Leader 也会定期向 PD 汇报信息,PD 不断的通过这些心跳包收集信息,获得整个集群的详细数据,从而进行调度,实现负载均衡。硬件选型和部署方案 在硬件的选型上,我们主要根据三个组件对硬件不同要求来考虑,具体选型如下:因 TiDB 和 PD 对磁盘 IO 要求不高,所以只需要普通磁盘即可。TiKV 对磁盘 IO 要求较高。TiKV 硬盘大小建议不超过 500G,以防止硬盘损害时,数据恢复耗时过长。综合内存和硬盘考虑,我们采用了 4 块 600G 的 SAS 盘,每个 TiKV 机器起 4 个 TiKV 实例,给节点配置 labels 并且通过在 PD 上配置 location-labels 来指明哪些 label 是位置标识,保证同一个机器上的 4 个 TiKV 具有相同的位置标识,同一台机器是多个实例也只会保留一个 Replica。有条件的最好使用 SSD,这样可以提供更好的性能。强烈推荐万兆网卡。TiDB 节点和 PD 节点部署在同台服务器上,共 3 台,而 TiKV 节点独立部署在服务器上,最少 3 台,保持 3 副本,根据容量大小进行扩展。 部署工具使用了 TiDB-Ansible,TiDB-Ansible 是 PingCAP 基于 Ansible playbook 功能编写了一个集群部署工具叫 TiDB-Ansible。使用该工具可以快速部署一个完整的 TiDB 集群(包括 PD、TiDB、TiKV 和集群监控模块),一键完成以下各项运维工作: 初始化操作系统,包括创建部署用户、设置 hostname 等 部署组件 滚动升级,滚动升级时支持模块存活检测 数据清理 环境清理 配置监控模块 监控方案 PingCAP 团队给 TiDB 提供了一整套监控的方案,他们使用开源时序数据库 Prometheus 作为监控和性能指标信息存储方案,使用 Grafana 作为可视化组件进行展示。具体如下图,在 client 端程序中定制需要的 Metric。Push GateWay 来接收 Client Push 上来的数据,统一供 Prometheus 主服务器抓取。AlertManager 用来实现报警机制,使用 Grafana 来进行展示。下图是某个集群的 Grafana 展示图。TiDB 使用情况 对于 TiDB 的使用,我们采用大胆实践、逐步推广的思路,一个产品是否经得起考验,需要先用起来,在运维的过程中才能发现问题并不断反馈完善。因此,去哪儿 DBA 经过了充分调研后,8 月下旬开始,我们就先在非核心业务使用,一方面不会因为 TiDB 的问题对现有业务影响过大,另一方面 DBA 自身也积累运维经验,随着产品自身的完善,集群使用量和运维经验的积累,后续我们再逐步推广到更重要的集群中,解决业务上遇到的数据存储的痛点。目前已经上线了两个 TiDB 集群。 机票离线集群,主要是为了替换离线 MySQL 库,用于数据统计。当前系统容量是 1.6T,每天以 10G 的增量进行增长,用 MySQL 存储压力较大,且没法扩展,另外一些 OLAP 查询也会影响线上的业务。  金融支付集群,主要是想满足两方面需求,一是当前存储在 MySQL 中的支付信息表和订单信息表都是按月进行分表,运营或者开发人员想对整表进行查询分析,现有的方案是查多个表查出来然后程序进行进一步统计 。二是有些查询并不需要在线上进行查询,只需要是作为线下统计使用,这样就没有必要对线上表建立索引,只需要离线库建立索引即可,这样可以避免降低写性能。  目前的架构是用 syncer 从线上 MySQL 库做分库分表数据同步到 TiDB 中,然后开发可以在 TiDB 上进行 merge 单表查询、连表 join 或者 OLAP。"}, {"url": "https://pingcap.com/cases-cn/user-case-yuanfudao/", "title": "TiDB 在猿辅导数据快速增长及复杂查询场景下的应用实践", "content": "猿辅导是国内拥有最多中小学生用户的在线教育机构,旗下有猿题库、小猿搜题、猿辅导三款在线教育 APP,为用户提供在线题库、拍照搜题、名师在线辅导相关的服务。其中,猿辅导 APP 已经有超过 116 万付费用户,提供小学英语、奥数,和初中高中全学科的直播辅导课程,全国任何地区的中小学生,都可以享受在家上北京名师辅导课的服务。海量的题库、音视频答题资料、用户数据以及日志,对猿辅导后台数据存储和处理能力都提出了严峻的要求。猿辅导的业务决定了其后台系统具有以下特点: 数据体量大,增速快,存储系统需要能够灵活的水平扩展; 有复杂查询,BI 方面的需求,可以根据索引,例如城市、渠道等,进行实时统计; 数据存储要具备高可用、高可运维性,实现自动故障转移。 在最初方案选型时,猿辅导初期考虑用单机 MySQL。但根据业务发展速度预估,数据存储容量和并发压力很快就会达到单机数据库的处理瓶颈。如果在 MySQL 上加入分库中间件方案,则一定要指定 sharding key,这样是无法支持跨 shard 的分布式事务。同时 proxy 的方案对业务层的侵入性较强,开发人员必须了解数据库的分区规则,无法做到透明。除此之外,分库分表很难实现跨 shard 的聚合查询,例如全表的关联查询、子查询、分组聚合等业务场景,查询的复杂度需要转嫁给开发者。即使某些中间件能实现简单的 join 支持,但是仍然没有办法保证查询的正确性。另外广播是一个没有办法 Scale 的方案,当集群规模变大,广播的性能开销是很大的。同时,传统 RDBMS 上 DDL 锁表的问题,对于数据量较大的业务来说,锁定的时间会很长,如果使用 gh-ost 这样第三方工具来实现非阻塞 DDL,额外的空间开销会比较大,而且仍然需要人工的介入确保数据的一致性,最后切换的过程系统可能会有抖动。可以说,运维的复杂性是随着机器数量指数级增长,而扩容复杂度则是直接转嫁给了 DBA。最终,猿辅导的后台开发同学决定寻求一个彻底的分布式存储解决方案。通过对社区方案的调研,猿辅导发现分布式关系型数据库 TiDB 项目。TiDB 是一款定位于在线事务处理/在线分析处理(HTAP)的融合型数据库产品,具备在线弹性水平扩展、分布式强一致性事务、故障自恢复的高可用、跨数据中心多活等核心特性;对业务没有任何侵入性,能优雅的替换传统的数据库中间件、数据库分库分表等 Sharding 方案,并在此过程中保证了事务的 ACID 特性。同时它也让开发运维人员不用关注数据库 Scale 的细节问题,专注于业务开发,极大的提升研发的生产力。用户可以把 TiDB 当作一个容量无限扩展的单机数据库,复杂的分布式事务和数据复制由底层存储引擎来支持,开发者只需要集中精力在业务逻辑的开发上面。图为 TiDB 与传统的 MySQL 中间件方案的一些对比TiDB 集群主要分为三个组件:TiDB Server、TiKV Server、PD Server。TiDB 整体架构图TiDB Server 负责处理 SQL 请求,随着业务的增长,可以简单的添加 TiDB Server 节点,提高整体的处理能力,提供更高的吞吐。TiKV 负责存储数据,随着数据量的增长,可以部署更多的 TiKV Server 节点解决数据 Scale 的问题。PD 会在 TiKV 节点之间以 Region 为单位做调度,将部分数据迁移到新加的节点上。所以企业在业务的早期,可以只部署少量的服务实例,随着业务量的增长,按照需求添加 TiKV 或者 TiDB 实例。在实际上线的部署设置中,猿辅导选择了 2 TiDB + 3 TiKV + 3 PD 的架构,随着业务数据的增加可以弹性扩容,数据条数每天 500w,日常库中数亿条记录,峰值 QPS 1000。猿辅导的用户端会做一些直播过程的音视频质量的数据收集,比如丢包,延迟,质量打分。然后客户端把这些数据发回服务器,服务器把这些数据存到 TiDB 上。在猿辅导研发副总裁郭常圳看来:“TiDB 是一个很有野心的项目,从无到有的解决了 MySQL 过去遇到的扩展性问题,在很多场合下也有 OLAP 的能力,省去了很多数据仓库搭建成本和学习成本。这在业务层是非常受欢迎的。”对于接下来的计划,猿辅导预计在其他分库分表业务中,通过 syncer 同步,进行合并,然后进行统计分析。实际上,类似猿辅导这种场景的并不是第一家,在互联网快速发展下,大量的企业面对着业务激增的情况。TiDB 灵活的水平扩展能力,能够满足企业业务快速发展的需要。"}, {"url": "https://pingcap.com/cases-cn/user-case-erweihuo/", "title": "TiDB 在二维火餐饮管理实时报表中的实践", "content": " 作者介绍:火烧(花名),二维火架构运维负责人 二维火 SaaS 平台介绍 二维火作为餐饮商家管理标准化服务提供商,帮助商家节省经营成本、提升服务效果是我们的使命。在商家日常生产中,上游系统产生了很多数据,包括供应链采购系统(Support),门店收银系统(POS),食客排队系统(Queueing),智能厨房显示系统(KDS),电子菜谱系统等(E-Menu), 一个实时、精准、可多维度分析的报表系统能充分利用这些数据,支持商家对经营决策进行优化,极大提升商家工作效率。主要服务于以下场景: 收银员交接班需要通过收银软件、财务报表进行多维度对账,来保障收银员一天的辛苦劳动。 商家运营人员需要时段性监控每个门店的经营状况,并通过监控数据实时调整运营策略。 中小型店老板解放自我,不再需要时时刻刻呆在门店里,也能从原料变化到收入波动进行实时把控。 大型门店连锁更有专门的指挥中心,实时了解每个门店的经营状况,实现一体化管理。 二维火各类报表界面:二维火实时报表的业务约束 要求实时或者准实时,数据延迟不超过 3 秒。 数据量大、数据增速快,报表结果查询需要在 500 ms 之内返回结果。 查询维度多,查询条件复杂,涉及十几个业务表,上百个维度汇总查询。 随着业务范围扩大以及功能扩展,实时报表业务不光承担了报表业务,业务方同时希望这是一个数据实时、可多维度查询分析的数据平台工具,为业务进行各种数据支持。二维火数据的特殊场景 商家门店连锁关系不是固定的,A 门店数据今天属于 AA 连锁,明天可能会变成 BB 连锁。 数据展现多人多面,权限不同展现结果不同。 数据变更非常频繁,一条数据最少也会经过五六次变更操作。 实时报表展现的不仅是当天数据,涉及到挂帐、垮天营业、不结账等复杂状况,生产数据的生命周期往往会超过一个月以上。 如果用 MySQL 作为报表存储引擎的话,一个门店所属连锁总部变更,相当于分库的路由值产生了变化,意味着需要对这家店的数据进行全量迁移,这是个灾难性的事情,我们希望连锁只是个属性,而不用受到 Sharding Key 的制约导致的一地鸡毛。开始的解决方案 我们的业务数据是构建在 MySQL 之上,按照业务和商家维度进行分库。利用 Otter 订阅业务数据,进行数据整理归并到 Apache Solr[1] 中,输出分析、统计报表所需要的数据。然而随着业务的快速发展以及客户定制化需求不断增加,Solr 的瓶颈从一张白纸慢慢地被填充。 不断的添加检索维度,需要不停的对索引进行 Full Build,Solr 的 Full Build 成本慢慢地高成了一座大山。 为保障数据精准,Solr 的 Full Build 必须隔段时间操作一次。 随着业务蓬勃发展,数据更新频率越来越高、范围时间内更新的数据条数越来越多,面对 Solr GC 这个问题,我们对数据更新做了窗口控制,一条数据的更新延迟到了 10 秒钟。 Solr 的故障恢复时间过长,严重影响业务可用性。 Solr 很好,但是对于要求能灵活定制、数据即时、维度复杂的报表业务来说,他还不够好。 引入 TiDB 在引入 TiDB 之前,我们也评估和使用过 Greenplum,但是发现并发实时写入的性能无法满足我们的业务承载,在机缘巧合之下认识了 TiDB 同学们,经过了一段时间熟悉、测试和研究之后,我们意识到 TiDB 就是我们想要的产品,于是就开始在实际环境中使用 TiDB 来构建实时报表系统。特别要赞美下 TiDB 的软件太棒了,国内开源软件无出其右,天然的高可用,本身减少了很多运维工作,Ansible 多节点、多组件一键部署,业务无感知滚动升级,特别是把监控也嵌合到软件本身里,让我们追踪、定位问题异常清晰明了,大大缩减了排查成本。当然 TiDB 的优点很多: 首先解决了繁琐的分库分表以及无限水平扩展问题,并且能保证事务特性。 其次故障自恢复,计算、存储全无单点。 还有 TiDB 在做 DDL 操作时候,基本不影响业务使用,这个对我们经常调整表结构的业务来说,大大加快了迭代速度,在线 DDL 不再是一件奢侈的事情。 TiDB 通过下推到存储节点进行并行计算等技术,在数据量大、同时不影响写入的情况下,能非常好的满足各种统计查询响应时间要求。 总之,TiDB 很好的解决了我们之前在实时报表碰到了各种痛点,让我们终于不用为业务方的每项决策和用户的需求而绞尽脑汁和痛苦不堪。TiDB 集群总体配置如下:2*TiDB、3*TiKV、3*PDTiDB 使用体验 我们基于 TiDB 的实时报表系统稳定运行了一个多月,目前实时报表承载的日数据更新量在一亿以上,高峰时期 TiDB 写入 QPS 高于 4000,百级读 QPS ,读流量基本上是统计类 SQL, 比较复杂,最多包含十几张表的关联查询。TiDB 不管从性能、容量还是高可用性以及可运维性来说,完全超出了我们的预期。TiDB 让我们的业务开发回到了业务本质,让简单的再简单,开发不需要再拆东墙补西墙忙于数据爆发带来的各种手忙脚乱。整体数据库架构图:在 TiDB 使用中的几点注意事项 一些注意事项,TiDB 的官方文档写的非常详细全面了,这里我再画蛇添足几点个人觉得非常重要的几项: TiDB 对 IO 操作的延迟有一定的要求,所以一定要本地 SSD 盘。我们一开始在一个集群中使用了云 SSD 盘,发现性能抖动非常厉害,特别是扩容缩容会导致业务基本不可用。 删除大数据的时候,用小数据多批量方式操作,我们现在每批次是小于 1000 条进行删除。 在大批量数据导入后,务必先操作 analyze table${table_name}。 TiDB 开发非常活跃,版本迭代非常快,升级的时候先在非生产环境演练下业务,特别是一些复杂 SQL 的场景。 后续计划 在接入一个业务实时报表后,我们对 TiDB 越来越了解,后续我们计划对 TiDB 进行推广使用,具体包括: 把公司所有实时报表以及统计结果都逐渐迁移到 TiDB 中。 对业务进行分类,大表、多表关联的复杂场景准备开始使用 TiSpark,结合现有 TiDB,会大大简化目前整个数据产品、架构,同时大大解放运维。 中期会考虑将 OLTP 的业务迁入到 TiDB。 最终通过 TiDB 构造成一个同时兼容分析型和事务型(HTAP)的统一数据库平台。"}, {"url": "https://pingcap.com/cases-cn/user-case-keruyun/", "title": "TiDB 助力客如云餐饮 SaaS 服务", "content": " 作者:客如云 BigData Infra Team  公司介绍 客如云成立于 2012 年,是全球领先、 国内最大的 SaaS 系统公司。 目前面向餐饮、 零售等服务业商家, 提供软硬一体的新一代智能化前台、收银等 SaaS 云服务,包括预订、排队、外卖、点餐、收银、会员管理、进销存等系统服务,并将数据实时传达云端。我们是客如云的大数据基础架构组,负责公司的大数据架构和建设工作,为公司提供大数据基础数据服务。业务发展遇到的痛点 1. 随着公司业务架构越来越复杂,技术架构组需要在服务器端与应用端尽可能的通过微服务化实现业务解耦,同时需要第三方熔断服务机制来保证核心业务正常运行。数据库层面,为了保证高并发的实时写入、实时查询、实时统计分析,我们针对地做了很多工作,比如对实时要求较高的服务走缓存、对大表进行分库分表操作、对有冷热属性的大表进行归档、库分离,虽然用大量人力资源解决了部分问题,但是同时也带来了历史数据访问、跨库分表操作、多维度查询等问题。2. 大数据量下,MySQL 稍微复杂的查询都会很慢,线上业务也存在单一复杂接口包含执行几十次 SQL 的情况,部分核心交易大库急需解决访问性能。3. 餐饮行业有明显的业务访问高峰时间,高峰期期间数据库会出现高并发访问,而有些业务,比如收银,在高峰期出现任何 RDS 抖动都会严重影响业务和用户体验。4. 传统的数仓业务往往有复杂的 T+1 的 ETL 过程,无法实时的对业务变化进行动态分析和及时决策。业务描述 大数据的 ODS(Operational Data Store)以前选型的是 MongoDB,ODS 与支持 SaaS 服务的 RDS 进行数据同步。初期的设想是线上的复杂 SQL、分析 SQL,非核心业务 SQL 迁移到大数据的 ODS 层。同时 ODS 也作为大数据的数据源,可以进行增量和全量的数据处理需求。但是由于使用的 MongoDB,对业务有一定侵入,需要业务线修改相应查询语句,而这点基本上遭到业务线的同学不同程度的抵制。同时目前大数据使用的是 Hadoop + Hive 存储和访问方案,业务线需要把历史明细查询迁移到 Hadoop ,然后通过 Impala、Spark SQL、Hive SQL 进行查询,而这三个产品在并发度稍微高的情况下,响应时间都会很慢,所以大数据组在提供明细查询上就比较麻烦。 同时更为棘手的是,面对客户查询服务(历史细则、报表等),这类查询的并发会更高,而且客户对响应时间也更为敏感,我们首先将处理后的数据(历史细则等)放在了 MongoDB 上(当时 TiDB 1.0 还没有 GA,不然就使用 TiDB 了),然后针对 OLAP 查询使用了 Kylin ,先解决了明细查询的问题。 但是由于业务很复杂, 数据变更非常频繁,一条数据最少也会经过五六次变更操作。报表展现的不仅是当天数据,涉及到挂账、跨天营业、不结账、预定等复杂状况,生产数据的生命周期往往会超过一个月以上。所以当前的 OLAP 解决方案还有痛点,所以后续我们要把 OLAP 查询移植一部分到 TiDB 上面去,来减轻 Kylin 的压力并且支持更加灵活的查询需求,这个目前还在论证当中。 同时,我们发现 TiDB 有一个子项目 TiSpark, TiSpark 是建立在 Spark 引擎之上,Spark 在机器学习领域里有诸如 MLlib 等诸多成熟的项目,算法工程师们使用 TiSpark 去操作 TiDB 的门槛非常低,同时也会大大提升算法工程师们的效率。我们可以使用 TiSpark 做 ETL,这样我们就能做到批处理和实时数仓,再结合 CarbonData 可以做到非常灵活的业务分析和数据支持,后期根据情况来决定是否可以把一部分 Hive 的数据放在 TiDB 上。新老框架如下图:图:老的框架图:新的框架TiDB 测试应用 1. 配置 阿里云服务器: TiDB / PD:3 台 i1 型 机器,16c 64g ; TiKV :5 台 i2 型机器,16c 128g, 1.8T*2 每台机器部署两个 KV; 监控机一台。 目前我们将线上 RDS 中三个库的数据通过 Binlog 同步到 TiDB ,高峰期 QPS 23k 左右,接入了业务端部分查询服务;未来我们会将更多 RDS 库数据同步过来,并交付给更多业务组使用。因为 TiDB 是新上项目,之前的业务线也没有线上 SQL 迁移的经历,所以在写入性能上也没有历史数据对比。2. 性能对比 (1)查询一个索引后的数字列,返回 10 条记录,测试索引查询的性能。(2)查询两个索引后的数字列,返回 10 条记录(每条记录只返回 10 字节左右的 2 个小字段)的性能,这个测的是返回小数据量以及多一个查询条件对性能的影响。(3)查询一个索引后的数字列,按照另一个索引的日期字段排序(索引建立的时候是倒序,排序也是倒序),并且 Skip 100 条记录后返回 10 条记录的性能,这个测的是 Skip 和 Order 对性能的影响。(4)查询 100 条记录的性能(没有排序,没有条件),这个测的是大数据量的查询结果对性能的影响。(5)TiDB 对比 MySQL 复杂 SQL 执行速率: Table 1 TiDB 数据量 5 千万,MySQL数据量 2.5 千万; Table 2 TiDB 数据量 5 千万,MySQL数据量 2.5 千万; Table 3 TiDB 数据量 5 千万,MySQL数据量 2.5 千万。 a. 对应 SQL:SELECT sum(p.exempt_amount) exempt_amount FROM table1 p JOIN table2 c ONp.relate_id=c.id AND p.is_paid = 1 andp.shop_identy in(BBBBB) andp.brand_identy=AAAAA andp.is_paid=1 AND p.status_flag=1 AND p.payment_type!=8 WHEREc.brand_identy = AAAAA ANDc.shop_identy in(BBBBB) ANDc.trade_type in(1,3,4,2,5) ANDc.recycle_status=1 AND c.trade_statusIN (4,5,10) ANDp.payment_time BETWEEN '2017-08-11 16:56:19' AND '2018-01-13 00:00:22' ANDc.status_flag = 1 ANDc.trade_pay_status in(3,5) AND c.delivery_type in(1,2,3,4,15)  b. 对应 SQL:SELECT sum(c.sale_amount)tradeAmount,sum(c.privilege_amount) privilege_amount,sum(c.trade_amount)totalTradeAmount,sum(c.trade_amount_before) tradeAmountBefore FROM (SELECTc.sale_amount,c.privilege_amount,c.trade_amount,c.trade_amount_before FROM table1p JOIN table2c ON p.relate_id=c.id andp.shop_identy in(BBBBB) andp.brand_identy=AAAAA andp.is_paid=1 AND p.status_flag=1 AND p.payment_type!=8 and c.brand_identy = AAAAA ANDc.shop_identy in(BBBBB) ANDc.trade_type in(1,3,4,2,5) ANDc.recycle_status=1 AND c.trade_statusIN (4,5,10) ANDp.payment_time BETWEEN '2017-07-31 17:38:55' AND '2018-01-13 00:00:26' ANDc.status_flag = 1 ANDc.trade_pay_status in(3,5) ANDc.delivery_type in(1,2,3,4,15) ANDp.payment_type not in(4,5,6,8,9,10,11,12) GROUP BY p.relate_id ) c  c. 对应 SQL:SELECT SUM(if(pay_mode_id=-5 or pay_mode_id = -6,0,IFNULL(pi.face_amount, 0) - IFNULL(pi.useful_amount, 0) -IFNULL(pi.change_amount, 0))) redundant FROM table2c JOIN table1 p ON c.id = p.relate_id AND c.brand_identy=p.brand_identy JOIN table3pi ON pi.payment_id=p.id AND pi.pay_status in (3,5,10) AND pi.brand_identy=p.brand_identy ANDpi.pay_mode_id!=-23 andp.shop_identy in(BBBBB) andp.brand_identy=AAAAA andp.is_paid=1 AND p.status_flag=1 AND p.payment_type!=8 WHEREc.brand_identy = AAAAA ANDc.shop_identy in(BBBBB) ANDc.trade_type in(1,3,4,2,5) ANDc.recycle_status=1 AND c.trade_statusIN (4,5,10) ANDp.payment_time BETWEEN '2017-07-31 17:38:55' AND '2018-01-13 00:00:26' ANDc.status_flag = 1 ANDc.trade_pay_status in(3,5) AND c.delivery_type in(1,2,3,4,15) d. 对应 SQL:SELECT t.id tradeId,sum(t.trade_amount - t.trade_amount_before) AS roundAmount, sum(-p.exempt_amount) AS exemptAmount FROM table2t LEFT JOINtable1 p ON p.relate_id = t.id LEFT JOINtable3 pi ON pi.payment_id = p.id WHEREt.brand_identy =AAAAA AND t.trade_status IN (4,5,10) ANDt.trade_pay_status IN (3,4,5,6,8) ANDp.payment_type IN (1,2) ANDpi.pay_mode_id !=-23 ANDp.is_paid=1 AND t.status_flag=1 AND t.shop_identy IN(<123个商户号码>) GROUP BY t.id e. 对应 SQL:SELECT t.id tradeId, sum(t.trade_amount- t.trade_amount_before) AS roundAmount, sum(-p.exempt_amount)AS exemptAmount FROM table2t JOIN table1 p ON t.id = p.relate_id WHERE t.brand_identy = AAAA ANDt.trade_status IN(4,5,10) ANDt.trade_pay_status IN (3,4,5,6,8) ANDp.is_paid=1 AND t.status_flag=1 group by t.id ; (6)OLTP 对比测试结果:(7)简单测试结论: 不管是索引查询、分页查询、线上业务级的负载查询,大数据量下 TiDB 的性能都比 MySQL 更强; TiDB 整体性能表现满足我们业务的需求。 生产使用情况 目前线上已经存储超过 6 个月的数据,总数据量几 T,支持线上的查询和分析需求,很多一般复杂度 OLAP 查询都能够在秒级返回结果。TiSpark 我们也调试通过,准备移植一些支持 OLAP 的 ETL 应用做到实时 ETL。目前 TiDB 生产还有很多优化的空间,比如系统参数,SQL 的使用姿势,索引的设计等等。未来规划 已经有一个交易量很大业务部门在向我们了解 TiDB,有可能要使用 TiDB 作为线上交易系统; 后续大数据也会使用 TiSpark 来做 OLAP 查询和数据处理,同时也可能会作为 Kylin 的数据源; 可以预见将来不管是 OLTP,还是 OLAP 场景,TiDB 都会在客如云发挥越来越重要的作用。 致谢 感谢 TiDB 的厂商的人员给予了我们巨大的支持,希望我们能够提供给 TiDB 一些有意义的信息和建议,在 TiDB 成长的路上添砖加瓦。"}, {"url": "https://pingcap.com/cases-cn/user-case-kasi/", "title": "TiDB 助力卡思数据视频大数据业务创新", "content": " 作者:刘广信,火星文化技术经理 卡思数据是国内领先的视频全网数据开放平台,依托领先的数据挖掘与分析能力,为视频内容创作者在节目创作和用户运营方面提供数据支持,为广告主的广告投放提供数据参考和效果监测,为内容投资提供全面客观的价值评估。图 1 卡思数据产品展示图业务发展遇到的痛点 卡思数据首先通过分布式爬虫系统进行数据抓取,每天新增数据量在 50G - 80G 之间,并且入库时间要求比较短,因此对数据库写入性能要求很高,由于数据增长比较快,对数据库的扩展性也有很高的要求。数据抓取完成后,对数据进行清洗和计算,因为数据量比较大,单表 5 亿 + 条数据,所以对数据库的查询性能要求很高。起初卡思数据采用的是多个 MySQL 实例和一个 MongoDB 集群,如图 2。 MySQL 存储业务相关数据,直接面向用户,对事务的要求很高,但在海量数据存储方面偏弱,由于单行较大,单表数据超过千万或 10G 性能就会急剧下降。 MongoDB 存储最小单元的数据,MongoDB 有更好的写入性能,保证了每天数据爬取存储速度;对海量数据存储上,MongoDB 内建的分片特性,可以很好的适应大数据量的需求。 图 2 起初卡思数据架构图但是随着业务发展,暴露出一些问题: MySQL 在大数据量的场景下,查询性能难以满足要求,并且扩展能力偏弱,如果采用分库分表方式,需要对业务代码进行全面改造,成本非常高。 MongoDB 对复杂事务的不支持,前台业务需要用到数据元及连表查询,当前架构支持的不太友好。 架构优化 1. 需求 针对我们遇到的问题,我们急需这样一款数据库: 兼容 MySQL 协议,数据迁移成本和代码改造成本低 插入性能强 大数据量下的实时查询性能强,无需分库分表 水平扩展能力强 稳定性强,产品最好有成熟的案例 2. 方案调研 未选择 TiDB 之前我们调研了几个数据库,Greenplum、HybirdDB for MySQL(PetaData)以及 PolarDB。Greenplum 由于插入性能比较差,并且跟 MySQL 协议有一些不兼容,首先被排除。HybirdDB for MySQL 是阿里云推出的 HTAP 关系型数据库,我们在试用一段时间发现一些问题: 一是复杂语句导致计算引擎拥堵,阻塞所有业务,经常出现查询超时的情况。 二是连表查询性能低下,网络 I/O 出现瓶颈。举一个常见的关联查询,cd_video 表,2200 万数据,cd_program_video 表,节目和视频的关联表,4700 万数据,在关联字段上都建有索引,如下 SQL:select v.id,v.url,v.extra_id,v.title fromcd_video v join cd_program_video pv on v.id = pv.video_id where program_id =xxx;当相同查询并发超过一定数量时,就会频繁报数据库计算资源不可用的错误。 三是 DDL 操作比较慢,该字段等操作基本需要几分钟,下发至节点后易出现死锁。 PolarDB 是阿里云新推出新一代关系型数据库,主要思想是计算和存储分离架构,使用共享存储技术。由于写入还是单点写入,插入性能有上限,未来我们的数据采集规模还会进一步提升,这有可能成为一个瓶颈。另外由于只有一个只读实例,在对大表进行并发查询时性能表现一般。3. 选择 TiDB 在经历了痛苦的传统解决方案的折磨以及大量调研及对比后,卡思数据最终选择了 TiDB 作为数据仓库及业务数据库。TiDB 结合了传统的 RDBMS 和 NoSQL 的最佳特性,高度兼容 MySQL,具备强一致性和高可用性,100% 支持标准的 ACID 事务。由于是 Cloud Native 数据库,可通过并行计算发挥机器性能,在大数量的查询下性能表现良好,并且支持无限的水平扩展,可以很方便的通过加机器解决性能和容量问题。另外提供了非常完善的运维工具,大大减轻数据库的运维工作。上线 TiDB 卡思数据目前配置了两个 32C64G 的 TiDB、三个 4C16G 的 PD、四个 32C128G 的 TiKV。数据量大约 60 亿条、4TB 左右,每天新增数据量大约 5000 万,单节点 QPS 峰值为 3000 左右。由于数据迁移不能影响线上业务,卡思数据在保持继续使用原数据架构的前提下,使用 Mydumper、Loader 进行数据迁移,并在首轮数据迁移完成后使用 Syncer 进行增量同步。卡思数据部署了数据库监控系统(Prometheus/Grafana)来实时监控服务状态,可以非常清晰的查看服务器问题。由于 TiDB 对 MySQL 的高度兼容性,在数据迁移完成后,几乎没有对代码做任何修改,平滑实现了无侵入升级。目前卡思数据的架构如图 3:图 3 目前卡思数据架构图查询性能,单表最小 1000 万,最大 8 亿,有比较复杂的连表查询,整体响应延时非常稳定,监控展示如图 4、图 5。图 4 Duration 监控展示图图 5 QPS 监控展示图未来展望 目前的卡思数据已全部迁移至 TiDB,但对 TiDB 的使用还局限在数据存储上,可以说只实现了 OLTP。卡思数据准备深入了解 OLAP,将目前一些需要实时返回的复杂查询、数据分析下推至 TiDB。既减少计算服务的复杂性,又可增加数据的准确性。感谢 PingCAP 非常感谢 PingCAP 小伙伴们在数据库上线过程中的大力支持,每次遇到困难都能及时、细心的给予指导,非常的专业和热心。相信 PingCAP 会越来越好,相信 TiDB 会越来越完善,引领 NewSQL 的发展。"}, {"url": "https://pingcap.com/cases-cn/user-case-ifeng/", "title": "TiDB 在凤凰网新闻内容业务的创新实践", "content": " 作者:卞新栋,凤凰网工程师 背景 凤凰网(纽交所上市公司,代码:FENG)是全球领先的跨平台网络新媒体公司,整合旗下综合门户凤凰网、手机凤凰网和凤凰视频三大平台,秉承”中华情怀,全球视野,兼容开放,进步力量”的媒体理念,为主流华人提供互联网、无线通信、电视网的三网融合无缝衔接的新媒体优质内容与服务。在媒体行业,新闻内容就是核心的业务数据,我们需要一个稳定的、具有高可用的、易水平扩展的数据存储系统,来存放公司核心数据,在最早,我们采用比较流行的 MySQL 来存储各个业务模块的内容,通过主从切换的方式进行高可用,但随着数据量的增加,MySQL 单机容量成为了瓶颈,传统的基于 MySQL 分片方案在实现及运维都需要比较昂贵的成本,同时 MySQL 主流主从切换方案由于机制问题,在判断“主库真死”,“新主库选举”及“新路由信息广播”上都存在一些不足,整体时间消耗比较大,并不能达到公司核心业务的高可用要求。于是,我们不得不寻找新的解决方案。选择 TiDB 前期方案选择 在选择评估初期,我们主要有以下几个考虑点: 支持业务弹性的水平扩容与缩容; 满足业务故障自恢复的高可用; 支持 MySQL 便捷稳定的迁移,不影响线上业务; 支持 SQL,尽量少的改动代码; 使用上、运维上要足够优化,最好支持在线 DDL。 在寻找的道路中,我们惊喜的发现了 TiDB — 中国人研发主导的开源分布式数据库。数据库容量及扩展 记得有这样一句话:“单机 MySQL 能解决的问题,不要使用 TiDB!”,我们原有的数据存储存放于多个 MySQL 数据库中。诚然,对于数据量小的库来说,一些常见的点查、范围查 MySQL 的性能要比 TiDB 的性能好,如果不考虑扩张的问题,短期内使用主从 MySQL 比使用 TiDB 更满足我们的线上需求,但是,即使如此,我们也不得不考虑成本问题。将多套线上的主从 MySQL 迁移到 TiDB,更能充分利用服务器资源,减少资源的浪费。而对于很多业务来说,扩张问题是不可能回避的,对数据日益增长的数据库来说,单表响应时间会越来越长,而分库分表的成本过高,代码需要改动和测试,即使业务上能接受这一次的操作,那下次扩容呢?TiDB 通过底层自动进行分片帮我们解决了这个问题,同时业务量的增加或减少可以通过添加减少节点来处理,代码上基本无改动,这也是我们所期望的。高可用 对于原有的主从 MySQL,并没有配置高可用,我们也对 MHA 等第三方工具做过调研,在发生主从切换后,在新路由信息分发这块也依赖修改网络配置或者数据库中间件(DBproxy),有一定的时间成本,虽然业界有很多中间件方案,但也很多问题和技术成本,所以这个方向并不是我们首选,之前的方式是一旦 MySQL 主数据库宕机,我们通过内部的监控系统获知,再进行更改 Keepalived + HAproxy 配置,即使人为响应非常及时,其影响的时间也早已超过业务的容忍。而选择天然的多节点 TiDB 自然就避免了这一点,配合网络 HAproxy 完全实现了 DB 层面的高可用。前一段时间,我们内部监控系统升级,其中一台机器没有对 TiKV 添加监控,而该 TiKV 节点由于硬件原因服务宕了几天,我们业务上也未感知,当然这是我们的失误,但是也侧面反应了 TiDB 自身的高可用机制。OSC 问题 众所周知,一个可编程的内容管理平台,增加字段是再正常不过的业务场景,虽然 MySQL 5.7 已经支持 Online DDL,但实际操作还有很多限制,并且经常性导致从库延迟,TiDB 支持在线 DDL,加字段的过程是非阻塞的,平滑的,业务无感知的,这也是我们选择它的一个重要原因。迁移 TiDB MySQL 数据迁移到 TiDB 上,我们使用 mydumper、loader 对数据导入导出。而后续数据的增量同步,PingCAP 公司提供了 Syncer 工具回放 MySQL 传来的 Binlog 日志来模拟 MySQL 的 Slave。让我们感到惊喜的是,它支持多个 MySQL 同时同步到一个 TiDB,刚开始,我们就采用这种方式来搭建环境,这种便捷的方案可以把我们的精力从数据迁移中解放出来,更多关注在业务测试和试用上,经过完善的业务测试后,我们发现 TiDB 高度兼容 MySQL,于是我们逐步将每个库流量切到 TiDB 上,整个数据迁移、流量迁移都非常友好便捷。实际迁移过程中,只遇到了由于部分原有 MySQL 版本过低导致 Binlog 格式不兼容的问题,除此之外其他都很顺利。节点迁移 TiDB 的线上节点迁移。我们线上的部分 TiDB 是使用 Binary 部署的,且版本过老(2016 年的版本),无法进行及时的自动化升级,因此当我们涉及到机房迁移的时候,会担心是否会影响服务。好在迁移涉及的是无状态的 TiDB 节点和存储元数据的 PD 节点,在对 PD 节点一上一下逐步增加减少验证无误后,启动新机房中的 TiDB 节点,经 Haproxy 层灰度上线几台后,下掉原有的 TiDB 节点,完成迁移。官方强力支持 当然,虽然官方提供的迁移方案很友好,但在学习了解、实际操作阶段也免不了磕磕绊绊,之前很长一段时间内我们并没有与 PingCAP 公司取得联系,但当我们出现迁移问题的时候,我们选择求助官方,他们非常迅速的响应了我们,解决了线上迁移因 etcd 导致的 PD 节点去除故障,而且还安排了架构师来我们公司进行技术交流,锦上添花不如雪中送炭,当时我们在与官方人员沟通时,深深体会到了这一点。TiDB 数据库的稳定性还是非常不错的,在我们和官方取得联系的时候,我们使用的 beta4 版本也已稳定运行了近 220 天。TiDB 目前环境 目前我司有三套 TiDB 在使用,均为 OLTP 业务。其中前两套使用 Binary 安装的远古版本(beta4),第三套才是 TiDB GA 版本,通过官方 Ansible 进行的部署。在与官方沟通中获知,两套远古版本(beta4)由于距离现有版本太多需要进行迁移升级,官方也十分愿意为我们提供技术支持,在此,就先谢过官方的帮助,显然,对我们来说后续也少不了麻烦。一点吐槽 TiDB 的出现,帮我们跳过了传统的 Sharding + proxy 的路线,给我们节省了巨大的技术成本,我们对 TiDB 非常的钟爱,但在接触、使用 TiDB 过程中,我们也遇到一些问题。即使官方对于服务器配置有明确的要求(SSD 以上硬盘),但对我们公司内来说,刚开始很难申请到高性能的机器来进行 TiDB 功能性测试,和学习、熟悉 TiDB 的搭建、扩容、迁移等操作,于是在刚开始,我们拿几台低性能的测试机,使用 loader 导入数据进而进行增量测试的时候,出现了报错,TiDB 的报错信息并没有提醒我这是由于机器性能低引起的,而在官方文档中,也没有这方面的引导,这导致我们反复导入测试多次,问题依然存在,后才考虑可能是机器性能导致的(官方已经在最新的 ansible 安装脚本中做了硬件的性能检测),于是申请了几台高性能机器进行复测,验证确实是因为机器性能导致。后在与官方人员沟通时得知,整个 TiDB 架构是面向未来、面向海量数据高并发场景,底层存储技术(如数据定位 seek)都是针对当前主流的 SSD 进行设计和优化的,不会对传统的 SATA/SAS 机械硬盘再进行优化。至于官方文档和报错信息,他们也在持续快速的更新中。展望 在与官方进行详细沟通,听取官方架构师的分享后,我们近期打算再上 2-3 套 TiDB 集群对众多不同业务线主从 MySQL 进行整理归纳,这样不但可以逐步统一、规范数据库的使用,而且还可以大大减少机器资源浪费,运维成本,同时借助 TiDB 实现我司数据库的高可用。而在听取分享的其他部门的小伙伴也已经行动起来,对于我司一个重点 OLTP 新项目,已经确定使用 Cloud TiDB(在 K8S 上部署 TiDB)作为数据库系统。同时我们了解到,TiDB 不仅仅满足 OLTP 业务,它还能满足很多 OLAP 业务场景,听完分享后,大数据组小伙伴也在跃跃欲试中。未来,我们将加强与 PingCAP 官方的沟通交流,进行更深入的研究和开发,持续提升 TiDB 的性能,将它全面应用到凤凰网业务中。"}, {"url": "https://pingcap.com/cases-cn/user-case-g7/", "title": "TiDB 在 G7 的实践和未来", "content": " 作者简介:廖强,曾供职于百度,负责百度钱包的分布式事务数据库,基础架构和收银台。现 G7 汇通天下技术合伙人,负责金融产品研发、运维和安全。 背景 2010 年,G7 正式为物流运输行业提供面向车队管理的 SaaS 服务,经过持续创新,通过软硬一体化的产品技术能力,致力于数字化每一辆货车,以实时感知技术创造智慧物流新生态。G7 为客户提供全方位的数据服务、智能的安全和运营管理、手机管车、数字运力、以及 ETC、油和金融等增值服务。目前,G7 连接了 600,000 辆货车,每天行驶 6500 万公里(可绕地球赤道 1625 圈),13.5 亿个轨迹点和 2,200 万次车辆事件触发,并且以直线速度飞速增长。G7 每天产生的车辆行驶、状态、消费等数据超过 2T,飞速增加的车辆、数据类型和复杂的金融业务,使得数据库的事务、分析、扩展和可用性面临巨大的挑战。在大量的车辆信息和轨迹相关数据业务中,当前我们通过 Spark、Hive 等对大量原始数据进行分析后,存入阿里云 DRDS,对外提供基础数据接口服务。由于清洗后的数据量依然很大,使用 DRDS 的存储成本非常高,且面对很多 OLAP 的查询时,效率不如人意。而在金融和支付这种复杂业务场景中,面临 CAP 中 C 和 P 的挑战。在以往的工作中,支付系统由于面临强一致性事务的高峰值写入问题,采用了 2PC+MySQLXA(单个 MySQL 作为参与者,上层增加 Proxy 作为协调者)完成了分布式事务数据库的方案。但是这种方案在 Partition 中,极为麻烦。同时,运营和风控系统经常需要做相对复杂的查询,要么通过 MySQL+ETL+OLAP 数据库(成本高),要么容忍查询的效率问题。探索 G7 的技术团队一直在寻找一种能解决上述问题的数据库。要找到这样一种数据库,除了需要满足上述需求以外,还需要满足另一个需求:可维护性和易迁移性。这要求该数据库具体需要满足如下几个要求: 兼容 MySQL 协议,使得数据库的变更对上层业务透明,这个有多重要,做过基础架构升级落地的同学应该深有感触。 支持 MySQL 的主从同步机制,使得数据库的变更可以平滑逐步升级,降低变更风险。 必须是开源的。数据库的稳定需要付出很大的精力和时间,在这个过程中,或多或少都出现问题。出现问题不可怕,可怕的是无法定位和解决问题,只能依赖“他人”。数据库的一个 BUG 对“他人”来说,可能是一个小问题,对 G7 业务而言,可能是一个巨大的灾难。当“屁股”不在同一个板凳上时,我们需要对数据库有很强的掌控力。 开源的同时,背后一定需要有一个有实力的技术团队或商业公司的全力投入。在见识了不少“烂尾”和“政绩”的开源项目后,只有一个稳定全职投入的技术团队或商业公司,才能最终让这个数据库越来越好。 在这么多限制和需求的情况下,TiDB+TiSpark 很快进入我们的视线,并且开始调研。通过和 TiDB 技术人员的交流,除了满足上述的需求外,如下技术细节使我们一致认为可以选择这样的方案: 将 MySQL 架构中 Server 和 StorageEngine 概念进一步松耦合,分为 TiDB 和 TiKV,水平扩展性进一步提升。 定位于 Spanner 的开源实现,但是没有选择 Multi-Paxos,而是采用了更容易理解、实现和测试的 Raft,使得我们在分布式一致性上少了很多担忧。 使用 RocksDB 作为底层的持久化KV存储,单机的性能和稳定性经过了一定的考验。 基于 GooglePercolator 的分布式事务模型,在跨区域多数据中心部署时对网络的时延和吞吐要求比较高,但我们目前没有这样的强需求。 初体验——风控数据平台 该风控数据平台是将众多的业务数据做清洗和一定复杂度的计算,形成一个客户在 G7 平台上金融数据指标,供后续的风控人员来查询客户的风险情况,同时支撑运营相对复杂的查询。由于数据量较大,传统的关系型数据库在扩展性和处理 OLAP 上不符合该业务的需求;同时该业务面向内部,在一开始不熟悉的情况下,不会影响到客户,因此,我们决定在这个业务上,选择使用 TiDB。风控数据平台开始于 2017 年 8 月,2017 年 10 月上线了第一个版本,对线上用户提供服务。最开始使用的 TiDB RC4 版本,后升级到 Pre-GA,我们计划近期升级到 GA 版本。系统架构如下所示,整个流程非常简洁高效。在使用的过程中,我们还是遇到了不少兼容性相关的问题。为了增加我们对 TiDB 的理解,我们和 TiDB 技术团队取得联系,积极参与到 TiDB 项目中,熟悉代码和修复部分兼容性和 BUG 相关的问题。以下是我们在实践过程中解决的问题: 修复 INFORMATION_SCHEMA.COLUMNS 中 ,COLUMN_TYPE 不支持 UNSIGNED 的兼容性问题。https://github.com/pingcap/tidb/pull/3818 修复 IGNORE 关键字对 INSERT、UPDATE和DELETE 的兼容性问题。https://github.com/pingcap/tidb/pull/4376https://github.com/pingcap/tidb/pull/4397https://github.com/pingcap/tidb/pull/4564 修复 Set 和 Join 中存在的 PanicBUG。https://github.com/pingcap/tidb/pull/4326https://github.com/pingcap/tidb/pull/4613 增加了对 SQL_MODE 支持 ONLY_FULL_GROUP_BY 的特性。https://github.com/pingcap/tidb/pull/4613 这里仍然存在一个与 MySQL 不兼容的地方。当开启事务后,如果 insert 的语句会导致主键或者唯一索引冲突时,TiDB 为了节省与 TiKV 之间的网络开销,并不会去 TiKV 查询,因此不会返回冲突错误,而是在 Commit 时才告知是不是冲突了。希望准备使用或关注 TiDB 的朋友能注意到这一点。后来我们咨询 TiDB 官方,官方的解释是:TiDB 采用乐观事务模型,冲突检测在执行 Commit 操作时才会进行。感谢在初体验过程中,TiDB 团队非常认真、负责和快速的帮助我们排查和解决问题,提供了非常好的远程支持和运维建议。在其它业务中的推广规划 2018 年初,运维团队和每一个业务方进行了一次需求沟通,业务方对 TiDB 的需求越来越强烈。我们正沿着如下的路径,让 TiDB 发挥应用到更多的场景中。 将 TiDB 作为 RDS 的从库,将读流量迁移到 TiDB; 从内部业务开始,逐步将写流量迁移到 TiDB; 将更多 OLAP 的业务的迁到 TiSpark 上; 合作开发 TiDB 以及 TiDB 周边工具。 参与 TiDB 社区的 Tips 善用 GDB 工具去了解和熟悉 TiDB 的代码结构和逻辑。 初始选择一些 Issue,去分析和尝试修复。 利用火焰图去关注和优化性能。 如果没有读过周边的论文,可以试着去读一读,加深对系统原理的理解。 积极参与 TiDB 的社区活动,加深与 TiDB 核心研发的沟通。 有合适的业务场景,可以多试用 TiDB,拓宽 TiDB在 不同场景下的应用实践。 "}, {"url": "https://pingcap.com/cases-cn/user-case-mobikok/", "title": "TiDB 在 Mobikok 广告系统中的应用和实践", "content": " 作者:rayi,深圳可可网络服务端架构负责人 公司介绍 Mobikok(可可网络)成立于 2013 年,是一家快速成长的移动互联网营销公司,专注于移动 eCPM 营销。总部在中国深圳,聚焦于订阅 offer 的海外流量变现业务。Mobikok 提供的接口方式支持各类手机端流量(API、SDK、Smartlink),RTB(实时竞价系统)对接海外的 DSP(Demand-Side Platform,需求方平台)高效优化客户的广告效果。截止目前,系统已对 2 亿用户进行广告优化,已接入上百家广告主以及上百家渠道,Mobikok 致力于高效,便捷,专业的帮助广告主以及渠道互惠共赢。场景介绍:SSP 系统 订阅 SSP(Sell-Side-Platform)平台当前业务主要分为:SDK、Smartlink、Online API 以及 Offline API;在当前 SSP SDK 业务系统当中,累计用户已达到 2 亿,最初使用的是 MySQL 主从分表的方式存储用户数据,随着数据量的增加,MySQL 单机容量以及大数据量查询成为了瓶颈;当单表数据达到 2 千万以上时,单机 MySQL 的查询以及插入已经不能满足业务的需求,当访问量到一定阶段后,系统响应能力在数据库这一块是一个瓶颈。一次很偶然的机会在 GitHub 上面了解到 TiDB,并且因为现在业务系统当中使用的 Redis 集群是 Codis,已在线上稳定使用两年,听闻 TiDB 创始团队就是之前 Codis 的作者,所以对 TiDB 有了极大的兴趣并且进行测试。通过测试单机 MySQL 和 TiDB 集群,当数据量达到数千万级别的时候发现 TiDB 效率明显高于 MySQL。所以就决定进行 MySQL 到 TiDB 迁移。迁移后整体架构图:引入 TiDB 在选择使用替换 MySQL 方案当中。我们主要考虑几点: 支持 MySQL 便捷稳定的迁移,不影响线上业务; 高度兼容 MySQL,少改动代码; 支持水平弹性部署服务以及在线升级; 支持水平扩展业务; 成熟的配套监控服务。 TiDB 数据库整体集群配置:2* TiDB、3* TiKV、3* PD。从 12 月初正式上线到目前为止,TiDB 稳定运行四个多月,最高 QPS 达到 2000,平均 QPS 稳定在 500 左右。TiDB 在性能、可用性、稳定性上完全超出了我们的预期,但是由于前期我们对 TiDB 的了解还不深,在此迁移期间碰到的一些兼容性的问题,比如 TiDB 的自增 ID 的机制,排序的时候需要使用字段名等,咨询 TiDB 的工程师都很快的得到了解决,非常感谢 TiDB 团队的支持以及快速响应。下图是当前集群的 Grafana 展示图:后续计划 使用 TiDB 对于像我们这样可预期核心数据会暴增的场景,有非常大的意义。在后端支撑力量有限时,业务暴增时只需要增加机器,而不是频繁重构业务,让我们有更多精力在自己的业务上耕耘,增加我们的行业竞争力。未来我们还有 ADX(Ad Exchang,广告交易平台)和 DSP 业务,需要处理海量的用户数据以及广告数据。目前统计数据这一块当前业务当中使用的是 Spark Streaming,通过和 TiDB 开发团队沟通,官方 TiSpark 可直接引入到当前统计 Spark 群集当中,非常期望在后续开发当中使用 TiSpark。问题建议 在实际应用当中,因为我们切换的并不是只有用户数据表,还迁移了关于广告业务、渠道业务基础数据表。由于 TiDB 是一个分布式数据库,对于一些小表以及 count(*) 操作会影响效率,后来咨询 TiDB 官方得知,TiDB 有不同的隔离级别,SQL 也有高低优先级,如果有全表扫描的需求,可以使用低的隔离级别或者是低的优先级。将来我们就可以直接所有线上业务使用 TiDB 进行替换,最后还是非常感谢 TiDB 团队的支持与帮助。"}, {"url": "https://pingcap.com/cases-cn/user-case-linkdoc/", "title": "TiDB 在零氪科技(LinkDoc)大数据医疗系统的实践", "content": " 作者介绍:杨浩,现任零氪科技运维&安全负责人,曾就职于阿里巴巴-技术保障部-CDN。专注 CDN、安全、自动化运维、大数据等领域。 公司介绍 零氪科技作为全球领先的人工智能与医疗大数据平台,拥有国内最大规模、体量的医疗大数据资源库和最具优势的技术支撑服务体系。多年来,零氪科技凭借在医疗大数据整合、处理和分析上的核心技术优势,依托先进的人工智能技术,致力于为社会及行业、政府部门、各级医疗机构、国内外医疗器械厂商、药企等提供高质量医疗大数据整体解决方案,以及人工智能辅助决策系统(辅助管理决策、助力临床科研、AI 智能诊疗)、患者全流程管理、医院舆情监控及品牌建设、药械研发、保险控费等一体化服务。LinkDoc 的主要应用场景 LinkDoc 通过将患者真实的病例数据和算法模型应用于肿瘤治疗,构建精准的诊疗模型并提供数据支持,从而辅助医院管理决策、辅助科研、辅助临床诊疗。目前 Hubble 系统“肺癌淋巴结跳跃转移风险预测”模块可避免肺癌病人由于误判而导致提前 8-10 个月的复发,每年能让近两万病人的生命再延长 8-10 个月。Hubble 系统“AI - 肺结节智能诊断”模块全自动地识别 CT 影像中所有的结节,识别率达 91.5%。LinkDoc 希望凭借医疗大数据整合、处理和分析上的核心技术优势,以互联网人工智能上的创新研发,提升中国医师的全球医学水准,并通过支持药物研发与医疗保险行业的发展,让每一位患者享有普惠、精准的医疗服务。支撑 LinkDoc 业务的底层数据库平台也面临着医疗行业新领域的技术 & 业务挑战,如数据量的快速增长(亿级别)、大数据量下的清洗逻辑的数据擦写、分析型事务对数据库的读压力都要求我们在数据库平台进行重新探索,选择一款适合医疗大数据业务的数据库解决方案。选择 TiDB 业务痛点 数据量大,单实例 MySQL 扩容操作复杂; 写入量大,主从延时高,由于业务对数据有低延时的要求,所以传统的 MySQL 主从架构在该项目下不能满足需求,大量数据写入下主库成为性能瓶颈; 随着数据量越来越大,部分统计查询速度慢; 分库分表业务开发和维护成本高。 业务需求 高可靠性 & 稳定性; 可扩展性,可随数据量 & 请求量增长快速提升存储 & 请求处理能力; 更低的延时。 方案调研 未选择 TiDB 之前我们调研了 MyCAT、Cobar、Atlas 等中间件解决方案,这些中间件整体来说就是让使用者觉得很“拧巴”,从社区支持、MySQL 功能兼容、系统稳定性上都不尽人意,需要业务做大量改造,对于快速发展的公司来说切换成本太高。在 LinkDoc 首席架构师王晓哲的推荐下我们调研了 TiDB, TiDB 的如下特性让我们眼前一亮: 兼容绝大部分 SQL 功能(意味着业务可以简单改造后平滑迁移至 TiDB); 水平扩展能力; 分布式事务; 故障快速恢复能力; 监控指标覆盖度。 上线 TiDB 兼容性测试 经过兼容性测试后我们对业务做了如下简单改造: Blob 类型数据迁移至 HBase 做 key-value 存储; Batch delete 改成小批量多次操作,一批删除 1000 条。 灰度上线 由于业务对于主从同步延时要求较高,我们采用业务双写的方案切换了我们的第一个应用。灰度第一阶段业务同时写 MySQL、TiDB,读走 MySQL,并验证数据一致性,经过2周的验证后我们灰度第二阶段。灰度第二阶段业务双写 TiDB、MySQL,读业务走 TiDB。经过一个月的业务验证后我们彻底下掉了 MySQL。系统架构 上线过程中也遇到一个小坑,之前用的阿里云普通实例 + SSD 云盘跑 TiDB,在该配置下经常会遇到性能抖动问题,在 PingCAP 同学的建议下我们更换了阿里云本地 SSD 型机型,目前系统运行良好。系统配置 & 架构如下:生产集群部署情况(机器基于阿里云):目前现状和下一步规划 目前 TiDB 在 LinkDoc 已承载数据量最大的两个业务。平时 QPS 6K,峰值 12K。后续将使用 TiDB 承载更多大数据量业务库, 并调研 TiSpark。通过 TiDB 构造成一个兼容分析型和事务型的统一数据库 HTAP 平台。致 PingCAP 非常感谢 PingCAP 小伙伴们的大力支持,从硬件选型、业务优化、系统培训到上线支持 PingCAP 都展现了热情的服务态度、专业的技术能力,帮助 LinkDoc 顺利上线 TiDB,解决系统难题,支持业务快速发展。相信在这样一群小伙伴的努力下 TiDB 会越来越成熟、承载更多的业务场景,用技术创造奇迹。"}, {"url": "https://pingcap.com/cases-cn/user-case-yimian/", "title": "TiDB 助力一面数据实现消费领域的决策分析平台", "content": " 作者:刘畅,一面数据高级 AI 工程师。 公司介绍 深圳市一面网络技术有限公司(下称:一面数据)是一家为消费领域的领导企业提供实时、精准、全面的数据洞察和决策指导的创新型企业,利用人工智能和算法,进行自然语言处理,语义情感分析,回归预测模型等,帮助客户实现精准产品运营和预测市场变化。一面数据服务于国内外一流企业,包括世界最大的对冲基金、国际一线汽车品牌、快消品龙头厂商,以及时尚鞋服大牌等。改造前系统架构 一面数据的核心 IT 系统覆盖了从数据获取、数据清洗处理、数据建模到数据可视化的全套数据分析流程。核心系统每天有海量从互联网采集的公开数据和来自企业内部的数据,对数据存储的容量、扩展性和可用性都有很高的要求。起初,一面数据的核心系统采用的是多个 MySQL 实例和一个 Cassandra 集群。MySQL 多实例集群主要存储指定特征的爬虫数据,Cassandra 主要存储数据量大、不适合存储 MySQL 的全页面缓存的数据。在数据量/请求量小的时候系统运行正常。下图为一面数据改造前系统构架图:随着数据量的增长,逐渐暴露出很多问题: MySQL:随着数据增长,存储容量接近单机的磁盘极限,单机的磁盘 IO 繁忙且易阻塞,查询性能难以满足业务增长的需求。数据量大了以后,传统的 MySQL 水平扩展能力弱,性能和稳定性容易产生问题,在数据量和访问量增长到一定阶段将无法满足常见的 OLAP 场景分析需求。技术团队通过诊断系统性能问题,认识到现有数据库已经成为瓶颈。 Cassandra:Cassandra 对磁盘 IO 和内存要求高,添加一个实例,需要从其他实例迁数据,对网络带宽、 磁盘要求特别高。另外 CQL 支持的特性太少,业务开发麻烦,例如不能联表,不支持主键之外的索引,对主键以外的查询比较困难,虽然有 Secondary Index,但是使用限制大。生态圈不完善,例如很难找到好用的监控。 改造后的系统架构 引入 TiDB 替换 MySQL 和 Cassandra 为从根本上解决以上问题,一面数据的技术团队决定通过增加部署一套高性能的数据库系统,以解决当前业务的痛点。 在评估和验证了 MySQL Sharding 和 MongoDB 等传统技术手段之后,团队认识到:基于 MySQL Sharding (即利用 MySQL 中间件分库分表) 架构在高可用安全能力,业务和查询的灵活支持以及运维管理难度和成本上都不尽如人意,有着诸多架构上和技术上的缺陷;而 MongoDB 比较适合存储爬虫数据,但迁移成本高,不管是数据还是应用程序都需要做侵入性修改和调整,难度和开发成本骤升。另外,作为 NoSQL 数据库,MongoDB 不支持 SQL 和 JOIN ,对 BI 工具的支持也不完善,数据分析师们无法直接使用。 最终从满足业务需求、降低切换成本和减少运维成本等角度考虑,一面数据选择了分布式关系型数据库-TiDB 作为业务的首选事务型数据库。TiDB 支持包括跨行事务,JOIN 及子查询在内的绝大多数 MySQL 的语法,用户可以直接使用现有的 MySQL 客户端连接。如果现有的业务已经基于 MySQL 开发,大多数情况不需要修改代码即可直接替换单机的 MySQL。同时现有的大多数 MySQL 运维工具(如 PHPMyAdmin, Navicat, MySQL Workbench 等),以及备份恢复工具(如 mysqldump, mydumper / myloader)等都可以在 TiDB 直接使用,这也让开发运维人员不用关注数据库 scale 的细节问题,专注于业务开发,极大的提升研发的生产力。下图为一面数据改造后系统构架图:一面数据的生产环境部署了数十个 TiKV 节点及几个 TiDB 节点。迁移原有 MySQL 集群数据时使用 Percona 的 mydumper 以及 TiDB 专有优化的 loader 工具,逐个爬虫进行迁移。目前 TiDB 集群存储了接近数十 TB 的数据,把另外几个应用迁移完成后将会每日新增近亿条记录。完成迁移以后,系统不再需要维护多个 MySQL 实例以及 Cassandra 集群,运维成本大幅缩减,监控使用 Prometheus/Grafana,并且可以通过 Prometheus 的 AlertManager 定制规则复杂的报警规则。这些改变都让一面数据的爬虫存储侧的工作便利许多,可以让一面数据的研发把精力更多放在业务研发而不是运维多个不同技术栈的复杂集群。未来的架构规划 目前 TiDB 新增了 TiSpark 组件,并且在 TiKV 层实现了 Spark 的下推算子,使得可以直接在 TiDB 集群上跑 Spark 程序,这样可以省去 ETL 的步骤。后续一面数据也考虑深入使用 TiSpark 组件,让一面数据的整个系统增加一定的实时复杂查询的能力。长远来看,可以把现在 ElasticSearch,Impala,Hive 的业务都迁移到 Spark 集群上,这样一方面统一了分析侧的技术栈,另一方面连接了 Spark 丰富庞大的生态。下图为一面数据未来系统构架图:在一面数据 CTO 张锦杰看来:“TiDB 水平扩展性、兼容 MySQL 是非常好的特性,对需要使用关系型数据库作为存储方案的业务有极大的诱惑力,避免了传统分表、分库方案带来的上层应用的复杂性,解决了我们目前迫切的关系型数据存储的需求。”"}, {"url": "https://pingcap.com/blog/best-practices-for-developing-applications-with-tidb/", "title": "Best Practices for Developing Applications with TiDB", "content": " 1. Introduction: target audiences and why this blog exists Target audience: Database architects, database administrators, infrastructure engineers, or application developers.As an open source distributed database, in most cases, TiDB can serve as a scale-out MySQL database without manual sharding. However, because of its distributed nature, there are some differences between traditional relational databases like MySQL and TiDB. For detailed information, see TiDB Compatibility with MySQL.This article shows you how to efficiently develop high-quality applications with TiDB. You’ll get an in-depth look at several TiDB features that will save you hours of work and prevent some common coding errors. You’ll also learn important best practices from the many TiDB users that have come before you.This article is also useful if you’re migrating your current applications to TiDB.2. Transaction in TiDB and what it means to you This section is an in-depth look at transaction-related issues in TiDB and provides solutions to prevent or resolve them.2.1 Snapshot isolation in TiDB: Jepsen tested! And a few caveats TiDB supports snapshot isolation (SI) as is shown in TiDB Passes Jepsen Test for Snapshot Isolation and Single-Key Linearizability. For more information about the TiDB transaction model and translation level, see Transaction Model and TiDB Transaction Isolation Levels.2.2 Solution to hot record scenarios where the same row is modified concurrently TiDB uses the optimistic locking mechanism. This means that TiDB automatically retries the transactions that meet conflicts when being committed and backs off when TiDB handles concurrent transactions. However, if you specify the SELECT FOR UPDATE statement or disable the tidb_disable_txn_auto_retry variable, this backoff mechanism becomes invalid, and the transactions committed later are rolled back.SELECT FOR UPDATE is applied in hot record scenarios including the following: Counter, in which the value of a field is continuously increased by 1. Seckilling, in which newly-advertised goods quickly sell out. Account balancing in some financial scenarios with “hot Region”, in which the same row of data is modified concurrently. Generally, traditional standalone DBMSs use pessimistic locking to implement SELECT FOR UPDATE. After a transaction starts, they check locks. If the lock required by the transaction and the current lock on the data are incompatible, a lock wait occurs, and the transaction can be executed after the current lock is released. TiDB executes SELECT FOR UPDATE just like setting the lock wait time to 0 in a pessimistic locking system, and a transaction encountering lock conflicts fails to commit.To sum up, TiDB doesn’t apply to scenarios where the same row of data is modified concurrently. Using SELECT FOR UPDATE in a transaction can guarantee data consistency, but only the transaction committed earliest among the concurrently-executed transactions can be executed successfully. TiDB rolls back the remaining requests.The best practice to handle a hot record scenario is to transfer and implement the counter feature in the cache (like Redis and Codis).In a database with pessimistic locking applied, the concurrent SELECT FOR UPDATE transactions queue up and are executed serially. Therefore, the performance is not good. However, handling the counter with the cache improves performance.2.3 “Nested transaction” 2.3.1 Nested transactions in most RDBMS products According to the ACID (Atomicity, Consistency, Isolation, Durability) theory, concurrent transactions should be isolated from each other to avoid mutual interference. This means that transactions cannot be “nested.”At the read committed (RC) isolation level, if multiple reads exist in the same transaction, the data is read each time the data is committed. When multiple transactions execute concurrently, multiple read results in a transaction may be very different. These are called “non-repeatable reads.”Most RDBMS products use RC as the default isolation level. However, sometimes database application developers don’t pay attention to the isolation level setting. They even treat non-repeatable reads as a feature, and develop applications based on “nested transactions.”2.3.2 Nested transaction model does not apply to TiDB This section gives an example to explain why the nested transaction model does not apply to TiDB.2.3.2.1 Example for a set of nested transactionsThe following diagram shows the implementation logic for a set of nested transactions. At the top, session 1 and session 2 are two sessions initiated by the application. Down the left side, T1- T8 constitute a timeline. The logic is as follows: The application opens session 1 at T1, and then performs a query. (Note that in the MySQL protocol, the first statement that follows begin and accesses the table data is the start of a transaction.) From T3 to T5, the application opens session 2, writes a row of data, and then commits the data. The application continues to manipulate session 1. At T6, it tries to update the data just written. At T7, it commits the transaction opened at T2. At T8, session 1 executes a query statement to check the val value of the corresponding row for k=1 that is written by session 2 at T4. At the RC isolation level, the returned result for the query at T8 is 102, which seems to meet the feature requirement for a nested transaction. But this process does not conform to reality. The diagram above only uses a single thread to simulate a nested transaction. But in the concurrent requests in practical application scenarios, multiple transactions are alternately executed and committed in the timeline. In this case, we can’t predict the execution result of the nested transactions.At the snapshot isolation (SI) or repeatable read (RR) isolation levels, the returned result of any reads before being committed or rolled back corresponds to the consistency status when the transaction starts. The data accessible to the transaction in session 1 at T2 is determined. This is just like taking a snapshot of the database at T2. Even though session 2 is open from T3 to T5, and some data is written in and committed, this does not affect the data read by session 1 at T6. At T6, the row with k=1 has not been read, so Row 0 is updated. At T8, the returned value for the query is 2. At the SI or RR isolation levels, the degree of isolation is higher, and for concurrent requests, the results are predictable.2.3.2.2 Solution to the exampleFor these cases of nested transactions, if you only require that session 1 update the table after session 2 writes data into the table, you only need to control the application logic by adding a commit step after querying the statement at T2. This commits the query transaction in a timely manner. We then perform the rest of the steps on the timeline after T2.2.4 No support for the PROPAGATION_NESTED in the Java Spring Framework (relying on the savepoint mechanism) The Java Spring Framework supportsPROPAGATION_NESTED propagation, and it starts a nested transaction, which is a subtransaction started independently of the existing transaction. When a nested transaction starts, the database records a savepoint. If the nested transaction fails to be executed, the database rolls back the transaction to the savepoint status. The nested transaction is part of the outer transaction, and it is committed with the outer transaction. The following commands show a savepoint mechanism:mysql> BEGIN; mysql> INSERT INTO T2 VALUES(100); mysql> SAVEPOINT svp1; mysql> INSERT INTO T2 VALUES(200); mysql> ROLLBACK TO SAVEPOINT svp1; mysql> RELEASE SAVEPOINT svp1; mysql> COMMIT; mysql> SELECT * FROM T2; +------+ | ID | +------+ | 100 | +------+ TiDB does not support the savepoint mechanism, and therefore it does not support the PROPAGATION_NESTED propagation …"}, {"url": "https://pingcap.com/blog-cn/dm-source-code-reading-8/", "title": "DM 源码阅读系列文章(八)Online Schema Change 同步支持", "content": " 本文为 DM 源码阅读系列文章的第八篇,上篇文章 对 DM 中的定制化数据同步功能进行详细的讲解,包括库表路由(Table routing)、黑白名单(Black & white table lists)、列值转化(Column mapping)、binlog 过滤(Binlog event filter)四个主要功能的实现。本篇文章将会以 gh-ost 为例,详细地介绍 DM 是如何支持一些 MySQL 上的第三方 online schema change 方案同步,内容包括 online schema change 方案的简单介绍,online schema change 同步方案,以及同步实现细节。MySQL 的 Online Schema Change 方案 目前有一些第三方工具支持在 MySQL 上面进行 Online Schema Change,比较主流的包括 pt-online-schema-change 和 gh-ost。这些工具的实现原理比较类似,本文会以 gh-ost 为例来进行分析讲解。从上图可以大致了解到 gh-ost 的逻辑处理流程: 在操作目标数据库上使用 create table ghost table like origin table 来创建 ghost 表; 按照需求变更表结构,比如 add column/index; gh-ost 自身变为 MySQL replica slave,将原表的全量数据和 binlog 增量变更数据同步到 ghost 表; 数据同步完成之后执行 rename origin table to table_del, table_gho to origin table 完成 ghost 表和原始表的切换 pt-online-schema-change 通过 trigger 的方式来实现数据同步,剩余流程类似。在 DM 的 task 配置中可以通过设置 online-ddl-scheme 来配置的 online schema change 方案,目前仅支持 gh-ost/pt 两个配置选项。DM Online Schema Change 同步方案 根据上个章节介绍的流程,pt 和 gh-ost 除了 replicate 数据的方式不一样之外,其他流程都类似,并且这种 native 的模式可以使得 binlog replication 几乎不需要修改就可以同步数据。但是 DM 为了减少同步的数据量,简化一些场景(如 shard tables merge)下的处理流程,并做了额外的优化,即,不同步 ghost 表的数据。继续分析 online schema change 的流程,从数据同步的角度看有下面这些需要关注的点: 原始表的增量数据同步模式有没有变化 ghost 表会产生跟原始表几乎一样的冗余 binlog events 通过 rename origin table to table_del, table_gho to origin table 完成 ghost 表和原始表的切换 如果使用 ghost 表的 alter DDL 替换掉 rename origin table to table_del, table_gho to origin table ,那么就可以实现我们的不同步 ghost 表数据的目的。DM Online Schema Change 同步实现细节 Online schema change 模块代码实现如下: gh-ost 同步代码实现 pt-online-schema-change 同步代码实现 DM 将 同步的表分为三类: real table - 原始表 trash table - online schema change 过程中产生的非关键数据表,比如以 _ghc, _del 为后缀的表 ghost table - 与原始表对应的经过 DDL 变更的数据表,比如以 _gho 为后缀的表 当 DM 遇到 DDL 的时候,都会 调用 online schema change 模块的代码进行处理,首先判断表的类型,接着针对不同类型作出不同的处理: real table - 对 rename table statement 进行模式检查,直接返回执行 trash table - 对 rename table statement 做一些模式检查,直接忽略同步 ghost table 如果 DDL 是 create/drop table statement ,则 清空内存中的残余信息后忽略这个 DDL 继续同步 如果 DDL 是 rename table statement ,则 返回内存中保存的 ghost table 的 DDLs 如果是其他类型 DDL,则把这些 DDL 保存在内存中 下面是一个执行示例,方便大家对照着来理解上面的代码逻辑: Section 1: 使用 create table like statement 创建 ghost table,DM 会清空内存中 online_ddl._t2_gho 对应的 DDL 信息 Section 2: 执行 alter table statement,DM 会保存 DDL 到内存中 Section 3:trash table 的 DDLs 会被忽略 Section 4:遇到 ghost table 的 rename table statement 会替换成 Section 2 的 DDL, 并且将该 DDL 的 table name 更换成对应 real table name 去执行 注意: rename table statement 模式检查主要是为了确保在 online schema change 变更过程中除了 rename origin table to table_del, table_gho to origin table 之外没有其他 rename table statement,避免同步状态的复杂化。小结 本篇文章详细地介绍 DM 对 online schema change 方案的同步支持,内容包含 online schema change 方案的简单介绍, online schema change 同步方案,以及同步实现细节。下一章会对 DM 的 shard DDL merge 方案进行详细的讲解,敬请期待。"}, {"url": "https://pingcap.com/blog-cn/tidb-binlog-source-code-reading-1/", "title": "TiDB Binlog 源码阅读系列文章(一)序", "content": " TiDB Binlog 组件用于收集 TiDB 的 binlog,并准实时同步给下游,如 TiDB、MySQL 等。该组件在功能上类似于 MySQL 的主从复制,会收集各个 TiDB 实例产生的 binlog,并按事务提交的时间排序,全局有序的将数据同步至下游。利用 TiDB Binlog 可以实现数据准实时同步到其他数据库,以及 TiDB 数据准实时的备份与恢复。随着大家使用的广泛和深入,我们遇到了不少由于对 TiDB Binlog 原理不理解而错误使用的情况,也发现了一些 TiDB Binlog 支持并不完善的场景和可以改进的设计。在这样的背景下,我们开展 TiDB Binlog 源码阅读分享活动,通过对 TiDB Binlog 代码的分析和设计原理的解读,帮助大家理解 TiDB Binlog 的实现原理,和大家进行更深入的交流,同时也有助于社区参与 TiDB Binlog 的设计、开发和测试。背景知识 本系列文章会聚焦 TiDB Binlog 本身,读者需要有一些基本的知识,包括但不限于: Go 语言,TiDB Binlog 由 Go 语言实现,有一定的 Go 语言基础有助于快速理解代码。 数据库基础知识,包括 MySQL、TiDB 的功能、配置和使用等;了解基本的 DDL、DML 语句和事务的基本常识。 了解 Kafka 的基本原理。 基本的后端服务知识,比如后台服务进程管理、RPC 工作原理等。 总体而言,读者需要有一定 MySQL/TiDB/Kafka 的使用经验,以及可以读懂 Go 语言程序。在阅读 TiDB Binlog 源码之前,可以先从阅读 《TiDB Binlog 架构演进与实现原理》 入手。内容概要 本篇作为《TiDB Binlog 源码阅读系列文章》的序篇,会简单的给大家讲一下后续会讲哪些部分以及逻辑顺序,方便大家对本系列文章有整体的了解。 初识 TiDB Binlog 源码:整体介绍一下 TiDB Binlog 以及源码,包括 TiDB Binlog 主要有哪些组件与模块,以及如何在本地利用集成测试框架快速启动一个集群,方便大家体验 Binlog 同步功能与后续可能修改代码的测试。 pump client 介绍:介绍 pump client 同时让大家了解 TiDB 是如何生成 binlog 的。 pump server 介绍:介绍 pump 启动的主要流程,包括状态维护,定时触发 gc 与生成 fake binlog 驱动下游。 pump storage 模块:storage 是 pump 的主要模块,主要负载 binlog 的存储,读取与排序, 可能分多篇讲解。 drainer server 介绍:drainer 启动的主要流程,包括状态维护,如何获取全局 binlog 数据以及 Schema 信息。 drainer loader package 介绍:loader packge 是负责实时同步数据到 mysql 的模块,在 TiDB Binlog 里多处用到。 drainer sync 模块介绍:以同步 mysql 为例介绍 drainer 是如何同步到不同下游系统。 slave binlog 介绍:介绍 drainer 如何转换与输出 binlog 数据到 Kafka。 arbiter 介绍:同步 Kafka 中的数据到下游,通过了解 arbiter,大家可以了解如何同步数据到其他下游系统,比如更新 Cache,全文索引系统等。 reparo 介绍:通过了解 reparo,大家可以将 drainer 的增量备份文件恢复到 TiDB 中。 小结 本篇文章主要介绍了 TiDB Binlog 源码阅读系列文章的目的和规划。下一篇文章我们会从 TiDB Binlog 的整体架构切入,然后分别讲解各个组件和关键设计点。更多的源码内容会在后续文章中逐步展开,敬请期待。最后欢迎大家参与 TiDB Binlog 的开发。"}, {"url": "https://pingcap.com/weekly/2019-06-17-tidb-weekly/", "title": "Weekly update (June 10 ~ June 16, 2019)", "content": " Weekly update in TiDB Last week, we landed 40 PRs in the TiDB repository.Added Add a failpoint switch to control expression pushdown in the integration test Add the SplitTable syntax to split table Regions Allow using SHARD_ROW_ID_BITS for tables with auto-increment columns Improved Support loading statistics when the statistics lease is 0 Disallow adding stored generated columns using ALTER TABLE Adjust the datum type when using the index query feedback Set SyncerSessionTTL to a smaller value Add a blacklist to disallow pushing down specific expressions Propagate more possible props to enlarge search space in the planner Support resolving specified lock keys Fixed Fix the create table like operation for partitioned tables Fix an issue about split table by Fix the wrong selectivity for the inner selection in index join Fix the behavior when the output of date_sub is of the Time type Fix some unnecessary loops in failures of running a range Exclude keys that are already locked in LockKeys Fix the issue that the backoff retry stops early Weekly update in TiSpark Last week, we landed 9 PRs in the TiSpark repository.Fixed Fix a potential empty collection issue during Region pre-splitting Improved Add the TiDB/TiKV/PD/Spark version supported for each latest major release Weekly update in TiKV and PD Last week, we landed 37 PRs in the TiKV and PD repositories.Added Add blob-run-mode for Titan Pessimistic-txn Resolve all optimistic locks when a non-pessimistic lock conflict occurs Add metrics Implement pessimistic rollback Implement the LIKE RPN Function Add metrics for Titan Use a different timeout for HTTP clients Add two metrics for gc_worker Improved Reduce allocations in slow hash aggregation Check the iter status when scanning Improve bad_regions and tombstone subcommands for tikv-ctl Always merge left Regions into right ones Use spin::Mutex Use static metrics to reduce the cost of with_label_values Avoid useless calculation and WakeUp messages Expire long remaining detection information in DetectTable to prevent memory leak Disable the label check by default Fixed Acquire the write lock for the write command New contributors (Thanks!) tispark: windtalker tikv: xiamengyu tidb-operator: tkanng pd: b41sh docs-cn: tangenta "}, {"url": "https://pingcap.com/blog/tidb-passes-jepsen-test-for-snapshot-isolation-and-single-key-linearizability/", "title": "TiDB Passes Jepsen Test for Snapshot Isolation and Single-Key Linearizability", "content": " Earlier this year, we reached out to Kyle Kingsbury, the creator of the Jepsen test suite, to conduct an official Jepsen test on TiDB. Even though we’ve done our own Jepsen test before, we thought it was important and valuable to work with Kyle directly to put TiDB through the Jepsen wringer. The results would not only benefit our team as we continue to drive the development of TiDB, but also our users, partners, and community.After several months of close collaboration with Kyle, we are excited that TiDB’s first official Jepsen Test report is published. You can read it HERE.tl;dr: Kyle tested the following versions of TiDB: 2.1.7, 2.1.8, 3.0.0-beta.1-40, and 3.0.0-rc.2. The latest version, 3.0.0-rc.2, passes Jepsen tests for snapshot isolation and single-key linearizability, and previous versions, TiDB 2.1.8 through 3.0.0-beta.1-40, also pass when the auto-retry mechanism is disabled, which was enabled by default. In 3.0.0-rc.2 and future versions of TiDB, the auto-retry mechanism is disabled by default.See the discussion of TiDB’s results on Hacker News.In this blog post, we would like to provide some additional context to the results and share our thoughts on what’s next.To (Auto) Retry or Not (Auto) Retry? Although TiDB tries to be as compatible with MySQL as possible, its nature of a distributed system results in certain differences, one of which is the transaction-model. On the one hand, TiDB adopts an optimistic transaction model that detects conflicts only when transactions are committing, and the transaction will be rolled back if any conflicts are detected. On the other, being a distributed database means that transactions in TiDB will also be rolled back in the event of failures such as network partitions. However, many of the clients that our current customers use to talk to the databases are tailored for traditional databases like MySQL and Oracle, where commits rarely fail at the default isolation level so retry mechanisms are not needed. For these clients, when commits fail, they abort with errors as this is rendered as rare exceptions in these databases. Unlike traditional databases such as MySQL, in TiDB, if users want to avoid massive commit failures, they need to add mechanisms in their own business logic of their applications to handle the related errors, which is the last thing some customers are willing to do.To help our customers solve this problem, we provide a retry mechanism for those failed commits, which automatically retry the conflicting transactions in TiDB. This, however, has its downsides, which we didn’t clearly document. Thanks to Kyle and Jepsen tests for pointing this out, we have updated our documentation to keep the users aware of the difference and its possible consequences, and we disabled the transaction retry mechanism by default by changing the default value of tidb_disable_txn_auto_retry to on.Regarding the decision on whether to enable or disable the retry mechanism by default, we had gone through some changes. For the 3.0.0 GA version, we had originally planned to change the behavior of tidb_disable_txn_auto_retry to make it control the retry over transactions in write conflicts only, and we implemented the same in 3.0.0-beta.1-40. Unfortunately, though, we didn’t update the documentation to reflect the change in time. Thanks to Jepsen tests, we reflected on this change and believed this was not a good design. Therefore, in 3.0.0-rc2, we have adjusted the behavior of tidb_disable_txn_auto_retry to what it used to be prior to 3.0.0-beta.1-40, which is consistent with the documentation.To disable or enable the transaction retry, users can configure both tidb_retry_limit and tidb_disable_txn_auto_retry, with the following differences: tidb_retry_limit = 0 disables all forms of retries. tidb_disable_txn_auto_retry = on only disables retries for explicit transactions, while those auto-committed transactions are still available for the auto-retry if conditions are met. For the auto-committed transactions, auto-retry would not break Snapshot Isolation. What Else? Jepsen report has also found some other issues, some of which are expected behaviors, and some are already fixed or being fixed. Here is more information about these issues:Expected behavior: Crashes on Startup This behavior is expected for TiDB server. We decided to adopt the fail-fast approach when we first designed TiDB, so that DBAs and operation engineers can quickly discover and identify issues at the deployment stage. If the startup fails because of errors with some processes, or the system restarts frequently after startup, an alerting system is available to inform the DBAs and operation engineers timely as well. In a production environment, we use systemd to ensure that the service can be restarted even if there are any issues.Download TiDB Subscribe to Blog Fixed in the 3.0.0-rc.2: Created Tables May Not Exist This issue is fixed in 3.0.0-rc.2 and the pull request to handle the related issue had been merged into the master branch before Kyle filed the issue. The reason for this issue is that if a new cluster is created and multiple TiDB servers are bootstrapped, write conflicts occur because the bootstrapping process changes the global variables in one TiKV server. The issue is triggered only if all the following 3 conditions are met:1) The TiDB server in the DDL Owner node which handles the DDL job is not the first one to finish bootstrapping, and2) Write conflict occurs right before the very same TiDB server finishes bootstrapping, and3) When 2) happens, the TiDB server that finishes bootstrapping receives the DDL requestThe issue is fixed by ensuring that only the TiDB server who is the DDL Owner can process the bootstrap logic.Fixing: Under-Replicated Regions To ensure all regions in the cluster have enough replicas before providing services, we have added a new API to let users know quickly when a cluster is initialized and ready. This new API will be integrated into the deployment tools such as TiDB Ansible and TiDB Operator (a Kubernetes operator) so that we can identify a successful deployment only when the number of replicas is sufficient.Documentation Fixes Admittedly, there is always room for improvement in our documentation. Thanks to Jepsen tests, here are some immediate fixes per issues found in the test report: Comment from Jepsen report: “The documentation is therefore somewhat confusing: some of its descriptions of repeatable read actually refer to repeatable read, and other parts refer to snapshot isolation.” Fix: We have updated our transactional isolation documentation to state that “TiDB allows some phantoms (P3), but does not allow strict phantoms (A3)” to clear the inconsistency implied in our documentation. Comments from Jepsen report: “TiDB’s automatic transaction retry mechanism was documented, but poorly’”, “The documentation for auto-retries was titled “Description of optimistic transactions”, and it simply said that the automatic-retry mechanism “cannot guarantee the final result is as expected” — but did not describe how.” Fix: We have updated the TiDB transaction documentation to note that automatic transaction retry is disabled in TiDB by default and that enabling it can result in lost updates. Transactional anomalies caused by automatic retries are also introduced in detail. Comment from Jepsen report: “PingCAP’s official documentation did not describe what select … for update should have done.” Fix: We have updated the description of Select for Update with more detailed behaviors of the clause and its difference with other databases. Next Steps Building a distributional database and continuously improving it is a long stretching battle. Inspired by Jepsen tests, we are planning to continue to integrate Jepsen and other forms of tests more comprehensively …"}, {"url": "https://pingcap.com/blog-cn/tikv-source-code-reading-8/", "title": "TiKV 源码解析系列文章(八)grpc-rs 的封装与实现", "content": " 上一篇《gRPC Server 的初始化和启动流程》为大家介绍了 gRPC Server 的初始化和启动流程,本篇将带大家深入到 grpc-rs 这个库里,查看 RPC 请求是如何被封装和派发的,以及它是怎么和 Rust Future 进行结合的。gRPC C Core gRPC 包括了一系列复杂的协议和流控机制,如果要为每个语言都实现一遍这些机制和协议,将会是一个很繁重的工作。因此 gRPC 提供了一个统一的库来提供基本的实现,其他语言再基于这个实现进行封装和适配,提供更符合相应语言习惯或生态的接口。这个库就是 gRPC C Core,grpc-rs 就是基于 gRPC C Core 进行封装的。要说明 grpc-rs 的实现,需要先介绍 gRPC C Core 的运行方式。gRPC C Core 有三个很关键的概念 grpc_channel、grpc_completion_queue、grpc_call。grpc_channel 在 RPC 里就是底层的连接,grpc_completion_queue 就是一个处理完成事件的队列。grpc_call 代表的是一个 RPC。要进行一次 RPC,首先从 grpc_channel 创建一个 grpc_call,然后再给这个 grpc_call 发送请求,收取响应。而这个过程都是异步,所以需要调用 grpc_completion_queue 的接口去驱动消息处理。整个过程可以通过以下代码来解释(为了让代码更可读一些,以下代码和实际可编译运行的代码有一些出入)。grpc_completion_queue* queue = grpc_completion_queue_create_for_next(NULL); grpc_channel* ch = grpc_insecure_channel_create("example.com", NULL); grpc_call* call = grpc_channel_create_call(ch, NULL, 0, queue, "say_hello"); grpc_op ops[6]; memset(ops, 0, sizeof(ops)); char* buffer = (char*) malloc(100); ops[0].op = GRPC_OP_SEND_INITIAL_METADATA; ops[1].op = GRPC_OP_SEND_MESSAGE; ops[1].data.send_message.send_message = "gRPC"; ops[2].op = GRPC_OP_SEND_CLOSE_FROM_CLIENT; ops[3].op = GRPC_OP_RECV_INITIAL_METADATA; ops[4].op = GRPC_OP_RECV_MESSAGE; ops[4].data.recv_message.recv_message = buffer; ops[5].op = GRPC_OP_RECV_STATUS_ON_CLIENT; void* tag = malloc(1); grpc_call_start_batch(call, ops, 6, tag); grpc_event ev = grpc_completion_queue_next(queue); ASSERT_EQ(ev.tag, tag); ASSERT(strcmp(buffer, "Hello gRPC")); 可以看到,对 grpc_call 的操作是通过一次 grpc_call_start_batch 来指定的。这个 start batch 会将指定的操作放在内存 buffer 当中,然后通过 grpc_completion_queue_next 来实际执行相关操作,如收发消息。这里需要注意的是 tag 这个变量。当这些操作都完成以后,grpc_completion_queue_next 会返回一个包含 tag 的消息来通知这个操作完成了。所以在代码的末尾就可以在先前指定的 buffer 读出预期的字符串。由于篇幅有限,对于 gRPC C Core 的解析就不再深入了,对这部分很感兴趣的朋友也可以在 github.com/grpc/grpc 阅读相关文档和源码。封装与实现细节 通过上文的分析可以明显看到,gRPC C Core 的通知机制其实和 Rust Future 的通知机制非常类似。Rust Future 提供一个 poll 方法来检验当前 Future 是否已经 ready。如果尚未 ready,poll 方法会注册一个通知钩子 task。等到 ready 时,task 会被调用,从而触发对这个 Future 的再次 poll,获取结果。task 其实和上文中的 tag 正好对应起来了,而在 grpc-rs 中,tag 就是一个储存了 task 的 enum。pub enum CallTag { Batch(BatchPromise), Request(RequestCallback), UnaryRequest(UnaryRequestCallback), Abort(Abort), Shutdown(ShutdownPromise), Spawn(SpawnNotify), } tag 之所以是一个 enum 是因为不同的 call 会对应不同的行为,如对于服务器端接受请求的处理和客户端发起请求的处理就不太一样。grpc-rs 在初始化时会创建多个线程来不断调用 grpc_completion_queue_next 来获取已经完成的 tag,然后根据 tag 的类型,将数据存放在结构体中并通知 task 来获取。下面是这个流程的代码。// event loop fn poll_queue(cq: Arc<CompletionQueueHandle>) { let id = thread::current().id(); let cq = CompletionQueue::new(cq, id); loop { let e = cq.next(); match e.event_type { EventType::QueueShutdown => break, // timeout should not happen in theory. EventType::QueueTimeout => continue, EventType::OpComplete => {} } let tag: Box<CallTag> = unsafe { Box::from_raw(e.tag as _) }; tag.resolve(&cq, e.success != 0); } } 可以看到,tag 会被强转成为一个 CallTag,然后调用 resolve 方法来处理结果。不同的 enum 类型会有不同的 resolve 方式,这里挑选其中 CallTag::Batch 和 CallTag::Request 来进行解释,其他的 CallTag 流程类似。BatchPromise 是用来处理上文提到的 grpc_call_start_batch 返回结果的 tag。RequestCallback 则用来接受新的 RPC 请求。下面是 BatchPromise 的定义及其 resolve 方法。/// A promise used to resolve batch jobs. pub struct BatchPromise { ty: BatchType, ctx: BatchContext, inner: Arc<Inner<Option<MessageReader>>>, } impl BatchPromise { fn handle_unary_response(&mut self) { let task = { let mut guard = self.inner.lock(); let status = self.ctx.rpc_status(); if status.status == RpcStatusCode::Ok { guard.set_result(Ok(self.ctx.recv_message())) } else { guard.set_result(Err(Error::RpcFailure(status))) } }; task.map(|t| t.notify()); } pub fn resolve(mut self, success: bool) { match self.ty { BatchType::CheckRead => { assert!(success); self.handle_unary_response(); } BatchType::Finish => { self.finish_response(success); } BatchType::Read => { self.read_one_msg(success); } } } } 上面代码中的 ctx 是用来储存响应的字段,包括响应头、数据之类的。当 next 返回时,gRPC C Core 会将对应内容填充到这个结构体里。inner 储存的是 task 和收到的消息。当 resolve 被调用时,先判断这个 tag 要执行的是什么任务。BatchType::CheckRead 表示是一问一答式的读取任务,Batch::Finish 表示的是没有返回数据的任务,BatchType::Read 表示的是流式响应里读取单个消息的任务。拿 CheckRead 举例,它会将拉取到的数据存放在 inner 里,并通知 task。而 task 对应的 Future 再被 poll 时就可以拿到对应的数据了。这个 Future 的定义如下:/// A future object for task that is scheduled to `CompletionQueue`. pub struct CqFuture<T> { inner: Arc<Inner<T>>, } impl<T> Future for CqFuture<T> { type Item = T; type Error = Error; fn poll(&mut self) -> Poll<T, Error> { let mut guard = self.inner.lock(); if guard.stale { panic!("Resolved future is not supposed to be polled again."); } if let Some(res) = guard.result.take() { guard.stale = true; return Ok(Async::Ready(res?)); } // So the task has not been finished yet, add notification hook. if guard.task.is_none() || !guard.task.as_ref().unwrap().will_notify_current() { guard.task = Some(task::current()); } Ok(Async::NotReady) } } Inner 是一个 SpinLock。如果在 poll 时还没拿到结果时,会将 task 存放在锁里,在有结果的时候,存放结果并通过 task 通知再次 poll。如果有结果则直接返回结果。下面是 RequestCallback 的定义和 resolve 方法。pub struct RequestCallback { ctx: RequestContext, } impl RequestCallback { pub fn resolve(mut self, cq: &CompletionQueue, success: bool) { let mut rc = self.ctx.take_request_call_context().unwrap(); if !success { server::request_call(rc, cq); return; } match self.ctx.handle_stream_req(cq, &mut rc) { Ok(_) => server::request_call(rc, cq), Err(ctx) => ctx.handle_unary_req(rc, cq), } } } 上面代码中的 ctx 是用来储存请求的字段,主要包括请求头。和 BatchPromise 类似,ctx 的内容也是在调用 next 方法时被填充。在 resolve 时,如果失败,则再次调用 request_call 来接受下一个 RPC,否则会调用对应的 RPC 方法。handle_stream_req 的定义如下:pub fn handle_stream_req( self, cq: &CompletionQueue, rc: &mut RequestCallContext, ) -> result::Result<(), Self> { let handler = unsafe { rc.get_handler(self.method()) }; match handler { Some(handler) => match handler.method_type() { MethodType::Unary | MethodType::ServerStreaming => Err(self), _ => { execute(self, cq, None, handler); Ok(()) } }, None => { execute_unimplemented(self, cq.clone()); Ok(()) } } } 从上面可以看到,整个过程先通过 get_handler,根据 RPC 想要执行的方法名字拿到方法并调用,如果方法不存在,则向客户端报错。可以看到这里对于 Unary 和 ServerStreaming 返回了错误。这是因为这两种请求都是客户端只发一次请求,所以返回错误让 resolve 继续拉取消息体然后再执行对应的方法。为什么 get_handler 可以知道调用的是什么方法呢?这是因为 gRPC 编译器在生成代码里对这些方法进行了映射,具体的细节在生成的 create_xxx_service 里,本文就不再展开了。小结 最后简要总结一下 grpc-rs 的封装和实现过程。当 grpc-rs 初始化时,会创建数个线程轮询消息队列(grpc_completion_queue)并 resolve。当 server 被创建时,RPC 会被注册起来,server 启动时,grpc-rs 会创建数个 RequestCall 来接受请求。当有 RPC 请求发到服务器端时,CallTag::Request 就会被返回并 resolve,并在 resolve 中调用对应的 RPC 方法。而 client 在调用 RPC 时,其实都是创建了一个 Call,并产生相应的 BatchPromise 来异步通知 RPC 方法是否已经完成。还有很多 grpc-rs 的源码在我们的文章中暂未涉及,其中还有不少有趣的技巧,比如,如何减少唤醒线程的次数而减少切换、如何无锁地注册调用各个 service 钩子等。欢迎有好奇心的小伙伴自行阅读源码,也欢迎大家提 issue 或 PR 一起来完善这个项目。"}, {"url": "https://pingcap.com/blog/percona-live-austin-summary-and-reflection/", "title": "Percona Live Austin -- Summary and Reflection", "content": " From May 28 to May 30, the PingCAP team attended Percona Live, a leading open-source database conference, in Austin, Texas. It’s our 5th Percona Live, and it’s clear to us that the Percona-led open-source database community is growing strong, and the TiDB community is growing alongside it.At last year’s Percona Live North America edition in Santa Clara, CA, TiDB was a relative unknown, and we only had one session near the end of Day 2 of the conference to introduce it. Since then, we participated in Percona Live Europe edition in Frankfurt, Germany, where myself and our VP of Engineering, Li Shen, delivered a keynote on TiDB 2.1 and my colleague Morgan Tocker gave a deep dive talk that famously lasted more than an hour for a 25-minute slot. Clearly, the Percona audience was hungry to learn more.To satisfy that hunger, we decided to organize a keynote plus an entire TiDB track with seven deep dive sessions.Tech Tech Tech Our presence at Percona Live started off with a bang where Ed Huang, PingCAP’s co-founder and CTO, delivered a keynote on all that’s new and cool in TiDB 3.0, our newest version that will reach General Availability later this month. It was a very technical talk – rare for a keynote – and Ed himself admitted during the keynote that there are just too many new features in 3.0 to cover in a 15-minute window. (Here are his slides.) Luckily, a full TiDB track awaits to showcase them all. Ed Huang on the Percona Live keynote stage We designed the TiDB track to share with developers, DBAs, and all practitioners technical know-how, reproducible benchmarks, and best practices on how TiDB can solve their problems. Here is the list of topics linked to their respective slides, along with some action shots: TiDB and Amazon Aurora: Compare, Contrast, Combine Using Chaos Engineering to Build a Reliable TiDB Deep Dive into TiDB SQL Layer Making HTAP Real with TiFlash – A TiDB Native Columnar Extension Making an ‘aaS’ out of TiDB: building DBaaS on a Kubernetes Operator From MySQL to TiDB and back again Siddon Tang sharing how our team uses Chaos Engineering to make TiDB more reliable Jason Zhang deep diving into the TiDB SQL layer Ruoxi Sun explaining our new columnar storage, TiFlash Greg Weber introducing how to make an ‘aaS’ out of TiDB: building DBaaS on a Kubernetes Operator Morgan Tocker sharing how to use open source tools to migrate from MySQL into (and out of) TiDB In addition to our own team’s presence, we were lucky to have Frank Ober from Intel sharing his take on how TiDB is leveraging Intel Optane to tackle IO challenges (download Frank’s slides HERE), and Jervin Real and Francisco Bordenave from Percona, who shared their own take on how to replicate MySQL data to TiDB for real-time analytics.By our own team’s rough estimate, out of the 700+ people who attended this Percona Live, less than half have heard of TiDB before the conference. After Ed’s keynote, almost all of them know about the technology, and many of whom have either attended the TiDB track or visited our booth to learn more.Making Friends, Building Team, Fostering Community Of course, going to a community-oriented tech conference like Percona Live isn’t just about talking, it’s also about listening and learning from others. The conference was full of interesting talks, with track topics ranging from stalwarts like MySQL, PostgreSQL and MariaDB, to newer subjects like Kubernetes and Observability & Monitoring. There was something for everyone.The PingCAP booth was also buzzing with activities as many attendees came by to ask questions about TiDB, enter our raffle, grab stickers, and share learnings about what they were doing with open-source databases. We saw many familiar faces from past Percona conferences, but also made new friends from all over the country and the world. As a case in point, I personally had in-depth conversations with folks from Indeed.com, Kohl’s, and one of the largest Internet companies in Turkey!Open-source is truly a global force. Queeny Jin chatting with a TiDB enthusiast at the PingCAP booth Samantha Peters running a raffle for conference attendees Our stickers. Yes we love stickers! Going to conferences serves a double purpose for our team. At PingCAP, we are a very remote-friendly workforce with people residing and working from many different cities in China and throughout North America. So going to conferences is also an opportunity to meet other team members face to face, work and live together under one roof, and connect as human beings.This time, we had 16 people together in Austin, where we did some team-building activities, ate (a lot of) BBQ, and celebrated three birthdays! Here’s a homemade video produced by our community and events manager, Samantha, that captured all our favorite moments. Suffice to say, Percona Live Austin edition was a great experience from many angles. We are pleased that an unbiased expert team like Percona is leading the charge to continue bringing together and growing the open-source database community. And we are grateful to be part of it."}, {"url": "https://pingcap.com/weekly/2019-06-10-tidb-weekly/", "title": "Weekly update (June 03 ~ June 09, 2019)", "content": " Weekly update in TiDB Last week, we landed 70 PRs in the TiDB repository.Added Add a configuration item for the bind info lease Support the := MySQL extension syntax in variable assignment Add the host column in the slow_query table Support the deadlock detector in mocktikv for pessimistic transactions Support single statement rollback for pessimistic transactions Support SplitIndexRegion with lower and upper values Add a configuration item to specify GC concurrency manually Improved Print an expensive log when a query exceeds the time threshold Use the maximum correlation in heuristic row count estimation Optimize addSpecialComment by reusing compiled regular expressions Fixed Commit the current transaction automatically before executing some statements Fix the wrong output of show create table for partitioned tables Fix an issue for RBAC methods when enabling --skip-grant-table Fix fast Analyze bugs during the Analyze process Fix a shallow copy problem about slow_query Fix a panic for CreateView using Prepare Fix the issue that duplicate values might occur when the uuid function is executed simultaneously on multiple nodes Check whether the JSON value is valid before UnmarshalJSON Fix the bug that a blob column is changed to a text column when altering the table charset Fix an issue when the constant overflows the range of the primary key type Fix an issue when the predicate is PKIsHandle = 1.1 Weekly update in TiSpark Last week, we landed 17 PRs in the TiSpark repository.Fixed Fix the IndexOutOfBoundException issue when trying to get the PD member Fix the issue that the key not in range exception is thrown Added Support the auto increment attribute for batch writes Weekly update in TiKV and PD Last week, we landed 28 PRs in the TiKV and PD repositories.Added Add batch aggregate functions MultiplyDecimal MAX() MIN() BitAnd() BitOr() BitXor() UnaryNot Add the batch Top-N executor Add the initialized flag in the cluster status Add the HTTP endpoint to enable the Jemalloc profile Improved Upgrade sys-info Fixed Fix the issue that the TiKV snapshot might not be replicated New contributors (Thanks!) tidb: suzaku tangenta tikv: YKG YangKeao benpigchu chenyukang hhhowyou parser: tangenta "}, {"url": "https://pingcap.com/meetup/meetup-105-20190606/", "title": "【Infra Meetup No.105】 Happy Hacking TiDB & Chaos Practice in TiDB", "content": " Topic 1:Happy Hacking TiDB 讲师介绍:杜川,数据库技术爱好者,TiDB Committer。主要专业方向是分布式关系型计算,关注数据库优化器和执行引擎相关技术。目前主要从事云数据库相关开发工作。 视频 | Infra Meetup No.105:Happy Hacking TiDB PPT 链接 本次杜川老师的分享主要分成三个部分: 首先通过对现有 Streaming 系统和 Batch 系统的分析,讨论了在数据处理领域 Streaming 和 Batch 的异同,明确了 Streaming 的核心本质,探讨了 Streaming 和 Batch 融合处理的可能性和必要性,并对现有类似系统进行了简单的分析。 简单回顾了 RDMS 中经典的 Volcano 模型的执行流程,探讨了在 RDMS 上支持 Streaming 处理的难点以及 Streaming SQL 设计的关键要素。 介绍了 TBSSQL 的设计思路,架构设计和若干关键技术点的方案选择,展示了 TBSSQL 的运行 Demo。并以 TBSSQL 为例,简单介绍了在 TiDB 上增加一个 Feature 的大致思路和入手点。 Topic 2:Chaos Practice in TiDB 讲师介绍:舒科,PingCAP 研发工程师。主要方向是 TiDB 质量保障,关注提升测试质量和效率的新技术。 视频 | Infra Meetup No.105:Chaos Practice in TiDB PPT 链接 本次分享舒科老师首先介绍了什么是 Chaos、为什么要用 Chaos,然后分享了我司使用 Chaos 的经验以及在错误注入上的一些积累,最后分享了我司薛定谔系统的基本概念和架构,以及 Chaos Operator 相关的内容。"}, {"url": "https://pingcap.com/meetup/meetup-106-20190606/", "title": "【Infra Meetup No.106】 Alluxio & 仿真技术 & TiDB 的 HTAP 之路 & Zeppelin", "content": " Topic 1:Alluxio 的新特性介绍与缓存性能优化 讲师介绍:顾荣,博士毕业于南京大学计算机系,南大 PASA 大数据实验室助理研究员,研究方向大数据处理系统,现担任江苏省计算机学会大数据专家委员会秘书长、中国计算机学会系统软件专委会委员,Alluxio 项目 PMC Member & Maintainer。 PPT 链接 顾博士首先介绍了大数据处理的四大趋势,并分析其驱动新型基础架构方面的需求。为了满足这些需求,接着他介绍了 Alluxio 数据编排的思想,为上层计算框架和底层存储系统构建桥梁。最后,他介绍了 Alluxio 2.0 新特性,以及顾博士所在南大实验室在 Alluxio 缓存优化方面的一些工作,包括通用化缓存调度框架,缓存策略,以及内存读优化等。Topic 2:基于仿真技术规划部署和调优大数据集群 讲师介绍:刘华,来自于 Intel 系统技术与优化部门,负责 Intel 系统建模和模拟解决方案在亚太区的业务推广。目前致力向客户提供基于仿真技术的 Intel®CoFluent™ 大数据技术,帮助客户规划部署和调优大数据集群。 PPT 链接 刘华老师首先探讨了规划部署和调优大数据集群时通常面临的一些挑战: 集群规模规划、硬件配置选型、软件参数优化和可扩展性分析。基于 Intel® CoFluent™ Studio 仿真技术,Intel 开发了端到端大数据集群仿真器——Intel® CoFluent™ Technology For Big Data,利用软硬件协同仿真的方法来应对这些大数据挑战。接着他结合健康监控集群和视屏分析系统中的实例场景,阐述了仿真器是如何执行全栈参数优化,集群规模预测,硬件配置规划和扩展性分析,以及集群容量预测和瓶颈识别等分析和原理。Intel® CoFluent™ Technology For Big Data 可以在软件环境中模拟完整的大规模集群,支持各种 Intel 硬件产品和各种大数据软件栈的模拟。通过灵活的 what-if 分析和精确的量化预测,可以帮助客户快速设部署集群,充分发挥硬件利性能,最小化成本,提供最好的用户体验。Topic 3:TiDB 的 HTAP 之路 - 过去,现在和将来 讲师介绍:马晓宇,PingCAP 分析型产品负责人。曾就职于 Quantcast、网易,负责构建和管理大数据平台。关注分布式系统,SQL 引擎开发等领域。 视频 | Infra Meetup No.106:TiDB 的 HTAP 之路 - 过去,现在和将来 PPT 链接 本次分享马老师从 TiDB 在 HTAP 道路上不断探索的过程为线索,从 TiDB 最初版在分析场景的功能特点,合适与不合适的场景为起始,介绍了为何会引入 TiSpark 组件解决大规模批量计算和大数据生态整合;我们如何优化 TiDB 本体的引擎;以及 TiFlash 又如何帮这个体系彻底解决资源隔离和行列混存的矛盾,从而最终向真 HTAP 数据库演进的过程。Topic 4:Zeppelin 在机器学习领域的最新特性和规划 讲师介绍:刘勋,Zeppelin Committer;Hadoop Submarine Team Member;目前就职网易杭研数据科学中心,负责机器学习平台的开发和建设。关注 Hadoop 、大数据平台和机器学习等领域。 视频 | Infra Meetup No.106:Zeppelin 在机器学习领域的最新特性和规划 PPT 链接 本次分享刘勋老师介绍了 Apache Zeppelin,这是一个可以进行大数据可视化分析的交互式开发系统。刘勋老师首先介绍了在 zeppelin 最新的版本中增加了对 Tensorflow、PyTorch 的深度学习框架的支持。在 zeppelin 中可以完成机器学习的数据预处理、算法开发和调试、算法作业调度的工作,将来还将提供算法的模型 Serving 服务,Workflow 工作流编排等新特性,让 zeppelin 可以完全覆盖机器学习的全流程工作。接着他介绍了 zeppelin 还提供了单机 Docker、分布式、K8s、Yarn 四种系统运行模式,无论你是小规模的开发团队,还是 hadoop 技术栈的大数据团队、K8s 技术栈的云计算团队,zeppelin 都可以让你轻松的进行部署和使用 zeppelin 丰富的数据和算法的开发能力。"}, {"url": "https://pingcap.com/blog-cn/dm-source-code-reading-7/", "title": "DM 源码阅读系列文章(七)定制化数据同步功能的实现", "content": " 本文为 DM 源码阅读系列文章的第七篇,在 上篇文章 中我们介绍了 relay log 的实现,主要包括 relay log 目录结构定义、relay log 数据的处理流程、主从切换支持、relay log 的读取等逻辑。本篇文章我们将会对 DM 的定制化数据同步功能进行详细的讲解。在一般的数据同步中,上下游的数据是一一对应的,即上下游的库名、表名、列名以及每一列的值都是相同的,但是很多用户因为业务的原因希望 DM 在同步数据到 TiDB 时进行一些定制化的转化。下面我们将主要介绍数据同步定制化中的库表路由(Table routing)、黑白名单(Black & white table lists)、列值转化(Column mapping)、binlog 过滤(Binlog event filter)四个主要功能的实现。值得注意的是,由于其他一些工具(例如 TiDB Lightning 和 TiDB Binlog)也需要类似的功能,所以这四个功能都以 package 的形式维护在 tidb-tools 项目下,这样方便使用和维护。库表路由(Table routing) 库表路由顾名思义就是对库名和表名根据一定的路由规则进行转换。比如用户在上游多个 MySQL 实例或者 schema 有多个逻辑上相同的表,需要把这些表的数据同步到 TiDB 集群的同一个表中,这个时候就可以使用 table-router 功能,如下图所示:该功能实现在 pkg/table-router 中,库表路由的规则定义在结构 TableRule 中,其中的属性 SchemaPattern 和 TablePattern 用于配置原库名和表名的模式,TargetSchema 和 TargetTable 用于配置目标库和表名,即符合指定 pattern 的库和表名都将转化成目标库名和表名。使用结构 Table 对路由规则进行维护,Table 提供了如下方法: 方法 说明 AddRule 增加规则 UpdateRule 修改规则 RemoveRule 删除规则 Route 获取路由后的结果 Table 结构中组合了 Selector,Selector 用于管理指定模式的库、表的规则,提供如下方法: 方法 说明 Insert 增加规则 Match 查找指定的库、表匹配到的规则 Remove 删除规则 AllRules 返回所有的规则 Selector 的底层实现是 trieSelector,使用了单词查找树的结构来维护库、表与规则的对应关系,感兴趣的同学可以阅读代码深入了解一下。 trieSelector 中使用 cache 缓存了库、表到规则的映射关系,这样可以减少相同库、表匹配规则的资源消耗。除了 table routing,以下的列值转化和 binlog 过滤功能也都使用了 Selector,在下面的介绍中就不再赘述。黑白名单(black & white table lists) 黑白名单功能用来选择同步哪些库和表,以及不同步哪些库和表,这部分代码维护在 pkg/filter 中。黑白名单规则配置在 Rules 结构中,该结构包括 DoTables、DoDBs、IgnoreTables 和 IgnoreDBs 四个属性,下面以判断表 test.t 是否应该被过滤的例子说明配置的作用: 首先 schema 过滤判断。 如果 do-dbs 不为空,则判断 do-dbs 中是否存在一个匹配的 schema。 如果存在,则进入 table 过滤判断。 如果不存在,则过滤 test.t。 如果 do-dbs 为空并且 ignore-dbs 不为空,则判断 ignore-dbs 中是否存在一个匹配的 schema。 如果存在,则过滤 test.t。 如果不存在,则进入 table 过滤判断。 如果 do-dbs 和 ignore-dbs 都为空,则进入 table 过滤判断。 进行 table 过滤判断。 如果 do-tables 不为空,则判断 do-tables 中是否存在一个匹配的 table。 如果存在,则同步 test.t。 如果不存在,则过滤 test.t。 如果 ignore-tables 不为空,则判断 ignore-tables 中是否存在一个匹配的 table。 如果存在,则过滤 test.t。 如果不存在,则同步 test.t。 如果 do-tables 和 ignore-tables 都为空,则同步 test.t。 使用 Filter 对黑白名单进行管理,Filter 提供了 ApplyOn 方法来判断一组 table 中哪些表可以同步。列值转化(Column mapping) 列值转化功能用于对指定列的值做一些转化,主要用于分库分表的同步场景。比较典型的场景是:在上游分表中使用自增列作为主键,这样数据在同步到 TiDB 的一个表时会出现主键冲突,因此我们需要根据一定规则对主键做转化,保证每个主键在全局仍然是唯一的。该功能实现在 pkg/column-mapping 中的 PartitionID:修改列的值的最高几位为 PartitionID 的值(只能作用于 Int64 类型的列)。代码中使用 Rule 来设置 column mapping 的规则,Rule 的属性及说明如下表所示: 属性 说明 值 PatternSchema 匹配规则的库的模式 可以设置为指定的库名,也可以使用通配符 “*” 和 “?” PatternTable 匹配规则的表的模式 可以设置为指定的表名,也可以使用通配符 “*” 和 “?” SourceColumn 需要转化的列 列名 TargetColumn 转化后的值保存到哪个列 列名 Expression 转化表达式 目前只支持 PartitionID Arguments 转化所需要的参数 Expression 为 PartitionID,参数为 InstanceID、schema 名称前缀、table 名称前缀以及前缀与 ID 的分割符号 Expression 为 PartitionID 的配置和转化的计算方式都较为复杂,下面举个例子说明。例如 Arguments 为 [1, “test”, “t”, “_”],1 表示数据库实例的 InstanceID,“test” 为库名称的前缀,“t” 为表名称的前缀,“_” 为前缀与 ID 的分隔符,则表 test_1.t_2 的 SchemaID 为 1,TableID 为 2。转化列值时需要对 InstanceID、SchemaID、TableID 进行一定的位移计算,然后与原始的值进行或运算得出一个新的值。对于具体的计算方式,可以查看代码 partitionID 和 computePartitionID。下面是一个 PartitionID 逻辑简化后的示意图:使用 Mapping 结构对 column mapping 的规则进行管理,Mapping 提供列如下方法: 方法 说明 AddRole 增加规则 UpdateRule 修改规则 RemoveRule 删除规则 HandleRowValue 获取转化结果 binlog 过滤(binlog event filter) binlog 过滤功能支持过滤指定类型的 binlog,或者指定模式的 query,该功能维护在 pkg/binlog-filter 中。某些用户不希望同步一些指定类型的 binlog,例如 drop table 和 truncate table,这样就可以在下游仍然保存这些表的数据作为备份,或者某些 SQL 语句在 TiDB 中不兼容,希望可以在同步中过滤掉,都可以通过配置 binlog event filter 功能来实现。首先需要对 binlog 进行分类,可以查看代码 Event Type List。然后再定义过滤规则 BinlogEventRule,包括以下属性: 属性 说明 值 SchemaPattern 匹配规则的库的模式 可以设置为指定的库名,也可以使用通配符 “*” 和 “?” TablePattern 匹配规则的表的模式 可以设置为指定的表名,也可以使用通配符 “*” 和 “?” Events 规则适用于哪些类型的 binlog binlog event 的类型 SQLPattern 匹配的 SQL 的模式 SQL 语句的模式,支持适用正则表达式 Action 是否对符合上面要求的 binlog 进行过滤 Ignore 或者 Do 例如,TiDB 对 ADD PARTITION 和 DROP PARTITION 语句不兼容,在同步时需要过滤掉相关的 SQL 语句,就可以在 DM 中使用如下配置:filter-partition-rule: schema-pattern: "*" sql-pattern: ["ALTERs+TABLE[sS]*ADDs+PARTITION", "ALTERs+TABLE[sS]*DROPs+PARTITION"] action: Ignore 如果需要过滤掉所有的 DROP DATABASE 语句,则可以在 DM 中使用如下配置:filter-schema-rule: schema-pattern: "*" events: ["drop database"] action: Ignore 代码中通过 BinlogEvent 结构对 binlog event 过滤规则做统一的管理,BinlogEvent 提供了如下的方法: 方法 说明 AddRule 增加规则 UpdateRule 修改规则 RemoveRule 删除规则 Filter 判断指定的 binlog 是否应该过滤 小结 以上就是定制化数据同步功能中库表路由(Table routing)、黑白名单(Black & white table lists)、列值转化(Column mapping)、binlog 过滤(Binlog event filter)的实现介绍。欢迎大家阅读相关代码深入了解,也欢迎给我们提 pr 优化代码。下一篇我们将介绍 DM 是如何支持上游 online DDL 工具(pt-osc,gh-ost)的 DDL 同步场景的。"}, {"url": "https://pingcap.com/weekly/2019-06-03-tidb-weekly/", "title": "Weekly update (May 27 ~ June 02, 2019)", "content": " Weekly update in TiDB Last week, we landed 37 PRs in the TiDB repository.Added Support the SQL_BIG_RESULT, SQL_SMALL_RESULT, and SQL_BUFFER_RESULT syntax Add usability-team in PR guidelines Improved Check Deadlock in KeyError of the pessimistic locking operation result returned by TiKV Remove canceled requests before sending them to TiKV Implement IterReverse and use it to speed up admin show ddl jobs Preload frequently used variables Improve the UT coverage of the expression package to 80% Improve the UT coverage of the distsql package to 85% Speed up testing by 50% in the ddl package Fixed Skip virtual generated column during collecting statistics Fix a potential goroutine leak in gcworker Fix the Analyze worker panic caused by sending messages in the closed channel Fix the unexpected result of unsigned_int_col >= -int_cnst in the planner Fix a panic when using the Window function in some cases Fix SHOW VIEW privileges for EXPLAIN Fix the goroutine leak when TiKV is offline Fix the issue that StreamAggExec does not clean up resources when the Close function is called Weekly update in TiSpark Last week, we landed 4 PRs in the TiSpark repository.Fixed Fix the issue that the MatchError exception might occur when unsigned BigInt is used in the group by column Weekly update in TiKV and PD Last week, we landed 38 PRs in the TiKV and PD repositories.Added Add batch aggregate functions SUM() AVG() FIRST() Add BatchStreamAggregationExecutor Support resolve_lock_lite Improved Reuse monotonic-raw-now for each round in raftstore Improve batch vector performance Fixed Fix the issue that the TiKV snapshot might not be synchronized Fix the TiKV read stale issue after role change Fix the issue that the hot Region limit for PD might be 0 Fix a TiKV panic when no aggregate function exists in the batch executor Fix Duration::parse bugs New contributors (Thanks!) tikv: senden9 parser: b41sh db-storage docs-cn: emhlbmc "}, {"url": "https://pingcap.com/blog-cn/dm-source-code-reading-6/", "title": "DM 源码阅读系列文章(六)relay log 的实现", "content": " 本文为 DM 源码阅读系列文章的第六篇,在 上篇文章 中我们介绍了 binlog replication 处理单元的实现,对在增量复制过程中 binlog event 的读取、过滤、路由、转换以及执行等逻辑进行了分析。本篇文章我们将会对 relay 数据处理单元的实现进行详细的讲解。这个单元的作用是从上游 MySQL/MariaDB 读取 binlog event 并写入到本地的 relay log file 中;当执行增量复制任务时,binlog replication 处理单元将读取 relay log file 中的 event 并在进行解析后复制到下游的 TiDB 中。本篇文章的内容包括 relay log 目录结构定义、relay log 数据的处理流程、主从切换支持、relay log 的读取等逻辑。值得注意的是,由于我们近期正在对 relay 处理单元进行重构,因此源码中会同时包含重构前后的相关代码实现。relay log 目录结构 一个已经进行过一次主从切换的 relay log 目录结构大致如下:<deploy_dir>/relay_log/ |-- 7e427cc0-091c-11e9-9e45-72b7c59d52d7.000001 | |-- mysql-bin.000001 | |-- mysql-bin.000002 | |-- mysql-bin.000003 | |-- mysql-bin.000004 | `-- relay.meta |-- 842965eb-091c-11e9-9e45-9a3bff03fa39.000002 | |-- mysql-bin.000001 | `-- relay.meta `-- server-uuid.index 在 relay log 目录下,主要包含以下几类文件或文件夹数据: 类别 作用 文件(夹)名示例 relay log 子目录 以单次主从切换发生时对应于某个 MySQL/MariaDB server 为单位组织 relay log 数据及 meta 信息 7e427cc0-091c-11e9-9e45-72b7c59d52d7.000001 relay log 数据文件 存储实际的 binlog event 数据 mysql-bin.000001 relay meta 信息 存储当前已从上游读取并写入为 relay log 的 binlog event 对应于上游的 binlog position/GTID sets 信息 relay.meta relay log 子目录索引 索引各有效的 relay log 子目录列表 server-uuid.index relay log 处理流程 从上图大致可以了解 relay log 的逻辑处理流程,对应的入口代码为 Relay.Process,主要步骤包括: 使用 binlog reader 从上游 MySQL/MariaDB 读取 binlog event。 将读取到的 binlog event 使用 binlog transformer 进行转换。 将转换后的 binlog event 使用 binlog writer 以 relay log file 的形式存储在本地。 当需要将数据以增量的方式同步到下游 TiDB 时,binlog replication 通过使用 relay reader 从 relay log file 中读取 binlog event。 读取 binlog event relay 处理单元通过 Reader interface 从上游读取 binlog event,其中最重要的方法为读取 binlog event 对象的 GetEvent。当前对 Reader interface 的实现为 reader,它最终通过 in 这个 br.Reader interface 从上游读取 binlog event。reader 的使用流程为: 调用 Start 启动读取流程,并根据配置中是否启用了 GTID 模式分别调用 setUpReaderByGTID 或 setUpReaderByPos 来启动下层的 br.Reader 对象。 调用 GetEvent 读取 binlog event,具体为 调用下层的 GetEvent 方法 获取 binlog event。 当不再需要读取 binlog event 时,调用 Close 关闭读取操作。 从上面的流程可以看出,具体的 binlog event 读取操作使用的是另一个下层的 br.Reader interface,当前选择的具体实现 为通过 TCP 连接进行读取的 TCPReader。在 TCPReader 中,使用了 go-mysql 提供的 BinglogSyncer.StartSync 和 BinlogSyncer.StartSyncGTID 来启动以 binlog position 模式或 GTID sets 模式读取 binlog event,并通过 BinlogStreamer.GetEvent 读取来自 TCP 的 binlog event。转换 binlog event 在 relay 处理单元中,对于从上游读取到的 binlog event,我们需要判断是否需要写进 relay log file 及是否需要更新对应的 relay.meta 内的断点信息。因此在通过 Reader interface 读取到 binlog event 后,通过调用 Transformer interface 来对 binlog event 进行相关的转换处理。当前对 Transformer interface 的实现为 transformer,其主要通过在 Transform 方法中 对 binlog event 的类型进行判断 后再进行相应处理,包括: binlog event 类型 是否过滤 是否需要更新 relay.meta RotateEvent 当是 fake RotateEvent 时过滤 否 QueryEvent 否 当是 DDL 时更新 XIDEvent 否 是 GenericEvent 当是 Heartbeat Event 时过滤 否 其他类型 当 ARTIFICIAL flag 被设置时过滤 否 在 Transformer 中,我们期望能达到以下目标: 过滤上游 master server 上的 binlog file 中不存在的 binlog event,即期望 relay log file 中最终保存的 binlog event 与上游 master server 上的 binlog file 一致。 仅在 DDL QueryEvent 时或 DML 事务完成时更新 relay.meta 以确保中断恢复时能避免从 DML 事务进行中的 binlog event 处开始从上游请求 binlog event(对于 DML 相关的 binlog event,如果希望解析 INSERT/UPDATE/DELETE 等操作,则需要先获取到对应的 TableMap event)。 写入 relay log 在从上游读取到 binlog event 并对其进行了相关转换后,我们就可以尝试将其写入到本地的 relay log file 中。在 relay 处理单元中,用于将 binlog event 写入 relay log file 的是 Writer interface,当前对应的实现为 FileWriter,其内部会使用 out 这个 bw.FileWriter 来执行文件写入操作,具体对 binlog event 执行写入操作的是 WriteEvent 方法。1. 各类型 binlog event 的判断处理 在尝试对 binlog event 进行写入时,对于不同类型的 binlog event,需要 进行不同的判断处理。RotateEvent 在从上游读取 binlog event 时,主要在以下情况下可能会读取到 RotateEvent: 连接到上游 master server 开始读取 binlog event 时,master 会发送一个 fake RotateEvent 告知 slave 后续 binlog event 对应的起始 binlog position。 一个 master server 上的 binlog file 将要被读取完成时,可能会包含一个 RotateEvent 以指示下一个 binlog file 的 filename 与起始 position。 因此,在处理 RotateEvent 写入的 handleRotateEvent 方法中,主要包含以下操作: 尝试更新 FileWriter 内部记录的当前 binlog 文件名为 RotateEvent 内包含的文件名。 判断是否是 fake RotateEvent,如果是则跳过后续处理。 与当前 relay log file 的 size 及内部 event 进行比较,判断如果将当前 event 写入到文件后是否会造成文件存在 hole 及该 event 是否在 relay log file 中已经存在,如果会造成 hole 则需要填充该 hole,如果已经存在则跳过后续的处理。 将 event 写入到 relay log file 中。 需要注意的是,我们不能确保 master server 会将其 binlog file 中的所有 event 都发送给 slave(如当 MariaDB 未设置 BINLOG_SEND_ANNOTATE_ROWS_EVENT flag 时,master 就不会向 slave 发送 ANNOTATE_ROWS_EVENT),因此在写入 event 到文件前,需要通过 handleFileHoleExist 判断如果将 event 写入到文件是否会存在 hole。如果存在 hode,则通过 event.GenDummyEvent 生成相应 size 的 dummy event 对 hole 进行填充。另外需要注意的是,我们不能确保 master server 不会将其已经发送给 slave 并写入到了 relay log file 的 event 再次发送给 slave(如 master 在开始发送 slave 请求的 binlog event 前,会先发送 FormatDescriptionEvent 与 PreviousGTIDsEvent 等给 slave),因此在写入 event 到文件前,需要通过 handleDuplicateEventsExist 判断该 event 是否已经存在于 relay log file 中。FormatDescriptionEvent 在从上游读取 binlog event 时,主要在以下情况下可能会读取到 FormatDescriptionEvent: 上游 master server 在发送除 RotateEvent 外的其他 binlog event 之前,会发送一个 FormatDescriptionEvent 以使 slave 能正确 decode 后续的 binlog event。 上游 master server 会将自身 binlog file 中存在的 FormatDescriptionEvent 发送给 slave,且这个 FormatDescriptionEvent 总是 binlog file 中的第 1 个 event。 因此,在处理 FormatDescriptionEvent 的 handleFormatDescriptionEvent 方法中,主要包含以下操作: 关闭之前可能已经打开的 relay log file。 打开该 event 需要写入到的 relay log file 作为当前活跃的 relay log file。 检查当前 relay log file 中是否存在 binlog file header(fe `bin`),如果不存在则为其 写入 binlog file header。 检查当前 relay log file 中是否存在 FormatDescriptionEvent,如果不存在则为其 写入该 FormatDescriptionEvent。 其他类型 event 对于其他类型的 binlog event,写入操作由 handleEventDefault 进行处理,主要包含以下操作: 与当前 relay log file 的 size 及内部 event 进行比较,判断如果将当前 event 写入到文件后是否会造成文件存在 hole 及该 event 是否在 relay log file 中已经存在,如果会造成 hole 则需要填充该 hole,如果已经存在则跳过后续的处理。 将 event 写入到 relay log file 中。 2. Recover relay log file 在写入 binlog event 到 relay log file 时,尽管可以通过 Flush 方法强制将缓冲中的数据刷新到磁盘文件中,但仍然可能出现 DM-worker 进程异常退出时部分数据未能刷新到磁盘文件中的情况,造成 relay log file 内部分 event 数据缺失。另外,对于一个事务对应的多个 binlog event,可能出现仅写入了其中一部分 event 时 DM-worker 发生退出的情况,造成 relay log file 中部分事务缺失部分 event。因此,在 relay 处理单元中,我们引入了对 relay log file 执行 Recover 的机制,用于将 relay log file 尾部不完整的 event 及事务进行踢除,对应的方法为 FileWrite.Recover,具体实现在 doRecovering 方法中,主要操作包括: 获取 relay log file 中直到最后一个完整事务对应的 binlog position 与 GTID sets。 比较 relay log file 的 size 与获取到的 binlog position,如果相等则说明这个 relay log file 中包含的事务都是完整的,跳过后续的处理。 如果 relay log file 的 size 比 binlog position 更小,则向外部报告错误并跳过后续的处理。 如果 relay log file 的 size 比 binlog position 大,则 将 relay log file 中超出 binlog position 的部分执行 Truncate 进行截断。 主从切换支持 为支持将 relay 处理单元连接的上游 master server 在 replica group 内的不同 server 间进行切换(也包括 relay 处理单元连接的上游 VIP 指向的实际 server 发生了改变),relay 处理单元会尝试将从不同上游 server 读取到的 binlog event 保存到不同的 relay log 子目录中,目录与文件结构可以参考前文的 relay log 目录结构。为支持上述功能,relay 处理单元在读取 binlog event 前主要执行以下操作: 比较当前上游 server 的 UUID 信息与 relay.meta 信息,判断当前连接到的是否是前一次连接过的 server。 如果不是前一次连接过的 server,则说明切换到了新的 server,因此创建新的 relay log 子目录并更新对应的 meta 信息。 读取 relay log relay 处理单元用于从上游读取 binlog event 并将其写入到本地的 relay log file 中。当执行增量数据复制时,binlog replication 处理单元需要通过 streamer pkg 读取 relay log file 并从中解析获取需要同步的数据,其中执行读取的对象为 BinlogReader。由前文介绍过的主从切换支持可知我们会将具体的 relay log 数据存储在可能的多个子目录中,因此在读取 relay log 时,我们也 需要考虑按序依次读取,主要操作包括: 调用 parseRelay 开始从 relay log 的根目录执行解析读取。 调用 parseDirAsPossible 开始从外部指定的或上一次调用返回的子目录、文件及 offset 处开始读取,并返回下一次调用时需要的子目录、文件及 offset(即可实现切换到新的 relay log 子目录)。 对于当前需要读取的子目录,调用 CollectBinlogFilesCmp 收集该目录内指定 relay log 文件及其之后的所有 relay log 文件。 对于每一个收集到的 relay log 文件,调用 parseFileAsPossible 尝试对其进行解析读取。 在 parseFileAsPossible 中,反复返回 调用 parseFile 进行 binlog event 的读取,直到 发生错误 或 检测到需要切换到新的 relay log 文件或子目录。 对于是否需要切换到新的 relay log 文件或子目录的检测通过在 parseFile 内 调用 needSwitchSubDir 与 调用 relaySubDirUpdated 实现。 小结 本篇文章详细地介绍了 relay 处理单元的实现,内容包括了 relay log 的目录结构、如何从上游 server 读取 binlog event 并写入到本地的 relay log file 中,以及 binlog replication 处理单元将如何读取本地的 relay log file。到本篇文章为止,我们完成了对 DM 中的数据处理单元的介绍。从下一篇文章开始,我们将开始详细介绍 DM 内部主要功能的设计与实现原理。"}, {"url": "https://pingcap.com/meetup/meetup-103-20190530/", "title": "【Infra Meetup No.103】 TiDB 开源社区专题", "content": " Topic :TiDB 开源社区专题 讲师介绍:PingCAP 技术 VP,TiDB Tech Lead,前网易有道、360 搜索资深研发。 视频 | Infra Meetup No.103:TiDB 开源社区专题 PPT 链接 本次 Talk 申砾老师首先给大家介绍了 TiDB 的演进历史,社区发展现状。然后详细介绍了如何参与到 TiDB 开源社区中,从了解社区、学习 TiDB 代码,到做出第一个贡献,接下来不断地做更加深入地贡献,成为 committer 甚至 maintainer。在这个过程中,我们准备了很多相关资料,帮助社区了解 TiDB 技术细节以及社区治理规范,希望能和大家一起构建一个全球顶尖的开源社区。"}, {"url": "https://pingcap.com/meetup/meetup-104-20190530/", "title": "【Infra Meetup No.104】 云原生数据库的核心特点 - TiDB 架构及发展 & 反应式编程之 Spring WebFlux", "content": " Topic 1:云原生数据库的核心特点 - TiDB 架构及发展 讲师介绍:杨洋,TiDB contributor。 视频 | Infra Meetup No.104:云原生数据库的核心特点 - TiDB 架构及发展 PPT 链接 杨洋老师本次分享的主要内容为: 介绍了云原生的概念及演化; 结合云原生的特点分析传统数据库在架构方面的不足; 着重介绍云原生数据库在架构方面的特点; 作为云原生数据库中的佼佼者 TiDB 的技术架构是怎样的; TiDB 版本的演进,用户案例展示,技术社区活动介绍。 Topic 2:反应式编程之 Spring WebFlux 讲师介绍:张锦文,现任职于金数据,主任工程师,前 ThoughtWorks 高级咨询师,AWS 助理架构师,《Scala 编程实战》合译者,有丰富的开发及工程实践,喜欢技术及编程挑战,热爱开源技术,贡献过 Pact-JVM 等开源框架,擅长 Java 生态及 DevOPS 技术。 视频 | Infra Meetup No.104:反应式编程之 Spring WebFlux PPT 链接 张锦文老师本次分享主要讲述了计算机由单核到多核的处理架构的变化,接着从理论出发指出多线程编程所面临的问题,并给出了解决方案,进一步详细介绍了反应式编程的由来及反应式编程宣言: Responsive Resilient Elastic Message-Driven 然后理论落地引出函数式编程并进一步讲述了其特点: 副作用 不可变及幂等性 惰性求职 函数式一等公民 最后介绍了 Spring WebFlux 在反应式编程中的应用以及它的核心组件,并结合工程实践分享了项目中的踩坑过程。"}, {"url": "https://pingcap.com/meetup/meetup-100-20190510/", "title": "势高,则围广:TiDB 的架构演进哲学", "content": " 大家可能知道我是 PingCAP CEO,但是不知道的是,我也是 PingCAP 的产品经理,应该也是最大的产品经理,是对于产品重大特性具有一票否决权的人。中国有一类产品经理是这样的,别人有的功能我们统统都要有,别人没有的功能,我们也统统都要有,所以大家看到传统的国内好多产品就是一个超级巨无霸,功能巨多、巨难用。所以我在 PingCAP 的一个重要职责是排除掉“看起来应该需要但实际上不需要”的那些功能,保证我们的产品足够的专注、足够聚焦,同时又具有足够的弹性。一、最初的三个基本信念 本次分享题目是《TiDB 的架构演进哲学》,既然讲哲学那肯定有故事和教训,否则哲学从哪儿来呢?但从另外的角度来说,一般大家来讲哲学就先得有信念。有一个内容特别扯的美剧叫做《美国众神》,里面核心的一条思路是“你相信什么你就是什么”。其实人类这么多年来,基本上也是朝这条线路在走的,人类对于未知的东西很难做一个很精确的推导,这时信念就变得非常重要了。图 1 最初的基本信念实际上,我们开始做 TiDB 这个产品的时候,第一个信念就是相信云是未来。当年 K8s 还没火,我们就坚定的拥抱了 K8s。第二是不依赖特定硬件、特定的云厂商,也就是说 TiDB 的设计方向是希望可以 Run 在所有环境上面,包括公有云私有云等等。第三是能支持多种硬件,大家都知道我们支持 X86、AMD64、ARM 等等,可能大家不清楚的是 MIPS,MIPS 典型代表是龙芯,除此之外,TiDB 未来还可以在 GPU 上跑(TiFlash 的后续工作会支持 GPU)。二、早期用户故事 2.1 Make it work 有一句话大概是“眼睛里面写满了故事,脸上没有一点沧桑”,其实现实是残酷的,岁月一定会给你沧桑的。我们早期的时候,也有相对比较难的时候,这时候就有一些故事关于我们怎么去经历、怎么渡过的。 首先大家做产品之前肯定先做用户调研,这是通用的流程,我们当初也做过这个事,跟用户聊。我们通常会说:“我们要做一个分布式数据库,自动弹性伸缩,能解决分库分表的问题,你会用吗?”用户说“那肯定啊,现在的分库分表太痛苦了。”这是最初我们获取需求最普通的方式,也是我们最容易掉入陷阱的方式,就好像“我有一百万,你要不要?肯定要。”“我有一瓶水,喝了之后就健康无比,延年益寿你要不要?肯定要。”很容易就得到类似的结论。所以这个一句话结论的代价是我们进行了长达两年的开发。在这两年的时间里,我们确定了很多的技术方向,比如最初 TiDB 就决定是分层的。很显然一个复杂的系统如果没有分层,基本上没有办法很好的控制规模和复杂度。TiDB 分两层,一层是 SQL 层,一层是 key-value 层,那么到底先从哪一个层开始写呢?其实从哪层开始都可以,但是总要有一个先后,如何选择?这里就涉及到 TiDB 的第一条哲学。我们做一个产品的时候会不断面临选择,那每次选择的时候核心指导思想是什么?核心思想是能一直指导我们持续往前去迭代,所以我们第一条哲学就是:永远站在离用户更近的地方去考虑问题。为什么我们会定义这样一条哲学?因为离用户越近越能更快的得到用户的反馈,更快的验证你的想法是不是可行的。显然 SQL 层离用户更近,所以我们选择从 SQL 层写起。其实一直到现在,绝大多数用户用 TiDB 的时候根本感受不到 KV 层的存在,用户写的都是 SQL,至于底层存储引擎换成了别的,或者底层的 RocksDB 做了很多优化和改进,这些变化对于用户关注的接口来说是不可见的。选择从 SQL 层开始写之后,接下来面临的问题就是怎么做测试,怎么去更好的做验证,怎么让整个架构,先能够完整跑起来。在软件开发领域有一条非常经典的哲学:「Make it work, make it right, make it fast」。我想大家每一个学软件开发的人,或者每一个学计算机的人可能都听过这样一句话。所以当时我们就做另外一个决定,先在已有的 KV 上面构建出一个原形,用最短的时间让整个系统能够先能 work。我们在 2015 年的 9 月份开源了第一个版本,当时是没有存储层的,需要接在 HBase 上。当这个系统能跑起来之后,我们的第一想法是赶紧找到当初调研时说要用的那些用户,看看他们是什么想法,尽快的去验证我们的想法是不是可行的。因为很多人做产品思维属于自嗨型,“我做的东西最厉害,只要一推出去肯定一群人蜂拥而至。”抱有这种想法的人太多了,实际上,只有尽快去验证才是唯一的解决之道,避免产品走入误区。图 2 与调研用户第二次对话然而当我跟用户讲,你需要先装一个 Hadoop,可能还要装一组 Zookeeper,但用户说:“我只想要一个更强大的 MySQL,但是让我装这一堆东西,你是解决问题还是引入问题?”这个问题有什么解决办法呢?一个办法是你去解决用户,可以通过销售或者通过某些关系跟用户聊,显然这是一个不靠谱的思路。作为一个产品型的公司,我们很快就否了这个想法。用户的本质要求是:你不要给我装一堆的东西,要真正解决我的问题。所以我们马上开始启动分布式 KV 的开发工作,彻底解决掉这个问题,满足用户的要求。图 3 开发 TiKV 前的技术考量开始开发 KV 层时候又会面临很多技术选择,我们有很多考量(如图 3)。第一点,我们认为作为数据库最重要的是正确性。 假设这个数据库要用在金融行业,用在银行、保险、证券,和其他一些非常关键的场合的时候,正确性就是无比重要的东西。没有人会用一个不正确的数据库。第二点是实现简洁、易用。 用户对于一个不简洁、不易用的东西是无法接受的,所以我们当时的一个想法是一定要做得比 HBase 更加易用,代码量也要比 HBase 小,所以时至今天 TiDB 代码量仍然是比 HBase 小得多,大约还不到 HBase 的十分之一。第三点考虑是扩展性。 TiDB 不仅在整体上是分层的,在存储层 TiKV 内部也是分层的,所以有非常好的扩展性,也支持 Raw KV API、Transaction API,这个设计后来也收获了很多用户的支持,比如一点资讯的同学就是用的 Raw KV API。第四点就是要求高性能低延迟。 大家对于数据库的性能和延迟的追求是没有止境的,但是我们当时并没有把太多精力花在高性能低延迟上。刚才说到我们有一条哲学是「Make it work, make it right, make it fast」,大家可以看到这句话里面 「Fast」是放最后的,这一点也是 TiDB 和其他产品有非常大的差异的地方。作为一个技术人员,通常大家看一个产品好不好,就会想:“来,不服跑个分,产品架构、易用性、技术文档、Community 这些指标都不看,先跑个分让大家看看行不行”。这个思路真正往市场上去推时是不对的。很多事情的选择是一个综合的过程。你可以让你的汽车跑的巨快无比,上面东西全拆了就留一个发动机和四个轮子,那肯定也是跑得巨快,重量轻,而且还是敞篷车,但没有一个人会在路上用的。同样的,选择 Rust 也是综合考量的结果。我们看中了 Rust 这个非常具有潜力的语言。当时 Rust 没有发布 1.0,还不是一个 stable 版本,但我们相信它会有 1.0。大概过了几个月,Rust 就发布了 1.0 版本,证明我们的选择还是非常正确的。最后一点就是稳定性。 作为一个分布式数据库,每一层的稳定性都非常重要。最底下的一个存储引擎,我们选择了非常稳定的 RocksDB。不过后来我们也查到几个 RocksDB 掉数据的 Bug。这也是做数据库或者说做基础产品的残酷性,我们在做产品的过程中找到了 Rust 编译器的 Bug,XFS 掉数据的 Bug,RocksDB 掉数据的 Bug,好像几大基础组件的 Bug 都聚在这里开会。接着我们辛辛苦苦干了三个月,然后就开源了 TiKV,所以这时候看起来没有那么多的组件了。我们也不忘初心,又去找了我们当初那个用户,说我们做了一些改进,你要不要试一试。图 4 与调研用户第三次对话但是用户这时候给了一个让我们非常伤心非常难受的回答:没有,我们不敢上线,虽然你们的产品听起来挺好的,但是数据库后面有很大的责任,心理上的担心确实是过不去。于是我们回去开始加班加点写 TiDB Binlog,让用户可以把 binlog 同步给 MySQL。毕竟用户需要一个 Backup:万一 TiDB 挂了怎么办,我需要切回 MySQL,这样才放心,因为数据是核心资产。图 5 第一个上线用户的架构图所以最终我们第一个用户上线的时候,整个 TiDB 的架构是这样的(如图 5)。用户通过 Client 连上 TiDB,然后 TiDB 后面就通过 Binlog 同步到 MySQL。后来过了一段时间,用户就把后面的 MySQL 撤了。我们当时挺好奇为什么撤了,用户说,第一个原因是后面 MySQL 撑不起一个集群给它回吐 Binlog,第二就是用了一段时间觉得 TiDB 挺稳定的,然后就不需要 Binlog 备份了。其实第一个用户上线的时候,数据量并不算大,大概 800G 的数据,使用场景是 OLTP 为主,有少量的复杂分析和运算,但这少量的复杂分析运算是当时他们选择 TiDB 最重要的原因。因为当时他们需要每隔几分钟算一个图出来,如果是在 MySQL 上面跑,大约需要十几分钟,但他们需要每隔几分钟打一个点,后来突然发现第二天才能把前一天的点都打出来,这对于一个实时的系统来说就很不现实了。虽然这个应用实践只有少部分运算,但也是偏 OLAP,我记得 TiDB 也不算特别快,大概是十几秒钟,因为支持了一个并行的 Hash Join。不管怎样,这个时候终于有第一个用户能证明我们做到了「Make it work」。2.2 Make it right 接下来就是「Make it right」。大家可能想象不到做一个保证正确性的数据库这件事情有多么难,这是一个巨大的挑战,也有巨大的工作量,是从理论到实践的距离。图 6 理论到实践的距离2.2.1 TLA+ 证明 大家可能会想写程序跟理论有什么关系?其实在分布式数据库领域是有一套方法论的。这个方法论要求先实现正确性,而实现正确的前提是有一个形式化的证明。为了保证整个系统的理论正确,我们把所有的核心算法都用 TLA+ 写了一遍证明,并且把这个证明 开源 出去了,如果大家感兴趣可以翻看一下。以前写程序的时候,大家很少想到先证明一下算法是对的,然后再把算法变成一个程序,其实今天还有很多数据库厂商没有做这件事。2.2.2 千万级别测试用例 在理论上保证正确性之后,下一步是在现实中测试验证。这时只有一个办法就是用非常庞大的测试用例做测试。大家通常自己做测试的时候,测试用例应该很少能达到十万级的规模,而我们现在测试用例的规模是以千万为单位的。当然如果以千万为单位的测试用例靠纯手写不太现实,好在我们兼容了 MySQL 协议,可以直接从 MySQL 的测试用例里收集一些。这样就能很快验证整个系统是否具备正确性。这些测试用例包括应用、框架、管理工具等等。比如有很多应用程序是依赖 MySQL,那直接拿这个应用程序在 TiDB 上跑一下,就知道 TiDB 跟 MySQL 的兼容没问题,如 Wordpress、无数的 ORM 等等。还有一些 MySQL 的管理工具可以拿来测试,比如 Navicat、PHP admin 等。另外我们把公司内部在用的 Confluence、Jira 后面接的 MySQL 都换成了 TiDB,虽然说规模不大,但是我们是希望在应用这块有足够的测试,同时自己「Eat dog food」。2.2.3 7*24 的错误注入测试用例 这些工作看起来已经挺多的了,但实际上还有一块工作比较消耗精力,叫 7*24 的错误注入测试。最早我们也不知道这个测试这么花钱,我们现在测试的集群已经是几百台服务器了。如果创业的时候就知道需要这么多服务器测试,我们可能就不创业了,好像天使轮的融资都不够买服务器的。不过好在这个事是一步一步买起来,刚开始我们也没有买这么多测试服务器,后来随着规模的扩大,不断的在增加这块的投入。大家可能到这儿的时候还是没有一个直观的感受,说这么多测试用例,到底是一个什么样的感受。我们可以对比看一下行业巨头 Oracle 是怎么干的。图 7 前 Oracle 员工的描述这是一篇 在 HackNews上面的讨论,讨论的问题是:你觉得这个最坏的、规模最大的代码是什么样子的?下面就有一个 Oracle 的前员工就介绍了 Oracle Database 12.2 这个版本的情况。他说这个整体的源代码接近 2500 万行 C 代码,可能大家维护 25 万行 C 代码的时候就会痛不欲生了,可以想想维护这么多代码的是一种什么样的感受。到现在为止,TiDB 的代码应该还不到 25 万行。当然 TiDB 的功能远远没有 Oracle 那么多,Oracle 的功能其实是很多的,历史积累一直往上加,加的很凶。这位 Oracle 前员工介绍了自己在 Oracle 的开发工作的流程,如下图:图 8 Oracle 开发者 fix bug 的过程比如用户报了一个 Bug,然后他开始 fix。第一件事是花两周左右的时间去理解 20 个不同的 flag,看看有没有可能因为内部乱七八糟的原因来造成这个 Bug。大家可能不知道 MySQL 有多少变量,我刚做 TiDB 的时候也不知道,当时我觉得自己是懂数据库的,后来去看了一下 MySQL 的 flag 的变量数就惊呆了,但看到 Oracle 的 flag 变量数,那不是惊呆了,是绝望了。大家可能知道开启 1 个 flag 的时候会对什么东西有影响,但是要去理解 20 个 flag 开启时和另外几个 flag 组合的时候都有什么影响,可能会崩溃。所以其实精通 Oracle 这件事情,实际上可能比精通 C++ 这件事情更困难的。一个 Oracle 开发者在内部处理这件事情都这么复杂,更何况是外面的用户。但 Oracle 确实是功能很强大。说回这位前 Oracle 员工的描述,他接着添加了更多的 flag 处理一个新的用户场景的问题,然后加强代码,最后改完以后会提交一个测试。先在 100 到 200 台机器上面把这个 Oracle 给 build 出来,然后再对这个 Oracle 去做新的测试。他应该对 Oracle 的测试用例的实际数量了解不深刻,我猜他可能不知道 Oracle 有多少个测试,所以写的是 “millions of tests”,这显然太低估了 Oracle 的测试数量。通常情况下,只会看到挂了的测试,看不到全部的测试数量。下面的步骤更有意思了:Go home,因为整个测试需要 20-30 个小时,跑完之后测试系统反馈了一个报告:挂了 200 多个 test,更茫然的是这 200 tests 他以前都没见过,这也是 Oracle 非常强大的一个地方,如果一个开发者的代码提交过去挂掉一两百个测试,是很正常的事情,因为 Oracle 的测试能 Cover 东西非常多,是这么多年来非常强大的积累,不停的堆功能的同时就不停的堆测试,当然也不停的堆 flag。所以从另一个角度来看,限制一个系统的功能数量,对于维护来说是非常重要的。总之,看完这个回复之后,我对行业前辈们充满了敬畏之情。2.3 Make it fast 2.3.1 新问题 随着 TiDB 有用户开始上线,用户的数量和规模越来越大,这时候就出现了一个很有意思的事情,一部分用户把 TiDB 当成了可以支持事务、拥有良好实时性的数据仓库在用,和我们说:我们把公司 Hadoop 换了,数据量十几 T。我们就一下开始陷入了深深的思考,因为 TiDB 本来设计的目的不是这个方向,我们想做一个分布式 OLTP 数据库,并没有想说我们要做一个 Data Warehouse。但是用户的理由让我们觉得也很有道理,无法反驳——TiDB 兼容 MySQL,会 MySQL 的人很多,更好招人,最重要的是 Hadoop 跑得还不够快。虽然我们自己也很吃惊,但这体现了 TiDB 另一方面的价值,所以我们继续问用户还有什么痛点。用户表示还有一部分查询不够快,数据没办法做到 shuffle,而且以前用 Spark,TiDB 好像没有 Spark 的支持。我们想了想,TiDB 直接连 Spark 也是可以的,但这样 Spark 对底下没有感知,事务跑得巨慢,就跟 Spark 接 MySQL 没什么差别。我们研究了一下,做出了一个新的东西——TiSpark。TiSpark 就开始能够同时在 TiDB 上去跑 OLAP 和 OLTP。图 9 出现的新问题就在我们准备改进 TiDB 的数据分析能力的时候,突然又有一大批 TP 用户上线了,给我们报了一堆问题,比如执行计划不准确,选不到最优执行计划,数据热点分布不均匀,Raft store 单线程写入瓶颈,报表跑的慢等等……于是我们制定了 1.0 到 2.X 的计划,先把用户提的这些问题一一解决。这里有另外一条哲学:将用户遇到的问题放在第一优先级。我们从产品最初设计和之后 Roadmap 计划永远是按照这个原则去做的。首先,执行计划不准确的问题。 最简单有效的解决办法是加一个 Index Hint,就像是“你告诉我怎么执行,我就怎么执行,我自己不会自作聪明的选择”。但这不是长久之计,因为用户可能是在一个界面上选择各种条件、参数等等,最后拼成一个 SQL,他们自己没办法在里面加 Index Hint。我们不能决定用户的使用习惯,所以从这时开始,我们决定从 RBO(Rule Based Optimizer)演进到 CBO(Cost Based Optimizer),这条路也走了非常久,而且还在持续进行。第二个是热点数据处理问题。 我们推出了一个热点调度器,这个可能大家在分布式数据库领域第一次听说,数据库领域应该是 PingCAP 首创。 热点调度器会统计、监控整个系统热点情况,再把这些热点做一个快速迁移和平衡,比如整个系统有 10 个热点,某一个机器上有 6 个热点,这台机器就会很卡,这时热点调度器会开始将热点打散,快速分散到集群的其他机器上去,从而让整个集群的机器都处于比较正常的负载状态。第三个就是解决 Raft store 单线程瓶颈的问题。为了改变 Raft store 单线程,我们大概花了一年多的时间,目前已经在 TiDB 3.0 里实现了。我们将 Raft store 线程更多耗时的计算变成异步操作,offload 到其它线程。不知道有没有人会好奇为什么这个改进会花这么长时间?我们一直认为数据库的稳定性第一位的。分布式系统里面一致性协议本身也复杂,虽然说 Raft 是比 Paxos 要简单,但它实际做起来也很复杂,要在一个复杂系统里支持多线程,并且还要做优化,尽可能让这个 I/O 能 group 到一起,其实非常耗精力。第四个就是解决报表跑得慢的问题,这个骨头特别硬,我们也是啃到今天还在继续。首先要大幅提升 TiDB 在分析场景下的能力。大家都可以看到我们在发布每一个版本的时候,都会给出与上一个版本的 TPC-H 性能对比(TPC-H 是一个有非常多的复杂查询、大量运算的场景)。其次就是高度并行化,充分利用多核,并提供 参数 控制,这个特性可能很多用户不知道,我们可以配一下参数, …"}, {"url": "https://pingcap.com/weekly/2019-05-27-tidb-weekly/", "title": "Weekly update (May 20 ~ May 26, 2019)", "content": " Weekly update in TiDB Last week, we landed 44 PRs in the TiDB repository.Added Support the tidb_txn_mode session variable Support dump/load correlation of statistics histograms Add a make ddltest target to generate ddltest binary data Add the tidb_low_resolution_tso session variable for reading data with low resolution TSO Support View related privileges in mysql.tables_priv Support LOAD DATA...IGNORE/REPLACE in the syntax Improved Make pessimistic lock TTL easier to set and validate the value Refine the error message in latches Enhance index join for more scenarios Update 2019-04-11-indexmerge.md with some edits Fix a potential goroutine leak in distSQL Add a flag to indicate that the TiDB server is closing Validate table information before doing DDL jobs to avoid potential loading infoschema errors Merge Window functions with the same specification name Only retry needed Regions when getting Region row count during fast Analyze Cache the vendor to speed up Docker building Fixed Correct the behavior of the IndexJoin hint for SemiJoin Fix notLeader in the Region cache Fix the show grants result for RBAC Fix the issue that the OOM panic is not recovered currently in the distSQL layer Fix a bug in the AlterSchema job and add more tests Check the table name for columns in the partition expression Fix the issue that ByItem does not filter NULL out Fix the issue that canceling range tasks might not return errors Fix a GetPartition function bug when adding indexes Fix the issue that the maximum value of UINT64 is returned when -1 is used for point-get Fix the issue that TiDB cannot be used for a long time under extreme conditions if relying only on the information in PD Weekly update in TiSpark Last week, we landed 17 PRs in the TiSpark repository.Fixed Fix index scans on partition tables Fix the issue that RegionVerId missed to implement equals() Fix the issue that the default value of Integer is not parsed to Long Fix the issue that FromSparkType() incorrectly transforms Integer to Long Fix the issue that KeyNotInRegion might occur when retrieving rows by handles Fix the issue of encodeValue with unsigned comparison Weekly update in TiKV and PD Last week, we landed 45 PRs in the TiKV and PD repositories.Added Add the statistics of the store flow Add BatchSlowHashAggregationExecutor Add configuration items and enable pessimistic-txn by default Add RPN functions Int, Real and Decimal IsNull, IsTrue and IsFalse Support pessimistic transactions (experimental feature) Add the ReadIndex service Add hibernate Regions Add operator limit for stores Improved Remove the local reader thread Retry to connect PD for infinity, and log periodically Remove the scheduler thread Fixed Fix the issue that snapshots might lose some applied results New contributors (Thanks!) tidb: sundl123 sunxiaoguang tikv: wjhuang2016 parser: sundl123 "}, {"url": "https://pingcap.com/blog/use-tidb-dm-to-migrate-and-replicate-data-from-mysqlmariadb-amazon-aurora/", "title": "Tutorial: Use TiDB DM to Migrate and Replicate Data from MySQL, MariaDB & Amazon Aurora", "content": "Earlier this year, Team PingCAP open-sourced TiDB Data Migration (DM), an integrated data transfer and replication management platform that supports full data migration and incremental data replication from MySQL or MariaDB instances, or Amazon Aurora, into a TiDB cluster.Many TiDB users currently use TiDB DM to connect sharded MySQL, MariaDB, or Amazon Aurora to TiDB, treating TiDB almost as a slave, then run analytical workloads on this TiDB cluster to fulfill real-time reporting needs. TiDB DM provides good support if you need to manage multiple data replication tasks at the same time or need to merge multiple MySQL or MariaDB instances into a single TiDB cluster. TiDB Data Migration Architecture (For more detailed information on the architectural design and implementation of TiDB DM, read this deep-dive blog post.)To ease the learning curve of using TiDB DM, we’ve put together a TiDB DM (Data Migration) Tutorial for you to experiment with this tool. While the settings used in this tutorial are certainly not for production deployment, it’s a quick and easy way to kick the tires before going further on your TiDB journey. And our team is always here—if you need help or support, just contact us."}, {"url": "https://pingcap.com/meetup/meetup-102-20190523/", "title": "【Infra Meetup No.102】 How We Build TiDB & TiDB Ecosystem Tools 概览", "content": " Topic 1:How We Build TiDB 讲师介绍:姚维,TiDB 核心开发工程师,分布式数据库专家,我司华南区总经理。知名开源数据库中间件 Atlas 作者。 视频 | Infra Meetup No.102:How We Build TiDB PPT 链接 本次分享姚维老师从数据库发展的历程讲起,讲解了 TiDB 的使命。以及从底向上的描述我们是怎么去实现一个 HTAP 数据库的,内容包括如果实现容错性,如果实现扩展性,如何在分布式的事务型 KV 存储之上构建完整的 SQL 系统。Topic 2:TiDB Ecosystem Tools 概览 讲师介绍:杨非,TiDB Ecosystem Tools Team 研发工程师,目前主要负责数据迁移平台 TiDB DM 的设计与开发工作。 视频 | Infra Meetup No.102:TiDB Ecosystem Tools 概览 杨非老师围绕 TiDB 生态系统架构中数据同步相关工具展开介绍,内容包括工具的架构设计,实现原理和使用场景等。具体工具包括以下三款:从 TiDB 集群实时同步数据到自定义下游的工具 TiDB-Binlog,从 MySQL/MariaDB 同步数据到 TiDB 的工具 Data Migration,以及支持将数据从 SQL 文件或 csv 格式文件快速导入 TiKV 集群的 TiDB-lightning toolset。"}, {"url": "https://pingcap.com/blog-cn/fix-two-linux-kernel-bugs-while-testing-tidb-operator-in-k8s/", "title": "诊断修复 TiDB Operator 在 K8s 测试中遇到的 Linux 内核问题", "content": " Kubernetes(K8s)是一个开源容器编排系统,可自动执行应用程序部署、扩展和管理。它是云原生世界的操作系统。 K8s 或操作系统中的任何缺陷都可能使用户进程存在风险。作为 PingCAP EE(效率工程)团队,我们在 K8s 中测试 TiDB Operator(一个创建和管理 TiDB 集群的工具)时,发现了两个 Linux 内核错误。这些错误已经困扰我们很长一段时间,并没有在整个 K8s 社区中彻底修复。经过广泛的调查和诊断,我们已经确定了处理这些问题的方法。在这篇文章中,我们将与大家分享这些解决方法。不过,尽管这些方法很有用,但我们认为这只是权宜之策,相信未来会有更优雅的解决方案,也期望 K8s 社区、RHEL 和 CentOS 可以在不久的将来彻底修复这些问题。Bug #1: 诊断修复不稳定的 Kmem Accounting 关键词:SLUB: Unable to allocate memory on node -1社区相关 Issue: https://github.com/kubernetes/kubernetes/issues/61937 https://github.com/opencontainers/runc/issues/1725 https://support.mesosphere.com/s/article/Critical-Issue-KMEM-MSPH-2018-0006 问题起源 薛定谔平台是我司开发的基于 K8s 建立的一套自动化测试框架,提供各种 Chaos 能力,同时也提供自动化的 Bench 测试,各类异常监控、告警以及自动输出测试报告等功能。我们发现 TiKV 在薛定谔平台上做 OLTP 测试时偶尔会发生 I/O 性能抖动,但从下面几项来看未发现异常: TiKV 和 RocksDB 的日志 CPU 使用率 内存和磁盘等负载信息 只能偶尔看到 dmesg 命令执行的结果中包含一些 “SLUB: Unable to allocate memory on node -1” 信息。问题分析 我们使用 perf-tools 中的 funcslower trace 来执行较慢的内核函数并调整内核参数 hung_task_timeout_secs 阈值,抓取到了一些 TiKV 执行写操作时的内核路径信息:从上图的信息中可以看到 I/O 抖动和文件系统执行 writepage 有关。同时捕获到性能抖动的前后,在 node 内存资源充足的情况下,dmesg 返回的结果也会出现大量 “SLUB: Unable to allocate memory on node -1” 的信息。从 hung_task 输出的 call stack 信息结合内核代码发现,内核在执行 bvec_alloc 函数分配 bio_vec 对象时,会先尝试通过 kmem_cache_alloc 进行分配,kmem_cache_alloc 失败后,再进行 fallback 尝试从 mempool 中进行分配,而在 mempool 内部会先尝试执行 pool->alloc 回调进行分配,pool->alloc 分配失败后,内核会将进程设置为不可中断状态并放入等待队列中进行等待,当其他进程向 mempool 归还内存或定时器超时(5s) 后,进程调度器会唤醒该进程进行重试 ,这个等待时间和我们业务监控的抖动延迟相符。但是我们在创建 Docker 容器时,并没有设置 kmem limit,为什么还会有 kmem 不足的问题呢?为了确定 kmem limit 是否被设置,我们进入 cgroup memory controller 对容器的 kmem 信息进行查看,发现 kmem 的统计信息被开启了, 但 limit 值设置的非常大。我们已知 kmem accounting 在 RHEL 3.10 版本内核上是不稳定的,因此怀疑 SLUB 分配失败是由内核 bug 引起的,搜索 kernel patch 信息我们发现确实是内核 bug, 在社区高版本内核中已修复:slub: make dead caches discard free slabs immediately同时还有一个 namespace 泄漏问题也和 kmem accounting 有关:mm: memcontrol: fix cgroup creation failure after many small jobs那么是谁开启了 kmem accounting 功能呢?我们使用 bcc 中的 opensnoop 工具对 kmem 配置文件进行监控,捕获到修改者 runc 。从 K8s 代码上可以确认是 K8s 依赖的 runc 项目默认开启了 kmem accounting。解决方案 通过上述分析,我们要么升级到高版本内核,要么在启动容器的时候禁用 kmem accounting 功能,目前 runc 已提供条件编译选项,可以通过 Build Tags 来禁用 kmem accounting,关闭后我们测试发现抖动情况消失了,namespace 泄漏问题和 SLUB 分配失败的问题也消失了。操作步骤 我们需要在 kubelet 和 docker 上都将 kmem account 功能关闭。 kubelet 需要重新编译,不同的版本有不同的方式。如果 kubelet 版本是 v1.14 及以上,则可以通过在编译 kubelet 的时候加上 Build Tags 来关闭 kmem account:$ git clone --branch v1.14.1 --single-branch --depth 1 [https://github.com/kubernetes/kubernetes](https://github.com/kubernetes/kubernetes) $ cd kubernetes $ KUBE_GIT_VERSION=v1.14.1 ./build/run.sh make kubelet GOFLAGS="-tags=nokmem" 但如果 kubelet 版本是 v1.13 及以下,则无法通过在编译 kubelet 的时候加 Build Tags 来关闭,需要重新编译 kubelet,步骤如下。首先下载 Kubernetes 代码:$ git clone --branch v1.12.8 --single-branch --depth 1 https://github.com/kubernetes/kubernetes $ cd kubernetes 然后手动将开启 kmem account 功能的 两个函数 替换成 下面这样:func EnableKernelMemoryAccounting(path string) error { return nil } func setKernelMemory(path string, kernelMemoryLimit int64) error { return nil } 之后重新编译 kubelet:$ KUBE_GIT_VERSION=v1.12.8 ./build/run.sh make kubelet 编译好的 kubelet 在 ./_output/dockerized/bin/$GOOS/$GOARCH/kubelet 中。 同时需要升级 docker-ce 到 18.09.1 以上,此版本 docker 已经将 runc 的 kmem account 功能关闭。 最后需要重启机器。 验证方法是查看新创建的 pod 的所有 container 已关闭 kmem,如果为下面结果则已关闭:$ cat /sys/fs/cgroup/memory/kubepods/burstable/pod<pod-uid>/<container-id>/memory.kmem.slabinfo cat: memory.kmem.slabinfo: Input/output error Bug #2:诊断修复网络设备引用计数泄漏问题 关键词:kernel:unregister_netdevice: waiting for eth0 to become free. Usage count = 1社区相关 Issue: https://github.com/kubernetes/kubernetes/issues/64743 https://github.com/projectcalico/calico/issues/1109 https://github.com/moby/moby/issues/5618 问题起源 我们的薛定谔分布式测试集群运行一段时间后,经常会持续出现“kernel:unregister_netdevice: waiting for eth0 to become free. Usage count = 1” 问题,并会导致多个进程进入不可中断状态,只能通过重启服务器来解决。问题分析 通过使用 crash 工具对 vmcore 进行分析,我们发现内核线程阻塞在 netdev_wait_allrefs 函数,无限循环等待 dev->refcnt 降为 0。由于 pod 已经释放了,因此怀疑是引用计数泄漏问题。我们查找 K8s issue 后发现问题出在内核上,但这个问题没有简单的稳定可靠复现方法,且在社区高版本内核上依然会出现这个问题。为避免每次出现问题都需要重启服务器,我们开发一个内核模块,当发现 net_device 引用计数已泄漏时,将引用计数清 0 后移除此内核模块(避免误删除其他非引用计数泄漏的网卡)。为了避免每次手动清理,我们写了一个监控脚本,周期性自动执行这个操作。但此方案仍然存在缺陷: 引用计数的泄漏和监控发现之间存在一定的延迟,在这段延迟中 K8s 系统可能会出现其他问题; 在内核模块中很难判断是否是引用计数泄漏,netdev_wait_allrefs 会通过 Notification Chains 向所有的消息订阅者不断重试发布 NETDEV_UNREGISTER 和 NETDEV_UNREGISTER_FINAL 消息,而经过 trace 发现消息的订阅者多达 22 个,而去弄清这 22 个订阅者注册的每个回调函数的处理逻辑来判断是否有办法避免误判也不是一件简单的事。 解决方案 在我们准备深入到每个订阅者注册的回调函数逻辑的同时,我们也在持续关注 kernel patch 和 RHEL 的进展,发现 RHEL 的 solutions:3659011 有了一个更新,提到 upstream 提交的一个 patch:route: set the deleted fnhe fnhe_daddr to 0 in ip_del_fnhe to fix a race在尝试以 hotfix 的方式为内核打上此补丁后,我们持续测试了 1 周,问题没有再复现。我们向 RHEL 反馈测试信息,得知他们已经开始对此 patch 进行 backport。操作步骤 推荐内核版本 Centos 7.6 kernel-3.10.0-957 及以上。 安装 kpatch 及 kpatch-build 依赖:UNAME=$(uname -r) sudo yum install gcc kernel-devel-${UNAME%.*} elfutils elfutils-devel sudo yum install pesign yum-utils zlib-devel binutils-devel newt-devel python-devel perl-ExtUtils-Embed audit-libs audit-libs-devel numactl-devel pciutils-devel bison # enable CentOS 7 debug repo sudo yum-config-manager --enable debug sudo yum-builddep kernel-${UNAME%.*} sudo debuginfo-install kernel-${UNAME%.*} # optional, but highly recommended - enable EPEL 7 sudo yum install ccache ccache --max-size=5G 安装 kpatch 及 kpatch-build:git clone https://github.com/dynup/kpatch && cd kpatch make sudo make install systemctl enable kpatch 下载并构建热补丁内核模块:curl -SOL https://raw.githubusercontent.com/pingcap/kdt/master/kpatchs/route.patch kpatch-build -t vmlinux route.patch (编译生成内核模块) mkdir -p /var/lib/kpatch/${UNAME} cp -a livepatch-route.ko /var/lib/kpatch/${UNAME} systemctl restart kpatch (Loads the kernel module) kpatch list (Checks the loaded module) 总结 虽然我们修复了这些内核错误,但是未来应该会有更好的解决方案。对于 Bug#1,我们希望 K8s 社区可以为 kubelet 提供一个参数,以允许用户禁用或启用 kmem account 功能。对于 Bug#2,最佳解决方案是由 RHEL 和 CentOS 修复内核错误,希望 TiDB 用户将 CentOS 升级到新版后,不必再担心这个问题。"}, {"url": "https://pingcap.com/weekly/2019-05-20-tidb-weekly/", "title": "Weekly update (May 13 ~ May 19, 2019)", "content": " Weekly update in TiDB Last week, we landed 37 PRs in the TiDB repository.Added Implement ALTER DATABASE to alter the charset/collation Support the incremental and repeats importer columns Improved Disable the new @@wait_timeout feature Improve the package test code coverage to above 85% in util/codec Make the resolveLock phase of GC concurrent to improve its speed Always set isPessimisticLock in the request for pessimistic transactions Use multiple indexes to scan a table if possible Refine transaction retry error messages Reduce the wait backoff time when the lock has expired Fixed Fix a time format error in ParseSlowLog Fix the issue that the RANGE frame has no ORDER BY clause in the Window function Fix the inconsistency problem by prohibiting modifying decimal precision Fix a bug when an unsigned histogram meets signed ranges in the feedback Fix the issue that period_diff is not compatible with MySQL 8.0 Fix the wrong range calculation for the CHAR column Fix the issue that the type of the add_date result is not compatible with MySQL Fix the float overflow issue when converting a decimal to a float and then converting a float to an uint Weekly update in TiSpark Last week, we landed 19 PRs in the TiSpark repository.Fixed Fix the concurrent dagRequest issue Fix the behavior of downgrading to a table scan when performing an index scan Weekly update in TiKV and PD Last week, we landed 24 PRs in the TiKV and PD repositories.Added Add the explain analyze support for the aggregation executor Add the summary support for the Top-N executor Set timeout for MoveLeader Support the execution summary for the selection executor Add the ScanRegions gRPC protocol in PD Fixed Do not collect requests that read indexes during transferring the leader Reject transferring the leader when the Region’s configuration is recently changed Improved Handle system commands in the high priority pool Make most ticks lazy in TiKV Use tokio_threadpool for the transaction scheduler Speed up Docker building and remove unused files New contributors (Thanks!) tikv: andrisak tidb: hailanwhu jzdxeb docs-cn: spongedu "}, {"url": "https://pingcap.com/blog/design-and-implementation-of-golang-failpoints/", "title": "Design and Implementation of Golang Failpoints", "content": " Generally, a large complex system consists of multiple components. To test this system, it is essential to simulate faults in each component. Further, we should integrate fault simulation into the automated testing system without being intrusive to the testing system. We judge the correctness and stability of the system by automatically activating fault points in automated testing to simulate faults and observing whether the final result meets our expectations.Manual testing can be expensive or lack the fine-grained control we need. For example, it could be disastrous to simulate a network anomaly by plugging and unplugging the network cable in a distributed system or to simulate a disk failure by damaging the disks in a storage system. Therefore, we need some automated methods to perform deterministic fault testing.The Failpoint project was created just for this purpose. It is an implementation of FreeBSD failpoints for Golang. Failpoints let us inject errors or abnormal behaviors into the code and trigger these abnormal behaviors by environment variables or code. A failpoint simulates error handling in various complex systems to improve their fault tolerance and stability.You can use failpoints to simulate any error a component produces. Some typical application scenarios include the following: Random service delays or unavailable services in the microservice architecture. Increases in disk I/O latency, insufficient I/O throughput, and long times to persist data to the disk in the storage system. Hotspots in the scheduling system and failure of executing some scheduling instructions. A third party repeatedly requests the callback interface of a successful charge in the charging system. Instability, framedrops, and high latency in a game player’s network, and various anomalous inputs (plug-in requests) in game developing. Why “reinvent the wheel”? Although the etcd team developed gofail in 2016 that has greatly simplified fault injection, and we introduced gofail to perform fault injection testing in 2018, we still want to improve its features and make it easier to use. Therefore, we decided to invent a better “wheel.”Before introducing our new wheel, let’s first see how to use the old one.How to use gofail? Inject a failpoint in the program by adding code comments.// gofail: var FailIfImportedChunk int // if merger, ok := scp.merger.(*ChunkCheckpointMerger); ok && merger.Checksum.SumKVS() >= uint64(FailIfImportedChunk) { // rc.checkpointsWg.Done() // rc.checkpointsWg.Wait() // panic("forcing failure due to FailIfImportedChunk") // } // goto RETURN1 // gofail: RETURN1: // gofail: var FailIfStatusBecomes int // if merger, ok := scp.merger.(*StatusCheckpointMerger); ok && merger.EngineID >= 0 && int(merger.Status) == FailIfStatusBecomes { // rc.checkpointsWg.Done() // rc.checkpointsWg.Wait() // panic("forcing failure due to FailIfStatusBecomes") // } // goto RETURN2 // gofail: RETURN2: Use the gofail enable command to convert the comments to code.if vFailIfImportedChunk, __fpErr := __fp_FailIfImportedChunk.Acquire(); __fpErr == nil { defer __fp_FailIfImportedChunk.Release(); FailIfImportedChunk, __fpTypeOK := vFailIfImportedChunk.(int); if !__fpTypeOK { goto __badTypeFailIfImportedChunk} if merger, ok := scp.merger.(*ChunkCheckpointMerger); ok && merger.Checksum.SumKVS() >= uint64(FailIfImportedChunk) { rc.checkpointsWg.Done() rc.checkpointsWg.Wait() panic("forcing failure due to FailIfImportedChunk") } goto RETURN1; __badTypeFailIfImportedChunk: __fp_FailIfImportedChunk.BadType(vFailIfImportedChunk, "int"); }; /* gofail-label */ RETURN1: if vFailIfStatusBecomes, __fpErr := __fp_FailIfStatusBecomes.Acquire(); __fpErr == nil { defer __fp_FailIfStatusBecomes.Release(); FailIfStatusBecomes, __fpTypeOK := vFailIfStatusBecomes.(int); if !__fpTypeOK { goto __badTypeFailIfStatusBecomes} if merger, ok := scp.merger.(*StatusCheckpointMerger); ok && merger.EngineID >= 0 && int(merger.Status) == FailIfStatusBecomes { rc.checkpointsWg.Done() rc.checkpointsWg.Wait() panic("forcing failure due to FailIfStatusBecomes") } goto RETURN2; __badTypeFailIfStatusBecomes: __fp_FailIfStatusBecomes.BadType(vFailIfStatusBecomes, "int"); }; /* gofail-label */ RETURN2: Issues while using gofail While using gofail, we encountered the following issues: If we inject a failpoint in the program by modifying code comments and there is no compiler to detect errors, we may introduce errors in the code. Our failpoints are globally valid. To shorten the time of automated testing, large projects usually introduce parallel testing. In this case, different parallel projects interfere with each other. We need to write some hacking code to avoid unnecessary error logs. Taking the above code as an example, we must write // goto RETURN2 and // gofail: RETURN2: with a blank line between them. (To understand the reason for this approach, review the above generated code.) What kind of failpoints should we design? What should an ideal failpoint look like? An ideal failpoint should be defined by the code and not be intrusive to the application logic. In a language that supports macros (like Rust), we can define a failpoint by defining a fail_point macro as follows:fail_point!("transport_on_send_store", |sid| if let Some(sid) = sid { let sid: u64 = sid.parse().unwrap(); if sid == store_id { self.raft_client.wl().addrs.remove(&store_id); } }) However, we face the following difficulties: Golang does not support macros. Golang does not support compiler plug-ins. Golang tags cannot be implemented elegantly (go build --tag="enable-failpoint-a"). Failpoint design principles We designed a failpoint based on the following principles: A failpoint should be defined by the Golang code instead of comments or other techniques. The failpoint code shouldn’t have extra overhead. It shouldn’t affect the normal feature logic or be intrusive to any feature code. Injecting the failpoint code shouldn’t lead to performance regression. The failpoint code can’t appear in the finally released binary files. The failpoint code should be easy to read and write, and can be checked by an introduced compiler. The finally generated failpoint code should be readable. For the generated code, the line number of the feature logic code should remain the same for the convenience of debugging. Parallel testing should be supported, and we should be able to control whether a specific failpoint is activated using context. Context. How to implement a failpoint macro in Golang? We can use an abstract syntax tree (AST) to rewrite the code to implement failpoints that meet the above design principles in Golang. The rationale diagram is as follows:For any Golang source file, we can parse its syntax tree to traverse the whole syntax tree, find the injection points of all failpoints, and rewrite and convert the syntax tree to the logic we want.Related concepts Failpoint A failpoint is a code snippet, and it is executed only when the corresponding failpoint name is activated. If we disable a failpoint using failpoint.Disable("failpoint-name-for-demo"), this failpoint isn’t triggered. Failpoint code snippets aren’t compiled in the final binary files.Look at the following example. Assume that we simulate the file system permission control:func saveTo(path string) error { failpoint.Inject("mock-permission-deny", func() error { // It's OK to access outer scope variable return fmt.Errorf("mock permission deny: %s", path) }) } If we just want to simulate no privilege for the specific directory, for example, /etc/ and /usr/, we can define GO_FAILPOINTS=mock-permission-deny=return("/etc/,/usr/").func saveTo(path string) error { failpoint.Inject("mock-permission-deny", func(val failpoint.Value) { …"}, {"url": "https://pingcap.com/blog-cn/tikv-source-code-reading-7/", "title": "TiKV 源码解析系列文章(七)gRPC Server 的初始化和启动流程", "content": " 本篇 TiKV 源码解析将为大家介绍 TiKV 的另一周边组件—— grpc-rs。grpc-rs 是 PingCAP 实现的一个 gRPC 的 Rust 绑定,其 Server/Client 端的代码框架都基于 Future,事件驱动的 EventLoop 被隐藏在了库的内部,所以非常易于使用。本文将以一个简单的 gRPC 服务作为例子,展示 grpc-rs 会生成的服务端代码框架和需要服务的实现者填写的内容,然后会深入介绍服务器在启动时如何将后台的事件循环与这个框架挂钩,并在后台线程中运行实现者的代码。基本的代码生成及服务端 API gRPC 使用 protobuf 定义一个服务,之后调用相关的代码生成工具就可以生成服务端、客户端的代码框架了,这个过程可以参考我们的 官方文档。客户端可以直接调用这些生成的代码,向服务端发送请求并接收响应,而服务端则需要服务的实现者自己来定制对请求的处理逻辑,生成响应并发回给客户端。举一个例子:#[derive(Clone)] struct MyHelloService {} impl Hello for MyHelloService { // trait 中的函数签名由 grpc-rs 生成,内部实现需要用户自己填写 fn hello(&mut self, ctx: RpcContext, req: HelloRequest, sink: UnarySink<HelloResponse>) { let mut resp = HelloResponse::new(); resp.set_to(req.get_from()); ctx.spawn( sink.success(resp) .map(|_| println!("send hello response back success")) .map_err(|e| println!("send hello response back fail: {}", e)) ); } } 我们定义了一个名为 Hello 的服务,里面只有一个名为 hello 的 RPC。grpc-rs 会为服务生成一个 trait,里面的方法就是这个服务包含的所有 RPC。在这个例子中唯一的 RPC 中,我们从 HelloRequest 中拿到客户端的名字,然后再将这个名字放到 HelloResponse 中发回去,非常简单,只是展示一下函数签名中各个参数的用法。然后,我们需要考虑的是如何把这个服务运行起来,监听一个端口,真正能够响应客户端的请求呢?下面的代码片段展示了如何运行这个服务:fn main() { // 创建一个 Environment,里面包含一个 Completion Queue let env = Arc::new(EnvBuilder::new().cq_count(4).build()); let channel_args = ChannelBuilder::new(env.clone()).build_args(); let my_service = MyHelloWorldService::new(); let mut server = ServerBuilder::new(env.clone()) // 使用 MyHelloWorldService 作为服务端的实现,注册到 gRPC server 中 .register_service(create_hello(my_service)) .bind("0.0.0.0", 44444) .channel_args(channel_args) .build() .unwrap(); server.start(); thread::park(); } 以上代码展示了 grpc-rs 的足够简洁的 API 接口,各行代码的意义如其注释所示。Server 的创建和启动 下面我们来看一下这个 gRPC server 是如何接收客户端的请求,并路由到我们实现的服务端代码中进行后续的处理的。第一步我们初始化一个 Environment,并设置 Completion Queue(完成队列)的个数为 4 个。完成队列是 gRPC 的一个核心概念,grpc-rs 为每一个完成队列创建一个线程,并在线程中运行一个事件循环,类似于 Linux 网络编程中不断地调用 epoll_wait 来获取事件,进行处理:// event loop fn poll_queue(cq: Arc<CompletionQueueHandle>) { let id = thread::current().id(); let cq = CompletionQueue::new(cq, id); loop { let e = cq.next(); match e.event_type { EventType::QueueShutdown => break, EventType::QueueTimeout => continue, EventType::OpComplete => {} } let tag: Box<CallTag> = unsafe { Box::from_raw(e.tag as _) }; tag.resolve(&cq, e.success != 0); } } 事件被封装在 Tag 中。我们暂时忽略对事件的具体处理逻辑,目前我们只需要知道,当这个 Environment 被创建好之后,这些后台线程便开始运行了。那么剩下的任务就是监听一个端口,将网络上的事件路由到这几个事件循环中。这个过程在 Server 的 start 方法中:/// Start the server. pub fn start(&mut self) { unsafe { grpc_sys::grpc_server_start(self.core.server); for cq in self.env.completion_queues() { let registry = self .handlers .iter() .map(|(k, v)| (k.to_owned(), v.box_clone())) .collect(); let rc = RequestCallContext { server: self.core.clone(), registry: Arc::new(UnsafeCell::new(registry)), }; for _ in 0..self.core.slots_per_cq { request_call(rc.clone(), cq); } } } } 首先调用 grpc_server_start 来启动这个 Server,然后对每一个完成队列,复制一份 handler 字典。这个字典的 key 是一个字符串,而 value 是一个函数指针,指向对这个类型的请求的处理函数——其实就是前面所述的服务的具体实现逻辑。key 的构造方式其实就是 /<ServiceName>/<RpcName>,实际上就是 HTTP/2 中头部字段中的 path 的值。我们知道 gRPC 是基于 HTTP/2 的,关于 gRPC 的请求、响应是如何装进 HTTP/2 的帧中的,更多的细节可以参考 官方文档,这里就不赘述了。接着我们创建一个 RequestCallContext,然后对每个完成队列调用几次 request_call。这个函数会往完成队列中注册若干个 Call,相当于用 epoll_ctl 往一个 epoll fd 中注册一些事件的关注。Call 是 gRPC 在进行远程过程调用时的基本单元,每一个 RPC 在建立的时候都会从完成队列里取出一个 Call 对象,后者会在这个 RPC 结束时被回收。因此,在 start 函数中每一个完成队列上注册的 Call 个数决定了这个完成队列上可以并发地处理多少个 RPC,在 grpc-rs 中默认的值是 1024 个。小结 以上代码基本都在 grpc-rs 仓库中的 src/server.rs 文件中。在 start 函数返回之后,服务端的初始化及启动过程便结束了。现在,可以快速地用几句话回顾一下:首先创建一个 Environment,内部会为每一个完成队列启动一个线程;接着创建 Server 对象,绑定端口,并将一个或多个服务注册到这个 Server 上;最后调用 Server 的 start 方法,将服务的具体实现关联到若干个 Call 上,并塞进所有的完成队列中。在这之后,网络上新来的 RPC 请求便可以在后台的事件循环中被取出,并根据具体实现的字典分别执行了。最后,不要忘记 start 是一个非阻塞的方法,调用它的主线程在之后可以继续执行别的逻辑或者挂起。本篇源码解析就到这里,下篇关于 grpc-rs 的文章我们会进一步介绍一个 Call 或者 RPC 的生命周期,以及每一阶段在 Server 端的完成队列中对应哪一种事件、会被如何处理,这一部分是 grpc-rs 的核心代码,敬请期待!"}, {"url": "https://pingcap.com/meetup/meetup-101-20190514/", "title": "【Infra Meetup No.101】 Log, Observability and Filebeat & 易果集团的数据平台建设历程 & The Evolution of TiKV", "content": " Topic 1:Log, Observability and Filebeat 讲师介绍:王鹏翰,Dashbase.Inc 研发工程师,参与研发下一代日志搜索引擎。 视频 | Infra Meetup No.101:Log, Observability and Filebeat PPT 链接 本次分享王鹏翰老师首先阐述了 Log 和 observability 是什么,以及建立可观察性(observability)的三个主要手段:logging, metrics, tracing。然后对 Filebeat 的设计架构,实现细节,以及如何对 filebeat 进行合理的监控进行了介绍。最后分享了在生产环境中 50TB/day 日志传输量下的 filebeat 调优细节。Topic 2:易果集团的数据平台建设历程 讲师介绍:罗瑞星,曾就职于前程无忧,参加过 Elasticsearch 官方文档中文翻译工作,现任易果集团数据架构专家,负责易果集团大数据平台架构,数据中台,数据仓库建设等工作。 视频 | Infra Meetup No.101:易果集团的数据平台建设历程 PPT 链接 本次分享罗瑞星老师为大家介绍了易果集团大数据体系的发展历程,主要包括: 数据工具的演进,调度工具,数据交换工具等; 数据架构的演进,包括离线架构,实时架构等。 最后详细介绍了 TiDB 在易果集团的使用,以及未来规划。 Topic 3:The Evolution of TiKV 讲师介绍:唐刘,PingCAP 首席架构师。 视频 | Infra Meetup No.101:The Evolution of TiKV PPT 链接 本次分享唐刘老师首先介绍了 3.0 整个 TiDB 集群的架构变化,主要包括如何通过 TiKV + TiFlash 来实现真正的 HTAP。然后介绍了 3.0 TiKV 的一些性能优化,最后按照 TiKV 架构自底向上,详细介绍了 engine、Raft、transaction、scheduler 等后面需要做的事情。"}, {"url": "https://pingcap.com/weekly/2019-05-13-tidb-weekly/", "title": "Weekly update (May 06 ~ May 12, 2019)", "content": " Weekly update in TiDB Last week, we landed 48 PRs in the TiDB repository.Added Make incremental Analyze support feedback Support pessimistic transactions (experimental feature) Support dumping history statistics Support SET ROLE statements Improved Handle virtual columns when fetching duplicate rows in batchChecker Split unit tests to shorten execution time Make Desc and show columns statements compatible with MySQL Reduce duplicate DigestHash calls Add a variable to control the back off time and disable transaction automatic retry by default Add the SplitIndexRegion syntax Enable hash partition and range column partition by default Fixed Fix wrong column calculation in ColumnPruning for LogicalUnionAll Fix the issue that period_add is not compatible with MySQL Fix the issue that the result of cast(-1 as datetime) is not compatible with MySQL Fix the wrong behavior of PushDownNot in some cases Fix the issue that addition between datetime and the real interval is not compatible with MySQL Fix the issue that addition between datetime and interval is not compatible with MySQL Fix token too long errors when parsing slow query logs Fix the issue of updating the self-schema version and update logs Fix wrong DATE/DATETIME comparison in the BETWEEN function Fix the panic in the regions/meta API Weekly update in TiSpark Last week, we landed 8 PRs in the TiSpark repository.Fixed Fix the index pushdown issue Fix the Table not exist exception in testing Weekly update in TiKV and PD Last week, we landed 45 PRs in the TiKV and PD repositories.Added Introduce tipb_helper Add the tick registry for raftstore Add *AnyValue functions Add cop_codegen to the tikv_alloc whitelist Add RPN comparison functions for Int, Decimal, String, Time, Duration and Json Support CPU profiling sections of code Support the exec summary for the Limit executor Improved Make some small improvements for codec Make tiny refactor for peer_storage in raftstore Skip deleting range check on raft cf Use raw_key in Error and reduce copies Simplify some code using field type conversion Make hot Region balance not affected by balance-region-scheduler-limit Fixed Fix the issue that metrics might be counted multiple times Fix tick_after_destroy in testing Fix the issue that the first safe_point sent to PD does not trigger GC New contributor (Thanks!) tikv: H-ZeX"}, {"url": "https://pingcap.com/blog-cn/what-is-new-in-tidb-3.0.0-rc.1/", "title": "What’s New in TiDB 3.0.0-rc.1", "content": " 2019 年 5 月 10 日,TiDB 3.0.0-rc.1 版本正式推出,该版本对系统稳定性,性能,安全性,易用性等做了较多的改进,接下来逐一介绍。提升系统稳定性 众所周知,数据库的查询计划的稳定性至关重要,此版本采用多种优化手段促进查询计划的稳定性得到进一步提升,如下: 新增 Fast Analyze 功能,使 TiDB 收集统计信息的速度有了数量级的提升,对集群资源的消耗和生产业务的影响比普通 Analyze 方式更小。 新增 Incremental Analyze 功能,对于值单调增的索引能够更加方便和快速地更新其统计信息。 在 CM-Sketch 中新增 TopN 的统计信息,缓解因为 CM-Sketch 哈希冲突导致估算偏大的问题,使代价估算更加准确。 优化 Cost Model,利用和 RowID 列之间的相关性更加精准的估算谓词的选择率,使得索引选择更加稳定和准确。 提升系统性能 TableScan,IndexScan,Limit 算子,进一步提升 SQL 执行性能。 TiKV 采用Iterator Key Bound Option存储结构减少内存分配及拷贝,RocksDB 的 Column Families 共享 block cache 提升 cache命中率等手段大幅提升性能。 TiDB Lightning encode SQL 性能提升 50%,将数据源内容解析成 TiDB 的 types.Datum,减少 encode 过程中多余的解析工作,使得性能得到较大的提升。 增强系统安全性 RBAC(Role-Based Access Control)基于角色的权限访问控制是商业系统中最常见的权限管理技术之一,通过 RBAC 思想可以构建最简单”用户-角色-权限“的访问权限控制模型。RBAC 中用户与角色关联,权限与角色关联,角色与权限之间一般是多对多的关系统,用户通过成为什么样的角色获取该角色所拥有的权限,达到简化权限管理的目的,通过此版本的迭代 RBAC 功能开发完成,欢迎试用。提升产品易用性 新增 SQL 方式查询慢查询,丰富 TiDB 慢查询日志内容,如:Coprocessor 任务数,平均/最长/90% 执行/等待时间,执行/等待时间最长的 TiKV 地址,简化慢查询定位工作,提升产品易用性。 新增系统配置项合法性检查,优化系统监控项等,提升产品易用性。 支持对 TableReader、IndexReader 和 IndexLookupReader 算子进行内存追踪控制,对 Query 内存使用统计更加精确,可以更好地检测、处理对内存消耗较大的语句。 社区贡献 V3.0.0-rc.1 版本的开发过程中,开源社区贡献者给予了我们极大的支持,例如美团的同学负责开发的 SQL Plan Management 特性对于提升产品的易用性有很大的帮助,一点资讯的陈付同学与其他同学一起对 TiKV 线程池进行了重构,提高了性能并降低了延迟,掌门科技的聂殿辉 同学实现 TiKV 大量 UDF 函数帮忙 TiKV 完善 Coprocessor 功能,就不再一一列举。在此对各位贡献者表示由衷的感谢。接下来我们会开展更多的专项开发活动以及一系列面向社区的培训课程,希望能对大家了解如何做分布式数据库有帮助。"}, {"url": "https://pingcap.com/blog-cn/dm-source-code-reading-5/", "title": "DM 源码阅读系列文章(五)Binlog replication 实现", "content": " 本文为 DM 源码阅读系列文章的第五篇。上篇文章 介绍了 dump 和 load 两个数据同步处理单元的设计实现,对核心 interface 实现、数据导入并发模型、数据导入暂停或中断的恢复进行了分析。本篇文章将详细地介绍 DM 核心处理单元 Binlog replication,内容包含 binlog 读取、过滤、路由、转换,以及执行等逻辑。文内涉及到 shard merge 相关逻辑功能,如 column mapping、shard DDL 同步处理,会在 shard merge 篇单独详细讲解,这里就不赘述了。Binlog replication 处理流程 从上图可以大致了解到 Binlog replication 的逻辑处理流程,对应的 逻辑入口代码。 从 relay log 或者 MySQL/MariaDB 读取 binlog events。 对 binlog events 进行处理转换(transformation),这里可以做三类操作: 操作 说明 Filter 根据 库/表同步黑白名单 对库/表进行过滤;根据 binlog event 类型过滤。 Routing 根据 库/表 路由规则 对库/表名进行转换,用于合库合表。 Convert 将 binlog 转换为 job 对象,发送到 executor。 executor 对 job 进行冲突检测,然后根据固定规则分发给对应的 worker 执行。 定期保存 binlog position/gtid 到 checkpoint。 Binlog 读取 Binlog replication 支持两种方式读取 binlog events: 从远程的 MySQL/MariaDB 从 DM-worker 的本地 relay log 两种方式都提供了同样的读取方法,处理核心都是 go-mysql。该库主要提供了两个功能: 注册为 MySQL/MariaDB 的 slave server ,从 MySQL/MariaDB 顺序读取 raw binlog events。 解析 raw binlog events。 更多的处理细节会在下篇关于 relay log 的文章中进行介绍,迫不及待的小伙伴可以先翻阅一下相关代码实现。Binlog 转换 处理程序拿到解析好的 binlog event 后,根据 binlog 的类型来对 binlog 进行分类处理。Binlog replication 主要关心以下类型的 binlog event : 类型 说明 rotate event 消费完一个 binlog 文件,开始消费下一个 binlog 文件,用于更新 checkpoint 的 binlog position。 row event 包含 insert/update/delete DML 数据。 query event 包含 DDL 或者 statement DML 等数据。 xid event 代表一个 transaction 的 commit,经过 go-mysql 的处理后带有对应 transaction 结束位置的 binlog position 和 gtid ,可以用来保存 checkpoint。 Binlog replication 数据处理单元会对每一类 binlog event 进行以下的处理步骤,具体实现的处理顺序可能略有差异,以代码实现为准。过滤 Binlog replication 会从两个维度对 binlog event 来进行过滤: 根据 同步库/表黑白名单,过滤掉对应库/表的所有 binlog event。 根据 binlog event 过滤规则,过滤掉对应库/表指定的 binlog event。 row event 过滤处理 和 query event 过滤处理 的实现在逻辑上面存在一些差异: row event 包含 库名和表名 信息;query event 需要通过 tidb parser 解析 event 里面包含的 query statement 来获取需要的库名,表名以及其他信息。 tidb parser 不是完全 100% 兼容 MySQL 语法,当遇到 parser 不支持的 query statement 时候,解析就会报错,从而无法获取到对应的库名和表名信息。Binlog replication 提供了一些 内置的不支持的 query statement 正则表达式,配合 使用 [schema-pattern: *, table-pattern: *]的 binlog event 过滤规则,来跳过 parser 不支持的 query statement。 query event 里面也会包含 statement format binlog event,此时 Binlog replication 就可以利用 parser 解析出来具体的 statement 类型,对不支持的 statement format binlog event 作出相应的处理: 对于需要同步的表,进行报错处理;不需要同步的表,忽略继续同步。 路由 binlog 过滤完成之后,对于需要同步的表就会根据过滤步骤获得的库名和表名,通过 路由规则 转换得到需要同步到的目标库名和表名,在接下来的转换步骤来使用目标库名和表名来转换出正确的 DML 和 DDL statement。转换 row event 转换处理和 query event 转换处理的实现存在一些差异,这里分开来讲述。row event 转换处理通过三个转换函数生成对应的 statements: generate insert sqls :将 write rows event 转换为 replace into statements。 generate update sqls: safe mode = true,将 update rows event 转换为 delete + replace statements。 safe mode = false,将 update row event 转换为 update statements。 generate delete sqls:将 delete rows event 转换为 delete statements。 query event 转换处理: 因为 TiDB 目前不支持一条 DDL 语句包含多个 DDL 操作,query event 转换处理会首先尝试将 包含多个 DDL 变更操作的单条 DDL 语句 拆分成 只包含一个 DDL 操作的多条 DDL 语句(具体代码实现)。 使用 parser 将 DDL statement 对应的 ast 结构里面的库名和表名替换成对应的目标库名和表名(具体代码实现)。 通过转换处理之后,将不同的 binlog event 包装成不同的 job 发送到 executor 执行: row event -> insert/update/delete job query event -> ddl job xid event -> xid job Job 执行 冲突检测 binlog 顺序同步模型要求按照 binlog 顺序一个一个来同步 binlog event,这样的顺序同步势必不能满足高 QPS 低同步延迟的同步需求,并且不是所有的 binlog 涉及到的操作都存在冲突。Binlog replication 采用冲突检测机制,鉴别出来需要顺序执行的 jobs,在确保这些 jobs 的顺序执行的基础上,最大程度地保持其他 job 的并发执行来满足性能方面的要求。冲突检测流程如下: 遇到 DDL job,等待前面已经分发出来的所有 DML jobs 执行完成后,然后单独执行该 DDL job,执行完成之后保存 checkpoint 信息。 遇到 DML job,会 先检测并且尝试解决冲突。如果检测到冲突(即存在两个 executor 的 worker 的 jobs 都需要与当前的 job 保持顺序执行),会发送一个 flush job 来等待已经分发的所有 DML jobs 执行完成,然后再将 job 分发到对应的 worker,并且记录该分发信息到内存。在没有冲突的情况下,如果不需要与已经分发出去的 job 保持顺序的话,发送 job 到任意 worker 上;如果需要保持顺序的话,那么根据内存储存的历史分发信息,发送 job 到对应的 worker 上。 冲突检测实现比较简单,根据转换步骤获得每条 statement 对应的 primary/unique key 信息,来进行交集检测,如果存在交集那么认定是需要顺序的执行两条 statement,请参考 具体实现代码。执行 job 分发到对应的 worker 后,worker 根据一定的规则来批量执行这些 job,如下: 遇到 DDL 立即执行。 遇到 flush 或者积累的 job 数量超过 配置的 batch 数量 立即执行。 没有新的 job 分发进来,清空当前已经积累的 jobs 或者 sleep 10 ms。 根据上面三个规则可以很快地将已经分发的 jobs 应用到下游 TiDB。小结 本篇文章详细地介绍 DM 核心处理单元 Binlog replication,内容包含 binlog 读取、过滤、路由、转换,以及执行等逻辑。下一篇我们会对 relay log 数据处理单元的设计进行详细的讲解。"}, {"url": "https://pingcap.com/blog/tidb-binlog-tutorial/", "title": "TiDB-Binlog Tutorial", "content": " This tutorial will start with a very simple TiDB-Binlog deployment with a single node of each component (Placement Driver, TiKV Server, TiDB Server, Pump, and Drainer), set up to push data into a MariaDB Server instance.This tutorial is targeted toward users who have some familiarity with the TiDB Architecture, who may have already set up a TiDB cluster (though that is not mandatory), and who wants to gain hands-on familiarity with the features and functionality of TiDB-Binlog. This tutorial is a good way to “kick the tires” of TiDB-Binlog and to familiarize yourself with the concepts of its architecture. Warning:The methodology used to deploy TiDB in this tutorial should not be used to deploy TiDB in a production or development setting. This tutorial assumes you’re using a modern Linux distribution on x86-64. I’ll use a minimal CentOS 7 installation running in VMware for the examples. It’ll be easiest if you start from a clean install, so that you aren’t impacted by quirks of your existing environment. If you don’t want to use local virtualization, you can easily and inexpensively start a CentOS 7 VM in your favorite cloud provider.Overview TiDB-Binlog is a solution to collect binary log data from TiDB server and provide real-time data backup and replication. It pushes incremental data updates made to a TiDB Server cluster into any of various downstream platforms.You can use TiDB-Binlog for incremental backups, to replicate data from one TiDB cluster to another, or to send TiDB updates through Kafka to a downstream platform of your choice.TiDB-Binlog is particularly useful when you migrate data from MySQL or MariaDB to TiDB, in which case you may use the TiDB DM (Data Migration) platform to get data from a MySQL/MariaDB cluster into TiDB, and then use TiDB-Binlog to keep a separate, downstream MySQL/MariaDB instance/cluster in sync with your TiDB cluster. TiDB-Binlog enables application traffic to TiDB to be pushed to a downstream MySQL or MariaDB instance/cluster, which reduces the risk of a migration to TiDB because you can easily revert the application to MySQL or MariaDB without downtime or data loss.See TiDB-Binlog Cluster User Guide for more information.Architecture TiDB-Binlog comprises two components: the Pump and the Drainer. Several Pump nodes make up a Pump cluster. Each Pump node connects to TiDB Server instances and receives updates made to each of the TiDB Server instances in a cluster. A Drainer connects to the Pump cluster and transforms updates into the correct format for a particular downstream destination, be it Kafka or another TiDB Cluster or a MySQL/MariaDB server.The clustered architecture of the Pump component ensures that updates won’t be lost as new TiDB Server instances join or leave the TiDB Cluster or Pump nodes join or leave the Pump cluster.Installation We’re using MariaDB Server in this case instead of MySQL Server because RHEL/CentOS 7 includes MariaDB Server in their default package repositories. We’ll need the client as well as the server for later, so let’s install them now:sudo yum install -y mariadb-server Even if you’ve already started a TiDB cluster, it will be easier to follow along with this tutorial if you set up a new, very simple cluster. We will install from a tarball, using a simplified form of the Local Deployment guide. You may also wish to consult Testing Deployment from Binary Tarball for best practices establishing a real testing deployment that goes beyond the scope of this tutorial.curl -L http://download.pingcap.org/tidb-latest-linux-amd64.tar.gz | tar xzf - cd tidb-latest-linux-amd64 Expect this output:[kolbe@localhost ~]$ curl -LO http://download.pingcap.org/tidb-latest-linux-amd64.tar.gz | tar xzf - % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 368M 100 368M 0 0 8394k 0 0:00:44 0:00:44 --:--:-- 11.1M [kolbe@localhost ~]$ cd tidb-latest-linux-amd64 [kolbe@localhost tidb-latest-linux-amd64]$ Configuration Now we’ll start a very simple TiDB cluster, with a single instance each of pd-server, tikv-server, and tidb-server.First, let’s populate the config files we’ll use:printf > pd.toml %sn 'log-file="pd.log"' 'data-dir="pd.data"' printf > tikv.toml %sn 'log-file="tikv.log"' '[storage]' 'data-dir="tikv.data"' '[pd]' 'endpoints=["127.0.0.1:2379"]' '[rocksdb]' max-open-files=1024 '[raftdb]' max-open-files=1024 printf > pump.toml %sn 'log-file="pump.log"' 'data-dir="pump.data"' 'addr="127.0.0.1:8250"' 'advertise-addr="127.0.0.1:8250"' 'pd-urls="http://127.0.0.1:2379"' printf > tidb.toml %sn 'store="tikv"' 'path="127.0.0.1:2379"' '[log.file]' 'filename="tidb.log"' '[binlog]' 'enable=true' printf > drainer.toml %sn 'log-file="drainer.log"' '[syncer]' 'db-type="mysql"' '[syncer.to]' 'host="127.0.0.1"' 'user="root"' 'password=""' 'port=3306' This will allow you to see the contents of the config files:for f in *.toml; do echo "$f:"; cat "$f"; echo; done Expect this output:drainer.toml: log-file="drainer.log" [syncer] db-type="mysql" [syncer.to] host="127.0.0.1" user="root" password="" port=3306 pd.toml: log-file="pd.log" data-dir="pd.data" pump.toml: log-file="pump.log" data-dir="pump.data" addr="127.0.0.1:8250" advertise-addr="127.0.0.1:8250" pd-urls="http://127.0.0.1:2379" tidb.toml: store="tikv" path="127.0.0.1:2379" [log.file] filename="tidb.log" [binlog] enable=true tikv.toml: log-file="tikv.log" [storage] data-dir="tikv.data" [pd] endpoints=["127.0.0.1:2379"] [rocksdb] max-open-files=1024 [raftdb] max-open-files=1024 Bootstrapping Now we can start each component. This is best done in a specific order, first bringing up the PD (Placement Driver), then TiKV Server (the backend key/value store used by TiDB Platform), then Pump (because TiDB must connect to the Pump service to send the binary log), and finally TiDB Server (the frontend that speaks the MySQL protocol to your applications).Start all the services:./bin/pd-server --config=pd.toml &>pd.out & ./bin/tikv-server --config=tikv.toml &>tikv.out & ./bin/pump --config=pump.toml &>pump.out & sleep 3 ./bin/tidb-server --config=tidb.toml &>tidb.out & Expect this output:[kolbe@localhost tidb-latest-linux-amd64]$ ./bin/pd-server --config=pd.toml &>pd.out & [1] 20935 [kolbe@localhost tidb-latest-linux-amd64]$ ./bin/tikv-server --config=tikv.toml &>tikv.out & [2] 20944 [kolbe@localhost tidb-latest-linux-amd64]$ ./bin/pump --config=pump.toml &>pump.out & [3] 21050 [kolbe@localhost tidb-latest-linux-amd64]$ sleep 3 [kolbe@localhost tidb-latest-linux-amd64]$ ./bin/tidb-server --config=tidb.toml &>tidb.out & [4] 21058 And if you execute jobs, you should see the list of running daemons:[kolbe@localhost tidb-latest-linux-amd64]$ jobs [1] Running ./bin/pd-server --config=pd.toml &>pd.out & [2] Running ./bin/tikv-server --config=tikv.toml &>tikv.out & [3]- Running ./bin/pump --config=pump.toml &>pump.out & [4]+ Running ./bin/tidb-server --config=tidb.toml &>tidb.out & If one of the services has failed to start (if you see “Exit 1” instead of “Running”, for example), try to restart that individual service.Download TiDB Subscribe to Blog Connecting You should have all 4 components of our TiDB Cluster running now, and you can now …"}, {"url": "https://pingcap.com/blog/pingcap-open-sources-tidb-binlog-to-reduce-migration-cost/", "title": "PingCAP Open-Sources TiDB-Binlog to Reduce Migration Cost", "content": " Today, our team at PingCAP is excited to announce that we are open-sourcing our own binlog implementation, TiDB-Binlog. TiDB-Binlog is a tool we’ve been developing in-house (until now) to collect binary log data from TiDB server and provide real-time data backup and replication.It’s similar in functionality to MySQL master-slave replication, but the main difference is that because TiDB is a distributed database, the binlog generated by each TiDB instance needs to be merged and sorted according to the time of the transaction commit before being consumed downstream.Many of our users have been using TiDB-Binlog in-production for incremental backups, to replicate data from one TiDB cluster to another (often across multiple data centers or availability zones), or to send TiDB updates through Kafka to any given downstream platform. It’s particularly useful if you are a MySQL or MariaDB user thinking about trying out TiDB, but would like to minimize the risk of migration and testing. TiDB-Binlog enables application traffic to TiDB to be pushed to a downstream MySQL or MariaDB instance/cluster. This ensures that even if the migration to TiDB encounters problems, you can easily revert the application back to MySQL or MariaDB. TiDB-Binlog Architecture (For more detailed information on the architectural design and implementation of TiDB-Binlog, read this deep-dive blog post.)Why Open-Source TiDB-Binlog? TiDB-Binlog has become a popular tool among our users, as TiDB itself continues to gain popularity. Whether it’s replicating data between two data centers to provide disaster recovery, or evaluating TiDB in a more risk-free manner, TiDB-Binlog is an essential part of that experience. In order to deliver better testing and in-production experience to more users, we believe open-sourcing it is the best approach. As a company that lives and breathes the open-source ethos, we firmly believe that more openness in general is always better than less.To help you “kick the tires” now, we’ve put together this tutorial to help you deploy and get a feel for how TiDB-Binlog operates.We look forward to your issues, PRs, and contributions. As always, we appreciate everything our open-source community has done to make TiDB what it is today."}, {"url": "https://pingcap.com/blog-cn/tidb-binlog-open-source/", "title": "TiDB Binlog 组件正式开源", "content": " TiDB Binlog 组件用于收集 TiDB 的 binlog,并准实时同步给下游,如:TiDB/MySQL等。该组件在功能上类似于 MySQL 的主从复制,会收集各个 TiDB 实例产生的 binlog,并按事务提交的时间排序,全局有序的将数据同步至下游。利用 TiDB Binlog 可以实现数据准实时同步到其他数据库,以及 TiDB 数据准实时的备份与恢复。TiDB Binlog 作为 TiDB 的核心组件之一,已经在上百家用户的生产环境中长时间稳定运行。为方便用户和开发者更加深入理解和使用 TiDB Binlog 组件,以及基于 TiDB Binlog 组件做二次开发用于更多的业务场景, 我们决定今天正式开源 TiDB Binlog 组件。TiDB Binlog 适用的功能场景 准实时数据同步:同步 TiDB 数据到其他数据库或消息队列(如 TiDB/MySQL/MariaDB/Kafka)。 准实时备份和恢复:增量备份 TiDB 集群数据到外部系统,利用备份的数据在系统故障或者其他场景时可将数据恢复到任意时间点。 TiDB Binlog 架构 TiDB Binlog 核心特性 支持类似 MySQL ROW 复制模式。 准实时并按事务提交的时间顺序将数据同步至下游。 分布式架构设计,支持水平弹性扩容和服务高可用。 数据高可靠,系统实时将数据持久化到本地磁盘。 支持多种输出方式,如下: 文件:系统准实时将 binlog 写入文件系统作为增量备份,利用此增量备份文件可将数据恢复到任意时间点。 消息队列:按照 binlog slave protocol 输出到 Kafka。 下游目标数据库:TiDB/MySQL/MariaDB。 TiDB Binlog 代码及文档资源 TiDB Binlog 源代码 TiDB Binlog 使用手册 深入理解 TiDB Binlog 组件实现原理 定制输出方式或者输出到其他下游存储系统 欢迎大家一起参与 TiDB Binlog 的设计、研发、测试共同推进 TiDB Binlog 走向更成熟,更稳定。近期我们将发布 TiDB Binlog 源码阅读指南,敬请期待。"}, {"url": "https://pingcap.com/weekly/2019-05-06-tidb-weekly/", "title": "Weekly update (April 29 ~ May 05, 2019)", "content": " Weekly update in TiDB Last week, we landed 41 PRs in the TiDB repository.Added Make the Performance.*, OOMAction and MemQuotaQuery config items support hot-reloading Make SQL bind work when executing select and explain select statements Add the memory table to show Region information Support CMSketch with TopN Improved Change the offset type of chunk to int64 Make tidb_disable_txn_auto_retry disable retry for explicit transactions Refine point get failpoint Check time zone when encoding timestamp datum Check whether the TiKV instance is stopped when recreating connection to it Add transaction commit time to slow log Ignore the overflow error when constructing inner keys Support pre-splitting Region for the partitioned table Enhance Index Join Fixed Fix the issue that TiDB handles leap year incorrectly in some cases Fix incorrect Internal display in the admin show slow output Fix data race of parser in the global handle Fix the issue that timestampadd is not compatible with MySQL Fix wrong judgment conditions when judging point get Fix the issue that JSON_CONTAINS is not compatible with MySQL Fix the wrong privilege check for update in some cases Fix the wrong error message when granting to a non-existent user Weekly update in TiSpark Last week, we landed 4 PRs in the TiSpark repository.Fixed Fix the issue that tempView might be unresolved when applying a timestamp to the plan Fix the issue that Catalog is not closed when Session closes Weekly update in TiKV and PD Last week, we landed 21 PRs in the TiKV and PD repositories.Added Share block cache among column families Add the AVG batch aggregate function Add the LogicalAnd RPN function Enable using Region storage by default in PD config Add the LogicalOr RPN function Add the LTReal, LEReal, GTReal, GEReal, NEReal, EQReal RPN functions Add more fuzz tests Add an execution summary framework for non-batch executors Improved Simplify the usage of RpnExpressionBuilder Enhance the error report for fuzzing Rename and move ExecSummary to support non-batch executors Simplify the exec summary Support the exec summary for normal table/index scan executor Tune SstFileWriter options to reduce CPU usage in Importer Fixed Register mailbox before sending in tests Fix read pool names Update Raft to 0.4.2 New contributors (Thanks!) tidb: gunjanpatidar ian-p-cooke mantuliu tikv: yeshengm tidb-operator: jlerche "}, {"url": "https://pingcap.com/blog/try-to-fix-two-linux-kernel-bugs-while-testing-tidb-operator-in-k8s/", "title": "Try to Fix Two Linux Kernel Bugs While Testing TiDB Operator in K8s", "content": " Author: Wenbo Zhang (Linux Kernel Engineer of the EE team at PingCAP)Kubernetes (K8s) is an open-source container orchestration system that automates application deployment, scaling, and management. It’s the operating system of the cloud-native world. Any defects in K8s or the operation system might put the application in the upper layer at risk.As the EE (Efficiency Engineering) team at PingCAP, we optimize our office infrastructure and improve office automation and teamwork effectiveness. When we tested TiDB Operator (which creates and manages TiDB clusters) in K8s, we found two Linux kernel bugs. These bugs have plagued our PingCAP K8s environment for quite a while and aren’t thoroughly fixed in the K8s community as a whole.After extensive investigation and diagnosis, we’ve identified ways to handle these bugs. In this post, I’ll share with you how we tackled these problems. However, as useful as these solutions are, we consider them workarounds, and we believe that more elegant solutions are possible. The goal of this post is to inspire the K8s community, RHEL, and CentOS so that they can help fix these bugs thoroughly in the near future.Bug #1: Unstable kmem accounting Keyword: SLUB: Unable to allocate memory on node -1Related issues in the community: https://github.com/kubernetes/kubernetes/issues/61937 https://github.com/opencontainers/runc/issues/1725 https://support.mesosphere.com/s/article/Critical-Issue-KMEM-MSPH-2018-0006 Issue description When we tested TiKV’s online transaction processing (OLTP) performance in K8s, I/O performance occasionally jittered. However, following items appeared normal: TiKV and RocksDB log files; RocksDB is TiKV’s underlying storage engine CPU (no bottleneck) Memory and disk statistics from the load information The only negative indicator was the message, “SLUB: Unable to allocate memory on node -1” in the output of the dmesg command.Issue analysis We used funcslower in perf-tools to trace kernel functions that were executed slowly and adjusted the threshold value of the hung_task_timeout_secs kernel parameter. When we performed the write operations in TiKV, we found the following kernel path information:Based on the information above, we could see that the I/O jitter was related to the file system executing writepage. At the same time, before and after the performance jitter was captured, the dmesg output contained a large amount of information about “SLUB: Unable to allocate memory on node -1”. This message occurred even when the node had sufficient memory.We analyzed the call stack information output from hung_task and the kernel code. We found that the kernel tried to allocate the bio_vec object via kmem_cache_alloc when executing the bvec_alloc function to allocate the bio_vec object.If this operation failed, the kernel fell back to allocate the bio_vec object from the mempool. However, inside the mempool, the system first tried to execute the pool->alloc callback for allocation. If this allocation failed, the kernel set the process to an uninterruptible state and put the process in a wait queue. After other processes returned memory resources to the mempool or the timer timed out (after 5 seconds), the process scheduler invoked the process for retry. The wait time of the wait queue was consistent with the jitter delay of our application monitoring.When we created the Docker container, we didn’t set memory.kmem.limit_in_bytes. But why was kmem insufficient? To determine whether memory.kmem.limit_in_bytes was set, we went to the cgroup memory controller to check the container’s kmem information. The kmem statistics were enabled, and the kemem limit was set to a very large value.Because we knew that the kmem accounting was unstable in the RHEL 3.10 kernel, we suspected that a kernel bug caused the SLUB allocation failure. We searched for kernel patch information, and found that it was a kernel bug, and that it had been fixed in Linux kernel version 4.x: slub: make dead caches discard free slabs immediately. There was also a namespace leak issue associated with kmem accounting: mm: memcontrol: fix cgroup creation failure after many small jobs.So how was kmem accounting enabled? We used the opensnoop tool in bcc to monitor the kmem configuration file and captured runc as the file modifier. From the K8s code, we found that kmem accounting was enabled by default in the K8s-dependent runc project.Solution Based on our issue analysis, we can fix the bug in either of the following ways: Upgrade the existing kernel to a later version. Disable the kmem accounting feature when starting the container. Now runc includes the conditional compilation option, and you can disable kmem accounting via Build Tags. After we disabled this feature, our test result showed that the jitter disappeared, and so did the namespace leak and SLUB allocation failure problems.Operation steps We need to disable the kmem accounting feature on both kubelet and Docker. Recompile kubelet according to the corresponding kubelet version: For kubelet v1.14 or later, add Build Tags when you compile kubelet to disable kmem accounting: $ make BUILDTAGS="nokmem" For kubelet v1.13 or earlier, we cannot add Build Tags when compiling kubelet. Instead, manually replace the two functions that enable kmem accounting with the following code: func EnableKernelMemoryAccounting(path string) error { return nil }func setKernelMemory(path string, kernelMemoryLimit int64) error { return nil } Then recompile kubelet. Upgrade docker-ce to v18.09.1 or later. In these versions, kmem accounting of runc is disabled in Docker. Reboot the system. To verify whether the kmem accounting feature of all the containers is disabled in the newly created pod, run the following command:$ cat /sys/fs/cgroup/memory/kubepods/burstable/pod<pod-uid>/<container-id>/memory.kmem.slabinfo If the returned result is as follows, it means that the kmem accounting feature is disabled successfully:> cat: memory.kmem.slabinfo: Input/output error Bug #2: Network device reference count leak Keyword: kernel:unregister_netdevice: waiting for eth0 to become free. Usage count = 1Related issues in the community: https://github.com/kubernetes/kubernetes/issues/64743 https://github.com/projectcalico/calico/issues/1109 https://github.com/moby/moby/issues/5618 Download TiDB Subscribe to Blog Issue description After the K8s platform ran for a time, the following message was displayed: “Kernel:unregister_netdevice: waiting for eth0 to become free. Usage count = 1”. In addition, multiple processes would be in an uninterruptible state. The only workaround for this problem was to restart the server.Issue analysis We used the crash tool to analyze vmcore. We found that the netdev_wait_allrefs function blocked the kernel thread, and that the function was in an infinite loop waiting for dev->refcnt to drop to 0. Because the pod had been released, we suspected that this issue was due to a reference count leak. After analyzing K8s issues, we found that the kernel was the crux of the issue, but there was no simple, stable, and reliable method to reproduce the issue. Also, this issue still occurred in the later community version of the kernel.To avoid restarting the server every time this issue occurred, we developed a kernel module. When the net_device reference count was found to leak, this kernel module would be removed after the reference count was dropped to 0 (to avoid accidentally deleting other NICs that weren’t referenced by the leak). Besides, to avoid manual cleanup, we wrote a monitoring script that periodically automated this operation.However, this solution had the following defects: A delay existed between the starting time of the reference count leak and our discovering the leak via the monitoring system. In this delay, other problems might appear in the K8s system. It was …"}, {"url": "https://pingcap.com/blog-cn/golang-failpoint/", "title": "Golang Failpoint 的设计与实现", "content": " 对于一个大型复杂的系统来说,通常包含多个模块或多个组件构成,模拟各个子系统的故障是测试中必不可少的环节,并且这些故障模拟必须做到无侵入地集成到自动化测试系统中,通过在自动化测试中自动激活这些故障点来模拟故障,并观测最终结果是否符合预期结果来判断系统的正确性和稳定性。如果在一个分布式系统中需要专门请一位同事来插拔网线来模拟网络异常,一个存储系统中需要通过破坏硬盘来模拟磁盘损坏,昂贵的测试成本会让测试成为一场灾难,并且难以模拟一些需要精细化控制的的测试。所以我们需要一些自动化的方式来进行确定性的故障测试。Failpoint 项目 就是为此而生,它是 FreeBSD failpoints 的 Golang 实现,允许在代码中注入错误或异常行为, 并由环境变量或代码动态激活来触发这些异常行为。Failpoint 能用于各种复杂系统中模拟错误处理来提高系统的容错性、正确性和稳定性,比如: 微服务中某个服务出现随机延迟、某个服务不可用。 存储系统磁盘 I/O 延迟增加、I/O 吞吐量过低、落盘时间长。 调度系统中出现热点,某个调度指令失败。 充值系统中模拟第三方重复请求充值成功回调接口。 游戏开发中模拟玩家网络不稳定、掉帧、延迟过大等,以及各种异常输入(外挂请求)情况下系统是否正确工作。 …… 为什么要重复造轮子? etcd 团队在 2016 年开发了 gofail 极大地简化了错误注入,为 Golang 生态做出了巨大贡献。我们在 2018 年已经引入了 gofail 进行错误注入测试,但是我们在使用中发现了一些功能性以及便利性的问题,所以我们决定造一个更好的「轮子」。如何使用 gofail 使用注释在程序中注入一个 failpoint:// gofail: var FailIfImportedChunk int // if merger, ok := scp.merger.(*ChunkCheckpointMerger); ok && merger.Checksum.SumKVS() >= uint64(FailIfImportedChunk) { // rc.checkpointsWg.Done() // rc.checkpointsWg.Wait() // panic("forcing failure due to FailIfImportedChunk") // } // goto RETURN1 // gofail: RETURN1: // gofail: var FailIfStatusBecomes int // if merger, ok := scp.merger.(*StatusCheckpointMerger); ok && merger.EngineID >= 0 && int(merger.Status) == FailIfStatusBecomes { // rc.checkpointsWg.Done() // rc.checkpointsWg.Wait() // panic("forcing failure due to FailIfStatusBecomes") // } // goto RETURN2 // gofail: RETURN2: 使用 gofail enable 命令将注释转换为代码:if vFailIfImportedChunk, __fpErr := __fp_FailIfImportedChunk.Acquire(); __fpErr == nil { defer __fp_FailIfImportedChunk.Release(); FailIfImportedChunk, __fpTypeOK := vFailIfImportedChunk.(int); if !__fpTypeOK { goto __badTypeFailIfImportedChunk} if merger, ok := scp.merger.(*ChunkCheckpointMerger); ok && merger.Checksum.SumKVS() >= uint64(FailIfImportedChunk) { rc.checkpointsWg.Done() rc.checkpointsWg.Wait() panic("forcing failure due to FailIfImportedChunk") } goto RETURN1; __badTypeFailIfImportedChunk: __fp_FailIfImportedChunk.BadType(vFailIfImportedChunk, "int"); }; /* gofail-label */ RETURN1: if vFailIfStatusBecomes, __fpErr := __fp_FailIfStatusBecomes.Acquire(); __fpErr == nil { defer __fp_FailIfStatusBecomes.Release(); FailIfStatusBecomes, __fpTypeOK := vFailIfStatusBecomes.(int); if !__fpTypeOK { goto __badTypeFailIfStatusBecomes} if merger, ok := scp.merger.(*StatusCheckpointMerger); ok && merger.EngineID >= 0 && int(merger.Status) == FailIfStatusBecomes { rc.checkpointsWg.Done() rc.checkpointsWg.Wait() panic("forcing failure due to FailIfStatusBecomes") } goto RETURN2; __badTypeFailIfStatusBecomes: __fp_FailIfStatusBecomes.BadType(vFailIfStatusBecomes, "int"); }; /* gofail-label */ RETURN2: gofail 使用中遇到的问题 使用注释的方式在代码中注入 failpoint,代码容易出错,并且没有编译器检测。 只能全局生效,大型项目为了缩短自动化测试的时间会引入并行测试,不同并行任务之间会存在干扰。 需要写一些 hack 代码来避免一些不必要的错误日志,比如如上代码,必须要写 // goto RETURN2 和 // gofail: RETURN2:,并且中间必须添加一个空行,至于原因可以看 generated code 逻辑。 我们要设计一个什么样子的 failpoint? 理想的 failpoint 实现应该是什么样子? 理想中的 failpoint 应该是使用代码定义并且对业务逻辑无侵入,如果在一个支持宏的语言中 (比如 Rust),我们可以定义一个 fail_point 宏来定义 failpoint:fail_point!("transport_on_send_store", |sid| if let Some(sid) = sid { let sid: u64 = sid.parse().unwrap(); if sid == store_id { self.raft_client.wl().addrs.remove(&store_id); } }) 但是我们遇到了一些问题: Golang 并不支持 macro 语言特性。 Golang 不支持编译器插件。 Golang tags 也不能提供一个比较优雅的实现 (go build --tag="enable-failpoint-a")。 Failpoint 设计准则 使用 Golang 代码定义 failpoint,而不是注释或其他形式。 Failpoint 代码不应该有任何额外开销: 不能影响正常功能逻辑,不能对功能代码有任何侵入。 注入 failpoint 代码之后不能导致性能回退。 Failpoint 代码最终不能出现在最终发行的二进制文件中。 Failpoint 代码必须是易读、易写并且能引入编译器检测。 最终生成的代码必须具有可读性。 生成代码中,功能逻辑代码的行号不能发生变化(便于调试)。 支持并行测试,可以通过 context.Context 控制一个某个具体的 failpoint 是否激活。 Golang 如何实现一个类似 failpoint 宏? 宏的本质是什么?如果追本溯源,发现其实可以通过 AST 重写在 Golang 中实现满足以上条件的 failpoint,原理如下图所示:对于任何一个 Golang 代码的源文件,可以通过解析出这个文件的语法树,遍历整个语法树,找出所有 failpoint 注入点,然后对语法树重写,转换成想要的逻辑。相关概念 Failpoint Failpoint 是一个代码片段,并且仅在对应的 failpoint name 激活的情况下才会执行,如果通过 failpoint.Disable("failpoint-name-for-demo") 禁用后,那么对应的的 failpoint 永远不会触发。所有 failpoint 代码片段不会编译到最终的二进制文件中,比如我们模拟文件系统权限控制:func saveTo(path string) error { failpoint.Inject("mock-permission-deny", func() error { // It's OK to access outer scope variable return fmt.Errorf("mock permission deny: %s", path) }) } Marker 函数 AST 重写阶段标记需要被重写的部分,主要有以下功能: 提示 Rewriter 重写为一个相等的 IF 语句。 标记函数的参数是重写过程中需要用到的参数。 标记函数是一个空函数,编译过程会被 inline,进一步被消除。 标记函数中注入的 failpoint 是一个闭包,如果闭包访问外部作用域变量,闭包语法允许捕获外部作用域变量,则不会出现编译错误,同时转换后的的代码是一个 IF 语句,IF 语句访问外部作用域变量不会产生任何问题,所以闭包捕获只是为了语法合法,最终不会有任何额外开销。 简单、易读、易写。 引入编译器检测,如果 Marker 函数的参数不正确,程序不能通过编译的,进而保证转换后的代码正确性。 目前支持的 Marker 函数列表: func Inject(fpname string, fpblock func(val Value)) {} func InjectContext(fpname string, ctx context.Context, fpblock func(val Value)) {} func Break(label ...string) {} func Goto(label string) {} func Continue(label ...string) {} func Return(results ...interface{}) {} func Fallthrough() {} func Return(results ...interface{}) {} func Label(label string) {} 如何在你的程序中使用 failpoint 进行注入? 最简单的方式是使用 failpoint.Inject 在调用的地方注入一个 failpoint,最终 failpoint.Inject 调用会重写为一个 IF 语句,其中 mock-io-error 用来判断是否触发,failpoint-closure 中的逻辑会在触发后执行。 比如我们在一个读取文件的函数中注入一个 I/O 错误:failpoint.Inject("mock-io-error", func(val failpoint.Value) error { return fmt.Errorf("mock error: %v", val.(string)) }) 最终转换后的代码如下:if ok, val := failpoint.Eval(_curpkg_("mock-io-error")); ok { return fmt.Errorf("mock error: %v", val.(string)) } 通过 failpoint.Enable("mock-io-error", "return("disk error")") 激活程序中的 failpoint,如果需要给 failpoint.Value 赋一个自定义的值,则需要传入一个 failpoint expression,比如这里 return("disk error"),更多语法可以参考 failpoint 语法。闭包可以为 nil,比如 failpoint.Enable("mock-delay", "sleep(1000)"),目的是在注入点休眠一秒,不需要执行额外的逻辑。failpoint.Inject("mock-delay", nil) failpoint.Inject("mock-delay", func(){}) 最终会产生以下代码:failpoint.Eval(_curpkg_("mock-delay")) failpoint.Eval(_curpkg_("mock-delay")) 如果我们只想在 failpoint 中执行一个 panic,不需要接收 failpoint.Value,则我们可以在闭包的参数中忽略这个值。例如:failpoint.Inject("mock-panic", func(_ failpoint.Value) error { panic("mock panic") }) // OR failpoint.Inject("mock-panic", func() error { panic("mock panic") }) 最佳实践是以下这样:failpoint.Enable("mock-panic", "panic") failpoint.Inject("mock-panic", nil) // GENERATED CODE failpoint.Eval(_curpkg_("mock-panic")) 为了可以在并行测试中防止不同的测试任务之间的干扰,可以在 context.Context 中包含一个回调函数,用于精细化控制 failpoint 的激活与关闭:failpoint.InjectContext(ctx, "failpoint-name", func(val failpoint.Value) { fmt.Println("unit-test", val) }) 转换后的代码:if ok, val := failpoint.EvalContext(ctx, _curpkg_("failpoint-name")); ok { fmt.Println("unit-test", val) } 使用 failpoint.WithHook 的示例:func (s *dmlSuite) TestCRUDParallel() { sctx := failpoint.WithHook(context.Backgroud(), func(ctx context.Context, fpname string) bool { return ctx.Value(fpname) != nil // Determine by ctx key }) insertFailpoints = map[string]struct{} { "insert-record-fp": {}, "insert-index-fp": {}, "on-duplicate-fp": {}, } ictx := failpoint.WithHook(context.Backgroud(), func(ctx context.Context, fpname string) bool { _, found := insertFailpoints[fpname] // Only enables some failpoints. return found }) deleteFailpoints = map[string]struct{} { "tikv-is-busy-fp": {}, "fetch-tso-timeout": {}, } dctx := failpoint.WithHook(context.Backgroud(), func(ctx context.Context, fpname string) bool { _, found := deleteFailpoints[fpname] // Only disables failpoints. return !found }) // other DML parallel test cases. s.RunParallel(buildSelectTests(sctx)) s.RunParallel(buildInsertTests(ictx)) s.RunParallel(buildDeleteTests(dctx)) } 如果我们在循环中使用 failpoint,可能我们会使用到其他的 Marker 函数:failpoint.Label("outer") for i := 0; i < 100; i++ { inner: for j := 0; j < 1000; j++ { switch rand.Intn(j) + i { case j / 5: failpoint.Break() case j / 7: failpoint.Continue("outer") case j / …"}, {"url": "https://pingcap.com/blog/pingcapers-at-the-first-rustcon-asia/", "title": "PingCAPers at the First RustCon Asia!", "content": "Sunshine, blue sky, with sprinkles of rain - such diverse weather these days echoed the lively atmosphere at Hyatt Regency Beijing, where the Rust Conference was held for the first time in Asia. Co-organized by PingCAP and Cryptape, this conference lasted for 4 days, involving 2 days of talks and 2 days of workshops. Speakers came from all over the world, representing some of the top influencers in the Rust community, especially in Asia. Nearly 300 Rust developers and fans attended this international event. Some of them even traveled all the way from Singapore, Germany, Australia, and Canada. For many attendees, first time encounters blossomed into new friendships. Signature Board Souvenir and material offered at RustCon Asia PingCAP has been an active member in the Rust community, and one of the first production users of Rust in building TiKV, a CNCF project. For this conference, as part of being a co-organizer, we also contributed several topics in both keynote sessions and workshops. Siddon Tang, Chief Engineer and TiKV team lead at PingCAP, at RustCon Asia In the opening remarks, Siddon Tang, our chief engineer, shared the birth of TiKV and its “marriage” with Rust, which has been proven to be a win-win choice for both TiKV project and the Rust community. According to Siddon, Rust is simply more performant, safe, and reliable. Thanks to these qualities, TiKV 3.0 GA is around the corner as TiKV evolves fast into maturity. TiKV has been adopted in-production in several hundred companies. On the other hand, we are giving back to the Rust community along the way. Since TiKV was born, key components such as grpc-rs, raft-rs and rust-Prometheus have been maintained as reusable open source packages, thereby enriching the Rust ecosystem in terms of tooling and libraries. PingCAP has been actively promoting Rust by organizing or participating in Rust meetups and conferences, and also developing beginners’ online courses on Rust and distributed systems. Nick Cameron is making an opening remark at RustCon Asia Normally we use a language to implement our designs, but how often do we get to think about the design codes and philosophies behind a language? Well, lucky for the attendees in RustCon Asia, we had Nick Cameron, a Rust core team member and now a PingCAP senior engineer, give an opening speech entitled “Making Rust Delightful”. It was indeed a delightful speech! Nick shared in detail some key philosophies of Rust, which could be highlighted by ergonomics and empathy. According to Nick, ergonomics with minimum friction has been a goal for Rust core teams, especially since the release of Rust’s language ergonomics initiative in 2017 aiming to improve productivity and bring down the learning curve. This initiative ultimately comes down to empathy for users of the language. The inspiring highlights in his talk were some principles and heuristics to be followed during the design and improvement process of Rust, but you may find them applicable to other programming work as well, such as “The right choice should be the easy choice”, “Important should be implicit”, etc. Rust developers need to figure out what’s important before starting their work. Qu Peng, TiKV team engineer, at RustCon Asia Download TiDB Subscribe to Blog While talks from Siddon and Nick were delivered from high-level perspectives on development and designs, our third talk, “Futures in TiKV” from Qu Peng, a core engineer on the TiKV team, focused on some hardcore implementation details of Rust in TiKV. (Not the development or roadmap of our product in the future, but the futures crate in Rust provides common asynchronous primitives for Rust). With a few code samples, Qu Peng illustrated how TiKV utilizes futures in multiple critical contexts like basic streaming and remote procedure calls (RPCs), how TiKV collects futures in a stream to reduce context switch cost, and how TiKV implements an executor-task style batch system using future and stream traits.On Day 2, Nick closed RustCon Asia with results from a survey on the Rust community. He listed many figures which vividly showed how the Rust community is growing worldwide. Particularly, the Chinese community is becoming a key contributor in Asia. According to Nick, the Rust 2018 edition has offered stability without stagnation. Besides, “Rust is a language that can really get your work done,” said Nick. In 2019, what we can expect is a more mature Rust. As the community grows in popularity, governance has started to become an important issue. Objectively, we should admit that there are still some long-standing requests. However, as Rust advances, we can have confidence and optimism about the continuous improvements to resolve these requests. Nick Cameron making a closing talk at RustCon Asia The two-day talks received many kudos from the attendees. Beside PingCAP, other speakers from our co-organizer, Cryptape, Internet giants like Baidu and Alibaba, and emerging companies like Bilibili, a popular video sharing website, and Zhihu, a Chinese version of Quora delivered equally brilliant talks covering a wide range of Rust applications, including security, IoT embedded systems, blockchain, and image processing. The engaging discussions still went on after the conference. If you missed the talks, go to the Rust Channel on Youtube for the available videos.Meanwhile, the Rust workshops on the following days also provided a great opportunity to get your hands dirty with Rust. To continue the topic Make Rust Delightful, Nick provided a half-day workshop named Think in Rust. We also had TiKV core engineers, Shirley Wu and Wish Shi there to show attendees how to integrate rust-Prometheus into your application to continuously collect metrics for monitoring purposes. Wish Shi at workshop Nick Cameron is helping the “students” The first RustCon Asia was absolutely a success. All of this indicates the thriving global community and the rise of visibility of Rust. As a committed user and promoter of Rust, we are glad to be part of the Rust community and grow together with it. We look forward to more interesting stuff at next year’s RustCon Asia!Finally, we would like to express our thanks to Cryptape and Baidu X-Lab. It was truly a pleasure working with these two companies as co-organizers, and we are honored to be part of the event with them. We can’t wait to continue this collaboration for next year’s conference and together let’s build a stronger Rust community here in Asia."}, {"url": "https://pingcap.com/weekly/2019-04-29-tidb-weekly/", "title": "Weekly update (April 22 ~ April 28, 2019)", "content": " Weekly update in TiDB Last week, we landed 59 PRs in the TiDB repository.Added Support drop session binding Support show session bindings Support add session binding Support show analyze status Support building CMSketch with Top-N Support incremental Analyze for indexes without feedback updates Improved Add cop-tasks and memory information into the slow_query table Support building statistics on samples from fast Analyze Add memory tables to store TiKV status Check inconsistent indexes in IndexLookupExecutor Remove some unused gRPC metrics in the TiKV client Predefine metrics labels in the conn/session package to improve the performance Predefine metrics labels in the plan/executor package to improve the performance Predefine metrics labels in the store package to improve the performance Reduce alloc and lock-hold-time caused by counting errors and warnings Make SHOW COLLATIONS show supported collations only Lazily evaluate explainID and the memory tracker label to improve the performance Add pre_split_regions when creating a table with shard_row_id_bits Take more prefix columns into account when building ranges Support the column collate syntax create table t(col type collate...) Improve the row count estimation by using column order correlation Make Join reorder by dynamic programming work Fixed Fix the panic when moving Analyze jobs to history Fix wrong PointGet judgement conditions in some cases Fix the issue that the slow log parser parses logs wrongly when a slow log includes # Fix the issue that distinct on multiple strings returns wrong results in some cases Fix the wrong behavior of group_concat(distinct) in some cases Weekly update in TiSpark Last week, we landed 14 PRs in the TiSpark repository.Added Support Spark 2.4.x with the maven profile Fixed Fix the retry method in Scan when encountering locks Fix the next() implementation in the point key Fix inconsistent timestamps of different tables in the same plan Fix the issue that the supported Spark version is not updated in the maven profile Fix the issue that the desc formatted table command for hive tables might lead to ArrayIndexOutOfBoundException Weekly update in TiKV and PD Last week, we landed 25 PRs in the TiKV and PD repositories.Added Add a speed limit for tikv-importer Implement the period_add and period_diff Coprocessor functions Add the check command for operators Add the GetOperator service Add BatchSelectionExecutor Add the COUNT batch aggregate function Improvement Disable unused features of dependencies Add storeID to metrics back Utilize the iter_batched_ref new feature of Criterion Add a timeout for the HTTP client Improve some logs Add jq and cache dependencies for the docker image Use the memory to cache split SST instead of disk files Upgrade the Rust compiler version to 2019-04-25 Fixed Fix the problem of not considering the learner in the merge process Fix the sending error failure in heartbeat stream New contributors (Thanks!) tikv: Seact jswh psinghal20 "}, {"url": "https://pingcap.com/blog-cn/dm-source-code-reading-4/", "title": "DM 源码阅读系列文章(四)dump/load 全量同步的实现", "content": " 本文为 DM 源码阅读系列文章的第四篇,上篇文章 介绍了数据同步处理单元实现的功能,数据同步流程的运行逻辑以及数据同步处理单元的 interface 设计。本篇文章在此基础上展开,详细介绍 dump 和 load 两个数据同步处理单元的设计实现,重点关注数据同步处理单元 interface 的实现,数据导入并发模型的设计,以及导入任务在暂停或出现异常后如何恢复。dump 处理单元 dump 处理单元的代码位于 github.com/pingcap/dm/mydumper 包内,作用是从上游 MySQL 将表结构和数据导出到逻辑 SQL 文件,由于该处理单元总是运行在任务的第一个阶段(full 模式和 all 模式),该处理单元每次运行不依赖于其他处理单元的处理结果。另一方面,如果在 dump 运行过程中被强制终止(例如在 dmctl 中执行 pause-task 或者 stop-task),也不会记录已经 dump 数据的 checkpoint 等信息。不记录 checkpoint 是因为每次运行 mydumper 从上游导出数据,上游的数据都可能发生变更,为了能得到一致的数据和 metadata 信息,每次恢复任务或重新运行任务时该处理单元会 清理旧的数据目录,重新开始一次完整的数据 dump。导出表结构和数据的逻辑并不是在 DM 内部直接实现,而是 通过 os/exec 包调用外部 mydumper 二进制文件 来完成。在 mydumper 内部,我们需要关注以下几个问题: 数据导出时的并发模型是如何实现的。 no-locks, lock-all-tables, less-locking 等参数有怎样的功能。 库表黑白名单的实现方式。 mydumper 的实现细节 mydumper 的一次完整的运行流程从主线程开始,主线程按照以下步骤执行: 解析参数。 创建到数据库的连接。 会根据 no-locks 选项进行一系列的备份安全策略,包括 long query guard 和 lock all tables or FLUSH TABLES WITH READ LOCK。 START TRANSACTION WITH CONSISTENT SNAPSHOT。 记录 binlog 位点信息。 less locking 处理线程的初始化。 普通导出线程初始化。 如果配置了 trx-consistency-only 选项,执行 UNLOCK TABLES /* trx-only */ 释放之前获取的表锁。注意,如果开启该选项,是无法保证非 InnoDB 表导出数据的一致性。更多关于一致性读的细节可以参考 MySQL 官方文档 Consistent Nonlocking Reads 部分。 根据配置规则(包括 –database, –tables-list 和 –regex 配置)读取需要导出的 schema 和表信息,并在这个过程中有区分的记录 innodb_tables 和 non_innodb_table。 为工作子线程创建任务,并将任务 push 到相关的工作队列。 如果没有配置 no-locks 和 trx-consistency-only 选项,执行 UNLOCK TABLES /* FTWRL */ 释放锁。 如果开启 less-locking,等待所有 less locking 子线程退出。 等待所有工作子线程退出。 工作线程的并发控制包括了两个层面,一层是在不同表级别的并发,另一层是同一张表级别的并发。mydumper 的主线程会将一次同步任务拆分为多个同步子任务,并将每个子任务分发给同一个异步队列 conf.queue_less_locking/conf.queue,工作子线程从队列中获取任务并执行。具体的子任务划分包括以下策略: 开启 less-locking 选项的非 InnoDB 表的处理。 先将所有 non_innodb_table 分为 num_threads 组,分组方式是遍历这些表,依此将遍历到的表加入到当前数据量最小的分组,尽量保证每个分组内的数据量相近。 上述得到的每个分组内会包含一个或多个非 InnoDB 表,如果配置了 rows-per-file 选项,会对每张表进行 chunks 估算,对于每一张表,如果估算结果包含多个 chunks,会将子任务进一步按照 chunks 进行拆分,分发 chunks 数量个子任务,如果没有 chunks 划分,分发为一个独立的子任务。 注意,在该模式下,子任务会 发送到 queue_less_locking,并在编号为 num_threads ~ 2 * num_threads 的子线程中处理任务。 less_locking_threads 任务执行完成之后,主线程就会 UNLOCK TABLES /* FTWRL */ 释放锁,这样有助于减少锁持有的时间。主线程根据 conf.unlock_tables 来判断非 InnoDB 表是否全部导出,普通工作线程 或者 queue_less_locking 工作线程每次处理完一个非 InnoDB 表任务都会根据 non_innodb_table_counter 和 non_innodb_done 两个变量判断是否还有没有导出结束的非 InnoDB 表,如果都已经导出结束,就会向异步队列 conf.unlock_tables 中发送一条数据,表示可以解锁全局锁。 每个 less_locking_threads 处理非 InnoDB 表任务时,会先 加表锁,导出数据,最后 解锁表锁。 未开启 less-locking 选项的非 InnoDB 表的处理。 遍历每一张非 InnoDB 表,同样对每张表进行 chunks 估算,如果包含多个 chunks,按照 chunks 个数分发同样的子任务数;如果没有划分 chunks,每张表分发一个子任务。所有的任务都分发到 conf->queue 队列。 InnoDB 表的处理。 与未开启 less-locking 选项的非 InnoDB 表的处理相同,同样是 按照表分发子任务,如果有 chunks 子任务会进一步细分。 从上述的并发模型可以看出 mydumper 首先按照表进行同步任务拆分,对于同一张表,如果配置 rows-per-file 参数,会根据该参数和表行数将表划分为合适的 chunks 数,这即是同一张表内部的并发。具体表行数的估算和 chunks 划分的实现见 get_chunks_for_table 函数。需要注意目前 DM 在任务配置中指定的库表黑白名单功能只应用于 load 和 binlog replication 处理单元。如果在 dump 处理单元内使用库表黑白名单功能,需要在同步任务配置文件的 dump 处理单元配置提供 extra-args 参数,并指定 mydumper 相关参数,包括 –database, –tables-list 和 –regex。mydumper 使用 regex 过滤库表的实现参考 check_regex 函数。load 处理单元 load 处理单元的代码位于 github.com/pingcap/dm/loader 包内,该处理单元在 dump 处理单元运行结束后运行,读取 dump 处理单元导出的 SQL 文件解析并在下游数据库执行逻辑 SQL。我们重点分析 Init 和 Process 两个 interface 的实现。Init 实现细节 该阶段进行一些初始化和清理操作,并不会开始同步任务,如果在该阶段运行中出现错误,会通过 rollback 机制 清理资源,不需要调用 Close 函数。该阶段包含的初始化操作包括以下几点: 创建 checkpoint,checkpoint 用于记录全量数据的导入进度和 load 处理单元暂停或异常终止后,恢复或重新开始任务时可以从断点处继续导入数据。 应用任务配置的数据同步规则,包括以下规则: 初始化黑白名单 初始化表路有规则 初始化列值转换规则 Process 实现细节 该阶段的工作流程也很直观,通过 一个收发数据类型为 *pb.ProcessError 的 channel 接收运行过程中出现的错误,出错后通过 context 的 CancelFunc 强制结束处理单元运行。在核心的 数据导入函数 中,工作模型与 mydumper 类似,即在 主线程中分发任务,有多个工作线程执行具体的数据导入任务。具体的工作细节如下: 主线程会按照库,表的顺序读取创建库语句文件 <db-name>-schema-create.sql 和建表语句文件 <db-name>.<table-name>-schema-create.sql,并在下游执行 SQL 创建相对应的库和表。 主线程读取 checkpoint 信息,结合数据文件信息创建 fileJob 随机分发任务给一个工作子线程,fileJob 任务的结构如下所示 :type fileJob struct { schema string table string dataFile string offset int64 // 表示读取文件的起始 offset,如果没有 checkpoint 断点信息该值为 0 info *tableInfo // 保存原库表,目标库表,列名,insert 语句 column 名字列表等信息 } 在每个工作线程内部,有一个循环不断从自己 fileJobQueue 获取任务,每次获取任务后会对文件进行解析,并将解析后的结果分批次打包为 SQL 语句分发给线程内部的另外一个工作协程,该工作协程负责处理 SQL 语句的执行。工作流程的伪代码如下所示,完整的代码参考 func (w *Worker) run():// worker 工作线程内分发给内部工作协程的任务结构 type dataJob struct { sql string // insert 语句, insert into <table> values (x, y, z), (x2, y2, z2), … (xn, yn, zn); schema string // 目标数据库 file string // SQL 文件名 offset int64 // 本次导入数据在 SQL 文件的偏移量 lastOffset int64 // 上一次已导入数据对应 SQL 文件偏移量 } // SQL 语句执行协程 doJob := func() { for { select { case <-ctx.Done(): return case job := <-jobQueue: sqls := []string{ fmt.Sprintf("USE `%s`;", job.schema), // 指定插入数据的 schema job.sql, checkpoint.GenSQL(job.file, job.offset), // 更新 checkpoint 的 SQL 语句 } executeSQLInOneTransaction(sqls) // 在一个事务中执行上述 3 条 SQL 语句 } } } ​ // worker 主线程 for { select { case <-ctx.Done(): return case job := <-fileJobQueue: go doJob() readDataFileAndDispatchSQLJobs(ctx, dir, job.dataFile, job.offset, job.info) } } dispatchSQL 函数负责在工作线程内部读取 SQL 文件和重写 SQL,该函数会在运行初始阶段 创建所操作表的 checkpoint 信息,需要注意在任务中断恢复之后,如果这个文件的导入还没有完成,checkpoint.Init 仍然会执行,但是这次运行不会更新该文件的 checkpoint 信息。列值转换和库表路由也是在这个阶段内完成。 列值转换:需要对输入 SQL 进行解析拆分为每一个 field,对需要转换的 field 进行转换操作,然后重新拼接起 SQL 语句。详细重写流程见 reassemble 函数。 库表路由:这种场景下只需要 替换源表到目标表 即可。 在工作线程执行一个批次的 SQL 语句之前,会首先根据文件 offset 信息生成一条更新 checkpoint 的语句,加入到打包的 SQL 语句中,具体执行时这些语句会 在一个事务中提交,这样就保证了断点信息的准确性,如果导入过程暂停或中断,恢复任务后从断点重新同步可以保证数据一致。 小结 本篇详细介绍 dump 和 load 两个数据同步处理单元的设计实现,对核心 interface 实现、数据导入并发模型、数据导入暂停或中断的恢复进行了分析。接下来的文章会继续介绍 binlog replication,relay log 两个数据同步处理单元的实现。"}, {"url": "https://pingcap.com/blog/titan-storage-engine-design-and-implementation/", "title": "Titan: A RocksDB Plugin to Reduce Write Amplification", "content": " Introduction Titan is a RocksDB plugin for key-value separation, inspired by WiscKey, a paper issued in USENIX FAST 2016. It’s available for preview in TiDB 3.0. The goal of Titan is to reduce write amplification in RocksDB when using large values.WiscKey is specifically designed for solid-state drives (SSDs). WiscKey separates keys from values, which generates random I/O that is not well suited for hard drives. Our internal testing indicates that when the value size in Key-Value pairs is large, Titan performs better than RocksDB in write, update, and point read scenarios. However, according to RUM Conjecture, improvements achieved in one are usually at the cost of another. The same is true with Titan – it gets a higher write performance by sacrificing storage space and range query performance. As the price of SSDs continues to decrease, we believe this trade-off will be more and more meaningful.Design goals As a child project of TiKV, Titan’s first design goal is to be compatible with RocksDB. As the bottom-layer storage engine for TiKV and a mature project itself, RocksDB already has a large user base. These users should be able to seamlessly upgrade their RocksDB-based TiKV to a Titan-based TiKV. Therefore, we have developed the following design goals: Reduce write amplification by separating values from the log-structured merge-tree (LSM tree) and storing them independently. Seamlessly upgrade RocksDB instances to Titan. The upgrade will not require human intervention and will not impact online services. Achieve 100% compatibility with all RocksDB features used by the current TiKV. Minimize invasive changes to RocksDB to ensure an easier RocksDB upgrade to Titan. Architecture and implementation The following figure shows the basic Titan architecture:Figure 1. During flush and compaction operations, Titan separates values from the LSM tree. The advantage of this approach is that the write process is consistent with RocksDB, which reduces the chance of invasive changes to RocksDB.Titan core components include BlobFile, TitanTableBuilder, Version, and Garbage Collection (GC). We will dive into each of these components in the following sections.BlobFile When Titan separates the value file from the LSM tree, it stores the value file in the BlobFile. The following figure shows the BlobFile format:Figure 2. A blob file is mainly comprised of blob records, meta blocks, a meta index block, and a footer. Each block record stores a Key-Value pair. The meta blocks are used for scalability, and store properties related to the blob file. The meta index block is used for meta block searching.Notes: The Key-Value pairs in the blob file are stored in order, so that when the Iterator is implemented, we can use prefetching to improve sequential reading performance. Each blob record keeps a copy of the user key corresponding to the value. This way, when Titan performs Garbage Collection (GC), it can query the user key and identify whether the corresponding value is outdated. However, this process introduces some write amplification. BlobFile supports compression at the blob record level. Titan supports multiple compression algorithms, such as Snappy, LZ4, and Zstd. Currently, the default compression algorithm Titan uses is LZ4. TitanTableBuilder TitanTableBuilder is the key to achieving Key-Value separation. As you may know, RocksDB supports custom table builders for creating a Sorted String Table (SST). This feature let’s us separate values from the SST without making any invasive changes to the existing table building process. Now, let’s look at the TitanTableBuilder main workflow:Figure 3. TitanTableBuilder determines the Key-Pair value size, and based on that, decides whether to separate the value from the Key-Value pair and store it in the blob file. If the value size is greater than or equal to min_blob_size, TitanTableBuilder separates the value and stores it in the blob file. TitanTableBuilder also generates an index and writes it into the SST. If the value size is less than min_blob_size, TitanTableBuilder writes the value directly into the SST. Titan’s design differs greatly from Badger, which converts the Write-Ahead Log (WAL) into a Vlog to save overhead for a single flush. There are two main reasons why Titan takes a different approach: Assume that the maximum level for LSM tree is 5, and the amplification factor is 10. The overall write amplification for the LSM tree is 1+1+10+10+10+10=42, among which the write amplification for the flush is 1, with a proportion of 1:42. Compared to the overall write amplification for the LSM tree, the write amplification for the flush can be ignored. Because we can ignore the write amplification for the flush, keeping the WAL could significantly reduce invasive changes to RocksDB, which is one of our design goals. Version Titan uses Version to refer to a valid blob file at a specific point of time. We draw this data management method from LevelDB. The core concept behind this method is Multi-version Concurrency Control (MVCC). The advantage of this approach is that it allows concurrent reads with no locking required when a file is added or deleted. Each time a file is added or deleted, Titan generates a new version. Before each read, Titan gets the latest version.Figure 4. New and old versions form a head-to-tail bidirectional linked list. VersionSet manages all versions, with a current pointer that points to the latest version.Garbage Collection Titan uses Garbage Collection (GC) to reclaim space. When weighing write amplification and space amplification, an efficient GC algorithm should recover the maximum space with the fewest cycles. When designing GC, there are two major problems to consider: When to perform GC On which files to perform GC Titan addresses these problems using two features of RocksDB: the Table Properties Collector and the Event Listener. Next, we will elaborate on how these two features assist GC.BlobFileSizeCollector RocksDB let’s us use BlobFileSizeCollector, a custom table property collector, to collect properties from the SST which are written into corresponding SST files. The collected properties are named BlobFileSizeProperties. The following figure shows the BlobFileSizeCollector workflow and data formats.Figure 5. On the left is the SST index format. The first column is the blob file ID; the second column is the offset for the blob record in the blob file; the third column is the blob record size.On the right is the BlobFileSizeProperties format. Each line represents a blob file and how much data is saved in this blob file. The first column is the blob file ID; the second column is the size of the data.EventListener As we know, RocksDB uses compaction to discard old data and reclaim space. After each compaction, some blob files in Titan may contain partly or entirely outdated data. Therefore, we could trigger GC by listening to compaction events. During compaction, we could collect and compare the input/output blob file size properties of SST to determine which blob files require GC. The following figure shows the general process:Figure 6. Event Listener Process inputs stands for the blob file size properties for all SSTs that participate in the compaction. outputs stands for the blob file size properties for all SSTs generated in the compaction. discardable size is the size of the file to be discarded for each blob file, calculated based on inputs and outputs. The first column is the blob file ID. The second column is the size of the file to be discarded. For each valid blob file, Titan maintains a discardable size variable in memory. After each compaction, this variable is accumulated for the corresponding blob file. Each time when GC starts, it picks the blob file with the greatest discardable size as the candidate file for GC.Sample Before each compaction, we pick a set of blob files as our candidates as described in the previous …"}, {"url": "https://pingcap.com/blog-cn/tikv-source-code-reading-6/", "title": "TiKV 源码解析系列文章(六)raft-rs 日志复制过程分析", "content": " 在 《TiKV 源码解析系列文章(二)raft-rs proposal 示例情景分析》 中,我们主要介绍了 raft-rs 的基本 API 使用,其中,与应用程序进行交互的主要 API 是: RawNode::propose 发起一次新的提交,尝试在 Raft 日志中追加一个新项; RawNode::ready_since 从 Raft 节点中获取最近的更新,包括新近追加的日志、新近确认的日志,以及需要给其他节点发送的消息等; 在将一个 Ready 中的所有更新处理完毕之后,使用 RawNode::advance 在这个 Raft 节点中将这个 Ready 标记为完成状态。 熟悉了以上 3 个 API,用户就可以写出基本的基于 Raft 的分布式应用的框架了,而 Raft 协议中将写入同步到多个副本中的任务,则由 raft-rs 库本身的内部实现来完成,无须应用程序进行额外干预。本文将对数据冗余复制的过程进行详细展开,特别是关于 snapshot 及流量控制的机制,帮助读者更深刻地理解 Raft 的原理。一般 MsgAppend 及 MsgAppendResponse 的处理 在 Raft leader 上,应用程序通过 RawNode::propose 发起的写入会被处理成一条 MsgPropose 类型的消息,然后调用 Raft::append_entry 和 Raft::bcast_append 将消息中的数据追加到 Raft 日志中并广播到其他副本上。整体流程如伪代码所示:fn Raft::step_leader(&mut self, mut m: Message) -> Result<()> { if m.get_msg_type() == MessageType::MsgPropose { // Propose with an empty entry list is not allowed. assert!(!m.get_entries().is_empty()); self.append_entry(&mut m.mut_entries()); self.bcast_append(); } } 这段代码中 append_entry 的参数是一个可变引用,这是因为在 append_entry 函数中会为每一个 Entry 赋予正确的 term 和 index。term 由选举产生,在一个 Raft 系统中,每选举出一个新的 Leader,便会产生一个更高的 term。而 index 则是 Entry 在 Raft 日志中的下标。Entry 需要带上 term 和 index 的原因是,在其他副本上的 Raft 日志是可能跟 Leader 不同的,例如一个旧 Leader 在相同的位置(即 Raft 日志中具有相同 index 的地方)广播了一条过期的 Entry,那么当其他副本收到了重叠的、但是具有更高 term 的消息时,便可以用它们替换旧的消息,以便达成与最新的 Leader 一致的状态。在 Leader 将新的写入追加到自己的 Raft log 中之后,便可以调用 bcast_append 将它们广播到其他副本了。注意这个函数并没有任何参数,那么 Leader 如何知道应该给每一个副本从哪一个位置开始广播呢?原来在 Leader 上对每一个副本,都关联维护了一个 Progress,该结构体定义如下:pub struct Progress { pub matched: u64, // 该副本期望接收的下一个 Entry 的 index pub next_idx: u64, // 未 commit 的消息的滑动窗口 pub ins: Inflights, // ProgressState::Probe:Leader 每个心跳间隔中最多发送一条 MsgAppend // ProgressState::Replicate:Leader 在每个心跳间隔中可以发送多个 MsgAppend // ProgressState::Snapshot:Leader 无法再继续发送 MsgAppend 给这个副本 pub state: ProgressState, // 是否暂停给这个副本发送 MsgAppend 了 pub paused: bool, // 一些其他字段…… } 如代码注释中所说的那样,Leader 在给副本广播新的日志时,会从对应的副本的 next_idx 开始。这就蕴含了两个问题: 在刚开始启动的时候,所有副本的 next_idx 应该如何设置? 在接收并处理完成 Leader 广播的新写入后,其他副本应该如何向 Leader 更新 next_idx? 第一个问题的答案在 Raft::reset 函数中。这个函数会在 Raft 完成选举之后选出的 Leader 上调用,会将 Leader 的所有其他副本的 next_idx 设置为跟 Leader 相同的值。之后,Leader 就可以会按照 Raft 论文里的规定,广播一条包含了自己的 term 的空 Entry 了。第二个问题的答案在 Raft::handle_append_response 函数中。我们继续考察上面的情景,Leader 的其他副本在收到 Leader 广播的最新的日志之后,可能会采取两种动作:fn Raft::handle_append_entries(&mut self, m: &Message) { let mut to_send = Message::new_message_append_response(); match self.raft_log.maybe_append(...) { // 追加日志成功,将最新的 last index 上报给 Leader Some(last_index) => to_send.set_index(last_index), // 追加日志失败,设置 reject 标志,并告诉 Leader 自己的 last index None => { to_send.set_reject(true); to_send.set_reject_hint(self.raft_log.last_index()); } } } self.send(to_send); 其他副本调用 maybe_append 失败的原因可能是比 Leader 的日志更少,但是 Leader 在刚选举出来的时候将所有副本的 next_idx 设置为与自己相同的值了。这个时候这些副本就会在 MsgAppendResponse 中设置拒绝的标志。在 Leader 接收到这样的反馈之后,就可以将对应副本的 next_idx 设置为正确的值了。这个逻辑在 Raft::handle_append_response 中:fn Raft::handle_append_response(&mut self, m: &Message, …) { if m.get_reject() { let pr: &mut Progress = self.get_progress(m.get_from()); // 将副本对应的 `next_idx` 回退到一个合适的值 pr.maybe_decr_to(m.get_index(), m.get_reject_hint()); } else { // 将副本对应的 `next_idx` 设置为 `m.get_index() + 1` pr.maybe_update(m.get_index()); } } 以上伪代码中我们省略了一些丢弃乱序消息的代码,避免过多的细节造成干扰。pipeline 优化和流量控制机制 上一节我们重点观察了 MsgAppend 及 MsgAppendResponse 消息的处理流程,原理是非常简单、清晰的。然而,这个未经任何优化的实现能够工作的前提是在 Leader 收到某个副本的 MsgAppendResponse 之前,不再给它发送任何 MsgAppend。由于等待响应的时间取决于网络的 TTL,这在实际应用中是非常低效的,因此我们需要引入 pipeline 优化,以及配套的流量控制机制来避免“优化”带来的网络壅塞。Pipeline 在 Raft::prepare_send_entries 函数中被引入。这个函数在 Raft::send_append 中被调用,内部会直接修改对目标副本的 next_idex 值,这样,后续的 MsgAppend 便可以在此基础上继续发送了。而一旦之前的 MsgAppend 被该目标副本拒绝掉了,也可以通过上一节中介绍的 maybe_decr_to 机制将 next_idx 重置为正确的值。我们来看一下这段代码:// 这个函数在 `Raft::prepare_send_entries` 中被调用 fn Progress::update_state(&mut self, last: u64) { match self.state { ProgressState::Replicate => { self.next_idx = last + 1; self.ins.add(last); }, ProgressState::Probe => self.pause(), _ => unreachable!(), } } Progress 有 3 种不同的状态,如这个结构体的定义的代码片段所示。其中 Probe 状态和 Snapshot 状态会在下一节详细介绍,现在只需要关注 Replicate 状态。我们已经知道 Pipeline 机制是由更新 next_idx 的那一行引入的了,那么下面更新 ins 的一行的作用是什么呢?从 Progress 的定义的代码片段中我们知道,ins 字段的类型是 Inflights,可以想象成一个类似 TCP 的滑动窗口:所有 Leader 发出了,但是尚未被目标副本响应的消息,都被框在该副本在 Leader 上对应的 Progress 的 ins 中。这样,由于滑动窗口的大小是有限的,Raft 系统中任意时刻的消息数量也会是有限的,这就实现了流量控制的机制。更具体地,Leader 在给某一副本发送 MsgAppend 时,会检查其对应的滑动窗口,这个逻辑在 Raft::send_append 函数中;在收到该副本的 MsgAppendResponse 之后,会适时调用 Inflights 的 free_to 函数,使窗口向前滑动,这个逻辑在 Raft::handle_append_response 中。ProgressState 相关优化 我们已经在 Progress 结构体的定义以及上面一些代码片段中见过了 ProgressState 这个枚举类型。在 3 种可能的状态中,Replicate 状态是最容易理解的,Leader 可以给对应的副本发送多个 MsgAppend 消息(不超过滑动窗口的限制),并适时地将窗口向前滑动。然而,我们注意到,在 Leader 刚选举出来时,Leader 上面的所有其他副本的状态却被设置成了 Probe。这是为什么呢?从 Progress 结构体的字段注释中,我们知道当某个副本处于 Probe 状态时,Leader 只能给它发送 1 条 MsgAppend 消息。这是因为,在这个状态下的 Progress 的 next_idx 是 Leader 猜出来的,而不是由这个副本明确的上报信息推算出来的。它有很大的概率是错误的,亦即 Leader 很可能会回退到某个地方重新发送;甚至有可能这个副本是不活跃的,那么 Leader 发送的整个滑动窗口的消息都可能浪费掉。因此,我们引入 Probe 状态,当 Leader 给处于这一状态的副本发送了 MsgAppend 时,这个 Progress 会被暂停掉(源码片段见上一节),这样在下一次尝试给这个副本发送 MsgAppend 时,会在 Raft::send_append 中跳过。而当 Leader 收到了这个副本上报的正确的 last index 之后,Leader 便知道下一次应该从什么位置给这个副本发送日志了,这一过程在 Progress::maybe_update 函数中:fn Progress::maybe_update(&mut self, n: u64) { if self.matched < n { self.matched = n; self.resume(); // 取消暂停的状态 } if self.next_idx < n + 1 { self.next = n + 1; } } ProgressState::Snapshot 状态与 Progress 中的 pause 标志十分相似,一个副本对应的 Progress 一旦处于这个状态,Leader 便不会再给这个副本发送任何 MsgAppend 了。但是仍有细微的差别:事实上在 Leader 收到 MsgHeartbeatResponse 时,也会调用 Progress::resume 来将取消对该副本的暂停,然而对于 ProgressState::Snapshot 状态的 Progress 则没有这个逻辑。这个状态会在 Leader 成功发送完成 Snapshot,或者收到了对应的副本的最新的 MsgAppendResponse 之后被改变,详细的逻辑请参考源代码,这里就不作赘述了。我们把篇幅留给在 Follower 上收到 Snapshot 之后的处理逻辑,主要是 Raft::restore_raft 和 RaftLog::restore 两个函数。前者中主要包含了对 Progress 的处理,因为 Snapshot 包含了 Leader 上最新的信息,而 Leader 上的 Configuration 是可能跟 Follower 不同的。后者的主要逻辑伪代码如下所示:fn RaftLog::restore(&mut self, snapshot: Snapshot) { self.committed = snapshot.get_metadata().get_index(); self.unstable.restore(snapshot); } 可以看到,内部仅更新了 committed,并没有更新 applied。这是因为 raft-rs 仅关心 Raft 日志的部分,至于如何把日志中的内容更新到真正的状态机中,是应用程序的任务。应用程序需要从上一篇文章中介绍的 Ready 接口中把 Snapshot 拿到,然后自行将其应用到状态机中,最后再通过 RawNode::advance 接口将 applied 更新到正确的值。总结 Raft 日志复制及相关的流量控制、Snapshot 流程就介绍到这里,代码仓库仍然在 https://github.com/pingcap/raft-rs,source-code 分支。下一期 raft-rs 源码解析我们会继续为大家带来 configuration change 相关的内容,敬请期待!"}, {"url": "https://pingcap.com/weekly/2019-04-22-tidb-weekly/", "title": "Weekly update (April 15 ~ April 21, 2019)", "content": " Weekly update in TiDB Last week, we landed 28 PRs in the TiDB repository.Added Support show global bind Support drop global binding Support create global binding Support show open tables with empty results Add the role support for SHOW GRANT Support SET DEFAULT ROLE Support the JSON_SEARCH built-in function Improved Speed up decoding the column ID Adapt the utf8mb4_0900_ai_ci collation Show more information about Coprocessor tasks in slow query logs Show memory consumption in slow query logs Add a memory table to store hot Region information Show StatsVersion of the table in slow query logs Support ConstItem() for expressions to test if it is a constant Fixed Fix wrong results caused by splitting ranges inappropriately in some cases Fix the stack overflow issue caused by folding constants in some cases Fix the issue that the bad null error is ignored when disabling the strict SQL mode Fix a race when recreating the batch-commands client when TiKV crashes Fix wrong results caused by pruning unfoldable expressions in order by Weekly update in TiSpark Last week, we landed 11 PRs in the TiSpark repository.Added Add a tidb-adapter plugin to use TiDB as a metastore Support Spark 2.3.x by reflection rather than profile Fixed Fix a partition parser bug when encountering ZERO_DECIMAL Fix a bug in unsupported partition expressions Fix the issue that the prefix length is larger than the value used in reality Improved Add the Spark version in TiSpark version description Weekly update in TiKV and PD Last week, we landed 27 PRs in the TiKV and PD repositories.Added Add the batch aggregation framework Add rocksdb.num-immutable-mem-table Support scattering Regions and get the operator status Make properties index distance configurable Add ret_field_type Improvement Move kvGet to the util package Use builder to build the field type Add comments for several modules Clean up util and engine components Use KeyBuilder to reduce memory allocation and copy bound keys Use bitflag to replace bool Adapt the BatchLimitExecutor style to other batch executors Move build related functions to DAGBuilder Make executor summary collector work easier Move the logic of checking the availability of batch execution to the executor Fixed Make tidy check only module files Add the default HTTP prefix for the detaching mode Make LoadRange return both keys and values Retry updating the leader when initializing the PD client Update the copyright Remove repeated content Make TestBalance of the hot-write-region scheduler stable Accept multiple PK handles in the request Check the iterator status when the iterator is invalid Import data after all Regions are scattered New contributors (Thanks!) tikv: b41sh tidb-operator: cofyc parser: Ryan-Git pingyu docs: philip "}, {"url": "https://pingcap.com/meetup/meetup-97-20190420/", "title": "【Infra Meetup No.97】What's New in TiDB 3.0 & An Introduction to Failpoint Design", "content": " Topic 1:What’s New in TiDB 3.0 讲师介绍:申砾,PingCAP 技术 VP。 视频 | Infra Meetup No.97:What’s New in TiDB 3.0 PPT 链接 1 月 19 日,TiDB 发布 3.0 Beta 版,相比 2.1 版本,该版本对系统稳定性、优化器、统计信息以及执行引擎做了很多改进。申砾老师为大家分享了 TiDB 3.0 的新特性及未来的规划。Topic 2:An Introduction to Failpoint Design 讲师介绍:龙恒,TiDB SQL Infra Team 开发工程师,主要工作是 TiDB-Lightning / TiKV-Importer 的维护和新功能开发,致力于性能和稳定性提升。 视频 | Infra Meetup No.97:An Introduction to Failpoint Design PPT 链接 本次分享龙恒老师首先介绍了 Failpoint 的使用场景,以及 github.com/etcdi-io/gofail 的优缺点,然后对 Failpoint 设计原则、实现细节及实现过程中的取舍作了介绍,最后演示了各种 Marker 函数的用法,以及在并行测试中如何使用 context 控制 failpoints Enable/Disable,从而达到隔离不同并行任务的目的。 PingCAP Infra Meetup作为一个基础架构领域的前沿技术公司,PingCAP 希望能为国内真正关注技术本身的 Hackers 打造一个自由分享的平台。自 2016 年 3 月 5 日开始,我们定期在周末举办 Infra Meetup,与大家深度探讨基础架构领域的前瞻性技术思考与经验,目前已在北京、上海、广州、成都、杭州等地举办。在这里,我们希望提供一个高水准的前沿技术讨论空间,让大家真正感受到自由的开源精神魅力。 "}, {"url": "https://pingcap.com/meetup/meetup-98-20190420/", "title": "【Infra Meetup No.98】Everything You Always Wanted to Know About Compiled and Vectorized Queries But Were Afraid to Ask", "content": " 讲师介绍:徐怀宇,TiDB 研发工程师,目前主要负责查询执行引擎相关工作。 视频 | Infra Meetup No.98:Compiled and Vectorized Queries PPT 链接 本次分享徐怀宇老师为大家介绍了论文《Everything You Always Wanted to Know About Compiled and Vectorized Queries But Were Afraid to Ask》,主要包括: 介绍经典 Volcano 模型的执行流程,并分析其运行时性能。 介绍行存、列存的基本概念,并进而引出向量化执行,分析其如何克服经典 Volcano 模型的缺点。 介绍代码生成的基本概念,结合案例分析其如何克服经典 Volcano 模型的缺点。 最后,结合论文内容,重点从 micro-architecture, data-parallel execution 两个方面,分析对比向量化执行和代码生成的特性,进而引出论文结论:向量化执行在 memory-bound 类的查询中更有优势,代码生成在 calculation-heavy 类的查询中更有优势。但是总体来看,在 OLAP 场景中,向量化执行和代码生成的执行性能相近。延伸阅读 : 论文链接 PingCAP Infra Meetup作为一个基础架构领域的前沿技术公司,PingCAP 希望能为国内真正关注技术本身的 Hackers 打造一个自由分享的平台。自 2016 年 3 月 5 日开始,我们定期在周末举办 Infra Meetup,与大家深度探讨基础架构领域的前瞻性技术思考与经验,目前已在北京、上海、广州、成都、杭州等地举办。在这里,我们希望提供一个高水准的前沿技术讨论空间,让大家真正感受到自由的开源精神魅力。 "}, {"url": "https://pingcap.com/meetup/meetup-99-20190420/", "title": "【Infra Meetup No.99】数据中台 & WiredTiger 引擎实现原理 & JIT in Databases", "content": " Topic 1: 宝尊对数据中台搭建的思考与探索 讲师介绍:张建,宝尊电商技术总监。 数据中台最近特别火,很多企业都在关注如何构建自己的数据中台,利用数据中台打造数据驱动的经营能力。同时,数据中台的概念也漫天飞,但是很难有一个大家都认同的标准。一个有趣的现象是数据中台在国内数据圈子里在升温,但是国外却鲜有提及,以至于我们在向 Gartner 咨询相关主题的时候,国外的咨询师都一头雾水,不知道数据中台是什么。甚至想找一个对“数据中台”比较恰当的英文翻译都很难。面对这个既新又杂的概念,宝尊也在进行自己的“数据中台”探索。本次研讨会,宝尊的算法和大数据部负责人张建就自己对数据中台的调研和思考,与大家一起开脑洞,共同研究和探讨了数据中台是什么,它和数据仓库有什么区别,数据中台的核心价值是什么等主题。 应讲师要求,该分享视频&PPT 资料仅限内部学习交流,不对外公开~ Topic 2:WiredTiger 引擎实现原理 讲师介绍:许鹏,携程机票技术总监,负责机票大数据基础平台的架构和运维,《Apache Spark 源码剖析》一书作者,三年 Presto 及 Elasticsearch,MongoDB 集群的一线运维经验。长期专注于分布式计算引擎技术和分布式存储的设计与实现。 视频 | Infra Meetup No.99:WiredTiger 引擎实现原理 PPT 链接 WiredTiger 作为 MongoDB 的默认存储引擎,许鹏从整体架构,内存管理,磁盘寻址,数据持久化,文件压缩最佳实践等维度介绍 WiredTiger,并描述如何最小化线程间的竞争,如何充分现代计算机平台中的多核和大内存的优势,在 WiredTiger 并发控制机制中的体现,最后也介绍从源码级别怎么分析和调试 WiredTiger。Topic 3:JIT in Databases 讲师介绍:吴逸飞,TiSpark 研发工程师。 视频 | Infra Meetup No.99:WiredTiger 引擎实现原理 PPT 链接 本次分享内容主要包括: 介绍了 JIT (即时编译技术) 在数据库中的意义: 避免传统解释系统的无关开销。 通过生成围绕寄存器优化的代码来最小化内存流量。 介绍了 JIT 在查询编译时的几种优化:HIQUE 的算子编译,Hyper 的管道编译,Impala 的表达式编译。以及他们相比较火山模型的优点。 讨论了选择什么语言来进行 JIT 优化,以及每一种选择的优劣。 讨论了 JIT 在工业上的一些实践,并以 Apache Spark 的 codegen 做例子,介绍了 Spark 在 Join 时的 JIT 模型。 简单的介绍了一下 JIT 结合其他运行时优化(向量化,预取)在学术界的一些实践。 最后总结了一下 JIT 的特点和前景: 减少数据库系统执行的指令数。 在运行时根据运行时信息做特化。 和不同的运行时优化结合以达到更好的效果。 注:吴逸飞老师本次分享内容参考了知乎专栏「分布式和存储的那些事」收录的 《查询编译综述》 一文,感谢文章作者王欢明老师的分享~ PingCAP Infra Meetup作为一个基础架构领域的前沿技术公司,PingCAP 希望能为国内真正关注技术本身的 Hackers 打造一个自由分享的平台。自 2016 年 3 月 5 日开始,我们定期在周末举办 Infra Meetup,与大家深度探讨基础架构领域的前瞻性技术思考与经验,目前已在北京、上海、广州、成都、杭州等地举办。在这里,我们希望提供一个高水准的前沿技术讨论空间,让大家真正感受到自由的开源精神魅力。 "}, {"url": "https://pingcap.com/weekly/2019-04-15-tidb-weekly/", "title": "Weekly update (April 08 ~ April 14, 2019)", "content": " Weekly update in TiDB Last week, we landed 52 PRs in the TiDB repository.Added Add the tidb_skip_isolation_level_check variable to skip the isolation check Support fast Analyze in the planner and executor’s builder Support EXPLAIN on ConnectionID Add the RevokeRole support Improved Validate the value when setting the read_only variable Refine some code in http_handler.go Improve the UT coverage of the metrics package to 100% Remove the useless memoryAllocator struct Make convertToIndexScan and convertToTableScan return results more quickly Improve the UT coverage of the meta package from 76% to 85% Solve the bootstrap write conflict when starting multiple tidb-servers in a new cluster Update the db-table/{table_partition_id} HTTP API Improve the UT coverage of the util/kvencoder package to 85% Trace and control the memory usage in DistSQL layer Improve the UT coverage of types/json package to 85% Stop server startup if an unrecognized option is found in a configuration file Ignore Fulltext keys in the CREATE TABLE and ALTER TABLE statements Check the KEY option for the generated column when creating a table Fixed Fix the issue that EXTRACT MONTH does not support 0 Fix the issue that MONTHNAME is incompatible with MySQL Fix the issue that JSON_KEY is incompatible with MySQL Fix the issue that MAKETIME is incompatible with MySQL Fix the leak test failure issue of RecordSet Fix the issue that EXPLAIN ANALYZE does not include the execution info column Fix the issue that Charset/Collation shown in SHOW FULL COLUMNS is incompatible with MySQL Fix the wrongly altering SHARD_ROW_ID_BITS issue in some cases Weekly update in TiSpark Last week, we landed 9 PRs in the TiSpark repository.Added Support the CreateTableLike command Fixed Remove the Spark dependency in tikv-client Fix the desc table command failure issue for hive tables Fix incorrect next() implementation for Key Improved Clean up the JSONType code Weekly update in TiKV and PD Last week, we landed 32 PRs in the TiKV and PD repositories.Added Add metrics for TSO handling time in PD Improvement Move tikv_util as a component Use tokio-threadpool and thread local metrics for readpool Add the max_columns check when building a vectorized expression Add the tcmalloc support to the tikv_alloc crate Fixed Fix the prefix extractor panic when deleting a range Remove strict_sql_mode covered by sql_mode Avoid a panic in RotatingFileLogger of TiKV Enlarge the stall buffer ingested by TiKV New contributors (Thanks!) tikv: ZhangHanDong tidb-operator: qiffang parser: hhxcc docs-cn: reAsOn2010 "}, {"url": "https://pingcap.com/meetup/meetup-96-20190413/", "title": "【Infra Meetup No.96】Introduction to Titan", "content": "在上周六举办的 Infra Meetup No.96 上,我司 TiKV 研发工程师张博康为大家介绍了我们自研的高性能单机 key-value 存储引擎 Titan,以下是视频 & 文字回顾,enjoy~ 讲师介绍:张博康,TiKV 研发工程师,目前负责 TiKV raftstore 以及存储引擎相关工作。 视频 | Infra Meetup No.96:Introduction to Titan PPT 链接 本次分享的主要内容包括: 分析 LSM-Tree 的写放大问题,以阐述 Titan 核心的思路——key-value 分离。 从设计目标出发,介绍并对比了 Wisckey 和 Titan 的结构差异。 介绍了 Titan 的具体设计与实现,包括如何通过 RocksDB 的 TableBuilder,TableProperties,EventListener,WriteCallback 等现有机制实现 key-value 的分离以及 Titan 的 GC 流程。 展示了 Titan 与 RocksDB 在大 value 情况下的性能对比。 延展阅读 : Titan 的设计与实现 The Way to TiDB 3.0 and Beyond WiscKey 论文 PingCAP Infra Meetup作为一个基础架构领域的前沿技术公司,PingCAP 希望能为国内真正关注技术本身的 Hackers 打造一个自由分享的平台。自 2016 年 3 月 5 日开始,我们定期在周末举办 Infra Meetup,与大家深度探讨基础架构领域的前瞻性技术思考与经验,目前已在北京、上海、广州、成都、杭州等地举办。在这里,我们希望提供一个高水准的前沿技术讨论空间,让大家真正感受到自由的开源精神魅力。 "}, {"url": "https://pingcap.com/success-stories/tidb-in-xiaomi/", "title": "Powering the Xiaomi Mobile Lifestyle with TiDB", "content": " Industry: Consumer ElectronicsAuthors: Liang Zhang (DBA team leader at Xiaomi), Youfei Pan (DBA at Xiaomi), and Biwen Wang (Software engineer at Xiaomi)Xiaomi is a leading consumer electronics and software company and the fourth-largest smartphone manufacturer in the world, with market-leading positions in both China and India.MIUI (MI User Interface) is a mobile operating system developed by Xiaomi, based on Google’s Android operating system. It supports various Xiaomi services and customized apps, such as Themes, Music, and its own App Store. Figure 1: MIUI interface As sales of Xiaomi smartphones continue to climb and the MIUI user base continues to grow, we as the Database Administration (DBA) team was having an increasingly hard time managing our MySQL database infrastructure. That is until we adopted TiDB, an open source distributed hybrid transactional and analytical processing (HTAP) database created and supported by PingCAP.Currently, TiDB is deployed in Xiaomi’s production environment to service two applications: instant delivery and our third party ad network. These two workloads generate about 100 million read and write queries daily. We have plans to migrate additional workloads to TiDB in the future.In this post, we will show how we stress-tested TiDB during our evaluation, how we migrated data from MySQL to TiDB, and the issues and learning we encountered along the way.What Is TiDB? TiDB Architecture TiDB in a nutshell is a platform comprised of multiple components that when used together becomes a NewSQL database that has HTAP capabilities. Figure 2: TiDB platform architecture Inside the TiDB platform, the main components are as follows: TiDB Server is a stateless SQL layer that processes users’ SQL queries, accesses data in the storage layer, and returns corresponding results to the application. It is MySQL compatible and sits on top of TiKV. TiKV is the distributed transactional key-value storage layer where the data persists. It uses the Raft consensus protocol for replication to ensure strong data consistency and high availability. TiSpark cluster also sits on top of TiKV. It is an Apache Spark plugin that works with the TiDB platform to support complex OLAP queries for BI analysts and data scientists. Placement Driver (PD): A metadata cluster powered by etcd that manages and schedules TiKV. Beyond these main components, TiDB also has an ecosystem of tools, such as Ansible scripts for quick deployment, Syncer and DM for data replication from existing MySQL instances (both sharded and unsharded), and TiDB-Binlog, which is used to ​collect the logical changes made to a TiDB cluster and provide incremental backup and replication to different downstream options (e.g. TiDB, Kafka or MySQL)​.Core Features Because of its component-based layered architecture, TiDB can be used as a single system for both OLTP (Online Transactional Processing) and OLAP (Online Analytical Processing) workloads, thus enabling HTAP capabilities.It has the following core features: Highly compatible with MySQL and users can easily enhance their current MySQL deployment with TiDB to power their applications without changing a single line of code in most cases and still benefit from the MySQL ecosystem. PingCAP is very transparent with aspects of MySQL that are not currently compatible in TiDB, which are all listed here. Horizontal scalability achieved by simply by adding new nodes. Because the SQL processing layer (TiDB Server) and the storage layer (TiKV) are decoupled, you can scale each resource independently of each other. ACID compliance where all your data is consistent. High availability of all your data as guaranteed by TiDB’s implementation of the Raft consensus algorithm. Our Paint Point Before using TiDB, our team was managing our core business data on a standalone MySQL on disks with 2.6 TB capacity. As data volume surged, bottlenecks began to form. We noticed significant performance degradation and lack of availability in storage capacity, while DDL (data definition language) operations on large tables simply could not be performed.We initially chose to solve our scalability challenges via traditional MySQL manual sharding, but we found this solution undesirable in that: It is intrusive to the application code. When sharding our database, we had to stop the on-going business, refactor the application code, and then migrate the data. It increases the maintenance costs for our team. We had to continuously shard our database in different ways to keep up with growth. We used MySQL proxy and a middleware solution, but it was still difficult to implement cross-shard transactions and cross-shard aggregate queries, such as correlated query, subquery and group-by aggregation of the whole table. Stress Testing TiDB As part of our evaluation of TiDB, we performed some stress testing to verify whether its performance on OLTP workloads would satisfy our service requirements. Here is the configuration and results of our tests, using TiDB 2.0.Hardware Configuration Component Number of instances CPU Memory Disk Version Operating system TiDB 3 Intel® Xeon® CPU E5-2620 v3 @ 2.40GHz 128G SSD Raid 5 2.0.3 PD 3 Intel® Xeon® CPU E5-2620 v3 @ 2.40GHz 128G SSD Raid 5 2.0.3 CentOS Linux release 7.3.1611 TiKV 4 Intel® Xeon® CPU E5-2620 v3 @ 2.40GHz 128G SSD Raid 5 2.0.3 CentOS Linux release 7.3.1611 Objects and Results Standard stress testing for the SELECT statement: Threads QPS Latency (avg/.95/max) 8 12650.81 0.63 / 0.90 / 15.62 16 21956.21 0.73 / 1.50 / 15.71 32 31534.8 1.01 / 2.61 / 25.16 64 38217 1.67 / 5.37 / 49.80 128 39943.05 3.20 / 8.43 / 58.60 256 40920.64 6.25 / 13.70 / 95.13 Figure 3: Standard stress testing for the SELECT statement Standard stress testing for OLTP workloads: Threads TPS QPS Latency (avg/.95/max) 8 428.9 8578.09 18.65 / 21.89 / 116.06 16 731.67 14633.35 21.86 / 25.28 / 120.59 32 1006.43 20128.59 31.79 / 38.25 / 334.92 64 1155.44 23108.9 55.38 / 71.83 / 367.53 128 1121.55 22431 114.12 / 161.51 / 459.03 256 941.26 18825.1 271.94 / 369.77 / 572.88 Figure 4: Standard stress testing for OLTP workloads Standard stress testing for the INSERT statement: Threads QPS Latency (avg/.95/max) 8 3625.75 2.20 / 2.71 / 337.94 16 6527.24 2.45 / 3.55 / 160.84 32 10307.66 3.10 / 4.91 / 332.41 64 13662.83 4.68 / 7.84 / 467.56 128 15100.44 8.47 / 16.41 / 278.23 256 17286.86 14.81 / 25.74 / 3146.52 Figure 5: Standard stress testing for the INSERT statement TiDB’s performance results were able to meet our requirements, even though the system did have some stability issues when we dramatically increased our test workloads that far exceeded the real production environment. Therefore, we decided to use some read traffic in the MySQL slave as the canary traffic in TiDB.Download TiDB Subscribe to Blog Migration Process Our team conducted the migration process to TiDB in two steps: data migration and traffic shifting.Data Migration The data to be migrated included full data and incremental data. We made use of two tools developed by PingCAP, Lightning and Syncer. Logical backup and import can be used for full data migration. In addition, TiDB-Lightning is a tool for the physical import of full data to the TiDB cluster. Syncer (now Data Migration) can be used to replicate the incremental data from MySQL to TiDB. As shown in the following diagram, Syncer relies on various rules to implement different filtering and merging effects; one upstream MySQL instance corresponds to one Syncer process; multiple Syncer processes are required when sharding data is replicated. Figure 6: Syncer architecture Here is our experience with using Syncer: Before using Syncer to synchronize data, check the user privilege, the binlog information, whether server-id, log_bin, and binlog_format is ROW, and whether binlog_row_image is FULL. Enable the rigid mode of data …"}, {"url": "https://pingcap.com/blog-cn/dm-source-code-reading-3/", "title": "DM 源码阅读系列文章(三)数据同步处理单元介绍", "content": " 本文为 DM 源码阅读系列文章的第三篇,上篇文章 介绍了 DM 的整体架构,DM 组件 DM-master 和 DM-worker 的入口代码,以及两者之间的数据交互模型。本篇文章详细地介绍 DM 数据同步处理单元(DM-worker 内部用来同步数据的逻辑单元),包括数据同步处理单元实现了什么功能,数据同步流程、运行逻辑,以及数据同步处理单元的 interface 设计。数据同步处理单元 从上图可以了解到目前 DM 包含 relay log、dump、load、binlog replication(sync) 4 个数据同步处理单元,涵盖了以下数据同步处理的功能: 处理单元 功能 relay log 持久化 MySQL/MariaDB Binlog 到磁盘 dump 从 MySQL/MariaDB dump 全量数据 load 加载全量数据到 TiDB cluster binlog replication(sync) 复制 relay log 存储的 Binlog 到 TiDB cluster 数据同步流程 Task 数据同步流程初始化操作步骤: DM-master 接收到 task,将 task 拆分成 subtask 后 分发给对应的各个 DM-worker; DM-worker 接收到 subtask 后 创建一个 subtask 对象,然后 初始化数据同步流程。 从 初始化数据同步流程 的代码中我们可以看到,根据 task 配置项 task-mode 的不同,DM-worker 会初始化不同的数据同步流程: task-mode 同步流程 需要的数据同步处理单元 all 全量同步 -> 增量数据同步 relay log、dump、load、binlog replication(sync) full 全量同步 dump、load incremental 增量同步 relay log,binlog replication(sync) 运行逻辑 DM 数据同步处理单元 interface 定义在 dm/unit,relay log、dump、load、binlog replication(sync)都实现了该 interface(golang interface 介绍)。实际上 DM-worker 中的数据同步处理单元分为两类: 全局共享单例。dm-worker 启动的时候只初始化一次这类数据同步处理单元,所有的 subtask 都可以使用这类数据同步处理单元的服务;relay log 属于这种类型。 subtask 独享。dm-worker 会为每个 subtask 初始化一系列的数据同步处理单元;dump、load、binlog replication(sync)属于这种类型。 两类数据同步处理单元的使用逻辑不同,这篇文档会着重讲一下 subtask 独享的数据同步处理单元的使用逻辑,不会囊括更多的 relay log 相关的内容,后面会有单独一篇文章详细介绍它。relay log 相关使用代码在 dm/worker/relay.go 、具体功能实现代码在 relay/relay.go,有兴趣的同学也可以先行阅读一下相关代码,relay log 的代码注释也是比较丰富,并且简单易懂。subtask 独享数据同步处理单元使用逻辑相关代码在 dm/worker/subtask.go。subtask 对象包含的主要属性有: units:初始化后要运行的数据同步处理单元。 currUnit:当前正在运行的数据同步处理单元。 prevUnit:上一个运行的数据同步处理单元。 stage:subtask 的运行阶段状态, 包含 New、Running、Paused,Stopped,Finished,具体定义的代码在 dm/proto/dmworker.proto。 result:subtask 当前数据同步处理单元的运行结果,对应着 stage = Paused/Stopped/Finished 的详细信息。 主要的逻辑有: 初始化 subtask 对象实例的时候会 编排数据同步处理单元的运行先后顺序。所有的数据同步处理单元都实现了 dm/unit interface,所以接下来的运行中就不需要关心具体的数据同步处理单元的类型,可以按照统一的 interface 方法来运行数据同步处理单元,以及对其进行状态监控。 初始化各个数据同步处理单元。subtask 在运行前集中地初始化所有的数据同步处理单元,我们计划之后优化成在各个数据同步处理单元运行前再进行初始化,这样子减少资源的提前或者无效的占用。 数据同步处理单元运行状态监控。通过监控当前运行的数据同步处理单元的结果,将 subtask 的 stage 设置为 Paused/Stopped/Finished。 如果 当前的数据同步处理单元工作已经完成,则会根据 units 来 选取下一个需要运行的数据同步处理单元,如果没有需要的数据同步处理单元,那么会将 subtask 的 stage 设置为 Finished。这里有个注意点,因为 binlog replication 单元永远不会结束,所以不会进入 Finished 的状态。 如果 返回的 result 里面包含有错误信息,则会将 subtask 的 stage 设置为 Paused,并且打印具体的错误信息。 如果是用户手动暂停或者停止,则会将 subtask 的 stage 设置为 Paused/Stopped。这里有个注意点,这个时候 stage=Paused 是没有错误信息的。 数据同步处理单元之间的运行交接处理逻辑。部分数据同步处理单元在开始工作的时候需要满足一些前置条件,例如 binlog replication(sync)的运行需要等待 relay log 处理单元已经储存下来其开始同步需要的 binlog 文件,否则 subtask 将处于 stage=Paused 的暂停等待状态。 小结 本篇文章主要介绍了数据同步处理单元实现了什么功能,数据同步流程、运行逻辑,以及数据同步处理单元的 interface 设计。后续会分三篇文章详细地介绍数据同步处理单元的实现,包括: dump/load 全量同步实现 binlog replication 增量同步实现 relay log 实现 "}, {"url": "https://pingcap.com/weekly/2019-04-08-tidb-weekly/", "title": "Weekly update (April 01 ~ April 07, 2019)", "content": " Weekly update in TiDB Last week, we landed 61 PRs in the TiDB repository.Improved Improve the unit test coverage of util/set from 0% to 100% Support a fast Analyze session control variable Improve the error message for connection closing Remove the errors.Trace call in some high-level packages to improve the performance Correct the estimated row count for the inner plan of IndexJoin Replace lock operations in MemTracker with atomic operations to improve the performance Improve the unit test coverage of executor/aggfuncs from 47.7% to 70% Improve the unit test coverage of global_vars_cache from 0% to 100% Raise a warning for unmatched Join hint in some cases Improve the unit test coverage of infoschema from 65.20% to 85.2% Push down constant filters over Join properly Allow kill tidb [session id] to stop executors and release resources quickly Improve the performance of computing FieldType.Flen of int64/uint64 Make TableReader&IndexReader&IndexLookup support the chunk size control Fixed Fix the issue that Desc table is incompatible with MySQL in some cases Fix the issue that JSON_contains_path is incompatible with MySQL Fix the issue that the dayname function is incompatible with MySQL when doing arithmetic Fix wrong results of group_concat in some cases Fix the data race issue in config.TreatOldVersionUTF8AsUTF8MB4 Fix incorrect time fraction parsing method Fix wrong results of count(distinct)/sum(distinct) of decimal columns in some cases Make the Alter and Rename privileges the same with MySQL Fix the Alter table charset bug and compatibility in some cases Fix the read-only check for the Prepare/Execute statement Fix improper using of the time zone in checking default column values Weekly update in TiSpark Last week, we landed 9 PRs in the TiSpark repository.Added Support range partition pruning Fixed Fix the issue that ManagedChannel is not closed in the test Weekly update in TiKV and PD Last week, we landed 21 PRs in the TiKV and PD repositories.Added Add the execution summary framework Introduce the vectorized evaluation framework Add one more configuration option for very fast builds Make end-point-enable-batch-if-possible configurable Use static metrics for KV_COMMAND_COUNTER Enlarge the ingest buffer Fixed Fix the performance bug Improve the Region heartbeat Fix the issue that the Region scatter might transfer the leader to a removed peer New contributors (Thanks!) tidb: Debiancc tikv: myguyy seansu4you87 yaalsn pd: yaalsn parser: wuudjac "}, {"url": "https://pingcap.com/meetup/meetup-94-20190403/", "title": "【Infra Meetup No.94】TiFlash、Spark SQL", "content": " Topic 1:TiDB 与 TiFlash 扩展 - 向真 HTAP 平台前进 讲师介绍:孙若曦,PingCAP 核心开发工程师,负责 OLAP 相关产品设计和开发。曾在星环科技、NVIDIA 就职担任 Tech Lead。主要研究分布式系统、数据库等领域。 视频 | Infra Meetup No.94:TiDB 与 TiFlash 扩展 - 向真 HTAP 平台前进 PPT 链接 本次分享的内容主要包括以下三个方面: HTAP 的核心价值:能够解决当前各类数据平台上广泛存在的工具链过于复杂,运维成本高,数据实效性和一致性等问题。 HTAP 面临的技术挑战:OLTP 场景通常使用行存,而 OLAP 场景通常使用列存;另外,OLAP 任务因为对系统资源占用较多,也会严重影响 OLTP 业务。 TiFlash 是如何解决这些问题的: 使用列存及向量化计算来满足 OLAP 业务; 数据使用 Raft Learner 机制同步到列存; 拥有与 TiDB 相同的 Scalability; OLTP 与 OLAP 的物理资源完全隔离,避免互相干扰; TiDB/TiSpark 能够同时访问行存和列存副本,通过 CBO 选取最优化的访问方式; 为 TiFlash 引入 MPP 能力。 Topic 2:eBay 在 Spark SQL 的性能优化 讲师介绍:王刚,eBay 大数据工程师。2017 年硕士毕业于南京大学,后一直在 eBay 从事大数据研发工作。 视频 | Infra Meetup No.94:eBay 在 Spark SQL 的性能优化 PPT 链接 本次 Topic 主要分享了 eBay 在 Spark SQL 上所做的一系列探索与优化工作。其中包括我们在 index 上所尝试 Bloom filter index,还有我们为了解决大表与大表 join 所做的一些方案,bucket join 和 range partition。MV 是我们在 Cache 上做的探索。新的 CBO 弥补了部分现有 Spark CBO 的缺陷,提供了更强大的 cost based optimize 的能力。 PingCAP Infra Meetup作为一个基础架构领域的前沿技术公司,PingCAP 希望能为国内真正关注技术本身的 Hackers 打造一个自由分享的平台。自 2016 年 3 月 5 日开始,我们定期在周末举办 Infra Meetup,与大家深度探讨基础架构领域的前瞻性技术思考与经验,目前已在北京、上海、广州、成都、杭州等地举办。在这里,我们希望提供一个高水准的前沿技术讨论空间,让大家真正感受到自由的开源精神魅力。 "}, {"url": "https://pingcap.com/meetup/meetup-95-20190403/", "title": "【Infra Meetup No.95】Introduction of TiDB SQL Layer", "content": "在上周举办的成都 · Infra Meetup No.95 上,我司 TiDB SQL Engine 开发工程师姚珂男为大家介绍 TiDB SQL 层的技术原理,以下是视频&文字回顾,enjoy~ 讲师介绍:姚珂男,TiDB SQL Engine 开发工程师,主要工作为优化器及相关模块的维护和新功能开发,致力于提升查询计划的正确性和稳定性。 视频 | Infra Meetup No.95:Introduction of TiDB SQL Layer PPT 链接 本次分享主要介绍 TiDB SQL 层的三个组件:优化器,统计信息和执行引擎。 优化器部分主要举例介绍了逻辑优化规则和物理优化框架; 统计信息部分主要介绍直方图,CMSketch 以及使用方法; 执行引擎部分以两种 join 方式为例介绍了我们在执行引擎实现中用到的一些优化方法。 PingCAP Infra Meetup作为一个基础架构领域的前沿技术公司,PingCAP 希望能为国内真正关注技术本身的 Hackers 打造一个自由分享的平台。自 2016 年 3 月 5 日开始,我们定期在周末举办 Infra Meetup,与大家深度探讨基础架构领域的前瞻性技术思考与经验,目前已在北京、上海、广州、成都、杭州等地举办。在这里,我们希望提供一个高水准的前沿技术讨论空间,让大家真正感受到自由的开源精神魅力。 "}, {"url": "https://pingcap.com/blog-cn/tidb-opeartor-webhook/", "title": "Kubernetes 中如何保证优雅地停止 Pod", "content": " 一直以来我对优雅地停止 Pod 这件事理解得很单纯:不就利用是 PreStop hook 做优雅退出吗?但最近发现很多场景下 PreStop Hook 并不能很好地完成需求,这篇文章就简单分析一下“优雅地停止 Pod”这回事儿。何谓优雅停止? 优雅停止(Graceful shutdown)这个说法来自于操作系统,我们执行关机之后都得 OS 先完成一些清理操作,而与之相对的就是硬中止(Hard shutdown),比如拔电源。到了分布式系统中,优雅停止就不仅仅是单机上进程自己的事了,往往还要与系统中的其它组件打交道。比如说我们起一个微服务,网关把一部分流量分给我们,这时: 假如我们一声不吭直接把进程杀了,那这部分流量就无法得到正确处理,部分用户受到影响。不过还好,通常来说网关或者服务注册中心会和我们的服务保持一个心跳,过了心跳超时之后系统会自动摘除我们的服务,问题也就解决了;这是硬中止,虽然我们整个系统写得不错能够自愈,但还是会产生一些抖动甚至错误。 假如我们先告诉网关或服务注册中心我们要下线,等对方完成服务摘除操作再中止进程,那不会有任何流量受到影响;这是优雅停止,将单个组件的启停对整个系统影响最小化。 按照惯例,SIGKILL 是硬终止的信号,而 SIGTERM 是通知进程优雅退出的信号,因此很多微服务框架会监听 SIGTERM 信号,收到之后去做反注册等清理操作,实现优雅退出。PreStop Hook 回到 Kubernetes(下称 K8s),当我们想干掉一个 Pod 的时候,理想状况当然是 K8s 从对应的 Service(假如有的话)把这个 Pod 摘掉,同时给 Pod 发 SIGTERM 信号让 Pod 中的各个容器优雅退出就行了。但实际上 Pod 有可能犯各种幺蛾子: 已经卡死了,处理不了优雅退出的代码逻辑或需要很久才能处理完成。 优雅退出的逻辑有 BUG,自己死循环了。 代码写得野,根本不理会 SIGTERM。 因此,K8s 的 Pod 终止流程中还有一个“最多可以容忍的时间”,即 grace period(在 Pod 的 .spec.terminationGracePeriodSeconds 字段中定义),这个值默认是 30 秒,我们在执行 kubectl delete 的时候也可通过 --grace-period 参数显式指定一个优雅退出时间来覆盖 Pod 中的配置。而当 grace period 超出之后,K8s 就只能选择 SIGKILL 强制干掉 Pod 了。很多场景下,除了把 Pod 从 K8s 的 Service 上摘下来以及进程内部的优雅退出之外,我们还必须做一些额外的事情,比如说从 K8s 外部的服务注册中心上反注册。这时就要用到 PreStop Hook 了,K8s 目前提供了 Exec 和 HTTP 两种 PreStop Hook,实际用的时候,需要通过 Pod 的 .spec.containers[].lifecycle.preStop 字段为 Pod 中的每个容器单独配置,比如:spec: contaienrs: - name: my-awesome-container lifecycle: preStop: exec: command: ["/bin/sh","-c","/pre-stop.sh"] /pre-stop.sh 脚本里就可以写我们自己的清理逻辑。最后我们串起来再整个表述一下 Pod 退出的流程(官方文档里更严谨哦): 用户删除 Pod。 2.1. Pod 进入 Terminating 状态。 2.2. 与此同时,K8s 会将 Pod 从对应的 service 上摘除。 2.3. 与此同时,针对有 PreStop Hook 的容器,kubelet 会调用每个容器的 PreStop Hook,假如 PreStop Hook 的运行时间超出了 grace period,kubelet 会发送 SIGTERM 并再等 2 秒。 2.4. 与此同时,针对没有 PreStop Hook 的容器,kubelet 发送 SIGTERM。 grace period 超出之后,kubelet 发送 SIGKILL 干掉尚未退出的容器。 这个过程很不错,但它存在一个问题就是我们无法预测 Pod 会在多久之内完成优雅退出,也无法优雅地应对“优雅退出”失败的情况。而在我们的产品 TiDB Operator 中,这就是一个无法接受的事情。有状态分布式应用的挑战 为什么说无法接受这个流程呢?其实这个流程对无状态应用来说通常是 OK 的,但下面这个场景就稍微复杂一点:TiDB 中有一个核心的分布式 KV 存储层 TiKV。TiKV 内部基于 Multi-Raft 做一致性存储,这个架构比较复杂,这里我们可以简化描述为一主多从的架构,Leader 写入,Follower 同步。而我们的场景是要对 TiKV 做计划性的运维操作,比如滚动升级,迁移节点。在这个场景下,尽管系统可以接受小于半数的节点宕机,但对于预期性的停机,我们要尽量做到优雅停止。这是因为数据库场景本身就是非常严苛的,基本上都处于整个架构的核心部分,因此我们要把抖动做到越小越好。要做到这点,就得做不少清理工作,比如说我们要在停机前将当前节点上的 Leader 全部迁移到其它节点上。得益于系统的良好设计,大多数时候这类操作都很快,然而分布式系统中异常是家常便饭,优雅退出耗时过长甚至失败的场景是我们必须要考虑的。假如类似的事情发生了,为了业务稳定和数据安全,我们就不能强制关闭 Pod,而应该停止操作过程,通知工程师介入。 这时,上面所说的 Pod 退出流程就不再适用了。小心翼翼:手动控制所有流程 这个问题其实 K8s 本身没有开箱即用的解决方案,于是我们在自己的 Controller 中(TiDB 对象本身就是一个 CRD)与非常细致地控制了各种操作场景下的服务启停逻辑。抛开细节不谈,最后的大致逻辑是在每次停服务前,由 Controller 通知集群进行节点下线前的各种迁移操作,操作完成后,才真正下线节点,并进行下一个节点的操作。而假如集群无法正常完成迁移等操作或耗时过久,我们也能“守住底线”,不会强行把节点干掉,这就保证了诸如滚动升级,节点迁移之类操作的安全性。但这种办法存在一个问题就是实现起来比较复杂,我们需要自己实现一个控制器,在其中实现细粒度的控制逻辑并且在 Controller 的控制循环中不断去检查能否安全停止 Pod。另辟蹊径:解耦 Pod 删除的控制流 复杂的逻辑总是没有简单的逻辑好维护,同时写 CRD 和 Controller 的开发量也不小,能不能有一种更简洁,更通用的逻辑,能实现“保证优雅关闭(否则不关闭)”的需求呢?有,办法就是 ValidatingAdmissionWebhook。这里先介绍一点点背景知识,Kubernetes 的 apiserver 一开始就有 AdmissionController 的设计,这个设计和各类 Web 框架中的 Filter 或 Middleware 很像,就是一个插件化的责任链,责任链中的每个插件针对 apiserver 收到的请求做一些操作或校验。举两个插件的例子: DefaultStorageClass,为没有声明 storageClass 的 PVC 自动设置 storageClass。 ResourceQuota,校验 Pod 的资源使用是否超出了对应 Namespace 的 Quota。 虽然说这是插件化的,但在 1.7 之前,所有的 plugin 都需要写到 apiserver 的代码中一起编译,很不灵活。而在 1.7 中 K8s 就引入了 Dynamic Admission Control 机制,允许用户向 apiserver 注册 webhook,而 apiserver 则通过 webhook 调用外部 server 来实现 filter 逻辑。1.9 中,这个特性进一步做了优化,把 webhook 分成了两类: MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook,顾名思义,前者就是操作 api 对象的,比如上文例子中的 DefaultStroageClass,而后者是校验 api 对象的,比如 ResourceQuota。拆分之后,apiserver 就能保证在校验(Validating)之前先做完所有的修改(Mutating),下面这个示意图非常清晰:而我们的办法就是,利用 ValidatingAdmissionWebhook,在重要的 Pod 收到删除请求时,先在 webhook server 上请求集群进行下线前的清理和准备工作,并直接返回拒绝。这时候重点来了,Control Loop 为了达到目标状态(比如说升级到新版本),会不断地进行 reconcile,尝试删除 Pod,而我们的 webhook 则会不断拒绝,除非集群已经完成了所有的清理和准备工作。下面是这个流程的分步描述: 用户更新资源对象。 controller-manager watch 到对象变更。 controller-manager 开始同步对象状态,尝试删除第一个 Pod。 apiserver 调用外部 webhook。 webhook server 请求集群做 tikv-1 节点下线前的准备工作(这个请求是幂等的),并查询准备工作是否完成,假如准备完成,允许删除,假如没有完成,则拒绝,整个流程会因为 controller manager 的控制循环回到第 2 步。 好像一下子所有东西都清晰了,这个 webhook 的逻辑很清晰,就是要保证所有相关的 Pod 删除操作都要先完成优雅退出前的准备,完全不用关心外部的控制循环是怎么跑的,也因此它非常容易编写和测试,非常优雅地满足了我们“保证优雅关闭(否则不关闭)”的需求,目前我们正在考虑用这种方式替换线上的旧方案。后记 其实 Dynamic Admission Control 的应用很广,比如 Istio 就是用 MutatingAdmissionWebhook 来实现 envoy 容器的注入的。从上面的例子中我们也可以看到它的扩展能力很强,而且常常能站在一个正交的视角上,非常干净地解决问题,与其它逻辑做到很好的解耦。当然了,Kubernetes 中还有 非常多的扩展点,从 kubectl 到 apiserver,scheduler,kubelet(device plugin,flexvolume),自定义 Controller 再到集群层面的网络(CNI),存储(CSI)可以说是处处可以做事情。以前做一些常规的微服务部署对这些并不熟悉也没用过,而现在面对 TiDB 这样复杂的分布式系统,尤其在 Kubernetes 对有状态应用和本地存储的支持还不够好的情况下,得在每一个扩展点上去悉心考量,做起来非常有意思,因此后续可能还有一些 TiDB Operator 中思考过的解决方案分享。"}, {"url": "https://pingcap.com/weekly/2019-04-01-tidb-weekly/", "title": "Weekly update (March 25 ~ March 31, 2019)", "content": " Weekly update in TiDB Last week, we landed 62 PRs in the TiDB repository.Added Introduce a new executor to support SQL statements like GRANT roles TO users Improved Refine the ErrEntryTooLarge error message Use the unified log format for the server package and the remaining packages Make tidb_disable_txn_auto_retry disable retry only for the write conflict Enhance the UTF-8 charset upgrade compatibility Add the HTTP status host Improve NULL count estimation for single-column indexes Add the audit plugin extension point Fixed Fix the wrong result of group_concat for cases like select group_concat(123, null) Fix the wrong result of count(distinct) when the parameter of count(distinct) is decimal Fix the issue that converting decimal to datetime or timestamp is incompatible with MySQL in some cases Fix the issue that plugin errors make TiDB exit in some cases Fix the issue that results of unix_timestamp()-unix_timestamp(now()) are wrong and not stable Fix the issue that date_add and date_sub are incompatible with Mysql in some cases Fix the issue that an invalid YEAR string is incompatible with MySQL Fix the issue that TIME_FORMAT is incompatible with MySQL Fix a wrong limit of MaxInt64 Fix a panic caused by a shallow copy of JSON Fix the issue that set time_zone is incompatible with MySQL Fix bugs when a window function meets Prepare in some cases Fix the issue that date_add and date_sub are incompatible with MySQL Set the correct return field type of the CONVERT() built-in function Weekly update in TiSpark Last week, we landed 10 PRs in the TiSpark repository.Added Support ShowColumnsCommand Improved Clean up the TiDAGRequest code Weekly update in TiKV and PD Last week, we landed 21 PRs in the TiKV and PD repositories.Added Add the batch index scan executor Add an API to clear tombstone stores Implement the is_ipv4_compat and is_ipv4_mapped coprocessor built-in functions Fixed Fix a Region statistics bug caused by a typo Fix a store statistics bug caused by a typo Fix a bug of GetHotStores Fix a log truncation issue during the exiting process New contributors (Thanks!) tidb: Debiancc tikv: myguyy seansu4you87 xiaobogaga yaalsn pd: bradyjoestar parser: wuudjac "}, {"url": "https://pingcap.com/blog-cn/tikv-source-code-reading-5/", "title": "TiKV 源码解析系列文章(五)fail-rs 介绍", "content": " 本文为 TiKV 源码解析系列的第五篇,为大家介绍 TiKV 在测试中使用的周边库 fail-rs。fail-rs 的设计启发于 FreeBSD 的 failpoints,由 Rust 实现。通过代码或者环境变量,其允许程序在特定的地方动态地注入错误或者其他行为。在 TiKV 中通常在测试中使用 fail point 来构建异常的情况,是一个非常方便的测试工具。Fail point 需求 在我们的集成测试中,都是简单的构建一个 KV 实例,然后发送请求,检查返回值和状态的改变。这样的测试可以较为完整地测试功能,但是对于一些需要精细化控制的测试就鞭长莫及了。我们当然可以通过 mock 网络层提供网络的精细模拟控制,但是对于诸如磁盘 IO、系统调度等方面的控制就没办法做到了。同时,在分布式系统中时序的关系是非常关键的,可能两个操作的执行顺行相反,就导致了迥然不同的结果。尤其对于数据库来说,保证数据的一致性是至关重要的,因此需要去做一些相关的测试。基于以上原因,我们就需要使用 fail point 来复现一些 corner case,比如模拟数据落盘特别慢、raftstore 繁忙、特殊的操作处理顺序、错误 panic 等等。基本用法 示例 在详细介绍之前,先举一个简单的例子给大家一个直观的认识。还是那个老生常谈的 Hello World:#[macro_use] extern crate fail; fn say_hello() { fail_point!(“before_print”); println!(“Hello World~”); } fn main() { say_hello(); fail::cfg("before_print", "panic"); say_hello(); } 运行结果如下:Hello World~ thread 'main' panicked at 'failpoint before_print panic' ... 可以看到最终只打印出一个 Hello World~,而在打印第二个之前就 panic 了。这是因为我们在第一次打印完后才指定了这个 fail point 行为是 panic,因此第一次在 fail point 不做任何事情之后正常输出,而第二次在执行到 fail point 时就会根据配置的行为 panic 掉!Fail point 行为 当然 fail point 不仅仅能注入 panic,还可以是其他的操作,并且可以按照一定的概率出现。描述行为的格式如下:[<pct>%][<cnt>*]<type>[(args...)][-><more terms>] pct:行为被执行时有百分之 pct 的机率触发 cnt:行为总共能被触发的次数 type:行为类型 off:不做任何事 return(arg):提前返回,需要 fail point 定义时指定 expr,arg 会作为字符串传给 expr 计算返回值 sleep(arg):使当前线程睡眠 arg 毫秒 panic(arg):使当前线程崩溃,崩溃消息为 arg print(arg):打印出 arg pause:暂停当前线程,直到该 fail point 设置为其他行为为止 yield:使当前线程放弃剩余时间片 delay(arg):和 sleep 类似,但是让 CPU 空转 arg 毫秒 args:行为的参数 比如我们想在 before_print 处先 sleep 1s 然后有 1% 的机率 panic,那么就可以这么写:"sleep(1000)->1%panic" 定义 fail point 只需要使用宏 fail_point! 就可以在相应代码中提前定义好 fail point,而具体的行为在之后动态注入。fail_point!("failpoint_name"); fail_point!("failpoint_name", |_| { // 指定生成自定义返回值的闭包,只有当 fail point 的行为为 return 时,才会调用该闭包并返回结果 return Error }); fail_point!("failpoint_name", a == b, |_| { // 当满足条件时,fail point 才被触发 return Error }) 动态注入 环境变量 通过设置环境变量指定相应 fail point 的行为:FAILPOINTS="<failpoint_name1>=<action>;<failpoint_name2>=<action>;..." 注意,在实际运行的代码需要先使用 fail::setup() 以环境变量去设置相应 fail point,否则 FAILPOINTS 并不会起作用。#[macro_use] extern crate fail; fn main() { fail::setup(); // 初始化 fail point 设置 do_fallible_work(); fail::teardown(); // 清除所有 fail point 设置,并且恢复所有被 fail point 暂停的线程 } 代码控制 不同于环境变量方式,代码控制更加灵活,可以在程序中根据情况动态调整 fail point 的行为。这种方式主要应用于集成测试,以此可以很轻松地构建出各种异常情况。fail::cfg("failpoint_name", "actions"); // 设置相应的 fail point 的行为 fail::remove("failpoint_name"); // 解除相应的 fail point 的行为 内部实现 以下我们将以 fail-rs v0.2.1 版本代码为基础,从 API 出发来看看其背后的具体实现。fail-rs 的实现非常简单,总的来说,就是内部维护了一个全局 map,其保存着相应 fail point 所对应的行为。当程序执行到某个 fail point 时,获取并执行该全局 map 中所保存的相应的行为。全局 map 其具体定义在 FailPointRegistry。struct FailPointRegistry { registry: RwLock<HashMap<String, Arc<FailPoint>>>, } 其中 FailPoint 的定义如下:struct FailPoint { pause: Mutex<bool>, pause_notifier: Condvar, actions: RwLock<Vec<Action>>, actions_str: RwLock<String>, } pause 和 pause_notifier 是用于实现线程的暂停和恢复,感兴趣的同学可以去看看代码,太过细节在此不展开了;actions_str 保存着描述行为的字符串,用于输出;而 actions 就是保存着 failpoint 的行为,包括概率、次数、以及具体行为。Action 实现了 FromStr 的 trait,可以将满足格式要求的字符串转换成 Action。这样各个 API 的操作也就显而易见了,实际上就是对于这个全局 map 的增删查改: fail::setup() 读取环境变量 FAILPOINTS 的值,以 ; 分割,解析出多个 failpoint name 和相应的 actions 并保存在 registry 中。 fail::teardown() 设置 registry 中所有 fail point 对应的 actions 为空。 fail::cfg(name, actions) 将 name 和对应解析出的 actions 保存在 registry 中。 fail::remove(name) 设置 registry 中 name 对应的 actions 为空。 而代码到执行到 fail point 的时候到底发生了什么呢,我们可以展开 fail_point! 宏定义看一下:macro_rules! fail_point { ($name:expr) => {{ $crate::eval($name, |_| { panic!("Return is not supported for the fail point "{}"", $name); }); }}; ($name:expr, $e:expr) => {{ if let Some(res) = $crate::eval($name, $e) { return res; } }}; ($name:expr, $cond:expr, $e:expr) => {{ if $cond { fail_point!($name, $e); } }}; } 现在一切都变得豁然开朗了,实际上就是对于 eval 函数的调用,当函数返回值为 Some 时则提前返回。而 eval 就是从全局 map 中获取相应的行为,在 p.eval(name) 中执行相应的动作,比如输出、等待亦或者 panic。而对于 return 行为的情况会特殊一些,在 p.eval(name) 中并不做实际的动作,而是返回 Some(arg) 并通过 .map(f) 传参给闭包产生自定义的返回值。pub fn eval<R, F: FnOnce(Option<String>) -> R>(name: &str, f: F) -> Option<R> { let p = { let registry = REGISTRY.registry.read().unwrap(); match registry.get(name) { None => return None, Some(p) => p.clone(), } }; p.eval(name).map(f) } 小结 至此,关于 fail-rs 背后的秘密也就清清楚楚了。关于在 TiKV 中使用 fail point 的测试详见 github.com/tikv/tikv/tree/master/tests/failpoints,大家感兴趣可以看看在 TiKV 中是如何来构建异常情况的。同时,fail-rs 计划支持 HTTP API,欢迎感兴趣的小伙伴提交 PR。"}, {"url": "https://pingcap.com/blog/introduction-to-analytics-queries-for-the-mysql-dba/", "title": "Introduction to Analytics Queries for the MySQL DBA", "content": " If you come to TiDB, an open source NewSQL database, from a MySQL background, you are likely comfortable with transaction processing workloads, but analytics may be new to you.I know in my case, as someone who’s spent more than 15 years in the MySQL world, while I can explain how redo log flushing works in InnoDB, only until recently did it become natural for me to write a simple query with a window function.So in this post, I want to go through some simple use cases where a MySQL DBA can expand their repertoire and answer some basic business questions by writing SQL queries with window functions. This knowledge could add a lot of value, especially if you work at a company that doesn’t yet have a full-fledged data science team, but is already doing some analytics.Our sample data set will be the 30 years of USA flight on-time statistics loaded into TiDB 3.0 BETA.Finding the average arrival delay Our sample data set includes all the flights in the U.S. and their on-time performance. But for your purposes, it could equally be the initial wait time a customer experiences before receiving customer service per location that you operate, or the average spend per customer per sales person.One of the most common types of queries you will need to do is to show the average for one group, and compare that to the previous year. Here is the query to do that:SET tidb_enable_window_function = 1; SELECT year, UniqueCarrier, COUNT(*) AS Flights, AVG(ArrDelay) AS avgArrDelay, lag(AVG(ArrDelay), 1) OVER (PARTITION BY UniqueCarrier ORDER BY year) AS prevAvgArrDelay, lag(AVG(ArrDelay), 1) OVER (PARTITION BY UniqueCarrier ORDER BY year)-AVG(ArrDelay) AS improvement FROM ontime WHERE UniqueCarrier IN ('AA', 'UA', 'DL') GROUP BY UniqueCarrier, year ORDER BY year, UniqueCarrier; +------+---------------+---------+-------------+-----------------+-------------+ | year | UniqueCarrier | Flights | avgArrDelay | prevAvgArrDelay | improvement | +------+---------------+---------+-------------+-----------------+-------------+ | 1987 | AA | 165121 | 4.8399 | NULL | NULL | | 1987 | DL | 185813 | 11.7843 | NULL | NULL | | 1987 | UA | 152624 | 8.5594 | NULL | NULL | | 1988 | AA | 694757 | 4.1720 | 4.8399 | 0.6679 | | 1988 | DL | 753983 | 6.1164 | 11.7843 | 5.6679 | | 1988 | UA | 587144 | 7.2269 | 8.5594 | 1.3325 | | 1989 | AA | 723252 | 6.0706 | 4.1720 | -1.8986 | | 1989 | DL | 783320 | 8.3806 | 6.1164 | -2.2642 | | 1989 | UA | 574674 | 11.0119 | 7.2269 | -3.7850 | | 1990 | AA | 712060 | 6.4061 | 6.0706 | -0.3355 | | 1990 | DL | 824062 | 9.3586 | 8.3806 | -0.9780 | | 1990 | UA | 606713 | 7.3276 | 11.0119 | 3.6843 | | 1991 | AA | 725191 | 3.8323 | 6.4061 | 2.5738 | | 1991 | DL | 874791 | 6.6681 | 9.3586 | 2.6905 | | 1991 | UA | 630093 | 6.5496 | 7.3276 | 0.7780 | | 1992 | AA | 782371 | 5.1185 | 3.8323 | -1.2862 | | 1992 | DL | 916593 | 7.4197 | 6.6681 | -0.7516 | | 1992 | UA | 639349 | 4.8295 | 6.5496 | 1.7201 | | 1993 | AA | 786696 | 5.9946 | 5.1185 | -0.8761 | | 1993 | DL | 898896 | 8.5415 | 7.4197 | -1.1218 | | 1993 | UA | 649086 | 6.7061 | 4.8295 | -1.8766 | | 1994 | AA | 722277 | 5.6174 | 5.9946 | 0.3772 | | 1994 | DL | 874526 | 6.4898 | 8.5415 | 2.0517 | | 1994 | UA | 638750 | 5.0592 | 6.7061 | 1.6469 | | 1995 | AA | 688471 | 7.1322 | 5.6174 | -1.5148 | | 1995 | DL | 884019 | 8.1233 | 6.4898 | -1.6335 | | 1995 | UA | 724807 | 7.0768 | 5.0592 | -2.0176 | | 1996 | AA | 655539 | 10.6212 | 7.1322 | -3.4890 | | 1996 | DL | 888306 | 11.2632 | 8.1233 | -3.1399 | | 1996 | UA | 735266 | 9.9883 | 7.0768 | -2.9115 | | 1997 | AA | 663954 | 4.7461 | 10.6212 | 5.8751 | | 1997 | DL | 921850 | 9.3616 | 11.2632 | 1.9016 | | 1997 | UA | 743847 | 8.4969 | 9.9883 | 1.4914 | | 1998 | AA | 653919 | 4.2881 | 4.7461 | 0.4580 | | 1998 | DL | 915095 | 5.6418 | 9.3616 | 3.7198 | | 1998 | UA | 748459 | 10.6677 | 8.4969 | -2.1708 | | 1999 | AA | 692653 | 8.5167 | 4.2881 | -4.2286 | | 1999 | DL | 914130 | 6.5076 | 5.6418 | -0.8658 | | 1999 | UA | 774370 | 9.5113 | 10.6677 | 1.1564 | | 2000 | AA | 742265 | 9.3036 | 8.5167 | -0.7869 | | 2000 | DL | 908029 | 7.9747 | 6.5076 | -1.4671 | | 2000 | UA | 776559 | 17.3881 | 9.5113 | -7.8768 | | 2001 | AA | 716985 | 5.5074 | 9.3036 | 3.7962 | | 2001 | DL | 835236 | 4.7777 | 7.9747 | 3.1970 | | 2001 | UA | 704977 | 8.1830 | 17.3881 | 9.2051 | | 2002 | AA | 852439 | 1.1673 | 5.5074 | 4.3401 | | 2002 | DL | 728758 | 5.6401 | 4.7777 | -0.8624 | | 2002 | UA | 587887 | 2.4105 | 8.1830 | 5.7725 | | 2003 | AA | 752241 | 3.1490 | 1.1673 | -1.9817 | | 2003 | DL | 660617 | 3.8085 | 5.6401 | 1.8316 | | 2003 | UA | 543957 | 3.0751 | 2.4105 | -0.6646 | | 2004 | AA | 698548 | 7.9799 | 3.1490 | -4.8309 | | 2004 | DL | 687638 | 8.0693 | 3.8085 | -4.2608 | | 2004 | UA | 555812 | 5.8948 | 3.0751 | -2.8197 | | 2005 | AA | 673569 | 8.1702 | 7.9799 | -0.1903 | | 2005 | DL | 658302 | 7.6846 | 8.0693 | 0.3847 | | 2005 | UA | 485918 | 7.4487 | 5.8948 | -1.5539 | | 2006 | AA | 643597 | 9.4219 | 8.1702 | -1.2517 | | 2006 | DL | 506086 | 6.8902 | 7.6846 | 0.7944 | | 2006 | UA | 500008 | 10.2646 | 7.4487 | -2.8159 | | 2007 | AA | 633857 | 13.9857 | 9.4219 | -4.5638 | | 2007 | DL | 475889 | 7.1869 | 6.8902 | -0.2967 | | 2007 | UA | 490002 | 12.4171 | 10.2646 | -2.1525 | | 2008 | AA | 604885 | 12.2029 | 13.9857 | 1.7828 | | 2008 | DL | 451931 | 7.7162 | 7.1869 | -0.5293 | | 2008 | UA | 449515 | 11.0016 | 12.4171 | 1.4155 | | 2009 | AA | 551597 | 6.2162 | 12.2029 | 5.9867 | | 2009 | DL | 428007 | 4.7114 | 7.7162 | 3.0048 | | 2009 | UA | 377049 | 1.3351 | 11.0016 | 9.6665 | | 2010 | AA | 540963 | 3.7431 | 6.2162 | 2.4731 | | 2010 | DL | 732973 | 5.3897 | 4.7114 | -0.6783 | | 2010 | UA | 343081 | -3.9465 | 1.3351 | 5.2816 | | 2011 | AA | 538179 | 5.1049 | 3.7431 | -1.3618 | | 2011 | DL | 732331 | 1.7066 | 5.3897 | 3.6831 | | 2011 | UA | 311212 | 2.6463 | -3.9465 | -6.5928 | | 2012 | AA | 525220 | 6.2599 | 5.1049 | -1.1550 | | 2012 | DL | 726879 | -0.7959 | 1.7066 | 2.5025 | | 2012 | UA | 531245 | 5.0069 | 2.6463 | -2.3606 | | 2013 | AA | 537891 | 5.7763 | 6.2599 | 0.4836 | | 2013 | DL | 754670 | 1.9709 | -0.7959 | -2.7668 | | 2013 | UA | 505798 | 3.7126 | 5.0069 | 1.2943 | | 2014 | AA | 537697 | 8.1398 | 5.7763 | -2.3635 | | 2014 | DL | 800375 | 2.2953 | 1.9709 | -0.3244 | | 2014 | UA | 493528 | 6.8186 | 3.7126 | -3.1060 | | 2015 | AA | 725984 | 3.3893 | 8.1398 | 4.7505 | | 2015 | DL | 875881 | 0.1856 | 2.2953 | 2.1097 | | 2015 | UA | 515723 | 5.3477 | 6.8186 | 1.4709 | | 2016 | AA | 914495 | 5.0123 | 3.3893 | -1.6230 | | 2016 | DL | 922746 | -0.5527 | 0.1856 | 0.7383 | | 2016 | UA | 545067 | 1.7335 | 5.3477 | 3.6142 | | 2017 | AA | 896348 | 3.7901 | 5.0123 | 1.2222 | | 2017 | DL | 923560 | -0.0739 | -0.5527 | -0.4788 | | 2017 | UA | 584481 | 1.7156 | 1.7335 | 0.0179 | | 2018 | AA | 766133 | 5.5236 | 3.7901 | -1.7335 | | 2018 | DL | 798041 | 0.1130 | -0.0739 | -0.1869 | | 2018 | UA | 518243 | 5.5611 | 1.7156 | -3.8455 | +------+---------------+---------+-------------+-----------------+-------------+ 96 rows in set (1 min 25.95 sec) Note: I have reduced the results to UA (United Airlines), DL (Delta) and AA (American) for readability.So in this query we are using a window function named LAG() to find the previous row. The window is defined as (PARTITION BY UniqueCarrier ORDER BY year).You can think of window functions like a GROUP BY, but the rows don’t collapse. The name literally comes from peeking through a window to observe other rows in the result.Sharpening our statistics skills In the previous example, we used the average or statistical mean. Let’s now discuss why that might not be the best approach.The mean sums up all of the values, and then divides the total by the number of values. By design each value contributes to the final mean. So take the following examples:# Series A 97.5 98.1 98.5 98.8 99.2 Average=98.42# Series B 97.5 98.2 104.0 97.7 98.1 Average=99.1 The average between these two series is not that much different, but you will notice …"}, {"url": "https://pingcap.com/meetup/meetup-92-20190327/", "title": "【Infra Meetup No.92】Introduction to TiDB Statistics", "content": "在上周六北京举办的 Infra Meetup No.92 上,我司 TiDB 开发工程师谢海滨老师为大家介绍了 TiDB 中统计信息的原理及细节。以下是视频 & 文字回顾,enjoy! 讲师介绍:谢海滨,TiDB 研发工程师,目前主要负责统计信息以及优化器相关工作。 视频 | Infra Meetup No.92:Introduction to TiDB Statistics PPT 链接 本次分享首先介绍了统计信息的作用以及 TiDB 统计信息的基本组成部分,接下来围绕着统计信息的估算、收集以及更新 3 个部分具体展开: 在估算方面,介绍了直方图和 Count-Min Sketch 的适用场景以及估算方法,以及 TiDB 是如何利用索引的统计信息来减少多列估算时的独立性假设。 在收集方面,介绍了 analyze 语句的具体流程以及相关参数,以及 auto analyze 的触发条件。 在更新方面,介绍了 TiDB 是如何更新 row count 和 modify count,以及是如何利用查询结果更新直方图和 Count-Min Sketch 的。 PingCAP Infra Meetup作为一个基础架构领域的前沿技术公司,PingCAP 希望能为国内真正关注技术本身的 Hackers 打造一个自由分享的平台。自 2016 年 3 月 5 日开始,我们定期在周末举办 Infra Meetup,与大家深度探讨基础架构领域的前瞻性技术思考与经验,目前已在北京、上海、广州、成都、杭州等地举办。在这里,我们希望提供一个高水准的前沿技术讨论空间,让大家真正感受到自由的开源精神魅力。 "}, {"url": "https://pingcap.com/meetup/meetup-93-20190327/", "title": "【Infra Meetup No.93】A Study of LSM-Tree", "content": "在上周六广州举办的 Infra Meetup No.93 上,来自微信的林金河老师为大家分享了 LSM-Tree 相关知识。以下为视频&文字回顾,enjoy~ 讲师介绍:林金河,目前在微信从事分布式存储相关的工作 视频 | Infra Meetup No.93:A Study of LSM-Tree PPT 链接 本次分享的主要内容包括: LSM-Tree 的基本原理,包括 LSM-Tree 的文件组织结构、Point Query、Range Query 和 Compaction。 LSM-Tree 存在的问题和相关的优化方法。主要有两方面: 读放大。目前的优化思路是通过 filter 来减少不必要的 I/O,比如 bloom filter、SuRF。 Compaction 造成的负载抖动和写放大。一方面,可以通过软硬件结合的方式,将 compaction 的任务交给专门的 coprocessor 来做,将 compaction 带来的负面影响尽可能隔离开。另一方面,就是从数据结构和算法上,尽可能减少写放大,比如 PebbleDB 和 WiscKey。 最后简单总结了一下:LSM-Tree 的优化,基本都是在读放大、写放大和空间放大这三者间做 trade-off。理论上没法同时让这三者达到最优(有点像分布式系统的 CAP 定理)。 PingCAP Infra Meetup作为一个基础架构领域的前沿技术公司,PingCAP 希望能为国内真正关注技术本身的 Hackers 打造一个自由分享的平台。自 2016 年 3 月 5 日开始,我们定期在周末举办 Infra Meetup,与大家深度探讨基础架构领域的前瞻性技术思考与经验,目前已在北京、上海、广州、成都、杭州等地举办。在这里,我们希望提供一个高水准的前沿技术讨论空间,让大家真正感受到自由的开源精神魅力。 "}, {"url": "https://pingcap.com/blog-cn/what-is-new-in-tidb-3.0.0-beta.1/", "title": "What’s New in TiDB 3.0.0 Beta.1", "content": " 今年 1 月份,我们发布了 TiDB 3.0.0 Beta 版本,DevCon 上也对这个版本做了介绍,经过两个月的努力,今天推出了下一个 Beta 版本 3.0.0 Beta.1。让我们看一下这个版本相比于之前有什么改进。新增特性解读 Skyline Pruning 查询计划正确性和稳定性对于关系型数据库来说至关重要,3.0.0 Beta.1 对这部分进行了优化,引入一个叫 Skyline Pruning 的框架,通过一些启发式规则来更快更准确地找到最好的查询计划。详细信息可以参考 这篇设计文档。日志格式统一 日志是排查程序问题的重要工具,统一且结构化的日志格式不但有利于用户理解日志内容,也有助于通过工具对日志进行定量分析。3.0.0 Beta.1 版本中对 tidb/pd/tikv 这三个组件的日志格式进行了统一,详细格式参见 这篇文档。慢查询相关改进 慢查询日志是常用于排查性能问题, 在 3.0.0 Beta.1 之前慢查询日志跟其他日志混合存储在同个日志文件,并且格式为自定义的格式,不支持使用 SQL 语句或工具对其进行分析,严重影响排查问题的效率。从3.0.0 Beta.1 版本开始 TiDB 将查询日志文件输出到单独的日志文件中(默认日志文件名为 tidb-slow.log),用户可以系统变量或配置文件进行修改,同时兼容 MySQL 慢查询日志格式,支持使用 MySQL 生态分析工具(如 pt-query-digest)对慢查询日志进行分析。除了慢查询日志之外,还增加一个虚拟表 INFORMATION_SCHEMA.SLOW_QUERY,可以对慢查询日志进行展示和过滤。关于如何处理慢查询,我们后续还会专门写一篇文档进行介绍。如果你有一些好用的慢查询处理工具,也欢迎和我们进行交流。Window Function MySQL 所支持的 Window Function TiDB 3.0.0 Beta.1 版本已经全都支持,这为 TiDB 向 MySQL 8 兼容迈出了一大步。想体验功能的可以下载版本尝鲜,但是不建议在生产中使用,这项功能还需要大量的测试,欢迎大家测试并反馈问题。热点调度策略可配置化 热点调度是保持集群负载均衡的重要手段,但是一些场景下默认的热点调度显得不那么智能,甚至会对集群负载造成影响,所以 3.0.0 Beta.1 中增加了对负载均衡策略的人工干预方法,可以临时调整调度策略。优化 Coprocessor 计算执行框架 目前已经完成 TableScan 算子,单 TableScan 即扫表性能提升 5% ~ 30%,接下来会对 IndexScan、Filter、Aggregation 等算子以及表达式计算框架进行优化。TiDB Lightning 性能优化 Lightning 是将大量数据导入 TiDB 的最佳方式,在特定表结构,单表数量,集群已有数量等条件下 1TB 数据导入性能提升 1 倍,时间从 6 小时降低到 3 小时以内,性能优化的脚步不会停,我们期望进一步提升性能,降低时间,期望能优化到 2 小时以内。易用性相关的特性 使用 /debug/zip HTTP 接口, 可以方便地一键获取当前 TiDB 实例的信息,便于诊断问题。 新增通过 SQL 语句方式管理 pump/drainer 状态,简化 pump/drainer 状态管理,当前仅支持查看状态。 支持通过配置文件管理发送 binlog 策略, 丰富 binlog 管理方式。 更多的改进可以参见 Release Notes,除了这些已经完成的特性之外,还有一些正在做的事情,比如 RBAC、Plan Management 都在密集开发中,希望在下一个 Beta 版本或者 RC 版本中能与大家见面。开源社区 在这个版本的开发过程中,社区依然给我们很有力的支持,比如潘迪同学一直在负责 View 的完善和测试,美团的同学在推进 Plan Management,一些社区同学参与了 TiDB 性能改进 活动。在这里对各位贡献者表示由衷的感谢。接下来我们会开展更多的专项开发活动以及一系列面向社区的培训课程,希望能对大家了解如何做分布式数据库有帮助。 One More ThingTiDB DevCon 2019 上对外展示的全新分析类产品 TiFlash 已经完成 Alpha 版本的开发,目前已经在进行内部测试,昨天试用了一下之后,我想说“真香”。 "}, {"url": "https://pingcap.com/blog-cn/dm-source-code-reading-2/", "title": "DM 源码阅读系列文章(二)整体架构介绍", "content": " 本文为 DM 源码阅读系列文章的第二篇,第一篇文章 简单介绍了 DM 源码阅读的目的和规划,以及 DM 的源码结构以及工具链。从本篇文章开始,我们会正式开始阅读 DM 的源码。本篇文章主要介绍 DM 的整体架构,包括 DM 有哪些组件、各组件分别实现什么功能、组件之间交互的数据模型和 RPC 实现。整体架构 通过上面的 DM 架构图,我们可以看出,除上下游数据库及 Prometheus 监控组件外,DM 自身有 DM-master、DM-worker 及 dmctl 这 3 个组件。其中,DM-master 负责管理和调度数据同步任务的各项操作,DM-worker 负责执行具体的数据同步任务,dmctl 提供用于管理 DM 集群与数据同步任务的各项命令。DM-master DM-master 的入口代码在 cmd/dm-master/main.go,其中主要操作包括: 调用 cfg.Parse 解析命令行参数与参数配置文件 调用 log.SetLevelByString 设置进程的 log 输出级别 调用 signal.Notify 注册系统 signal 通知,用于接受到指定信号时退出进程等 调用 server.Start 启动 RPC server,用于响应来自 dmctl 与 DM-worker 的请求 在上面的操作中,可以看出其中最关键的是步骤 4,其对应的实现代码在 dm/master/server.go 中,其核心为 Server 这个 struct,其中的主要 fields 包括: rootLis, svr:监听网络连接,分发 RPC 请求给对应的 handler。 workerClients:维护集群各 DM-worker ID 到对应的 RPC client 的映射关系。 taskWorkers:维护用于执行各同步(子)任务的 DM-worker ID 列表。 lockKeeper:管理在协调处理 sharding DDL 时的 lock 信息。 sqlOperatorHolder:管理手动 skip/replace 指定 sharding DDL 时的 SQL operator 信息。 在本篇文章中,我们暂时不会关注 lockKeeper 与 sqlOperatorHolder,其具体的功能与代码实现会在后续相关文章中进行介绍。在 DM-master Server 的入口方法 Start 中: 通过 net.Listen 初始化 rootLis 并用于监听 TCP 连接(借助 soheilhy/cmux,我们在同一个 port 同时提供 gRPC 与 HTTP 服务)。 根据读取的配置信息(DeployMap),初始化用于连接到各 DM-worker 的 RPC client 并保存在 workerClients 中。 通过 pb.RegisterMasterServer 注册 gRPC server(svr),并将该 Server 作为各 services 的 implementation。 调用 m.Serve 开始提供服务。 DM-master 提供的 RPC 服务包括 DM 集群管理、同步任务管理等,对应的 service 以 Protocol Buffers 格式定义在 dm/proto/dmmaster.proto 中,对应的 generated 代码在 dm/pb/dmmaster.pb.go 中。各 service 的具体实现在 dm/master/server.go 中(*Server)。DM-worker DM-worker 的结构与 DM-master 类似,其入口代码在 cmd/dm-worker/main.go 中。各 RPC services 的 Protocol Buffers 格式定义在 dm/proto/dmworker.proto 中,对应的 generated 代码在 dm/pb/dmworker.pb.go 中,对应的实现代码在 dm/worker/server.go 中(*Server)。DM-worker 的启动流程与 DM-master 类似,在此不再额外说明。Server 这个 struct 的主要 fields 除用于处理 RPC 的 rootLis 与 svr 外,另一个是用于管理同步任务与 relay log 的 worker(相关代码在 dm/worker/worker.go 中)。在 Worker 这个 struct 中,主要 fields 包括: subTasks:维护该 DM-worker 上的所有同步子任务信息。 relayHolder:对 relay 处理单元相关操作进行简单封装,转发相关操作请求给 relay 处理单元,获取 relay 处理单元的状态信息。 relayPurger:根据用户配置及相关策略,尝试定期对 relay log 进行 purge 操作。 数据同步子任务管理的代码实现主要在 dm/worker/subtask.go 中, relay 处理单元管理的代码实现主要在 dm/worker/relay.go 中,对 relay log 进行 purge 操作的代码实现主要在 relay/purger pkg 中。在本篇文章中,我们暂时只关注 DM 架构相关的实现,上述各功能的具体实现将在后续的相关文章中展开介绍。Worker 的入口方法为 Start,其中的主要操作包括: 通过 w.relayHolder.Start 启动 relay 处理单元,开始从上游拉取 binlog。 通过 w.relayPurger.Start 启动后台 purge 线程,尝试对 relay log 进行定期 purge。 其他的操作主要还包括处理 Server 转发而来的同步任务管理、relay 处理单元管理、状态信息查询等。dmctl dmctl 的入口代码在 cmd/dm-ctl/main.go,其操作除参数解析与 signal 处理外,主要为调用 loop 进入命令处理循环、等待用户输入操作命令。在 loop 中,我们借助 chzyer/readline 提供命令行交互环境,读取用户输入的命令并输出命令执行结果。一个命令的处理流程为: 调用 l.Readline 读取用户输入的命令 判断是否需要退出命令行交互环境(exit 命令)或需要进行处理 调用 ctl.Start 进行命令分发与处理 dmctl 的具体命令处理实现在 dm/ctl pkg 中,入口为 dm/ctl/ctl.go 中的 Start 方法,命令的分发与参数解析借助于 spf13/cobra。命令的具体功能实现在相应的子 pkg 中: master:dmctl 与 DM-master 交互的命令,是当前 DM 推荐的命令交互方式。 worker:dmctl 与 DM-worker 交互的命令,主要用于开发过程中进行 debug,当前并没有实现所有 DM-worker 支持的命令,未来可能废弃。 common:多个命令依赖的通用操作及 dmctl 依赖的配置信息等。 每个 dmctl 命令,其主要对应的实现包括 3 个部分: 在各命令对应的实现源文件中,通过 New***Cmd 形式的方法创建 cobra.Command 对象。 在 dm/ctl/ctl.go 中通过调用 rootCmd.AddCommand 添加该命令。 在各命令对应的实现源文件中,通过 ***Func 形式的方法实现参数验证、RPC 调用等具体功能。 任务管理调用链示例 让我们用一个启动数据同步任务的操作示例来说明 DM 中的组件交互与 RPC 调用流程。 用户在 dmctl 命令行交互环境中输入 start-task 命令及相应参数。 dmctl 在 dm/ctl/ctl.go 的 Start 方法中进行命令分发,请求 dm/ctl/master/start_task.go 中的 startTaskFunc 处理命令。 startTaskFunc 通过 cli.StartTask 调用 DM-master 上的 RPC 方法。 DM-master 中的 Server.StartTask 方法(dm/master/server.go)响应来自 dmctl 的 RPC 请求。 Server.Start 从 workerClients 中获取任务对应 DM-worker 的 RPC client,并通过 cli.StartSubTask 调用 DM-worker 上的 RPC 方法。 DM-worker 中的 Server.StartSubTask 方法(dm/worker/server.go)响应来自 DM-master 的 RPC 请求。 Server.StartSubTask 中将任务管理请求转发给 Worker.StartSubTask(dm/worker/worker.go),并将处理结果通过 RPC 返回给 DM-master。 DM-master 将 DM-worker 返回的 RPC 响应重新封装后通过 RPC 返回给 dmctl。 dmctl 通过 common.PrettyPrintResponse 输出命令操作的 RPC 响应。 小结 在本篇文章中,我们主要介绍了 DM 的各个组件的入口函数,最后以 dmctl 的 start-task 为例介绍了交互的调用流程细节。下一篇文章我们会开始介绍 DM-worker 组件内各数据同步处理单元(relay-unit, dump-unit, load-unit, sync-unit)的设计原理与具体实现。"}, {"url": "https://pingcap.com/weekly/2019-03-25-tidb-weekly/", "title": "Weekly update (March 18 ~ March 24, 2019)", "content": " Weekly update in TiDB Last week, we landed 50 PRs in the TiDB repository.Added Add the /debug/zip API to get useful debugging information at once Support SET ROLE and CURRENT_ROLE Improved Replace map in GetFormatType with switch-case to speed up execution Change the type of RoundMode from string to int32 to speed up execution Support Change and ChangeExec Improve row count estimation for the index range containing correlated columns Unify the log format for the util package Unify the log format for the store package Unify the log format for the ddl package Remove the wait time after canceling a job Support chunk size control for Joiners and Join-executors Unify the log format for the statistics package Add more checks for create table partition by range column Unify the log format for the session package Unify the log format for the table package Disable non-deterministic function calls for generated columns Fixed Fix the panic in SELECT * FROM information_schema.processlist Fix an unexpected overflow error when adding a big signed integer and a big unsigned integer Fix the privilege check failure for GRANT ON TABLE Fix the issue that str_to_date is not compatible with MySQL in some cases Fix a panic in the window function when order-by items contain NULL Fix a wrong format of the slow log Fix the issue that Select+1000 is not compatible with MySQL Weekly update in TiSpark Last week, we landed 4 PRs in the TiSpark repository.Fixed Update the kvproto version that breaks compilation Weekly update in TiKV and PD Last week, we landed 26 PRs in the TiKV and PD repositories.Added Implement the instr coprocessor built-in function Improved Use single quotes in coprocessor error messages Improve instr_binary, locate, and locate_binary coprocessor built-in functions Support batch for the table scan executor Fixed Fix the issue of the PD scheduler removing the Region leader Return warnings when casting a float string to an integer string Engineering Use lite-runtime in Protobuf Abstract some new jemalloc code Fix a compiling warning Check SSE4.2 in the release build Use static metrics in the TiKV raw read Refactor TableScanExecutor and IndexScanExecutor New contributors (Thanks!) tikv: Xuanwo tidb: b41sh pd: elitecodegroovy parser: aliiohs hawkingrei loxp wjhuang2016 "}, {"url": "https://pingcap.com/meetup/meetup-91-20190321/", "title": "【Infra Meetup No.91】Head First Distributed Transaction in TiDB", "content": "在上周六举办的 Infra Meetup No.91 上,我司 TiKV 研发工程师吴雪莲老师为杭州小伙伴分享了分布式事务在 TiDB 中实现的原理和细节,以下是视频 & 文字回顾,enjoy! 讲师介绍:吴雪莲,TiKV 研发工程师,目前主要负责 TiDB/TiKV 事务、TiKV 计算层 Coprocessor 相关研发。 视频 | Infra Meetup No.91:Head First Distributed Transaction in TiDB PPT 链接 本次分享的主题是分布式事务在 TiDB 中的实现,主要围绕以下三个方面展开: 分布式事务的定义 Percolator 中事务的实现 TiDB 中事务的实现及注意事项 首先,在分布式事务的定义中,主要介绍了 ACID 和四种常见隔离级别。然后解读了 Percolator 中事务实现,核心内容包括:1. 基于快照隔离级别的优缺点;2. 如何通过两阶段提交实现跨行跨表的分布式事务。最后,我们详细介绍了 TiDB 中分布式事务的实现,包括 TiDB 如何将关系型数据转化成 key-value 存储,TiDB 中两阶段提交的实现细节及异常处理,以及 TiDB 事务使用过程中的注意事项。 PingCAP Infra Meetup作为一个基础架构领域的前沿技术公司,PingCAP 希望能为国内真正关注技术本身的 Hackers 打造一个自由分享的平台。自 2016 年 3 月 5 日开始,我们定期在周末举办 Infra Meetup,与大家深度探讨基础架构领域的前瞻性技术思考与经验,目前已在北京、上海、广州、成都、杭州等地举办。在这里,我们希望提供一个高水准的前沿技术讨论空间,让大家真正感受到自由的开源精神魅力。 "}, {"url": "https://pingcap.com/blog-cn/dm-source-code-reading-1/", "title": "DM 源码阅读系列文章(一)序", "content": " 前言 TiDB-DM 是由 PingCAP 开发的一体化数据同步任务管理平台,支持从 MySQL 或 MariaDB 到 TiDB 的全量数据迁移和增量数据同步,在 TiDB DevCon 2019 正式开源。作为一款连接 MySQL/MariaDB 生态和 TiDB 生态的中台类型产品,DM 获得了广泛的关注,很多公司、开发者和社区的伙伴已经在使用 DM 来进行数据迁移和管理。随着大家使用的广泛和深入,遇到了不少由于对 DM 原理不理解而错误使用的情况,也发现了一些 DM 支持并不完善的场景和很多可以改进的地方。在这样的背景下,我们希望开展 DM 源码阅读分享活动,通过对 DM 代码的分析和设计原理的解读,帮助大家理解 DM 的实现原理,和大家进行更深入的交流,也有助于我们和社区共同进行 DM 的设计、开发和测试。背景知识 本系列文章会聚焦 DM 自身,读者需要有一些基本的知识,包括但不限于: Go 语言,DM 由 Go 语言实现,有一定的 Go 语言基础有助于快速理解代码。 数据库基础知识,包括 MySQL、TiDB 的功能、配置和使用等;知道基本的 DDL、DML 语句和事务的基本常识;MySQL 数据备份、主从同步的原理等。 基本的后端服务知识,比如后台服务进程管理、RPC 工作原理等。 总体而言,读者需要有一定 MySQL/TiDB 的使用经验,了解 MySQL 数据备份和主从同步的原理,以及可以读懂 Go 语言程序。在阅读 DM 源码之前,可以先从阅读《TiDB-DM 架构设计与实现原理》入手,并且参考 使用文档 在本地搭建一个 DM 的测试环境,从基础原理和使用对 DM 有一个初步的认识,然后再进一步分析源码,深入理解代码的设计和实现。内容概要 源码阅读系列将会从两条线进行展开,一条是围绕 DM 的系统架构和重要模块进行分析,另一条线围绕 DM 内部的同步机制展开分析。源码阅读不仅是对代码实现的分析,更重要的是深入的分析背后的设计思想,源码阅读和原理分析的覆盖范围包括但不限于以下列出的内容(因为目前 DM 仍处于快速迭代的阶段,会有新的功能和模块产生,部分模块在未来也会进行优化和重构,后续源码阅读的内容会随着 DM 的功能演进做适当的调整): 整体架构介绍,包括 DM 有哪些模块,分别实现什么功能,模块之间交互的数据模型和 RPC 实现。 DM-worker 内部组件设计原理(relay-unit, dump-unit, load-unit, sync-unit)和数据同步的并发模型设计与实现。 基于 binlog 的数据同步模型设计和实现。 relay log 的原理和实现。 定制化数据同步功能的实现原理(包括库表路由,库表黑白名单,binlog event 过滤,列值转换)。 DM 如何支持上游 online DDL 工具(pt-osc, gh-ost)的 DDL 同步场景。 sharding DDL 处理的具体实现。 checkpoint 的设计原理和实现,深入介绍 DM 如何在各类异常情况下保证上下游数据同步的一致性。 DM 测试的架构和实现。 代码简介 DM 源代码完全托管在 GitHub 上,从 项目主页 可以看到所有信息,整个项目使用 Go 语言开发,按照功能划分了很多 package,下表列出了 DM 每个 package 的基本功能: Package Introduction checker 同步任务上下游数据库配置、权限前置检查模块 cmd/dm-ctl, cmd/dm-master, cmd/dm-worker dmctl, DM-master, DM-worker 的 main 文件所在模块 dm/config 同步任务配置、子任务配置、前置检查配置定义模块 dm/ctl dmctl 所有 RPC 调用实现的模块 dm/master DM-master 的核心实现,包含了 DM-master 后台服务,对 dmctl 到 DM-master 的 RPC 调用的处理逻辑,对 DM-worker 的管理,对 sharding DDL 进行协调调度等功能 dm/pb, dm/proto dm/proto 定义了 DM-master 和 DM-worker 相关交互的 protobuf 协议,dm/pb 是对应的生成代码 dm/unit 定义了子任务执行的逻辑单元(包括 dump unit, load unit, sync unit, relay unit)接口,在每个不同逻辑单元对应的 package 内都有对应的 接口实现 dm/worker DM-worker 的核心实现,实现 DM-worker 后台服务,管理维护每个任务的 relay 逻辑单元,管理、调度每个子任务的逻辑单元 loader 子任务 load 逻辑单元的实现,用于全量数据的导入 mydumper 子任务 dump 逻辑单元的实现,用于全量数据的导出 pkg 包含了一些基础功能的实现,例如 gtid 操作、SQL parser 封装、binlog 文件流读写封装等 relay 处理 relay log 同步的核心模块 syncer 子任务 sync 逻辑单元的实现,用于增量数据的同步 对于理解代码最直接的手段就是从 DM-server, DM-worker 和 dmctl 三个 binary 对应的 main 文件入手,看 DM-worker, DM-master 是如何启动,DM-worker 如何管理一个上游实例和同步任务;如何从 dmctl 开始同步子任务;然后看一个同步子任务从全量状态,到增量同步状态,binlog 如何处理、sql 任务如何分发等。通过这样一个流程对 DM 的整体架构就会有全面的理解。进一步就可以针对每个使用细节去了解 DM 背后的设计逻辑和代码实现,可以从具体每个 package 入手,也可以从感兴趣的功能入手。实际上 DM 代码中使用了很多优秀的第三方开源代码,包括但不仅限于: 借助 grpc 实现各组件之间的 RPC 通信 借助 pingcap/parser 进行 DDL 的语法解析和语句还原 借助 pingcap/tidb-tools 提供的工具实现复杂的数据同步定制 借助 go-mysql 解析 MySQL/MariaDB binlog 等 在源码阅读过程中对于比较重要的、与实现原理有很高相关度的第三方模块,我们会进行相应的扩展阅读。工具链 工欲善其事,必先利其器,在阅读 DM 源码之前,我们先来介绍 DM 项目使用到的一些外部工具,这些工具通常用于 DM 的构建、部署、运行和测试,在逐步使用 DM,阅读代码、理解原理的过程中都会使用到这些工具。 golang 工具链:构建 DM 需要 go >= 1.11.4,目前支持 Linux 和 MacOS 环境。 gogoprotobuf:用于从 proto 描述文件生成 protobuf 代码,DM 代码仓库的 generate-dm.sh 文件封装了自动生成 DM 内部 protobuf 代码的脚本。 Ansible:DM 封装了 DM-Ansible 脚本用于 DM 集群的自动化部署,部署流程可以参考 使用 ansible 部署 DM。 pt-osc, gh-ost:用于上游 MySQL 进行 online-ddl 的同步场景。 mydumper:DM 的全量数据 dump 阶段直接使用 mydumper 的 binary。 MySQL, TiDB, sync_diff_inspector:这些主要用于单元测试和集成测试,可以参考 tests#preparations 这部分描述。 小结 本篇文章主要介绍了 DM 源码阅读的目的和源码阅读的规划,简单介绍了 DM 的源码结构和工具链。下一篇文章我们会从 DM 的整体架构入手,详细分析 DM-master、DM-worker 和 dmctl 三个组件服务逻辑的实现和功能抽象,RPC 数据模型和交互接口。更多的代码阅读内容会在后面的章节中逐步展开,敬请期待。"}, {"url": "https://pingcap.com/weekly/2019-03-18-tidb-weekly/", "title": "Weekly update (March 11 ~ March 17, 2019)", "content": " Weekly update in TiDB Last week, we landed 63 PRs in the TiDB repository.Added Support the NTILE window function Support the LEAD and LAG window functions Support the PERCENT_RANK window functions Support the CUME_DIST window function Support the NTH_VALUE window function Support the log_bin variable Support the JSON_ARRAY_APPEND built-in JSON function Improved Set the correlation when loading the needed column histogram Stabilize the sort order when computing the correlation Set the new child after injecting the Project operator Generate digest and log it in slow logs Skip loading the privilege when skip-grant-table is enabled Replace some maps with the switch table to save memory Unify the log format in the planner package Update the SELECT denied message for the compatibility with MySQL Update the binlog enabling configuration and add the tidb_log_bin system variable Support DROP ROLE Support ROLL BACK for renaming the table, modifying the table charset, and truncating the table partition Only consider prefix columns in indexes for choosing Index Join Unify the log format in the executor package Unify the log format in the expression package Refactor the slow log format for the compatibility with the MySQL slow log Create a SQL bind table during bootstrap Fixed Fix the issue that last_day is not compatible with MySQL Fix the issue that Point Get might miss the unique key if the primary key is also in the condition Fix the panic when wrapping the cast for the window function Fix the issue that str_to_date is not compatible with MySQL Fix the issue that default_week_format does not work Fix the Create Table ... Like bug when the refer table has non-public column or index Fix an unexpected error caused by Over Correct ExpectedCnt for children plans of Join Weekly update in TiSpark Last week, we landed 6 PRs in the TiSpark repository.Fixed Fix the incorrect default value of the Timestamp type due to TiDB upgrade Fix possible IndexOutOfBoundException in KeyUtils Weekly update in TiKV and PD Last week, we landed 28 PRs in the TiKV and PD repositories.Added Add an option to decide whether to panic or not when the key exceeds the bound or in the MVCC corruption Implement replace Implement builtin_string and instr_binary BatchExecutor Add the context for AsMySQLBool and remove BatchExecutorContext Implement AsMySQLBool Improved Remove the unnecessary Copy trait of Tz and EvalConfig Avoid some copies in the MVCC reader Refactor ScannerBuilder Export jemalloc statistics to Prometheus Use the address instead of the store ID for metrics Make the heartbeat more reasonable in the simulator Importer Wait after split before scatter Restrict the memory usage by RocksDB Do not read the entire SST file into memory Store the intermediate SST files on the disk instead of memory Increase the default region-split-size to 512 MB Fixed Fix the issue about instr_binary Allow restart with a StoreIdent when the cluster is not bootstrapped New contributors (Thanks!) tidb: jarvys liufuyang wjhuang2016 zyycj tikv: iosmanthus "}, {"url": "https://pingcap.com/success-stories/tidb-in-bookmyshow/", "title": "BookMyShow.com: More Uptime, 30% Less Operational Cost with TiDB", "content": " Since 2007, BookMyShow has made it easy for entertainment-seekers in India to buy tickets for movies, sporting events, plays, concerts, and more. As India’s largest e-commerce site for booking entertainment tickets online, it has a total user base of more than 50 million in over 650 cities and towns, and holds a lion’s share of the market in the country’s online ticketing space, processing millions of transactions a week. BookMyShow homepage Challenge To serve its users reliably, the company embraced microservices in recent years. But having all of those microservices meant that BookMyShow needed a seamless way for all of them to send their data over to its Big Data Platform. The company’s 6-person Data Operations team was tasked with building a highly sustainable data pipeline and warehouse, as well as providing operational and maintenance support for BookMyShow’s Big Data related infrastructure.Consistency was a priority, as the site gets regular updates for records in its transactions. The team was using Galera, but it wasn’t able to meet their needs. One of the major problems they faced with Galera was its master-master architecture and replication scheme, which requires each node to have a full copy of the data. This approach makes scaling both the reads and writes capacity, and resource utilization in general, inefficient once the data size reaches multiple terabytes. Due to this difficulty, one engineer was fully dedicated to operating and monitoring Galera - a big cost for a small team of engineers.The BookMyShow team experienced a particularly bad episode of split-brain with Galera, where the two masters were completely out sync. It took the whole team 2-3 days of dedicated effort to do recovery. The team knew then they needed a better solution.Evaluation and Migration The initial list of options was TiDB, Greenplum, and Vitess. After undertaking a lot of research on the basis of papers, blogs, and case studies, the BookMyShow team decided to spend their engineering time evaluating TiDB and Greenplum, since both promised a solution to the problems the firm had encountered with Galera.TiDB quickly emerged as the winner because it was easy to set up, deploy, and inject a lot of data into a cluster within a short time. “I was actually in charge of setting up and evaluating Greenplum and before I could figure out how to deploy it, my teammate who was responsible for evaluating TiDB was finished with deployment and already pushing a lot of data,” recounted one of the members of the engineering team.During the initial migration phase to TiDB, the BookMyShow team used Apache Spark to stream historical data into its TiDB cluster, which was fast and easy.Download TiDB Subscribe to Blog Result and Advice BookMyShow’s current architecture streams transactional data in near real-time from MS SQL to TiDB using Kafka. It currently has about 5TB of data in TiDB, and expects to reach 10TB soon.And for good reason: With TiDB, BookMyShow has experienced increased uptime and availability, since TiDB automatically partitions and distributes data across the cluster. Meanwhile, operational and maintenance cost has been reduced by 30%. No engineer needs to be fully dedicated to database operations anymore. The only time the engineering team needs to work on TiDB is when there’s a certain slow query performance and when capacity needs to be scaled out to store more data as the business grows. “Operational and maintenance cost has been reduced by 30%. No engineer needs to be fully dedicated to database operations anymore.” One of the biggest advantages for BookMyShow from using TiDB has been the fact that it effectively leverages RocksDB underneath, which gives a significantly improved performance as compared to any network-partitioned SQL engine. Since TiDB implements the Raft consensus algorithm and automates data replication and distribution, we need not worry about data partitioning and replication for failure tolerance.The BookMyShow engineering team did have a few suggestions for future TiDB users and the PingCAP team. Firstly, it initially experienced some bad performance after a custom configuration of the data partition (or Region) size in TiKV and the number of gRPC connections. (BookMyShow currently uses a larger Region size than the 96MB default.) While the issue with performance was resolved after contacting the PingCAP team for help, more information and improved documentation on the performance impact of these critical configurations would be helpful.Secondly, while a TiDB deployment installs a Grafana dashboard with all the system’s metrics streamed from Prometheus, the BookMyShow team has had difficulty figuring out which of the many metrics is relevant to troubleshoot specific performance issues. There was simply too much information with no guidance on the dashboard. Having more descriptions of important metrics in Grafana would go a long way in helping other TiDB users monitor and troubleshoot performance issues in their clusters more easily.Overall, integration went well at BookMyShow, thanks to the attentiveness of the PingCAP team and quick responses to issues over email and on GitHub. “The TiDB open source community is very active,” said the engineer from BookMyShow. “So it helped us a lot with our adoption.”With TiDB, gone are the days of stress and worry about the state and capacity of its database infrastructure. The engineering team can now rest easy, as BookMyShow’s many millions of users can continue to make plans to watch movies and shows without any hassle."}, {"url": "https://pingcap.com/meetup/meetup-90-20190314/", "title": "【Infra Meetup No.90】知乎已读服务架构演进", "content": "在第 90 期 Infra Meetup 上,来自知乎的孙晓光老师为大家分享了知乎已读服务的架构演进的经验。小伙伴们热情爆棚,QA 环节长达 1 小时,快戳视频看看孙老师都分享了哪些有趣的「踩坑经验」吧! 讲师介绍:孙晓光,知乎搜索工程团队负责人,TiKV Committer。 视频 | Infra Meetup No.90:知乎已读服务架构演进 PPT 链接 孙晓光老师在本期 Meetup 上提到,知乎已读服务的设计严格意义上来说同很多业务向系统的设计有不少差异,而这些差异反映在过程和结果上有些是正向的,也有些是负向的。但是很高兴的看到至少在上线一年多来,整体的收益是远高于所付出的代价的。最后他希望在近期全量数据迁移到 TiDB 完成后能够进一步解决目前架构的一些尚存的痛点问题,让这个架构跑的更稳跑的更好。 PingCAP Infra Meetup作为一个基础架构领域的前沿技术公司,PingCAP 希望能为国内真正关注技术本身的 Hackers 打造一个自由分享的平台。自 2016 年 3 月 5 日开始,我们定期在周末举办 Infra Meetup,与大家深度探讨基础架构领域的前瞻性技术思考与经验,目前已在北京、上海、广州、成都、杭州等地举办。在这里,我们希望提供一个高水准的前沿技术讨论空间,让大家真正感受到自由的开源精神魅力。 "}, {"url": "https://pingcap.com/blog-cn/tikv-source-code-reading-4/", "title": "TiKV 源码解析系列文章(四)Prometheus(下)", "content": " 本文为 TiKV 源码解析系列的第四篇,接上篇继续为大家介绍 rust-prometheus。上篇 主要介绍了基础知识以及最基本的几个指标的内部工作机制,本篇会进一步介绍更多高级功能的实现原理。 与上篇一样,以下内部实现都基于本文发布时最新的 rust-prometheus 0.5 版本代码,目前我们正在开发 1.0 版本,API 设计上会进行一些简化,实现上出于效率考虑也会和这里讲解的略微有一些出入,因此请读者注意甄别。指标向量(Metric Vector) Metric Vector 用于支持带 Label 的指标。由于各种指标都可以带上 Label,因此 Metric Vector 本身实现为了一种泛型结构体,Counter、Gauge 和 Histogram 在这之上实现了 CounterVec、GaugeVec 和 HistogramVec。Metric Vector 主要实现位于 src/vec.rs。以 HistogramVec 为例,调用 HistogramVec::with_label_values 可获得一个 Histogram 实例,而 HistogramVec 定义为:pub type HistogramVec = MetricVec<HistogramVecBuilder>; pub struct MetricVec<T: MetricVecBuilder> { pub(crate) v: Arc<MetricVecCore<T>>, } impl<T: MetricVecBuilder> MetricVec<T> { pub fn with_label_values(&self, vals: &[&str]) -> T::M { self.get_metric_with_label_values(vals).unwrap() } } 因此 HistogramVec::with_label_values 的核心逻辑其实在 MetricVecCore::get_metric_with_label_values。这么做的原因是为了让 MetricVec 是一个线程安全、可以被全局共享但又不会在共享的时候具有很大开销的结构,因此将内部逻辑实现在 MetricVecCore,外层(即在 MetricVec)套一个 Arc<T> 后再提供给用户。进一步可以观察 MetricVecCore 的实现,其核心逻辑如下:pub trait MetricVecBuilder: Send + Sync + Clone { type M: Metric; type P: Describer + Sync + Send + Clone; fn build(&self, &Self::P, &[&str]) -> Result<Self::M>; } pub(crate) struct MetricVecCore<T: MetricVecBuilder> { pub children: RwLock<HashMap<u64, T::M>>, // Some fields are omitted. } impl<T: MetricVecBuilder> MetricVecCore<T> { // Some functions are omitted. pub fn get_metric_with_label_values(&self, vals: &[&str]) -> Result<T::M> { let h = self.hash_label_values(vals)?; if let Some(metric) = self.children.read().get(&h).cloned() { return Ok(metric); } self.get_or_create_metric(h, vals) } pub(crate) fn hash_label_values(&self, vals: &[&str]) -> Result<u64> { if vals.len() != self.desc.variable_labels.len() { return Err(Error::InconsistentCardinality( self.desc.variable_labels.len(), vals.len(), )); } let mut h = FnvHasher::default(); for val in vals { h.write(val.as_bytes()); } Ok(h.finish()) } fn get_or_create_metric(&self, hash: u64, label_values: &[&str]) -> Result<T::M> { let mut children = self.children.write(); // Check exist first. if let Some(metric) = children.get(&hash).cloned() { return Ok(metric); } let metric = self.new_metric.build(&self.opts, label_values)?; children.insert(hash, metric.clone()); Ok(metric) } } 现在看代码就很简单了,它首先会依据所有 Label Values 构造一个 Hash,接下来用这个 Hash 在 RwLock<HashMap<u64, T::M>> 中查找,如果找到了,说明给定的这个 Label Values 之前已经出现过、相应的 Metric 指标结构体已经初始化过,因此直接返回对应的实例;如果不存在,则要利用给定的 MetricVecBuilder 构造新的指标加入哈希表,并返回这个新的指标。由上述代码可见,为了在线程安全的条件下实现 Metric Vector 各个 Label Values 具有独立的时间序列,Metric Vector 内部采用了 RwLock 进行同步,也就是说 with_label_values() 及类似函数内部是具有锁的。这在多线程环境下会有一定的效率影响,不过因为大部分情况下都是读锁,因此影响不大。当然,还可以发现其实给定 Label Values 之后调用 with_label_values() 得到的指标实例是可以被缓存起来的,只访问缓存起来的这个指标实例是不会有任何同步开销的,也绕开了计算哈希值等比较占 CPU 的操作。基于这个思想,就有了 Static Metrics,读者可以在本文的后半部分了解 Static Metrics 的详细情况。另外读者也可以发现,Label Values 的取值应当是一个有限的、封闭的小集合,不应该是一个开放的或取值空间很大的集合,因为每一个值都会对应一个内存中指标实例,并且不会被释放。例如 HTTP Method 是一个很好的 Label,因为它只可能是 GET / POST / PUT / DELETE 等;而 Client Address 则很多情况下并不适合作为 Label,因为它是一个开放的集合,或者有非常巨大的取值空间,如果将它作为 Label 很可能会有容易 OOM 的风险。这个风险在 Prometheus 官方文档中也明确指出了。整型指标(Integer Metric) 在讲解 Counter / Gauge 的实现时我们提到,rust-prometheus 使用 CAS 操作实现 AtomicF64 中的原子递增和递减,如果改用 atomic fetch-and-add 操作则一般可以取得更高效率。考虑到大部分情况下指标都可以是整数而不需要是小数,例如对于简单的次数计数器来说它只可能是整数,因此 rust-prometheus 额外地提供了整型指标,允许用户自由地选择,针对整数指标情况提供更高的效率。为了增强代码的复用,rust-prometheus 实际上采用了泛型来实现 Counter 和 Gauge。通过对不同的 Atomic(如 AtomicF64、AtomicI64)进行泛化,就可以采用同一份代码实现整数的指标和(传统的)浮点数指标。Atomic trait 定义如下(src/atomic64/mod.rs):pub trait Atomic: Send + Sync { /// The numeric type associated with this atomic. type T: Number; /// Create a new atomic value. fn new(val: Self::T) -> Self; /// Set the value to the provided value. fn set(&self, val: Self::T); /// Get the value. fn get(&self) -> Self::T; /// Increment the value by a given amount. fn inc_by(&self, delta: Self::T); /// Decrement the value by a given amount. fn dec_by(&self, delta: Self::T); } 原生的 AtomicU64、AtomicI64 及我们自行实现的 AtomicF64 都实现了 Atomic trait。进而,Counter 和 Gauge 都可以利用上 Atomic trait:pub struct Value<P: Atomic> { pub val: P, // Some fields are omitted. } pub struct GenericCounter<P: Atomic> { v: Arc<Value<P>>, } pub type Counter = GenericCounter<AtomicF64>; pub type IntCounter = GenericCounter<AtomicI64>; 本地指标(Local Metrics) 由前面这些源码解析可以知道,指标内部的实现是原子变量,用于支持线程安全的并发更新,但这在需要频繁更新指标的场景下相比简单地更新本地变量仍然具有显著的开销(大约有 10 倍的差距)。为了进一步优化、支持高效率的指标更新操作,rust-prometheus 提供了 Local Metrics 功能。rust-prometheus 中 Counter 和 Histogram 指标支持 local() 函数,该函数会返回一个该指标的本地实例。本地实例是一个非线程安全的实例,不能多个线程共享。例如,Histogram::local() 会返回 LocalHistogram。由于 Local Metrics 使用是本地变量,开销极小,因此可以放心地频繁更新 Local Metrics。用户只需定期调用 Local Metrics 的 flush() 函数将其数据定期同步到全局指标即可。一般来说 Prometheus 收集数据的间隔是 15s 到 1 分钟左右(由用户自行配置),因此即使是以 1s 为间隔进行 flush() 精度也足够了。普通的全局指标使用流程如下图所示,多个线程直接利用原子操作更新全局指标:本地指标使用流程如下图所示,每个要用到该指标的线程都保存一份本地指标。更新本地指标操作开销很小,可以在频繁的操作中使用。随后,只需再定期将这个本地指标 flush 到全局指标,就能使得指标的更新操作真正生效。TiKV 中大量运用了本地指标提升性能。例如,TiKV 的线程池一般都提供 Context 变量,Context 中存储了本地指标。线程池上运行的任务都能访问到一个和当前 worker thread 绑定的 Context,因此它们都可以安全地更新 Context 中的这些本地指标。最后,线程池一般提供 tick() 函数,允许以一定间隔触发任务,在 tick() 中 TiKV 会对这些 Context 中的本地指标进行 flush()。Local Counter Counter 的本地指标 LocalCounter 实现很简单,它是一个包含了计数器的结构体,该结构体提供了与 Counter 一致的接口方便用户使用。该结构体额外提供了 flush(),将保存的计数器的值作为增量值更新到全局指标:pub struct GenericLocalCounter<P: Atomic> { counter: GenericCounter<P>, val: P::T, } pub type LocalCounter = GenericLocalCounter<AtomicF64>; pub type LocalIntCounter = GenericLocalCounter<AtomicI64>; impl<P: Atomic> GenericLocalCounter<P> { // Some functions are omitted. pub fn flush(&mut self) { if self.val == P::T::from_i64(0) { return; } self.counter.inc_by(self.val); self.val = P::T::from_i64(0); } } Local Histogram 由于 Histogram 本质也是对各种计数器进行累加操作,因此 LocalHistogram 的实现也很类似,例如 observe(x) 的实现与 Histogram 如出一辙,除了它不是原子操作;flush() 也是将所有值累加到全局指标上去:pub struct LocalHistogramCore { histogram: Histogram, counts: Vec<u64>, count: u64, sum: f64, } impl LocalHistogramCore { // Some functions are omitted. pub fn observe(&mut self, v: f64) { // Try find the bucket. let mut iter = self .histogram .core .upper_bounds .iter() .enumerate() .filter(|&(_, f)| v <= *f); if let Some((i, _)) = iter.next() { self.counts[i] += 1; } self.count += 1; self.sum += v; } pub fn flush(&mut self) { // No cached metric, return. if self.count == 0 { return; } { let h = &self.histogram; for (i, v) in self.counts.iter().enumerate() { if *v > 0 { h.core.counts[i].inc_by(*v); } } h.core.count.inc_by(self.count); h.core.sum.inc_by(self.sum); } self.clear(); } } 静态指标(Static Metrics) 之前解释过,对于 Metric Vector 来说,由于每一个 Label Values 取值都是独立的指标实例,因此为了线程安全实现上采用了 HashMap + RwLock。为了提升效率,可以将 with_label_values 访问获得的指标保存下来,以后直接访问。另外使用姿势正确的话,Label Values 取值是一个有限的、确定的、小的集合,甚至大多数情况下在编译期就知道取值内容(例如 HTTP Method)。综上,我们可以直接写代码将各种已知的 Label Values 提前保存下来,之后可以以静态的方式访问,这就是静态指标。以 TiKV 为例,有 Contributor 为 TiKV 提过这个 PR:#2765 server: precreate some labal metrics。这个 PR 改进了 TiKV 中统计各种 gRPC 接口消息次数的指标,由于 gRPC 接口是固定的、已知的,因此可以提前将它们缓存起来:struct Metrics { kv_get: Histogram, kv_scan: Histogram, kv_prewrite: Histogram, kv_commit: Histogram, // ... } impl Metrics { fn new() -> Metrics { Metrics { kv_get: GRPC_MSG_HISTOGRAM_VEC.with_label_values(&["kv_get"]), kv_scan: GRPC_MSG_HISTOGRAM_VEC.with_label_values(&["kv_scan"]), kv_prewrite: GRPC_MSG_HISTOGRAM_VEC.with_label_values(&["kv_prewrite"]), kv_commit: GRPC_MSG_HISTOGRAM_VEC.with_label_values(&["kv_commit"]), // ... } } } 使用的时候也很简单,直接访问即可:@@ -102,10 +155,8 @@ fn make_callback<T: Debug + Send + 'static>() -> (Box<FnBox(T) + Send>, oneshot: impl<T: RaftStoreRouter + 'static> tikvpb_grpc::Tikv for …"}, {"url": "https://pingcap.com/blog/tidb-certification-beta-is-ready/", "title": "TiDB Certification (Beta) is Ready", "content": "Last November, we launched TiDB Academy, a series of online, self-paced technical training courses on TiDB and distributed database concepts in general. The first course is called “Distributed Database with TiDB for MySQL DBAs”, taught by me!Since we launched, hundreds of people have signed up from around the world to take the course and are looking to officially certify their new knowledge and skills.Today, I am excited to announce that the official TiDB Certification is now available in beta!This is an online exam which consists of 45 multiple choice questions weighted according to the following topics: 20% - Architecture 10% - Data Migration 20% - Placement Drive (PD) 30% - TiDB server 20% - TiKV If you plan on taking the exam, reading the TiDB Manual as a study guide would be helpful, in addition to taking the TiDB Academy courses and doing all the lab exercises. All exams are live proctored by our testing partner Examity. To sit for an exam, you will need a webcam and valid government identification (e.g., passport, driver’s license, etc.).Because the exam is in beta, you won’t immediately receive your score (as either pass or fail) until the exam completes the beta period. This is a common industry practice for many technical exams, where the beta period is partially used to evaluate the quality of our questions before a final outcome is determined for the exam taker.While in beta, the exams are heavily discounted: only $50!The exam is ready for you to sign up and take HERE, and if you have any questions, just email our teaching staff at: academy@pingcap.com. Good luck!"}, {"url": "https://pingcap.com/weekly/2019-03-11-tidb-weekly/", "title": "Weekly update (March 04 ~ March 10, 2019)", "content": " Weekly update in TiDB Last week, we landed 51 PRs in the TiDB repository.Added Support the first_value and last_value window functions Change all the MySQL versions from 5.7.10 to 5.7.25 Handle the default frame for window functions Support the show pump status and show drainer status statements Improved Add a new logger to help log connID and recvTs Improve the str_to_date built-in function compatibility Refine the rollback logic of canceling modifying a column and adding and dropping a foreign key Convert SemiJoin to InnerJoin in more cases Control the chunk size for StreamAgg and HashAgg Refactor the code related to join reorder Validate fsp for datetime and timestamp if they are used as the default value Upgrade to Go 1.12 Update ddl_slow_threshold Fixed Fix the error message that the table does not exist in Inner Join Fix the git describe error in some cases Fix an unexpected error when using window functions and View Fix the error when setting variable='' Fix the issue that etcd clients lost connection with the PD cluster in some cases Quote the database name before running SQL statements Fix the issue that the behavior of aggregation in subqueries is incompatible with MySQL Fix the error occurred when canceling dropping a table or database in some cases Fix the issue that the outer table is chosen wrongly when both tables are specified in TIDB_INLJ Weekly update in TiSpark Last week, we landed 2 PRs in the TiSpark repository.Improved Skip LockResolverTest when the PD client is not present Weekly update in TiKV and PD Last week, we landed 15 PRs in the TiKV and PD repositories.Added Introduce the VectorLike enum for the endpoint Add checks for the SSE4.2 support Add the communication section in README Implement sub_datetime_and_duration and sub_datetime_and_string Add functions to build batch executors Improved Unify the log format in the Raftstore module Validate the scheduler name in the PD configuration Remove extern crate statements Remove message boxing and message unboxing Publish the important configuration to metrics Use the address instead of the store ID for metrics Make the heartbeat more reasonable in the simulator Fixed Fix the issue that the low space store cannot be added in PD Fix several typos #1451 #4305 New contributors (Thanks!) tikv: wujunze pd: blankstars parser: sunxiaoguang "}, {"url": "https://pingcap.com/blog-cn/tikv-source-code-reading-3/", "title": "TiKV 源码解析系列文章(三)Prometheus(上)", "content": " 本文为 TiKV 源码解析系列的第三篇,继续为大家介绍 TiKV 依赖的周边库 rust-prometheus,本篇主要介绍基础知识以及最基本的几个指标的内部工作机制,下篇会介绍一些高级功能的实现原理。rust-prometheus 是监控系统 Prometheus 的 Rust 客户端库,由 TiKV 团队实现。TiKV 使用 rust-prometheus 收集各种指标(metric)到 Prometheus 中,从而后续能再利用 Grafana 等可视化工具将其展示出来作为仪表盘监控面板。这些监控指标对于了解 TiKV 当前或历史的状态具有非常关键的作用。TiKV 提供了丰富的监控指标数据,并且代码中也到处穿插了监控指标的收集片段,因此了解 rust-prometheus 很有必要。感兴趣的小伙伴还可以观看我司同学在 FOSDEM 2019 会议上关于 rust-prometheus 的技术分享。 基础知识 指标类别 Prometheus 支持四种指标:Counter、Gauge、Histogram、Summary。rust-prometheus 库目前还只实现了前三种。TiKV 大部分指标都是 Counter 和 Histogram,少部分是 Gauge。Counter Counter 是最简单、常用的指标,适用于各种计数、累计的指标,要求单调递增。Counter 指标提供基本的 inc() 或 inc_by(x) 接口,代表增加计数值。在可视化的时候,此类指标一般会展示为各个时间内增加了多少,而不是各个时间计数器值是多少。例如 TiKV 收到的请求数量就是一种 Counter 指标,在监控上展示为 TiKV 每时每刻收到的请求数量图表(QPS)。Gauge Gauge 适用于上下波动的指标。Gauge 指标提供 inc()、dec()、add(x)、sub(x) 和 set(x) 接口,都是用于更新指标值。这类指标可视化的时候,一般就是直接按照时间展示它的值,从而展示出这个指标按时间是如何变化的。例如 TiKV 占用的 CPU 率是一种 Gauge 指标,在监控上所展示的直接就是 CPU 率的上下波动图表。Histogram Histogram 即直方图,是一种相对复杂但同时也很强大的指标。Histogram 除了基本的计数以外,还能计算分位数。Histogram 指标提供 observe(x) 接口,代表观测到了某个值。举例来说,TiKV 收到请求后处理的耗时就是一种 Histogram 指标,通过 Histogram 类型指标,监控上可以观察 99%、99.9%、平均请求耗时等。这里显然不能用一个 Counter 存储耗时指标,否则展示出来的只是每时每刻中 TiKV 一共花了多久处理,而非单个请求处理的耗时情况。当然,机智的你可能想到了可以另外开一个 Counter 存储请求数量指标,这样累计请求处理时间除以请求数量就是各个时刻平均请求耗时了。实际上,这也正是 Prometheus 中 Histogram 的内部工作原理。Histogram 指标实际上最终会提供一系列时序数据: 观测值落在各个桶(bucket)上的累计数量,如落在 (-∞, 0.1]、(-∞, 0.2]、(-∞, 0.4]、(-∞, 0.8]、(-∞, 1.6]、(-∞, +∞) 各个区间上的数量。 观测值的累积和。 观测值的个数。 bucket 是 Prometheus 对于 Histogram 观测值的一种简化处理方式。Prometheus 并不会具体记录下每个观测值,而是只记录落在配置的各个 bucket 区间上的观测值的数量,这样以牺牲一部分精度的代价大大提高了效率。Summary Summary 与 Histogram 类似,针对观测值进行采样,但分位数是在客户端进行计算。该类型的指标目前在 rust-prometheus 中没有实现,因此这里不作进一步详细介绍。大家可以阅读 Prometheus 官方文档中的介绍了解详细情况。感兴趣的同学也可以参考其他语言 Client Library 的实现为 rust-prometheus 贡献代码。标签 Prometheus 的每个指标支持定义和指定若干组标签(Label),指标的每个标签值独立计数,表现了指标的不同维度。例如,对于一个统计 HTTP 服务请求耗时的 Histogram 指标来说,可以定义并指定诸如 HTTP Method(GET / POST / PUT / …)、服务 URL、客户端 IP 等标签。这样可以轻易满足以下类型的查询: 查询 Method 分别为 POST、PUT、GET 的 99.9% 耗时(利用单一 Label) 查询 POST /api 的平均耗时(利用多个 Label 组合) 普通的查询诸如所有请求 99.9% 耗时也能正常工作。需要注意的是,不同标签值都是一个独立计数的时间序列,因此应当避免标签值或标签数量过多,否则实际上客户端会向 Prometheus 服务端传递大量指标,影响效率。与 Prometheus Golang client 类似,在 rust-prometheus 中,具有标签的指标被称为 Metric Vector。例如 Histogram 指标对应的数据类型是 Histogram,而具有标签的 Histogram 指标对应的数据类型是 HistogramVec。对于一个 HistogramVec,提供它的各个标签取值后,可获得一个 Histogram 实例。不同标签取值会获得不同的 Histogram 实例,各个 Histogram 实例独立计数。基本用法 本节主要介绍如何在项目中使用 rust-prometheus 进行各种指标收集。使用基本分为三步: 定义想要收集的指标。 在代码特定位置调用指标提供的接口收集记录指标值。 实现 HTTP Pull Service 使得 Prometheus 可以定期访问收集到的指标,或使用 rust-prometheus 提供的 Push 功能定期将收集到的指标上传到 Pushgateway。 注意,以下样例代码都是基于本文发布时最新的 rust-prometheus 0.5 版本 API。我们目前正在设计并实现 1.0 版本,使用上会进一步简化,但以下样例代码可能在 1.0 版本发布后过时、不再工作,届时请读者参考最新的文档。 定义指标 为了简化使用,一般将指标声明为一个全局可访问的变量,从而能在代码各处自由地操纵它。rust-prometheus 提供的各个指标(包括 Metric Vector)都满足 Send + Sync,可以被安全地全局共享。以下样例代码借助 lazy_static 库定义了一个全局的 Histogram 指标,该指标代表 HTTP 请求耗时,并且具有一个标签名为 method:#[macro_use] extern crate prometheus; lazy_static! { static ref REQUEST_DURATION: HistogramVec = register_histogram_vec!( "http_requests_duration", "Histogram of HTTP request duration in seconds", &["method"], exponential_buckets(0.005, 2.0, 20).unwrap() ).unwrap(); } 记录指标值 有了一个全局可访问的指标变量后,就可以在代码中通过它提供的接口记录指标值了。在“基础知识”中介绍过,Histogram 最主要的接口是 observe(x),可以记录一个观测值。若想了解 Histogram 其他接口或其他类型指标提供的接口,可以参阅 rust-prometheus 文档。以下样例在上段代码基础上展示了如何记录指标值。代码模拟了一些随机值用作指标,装作是用户产生的。在实际程序中,这些当然得改成真实数据 :)fn thread_simulate_requests() { let mut rng = rand::thread_rng(); loop { // Simulate duration 0s ~ 2s let duration = rng.gen_range(0f64, 2f64); // Simulate HTTP method let method = ["GET", "POST", "PUT", "DELETE"].choose(&mut rng).unwrap(); // Record metrics REQUEST_DURATION.with_label_values(&[method]).observe(duration); // One request per second std::thread::sleep(std::time::Duration::from_secs(1)); } } Push / Pull 到目前为止,代码还仅仅是将指标记录了下来。最后还需要让 Prometheus 服务端能获取到记录下来的指标数据。这里一般有两种方式,分别是 Push 和 Pull。 Pull 是 Prometheus 标准的获取指标方式,Prometheus Server 通过定期访问应用程序提供的 HTTP 接口获取指标数据。 Push 是基于 Prometheus Pushgateway 服务提供的另一种获取指标方式,指标数据由应用程序主动定期推送给 Pushgateway,然后 Prometheus 再定期从 Pushgateway 获取。这种方式主要适用于应用程序不方便开端口或应用程序生命周期比较短的场景。 以下样例代码基于 hyper HTTP 库实现了一个可以供 Prometheus Server pull 指标数据的接口,核心是使用 rust-prometheus 提供的 TextEncoder 将所有指标数据序列化供 Prometheus 解析:fn metric_service(_req: Request<Body>) -> Response<Body> { let encoder = TextEncoder::new(); let mut buffer = vec![]; let mf = prometheus::gather(); encoder.encode(&mf, &mut buffer).unwrap(); Response::builder() .header(hyper::header::CONTENT_TYPE, encoder.format_type()) .body(Body::from(buffer)) .unwrap() } 对于如何使用 Push 感兴趣的同学可以自行参考 rust-prometheus 代码内提供的 Push 示例,这里限于篇幅就不详细介绍了。上述三段样例的完整代码可参见这里。内部实现 以下内部实现都基于本文发布时最新的 rust-prometheus 0.5 版本代码,该版本主干 API 的设计和实现 port 自 Prometheus Golang client,但为 Rust 的使用习惯进行了一些修改,因此接口上与 Golang client 比较接近。目前我们正在开发 1.0 版本,API 设计上不再主要参考 Golang client,而是力求提供对 Rust 使用者最友好、简洁的 API。实现上为了效率考虑也会和这里讲解的略微有一些出入,且会去除一些目前已被抛弃的特性支持,简化实现,因此请读者注意甄别。Counter / Gauge Counter 与 Gauge 是非常简单的指标,只要支持线程安全的数值更新即可。读者可以简单地认为 Counter 和 Gauge 的核心实现都是 Arc<Atomic>。但由于 Prometheus 官方规定指标数值需要支持浮点数,因此我们基于 std::sync::atomic::AtomicU64 和 CAS 操作实现了 AtomicF64,其具体实现位于 src/atomic64/nightly.rs。核心片段如下:impl Atomic for AtomicF64 { type T = f64; // Some functions are omitted. fn inc_by(&self, delta: Self::T) { loop { let current = self.inner.load(Ordering::Acquire); let new = u64_to_f64(current) + delta; let swapped = self .inner .compare_and_swap(current, f64_to_u64(new), Ordering::Release); if swapped == current { return; } } } } 另外由于 0.5 版本发布时 AtomicU64 仍然是一个 nightly 特性,因此为了支持 Stable Rust,我们还基于自旋锁提供了 AtomicF64 的 fallback,位于 src/atomic64/fallback.rs。 注:AtomicU64 所需的 integer_atomics 特性最近已在 rustc 1.34.0 stabilize。我们将在 rustc 1.34.0 发布后为 Stable Rust 也使用上原生的原子操作从而提高效率。 Histogram 根据 Prometheus 的要求,Histogram 需要进行的操作是在获得一个观测值以后,为观测值处在的桶增加计数值。另外还有总观测值、观测值数量需要累加。注意,Prometheus 中的 Histogram 是累积直方图,其每个桶的含义是 (-∞, x],因此对于每个观测值都可能要更新多个连续的桶。例如,假设用户定义了 5 个桶边界,分别是 0.1、0.2、0.4、0.8、1.6,则每个桶对应的数值范围是 (-∞, 0.1]、(-∞, 0.2]、(-∞, 0.4]、(-∞, 0.8]、(-∞, 1.6]、(-∞, +∞),对于观测值 0.4 来说需要更新(-∞, 0.4]、(-∞, 0.8]、(-∞, 1.6]、(-∞, +∞) 四个桶。一般来说 observe(x) 会被频繁地调用,而将收集到的数据反馈给 Prometheus 则是个相对很低频率的操作,因此用数组实现“桶”的时候,我们并不将各个桶与数组元素直接对应,而将数组元素定义为非累积的桶,如 (-∞, 0.1)、[0.1, 0.2)、[0.2, 0.4)、[0.4, 0.8)、[0.8, 1.6)、[1.6, +∞),这样就大大减少了需要频繁更新的数据量;最后在上报数据给 Prometheus 的时候将数组元素累积,得到累积直方图,这样就得到了 Prometheus 所需要的桶的数据。当然,由此可见,如果给定的观测值超出了桶的范围,则最终记录下的最大值只有桶的上界了,然而这并不是实际的最大值,因此使用的时候需要多加注意。Histogram 的核心实现见 src/histogram.rs:pub struct HistogramCore { // Some fields are omitted. sum: AtomicF64, count: AtomicU64, upper_bounds: Vec<f64>, counts: Vec<AtomicU64>, } impl HistogramCore { // Some functions are omitted. pub fn observe(&self, v: f64) { // Try find the bucket. let mut iter = self .upper_bounds .iter() .enumerate() .filter(|&(_, f)| v <= *f); if let Some((i, _)) = iter.next() { self.counts[i].inc_by(1); } self.count.inc_by(1); self.sum.inc_by(v); } } #[derive(Clone)] pub struct Histogram { core: Arc<HistogramCore>, } Histogram 还提供了一个辅助结构 HistogramTimer,它会记录从它创建直到被 Drop 的时候的耗时,将这个耗时作为 Histogram::observe() 接口的观测值记录下来,这样很多时候在想要记录 Duration / Elapsed Time 的场景中,就可以使用这个简便的结构来记录时间:#[must_use] pub struct HistogramTimer { histogram: Histogram, start: Instant, } impl HistogramTimer { // Some functions are omitted. pub fn observe_duration(self) { drop(self); } fn observe(&mut self) { let v = duration_to_seconds(self.start.elapsed()); self.histogram.observe(v) } } impl Drop for HistogramTimer { fn drop(&mut self) { self.observe(); } } HistogramTimer 被标记为了 must_use,原因很简单,作为一个记录流逝时间的结构,它应该被存在某个变量里,从而记录这个变量所处作用域的耗时(或稍后直接调用相关函数提前记录耗时),而不应该作为一个未使用的临时变量被立即 Drop。标记为 must_use 可以在编译期杜绝这种明显的使用错误。"}, {"url": "https://pingcap.com/weekly/2019-03-04-tidb-weekly/", "title": "Weekly update (February 25 ~ March 03, 2019)", "content": " Weekly update in TiDB Last week, we landed 63 PRs in the TiDB repository.Added Support the RANK and DENSE_RANK window functions Add the CREATE ROLE support Support range framed window functions Compute and store the column order correlation with a handle Improved Improve the performance of parsing date formatted values Improve the compatibility of modifying the column charset with MySQL Restrict the adjusting factor for the index feedback Control the chunk size for the selection and projection operators Handle default values for generated columns when adding an index Only cast aggregation’s argument types when necessary Add the error count limit for DDL jobs Disallow generated columns to refer to columns with the AUTO_INCREMENT flag Reduce lock contention of the statistics collector Write the binlog record when the DDL job state is done Check the column field length when adding a column Fixed Fix the rollback logic for the LOAD DATA statement Fix the wrong default value for time columns Only show valid columns in SHOW stats_histograms Fix a timezone misusage bug when converting the timestamp constant expression to the protobuf Fix the infinite retry issue when the sleep time is larger than max-txn-time Remove correlated columns from the items of the ORDER BY clause Only read the transaction buffer for dirty rows in UnionScan Fix a bug of modifying the column flag from null to not null Refine Compare methods of Merge Join to avoid incorrect results Fix wrong behaviors of PREPARE and EXECUTE Make Semi Join null- and empty-aware Fix a wrong result of UnionScan on partitioned tables Weekly update in TiSpark Last week, we landed 2 PRs in the TiSpark repository.Improved Skip LockResolverTest when the PD client is not present Weekly update in TiKV and PD Last week, we landed 36 PRs in the TiKV and PD repositories.Added Add thread context switch metrics Add a command line parameter to specify the metrics push address Add the encryption support for Importer Add make audit Add metrics for the key exceeding bound Add the batch executor interface Improved Use the dyn keyword for traits objects Direct the local reader for raw read Remove the Tokio runtime Implement encoding functions for VectorValue and LazyBatchColumn(Vec) Fixed Fix the encrypted file generated issue Fix the scatter range NPE when getting storeInfo of a non-existing store Fix the issue of getting tombstone stores Fix the merge panic when receiving multiple snapshots Fix range properties New contributors (Thanks!) tidb: guilhermehubner tikv: crlf0710 "}, {"url": "https://pingcap.com/meetup/meetup-89-20190227/", "title": "【Infra Meetup No.89】TiKV 最新性能优化", "content": "在上周六举办的 Infra Meetup 上,TiKV 研发工程师屈鹏为大家介绍了 TiKV 最新性能优化。现场讨论非常热烈,分享结束后还有很多小伙伴意犹未尽,留在现场和讲师交流。欢迎大家多来参加 Meetup 感受现场交流的乐趣哦~ 以下是本期 Meetup 的文字 & 视频回顾,enjoy! 讲师介绍:屈鹏,2017 年加入 PingCAP,TiKV 研发工程师。专注于分布式数据库领域,擅长 Raft 及 TiKV 的性能优化。 视频 | Infra Meetup No.89:TiKV 最新性能优化 PPT 链接 屈鹏老师首先为大家介绍了 TiKV 最新版本的 3 个新的优化: batch gRPC/Raft messages 特性可以将消息收集为一个 batch 批量发送,减少了网络相关的系统调用次数,达到了性能上的提升。 threaded raftstore/apply 特性将之前系统中的两个单线程组件替换为多线程,同时避免了数据倾斜和饥饿,消除了 TiKV 在写入上的瓶颈。 distributed GC 大幅重构了 GC 相关的代码,GC 的驱动者由客户端变成了 TiKV 自己,简化了客户端的编写难度,同时将 GC 速度加快了 3 倍。 最后屈鹏老师分享了几个正在开发中的优化,包括事务提交不取 timestamp 等等。 PingCAP Infra Meetup作为一个基础架构领域的前沿技术公司,PingCAP 希望能为国内真正关注技术本身的 Hackers 打造一个自由分享的平台。自 2016 年 3 月 5 日开始,我们定期在周末举办 Infra Meetup,与大家深度探讨基础架构领域的前瞻性技术思考与经验,目前已在北京、上海、广州、成都、杭州等地举办。在这里,我们希望提供一个高水准的前沿技术讨论空间,让大家真正感受到自由的开源精神魅力。 "}, {"url": "https://pingcap.com/weekly/2019-02-25-tidb-weekly/", "title": "Weekly update (February 18 ~ February 24, 2019)", "content": " Weekly update in TiDB Last week, we landed 75 PRs in the TiDB repository.Added Add system tables for the upcoming RBAC feature Add the log_bin system variable Implement the skyline pruning optimization Add the FlushTiDBPlugin support Add the ShowCreateView support Support the NAME_CONST built-in function Add the SHOW CREATE USER support Support the ROW_NUMBER() window function Add an HTTP API to control whether the check is enabled when the inserted column data is 4-byte UTF-8 characters Support ALTER TABLE ALGORITHM = INPLACE/INSTANT Improved Add the table ID into statistics of columns Improve the compatibility of RAND with MySQL Improve the compatibility of DDL errors with MySQL Control the number of rows returned by TopN/Sort in one chunk Support row framed window functions Control the number of rows returned by Limit in one chunk Change the cost formula for join reordering Handle Cancel properly for DDL jobs Push down the check condition to TiKV for the Prewrite request Refactor constant folding for IF/IFNULL Handle PARTITION BY RANGE COLUMNS for a single column Collect the Coprocessor runtime statistics for EXPLAIN ANALYZE Fixed Get the correct SHOW CREATE TABLE result when adding an index Merge the statement buffer in BatchGetValues Show the correct port used by the TiDB server Fix the unique key constraint check for CREATE TABLE Make the value of tidb_force_priority consistent with the configuration value Fix a panic caused by the ORDER BY clause Fix a panic caused by the timestamp in range partition Check the unique key constraint for ALTER TABLE ADD INDEX on partitioned tables Fix a wrong default value for the timestamp in multiple time zones Weekly update in TiSpark Last week, we landed 3 PRs in the TiSpark repository.Added Change the default spark.version to 2.3.3 Fixed Fix the issue of building key ranges with the XOR expression Weekly update in TiKV and PD Last week, we landed 21 PRs in the TiKV and PD repositories.Added Prewrite only when keys do not exist Support encryption for the data stored in the disk Improved Bind the port lazily when it is configured Make the batch strategy fairer New contributors (Thanks!) tidb: aliiohs ltg001 tikv: JoeWrightss docs-cn: ZhaoQi99 pcqz "}, {"url": "https://pingcap.com/weekly/2019-02-18-tidb-weekly/", "title": "Weekly update (February 11 ~ February 17, 2019)", "content": " Weekly update in TiDB Last week, we landed 50 PRs in the TiDB repository.Added Support the BENCHMARK() built-in function Add count metrics for statistics feedback indicating estimation inaccuracy Propose the design for skyline pruning Add the privilege check for querying View Add the privilege check for creating View Add metrics reflecting QPS at the database level Add benchmarks utilities for aggregation Add the JSON_QUOTE() built-in function Improved Improve the compatibility of the error code with MySQL Improve the compatibility of the error message with MySQL Handle DNF expressions when computing selectivity Reduce the memory usage when inserting many rows in one transaction Refine the Explain output for Window functions Improve the compatibility of SHOW CREATE TABLE with MySQL Handle the enum type in the VALUES() built-in function Extract the Scalar function to a Projection operator for computing Make the batch gRPC strategy self-adaptable Improve the compatibility of DATA_ADD with MySQL Fixed Fix the wrong result when converting “0000” to the year type Fix precision of default values of the time type Make the hach.String() function safe Handle errors when adding an index to a generated column Back off when the requested Region epoch is ahead of the Region meta returned from TiKV Fix the panic when the Equal condition of Join contains the Scalar function Weekly update in TiSpark Last week, we landed 2 PRs in the TiSpark repository.Fixed Fix the reading data bug when using an index on the partition table Weekly update in TiKV and PD Last week, we landed 16 PRs in the TiKV and PD repositories.Added Add checked_add and check_sub Coprocessor pushdown functions Add the sub_duration_and_duration Coprocessor pushdown function Add code of conduct for contributors Improved Enable auto compactions in the import mode New contributors (Thanks!) tidb: haplone overbool u5surf wuudjac tikv: caohe parser: eiantee docs-cn: bigzuo "}, {"url": "https://pingcap.com/blog-cn/tikv-source-code-reading-2/", "title": "TiKV 源码解析系列文章(二)raft-rs proposal 示例情景分析", "content": " 本文为 TiKV 源码解析系列的第二篇,按照计划首先将为大家介绍 TiKV 依赖的周边库 raft-rs 。raft-rs 是 Raft 算法的 Rust 语言实现。Raft 是分布式领域中应用非常广泛的一种共识算法,相比于此类算法的鼻祖 Paxos,具有更简单、更容易理解和实现的特点。分布式系统的共识算法会将数据的写入复制到多个副本,从而在网络隔离或节点失败的时候仍然提供可用性。具体到 Raft 算法中,发起一个读写请求称为一次 proposal。本文将以 raft-rs 的公共 API 作为切入点,介绍一般 proposal 过程的实现原理,让用户可以深刻理解并掌握 raft-rs API 的使用, 以便用户开发自己的分布式应用,或者优化、定制 TiKV。文中引用的代码片段的完整实现可以参见 raft-rs 仓库中的 source-code 分支。Public API 简述 仓库中的 examples/five_mem_node/main.rs 文件是一个包含了主要 API 用法的简单示例。它创建了一个 5 节点的 Raft 系统,并进行了 100 个 proposal 的请求和提交。经过进一步精简之后,主要的类型封装和运行逻辑如下:struct Node { // 持有一个 RawNode 实例 raft_group: Option<RawNode<MemStorage>>, // 接收其他节点发来的 Raft 消息 my_mailbox: Receiver<Message>, // 发送 Raft 消息给其他节点 mailboxes: HashMap<u64, Sender<Message>>, } let mut t = Instant::now(); // 在 Node 实例上运行一个循环,周期性地处理 Raft 消息、tick 和 Ready。 loop { thread::sleep(Duration::from_millis(10)); while let Ok(msg) = node.my_mailbox.try_recv() { // 处理收到的 Raft 消息 node.step(msg); } let raft_group = match node.raft_group.as_mut().unwrap(); if t.elapsed() >= Duration::from_millis(100) { raft_group.tick(); t = Instant::now(); } // 处理 Raft 产生的 Ready,并将处理进度更新回 Raft 中 let mut ready = raft_group.ready(); persist(ready.entries()); // 处理刚刚收到的 Raft Log send_all(ready.messages); // 将 Raft 产生的消息发送给其他节点 handle_committed_entries(ready.committed_entries.take()); raft_group.advance(ready); } 这段代码中值得注意的地方是: RawNode 是 raft-rs 库与应用交互的主要界面。要在自己的应用中使用 raft-rs,首先就需要持有一个 RawNode 实例,正如 Node 结构体所做的那样。 RawNode 的范型参数是一个满足 Storage 约束的类型,可以认为是一个存储了 Raft Log 的存储引擎,示例中使用的是 MemStorage。 在收到 Raft 消息之后,调用 RawNode::step 方法来处理这条消息。 每隔一段时间(称为一个 tick),调用 RawNode::tick 方法使 Raft 的逻辑时钟前进一步。 使用 RawNode::ready 接口从 Raft 中获取收到的最新日志(Ready::entries),已经提交的日志(Ready::committed_entries),以及需要发送给其他节点的消息等内容。 在确保一个 Ready 中的所有进度被正确处理完成之后,调用 RawNode::advance 接口。 接下来的几节将展开详细描述。Storage trait Raft 算法中的日志复制部分抽象了一个可以不断追加写入新日志的持久化数组,这一数组在 raft-rs 中即对应 Storage。使用一个表格可以直观地展示这个 trait 的各个方法分别可以从这个持久化数组中获取哪些信息: 方法 描述 initial_state 获取这个 Raft 节点的初始化信息,比如 Raft group 中都有哪些成员等。这个方法在应用程序启动时会用到。 entries 给定一个范围,获取这个范围内持久化之后的 Raft Log。 term 给定一个日志的下标,查看这个位置的日志的 term。 first_index 由于数组中陈旧的日志会被清理掉,这个方法会返回数组中未被清理掉的最小的位置。 last_index 返回数组中最后一条日志的位置。 snapshot 返回一个 Snapshot,以便发送给日志落后过多的 Follower。 值得注意的是,这个 Storage 中并不包括持久化 Raft Log,也不会将 Raft Log 应用到应用程序自己的状态机的接口。这些内容需要应用程序自行处理。RawNode::step 接口 这个接口处理从该 Raft group 中其他节点收到的消息。比如,当 Follower 收到 Leader 发来的日志时,需要把日志存储起来并回复相应的 ACK;或者当节点收到 term 更高的选举消息时,应该进入选举状态并回复自己的投票。这个接口和它调用的子函数的详细逻辑几乎涵盖了 Raft 协议的全部内容,代码较多,因此这里仅阐述在 Leader 上发生的日志复制过程。当应用程序希望向 Raft 系统提交一个写入时,需要在 Leader 上调用 RawNode::propose 方法,后者就会调用 RawNode::step,而参数是一个类型为 MessageType::MsgPropose 的消息;应用程序要写入的内容被封装到了这个消息中。对于这一消息类型,后续会调用 Raft::step_leader 函数,将这个消息作为一个 Raft Log 暂存起来,同时广播到 Follower 的信箱中。到这一步,propose 的过程就可以返回了,注意,此时这个 Raft Log 并没有持久化,同时广播给 Follower 的 MsgAppend 消息也并未真正发出去。应用程序需要设法将这个写入挂起,等到从 Raft 中获知这个写入已经被集群中的过半成员确认之后,再向这个写入的发起者返回写入成功的响应。那么, 如何能够让 Raft 把消息真正发出去,并接收 Follower 的确认呢?RawNode::ready 和 RawNode::advance 接口 这个接口返回一个 Ready 结构体:pub struct Ready { pub committed_entries: Option<Vec<Entry>>, pub messages: Vec<Message>, // some other fields... } impl Ready { pub fn entries(&self) -> &[Entry] { &self.entries } // some other methods... } 一些暂时无关的字段和方法已经略去,在 propose 过程中主要用到的方法和字段分别是: 方法/字段 作用 entries(方法) 取出上一步发到 Raft 中,但尚未持久化的 Raft Log。 committed_entries 取出已经持久化,并经过集群确认的 Raft Log。 messages 取出 Raft 产生的消息,以便真正发给其他节点。 对照 examples/five_mem_node/main.rs 中的示例,可以知道应用程序在 propose 一个消息之后,应该调用 RawNode::ready 并在返回的 Ready 上继续进行处理:包括持久化 Raft Log,将 Raft 消息发送到网络上等。而在 Follower 上,也不断运行着示例代码中与 Leader 相同的循环:接收 Raft 消息,从 Ready 中收集回复并发回给 Leader……对于 propose 过程而言,当 Leader 收到了足够的确认这一 Raft Log 的回复,便能够认为这一 Raft Log 已经被确认了,这一逻辑体现在 Raft::handle_append_response 之后的 Raft::maybe_commit 方法中。在下一次这个 Raft 节点调用 RawNode::ready 时,便可以取出这部分被确认的消息,并应用到状态机中了。在将一个 Ready 结构体中的内容处理完成之后,应用程序即可调用这个方法更新 Raft 中的一些进度,包括 last index、commit index 和 apply index 等。RawNode::tick 接口 这是本文最后要介绍的一个接口,它的作用是驱动 Raft 内部的逻辑时钟前进,并对超时进行处理。比如对于 Follower 而言,如果它在 tick 的时候发现 Leader 已经失联很久了,便会发起一次选举;而 Leader 为了避免自己被取代,也会在一个更短的超时之后给 Follower 发送心跳。值得注意的是,tick 也是会产生 Raft 消息的,为了使这部分 Raft 消息能够及时发送出去,在应用程序的每一轮循环中一般应该先处理 tick,然后处理 Ready,正如示例程序中所做的那样。总结 最后用一张图展示在 Leader 上是通过哪些 API 进行 propose 的:本期关于 raft-rs 的源码解析就到此结束了,我们非常鼓励大家在自己的分布式应用中尝试 raft-rs 这个库,同时提出宝贵的意见和建议。后续关于 raft-rs 我们还会深入介绍 Configuration Change 和 Snapshot 的实现与优化等内容,展示更深入的设计原理、更详细的优化细节,方便大家分析定位 raft-rs 和 TiKV 使用中的潜在问题。"}, {"url": "https://pingcap.com/success-stories/tidb-in-shopee/", "title": "Shopping on Shopee, the TiDB Way", "content": " Industry: E-CommerceAuthors: Chunhui Liu and Chao Hong (Database Administrators at Shopee)Shopee is the leading e-commerce platform in Southeast Asia and Taiwan. It is a platform tailored for the region, providing customers with an easy, secure and fast online shopping experience through strong payment and logistical support.Shopee aims to continually enhance its platform and become the region’s e-commerce destination of choice. Shopee has a wide selection of product categories ranging from consumer electronics to home & living, health & beauty, baby & toys, fashion and fitness equipment.Shopee, a Sea company, was first launched in Singapore in 2015, and has since expanded its reach to Malaysia, Thailand, Taiwan, Indonesia, Vietnam and the Philippines. Sea is a leader in digital entertainment, e-commerce and digital financial services across Greater Southeast Asia. Sea’s mission is to better the lives of consumers and small businesses with technology, and is listed on the NYSE under the symbol SE. Figure 1: The Shopee website As our business boomed, our team faced severe challenges in scaling our backend system to meet the demand. Fortunately, we found TiDB, a MySQL-compatible NewSQL hybrid transactional and analytical processing (HTAP) database, built and supported by PingCAP and its open-source community. Now we can provide better service and experience for our users without worrying about our database capacity.Currently, we have deployed two TiDB clusters totalling 60 nodes in our risk control and audit log systems. In this post, we will elaborate on why we chose TiDB, how we are using it, and our future plan for TiDB inside our infrastructure.What Is TiDB? TiDB in a nutshell is a platform comprised of multiple components that when used together becomes a NewSQL database that has HTAP capabilities. Figure 2: TiDB platform architecture Inside the TiDB platform, the main components are as follows: TiDB Server is a stateless SQL layer that processes users’ SQL queries, accesses data in the storage layer, and returns corresponding results to the application. It is MySQL compatible and sits on top of TiKV. TiKV is the distributed transactional Key-Value storage layer where the data persists. It uses the Raft consensus protocol for replication to ensure strong data consistency and high availability. TiSpark cluster also sits on top of TiKV. It is an Apache Spark plugin that works with the TiDB platform to support complex OLAP queries for BI analysts and data scientists. Placement Driver (PD): A metadata cluster powered by etcd that manages and schedules TiKV. Beyond these main components, TiDB also has an ecosystem of tools, such as Ansible scripts for quick deployment, Syncer and DM for seamless migration from MySQL, and TiDB-Binlog, which is used to ​collect the logical changes made to a TiDB cluster and provide incremental backup and replication to the downstream (TiDB, Kafka or MySQL)​.Our Pain Point Our risk control system detects abnormal behaviors and fraudulent transactions from our internal logs of orders and user behaviors. This log was stored in MySQL and sharded into 100 tables based on USER_ID. Figure 3: Data collection and processing in the risk control system This approach began to show its limitations as the major shopping season approached. During one major 2018 promotion event, the number of orders exceeded 11 million, 4.5 times larger than the previous year. On December 12, 2018, another major online shopping day known as “Double 12”, the number of orders reached an all-time high in Shopee history – 12 million.Although we enabled InnoDB Transparent Page Compression to compress the data size by half and increased the MySQL server storage space from 2.5TB to 6TB as a temporary fix, we knew we needed a horizontally scalable database solution for the long term.Evaluation Before choosing TiDB, we carefully researched and compared the pros and cons of MySQL sharding solution with the TiDB platform. Here was what our comparison found.MySQL Sharding To continue with our current sharding solution, we would need to re-shard the data in the existing 100 tables to 1,000 or perhaps 10,000 tables and then distribute these shards among different MySQL instances.Advantage: Our R&D and DBA (database administrator) teams are experienced with MySQL sharding. Disadvantages: The application code becomes complex and difficult to maintain. We code in Golang and Python and have deployed MySQL in several systems of Shopee. To support the new sharding rules, each system would need more code refactoring. Changing existing sharding key is troublesome. We must specify sharding key manually and carefully because it controls how the data is distributed across the shards. Changing an existing sharding key might lead to serious problems. In addition, cross-shard distributed transactions would not be supported. Upgrading application logic affects application usability. Mounting data often triggers a hung database, because it demands frequent table schema changes to perform DDL (Data Definition Language) operations. This impacts our application’s usability and even causes data inconsistency. TiDB Advantages: Supports elastic horizontal scalability. We can scale the database horizontally simply by adding new TiKV nodes when the data size increases. Guarantees strong data consistency with auto partitioning. All the data in TiDB is automatically partitioned into smaller chunks, and automatically replicated and made strongly consistent by executing the Raft Consensus Algorithm. This implementation saves us time and reduces operational cost of manually partitioning and managing tables. Highly compatible with the MySQL protocol. We can use TiDB as if we were using MySQL, almost like “a MySQL with infinite capacity”, so to speak. This compatibility also lowers migration cost and keeps our application development productive because developers do not have to learn a new database. Supports online DDL. We can add new columns and indices without downtime. Disadvantage: Our team has no experience in TiDB. After a thorough evaluation, we decided to be an early adopter of TiDB, given all its attractive advantages, and TiDB turned out to be a perfect match for our services. Here​ is​ how we migrated ​the traffic ​and ​how we are ​currently using​ TiDB in our system.Migration from MySQL to TiDB Our traffic migration process from MySQL to TiDB is as follows: Performed doublewrite on the application. The log data was written into MySQL and TiDB at the same time so that the data in the two databases were consistent. Migrated the old data from MySQL to TiDB and verified it to ensure the data in the two databases was consistent. Migrated the read-only traffic from MySQL to TiDB gradually. (See Figure 4 below) Stopped doublewrite and from this point, the application traffic was completely migrated to TiDB. Figure 4: Traffic migration process During the traffic migration process, we benefited a great deal from the doublewrite approach: We acquired enough time to get familiar with the TiDB behavior. Because of the doublewrite approach, the traffic switching process took several months, during which our R&D and DBA teams were able to learn more about how TiDB works and behaves. Before stopping the doublewrite operation, the application traffic could roll back to MySQL if TiDB encountered some issues, making the migration process relatively risk free. It gave us an opportunity to refactor the database. Shopee users are distributed among 7 regions. Before the application traffic was migrated to TiDB, all risk control logs were stored in the same logical database. This time we stored the log data into 7 different logical databases (rc_sg, rc_my, rc_ph, …, rc_tw) according to user regions. This way, we could customize a specific data structure for each region. Download TiDB Subscribe to Blog Deployment Set Up Initially, we migrated 4TB of data from MySQL …"}, {"url": "https://pingcap.com/blog/fosdem-2019-recap-global-technology-local-community/", "title": "FOSDEM 2019 Recap: Global Technology, Local Community", "content": " On the heels of a successful TiDB DevCon in Beijing, where more than 700 people attended this annual TiDB user conference, Team PingCAP trekked halfway around the world to Brussels, Belgium, to participate in the largest developer conference in Europe, FOSDEM. We had three talks to deliver, one in the MySQL, MariaDB and Friends DevRoom and two in the Rust DevRoom. Even though TiDB as an open-source NewSQL database is growing in popularity and global reach, our team knows that a truly global technology hinges on the support of local communities. After all, globalization is localization, and there’s no better place to “localize” a technology for thousands of developers than FOSDEM.Early Bird Gets the Beer Our FOSDEM activities got started early with the pre-FOSDEM MySQL community day, where technologists from across the MySQL community gathered for a day of technical talks about the present and future of MySQL. As the team that created and drives the development of TiDB, which is MySQL compatible, we were attentive, engaged, and eager to learn.A full day of technical presentations was followed by a festive community dinner, an annual pre-FOSDEM tradition, where attendees continue their technical (and non-technical) discussion over (16 kinds of) Belgian beers. I had the enviable job of “bartending” the event, which meant pouring beers in the proper Belgian way for a room of people who were both MySQL experts and beer aficionados. Me pouring Belgian beers at the MySQL community dinner Day 1 - MySQL DevRoom The official first day of FOSDEM 2019 was cold and snowy, and we made the unwise decision of walking to the Université Libre de Bruxelles to get some exercise. What resulted was wet shoes and cold feet for the whole day. But that didn’t dampen our spirit – the sight of more than 8000 developers gathered from around the world was energizing. As a FOSDEM rookie, the sheer number of talks, topics, and people overwhelmed me. I immediately gave up trying to go to all the talks I found interesting on the schedule; it was physically impossible to go to them all!I stuck around mostly in the MySQL DevRoom to deepen my learning from the previous day, and to cheer on my colleague Morgan Tocker, who gave a fantastic presentation on TiDB’s horizontal scalability and MySQL compatibility. Even though we had a flooding incident that forced us to move to a different room two hours before his talk, close to 100 people still came to learn about TiDB.If you missed Morgan’s presentation, here’s the recording. Download TiDB Subscribe to Blog Day 2 - Rust DevRoom The second day of FOSDEM was less snowy, but no less crowded. The Rust DevRoom was especially so given the growing popularity of the programming language. As one of the largest production Rust users, our team had a lot of learning to share with the community, and the organizers were generous in giving us two presentation slots.Our first talk was a joint presentation by Ana Hobden and Jay Lee, two of our database engineers, in front of a jam-packed room of almost 200 Rustaceans. The talk shared ideas and advice on improving development posture in Rust based on their work building TiKV, the distributed transactional key-value store that we started building with Rust almost three years ago. If you missed their talk, here’s the recording. Our second talk was by Wish Shi, another one of our database engineers, who introduced a Rust implementation of Prometheus, which our team maintains. Prometheus is a popular open-source monitoring system used by us and hundreds of other organizations. We also maintain other Rust crates for gRPC, the Raft consensus algorithm, and one that does failure injection, in addition to TiKV. If you missed Wish’s talk, here’s the recording. Post-FOSDEM – Cologne, Germany Brussels wasn’t our only stop. Reaching local communities is part of our global strategy, so we always try to attend local meetups during long trips whenever schedules allow. This time the folks at Giant Swarm graciously hosted us in Cologne, Germany, where I gave a talk to the local Kubernetes + Docker meetup on how we leverage the Operator pattern to run TiDB in the cloud. Since the Operator pattern is relatively new but gaining prominence, the audience was engaged and appreciative that we came all this way to share our learning.After my talk, over pizza and more beer (German beer this time), I learned about the lively local tech scene – a sizable insurance technology community, enterprise companies like Giant Swarm, and fellow open-source database company ArangoDB. It was also awesome to see big TiDB user, Mobike, on every busy street corner of Cologne. Mobike on the streets of Cologne, Germany TiDB’s presence is truly global. You just have to go local to see it."}, {"url": "https://pingcap.com/weekly/2019-02-11-tidb-weekly/", "title": "Weekly update (January 28 ~ February 10, 2019)", "content": " Weekly update in TiDB Last two weeks, we landed 25 PRs in the TiDB repository.Added Add the tidb_indexes table in infoschema Improved Enable the any_value function in aggregation if ONLY_FULL_GROUP_BY is set Improve the compatibility of the format function with MySQL Support the JSON type for the COALESCE built-in function Rewrite the exact like expression to the equal condition Derive the col is not null condition from col op col Fixed Create a new slice when cloning arguments of baseFuncDesc Handle Float32 for the values built-in function Fix precision when casting a float to a string Weekly update in TiSpark Last two weeks, we landed 2 PRs in the TiSpark repository.Added Support using TiDB as a meta store Fixed Fix the timestamp consistency issue Weekly update in TiKV and PD Last two weeks, we landed 23 PRs in the TiKV and PD repositories.Improved Configure criterion from TiKV command line arguments Enlarge max-manifest-file-size to 128MB Make PD Region commands support jq Fixed Revert updating to the latest version of etcd Do not return a retriable error when TiKV is closing Upgrade to Rust 2018 Disable procinfo on the non-Linux platform New contributors (Thanks!) tidb: caohe tender-boluo pd: fredchenbj "}, {"url": "https://pingcap.com/blog/tidb-dm-architecture-design-and-implementation-principles/", "title": "TiDB Tools (III): TiDB-DM Architecture Design and Implementation Principles", "content": " TiDB-DM (Data Migration) is an integrated data transfer and replication management platform that supports full data migration or incremental data replication from MySQL or MariaDB instances into a TiDB cluster.A common real-life use case is using TiDB-DM to connect sharded MySQL or MariaDB to TiDB, treating TiDB almost as a slave, then run analytical workloads on this TiDB cluster to fulfill real-time reporting needs. TiDB-DM provides good support if you need to manage multiple data replication tasks at the same time or need to merge multiple MySQL/MariaDB instances into a single TiDB cluster.Architecture design TiDB-DM consists of three components: DM-master, DM-worker, and dmctl. It supports migrating the data of multiple upstream MySQL instances to multiple downstream TiDB clusters. The architecture design is as follows: DM-master: Managing the whole TiDB-DM cluster, maintaining the topology information of the TiDB-DM cluster, and monitoring the running state of each DM-worker instance; Splitting and delivering the data replication tasks, and monitoring the running state of data replication tasks; Coordinating DM-workers to execute or skip the DDL statements when incrementally replicating data; Providing a unified portal for the management of data replication tasks. DM-worker: Executing the specific replication tasks of full backup data and incremental data, with each DM-worker corresponding to a unique upstream MySQL instance; Fetching the upstream MySQL binlog and persisting the binlog data to the local storage; Dumping the full backup data of upstream MySQL instances in the format of SQL files and loading them to the downstream TiDB; or parsing the persistent binlog in the local storage and replicating it to downstream TiDB cluster; Orchestrating the data replication subtasks split by DM-master and monitoring the running status of subtasks. dmctl The command line tool used to manage both the TiDB-DM cluster and data replication tasks after connecting to DM-master. Implementation principles Now, I’ll introduce TiDB-DM’s implementation principles in detail.Data migration process A single TiDB-DM cluster can perform multiple data replication tasks simultaneously. For each task, it can be split into multiple subtasks undertaken by many DM-worker nodes. Each DM-worker node is responsible for replicating the data of the corresponding upstream MySQL instance.The following diagram shows the data migration process of a single subtask of data replication on each DM-worker node.In the whole process, the upper data flow is the full backup migration and the lower data flow is the incremental data replication.In each DM-worker node, dumper, loader, relay, syncer (binlog replication) and other processing units perform the specific data replication step to complete a specific data replication subtask. For full backup migration: Dumper dumps the table schema and data from the upstream MySQL instance to SQL files. Loader loads these SQL files and then replicates these files to the downstream TiDB. For incremental data replication: Relay is used as a slave of the upstream MySQL to fetch the binlog that is persisted in the local storage as the relay log. Syncer reads and parses the relay log to build SQL statements, and then replicates these SQL statements to the downstream TiDB. This process is similar to the master-slave replication in MySQL. But the main difference is in TiDB-DM, the persisted relay log in the local storage can be used simultaneously by multiple syncer units of different subtasks, which avoids multiple tasks’ repeatedly fetching the binlog from the upstream MySQL.Concurrency model In order to accelerate data migration, TiDB-DM applies the concurrency model in part of the process of both full backup migration and incremental data replication.For full backup migration Dumper calls mydumper, a data exporting tool, to implement data exporting. For the corresponding concurrency model, see mydumper source code. Loader is used to load the data. For the corresponding concurrency model, see the following diagram: During the data exporting process with mydumper, a single table can be split into multiple SQL files with --chunk-filesize and other parameters. Each of these SQL files corresponds to a static snapshot data of the upstream MySQL at a specific moment and no correlation exists between two SQL files. So when importing the data with loader, you can directly start multiple worker goroutines in a loader unit and each worker goroutine reads to-be-imported SQL files independently and concurrently and applies them into downside streaming. That’s to say, loader loads data concurrently at the level of the SQL file. In task configuration,TiDB-DM controls the number of worker goroutines with the pool-size parameter in the loader unit.For incremental data replication When fetching the binlog from the upstream MySQL to persist it in the local storage, the binlog can only be handled serially because the upstream MySQL generates and sends the binlog in a stream. When importing the data using syncer, you can import data concurrently under limited conditions. The corresponding model architecture is as follows: Syncer reads and parses the local relay log in a stream, which is executed serially. When syncer parses the binlog events and builds the to-be-synchronized jobs, it delivers the jobs to different to-be-synchronized job channels after hash computing based on the primary key, index and other information of the corresponding row.At the other end of the channel, the worker goroutine concurrently fetches the job from the corresponding channel and replicates the job to the downstream TiDB.That’s to say, Syncer imports data concurrently at the level of the binlog event. In task allocation, TiDB-DM controls the number of worker goroutines with the worker-count parameter in the syncer unit.However, some limitations exist in this process as follows: For the DDL operation, the downstream table schema will change so the replication process can only start after all the DML events corresponding to the previous table schema have been successfully replicated. In TiDB-DM, a specific flush job is sent to each job channel after DDL events are obtained from the parsed binlog event. When each worker goroutine meets a flush job, it replicates all the previously fetched jobs to the downstream TiDB. When all the jobs in job channels are replicated to the downstream TiDB, the DDL event replication starts. After all the DDL events are replicated, DML event replication starts. In other words, DDL and DML events are not replicated concurrently, and the DML events before and after the DDL operation are not replicated concurrently either. For the DML operation, the conflict exists when multiple DML statements possibly concurrently modify the data of the same row, even the primary or the same unique key, which leads to the failure of some DML operations. If these DML events are replicated concurrently, data inconsistency might occur. Detection and resolution of DML event conflicts in TiDB-DM is similar to those in TiDB-Binlog. For more details of the specific principles, see TiDB-Binlog Architecture Evolution and Implementation Principles. Replicating data from merged tables When handling a large amount of data using MySQL, manual sharding is commonly used. After data has been replicated to TiDB, logically merging tables needs to be done.This section introduces some features of TiDB-DM for supporting replicating data from merged tables as follows.Table router Let’s start with an example as shown in the diagram below:In this example, there are two MySQL instances in the upstream; each instance has two schemas and each schema has two tables; there are eight tables in total. After we replicate the data to the downstream TiDB, the eight tables should be merged and replicated into one table.To replicate these tables with different names from different schemas of different …"}, {"url": "https://pingcap.com/blog/introducing-tidb-lightning/", "title": "TiDB Tools (II): Introducing TiDB Lightning", "content": " Lightning is an open source TiDB ecosystem tool that supports high speed full-import of a large SQL dump into a TiDB cluster. It is much faster than the traditional “execute each SQL statement” way of importing, at least tripling the import speed to about 250 GB per hour.Lightning is designed for quickly importing a large MySQL dump into a new TiDB cluster by using just a few spare machines. This is commonly used to bootstrap TiDB from a pre-existing production MySQL database, in order to evaluate TiDB with real-world data, or directly migrate the production workload into TiDB.Design TiDB has provided a “Loader” tool since early 2017. Loader itself is a fairly optimized full-import tool, using multi-threaded execution, error retry, checkpoints and adjustment of some TiDB-specific system variables for further speed-up.However, the heart of the process is still the same — run each INSERT statement in the SQL dump one-by-one. Our goal is to initialize an empty TiDB cluster, and loading data this way is fundamentally inefficient. The issue of importing through the SQL layer is having too much protection. As the import happens online, the TiDB cluster needs to guarantee many invariants throughout the process: To guarantee ACID, we need to wrap the INSERTs in transactions. To guarantee balance and data redundancy, TiKV needs to continuously split and move (“schedule”) Regions among servers as the database grows. These are what makes TiDB robust, but they are simply unnecessary in our use case.Furthermore, when we process the data in parallel, they are often not sorted. The ranges of new data will overlap with the existing ranges. TiKV requires the stored data to be always sorted, and high amount of random insertion in the middle would cause additional data movement (known as “compaction”), which would also slow down the insertion process.TiKV stores data as KV pairs on RocksDB, and RocksDB stores the persistent data as a series of “SST” files. Instead of using SQL, we could perform the import by parsing the SQL statements offline into KV pairs, and generate the sorted SST files and push (“ingest”) them into the corresponding RocksDB directly. The batch processing allows us to skip the cost of ACID and online sorting, and thus gain better performance.Architecture Lightning consists of two components: tidb-lightning (“Lightning”) and tikv-importer (“Importer”). Lightning is responsible for converting SQL into KV pairs, and Importer is responsible for sorting KV pairs and scheduling them into the TiKV servers.While it is possible to combine these into a single program like Loader, we chose to split it into two components for two reasons: Importer works closely with TiKV, while Lightning works closely with TiDB. Each component of the Toolset uses some part of the latter as libraries. This causes a linguistic mismatch between the two parts as TiKV is written in Rust and TiDB is written in Go. It is much easier to develop them as independent programs instead, and let the KV pairs be transmitted via gRPC. Having separate Lightning and Importer also gives more flexibility in horizontal scaling, for example, allowing multiple Lightning instances to stream to the same Importer. Below we’ll go through the internals of each component.Lightning Currently, Lightning only supports loading SQL dump created by mydumper. Unlike mysqldump, mydumper distributes the content of each table into individual files, which help us to process the database in parallel without parsing the entire dump first.First, Lightning scans the SQL dump, and classifies them into schema files (involving CREATE TABLE statements) and data files (involving INSERT statements). The schema files are executed directly on the target TiDB server to initialize the skeleton of the database. Lightning then processes each table in parallel.Here we focus on a single table. Each of the data files has a regular pattern like:INSERT INTO `tbl` VALUES (1, 2, 3), (4, 5, 6), (7, 8, 9); INSERT INTO `tbl` VALUES (10, 11, 12), (13, 14, 15), (16, 17, 18); INSERT INTO `tbl` VALUES (19, 20, 21), (22, 23, 24), (25, 26, 27); Lightning preliminarily parses the data files and assigns row numbers to each row. The row numbers are needed to uniquely identify each row when the table has no primary key. It also splits the data files into similar-sized “chunks” (256 MiB by default). Chunks are processed in parallel, so a huge table could still be imported quickly. As an example, we split the above data file into 5 chunks of ≥20 bytes each.INSERT INTO `tbl` VALUES (1, 2, 3), (4, 5, 6), (7, 8, 9); INSERT INTO `tbl` VALUES (10, 11, 12), (13, 14, 15), (16, 17, 18); INSERT INTO `tbl` VALUES (19, 20, 21), (22, 23, 24), (25, 26, 27); Inside Lightning, each table would be associated with a KV encoder, which is in fact a TiDB instance using an in-memory storage (“mocktikv”). Thus after executing an INSERT statement, the KV encoder would record the result into a memory buffer (containing the data itself and also the indices), which Lightning can read out as the resulting KV pairs. In this way, we could easily convert the SQL statement into KV pairs using the same rules as TiDB itself. After KV pairs are obtained, they will be passed to Importer.Download TiDB Subscribe to Blog Importer Since Lightning processes chunks in parallel, the KV pairs received by Importer are essentially out of order. Thus the primary work of Importer is to sort them. This would require a storage space as large as the table itself, which we call an “engine file”. Every table is associated with one engine file.Instead of reinventing the wheel, we implement the engine using RocksDB again — an engine file is equivalent to a local RocksDB instance optimized for bulk-write. Here “sorting” is simply done by writing the KV pairs into the engine file, which in the end automatically gets us the merged and sorted SST file.This SST file contains data and indices of a single table, and is typically much larger than a TiKV Region. Thus, Importer will need to pre-split it into Regions of appropriate sizes (96 MiB by default) and then query the Placement Driver (PD) for the addresses of TiKV servers to schedule these split Regions into different nodes.Finally, Importer uploads the Regions of SST into the TiKV nodes. The TiKV leader then sends an “Ingest” command to import the SST file into the Raft group and conclude the import of this Region.Checking With huge amount of data to operate on, we need to automatically ensure the integrity of data to prevent silent errors. After all Regions are imported, Lightning will compare the checksum of the data before delivering to Importer, and the checksum of the data after importing into the TiKV cluster. If they are the same, we can say that all data are intact.The checksum of a table is computed by hashing its KV pairs. Since the KV pairs are distributed into multiple TiKV servers, we need a hash function which is associative. Also, before delivering KV pairs to Importer, they are not completely sorted, so the hash function should also be order-independent (i.e. commutative). This means we cannot simply SHA-256 the entire SST file for instance.In TiDB, we implement the checksum by computing the CRC64 of each KV pair, and then XOR all values together. In addition to the XOR of CRC64, we also compute the number of KV pairs and total size of KV pairs (both are commutative and associative) to further reduce the rate of raising conflicts. In the future, we may include more contemporary hash algorithms if performance allows.What’s Ahead This article shows us that Lightning improves the import speed by skipping some complicated, time-consuming operations and thus makes it suitable for initializing a big database. Our team still has many items on our roadmap to make Lightning better.Speed improvement Currently, Lightning feeds the entire INSERT INTO statement into the KV encoder unmodified. So even though we have avoided the cost of distributed …"}, {"url": "https://pingcap.com/blog/tidb-binlog-architecture-evolution-and-implementation-principles/", "title": "TiDB Tools (I): TiDB-Binlog Architecture Evolution and Implementation Principles", "content": " TiDB-Binlog is a tool used to collect the logical changes made to a TiDB cluster and provide incremental backup and replication.TiDB-Binlog is similar in functionality to MySQL master-slave replication, which relies on persisting a logical set of changes into a binary log. The main difference is that since TiDB is a distributed database, the binlog generated by each TiDB instance needs to be merged and sorted according to the time of the transaction commit before being consumed downstream.You can use the TiDB-Binlog to asynchronously replicate between TiDB clusters across a WAN, or to subscribe to TiDB data changes and feed other systems (such as an external cache or fulltext index).A common real-life use case is using TiDB-Binlog to replicate data from one TiDB cluster in one data center (DC) to another TiDB cluster in a different data center, both in the same city or close geographical proximity (e.g. an Availability Zone). This scenario guarantees cross-DC availability of your data with high performance since the physical distance between the two DCs is short.Architecture evolution The first version of TiDB-Binlog was released more than two years ago. Among the multiple versions of TiDB-Binlog, the Kafka-based version was widely used. The architecture was as follows: The architecture of the Kafka version of TiDB-Binlog The Kafka version of TiDB-Binlog consisted of the following two components: PumpPump was a daemon that run on the background of each TiDB host. Its main function was to record the binlog files generated by TiDB in real time and write them to Kafka sequentially. DrainerDrainer collected binlog files from Kafka, converted them, in the commit order of TiDB transactions, into SQL statements compatible with the specified database or data with the specified format, and then synchronized the data to the target database or wrote the data sequentially to the file. The Kafka architecture worked as follows: TiDB needed to be bound to Pump, which meant the TiDB instance could only send the generated binlog to a specified Pump instance. Pump wrote the binlog to a local file and then wrote them asynchronously to Kafka. Drainer read the binlog from Kafka, sorted the binlog, parsed the binlog, and then synchronized the generated SQL statements or data in the specified format to the downstream. This Kafka-based version of TiDB-Binlog had a few issues: The TiDB load was unbalanced. Some TiDB instances undertook more applications and generated more binlog than others. The corresponding Pump instances also had a high load, which led to a high delay in data synchronization. Relying on Kafka clusters increased the operations and maintenance cost. In addition, the size of a single row of binlog generated by TiDB could reach up to 2GB (for example, deleting or writing data in batches), so you needed to configure the message size of Kafka. However, Kafka was not suitable for the scenario where a single row of data is large. Drainer needed to read, sort, and parse the binlog in Kafka, and then synchronized the data to the downstream. Apparently, Drainer had a lot of work to do, but it was a standalone node, so Drainer often became the bottleneck of data synchronization. To resolve these issues, we refactored TiDB-Binlog into the following architecture: The architecture of the latest (cluster) version of TiDB-Binlog This version of TiDB-Binlog no longer uses Kafka to store the binlog. Although it still keeps both the Pump and Drainer components, the functionality has been adjusted as follows: Pump records the binlog generated by TiDB in real time, and sorts the binlog according to the commit time of the transaction, and then provides it to Drainer for consumption. Drainer collects and merges the binlog from each Pump instance, and then converts the binlog into SQL statements or data in the specified format, and finally pushes the data to the downstream. This version has the following advantages: Multiple Pump instances form a cluster that can be scaled horizontally, and each Pump instance can deal with the application load in balance. TiDB distributes the binlog to each Pump instance through the built-in Pump Client. Even if some Pump instances break down, the application in TiDB will not be affected. A simple internal KV storage is implemented in Pump to store the binlog, making it easy to manage the binlog data. The original binlog sorting logic is moved from Drainer to the scalable Pump, which improves the overall synchronization performance. Drainer no longer needs to read a batch of binlog into memory for heap sorting. It only needs to read the binlog of each Pump instance in turn, merge and sort the binlog. This can save a lot of memory usage and make memory control easier. Since the biggest characteristic of this version of TiDB-Binlog is that multiple Pump instances constitute a cluster, we call this version: the cluster version.Implementation Now, I’ll introduce the implementation principles of the cluster version of TiDB-Binlog.Binlog TiDB uses the 2PC (two-phase commit) algorithm for transactions. A successful transaction writes two binlog records, including one Prewrite binlog record and one Commit binlog record. If the transaction fails, it will write a Rollback binlog record.Binlog structure definition The binlog structure is defined as below:# Binlog records all transaction changes and can be used to build SQL statements. message Binlog { # The binlog types, including Prewrite, Commit, Rollback, and so on. optional BinlogType tp = 1 [(gogoproto.nullable) = false]; # The “start_ts” in the Prewrite, Commit, and Rollback types of binlog, used to record the ts when a transaction starts. optional int64 start_ts = 2 [(gogoproto.nullable) = false]; # “commit_ts” records the ts when a transaction ends, and only records it in the Commit type of binlog. optional int64 commit_ts = 3 [(gogoproto.nullable) = false]; # “prewrite_key” is recorded only in the Prewrite type of binlog. # It is the primary key of a transaction, used to query whether the transaction is committed. optional bytes prewrite_key = 4; # “prewrite_value” is recorded in the Prewrite type of binlog, used to record the change of each row of data. optional bytes prewrite_value = 5; # “ddl_query” records the DDL statements. optional bytes ddl_query = 6; # “ddl_job_id” records the job ID of DDL statements. optional int64 ddl_job_id = 7 [(gogoproto.nullable) = false]; } For more details about binlog and the data structure definition, see binlog.proto.In TiDB, ts (TSO, Timestamp Oracle) is a globally unique time service provided by Placement Driver (PD). It is converted from the physical time and the logic time. start_ts is the starting timestamp value of a transaction. commit_ts is the commit timestamp value of a transaction.When a transaction is started, TiDB requests a timestamp from PD as the transaction start_ts. When the transaction is committed, TiDB requests a timestamp again from PD as the transaction commit_ts. This commit_ts is used to sort the binlog in Pump and Drainer.The TiDB-Binlog format is row-based logging (RBL), which stores the change of each row of data. The data change is recorded in the prewrite_value field, and the data in this field mainly consists of serialized data arrays with the following TableMutation structure:# The data change in the TableMutation storage table message TableMutation { # The table ID that uniquely identifies a table optional int64 table_id = 1 [(gogoproto.nullable) = false]; # Saves the data of each inserted row repeated bytes inserted_rows = 2; # Saves the data of each row both before and after the modification repeated bytes updated_rows = 3; # Deprecated repeated int64 deleted_ids = 4; # Deprecated repeated bytes deleted_pks = 5; # The data of deleted rows repeated bytes deleted_rows = 6; # Records the sequence of data changes repeated MutationType sequence = 7; } Storing data changes to binlog I’ll use an example to explain how binlog …"}, {"url": "https://pingcap.com/blog-cn/tikv-source-code-reading-1/", "title": "TiKV 源码解析系列文章(一)序", "content": " TiKV 是一个支持事务的分布式 Key-Value 数据库,有很多社区开发者基于 TiKV 来开发自己的应用,譬如 titan、tidis。尤其是在 TiKV 成为 CNCF 的 Sandbox 项目之后,吸引了越来越多开发者的目光,很多同学都想参与到 TiKV 的研发中来。这时候,就会遇到两个比较大的拦路虎: Rust 语言:众所周知,TiKV 是使用 Rust 语言来进行开发的,而 Rust 语言的学习难度相对较高,有些人认为其学习曲线大于 C++,所以很多同学在这一步就直接放弃了。 文档:最开始 TiKV 是作为 HTAP 数据库 TiDB 的一个底层存储引擎设计并开发出来的,属于内部系统,缺乏详细的文档,以至于同学们不知道 TiKV 是怎么设计的,以及代码为什么要这么写。 对于第一个问题,我们内部正在制作一系列的 Rust 培训课程,由 Rust 作者以及 Rust 社区知名的开发者亲自操刀,预计会在今年第一季度对外发布。希望通过该课程的学习,大家能快速入门 Rust,使用 Rust 开发自己的应用。而对于第二个问题,我们会启动 《TiKV 源码解析系列文章》以及 《Deep Dive TiKV 系列文章》计划,在《Deep Dive TiKV 系列文章》中,我们会详细介绍与解释 TiKV 所使用技术的基本原理,譬如 Raft 协议的说明,以及我们是如何对 Raft 做扩展和优化的。而 《TiKV 源码解析系列文章》则是会从源码层面给大家抽丝剥茧,让大家知道我们内部到底是如何实现的。我们希望,通过这两个系列,能让大家对 TiKV 有更深刻的理解,再加上 Rust 培训,能让大家很好的参与到 TiKV 的开发中来。结构 本篇文章是《TiKV 源码解析系列文章》的序篇,会简单的给大家讲一下 TiKV 的基本模块,让大家对这个系统有一个整体的了解。要理解 TiKV,只是了解 https://github.com/tikv/tikv 这一个项目是远远不够的,通常,我们也需要了解很多其他的项目,包括但不限于: https://github.com/pingcap/raft-rs https://github.com/pingcap/rust-prometheus https://github.com/pingcap/rust-rocksdb https://github.com/pingcap/fail-rs https://github.com/pingcap/rocksdb https://github.com/pingcap/grpc-rs https://github.com/pingcap/pd 在这个系列里面,我们首先会从 TiKV 使用的周边库开始介绍,然后介绍 TiKV,最后会介绍 PD。下面简单来说下我们的一些介绍计划。Storage Engine TiKV 现在使用 RocksDB 作为底层数据存储方案。在 pingcap/rust-rocksdb 这个库里面,我们会简单说明 Rust 是如何通过 Foreign Function Interface (FFI) 来跟 C library 进行交互,以及我们是如何将 RocksDB 的 C API 封装好给 Rust 使用的。另外,在 pingcap/rocksdb 这个库里面,我们会详细的介绍我们自己研发的 Key-Value 分离引擎 - Titan,同时也会让大家知道如何使用 RocksDB 对外提供的接口来构建自己的 engine。Raft TiKV 使用的是 Raft 一致性协议。为了保证算法的正确性,我们直接将 etcd 的 Go 实现 port 成了 Rust。在 pingcap/raft-rs,我们会详细介绍 Raft 的选举,Log 复制,snapshot 这些基本的功能是如何实现的。另外,我们还会介绍对 Raft 的一些优化,譬如 pre-vote,check quorum 机制,batch 以及 pipeline。最后,我们会说明如何去使用这个 Raft 库,这样大家就能在自己的应用里面集成 Raft 了。gRPC TiKV 使用的是 gRPC 作为通讯框架,我们直接把 Google C gRPC 库封装在 grpc-rs 这个库里面。我们会详细告诉大家如何去封装和操作 C gRPC 库,启动一个 gRPC 服务。另外,我们还会介绍如何使用 Rust 的 futures-rs 来将异步逻辑变成类似同步的方式来处理,以及如何通过解析 protobuf 文件来生成对应的 API 代码。最后,我们会介绍如何基于该库构建一个简单的 gRPC 服务。Prometheus TiKV 使用 Prometheus 作为其监控系统, rust-prometheus 这个库是 Prometheus 的 Rust client。在这个库里面,我们会介绍如果支持不同的 Prometheus 的数据类型(Coutner,Gauge,Historgram)。另外,我们会重点介绍我们是如何通过使用 Rust 的 Macro 来支持 Prometheus 的 Vector metrics 的。最后,我们会介绍如何在自己的项目里面集成 Prometheus client,将自己的 metrics 存到 Prometheus 里面,方便后续分析。Fail Fail 是一个错误注入的库。通过这个库,我们能很方便的在代码的某些地方加上 hook,注入错误,然后在系统运行的时候触发相关的错误,看系统是否稳定。我们会详细的介绍 Fail 是如何通过 macro 来注入错误,会告诉大家如何添加自己的 hook,以及在外面进行触发TiKV TiKV 是一个非常复杂的系统,这块我们会重点介绍,主要包括: Raftstore,该模块里面我们会介绍 TiKV 如何使用 Raft,如何支持 Multi-Raft。 Storage,该模块里面我们会介绍 Multiversion concurrency control (MVCC),基于 Percolator 的分布式事务的实现,数据在 engine 里面的存储方式,engine 操作相关的 API 等。 Server,该模块我们会介绍 TiKV 的 gRPC API,以及不同函数执行流程。 Coprocessor,该模块我们会详细介绍 TiKV 是如何处理 TiDB 的下推请求的,如何通过不同的表达式进行数据读取以及计算的。 PD,该模块我们会介绍 TiKV 是如何跟 PD 进行交互的。 Import,该模块我们会介绍 TiKV 如何处理大量数据的导入,以及如何跟 TiDB 数据导入工具 lightning 交互的。 Util,该模块我们会介绍一些 TiKV 使用的基本功能库。 PD PD 用来负责整个 TiKV 的调度,我们会详细的介绍 PD 内部是如何使用 etcd 来进行元数据存取和高可用支持,也会介绍 PD 如何跟 TiKV 交互,如何生成全局的 ID 以及 timestamp。最后,我们会详细的介绍 PD 提供的 scheduler,以及不同的 scheudler 所负责的事情,让大家能通过配置 scheduler 来让系统更加的稳定。小结 上面简单的介绍了源码解析涉及的模块,还有一些模块譬如 https://github.com/tikv/client-rust 仍在开发中,等完成之后我们也会进行源码解析。我们希望通过该源码解析系列,能让大家对 TiKV 有一个更深刻的理解。当然,TiKV 的源码也是一直在不停的演化,我们也会尽量保证文档的及时更新。最后,欢迎大家参与 TiKV 的开发。"}, {"url": "https://pingcap.com/weekly/2019-01-28-tidb-weekly/", "title": "Weekly update (January 21 ~ January 27, 2019)", "content": " Weekly update in TiDB Last week, we landed 22 PRs in the TiDB repository.Added Add a variable to control whether to check MB4 characters in the UTF-8 charset Improved Log failure reasons for AutoAnalyze Log the reason why AutoAnalyze is triggered Improve the message compatibility with MySQL when setting up connections with the server Improve the syntax error code and message compatibility with MySQL Fixed Use a separate backoffer structure for asynchronous commits Prohibit altering tables from the binary charset to utf8mb4 Fix wrong results of queries that contain any/all Resolve the charset of table columns correctly Weekly update in TiSpark Last week, we landed 1 PR in the TiSpark repository.Fixed Fix case sensitive configurations Weekly update in TiKV and PD Last week, we landed 20 PRs in the TiKV and PD repositories.Improved Update etcd in PD Make the hot Region scheduler configurable Fixed Fix the event listener issue by checking the CompactionJobInfos status Fix the is_point usage issue in IndexScanExecutor Fix the GC state of invalid logs New contributors (Thanks!) tidb: hueypark tikv: fredchenbj parser: arnkore docs-cn: tender-boluo "}, {"url": "https://pingcap.com/blog/tidb-3.0-beta-stability-at-scale/", "title": "TiDB 3.0 Beta: Stability at Scale", "content": " Author: Li Shen, VP of Engineering at PingCAPLast weekend, PingCAP hosted the second annual TiDB DevCon in Beijing, with a packed crowd of more than 700 developers, customers, and contributors. Among the many announcements that were made on TiDB’s core product, roadmap, and ecosystem was the release of TiDB 3.0 beta. Here’s a rundown of the highlights.TiDB 3.0 Beta: Key New Features TiDB is an open-source NewSQL Hybrid Transactional and Analytical Processing (HTAP) database with MySQL compatibility – one of the most popular and active database products on GitHub. It is designed to provide elastic horizontal scalability, strong consistency, and high availability. In TiDB’s previous 2.1 GA release in November 2018, we made major improvements in areas like our cost-based optimizer, query plan executor, Raft implementation (including pre-vote, learner and region merge), and concurrent online DDL. You can read about the details of that release in our announcement post.As our team brainstormed and roadmapped TiDB’s future, taking in valuable suggestions from hundreds of companies using TiDB in production, we arrived at a common theme: not only should TiDB deliver “SQL at scale,” it should also deliver “stability at scale.” To achieve this end, we decided to build the following improvements in TiDB 3.0:Multi-thread Raftstore: As you may know, the consensus algorithm that underpins TiDB’s strong consistency and high availability characteristics is Raft. As a distributed database serves more production traffic, concurrency increases and Raft becomes a bottleneck that can impact performance due to limitations in Raft’s original design. Thus, we built a new implementation where multiple Raft communication threads can be processed concurrently, leading to improved performance. Based on our internal testing, in TiDB with multi-thread raftstore INSERT performance increases as concurrency increases. We will release additional benchmark information soon for those interested in reproducing what we see.Download TiDB Subscribe to Blog Batch Message in gRPC: We use gRPC extensive inside the TiDB platform to execute communications between its various components, i.e. TiDB server, TiKV, Placement Driver, etc. While gRPC is easy to use and feature rich, it does create performance bottlenecks. To improve performance, we batch gRPC messages that are going to the same destination in a queue, with a timer that detects whether the receiver side is busy. If not, send the message immediately, and only triggers batch message when the receiver is too busy.Query Tracing: One of our big goals in 3.0 besides bringing stability at scale is improving usability, and one important element of the usability is diagnosing and debugging queries. Even though we improved the readability of EXPLAIN and EXPLAIN ANALYZE in TiDB 2.1, there’s more we can do to make DBAs’, architects’, and DevOps’ lives easier. Thus, we implemented Query Tracing, which is a much more clear, visual, and intuitive way to identify which part(s) of a query is slow or causing issues, making diagnosis and debugging a breeze. Query Tracing screenshot Of course, these aren’t the only new features. TiDB 3.0 beta also includes the following, which we will write more about in the future: Window Functions Views Table Partitioning (Hash/Range Partition) Restore Dropped Table TitanDB Storage Engine (Experimental) Distributed Garbage Collection We will write more deep dive blog posts on each of these developments soon. And while we don’t recommend that users deploy 3.0 beta in production, we are working hard with the help of the TiDB community to bring 3.0 to GA status by mid-2019. For details of the current release, please see our release notes."}, {"url": "https://pingcap.com/blog-cn/titan-design-and-implementation/", "title": "Titan 的设计与实现", "content": " Titan 是由 PingCAP 研发的一个基于 RocksDB 的高性能单机 key-value 存储引擎,其主要设计灵感来源于 USENIX FAST 2016 上发表的一篇论文 WiscKey。WiscKey 提出了一种高度基于 SSD 优化的设计,利用 SSD 高效的随机读写性能,通过将 value 分离出 LSM-tree 的方法来达到降低写放大的目的。我们的基准测试结果显示,当 value 较大的时候,Titan 在写、更新和点读等场景下性能都优于 RocksDB。但是根据 RUM Conjecture,通常某些方面的提升往往是以牺牲其他方面为代价而取得的。Titan 便是以牺牲硬盘空间和范围查询的性能为代价,来取得更高的写性能。随着 SSD 价格的降低,我们认为这种取舍的意义会越来越明显。设计目标 Titan 作为 TiKV 的一个子项目,首要的设计目标便是兼容 RocksDB。因为 TiKV 使用 RocksDB 作为其底层的存储引擎,而 TiKV 作为一个成熟项目已经拥有庞大的用户群体,所以我们需要考虑已有的用户也可以将已有的基于 RocksDB 的 TiKV 平滑地升级到基于 Titan 的 TiKV。因此,我们总结了四点主要的设计目标: 支持将 value 从 LSM-tree 中分离出来单独存储,以降低写放大。 已有 RocksDB 实例可以平滑地升级到 Titan,这意味着升级过程不需要人工干预,并且不会影响线上服务。 100% 兼容目前 TiKV 所使用的所有 RocksDB 的特性。 尽量减少对 RocksDB 的侵入性改动,保证 Titan 更加容易升级到新版本的 RocksDB。 架构与实现 Titan 的基本架构如下图所示: 图 1:Titan 在 Flush 和 Compaction 的时候将 value 分离出 LSM-tree,这样做的好处是写入流程可以和 RockDB 保持一致,减少对 RocksDB 的侵入性改动。 Titan 的核心组件主要包括:BlobFile、TitanTableBuilder、Version 和 GC,下面将逐一进行介绍。BlobFile BlobFile 是用来存放从 LSM-tree 中分离出来的 value 的文件,其格式如下图所示: 图 2:BlobFile 主要由 blob record 、meta block、meta index block 和 footer 组成。其中每个 blob record 用于存放一个 key-value 对;meta block 支持可扩展性,可以用来存放和 BlobFile 相关的一些属性等;meta index block 用于检索 meta block。 BlobFile 有几点值得关注的地方: BlobFile 中的 key-value 是有序存放的,目的是在实现 Iterator 的时候可以通过 prefetch 的方式提高顺序读取的性能。 每个 blob record 都保留了 value 对应的 user key 的拷贝,这样做的目的是在进行 GC 的时候,可以通过查询 user key 是否更新来确定对应 value 是否已经过期,但同时也带来了一定的写放大。 BlobFile 支持 blob record 粒度的 compression,并且支持多种 compression algorithm,包括 Snappy、LZ4 和 Zstd 等,目前 Titan 默认使用的 compression algorithm 是 LZ4 。 TitanTableBuilder TitanTableBuilder 是实现分离 key-value 的关键。我们知道 RocksDB 支持使用用户自定义 table builder 创建 SST,这使得我们可以不对 build table 流程做侵入性的改动就可以将 value 从 SST 中分离出来。下面将介绍 TitanTableBuilder 的主要工作流程: 图 3:TitanTableBuilder 通过判断 value size 的大小来决定是否将 value 分离到 BlobFile 中去。如果 value size 大于等于 min_blob_size 则将 value 分离到 BlobFile ,并生成 index 写入 SST;如果 value size 小于 min_blob_size 则将 value 直接写入 SST。 Titan 和 Badger 的设计有很大区别。Badger 直接将 WAL 改造成 VLog,这样做的好处是减少一次 Flush 的开销。而 Titan 不这么设计的主要原因有两个: 假设 LSM-tree 的 max level 是 5,放大因子为 10,则 LSM-tree 总的写放大大概为 1 + 1 + 10 + 10 + 10 + 10,其中 Flush 的写放大是 1,其比值是 42 : 1,因此 Flush 的写放大相比于整个 LSM-tree 的写放大可以忽略不计。 在第一点的基础上,保留 WAL 可以使 Titan 极大地减少对 RocksDB 的侵入性改动,而这也正是我们的设计目标之一。 Version Titan 使用 Version 来代表某个时间点所有有效的 BlobFile,这是从 LevelDB 中借鉴过来的管理数据文件的方法,其核心思想便是 MVCC,好处是在新增或删除文件的同时,可以做到并发读取数据而不需要加锁。每次新增文件或者删除文件的时候,Titan 都会生成一个新的 Version ,并且每次读取数据之前都要获取一个最新的 Version。 图 4:新旧 Version 按顺序首尾相连组成一个双向链表,VersionSet 用来管理所有的 Version,它持有一个 current 指针用来指向当前最新的 Version。 Garbage Collection Garbage Collection (GC) 的目的是回收空间,一个高效的 GC 算法应该在权衡写放大和空间放大的同时,用最少的周期来回收最多的空间。在设计 GC 的时候有两个主要的问题需要考虑: 何时进行 GC 挑选哪些文件进行 GC Titan 使用 RocksDB 提供的两个特性来解决这两个问题,这两个特性分别是 TablePropertiesCollector 和 EventListener 。下面将讲解我们是如何通过这两个特性来辅助 GC 工作的。BlobFileSizeCollector RocksDB 允许我们使用自定义的 TablePropertiesCollector 来搜集 SST 上的 properties 并写入到对应文件中去。Titan 通过一个自定义的 TablePropertiesCollector —— BlobFileSizeCollector 来搜集每个 SST 中有多少数据是存放在哪些 BlobFile 上的,我们将它收集到的 properties 命名为 BlobFileSizeProperties,它的工作流程和数据格式如下图所示: 图 5:左边 SST 中 Index 的格式为:第一列代表 BlobFile 的文件 ID,第二列代表 blob record 在 BlobFile 中的 offset,第三列代表 blob record 的 size。右边 BlobFileSizeProperties 中的每一行代表一个 BlobFile 以及 SST 中有多少数据保存在这个 BlobFile 中,第一列代表 BlobFile 的文件 ID,第二列代表数据大小。 EventListener 我们知道 RocksDB 是通过 Compaction 来丢弃旧版本数据以回收空间的,因此每次 Compaction 完成后 Titan 中的某些 BlobFile 中便可能有部分或全部数据过期。因此我们便可以通过监听 Compaction 事件来触发 GC,通过搜集比对 Compaction 中输入输出 SST 的 BlobFileSizeProperties 来决定挑选哪些 BlobFile 进行 GC。其流程大概如下图所示: 图 6:inputs 代表参与 Compaction 的所有 SST 的 BlobFileSizeProperties,outputs 代表 Compaction 生成的所有 SST 的 BlobFileSizeProperties,discardable size 是通过计算 inputs 和 outputs 得出的每个 BlobFile 被丢弃的数据大小,第一列代表 BlobFile 的文件 ID,第二列代表被丢弃的数据大小。 Titan 会为每个有效的 BlobFile 在内存中维护一个 discardable size 变量,每次 Compaction 结束之后都对相应的 BlobFile 的 discardable size 变量进行累加。每次 GC 开始时就可以通过挑选 discardable size 最大的 BlobFile 来作为作为候选的文件。Sample 每次进行 GC 前我们都会挑选一系列 BlobFile 作为候选文件,挑选的方法如上一节所述。为了减小写放大,我们可以容忍一定的空间放大,所以我们只有在 BlobFile 可丢弃的数据达到一定比例之后才会对其进行 GC。我们使用 Sample 算法来获取每个候选文件中可丢弃数据的大致比例。Sample 算法的主要逻辑是随机取 BlobFile 中的一段数据 A,计其大小为 a,然后遍历 A 中的 key,累加过期的 key 所在的 blob record 的 size 计为 d,最后计算得出 d 占 a 比值 为 r,如果 r >= discardable_ratio 则对该 BlobFile 进行 GC,否则不对其进行 GC。上一节我们已经知道每个 BlobFile 都会在内存中维护一个 discardable size,如果这个 discardable size 占整个 BlobFile 数据大小的比值已经大于或等于 discardable_ratio 则不需要对其进行 Sample。基准测试 我们使用 go-ycsb 测试了 TiKV 在 Txn Mode 下分别使用 RocksDB 和 Titan 的性能表现,本节我会简要说明下我们的测试方法和测试结果。由于篇幅的原因,我们只挑选两个典型的 value size 做说明,更详细的测试分析报告将会放在下一篇文章。测试环境 CPU:Intel® Xeon® CPU E5-2630 v4 @ 2.20GHz(40个核心) Memory:128GB(我们通过 Cgroup 限制 TiKV 进程使用内存不超过 32GB) Disk:SATA SSD 1.5TB(fio 测试:4KB block size 混合随机读写情况下读写 IOPS 分别为 43.8K 和 18.7K) 测试计划 数据集选定的基本原则是原始数据大小(不算上写放大因素)要比可用内存大,这样可以防止所有数据被缓存到内存中,减少 Cache 所带来的影响。这里我们选用的数据集大小是 64GB,进程的内存使用限制是 32GB。 Value Size Number of Keys (Each Key = 16 Bytes) Raw Data Size 1KB 64M 64GB 16KB 4M 64GB 我们主要测试 5 个常用的场景: Data Loading Performance:使用预先计算好的 key 数量和固定的 value 大小,以一定的速度并发写入。 Update Performance:由于 Titan 在纯写入场景下不需要 GC(BlobFile 中没有可丢弃数据),因此我们还需要通过更新来测试 GC 对性能的影响。 Output Size:这一步我们会测量更新场景完成后引擎所占用的硬盘空间大小,以此反映 GC 的空间回收效果。 Random Key Lookup Performance:这一步主要测试点查性能,并且点查次数要远远大于 key 的数量。 Sorted Range Iteration Performance:这一步主要测试范围查询的性能,每次查询 2 million 个相连的 key。 测试结果 图 7 Data Loading Performance:Titan 在写场景中的性能要比 RocksDB 高 70% 以上,并且随着 value size 的变大,这种性能的差异会更加明显。值得注意的是,数据在写入 KV Engine 之前会先写入 Raft Log,因此 Titan 的性能提升会被摊薄,实际上裸测 RocksDB 和 Titan 的话这种性能差异会更大。 图 8 Update Performance:Titan 在更新场景中的性能要比 RocksDB 高 180% 以上,这主要得益于 Titan 优秀的读性能和良好的 GC 算法。 图 9 Output Size:Titan 的空间放大相比 RocksDB 略高,这种差距会随着 Key 数量的减少有略微的缩小,这主要是因为 BlobFile 中需要存储 Key 而造成的写放大。 图 10 Random Key Lookup: Titan 拥有比 RocksDB 更卓越的点读性能,这主要得益与将 value 分离出 LSM-tree 的设计使得 LSM-tree 变得更小,因此 Titan 在使用同样的内存量时可以将更多的 index 、filter 和 DataBlock 缓存到 Block Cache 中去。这使得点读操作在大多数情况下仅需要一次 IO 即可(主要是用于从 BlobFile 中读取数据)。 图 11 Sorted Range Iteration:Titan 的范围查询性能目前和 RocksDB 相比还是有一定的差距,这也是我们未来优化的一个重要方向。 本次测试我们对比了两个具有代表性的 value size 在 5 种不同场景下的性能差异,更多不同粒度的 value size 的测试和更详细的性能报告我们会放在下一篇文章去说明,并且我们会从更多的角度(例如 CPU 和内存的使用率等)去分析 Titan 和 RocksDB 的差异。从本次测试我们可以大致得出结论,在大 value 的场景下,Titan 会比 RocksDB 拥有更好的写、更新和点读性能。同时,Titan 的范围查询性能和空间放大都逊于 RocksDB 。兼容性 一开始我们便将兼容 RocksDB 作为设计 Titan 的首要目标,因此我们保留了绝大部分 RocksDB 的 API。目前仅有两个 API 是我们明确不支持的: Merge SingleDelete 除了 Open 接口以外,其他 API 的参数和返回值都和 RocksDB 一致。已有的项目只需要很小的改动即可以将 RocksDB 实例平滑地升级到 Titan。值得注意的是 Titan 并不支持回退回 RocksDB。如何使用 Titan 创建 DB #include <assert>#include "rocksdb/utilities/titandb/db.h" // Open DB rocksdb::titandb::TitanDB* db; rocksdb::titandb::TitanOptions options; options.create_if_missing = true; rocksdb::Status status = rocksdb::titandb::TitanDB::Open(options, "/tmp/testdb", &db); assert(status.ok()); ... 或#include <assert>#include "rocksdb/utilities/titandb/db.h" // open DB with two column families rocksdb::titandb::TitanDB* db; std::vector<rocksdb::titandb::TitanCFDescriptor> column_families; // have to open default column family column_families.push_back(rocksdb::titandb::TitanCFDescriptor( kDefaultColumnFamilyName, rocksdb::titandb::TitanCFOptions())); // open the new one, too column_families.push_back(rocksdb::titandb::TitanCFDescriptor( "new_cf", rocksdb::titandb::TitanCFOptions())); std::vector<ColumnFamilyHandle*> handles; s = rocksdb::titandb::TitanDB::Open(rocksdb::titandb::TitanDBOptions(), kDBPath, column_families, &handles, &db); assert(s.ok()); Status 和 RocksDB 一样,Titan 使用 rocksdb::Status 来作为绝大多数 API 的返回值,使用者可以通过它检查执行结果是否成功,也可以通过它打印错误信息:rocksdb::Status s = ...; if (!s.ok()) cerr << s.ToString() << endl; 销毁 DB std::string value; rocksdb::Status s = db->Get(rocksdb::ReadOptions(), key1, &value); if (s.ok()) s = db->Put(rocksdb::WriteOptions(), key2, value); if (s.ok()) s = db->Delete(rocksdb::WriteOptions(), key1); 在 TiKV 中使用 Titan 目前 Titan 在 TiKV 中是默认关闭的,我们通过 TiKV 的配置文件来决定是否开启和设置 Titan,相关的配置项包括 [rocksdb.titan] 和 [rocksdb.defaultcf.titan], 开启 Titan 只需要进行如下配置即可:[rocksdb.titan] enabled = true 注意一旦开启 Titan 就不能回退回 RocksDB 了。未来的 …"}, {"url": "https://pingcap.com/weekly/2019-01-21-tidb-weekly/", "title": "Weekly update (January 14 ~ January 20, 2019)", "content": " Weekly update in TiDB Last week, we landed 57 PRs in the TiDB repository.Added Add the ALLOW_INVALID_DATES SQL mode support Support the PARTITION syntax for selecting partition tables Support the aggregate window function without the frame in the executor Support SHOW CREATE TABLE for View Support the OR REPLACE syntax for CREATE VIEW Support restoring deleted tables Improved Improve error messages for MySQL compatibility Cut the scan range by Region before sending requests to TiKV Handle the optimizer hint properly for Cartesian Join Enable the plugin framework in TiDB Support the Window function with the frame in the planner Send gRPC messages to TiKV in batch Disallow ALTER TABLE on View Use constraint propagation in partition pruning Print the column character set in SHOW CREATE TABLE Return an error when INSERT/UPDATE/ANALYZE/DELETE a View Modify the max length limit of the VARCHAR type Support the reverse raw scan on TiKV Build a new histogram using range information Fixed Fix a false negative result of ADMIN CHECK TABLE Make the statistics worker recover from the panic correctly Fix a wrong result of MergeJoin Fix a panic caused by parsing the CSV input Fix an incorrect behavior under the NO_ZERO_DATE SQL mode Weekly update in TiSpark Last week, we landed 1 PR in the TiSpark repository.Fixed Fix the possible null version in pom Weekly update in TiKV and PD Last week, we landed 19 PRs in the TiKV and PD repositories.Added Add the add_time_duration_null Coprocessor pushdown function Verify the TiDB range in Coprocessor Endpoint Add multi-thread RaftStore Improved Pass db options to ldb in tikv-ctl Fixed Fix the potential nil panic in the AdjacentRegion scheduler after PD leader is changed Fix the month_name Coprocessor pushdown function Fix the epollex error on MacOS New contributors (Thanks!) tidb: ariesdevil kamijin-fanta tikv: JaySon-Huang ariesdevil tispark: ariesdevil parser: GunaYX cgcgbcbc "}, {"url": "https://pingcap.com/meetup/meetup-88-20190116/", "title": "【Infra Meetup No.88】小红书的社区架构 & TiDB 在小红书的实践案例分享 & Vectorized Execution Explained", "content": " 在上周六举办的 Infra Meetup No.88 上海站上,来自小红书的郭一、张俊骏两位老师,和我司施闻轩老师一起,为大家带来三个精彩的分享,以下是视频&文字回顾,enjoy~小红书的社区架构 视频 | Infra Meetup No.88:小红书的社区架构 本次分享,郭一老师主要介绍了小红书社区的数据技术。首先介绍了小红书的产品和社区个性化的推荐需要的关键技术。然后对社区的数据技术分别从接入层,业务层,数据服务层和数据仓库层进行了概述。然后讲述了一个利用流计算引擎 Flink 给线上推荐提供用户行为实时的多维度聚合的业务实例。最后对小红书下一年的数据架构发展进行了展望。TiDB 在小红书的实践案例分享 视频 | Infra Meetup No.88:TiDB 在小红书的实践案例分享 本次分享,张俊骏老师主要介绍了小红书在以下两个场景中对 TiDB 的使用:第一个场景是大促实时看板,在高 QPS 场景下通过最终一致性写入模型完美地满足了需求,且全程保持稳定;第二个是作为分库分表 MySQL 的从库进行 ETL 任务,通过分析分库分表 MySQL 的特性自行开发了同步工具,解决了许多 ETL 任务的痛点。小红书未来还会在 TiDB 的容器化部署、自动化运维、接入更多业务场景等方向上努力。Vectorized Execution Explained 视频 | Infra Meetup No.88:Vectorized Execution Explained 2019 年我们会尝试针对一些主题进行一系列分享,Q1 计划的是查询执行(Query Execution)相关主题。针对这个主题我们会分享当前业界相对前沿的设计和算法,例如 JIT Compilation,向量化,SIMD,NUMA 相关优化等。本次 Meetup 施闻轩老师的分享主题是「向量化执行」。向量化是随着列存数据库一起成熟的新查询执行模型,诸如 Hive,Vertica,Vectorwise,Clickhouse 等都使用了该技术。向量化也是 TiDB 正在进行的优化之一。本次分享从为何进行向量化,块执行,SIMD 和晚期物化等多个方面阐述向量化引擎的设计和实现。 PPT 链接 "}, {"url": "https://pingcap.com/blog-cn/tidb-source-code-reading-24/", "title": "TiDB 源码阅读系列文章(二十四)TiDB Binlog 源码解析", "content": " TiDB Binlog Overview 这篇文章不是讲 TiDB Binlog 组件的源码,而是讲 TiDB 在执行 DML/DDL 语句过程中,如何将 Binlog 数据 发送给 TiDB Binlog 集群的 Pump 组件。目前 TiDB 在 DML 上的 Binlog 用的类似 Row-based 的格式。具体 Binlog 具体的架构细节可以参看这篇 文章。这里只描述 TiDB 中的代码实现。DML Binlog TiDB 采用 protobuf 来编码 binlog,具体的格式可以见 binlog.proto。这里讨论 TiDB 写 Binlog 的机制,以及 Binlog 对 TiDB 写入的影响。TiDB 会在 DML 语句提交,以及 DDL 语句完成的时候,向 pump 输出 Binlog。Statement 执行阶段 DML 语句包括 Insert/Replace、Update、Delete,这里挑 Insert 语句来阐述,其他的语句行为都类似。首先在 Insert 语句执行完插入(未提交)之前,会把自己新增的数据记录在 binlog.TableMutation 结构体中。// TableMutation 存储表中数据的变化 message TableMutation { // 表的 id,唯一标识一个表 optional int64 table_id = 1 [(gogoproto.nullable) = false]; // 保存插入的每行数据 repeated bytes inserted_rows = 2; // 保存修改前和修改后的每行的数据 repeated bytes updated_rows = 3; // 已废弃 repeated int64 deleted_ids = 4; // 已废弃 repeated bytes deleted_pks = 5; // 删除行的数据 repeated bytes deleted_rows = 6; // 记录数据变更的顺序 repeated MutationType sequence = 7; } 这个结构体保存于跟每个 Session 链接相关的事务上下文结构体中 TxnState.mutations。 一张表对应一个 TableMutation 对象,TableMutation 里面保存了这个事务对这张表的所有变更数据。Insert 会把当前语句插入的行,根据 RowID + Row-value 的格式编码之后,追加到 TableMutation.InsertedRows 中:func (t *Table) addInsertBinlog(ctx context.Context, h int64, row []types.Datum, colIDs []int64) error { mutation := t.getMutation(ctx) pk, err := codec.EncodeValue(ctx.GetSessionVars().StmtCtx, nil, types.NewIntDatum(h)) if err != nil { return errors.Trace(err) } value, err := tablecodec.EncodeRow(ctx.GetSessionVars().StmtCtx, row, colIDs, nil, nil) if err != nil { return errors.Trace(err) } bin := append(pk, value...) mutation.InsertedRows = append(mutation.InsertedRows, bin) mutation.Sequence = append(mutation.Sequence, binlog.MutationType_Insert) return nil } 等到所有的语句都执行完之后,在 TxnState.mutations 中就保存了当前事务对所有表的变更数据。Commit 阶段 对于 DML 而言,TiDB 的事务采用 2-phase-commit 算法,一次事务提交会分为 Prewrite 阶段,以及 Commit 阶段。这里分两个阶段来看看 TiDB 具体的行为。Prewrite Binlog 在 session.doCommit 函数中,TiDB 会构造 binlog.PrewriteValue:message PrewriteValue { optional int64 schema_version = 1 [(gogoproto.nullable) = false]; repeated TableMutation mutations = 2 [(gogoproto.nullable) = false]; } 这个 PrewriteValue 中包含了跟这次变动相关的所有行数据,TiDB 会填充一个类型为 binlog.BinlogType_Prewrite 的 Binlog:info := &binloginfo.BinlogInfo{ Data: &binlog.Binlog{ Tp: binlog.BinlogType_Prewrite, PrewriteValue: prewriteData, }, Client: s.sessionVars.BinlogClient.(binlog.PumpClient), } TiDB 这里用一个事务的 Option kv.BinlogInfo 来把 BinlogInfo 绑定到当前要提交的 transaction 对象中:s.txn.SetOption(kv.BinlogInfo, info) 在 twoPhaseCommitter.execute 中,在把数据 prewrite 到 TiKV 的同时,会调用 twoPhaseCommitter.prewriteBinlog,这里会把关联的 binloginfo.BinlogInfo 取出来,把 Binlog 的 binlog.PrewriteValue 输出到 Pump。binlogChan := c.prewriteBinlog() err := c.prewriteKeys(NewBackoffer(prewriteMaxBackoff, ctx), c.keys) if binlogChan != nil { binlogErr := <-binlogChan // 等待 write prewrite binlog 完成 if binlogErr != nil { return errors.Trace(binlogErr) } } 这里值得注意的是,在 prewrite 阶段,是需要等待 write prewrite binlog 完成之后,才能继续做接下去的提交的,这里是为了保证 TiDB 成功提交的事务,Pump 至少一定能收到 Prewrite Binlog。Commit Binlog 在 twoPhaseCommitter.execute 事务提交结束之后,事务可能提交成功,也可能提交失败。TiDB 需要把这个状态告知 Pump:err = committer.execute(ctx) if err != nil { committer.writeFinishBinlog(binlog.BinlogType_Rollback, 0) return errors.Trace(err) } committer.writeFinishBinlog(binlog.BinlogType_Commit, int64(committer.commitTS)) 如果发生了 error,那么输出的 Binlog 类型就为 binlog.BinlogType_Rollback,如果成功提交,那么输出的 Binlog 类型就为 binlog.BinlogType_Commit。func (c *twoPhaseCommitter) writeFinishBinlog(tp binlog.BinlogType, commitTS int64) { if !c.shouldWriteBinlog() { return } binInfo := c.txn.us.GetOption(kv.BinlogInfo).(*binloginfo.BinlogInfo) binInfo.Data.Tp = tp binInfo.Data.CommitTs = commitTS go func() { err := binInfo.WriteBinlog(c.store.clusterID) if err != nil { log.Errorf("failed to write binlog: %v", err) } }() } 值得注意的是,这里 WriteBinlog 是单独启动 goroutine 异步完成的,也就是 Commit 阶段,是不再需要等待写 binlog 完成的。这里可以节省一点 commit 的等待时间,这里不需要等待是因为 Pump 即使接收不到这个 Commit Binlog,在超过 timeout 时间后,Pump 会自行根据 Prewrite Binlog 到 TiKV 中确认当条事务的提交状态。DDL Binlog 一个 DDL 有如下几个状态:const ( JobStateNone JobState = 0 JobStateRunning JobState = 1 JobStateRollingback JobState = 2 JobStateRollbackDone JobState = 3 JobStateDone JobState = 4 JobStateSynced JobState = 6 JobStateCancelling JobState = 7 ) 这些状态代表了一个 DDL 任务所处的状态: JobStateNone,代表 DDL 任务还在处理队列,TiDB 还没有开始做这个 DDL。 JobStateRunning,当 DDL Owner 开始处理这个任务的时候,会把状态设置为 JobStateRunning,之后 DDL 会开始变更,TiDB 的 Schema 可能会涉及多个状态的变更,这中间不会改变 DDL job 的状态,只会变更 Schema 的状态。 JobStateDone, 当 TiDB 完成自己所有的 Schema 状态变更之后,会把 Job 的状态改为 Done。 JobStateSynced,当 TiDB 每做一次 schema 状态变更,就会需要跟集群中的其他 TiDB 做一次同步,但是当 Job 状态为 JobStateDone 之后,在 TiDB 等到所有的 TiDB 节点同步之后,会将状态修改为 JobStateSynced。 JobStateCancelling,TiDB 提供语法 ADMIN CANCEL DDL JOBS job_ids 用于取消某个正在执行或者还未执行的 DDL 任务,当成功执行这个命令之后,DDL 任务的状态会变为 JobStateCancelling。 JobStateRollingback,当 DDL Owner 发现 Job 的状态变为 JobStateCancelling 之后,它会将 job 的状态改变为 JobStateRollingback,以示已经开始处理 cancel 请求。 JobStateRollbackDone,在做 cancel 的过程,也会涉及 Schema 状态的变更,也需要经历 Schema 的同步,等到状态回滚已经做完了,TiDB 会将 Job 的状态设置为 JobStateRollbackDone。 对于 Binlog 而言,DDL 的 Binlog 输出机制,跟 DML 语句也是类似的,只有开始处理事务提交阶段,才会开始写 Binlog 出去。那么对于 DDL 来说,跟 DML 不一样,DML 有事务的概念,对于 DDL 来说,SQL 的事务是不影响 DDL 语句的。但是 DDL 里面,上面提到的 Job 的状态变更,是作为一个事务来提交的(保证状态一致性)。所以在每个状态变更,都会有一个事务与之对应,但是上面提到的中间状态,DDL 并不会往外写 Binlog,只有 JobStateRollbackDone 以及 JobStateDone 这两种状态,TiDB 会认为 DDL 语句已经完成,会对外发送 Binlog,发送之前,会把 Job 的状态从 JobStateDone 修改为 JobStateSynced,这次修改,也涉及一次事务提交。这块逻辑的代码如下:worker.handleDDLJobQueue(): if job.IsDone() || job.IsRollbackDone() { binloginfo.SetDDLBinlog(d.binlogCli, txn, job.ID, job.Query) if !job.IsRollbackDone() { job.State = model.JobStateSynced } err = w.finishDDLJob(t, job) return errors.Trace(err) } type Binlog struct { DdlQuery []byte DdlJobId int64 } DdlQuery 会设置为原始的 DDL 语句,DdlJobId 会设置为 DDL 的任务 ID。对于最后一次 Job 状态的提交,会有两条 Binlog 与之对应,这里有几种情况: 如果事务提交成功,类型分别为 binlog.BinlogType_Prewrite 和 binlog.BinlogType_Commit。 如果事务提交失败,类型分别为 binlog.BinlogType_Prewrite 和 binlog.BinlogType_Rollback。 所以,Pumps 收到的 DDL Binlog,如果类型为 binlog.BinlogType_Rollback 应该只认为如下状态是合法的: JobStateDone (因为修改为 JobStateSynced 还未成功) JobStateRollbackDone 如果类型为 binlog.BinlogType_Commit,应该只认为如下状态是合法的: JobStateSynced JobStateRollbackDone 当 TiDB 在提交最后一个 Job 状态的时候,如果事务提交失败了,那么 TiDB Owner 会尝试继续修改这个 Job,直到成功。也就是对于同一个 DdlJobId,后续还可能会有多次 Binlog,直到出现 binlog.BinlogType_Commit。"}, {"url": "https://pingcap.com/weekly/2019-01-14-tidb-weekly/", "title": "Weekly update (January 07 ~ January 13, 2019)", "content": " Weekly update in TiDB Last week, we landed 51 PRs in the TiDB repository.Added Add the SHOW CREATE USER syntax Support the named window feature Add support for JSON_MERGE_PRESERVE Propose the design for the plugin framework Add support for the distributed GC Improved Add the row format for trace Restrict tidb_ddl_reorg_worker_cnt and tidb_ddl_reorg_batch_size to be global Build the hash table in parallel for radix hash join Improve Unix socket handling Try closing connections gracefully first in the SIGTERM case Improve the compatibility with MySQL when inserting a negative value into an unsigned column Fixed Fix the mistaken privilege check failure for Update Fix bound overflow of the histogram Get the global value when the system variable only has a global scope Skip the SQL execution if the transaction is aborted Return an error if autoid overflows shard bits Fix the wrong result when int joins decimal Report an error for CAST(AS TIME) if the precision is too big Fix an error of canceling the Rename Index DDL Fix an incorrect behavior of the STR_TO_DATE() function Weekly update in TiSpark Last week, we landed 5 PRs in the TiSpark repository.Fixed Fix the possible NullPointerException issue when show_row_id is set to true Fix the Set type when inserting multiple Set values Weekly update in TiKV and PD Last week, we landed 32 PRs in the TiKV and PD repositories.Added Add a new metric for delayed Raft messages Introduce the Steady timer Implement Locate2Args, Locate3Args, LocateBinary2Args, and LocateBinary3Args in Coprocessor Add the scheduling configuration metrics Implement AddDurationAndString in Coprocessor Implement the FIELD() function in Coprocessor Implement run_afl and add seeds for the fuzzer Improved Provide efficient codecs Use the batch_raft RPC and batch channel to reduce gRPC CPU usage Make apply scale Switch the gRPC event engine from epollsig to epollex Enable the GC worker to do GC by itself Fixed Fix the useless log level Fix the panic when getting overlapping Regions with version 0 Fix the gRPC log redirection New contributors (Thanks!) tidb: alex-lx invzhi lnhote nange shinytang6 snithish tikv: DCjanus aylei gaodayue yeshengm parser: firekillice zhaoxiaojie0415 docs: lastzero "}, {"url": "https://pingcap.com/meetup/meetup-87-20190108/", "title": "【Infra Meetup No.87】摩拜数据复制中心 Gravity 介绍", "content": "在上周六举办的 Infra Meetup No.87 上,来自摩拜的胡明老师为大家介绍了摩拜数据复制中心 Gravity,现场讨论氛围非常热烈,来自摩拜的任弘迪老师也解答了大家的一些疑问。以下是现场视频&文字回顾,enjoy~ 视频 | Infra Meetup No.87:摩拜数据复制中心 Gravity 介绍 PPT 下载链接 Gravity 是摩拜数据库团队使用 Golang 研发的一款数据同步组件。实现了线上数据库变更的实时推送,MySQL 数据库的单向、双向同步。在数据同步过程中,还支持自定义的数据变换(列映射、重命名等)。Gravity 可以使用单进程的方式部署,也可以使用基于 Kubernetes 的集群模式部署。在摩拜内部,Gravity 被用在多种场景下,包括大数据总线的建设,分库分表到合库的同步,大规模数据清洗,配合微服务拆分的数据库实时双向同步。在此次 Meetup 上,大家一起讨论了很多数据同步方面遇到的宝贵经验。包括怎么实现数据库的双向同步,分库分表到合库时 DDL 的处理遇到的坑,在集群模式下可能发生脑裂带来的数据不一致的情况。最后,胡明老师分享了 Gravity 的 Roadmap,包括对 DDL 的支持,统一的序列化格式,bingo 归档,PostgreSQL 的支持,以及原生的 TiDB 增量同步的支持。"}, {"url": "https://pingcap.com/weekly/2019-01-07-tidb-weekly/", "title": "Weekly update (December 31 ~ January 06, 2019)", "content": " Weekly update in TiDB Last week, we landed 38 PRs in the TiDB repository.Added Add support for Views in Infoschema Add support for Drop View Support the Window function in the planner Add support for the default function Implement the parallel partition phase for Radix Hash Join Improved Support the show create database if not exists syntax Forbid committing the transaction if StmtCommit fails Report an error when updating the generated column inside a transaction Improve the compatibility when using an empty database Run FLUSH PRIVILEGES synchronously on GRANT Support show columns from for Views Refine the code about DDL job canceling or rollback Check the user privilege on SET GLOBAL Add the post-process stage after physical optimization Propagate the constraint for > and monotonous increasing functions Fixed Handle the empty input and improve the compatibility for format Fix the panic when writing a table with a column in the write only state Fix the insert failure when dropping a column Fix the hang issue of canceling Add Index Weekly update in TiSpark Last week, we landed 2 PRs in the TiSpark repository.Improved Add retry for getRegion requests when regionId returns 0 Fixed Allow checking the TiSpark version when a database is not selected Weekly update in TiKV and PD Last week, we landed 10 PRs in the TiKV and PD repositories.Added Add the add_duration_and_duration built-in function Improved Upgrade rust-rocksdb to upgrade RocksDB to Titan smoothly Rearrange loggers Retry validating the endpoints when creating a RPC client New contributors (Thanks!) tidb: Westicles1980 acmerfight parser: 924060929 buzzers lnhote shinytang6 docs-cn: zzh1985 docs: b41sh "}, {"url": "https://pingcap.com/blog-cn/tidb-source-code-reading-23/", "title": "TiDB 源码阅读系列文章(二十三)Prepare/Execute 请求处理", "content": " 在之前的一篇文章《TiDB 源码阅读系列文章(三)SQL 的一生》中,我们介绍了 TiDB 在收到客户端请求包时,最常见的 Command --- COM_QUERY 的请求处理流程。本文我们将介绍另外一种大家经常使用的 Command --- Prepare/Execute 请求在 TiDB 中的处理过程。Prepare/Execute Statement 简介 首先我们先简单回顾下客户端使用 Prepare 请求过程: 客户端发起 Prepare 命令将带 “?” 参数占位符的 SQL 语句发送到数据库,成功后返回 stmtID。 具体执行 SQL 时,客户端使用之前返回的 stmtID,并带上请求参数发起 Execute 命令来执行 SQL。 不再需要 Prepare 的语句时,关闭 stmtID 对应的 Prepare 语句。 相比普通请求,Prepare 带来的好处是: 减少每次执行经过 Parser 带来的负担,因为很多场景,线上运行的 SQL 多是相同的内容,仅是参数部分不同,通过 Prepare 可以通过首次准备好带占位符的 SQL,后续只需要填充参数执行就好,可以做到“一次 Parse,多次使用”。 在开启 PreparePlanCache 后可以达到“一次优化,多次使用”,不用进行重复的逻辑和物理优化过程。 更少的网络传输,因为多次执行只用传输参数部分,并且返回结果 Binary 协议。 因为是在执行的同时填充参数,可以防止 SQL 注入风险。 某些特性比如 serverSideCursor 需要是通过 Prepare statement 才能使用。 TiDB 和 MySQL 协议 一样,对于发起 Prepare/Execute 这种使用访问模式提供两种方式: Binary 协议:即上述的使用 COM_STMT_PREPARE,COM_STMT_EXECUTE,COM_STMT_CLOSE 命令并且通过 Binary 协议获取返回结果,这是目前各种应用开发常使用的方式。 文本协议:使用 COM_QUERY,并且用 PREPARE,EXECUTE,DEALLOCATE PREPARE 使用文本协议获取结果,这个效率不如上一种,多用于非程序调用场景,比如在 MySQL 客户端中手工执行。 下面我们主要以 Binary 协议来看下 TiDB 的处理过程。文本协议的处理与 Binary 协议处理过程比较类似,我们会在后面简要介绍一下它们的差异点。COM_STMT_PREPARE 首先,客户端发起 COM_STMT_PREPARE,在 TiDB 收到后会进入 clientConn#handleStmtPrepare,这个函数会通过调用 TiDBContext#Prepare 来进行实际 Prepare 操作并返回 结果 给客户端,实际的 Prepare 处理主要在 session#PrepareStmt 和 PrepareExec 中完成: 调用 Parser 完成文本到 AST 的转换,这部分可以参考《TiDB 源码阅读系列文章(五)TiDB SQL Parser 的实现》。 使用名为 paramMarkerExtractor 的 visitor 从 AST 中提取 “?” 表达式,并根据出现位置(offset)构建排序 Slice,后面我们会看到在 Execute 时会通过这个 Slice 值来快速定位并替换 “?” 占位符。 检查参数个数是否超过 Uint16 最大值(这个是 协议限制,对于参数只提供 2 个 Byte)。 进行 Preprocess, 并且创建 LogicPlan, 这部分实现可以参考之前关于 逻辑优化的介绍,这里生成 LogicPlan 主要为了获取并检查组成 Prepare 响应中需要的列信息。 生成 stmtID,生成的方式是当前会话中的递增 int。 保存 stmtID 到 ast.Prepared (由 AST,参数类型信息,schema 版本,是否使用 PreparedPlanCache 标记组成) 的映射信息到 SessionVars#PreparedStmts 中供 Execute 部分使用。 保存 stmtID 到 TiDBStatement (由 stmtID,参数个数,SQL 返回列类型信息,sendLongData 预 BoundParams 组成)的映射信息保存到 TiDBContext#stmts。 在处理完成之后客户端会收到并持有 stmtID 和参数类型信息,返回列类型信息,后续即可通过 stmtID 进行执行时,server 可以通过 6、7 步保存映射找到已经 Prepare 的信息。COM_STMT_EXECUTE Prepare 成功之后,客户端会通过 COM_STMT_EXECUTE 命令请求执行,TiDB 会进入 clientConn#handleStmtExecute,首先会通过 stmtID 在上节介绍中保存的 TiDBContext#stmts 中获取前面保存的 TiDBStatement,并解析出是否使用 userCursor 和请求参数信息,并且调用对应 TiDBStatement 的 Execute 进行实际的 Execute 逻辑: 生成 ast.ExecuteStmt 并调用 planer.Optimize 生成 plancore.Execute,和普通优化过程不同的是会执行 Exeucte#OptimizePreparedPlan。 使用 stmtID 通过 SessionVars#PreparedStmts 获取到到 Prepare 阶段的 ast.Prepared 信息。 使用上一节第 2 步中准备的 prepared.Params 来快速查找并填充参数值;同时会保存一份参数到 sessionVars.PreparedParams 中,这个主要用于支持 PreparePlanCache 延迟获取参数。 判断对比判断 Prepare 和 Execute 之间 schema 是否有变化,如果有变化则重新 Preprocess。 之后调用 Execute#getPhysicalPlan 获取物理计划,实现中首先会根据是否启用 PreparedPlanCache 来查找已缓存的 Plan,本文后面我们也会专门介绍这个。 在没有开启 PreparedPlanCache 或者开启了但没命中 cache 时,会对 AST 进行一次正常的 Optimize。 在获取到 PhysicalPlan 后就是正常的 Executing 执行。COM_STMT_CLOSE 在客户不再需要执行之前的 Prepared 的语句时,可以通过 COM_STMT_CLOSE 来释放服务器资源,TiDB 收到后会进入 clientConn#handleStmtClose,会通过 stmtID 在 TiDBContext#stmts 中找到对应的 TiDBStatement,并且执行 Close 清理之前的保存的 TiDBContext#stmts 和 SessionVars#PrepareStmts,不过通过代码我们看到,对于前者的确直接进行了清理,对于后者不会删除而是加入到 RetryInfo#DroppedPreparedStmtIDs 中,等待当前事务提交或回滚才会从 SessionVars#PrepareStmts 中清理,之所以延迟删除是由于 TiDB 在事务提交阶段遇到冲突会根据配置决定是否重试事务,参与重试的语句可能只有 Execute 和 Deallocate,为了保证重试还能通过 stmtID 找到 prepared 的语句 TiDB 目前使用延迟到事务执行完成后才做清理。其他 COM_STMT 除了上面介绍的 3 个 COM_STMT,还有另外几个 COM_STMT_SEND_LONG_DATA,COM_STMT_FETCH,COM_STMT_RESET 也会在 Prepare 中使用到。COM_STMT_SEND_LONG_DATA 某些场景我们 SQL 中的参数是 TEXT,TINYTEXT,MEDIUMTEXT,LONGTEXT and BLOB,TINYBLOB,MEDIUMBLOB,LONGBLOB 列时,客户端通常不会在一次 Execute 中带大量的参数,而是单独通过 COM_SEND_LONG_DATA 预先发到 TiDB,最后再进行 Execute。TiDB 的处理在 client#handleStmtSendLongData,通过 stmtID 在 TiDBContext#stmts 中找到 TiDBStatement 并提前放置 paramID 对应的参数信息,进行追加参数到 boundParams(所以客户端其实可以多次 send 数据并追加到一个参数上),Execute 时会通过 stmt.BoundParams() 获取到提前传过来的参数并和 Execute 命令带的参数 一起执行,在每次执行完成后会重置 boundParams。COM_STMT_FETCH 通常的 Execute 执行后,TiDB 会向客户端持续返回结果,返回速率受 max_chunk_size 控制(见《TiDB 源码阅读系列文章(十)Chunk 和执行框架简介》), 但实际中返回的结果集可能非常大。客户端受限于资源(一般是内存)无法一次处理那么多数据,就希望服务端一批批返回,COM_STMT_FETCH 正好解决这个问题。它的使用首先要和 COM_STMT_EXECUTE 配合(也就是必须使用 Prepared 语句执行), handleStmtExeucte 请求协议 flag 中有标记要使用 cursor,execute 在完成 plan 拿到结果集后并不立即执行而是把它缓存到 TiDBStatement 中,并立刻向客户端回包中带上列信息并标记 ServerStatusCursorExists,这部分逻辑可以参看 handleStmtExecute。客户端看到 ServerStatusCursorExists 后,会用 COM_STMT_FETCH 向 TiDB 拉去指定 fetchSize 大小的结果集,在 connClient#handleStmtFetch 中,会通过 session 找到 TiDBStatement 进而找到之前缓存的结果集,开始实际调用执行器的 Next 获取满足 fetchSize 的数据并返回客户端,如果执行器一次 Next 超过了 fetchSize 会只返回 fetchSize 大小的数据并把剩下的数据留着下次再给客户端,最后对于结果集最后一次返回会标记 ServerStatusLastRowSend 的 flag 通知客户端没有后续数据。COM_STMT_RESET 主要用于客户端主动重置 COM_SEND_LONG_DATA 发来的数据,正常 COM_STMT_EXECUTE 后会自动重置,主要针对客户端希望主动废弃之前数据的情况,因为 COM_STMT_SEND_LONG_DATA 是一直追加的操作,客户端某些场景需要主动放弃之前预存的参数,这部分逻辑主要位于 connClient#handleStmtReset 中。Prepared Plan Cache 通过前面的解析过程我们看到在 Prepare 时完成了 AST 转换,在之后的 Execute 会通过 stmtID 找之前的 AST 来进行 Plan 跳过每次都进行 Parse SQL 的开销。如果开启了 Prepare Plan Cache,可进一步在 Execute 处理中重用上次的 PhysicalPlan 结果,省掉查询优化过程的开销。TiDB 可以通过 修改配置文件 开启 Prepare Plan Cache, 开启后每个新 Session 创建时会初始化一个 SimpleLRUCache 类型的 preparedPlanCache 用于保存用于缓存 Plan 结果,缓存的 key 是 pstmtPlanCacheKey(由当前 DB,连接 ID,statementID,schemaVersion, snapshotTs,sqlMode,timezone 组成,所以要命中 plan cache 这以上元素必须都和上次缓存的一致),并根据配置的缓存大小和内存大小做 LRU。在 Execute 的处理逻辑 PrepareExec 中除了检查 PreparePlanCache 是否开启外,还会判断当前的语句是否能使用 PreparePlanCache。 只有 SELECT,INSERT,UPDATE,DELETE 有可能可以使用 PreparedPlanCache 。 并进一步通过 cacheableChecker visitor 检查 AST 中是否有变量表达式,子查询,”order by ?“,”limit ?,?” 和 UnCacheableFunctions 的函数调用等不可以使用 PlanCache 的情况。 如果检查都通过则在 Execute#getPhysicalPlan 中会用当前环境构建 cache key 查找 preparePlanCache。未命中 Cache 我们首先来看下没有命中 Cache 的情况。发现没有命中后会用 stmtID 找到的 AST 执行 Optimize,但和正常执行 Optimize 不同对于 Cache 的 Plan, 我需要对 “?” 做延迟求值处理, 即将占位符转换为一个 function 做 Plan 并 Cache, 后续从 Cache 获取后 function 在执行时再从具体执行上下文中实际获取执行参数。回顾下构建 LogicPlan 的过程中会通过 expressionRewriter 将 AST 转换为各类 expression.Expression,通常对于 ParamMarkerExpr 会重写为 Constant 类型的 expression,但如果该条 stmt 支持 Cache 的话会重写为 Constant 并带上一个特殊的 DeferredExpr 指向一个 GetParam 的函数表达式,而这个函数会在执行时实际从前面 Execute 保存到 sessionVars.PreparedParams 中获取,这样就做到了 Plan 并 Cache 一个参数无关的 Plan,然后实际执行的时填充参数。新获取 Plan 后会保存到 preparedPlanCache 供后续使用。命中 Cache 让我们回到 getPhysicalPlan,如果 Cache 命中在获取 Plan 后我们需要重新 build plan 的 range,因为前面我们保存的 Plan 是一个带 GetParam 的函数表达式,而再次获取后,当前参数值已经变化,我们需要根据当前 Execute 的参数来重新修正 range,这部分逻辑代码位于 Execute#rebuildRange 中,之后就是正常的执行过程了。文本协议的 Prepared 前面主要介绍了二进制协议的 Prepared 执行流程,还有一种执行方式是通过二进制协议来执行。客户端可以通过 COM_QUREY 发送:PREPARE stmt_name FROM prepareable_stmt; EXECUTE stmt_name USING @var_name1, @var_name2,... DEALLOCTE PREPARE stmt_name 来进行 Prepared,TiDB 会走正常 文本 Query 处理流程,将 SQL 转换 Prepare,Execute,Deallocate 的 Plan, 并最终转换为和二进制协议一样的 PrepareExec,ExecuteExec,DealocateExec 的执行器进行执行。写在最后 Prepared 是提高程序 SQL 执行效率的有效手段之一。熟悉 TiDB 的 Prepared 实现,可以帮助各位读者在将来使用 Prepared 时更加得心应手。另外,如果有兴趣向 TiDB 贡献代码的读者,也可以通过本文更快的理解这部分的实现。"}, {"url": "https://pingcap.com/weekly/2019-01-02-tidb-weekly/", "title": "Weekly update (December 24 ~ December 30, 2018)", "content": " Weekly update in TiDB Last week, we landed 57 PRs in the TiDB repository.Added Add SHOW FULL TABLES to display the View status Provide virtual table facilities Improved Generate the logical property for Group of the Cascades planner Improve the label of the QueryDurationHistogram metric Improve the compatibility with the old MySQL handshake protocol Support building DataS from View Remove the timezone information of records in mysql.tidb Improve selectivity estimation for the correlated column Refine transaction commit slow logs Check the null flag when eliminating the count aggregation Add support for recursive virtual columns Use single delRange and sessionPool in DDL Check the privilege of the ANALYZE TABLE statement Adjust the worker number of Add Index dynamically Fixed Avoid the overlapped range for the prefix index Validate the value for the time_zone global system variable Fix the panic when generating the index join for partitioned tables Avoid goroutine leak for hash aggregation Fix the failure when executing delete...from...join... with binlog enabled Fix the problem of adding partitions concurrently Fix the parser panic caused by hint in the subquery Return null when casting non-binary data to the huge binary type Fix the bug of canceling Drop Column Fix the bug of canceling Drop Table/Database Weekly update in TiSpark Last week, we landed 9 PRs in the TiSpark repository.Added Add set and enum type support Add time type support Add year type support Weekly update in TiKV and PD Last week, we landed 34 PRs in the TiKV and PD repositories.Added Introduce a new storage engine: Titan Introduce the batch system Add left_binary and right_binary built-in functions Add the add to_days built-in function Add the date_diff built-in function Use Observers to collect Region information to a collection Implement the Seek Region on RegionInfoAccessor Improved Perform a full synchronization of a new member New contributors (Thanks!) tikv: Fullstop000 GinYM edwardpku parser: SwanSpouse liutang123 o-oops wangbo docs-cn: httpcheck "}, {"url": "https://pingcap.com/blog-cn/for-community-tidb-2019-level-up/", "title": "写给社区的回顾和展望:TiDB 2019, Level Up!", "content": "2018 年对于 TiDB 和 PingCAP 来说是一个由少年向成年的转换的一年,如果用一个关键字来概括就是「蜕变」。在这一年很欣喜的看到 TiDB 和 TiKV 在越来越多的用户使用在了越来越广泛的场景中,作为一个刚刚 3 岁多的开源项目,没有背后强大的社区的话,是没有办法取得这样的进展的。 同时在技术上,2018 年我觉得也交出了一份令人满意的答卷,TiDB 的几个主要项目今年一共合并了 4380 个提交,这几天在整理 2018 年的 Change Log 时候,对比了一下年初的版本,这 4380 个 Commits 背后代表了什么,这里简单写一个文章总结一下。回想起来,TiDB 是最早定位为 HTAP 的通用分布式数据库之一,如果熟悉我们的老朋友一定知道,我们最早时候一直都是定位 NewSQL,当然现在也是。但是 NewSQL 这个词有个问题,到底 New 在哪,解决了哪些问题,很难一目了然,其实一开始我们就想解决一个 MySQL 分库分表的问题,但是后来慢慢随着我们的用户越来越多,使用的场景也越来越清晰,很多用户的场景已经开始超出了一个「更大的 MySQL 」的使用范围,于是我们从实验室和学术界找到了我们觉得更加清晰的定义:HTAP,希望能构建一个融合 OLTP 和 OLAP 通用型分布式数据库。但是要达成这个目标非常复杂,我们的判断是如果不是从最底层重新设计,很难达到我们的目标,我们认为这是一条更困难但是正确的路,现在看来,这条路是走对了,而且未来会越走越快,越走越稳。在 SQL 层这边,TiDB 选择了 MySQL 的协议兼容,一方面持续的加强语法兼容性,另一方面选择自研优化器和执行器,带来的好处就是没有任何历史负担持续优化。回顾今年最大的一个工作应该是重构了执行器框架,TiDB的 SQL 层还是经典的 Volcano 模型,我们引入了新的内存数据结构 Chunk 来批量处理多行数据,并对各个算子都实现了基于 Chunk 的迭代器接口,这个改进对于 OLAP 请求的改进非常明显,在 TiDB 的 TPC-H 测试集上能看出来(https://github.com/pingcap/docs-cn/blob/master/benchmark/tpch.md),Chunk 的引入为我们全面的向量化执行和 CodeGen 支持打下了基础。目前在 TiKV 内部对于下推算子的执行还没有使用 Chunk 改造,不过这个已经在计划中,在 TiKV 中这个改进,预期对查询性能的提升也将非常显著。另一方面,一个数据库查询引擎最核心的组件之一:优化器,在今年也有长足的进步。我们在 2017 年就已经全面引入了基于代价的 SQL 优化(CBO,Cost-Based Optimization),我们在今年改进了我们的代价评估模型,加入了一些新的优化规则,同时实现了 Join Re-Order 等一系列优化,从结果上来看,目前在 TPC-H 的测试集上,对于所有 Query,TiDB 的 SQL 优化器大多已给出了最优的执行计划。CBO 的另一个关键模块是统计信息收集,在今年,我们引入了自动的统计信息收集算法,使优化器的适应性更强。另外针对 OLTP 的场景 TiDB 仍然保留了轻量的 RBO 甚至直接 Bypass 优化器,以提升 OLTP 性能。另外,感谢三星韩国研究院的几位工程师的贡献,他们给 TiDB 引入了 Query Plan Cache,对高并发场景下查询性能的提升也很明显。另外在功能上,我们引入了 Partition Table 的支持,对于一些 Partition 特性很明显的业务,TiDB 能够更加高效的调度数据的写入读取和更新。一直以来,TiDB 的 SQL 层作为纯 Go 语言实现的最完备的 MySQL 语法兼容层,很多第三方的 MySQL 工具在使用着 TiDB 的 SQL Parser,其中的优秀代表比如 小米的 Soar(https://github.com/XiaoMi/soar)。为了方便第三方更好的复用 TiDB Parser,我们在 2018 年将 Parser 从主项目中剥离了出来,成为了一个独立的项目:pingcap/parser,希望能帮到更多的人。说到 TiDB 的底层存储 TiKV 今年也有很多让人眼前一亮的更新。在 TiKV 的基石——一致性算法 Raft 这边,大家知道 TiKV 采用的是 Multi-Raft 的架构,内部通过无数个 Raft Group 动态的分裂、合并、移动以达到动态伸缩和动态负载均衡。我们在今年仍然持续在扩展 Multi-Raft 的边界,我们今年加入了动态的 Raft Group 合并,以减轻元信息存储和心跳通信的负担;给 Raft 扩展了 Learner 角色(只同步 Log 不投票的角色) 为 OLAP Read 打下基础;给 Raft 的基础算法加入了 Pre-Vote 的阶段,让整个系统在异常网络状态下可靠性更高。Raft Group Merge在性能方面,我们花了很大的精力重构了我们单机上多 Raft Group 的线程模型(https://github.com/tikv/tikv/pull/3568), 虽然还没有合并到 master 分支,在我们测试中,这个优化带来了两倍以上的吞吐提升,同时写入延迟降低至现在的版本的 1⁄2 ,预计在这两周我们会完成这个巨大的 PR 的 Code Review,各位同学可以期待一下 :)第三件事情是我们开始将 TiKV 的本地存储引擎的接口彻底抽象出来,目标是能做到对 RocksDB 的弱耦合,这点的意义很大,不管是社区还是我们自己,对新的单机存储引擎支持将变得更加方便。在 TiKV 社区这边,今年的另外一件大事是加入了 CNCF,变成了 CNCF 的托管项目,也是 CNCF 基金会第一个非结构化数据库项目。 后来很多朋友问我,为什么捐赠的是 TiKV 而不是 TiDB,其实主要的原因就像我在当天的一条 Tweet 说的,TiKV 更像是的一个更加通用的组件,当你有一个可以弹性伸缩的,支持跨行 ACID 事务的 Key-Value 数据库时,你会发现构建其他很多可靠的分布式系统会容易很多,这在我们之后的 TiDB Hackathon 中得到了很好的体现。另外社区已经开始出现基于 TiKV 构建的 Redis 协议支持,以及分布式队列系统,例如 meitu/titan 项目。作为一个基金会项目,社区不仅仅可以直接使用,更能够将它作为构建其他系统的基石,我觉得更加有意义。类似的,今年我们将我们的 Raft 实现从主项目中独立了出来(pingcap/raft-rs),也是希望更多的人能从中受益。 “……其 KV与 SQL分层的方式,刚好符合我们提供 NoSQL 存储和关系型存储的需求,另外,PingCAP 的文档齐全,社区活跃,也已经在实际应用场景有大规模的应用,公司在北京,技术交流也非常方便,事实证明,后面提到的这几个优势都是对的……”——美图公司 Titan 项目负责人任勇全对 TiKV 的评论 在 TiDB 的设计之初,我们坚定将调度和元信息从存储层剥离出来(PD),现在看来,好处正渐渐开始显示出来。今年在 PD 上我们花了很大精力在处理热点探测和快速热点调度,调度和存储分离的架构让我们不管是在开发,测试还是上线新的调度策略时效率很高。瞬时热点一直是分布式存储的最大敌人,如何快速发现和处理,我们也有计划尝试将机器学习引入 PD 的调度中,这是 2019 会尝试的一个事情。总体来说,这个是一个长期的课题。我在几个月前的一篇文章提到过 TiDB 为什么从 Day-1 起就 All-in Kubernetes (《十问 TiDB:关于架构设计的一些思考》),今年很欣喜的看到,Kubernetes 及其周边生态已经渐渐成熟,已经开始有很多公司用 Kubernetes 来运行 Mission-critical 的系统,这也佐证了我们当年的判断。2018 年下半年,我们也开源了我们的 TiDB Operator(https://github.com/pingcap/tidb-operator),这个项目并不止是一个简单的在 K8s 上自动化运维 TiDB 的工具,在我们的战略里面,是作为 Cloud TiDB 的重要基座,过去设计一个完善的多租户系统是一件非常困难的事情,同时调度对象是数据库这种带状态服务,更是难上加难,TiDB-Operator 的开源也是希望能够借助社区的力量,一起将它做好。多租户 TiDB今年还做了一件很大的事情,我们成立了一个新的部门 TEP(TiDB Enterprise Platform)专注于商业化组件及相关的交付质量控制。作为一个企业级的分布式数据库,TiDB 今年完成了商业化从0到1的跨越,越来越多的付费客户证明 TiDB 的核心的成熟度已经可以委以重任,成立 TEP 小组也是希望在企业级产品方向上继续发力。从 TiDB-Lightning(MySQL 到 TiDB 高速离线数据导入工具)到 TiDB-DM(TiDB-DataMigration,端到端的数据迁移-同步工具)能看到发力的重点在让用户无缝的从上游迁移到 TiDB 上。另一方面,TiDB-Binlog 虽然不是今年的新东西,但是今年这一年在无数个社区用户的场景中锻炼,越来越稳定。做工具可能在很多人看来并不是那么「高科技」, 很多时候也确实是脏活累活,但是这些经过无数用户场景打磨的周边工具和生态才是一个成熟的基础软件的护城河和竞争壁垒,在 PingCAP 内部,负责工具和外围系统研发的团队规模几乎和内核团队是 1:1 的配比,重要性可见一斑。在使用场景上,TiDB 的使用规模也越来越大,下面这张图是我们统计的我们已知 TiDB 的用户,包括上线和准上线的用户,从 1.0 GA 后,几乎是以一个指数函数的曲线在增长,应用的场景也从简单的 MySQL Sharding 替代方案变成横跨 OLTP 到实时数据中台的通用数据平台组件。TiDB 的用户数统计今年几个比较典型的 用户案例,从 美团 的横跨 OLTP 和实时数仓的深度实践,到 转转 的 All-in TiDB 的体验,再到 TiDB 支撑的北京银行的核心交易系统。可以看到,这些案例从互联网公司的离线线数据存储到要求极端 SLA 的传统银行核心交易系统,TiDB 在这些场景里面都发光发热,甚至有互联网公司(转转)都喊出了 All-in TiDB 的口号,我们非常珍视这份信任,一定尽全力做出漂亮的产品,高质量的服务好我们的用户和客户。另一方面,TiDB 也慢慢开始产生国际影响力的,在线视频巨头葫芦软件(Hulu.com),印度最大的在线票务网站 BookMyShow,东南亚最大的电商之一 Shopee 等等都在大规模的使用 TiDB,在北美和欧洲也已经不少准上线和测试中的的巨头互联网公司。简单回顾了一下过去的 2018 年,我们看看未来在哪里。其实从我们在 2018 年做的几个比较大的技术决策就能看到,2019 年将是上面几个方向的延续。大的方向的几个指导思想是: Predicable. (靠谱,在更广泛的场景中,做到行为可预测。) Make it right before making it fast.(稳定,先做稳,再做快。) Ease of use. (好用,简单交给用户,复杂留给自己。) 对于真正的 HTAP 场景来说,最大的挑战的是如何很好的做不同类型的 workload 隔离和数据结构根据访问特性自适应。我们在这个问题上给出了自己的答案:通过拓展 Raft 的算法,将不同的副本存储成异构的数据结构以适应不同类型的查询。这个方法有以下好处: 本身在 Multi-Raft 的层面上修改,不会出现由数据传输组件造成的瓶颈(类似 Kafka 或者 DTS),因为 Multi-Raft 本身就是可扩展的,数据同步的单位从 binlog,变成 Raft log,这个效率会更高,进一步降低了同步的延迟。 更好的资源隔离,通过 PD 的调度,可以真正将不同的副本调度到隔离的物理机器上,真正做到互不影响。 TiDB 2019 年会变成这个样子Learner 在 HTAP 中的应用在执行器方面,我们会继续推进向量化,不出意外的话,今年会完成所有算子的全路径的向量化执行。这个 HTAP 方案的另一个关键是存储引擎本身。2019 年,我们会引入新的存储引擎,当然第一阶段仍然会继续在 RocksDB 上改进,改进的目标仍然是减小 LSM-Tree 本身的写放大问题。选用的模型是 WiscKey(FAST16),WiscKey 的核心思想是将 Value 从 LSM-Tree 中剥离出来,以减少写放大,如果关注 TiKV 的朋友,已经能注意到我们已经在前几天将一个新存储引擎 Titan(PingCAP 的 Titan,很遗憾和美图那个项目重名了)合并到了 TiKV 的主干分支,这个 Titan 是我们在 RocksDB 上的 WiscKey 模型的一个实现,除了 WiscKey 的核心本身,我们还加入了对小 KV 的 inline 等优化,Titan 在我们的内部测试中效果很好,对长度随机的 key-value 写入的吞吐基本能达到原生 RocksDB 的 2 - 3 倍,当然性能提升并不是我最关注的,这个引擎对于 TiDB 最大的意义就是,这个引擎将让 TiDB 适应性更强,做到更加稳定,更加「可预测」。TiKV 新的本地存储引擎 Titan在 Titan 走向稳定的同时,我们也在调研从头构建一个更适合 TiDB 的 OLTP workload 的存储引擎,前面说到 2018 年做了抽象 TiKV 的本地存储引擎的事情就是为了这个打基础,当然我们仍然会走 LSM-Tree 的路线。这里多提一句,其实很多人都误解了 LSM-Tree 模型的真正优势,在我看来并不是性能,而是:做到可接受的性能的同时,LSM-Tree 的实现非常简单可维护,只有简单的东西才可以依赖,这个决定和我们在 Raft 与 Paxos 之间的选择偏好也是一致的。另外 LSM-Tree 的设计从宏观上来说,更加符合「冷热分层」以适配异构存储介质的想法,这个我相信是未来在存储硬件上的大趋势。至于在 OLAP 的存储引擎这边,我们走的就是纯列式存储的路线了,但是会和传统的 Columnar 数据结构的设计不太一样,这块的进展,我们会在 1 月 19 号的 TiDB DevCon 上首秀,这里先卖个关子。另一个大的方向是事务模型,目前来说,TiDB 从诞生起,事务模型就没有改变过,走的是传统的 Percolator 的 2PC。这个模型的好处是简单,吞吐也不是瓶颈,但是一个比较大的问题是延迟,尤其在跨数据中心的场景中,这里的延迟主要表现在往 TSO 拿时间戳的网络 roundtrip 上,当然了,我目前仍然认为时钟(TSO)是一个必不可少组件,在不降低一致性和隔离级别的大前提下也是目前我们的最好选择,另外 Percolator 的模型也不是没有办法对延迟进行优化,我们其实在 2018 年,针对 Percolator 本身做了一些理论上的改进,减少了几次网络的 roundtrip,也在年中书写了新的 2PC 改进的 完整的 TLA+ 的证明,证明了这个新算法的正确性,2019 年在这块还会有比较多的改进,其实我们一直在思考,怎么样能够做得更好,选择合适的时机做合适的优化。另外一方面,在事务模型这方面,2PC 在理论和工程上还有很多可以改进的空间,但是现在的当务之急继续的优化代码结构和整体的稳定性,这部分的工作在未来一段时间还是会专注在理论和证明上。另外一点大家可以期待的是,2019 年我们会引入安全的 Follower/Learner Read,这对保持一致性的前提下读的吞吐会提升,另外在跨数据中心的场景,读的延迟会更小。差不多就这些吧,最后放一句我特别喜欢的丘吉尔的一句名言作为结尾。Success is not final, failure is not fatal: it is the courage to continue that counts.成功不是终点,失败也并非终结,最重要的是继续前进的勇气。"}, {"url": "https://pingcap.com/blog-cn/tidb-hackathon-2018-06/", "title": "TBSSQL 的那些事 | TiDB Hackathon 2018", "content": " 本文作者是来自 TiBoys 队的崔秋同学,他们的项目 TBSSQL 在 TiDB Hackathon 2018 中获得了一等奖。TiDB Batch and Streaming SQL(简称 TBSSQL)扩展了 TiDB 的 SQL 引擎,支持用户以类似 StreamSQL 的语法将 Kafka、Pulsar 等外部数据源以流式表的方式接入 TiDB。通过简单的 SQL 语句,用户可以实现对流式数据的过滤,流式表与普通表的 Join(比如流式事实表与多个普通维度表),甚至通过 CREATE TABLE AS SELECT 语法将处理过的流式数据写入普通表中。此外,针对流式数据的时间属性,我们实现了基于时间窗口的聚合/排序算子,使得我们可以对流式数据进行时间维度的聚合/排序。 序 算起来这应该是第三次参加的 Hackathon 了,第一次参加的时候还是在小西天的豌豆荚,和东旭一起,做跨平台数据传输的工具,两天一夜;第二次和奇叔一起在 3W 咖啡,又是两天一夜;这次在自己家举办 Hackathon 比赛,下定决心一定要佛性一些,本着能抱大腿就不单干的心态,迅速决定拉唐长老(唐刘)下水。接下来就计划着折腾点啥,因为我们两个前端都不怎么样,所以只能硬核一些,于是拍了两个方案。方案一:之前跟唐长老合作过很长一段时间,我们两个对于测试质量之类的事情也都非常关注,所以想着能不能在 Chaos 系统上做一些文章,把一些前沿的测试理论和经验方法结合到系统里面来,做一套通用的分布式系统测试框架,就像 Jepsen 那样,用这套系统去测试和验证主流的开源分布式项目。方案二:越接近于业务实时性的数据处理越有价值,不管是 Kafka/KSQL,Flink/Spark Streaming 都是在向着实时流计算领域方向进行未来的探索。TiDB 虽然已经能够支持类 Real Time OLAP 的场景,但是对于更实时的流式数据处理方面还没有合适的解决方案,不过 TiDB 具有非常好的 Scale 能力,天然的能存储海量的数据库表数据,所以在 Streaming Event 和 Table 关联的场景下具有非常明显的优势。如果在 TiDB 上能够实现一个 Streaming SQL 的引擎,实现 Batch/Streaming 的计算融合,那将会是一件非常有意思的事情。因为打 Hackathon 比赛主要是希望折腾一些新的东西,所以我们两个简单讨论完了之后还是倾向于方案二,当然做不做的出来另说。当我们正准备做前期调研和设计的时候,Hackathon 主办方把唐长老拉去做现场导师,参赛规则规定导师不能下场比赛,囧,于是就这样被被动放了鸽子。好在后来遇到了同样被霸哥(韩飞)当导师而放鸽子的川总(杜川),川总对于 Streaming SQL 非常感兴趣,于是难兄难弟一拍即合,迅速决定抱团取暖。随后,Robot 又介绍了同样还没有组队的社区小伙伴 GZY(高志远),这样算是凑齐了三个人,但是一想到没有前端肯定搞不定,于是就拜托娘家人(Dashbase)的交际小王子 WPH(王鹏翰)出马,帮助去召唤一个靠谱的前端小伙伴,后来交际未果直接把自己卖进了队伍,这样终于凑齐了四后端,不,应该是三后端 + 一伪前端的组合。因为马上要准备提交项目和团队名称,大家都一致觉得方案二非常有意思,所以就选定了更加儒雅的 TBSSQL(TiDB Batch and Streaming SQL)作为项目名称,TSBSQL 遗憾落选。在团队名称方面,打酱油老男孩 / Scboy / TiStream / 养生 Hackathon / 佛系 Hackathon 都因为不够符合气质被遗憾淘汰,最后代表更有青春气息的 TiBoys 入选(跟着我左手右手一个慢动作,逃……前期准备 所谓 “三军未动, 粮草先行”,既然已经报名了,还是要稍作准备,虽然已经确定了大的方向,但是具体的落地方案还没有细化,而且人员的分工也不是太明确。又经过一轮简单的讨论之后,明确了大家的职责方向,我这边主要负责项目整体设计,进度管理以及和 TiDB 核心相关的代码,川总主要负责 TiDB 核心技术攻关,GZY 负责流数据源数据的采集部分,WPH 负责前端展现以及 Hackathon 当天的 Demo 演示,分工之后大家就开始分头调研动工。作为这两年来基本没怎么写过代码的退役型选手来说,心里还是非常没底的,也不知道现在 TiDB 代码结构和细节变成什么样了,不求有功,但求别太拖后腿。对于项目本身的典型应用场景,大家还是比较明确的,觉得这个方向是非常有意义的。 应用层系统:实时流事件和离线数据的关联查询,比如在线广告推荐系统,在线推荐系统,在线搜索,以及实时反欺诈系统等。 内部数据系统: 实时数据采样统计,比如内部监控系统; 时间窗口数据分析系统,比如实时的数据流数据分析(分析一段时间内异常的数据流量和系统指标),用于辅助做 AI Ops 相关的事情(比如根据数据流量做节点自动扩容/自动提供参数调优/异常流量和风险报告等等)。 业界 Streaming 相关的系统很多,前期我这边快速地看了下能不能站在巨人的肩膀上做事情,有没有可借鉴或者可借用的开源项目。 Apache Beam本质上 Apache Beam 还是一个批处理和流处理融合的 SDK Model,用户可以在应用层使用更简单通用的函数接口实现业务的处理,如果使用 Beam 的话,还需要实现自定义的 Runner,因为 TiDB 本身主要的架构设计非常偏重于数据库方向,内部并没有特别明确的通用型计算引擎,所以现阶段基本上没有太大的可行性。当然也可以选择用 Flink 作为 Runner 连接 TiDB 数据源,但是这就变成了 Flink&TiDB 的事情了,和 Beam 本身关系其实就不大了。 Apache Flink / Spark StreamingFlink 是一个典型的流处理系统,批处理可以用流处理来模拟出来。本身 Flink 也是支持 SQL 的,但是是一种嵌入式 SQL,也就是 SQL 和应用程序代码写在一起,这种做法的好处是可以直接和应用层进行整合,但是不好的地方在于,接口不是太清晰,有业务侵入性。阿里内部有一个增强版的 Flink 项目叫 Blink,在这个领域比较活跃。如果要实现批处理和流处理融合的话,需要内部定制和修改 Flink 的代码,把 TiDB 作为数据源对接起来,还有可能需要把一些环境信息提交给 TiDB 以便得到更好的查询结果,当然或许像 TiSpark 那样,直接 Flink 对接 TiKV 的数据源应该也是可以的。因为本身团队对于 Scala/Java 代码不是很熟悉,而且 Flink 的模式会有一定的侵入性,所以就没有在这方面进行更多的探索。同理,没有选择 Spark Streaming 也是类似的原因。当然有兴趣的小伙伴可以尝试下这个方向,也是非常有意思的。 Kafka SQL因为 Kafka 本身只是一个 MQ,以后会向着流处理方向演进,但是目前并没有实现批处理和流处理统一的潜力,所以更多的我们只是借鉴 Kafka SQL 的语法。目前 Streaming SQL 还没有一个统一的标准 SQL,Kafka SQL 也只是一个 SQL 方言,支持的语法还比较简单,但是非常实用,而且是偏交互式的,没有业务侵入性。非常适合在 Hackathon 上做 Demo 演示,我们在项目实现中也是主要参考了 Kafka SQL 的定义,当然,Flink 和 Calcite 也有自己定义的 Streaming 语法,这里就不再讨论了。 调研准备工作讨论到这里基本上也就差不多了,于是我们开始各自备(hua)战(shui),出差的出差,加班的加班,接客户的接客户,学 Golang 的学 Golang,在这种紧(fang)张(fei)无(zi)比(wo)的节奏中,迎来了 Hackathon 比赛的到来。Hackathon 流水账 具体的技术实现方面都是比较硬核的东西,细节也比较多,扔在最后面写,免的大家看到一半就点×了。至于参加 Hackathon 的感受,因为不像龙哥那么文豪,也不像马老师那么俏皮,而且本来读书也不多,所以也只能喊一句“黑客马拉松真是太好玩了”! Day 1 3:30 AM 由于飞机晚点,川总这个点儿才辗转到酒店。睡觉之前非常担心一觉睡过头,让这趟 Hackathon 之旅还没开始就结束了,没想到躺下以后满脑子都是技术细节,怎么都睡不着。漫漫长夜,无眠。7:45 AM 川总早早来到 Hackathon 现场。由于来太早,其他选手都还没到,所以他提前刺探刺探敌情的计划也泡汤了,只好在赛场瞎晃悠一番熟悉熟悉环境,顺道跟大奖合了个影。11:00 AM 简单的开幕式之后,Hackathon 正式开始。我们首先搞定的是 Streaming SQL 的语法定义以及 Parser 相关改动。这一部分在之前就经过比较详细的在线讨论了,所以现场只需要根据碰头后统一的想法一顿敲敲敲就搞定了。快速搞定这一块以后,我们就有了 SQL 语法层面的 Streaming 实现。当然此时 Streaming 也仅限于语法层面,Streaming 在 SQL 引擎层面对应的其实还是普通的TiDB Table。接下来是 DDL 部分。这一块我们已经想好了要复用 TiDB Table 的 Meta 结构 TableInfo ,因此主要工作就是按照 DDL源码解析 依葫芦画瓢,难度也不大,以至于我们还有闲心纠结一下 SHOW TABLES 语法里到底要不要屏蔽掉 Streaming Table 的问题。整体上来看上午的热身活动还是进行的比较顺利的,起码 Streaming DDL 这块没有成为太大的问题。这里面有个插曲就是我在 Hackathon 之前下载编译 TiDB,结果发现 TiDB 的 parser 已经用上时髦的 go module 了(也是好久好久没看 TiDB 代码),折腾好半天,不过好处就是 Hackathon 当天的时候改起来 parser 就比较轻车熟路了,所以赛前编译一个 TiDB 还是非常有必要的。15:30 PM 随着热身的结束,马上迎来了稳定的敲敲敲阶段。川总简单弄了一个 Mock 的 StreamReader 然后丢给了我,因为我之前写 TiDB 的时候,时代比较遥远,那时候都还在用周 sir 的 Datum,现在一看,为了提高内存效率和性能,已经换成了高大上的 Chunk,于是一个很常见的问题:如何用最正确的做法把一个传过来的 Json 数据格式化成 Table Row 数据放到 Chunk 里面,让彻底我懵逼了。这里面倒不是技术的问题,主要是类型太多,如果枚举所有类型,搞起来很麻烦,按道理应该有更轻快的办法,但是翻了源代码还是没找到解决方案。这个时候果断去求助现场导师,也顺便去赛场溜(ci)达(tan)一(di)圈(qing)。随便扫了一眼,惊呆了,龙哥他们竟然已经开始写 PPT 了,之前知道龙哥他们强,但是没想到强到这个地步,还让不让大家一块欢快地玩耍了。同时,也了解到了不少非常有意思的项目,比如用机器学习方法去自动调节 TiDB 的调度参数,用 Lua 给 TiKV 添加 UDF 之类的,在 TiDB 上面实现异构数据库的关联查询(简直就是 F1 的大一统,而且听小道消息,他们都已经把 Join 推到 PG 上面去了,然而我们还没开始进入到核心开发流程),在 TiKV 上面实现时序数据库和 Memcached 协议等等,甚至东旭都按捺不住自己 Hackathon 起来了(嘻嘻,可以学学我啊 ;D )。本来还想去聊聊各个项目的具体实现方案,但是一想到自己挖了一堆坑还没填,只能默默回去膜拜 TiNiuB 项目。看起来不能太佛系了,于是乎我赶紧召开了一次内部团队 sync 的 catch up,明确下分工,川总开始死磕 TBSSQL 的核心逻辑 Streaming Aggregation 的实现,我这边继续搞不带 Aggregation 的 Streaming SQL 的其他实现,GZY 已经部署起来了 Pulsar,开始准备 Mock 数据,WPH 辅助 GZY 同时也快速理解我们的 Demo 场景,着手设计实现前端展现。18:00 PM 我这边和面带慈父般欣慰笑容的老师(张建)进行了一些技术方案实现上的交流后,了解到目前社区小伙伴已经在搞 CREATE TABLE AS SELECT 的重要信息(后续证明此信息值大概一千块 RMB)。此时,在解决了之前的问题之后,TBSSQL 终于能跑通简单的 SELECT 语句了。我们心里稍微有点底了,于是一鼓作气,顺路也实现了带 Where 条件的 Stream Table 的 SELECT,以及 Stream Table 和 TiDB Table 的多表 Join,到这里,此时,按照分工,我这边的主体工作除了 Streaming Position 的持久化支持以外,已经写的差不多了,剩下就是去实现一些 Nice to have 的 DDL 的语法支持。川总这里首先要搞的是基于时间窗口的 Streaming Aggregation。按照我们的如意算盘,这里基本上可以复用 TiDB 现有的 Hash Aggregation 的计算逻辑,只需要加上窗口的处理就完事儿了。不过实际下手的时候仔细一研究代码,发现 Aggregation 这一块代码在川总疏于研究这一段时间已经被重构了一把,加上了一个并发执行的分支,看起来还挺复杂。于是一不做二不休,川总把 Hash Aggregation 的代码拷了一份,删除了并发执行的逻辑,在比较简单的非并发分支加上窗口相关实现。不过这种方法意味着带时间窗口的 Aggregation 得单独出 Plan,Planner 上又得改一大圈。这一块弄完以后,还没来得及调试,就到吃晚饭的点儿了。21:00 PM 吃完晚饭,因为下午死磕的比较厉害,我和张建、川总出门去园区溜达了一圈。期间张建问我们搞得咋样了,我望了一眼川总,语重心长地说主要成败已经不在我了(后续证明这句语重心长至少也得值一千块 RMB),川总果断信心满满地说问题不大,一切尽在掌握之中。没想到这个 Flag 刚立起来还是温的,就立马被打脸了。问题出在吃饭前搞的聚合那块(具体细节可以看下后面的坑系列),为了支持时间窗口,我们必须确保 Streaming 上的窗口列能透传到聚合算子当中,为此我们屏蔽了优化器中窗口聚合上的列裁剪规则。可是实际运行当中,我们的修改并没有生效???而此时,川总昨天一整晚没睡觉的副作用开始显现出来了,思路已经有点不太清醒了。于是我们把张建拖过来一起 debug。然后我这边也把用 TiDB Global Variable 控制 Streaming Position 的功能实现了,并且和 GZY 这边也实现了 Mock 数据。之后,我也顺路休息休息,毕竟川总这边搞不定,我们这边搞的再好也没啥用。除了观摩川总和张建手把手,不,肩并肩结对小黑屋编程之外,我也顺便申请了部署 Kafka 联调的机器。23:00 PM 我们这边最核心的功能还没突破,亮眼的 CREATE TABLE AS SELECT Streaming 也还没影,其实中期进度还是偏慢了(或者说之前我设计实现的功能的工作量太大了,看起来今天晚上只能死磕了,囧)。我调试 Kafka 死活调不通,端口可以 Telnet 登陆,但是写入和获取数据的时候一直报超时错误,而且我这边已经开始困上来了,有点扛不动了,后来在 Kafka 老司机 WPH 一起看了下配置参数,才发现 Advertise URL 设置成了本地地址,换成对外的 IP 就好了,当然为了简单方便,我们设置了单 Partition 的 Topic,这样 collector 的 Kafka 部分就搞的差不多了,剩下就是实现一个 http 的 restful api 来提供给 TiDB 的 StreamReader 读取,整个连通工作就差不多了。Day 2 00:00 AM 这时候川总那边也传来了好消息,终于从 Streaming Aggregation 这个大坑里面爬出来了,后面也比较顺利地搞定了时间窗口上的聚合这块。此时时间已经到了 Hackathon 的第二天,不少其他项目的小伙伴已经收摊回家了。不过我们抱着能多做一个 Feature 是一个的心态,决定挑灯夜战。首先,川总把 Sort Executor 改了一把以支持时间窗口,可能刚刚的踩坑经历为我们攒了人品,Sort 上的改动竟然一次 AC 了。借着这股劲儿,我们又回头优化了一把 SHOW CREATE STREAM 的输出。这里有个插曲就是为了近距离再回味和感受下之前的开发流程,我们特意在 TiDB 的 repo 里面开了一个 tiboys/hackathon 的分支,然后提交的时候用了标准的 Pull Request 的方式,点赞了才能 merge(后来想想打 Hackathon 不是太可取,没什么用,还挺耽误时间,不知道当时怎么想的),所以在 master 分支和 tiboys/hackathon 分支看的时候都没有任何提交记录。嘻嘻,估计龙哥也没仔细看我们的 repo,所以其实在龙哥的激励下,我们的效率还是可以的 :) 。2:30 AM GZY 和 WPH 把今天安排的工作完成的差不多了,而且第二天还靠他们主要准备 Demo Show,就去睡觉了,川总也已经困得不行了,准备打烊睡觉。我和川总合计了一下,还差一个最重要的 Feature,抱着就试一把,不行就手工的心态,我们把社区的小伙伴王聪(bb7133)提的支持 CREATE TABLE AS SELECT 语法的 PR 合到了我们的分支,冲突竟然不是太多,然后稍微改了一下来支持 Streaming,结果一运行奇迹般地发现竟然能够运行,RP 全面爆发了,于是我们就近乎免费地增加了一个 Feature。改完这个地方,川总实在坚持不住了,就回去睡了。我这边的 http restful api 也搞的差不多了,准备联调一把,StreamReader 通过 http client 从 collector 读数据,collector 通过 kafka consumer 从 kafka broker 获取数据,结果获取的 Json 数据序列化成 TiDB 自定义的 Time 类型老是出问题,于是我又花了一些时间给 Time 增加了 Marshall 和 Unmarshal 的格式化支持,到这里基本上可以 work 了,看了看时间,凌晨四点半,我也准备去睡了。期间好几次看到霸哥(韩飞)凌晨还在一直帮小(tian)伙(zi)伴(ji)查(wa)问(de)题(keng),其实霸哥认真的时候还是非常靠谱的。7:30 AM 这个时候人陆陆续续地来了,我这边也进入了打酱油的角色,年纪大了确实刚不动了,吃了早餐之后,开始准备思考接下来的分工。因为大家都是临时组队,到了 Hackathon 才碰面,基本上没有太多磨合,而且普遍第二天状态都不大好。虽然大家都很努力,但是在我之前设计的宏大项目面前,还是感觉人力不太够,所以早上 10 …"}, {"url": "https://pingcap.com/blog-cn/tidb-ecosystem-tools-3/", "title": "TiDB Ecosystem Tools 原理解读系列(三)TiDB-DM 架构设计与实现原理", "content": " 简介 TiDB-DM(Data Migration)是用于将数据从 MySQL/MariaDB 迁移到 TiDB 的工具。该工具既支持以全量备份文件的方式将 MySQL/MariaDB 的数据导入到 TiDB,也支持通过解析执行 MySQL/MariaDB binlog 的方式将数据增量同步到 TiDB。特别地,对于有多个 MySQL/MariaDB 实例的分库分表需要合并后同步到同一个 TiDB 集群的场景,DM 提供了良好的支持。如果你需要从 MySQL/MariaDB 迁移到 TiDB,或者需要将 TiDB 作为 MySQL/MariaDB 的从库,DM 将是一个非常好的选择。架构设计 DM 是集群模式的,其主要由 DM-master、DM-worker 与 DM-ctl 三个组件组成,能够以多对多的方式将多个上游 MySQL 实例的数据同步到多个下游 TiDB 集群,其架构图如下: DM-master:管理整个 DM 集群,维护集群的拓扑信息,监控各个 DM-worker 实例的运行状态;进行数据同步任务的拆解与分发,监控数据同步任务的执行状态;在进行合库合表的增量数据同步时,协调各 DM-worker 上 DDL 的执行或跳过;提供数据同步任务管理的统一入口。 DM-worker:与上游 MySQL 实例一一对应,执行具体的全量、增量数据同步任务;将上游 MySQL 的 binlog 拉取到本地并持久化保存;根据定义的数据同步任务,将上游 MySQL 数据全量导出成 SQL 文件后导入到下游 TiDB,或解析本地持久化的 binlog 后增量同步到下游 TiDB;编排 DM-master 拆解后的数据同步子任务,监控子任务的运行状态。 DM-ctl:命令行交互工具,通过连接到 DM-master 后,执行 DM 集群的管理与数据同步任务的管理。 实现原理 数据迁移流程 单个 DM 集群可以同时运行多个数据同步任务;对于每一个同步任务,可以拆解为多个子任务同时由多个 DM-worker 节点承担,其中每个 DM-worker 节点负责同步来自对应的上游 MySQL 实例的数据。对于单个 DM-worker 节点上的单个数据同步子任务,其数据迁移流程如下,其中上部的数据流向为全量数据迁移、下部的数据流向为增量数据同步:在每个 DM-worker 节点内部,对于特定的数据同步子任务,主要由 dumper、loader、relay 与 syncer(binlog replication)等数据同步处理单元执行具体的数据同步操作。 对于全量数据迁移,DM 首先使用 dumper 单元从上游 MySQL 中将表结构与数据导出成 SQL 文件;然后使用 loader 单元读取这些 SQL 文件并同步到下游 TiDB。 对于增量数据同步,首先使用 relay 单元作为 slave 连接到上游 MySQL 并拉取 binlog 数据后作为 relay log 持久化存储在本地,然后使用 syncer 单元读取这些 relay log 并解析构造成 SQL 语句后同步到下游 TiDB。这个增量同步的过程与 MySQL 的主从复制类似,主要区别在于在 DM 中,本地持久化的 relay log 可以同时供多个不同子任务的 syncer 单元所共用,避免了多个任务需要重复从上游 MySQL 拉取 binlog 的问题。 数据迁移并发模型 为加快数据导入速度,在 DM 中不论是全量数据迁移,还是增量数据同步,都在其中部分阶段使用了并发处理。对于全量数据迁移,在导出阶段,dumper 单元调用 mydumper 导出工具执行实际的数据导出操作,对应的并发模型可以直接参考 mydumper 的源码。在使用 loader 单元执行的导入阶段,对应的并发模型结构如下:使用 mydumper 执行导出时,可以通过 --chunk-filesize 等参数将单个表拆分成多个 SQL 文件,这些 SQL 文件对应的都是上游 MySQL 某一个时刻的静态快照数据,且各 SQL 文件间的数据不存在关联。因此,在使用 loader 单元执行导入时,可以直接在一个 loader 单元内启动多个 worker 工作协程,由各 worker 协程并发、独立地每次读取一个待导入的 SQL 文件进行导入。即 loader 导入阶段,是以 SQL 文件级别粒度并发进行的。在 DM 的任务配置中,对于 loader 单元,其中的 pool-size 参数即用于控制此处 worker 协程数量。对于增量数据同步,在从上游拉取 binlog 并持久化到本地的阶段,由于上游 MySQL 上 binlog 的产生与发送是以 stream 形式进行的,因此这部分只能串行处理。在使用 syncer 单元执行的导入阶段,在一定的限制条件下,可以执行并发导入,对应的模型结构如下:当 syncer 读取与解析本地 relay log 时,与从上游拉取 binlog 类似,是以 stream 形式进行的,因此也只能串行处理。当 syncer 解析出各 binlog event 并构造成待同步的 job 后,则可以根据对应行数据的主键、索引等信息经过 hash 计算后分发到多个不同的待同步 job channel 中;在 channel 的另一端,与各个 channel 对应的 worker 协程并发地从 channel 中取出 job 后同步到下游的 TiDB。即 syncer 导入阶段,是以 binlog event 级别粒度并发进行的。在 DM 的任务配置中,对于 syncer 单元,其中的 worker-count 参数即用于控制此处 worker 协程数量。但 syncer 并发同步到下游 TiDB 时,存在一些限制,主要包括: 对于 DDL,由于会变更下游的表结构,因此必须确保在旧表结构对应的 DML 都同步完成后,才能进行同步。在 DM 中,当解析 binlog event 得到 DDL 后,会向每一个 job channel 发送一个特殊的 flush job;当各 worker 协程遇到 flush job 时,会立刻向下游 TiDB 同步之前已经取出的所有 job;等各 job channel 中的 job 都同步到下游 TiDB 后,开始同步 DDL;等待 DDL 同步完成后,继续同步后续的 DML。即 DDL 不能与 DML 并发同步,且 DDL 之前与之后的 DML 也不能并发同步。sharding 场景下 DDL 的同步处理见后文。 对于 DML,多条 DML 可能会修改同一行的数据,甚至是主键。如果并发地同步这些 DML,则可能造成同步后数据的不一致。DM 中对于 DML 之间的冲突检测与处理,与 TiDB-Binlog 中的处理类似,具体原理可以阅读《TiDB EcoSystem Tools 原理解读(一)TiDB-Binlog 架构演进与实现原理》中关于 Drainer 内 SQL 之间冲突检测的讨论。 合库合表数据同步 在使用 MySQL 支撑大量数据时,经常会选择使用分库分表的方案。但当将数据同步到 TiDB 后,通常希望逻辑上进行合库合表。DM 为支持合库合表的数据同步,主要实现了以下的一些功能。table router 为说明 DM 中 table router(表名路由)功能,先看如下图所示的一个例子:在这个例子中,上游有 2 个 MySQL 实例,每个实例有 2 个逻辑库,每个库有 2 个表,总共 8 个表。当同步到下游 TiDB 后,希望所有的这 8 个表最终都合并同步到同一个表中。但为了能将 8 个来自不同实例、不同库且有不同名的表同步到同一个表中,首先要处理的,就是要能根据某些定义好的规则,将来自不同表的数据都路由到下游的同一个表中。在 DM 中,这类规则叫做 router-rules。对于上面的示例,其规则如下:name-of-router-rule: schema-pattern: "schema_*" table-pattern: "table_*" target-schema: "schema" target-table: "table" name-of-router-rule:规则名,用户指定。当有多个上游实例需要使用相同的规则时,可以只定义一条规则,多个不同的实例通过规则名进行引用。 schema-pattern:用于匹配上游库(schema)名的模式,支持在尾部使用通配符(*)。这里使用 schema_* 即可匹配到示例中的两个库名。 table-pattern:用于匹配上游表名的模式,与 schema-pattern 类似。这里使用 table_* 即可匹配到示例中的两个表名。 target-schema:目标库名。对于库名、表名匹配的数据,将被路由到这个库中。 target-table:目标表名。对于库名、表名匹配的数据,将被路由到 target-schema 库下的这个表中。 在 DM 内部实现上,首先根据 schema-pattern / table-pattern 构造对应的 trie 结构,并将规则存储在 trie 节点中;当有 SQL 需要同步到下游时,通过使用上游库名、表名查询 trie 即可得到对应的规则,并根据规则替换原 SQL 中的库名、表名;通过向下游 TiDB 执行替换后的 SQL 即完成了根据表名的路由同步。有关 router-rules 规则的具体实现,可以阅读 TiDB-Tools 下的 table-router pkg 源代码。column mapping 有了 table router 功能,已经可以完成基本的合库合表数据同步了。但在数据库中,我们经常会使用自增类型的列作为主键。如果多个上游分表的主键各自独立地自增,将它们合并同步到下游后,就很可能会出现主键冲突,造成数据的不一致。我们可看一个如下的例子:在这个例子中,上游 4 个需要合并同步到下游的表中,都存在 id 列值为 1 的记录。假设这个 id 列是表的主键。在同步到下游的过程中,由于相关更新操作是以 id 列作为条件来确定需要更新的记录,因此会造成后同步的数据覆盖前面已经同步过的数据,导致部分数据的丢失。在 DM 中,我们通过 column mapping 功能在数据同步的过程中依据指定规则对相关列的数据进行转换改写来避免数据冲突与丢失。对于上面的示例,其中 MySQL 实例 1 的 column mapping 规则如下:mapping-rule-of-instance-1: schema-pattern: "schema_*" table-pattern: "table_*" expression: "partition id" source-column: "id" target-column: "id" arguments: ["1", "schema_", "table_"] mapping-rule-of-instance-1:规则名,用户指定。由于不同的上游 MySQL 实例需要转换得到不同的值,因此通常每个 MySQL 实例使用一条专有的规则。 schema-pattern / table-pattern:上游库名、表名匹配模式,与 router-rules 中的对应配置项一致。 expression:进行数据转换的表达式名。目前常用的表达式即为 "partition id",有关该表达式的具体说明见下文。 source-column:转换表达式的输入数据对应的来源列名,"id" 表示这个表达式将作用于表中名为 id 的列。暂时只支持对单个来源列进行数据转换。 target-column:转换表达式的输出数据对应的目标列名,与 source-column 类似。暂时只支持对单个目标列进行数据转换,且对应的目标列必须已经存在。 arguments:转换表达式所依赖的参数。参数个数与含义依具体表达式而定。 partition id 是目前主要受支持的转换表达式,其通过为 bigint 类型的值增加二进制前缀来解决来自不同表的数据合并同步后可能产生冲突的问题。partition id 的 arguments 包括 3 个参数,分别为: MySQL 实例 ID:标识数据的来源 MySQL 实例,用户自由指定。如 "1" 表示匹配该规则的数据来自于 MySQL 实例 1,且这个标识将被转换成数值后以二进制的形式作为前缀的一部分添加到转换后的值中。 库名前缀:标识数据的来源逻辑库。如 "schema_" 应用于 schema_2 逻辑库时,表示去除前缀后剩下的部分(数字 2)将以二进制的形式作为前缀的一部分添加到转换后的值中。 表名前缀:标识数据的来源表。如 "table_" 应用于 table_3 表时,表示去除前缀后剩下的部分(数字 3)将以二进制的形式作为前缀的一部分添加到转换后的值中。 各部分在经过转换后的数值中的二进制分布如下图所示(各部分默认所占用的 bits 位数如图所示):假如转换前的原始数据为 123,且有如上的 arguments 参数设置,则转换后的值为:1<<(64-1-4) | 2<<(64-1-4-7) | 3<<(64-1-4-7-8) | 123 另外,arguments 中的 3 个参数均可设置为空字符串(""),即表示该部分不被添加到转换后的值中,且不占用额外的 bits。比如将其设置为["1", "", "table_"],则转换后的值为:1 << (64-1-4) | 3<< (64-1-4-8) | 123 有关 column mapping 功能的具体实现,可以阅读 TiDB-Tools 下的 column-mapping pkg 源代码。sharding DDL 有了 table router 和 column mapping 功能,DML 的合库合表数据同步已经可以正常进行了。但如果在增量数据同步的过程中,上游待合并的分表上执行了 DDL 操作,则可能出现问题。我们先来看一个简化后的在分表上执行 DDL 的例子。在上图的例子中,分表的合库合表简化成了上游只有两个 MySQL 实例,每个实例内只有一个表。假设在开始数据同步时,将两个分表的表结构 schema 的版本记为 schema V1,将 DDL 执行完成后的表结构 schema 的版本记为 schema V2。现在,假设数据同步过程中,从两个上游分表收到的 binlog 数据有如下的时序: 开始同步时,从两个分表收到的都是 schema V1 的 DML。 在 t1 时刻,收到实例 1 上分表的 DDL。 从 t2 时刻开始,从实例 1 收到的是 schema V2 的 DML;但从实例 2 收到的仍是 schema V1 的 DML。 在 t3 时刻,收到实例 2 上分表的 DDL。 从 t4 时刻开始,从实例 2 收到的也是 schema V2 的 DML。 假设在数据同步过程中,不对分表的 DDL 进行处理。当将实例 1 的 DDL 同步到下游后,下游的表结构会变更成为 schema V2。但对于实例 2,在 t2 时刻到 t3 时刻这段时间内收到的仍然是 schema V1 的 DML。当尝试把这些与 schema V1 对应的 DML 同步到下游时,就会由于 DML 与表结构的不一致而发生错误,造成数据无法正确同步。继续使用上面的例子,来看看我们在 DM 中是如何处理合库合表过程中的 DDL 同步的。在这个例子中,DM-worker-1 用于同步来自 MySQL 实例 1 的数据,DM-worker-2 用于同步来自 MySQL 实例 2 的数据,DM-master 用于协调多个 DM-worker 间的 DDL 同步。从 DM-worker-1 收到 DDL 开始,简化后的 DDL 同步流程为: DM-worker-1 在 t1 时刻收到来自 MySQL 实例 1 的 DDL,自身暂停该 DDL 对应任务的 DDL 及 DML 数据同步,并将 DDL 相关信息发送给 DM-master。 DM-master 根据 DDL 信息判断需要协调该 DDL 的同步,为该 DDL 创建一个锁,并将 DDL 锁信息发回给 DM-worker-1,同时将 DM-worker-1 标记为这个锁的 owner。 DM-worker-2 继续进行 DML 的同步,直到在 t3 时刻收到来自 MySQL 实例 2 的 DDL,自身暂停该 DDL 对应任务的数据同步,并将 DDL 相关信息发送给 DM-master。 DM-master 根据 DDL 信息判断该 DDL 对应的锁信息已经存在,直接将对应锁信息发回给 DM-worker-2。 DM-master 根据启动任务时的配置信息、上游 MySQL 实例分表信息、部署拓扑信息等,判断得知已经收到了需要合表的所有上游分表的该 DDL,请求 DDL 锁的 owner(DM-worker-1)向下游同步执行该 DDL。 DM-worker-1 根据 step 2 时收到的 DDL 锁信息验证 DDL 执行请求;向下游执行 DDL,并将执行结果反馈给 DM-master;若执行 DDL 成功,则自身开始继续同步后续的(从 t2 时刻对应的 binlog 开始的)DML。 DM-master 收到来自 owner 执行 DDL 成功的响应,请求在等待该 DDL 锁的所有其他 DM-worker(DM-worker-2)忽略该 DDL,直接继续同步后续的(从 t4 时刻对应的 binlog 开始的)DML。 根据上面 DM 处理多个 DM-worker 间的 DDL 同步的流程,归纳一下 DM 内处理多个 DM-worker 间 sharding DDL 同步的特点: 根据任务配置与 DM 集群部署拓扑信息,在 DM-master 内建立一个需要协调 DDL 同步的逻辑 sharding group,group 中的成员为处理该任务拆解后各子任务的 DM-worker。 各 DM-worker 在从 binlog event 中获取到 DDL 后,会将 DDL 信息发送给 DM-master。 DM-master 根据来自 DM-worker 的 DDL 信息及 sharding group 信息创建/更新 DDL 锁。 如果 sharding group 的所有成员都收到了某一条 DDL,则表明上游分表在该 DDL 执行前的 DML 都已经同步完成,可以执行 DDL,并继续后续的 DML 同步。 上游分表的 DDL …"}, {"url": "https://pingcap.com/meetup/meetup-85-20181226/", "title": "【Infra Meetup No.85】宝尊 + Apache Spark + TiDB SQL Layer", "content": " 在上周六举办的 Infra Meetup 上海站上,来自宝尊电商和阿里云的资深工程师们,与我司核心开发工程师一起探讨了新一代开源分布式数据库 TiDB 和 Apache Spark 在 SQL 层面的执行原理、优化方案,以及电商数据的技术解决方案等话题。以下是视频&文字回顾,enjoy~宝尊的 Cloud Native Migration Path 与 TiDB 的应用展望 视频 | Infra Meetup No.85:宝尊的 Cloud Native Migration Path 与 TiDB 的应用展望 PPT 链接 宝尊是知名品牌电子商务商业伙伴和技术研发解决方案公司,拥有端到端数字化零售及供应链管理系统集成解决方案的自主知识产权。宝尊技术与创新中心为了达成“科技驱动商业未来”的宝尊战略愿景,启动全面的云转型战略以满足宝尊科技能力输出的战略目标。本次 Meetup 上,邵千里老师分享了宝尊在云原生转型中的技术路线选择和技术挑战,同时介绍 TiDB 作为新一代的数据库,在宝尊的科技转型的过程中的使用现状和效果以及宝尊对这一新技术在未来的平台版图中的位置的展望。Apache Spark 优化机会与探索 视频 | Infra Meetup No.85:Apache Spark 优化机会与探索 PPT 链接 本次分享,李呈祥老师主要介绍了阿里巴巴 EMR 团队在 Spark SQL 方向上的一些优化工作,通过在 Catalyst 和 shuffle 等模块的优化,大幅提高了用户数据处理的性能,最后也介绍了 EMR 在 Spark 优化方向上的一些认识和思考。首先介绍了 Catalyst 优化。 Join Order 优化,在多表 jion(>12) 的场景下,引入遗传算法,大大降低寻找最优执行计划的代价。 Runtime Filter,join 时,减少大表加载数据量,提升查询性能。 接着介绍了 Shuffle 优化:实现异步非阻塞的 push-style shuffle 模式,提升 shuffle 性能。最后李呈祥老师分享了 EMR 在云原生,使用场景和 SQL 技术三个方向上对 spark 优化的理解和优化方向。An Introduction to TiDB SQL Layer 视频 | Infra Meetup No.85:An Introduction to TiDB SQL Layer PPT 链接 张建老师首先了介绍了 TiDB SQL 层优化器跟执行引擎的一些核心原理,并以外链接消除和 Join Reorder 为例向大家讲解 SQL 转化为 AST 后的逻辑优化是如何进行等价变换得到一个逻辑上可执行的逻辑执行计划的。然后为大家介绍了物理算子的物理属性的概念,并以此向大家讲述了 TiDB 是如何利用算子的物理属性对逻辑执行计划进行动态规划求解到最终可执行的物理执行计划的。这个物理执行计划之后交给了执行引擎完成数据的读取和计算。为了更高效的完成 SQL 的执行,TiDB 执行引擎从经典 Volcano 模型演进到了向量化的计算模型,并在此基础上进一步对算子内进行多线程优化。最后为大家同步了一些 TiDB SQL 层正在开发的新功能以及接下来要做的一些事情。"}, {"url": "https://pingcap.com/blog/pingcap-2018-year-in-review/", "title": "PingCAP 2018 Year in Review", "content": ""}, {"url": "https://pingcap.com/weekly/2018-12-24-tidb-weekly/", "title": "Weekly update (December 17 ~ December 23, 2018)", "content": " Weekly update in TiDB Last week, we landed 48 PRs in the TiDB repository.Added Add the CFB mode for the AES encryption Implement the basic CreateView feature Add the HTTP API to query hot tables/indexes Improved Make Admin Check Table only check the public index Handle non-BMP characters in the UTF-8 charset Specify the sort order for the columns in the physical property Refactor error handling of transactions Close all connections directly when terminating TiDB Ignore the unknown hint and return a warning Support specifying the ending of a range to be scanned Add the extra information message to be sent to the MySQL client Fixed Fix the wrong output of Show Master Status Fix the incorrect result related to the Duration columns Fix the compatibility problem of renaming tables Correct the column name with the unary plus sign Handle corner cases for auto_increment columns Weekly update in TiKV and PD Last week, we landed 29 PRs in the TiKV and PD repositories.Added Add lpad and lpad_binary built-in functions Add add_datetime_and_duration, add_datetime_and_string and add_time_datetime_null built-in functions Add an HTTP port for Prometheus Introduce the Raftstore router Improved Check overlapped Regions for the new Region Remove the disturbing log when the store is tombstone Fixed Fix the possible panic issue caused by Approximate Size Split Fix a bug about Region merge in the case of multiple snapshots New contributors (Thanks!) tidb-operator: queenliuxx tidb: cuining kjzz parser: TennyZhuang caohe exialin hanchuanchuan honestold3 kangkaisen knarfeh tony612 "}, {"url": "https://pingcap.com/blog-cn/tidb-source-code-reading-22/", "title": "TiDB 源码阅读系列文章(二十二)Hash Aggregation", "content": " 聚合算法执行原理 在 SQL 中,聚合操作对一组值执行计算,并返回单个值。TiDB 实现了 2 种聚合算法:Hash Aggregation 和 Stream Aggregation。我们首先以 AVG 函数为例(案例参考 Stack Overflow),简述这两种算法的执行原理。假设表 t 如下: 列 a 列 b 1 9 1 -8 2 -7 2 6 1 5 2 4 SQL: select avg(b) from t group by a, 要求将表 t 的数据按照 a 的值分组,对每一组的 b 值计算平均值。不管 Hash 还是 Stream 聚合,在 AVG 函数的计算过程中,我们都需要维护 2 个中间结果变量 sum 和 count。Hash 和 Stream 聚合算法的执行原理如下。Hash Aggregate 的执行原理 在 Hash Aggregate 的计算过程中,我们需要维护一个 Hash 表,Hash 表的键为聚合计算的 Group-By 列,值为聚合函数的中间结果 sum 和 count。在本例中,键为 列 a 的值,值为 sum(b) 和 count(b)。计算过程中,只需要根据每行输入数据计算出键,在 Hash 表中找到对应值进行更新即可。对本例的执行过程模拟如下。 输入数据 a b Hash 表 [key] (sum, count) 1 9 [1] (9, 1) 1 -8 [1] (1, 2) 2 -7 [1] (1, 2) [2] (-7, 1) 2 6 [1] (1, 2) [2] (-1, 2) 1 5 [1] (6, 3) [2] (-1, 2) 2 4 [1] (6, 3) [2] (3, 3) 输入数据输入完后,扫描 Hash 表并计算,便可以得到最终结果: Hash 表 avg(b) [1] (6, 3) 2 [2] (3, 3) 1 Stream Aggregation 的执行原理 Stream Aggregate 的计算需要保证输入数据按照 Group-By 列有序。在计算过程中,每当读到一个新的 Group 的值或所有数据输入完成时,便对前一个 Group 的聚合最终结果进行计算。对于本例,我们首先对输入数据按照 a 列进行排序。排序后,本例执行过程模拟如下。 输入数据 是否为新 Group 或所有数据输入完成 (sum, count) avg(b) 1 9 是 (9, 1) 前一个 Group 为空,不进行计算 1 -8 否 (1, 2) 1 5 否 (6, 3) 2 -7 是 (-7, 1) 2 2 6 否 (-1, 2) 2 4 否 (3, 3) 是 1 因为 Stream Aggregate 的输入数据需要保证同一个 Group 的数据连续输入,所以 Stream Aggregate 处理完一个 Group 的数据后可以立刻向上返回结果,不用像 Hash Aggregate 一样需要处理完所有数据后才能正确的对外返回结果。当上层算子只需要计算部分结果时,比如 Limit,当获取到需要的行数后,可以提前中断 Stream Aggregate 后续的无用计算。当 Group-By 列上存在索引时,由索引读入数据可以保证输入数据按照 Group-By 列有序,此时同一个 Group 的数据连续输入 Stream Aggregate 算子,可以避免额外的排序操作。TiDB 聚合函数的计算模式 由于分布式计算的需要,TiDB 对于聚合函数的计算阶段进行划分,相应定义了 5 种计算模式:CompleteMode,FinalMode,Partial1Mode,Partial2Mode,DedupMode。不同的计算模式下,所处理的输入值和输出值会有所差异,如下表所示: AggFunctionMode 输入值 输出值 CompleteMode 原始数据 最终结果 FinalMode 中间结果 最终结果 Partial1Mode 原始数据 中间结果 Partial2Mode 中间结果 进一步聚合的中间结果 DedupMode 原始数据 去重后的原始数据 以上文提到的 select avg(b) from t group by a 为例,通过对计算阶段进行划分,可以有多种不同的计算模式的组合,如: CompleteMode此时 AVG 函数的整个计算过程只有一个阶段,如图所示: Partial1Mode –> FinalMode此时我们将 AVG 函数的计算过程拆成两个阶段进行,如图所示: 除了上面的两个例子外,还可能有如下的几种计算方式: 聚合被下推到 TiKV 上进行计算(Partial1Mode),并返回经过预聚合的中间结果。为了充分利用 TiDB server 所在机器的 CPU 和内存资源,加快 TiDB 层的聚合计算,TiDB 层的聚合函数计算可以这样进行:Partial2Mode –> FinalMode。 当聚合函数需要对参数进行去重,也就是包含 DISTINCT 属性,且聚合算子因为一些原因不能下推到 TiKV 时,TiDB 层的聚合函数计算可以这样进行:DedupMode –> Partial1Mode –> FinalMode。 聚合函数分为几个阶段执行, 每个阶段对应的模式是什么,是否要下推到 TiKV,使用 Hash 还是 Stream 聚合算子等都由优化器根据数据分布、估算的计算代价等来决定。TiDB 并行 Hash Aggregation 的实现 如何构建 Hash Aggregation 执行器 构建逻辑执行计划 时,会调用 NewAggFuncDesc 将聚合函数的元信息封装为一个 AggFuncDesc。 其中 AggFuncDesc.RetTp 由 AggFuncDesc.typeInfer 根据聚合函数类型及参数类型推导而来;AggFuncDesc.Mode 统一初始化为 CompleteMode。 构建物理执行计划时,PhysicalHashAgg 和 PhysicalStreamAgg 的 attach2Task 方法会根据当前 task 的类型尝试进行下推聚合计算,如果 task 类型满足下推的基本要求,比如 copTask,接着会调用 newPartialAggregate 尝试将聚合算子拆成 TiKV 上执行的 Partial 算子和 TiDB 上执行的 Final 算子,其中 AggFuncToPBExpr 函数用来判断某个聚合函数是否可以下推。若聚合函数可以下推,则会在 TiKV 中进行预聚合并返回中间结果,因此需要将 TiDB 层执行的 Final 聚合算子的 AggFuncDesc.Mode 修改为 FinalMode,并将其 AggFuncDesc.Args 修改为 TiKV 预聚合后返回的中间结果,TiKV 层的 Partial 聚合算子的 AggFuncDesc 也需要作出对应的修改,这里不再详述。若聚合函数不可以下推,则 AggFuncDesc.Mode 保持不变。 构建 HashAgg 执行器时,首先检查当前 HashAgg 算子是否可以并行执行。目前当且仅当两种情况下 HashAgg 不可以并行执行: 存在某个聚合函数参数为 DISTINCT 时。TiDB 暂未实现对 DedupMode 的支持,因此对于含有 DISTINCT 的情况目前仅能单线程执行。 系统变量 tidb_hashagg_partial_concurrency 和 tidb_hashagg_final_concurrency 被同时设置为 1 时。这两个系统变量分别用来控制 Hash Aggregation 并行计算时候,TiDB 层聚合计算 partial 和 final 阶段 worker 的并发数。当它们都被设置为 1 时,选择单线程执行。 若 HashAgg 算子可以并行执行,使用 AggFuncDesc.Split 根据 AggFuncDesc.Mode 将 TiDB 层的聚合算子的计算拆分为 partial 和 final 两个阶段,并分别生成对应的 AggFuncDesc,设为 partialAggDesc 和 finalAggDesc。若 AggFuncDesc.Mode == CompleteMode,则将 TiDB 层的计算阶段拆分为 Partial1Mode --> FinalMode;若 AggFuncDesc.Mode == FinalMode,则将 TiDB 层的计算阶段拆分为 Partial2Mode --> FinalMode。进一步的,我们可以根据 partialAggDesc 和 finalAggDesc 分别 构造出对应的执行函数。并行 Hash Aggregation 执行过程详述 TiDB 的并行 Hash Aggregation 算子执行过程中的主要线程有:Main Thead,Data Fetcher,Partial Worker,和 Final Worker: Main Thread 一个: 启动 Input Reader,Partial Workers 及 Final Workers 等待 Final Worker 的执行结果并返回 Data Fetcher 一个: 按 batch 读取子节点数据并分发给 Partial Worker Partial Worker 多个: 读取 Data Fetcher 发送来的数据,并做预聚合 将预聚合结果根据 Group 值 shuffle 给对应的 Final Worker Final Worker 多个: 读取 PartialWorker 发送来的数据,计算最终结果,发送给 Main Thread Hash Aggregation 的执行阶段可分为如下图所示的 5 步: 启动 Data Fetcher,Partial Workers 及 Final Workers。这部分工作由 prepare4Parallel 函数完成。该函数会启动一个 Data Fetcher,多个 Partial Worker 以及 多个 Final Worker。Partial Worker 和 Final Worker 的数量可以分别通过 tidb_hashgg_partial_concurrency 和 tidb_hashagg_final_concurrency 系统变量进行控制,这两个系统变量的默认值都为 4。 DataFetcher 读取子节点的数据并分发给 Partial Workers。这部分工作由 fetchChildData 函数完成。 Partial Workers 预聚合计算,及根据 Group Key shuffle 给对应的 Final Workers。这部分工作由 HashAggPartialWorker.run 函数完成。该函数调用 updatePartialResult 函数对 DataFetcher 发来数据执行 预聚合计算,并将预聚合结果存储到 partialResultMap 中。其中 partialResultMap 的 key 为根据 Group-By 的值 encode 的结果,value 为 PartialResult 类型的数组,数组中的每个元素表示该下标处的聚合函数在对应 Group 中的预聚合结果。shuffleIntermData 函数完成根据 Group 值 shuffle 给对应的 Final Worker。 Final Worker 计算最终结果,发送给 Main Thread。这部分工作由 HashAggFinalWorker.run 函数完成。该函数调用 consumeIntermData 函数 接收 PartialWorkers 发送来的预聚合结果,进而 合并 得到最终结果。getFinalResult 函数完成发送最终结果给 Main Thread。 Main Thread 接收最终结果并返回。 TiDB 并行 Hash Aggregation 的性能提升 此处以 TPC-H query-17 为例,测试并行 Hash Aggregation 相较于单线程计算时的性能提升。引入并行 Hash Aggregation 前,它的计算瓶颈在 HashAgg_35。该查询执行计划如下:在 TiDB 中,使用 EXPLAIN ANALYZE 可以获取 SQL 的执行统计信息。因篇幅原因此处仅贴出 TPC-H query-17 部分算子的 EXPLAIN ANALYZE 结果。HashAgg 单线程计算时:查询总执行时间 23 分 24 秒,其中 HashAgg 执行时间约 17 分 9 秒。HashAgg 并行计算时(此时 TiDB 层 Partial 和 Final 阶段的 worker 数量都设置为 16):总查询时间 8 分 37 秒,其中 HashAgg 执行时间约 1 分 4 秒。并行计算时,Hash Aggregation 的计算速度提升约 16 倍。"}, {"url": "https://pingcap.com/meetup/meetup-83-20181220/", "title": "【Infra Meetup No.83】What's New in TiDB 2.1 and What's Next", "content": "在 Infra Meetup 第 83 期上,我司 TiDB 核心开发工程师、分布式数据库专家姚维老师为广州的朋友们介绍 TiDB 2.1 的重要特性和未来的规划,以下是视频&文字回顾,enjoy~ 视频 | Infra Meetup No.83:What’s New in TiDB 2.1 and What’s Next PPT 链接 姚维老师主要介绍了 TiDB 2.1 版本的重要 Feature,包括这些 Feature 所解决的问题、背后的原理、达到的效果,特别是 TiDB 在优化器、计算引擎、存储引擎方面的改进,使得 2.1 版本成为更智能、更迅速、更稳定的数据库。接着展示了部分 Benchmark 结果,分别从 OLAP、OLTP 两个场景表明 TiDB 的性能提升。最后介绍了下一步工作的展望,让大家了解 TiDB 的演进方向。"}, {"url": "https://pingcap.com/meetup/meetup-84-20181220/", "title": "【Infra Meetup No.84】如何在三分钟内跑完千万测试 case & 硬核 Paper Reading", "content": " 谈谈 TiDB 背后的效率工程:如何在三分钟内跑完千万测试 case 视频 | Infra Meetup No.84:如何在三分钟内跑完千万测试 case PPT 链接 殷成文老师首先介绍了我们在保证 TiDB 正确性以及稳定性上做的一些事情,以及目前遇到的效率的问题, 然后逐个分析目前 CI 慢的原因以及分享我们是如何去解决这些问题的,并介绍了在优化过程中遇到一些坑。 流程上我们结合已有的基础架构选择了 Jenkins with Kubernetes 的方式来解决之前出现的资源调度以及并发上的问题。殷成文老师分享了在使用的过程中遇到的一些坑,以及我们在网络结构上做的调整,提高与 GitHub 交互的速度和稳定性。此外,我们为了减少网络 io 做的两层 cache,减少重复的下载上传操作。最后介绍了我们如何去优化具体 case 以及在优化 TiDB unit test 上用了哪些黑魔法。Paper Reading 视频 | Infra Meetup No.84:硬核 Paper Reading 论文《Robust Query Optimization Methods With Respect to Estimation Errors: A Survey》 PPT 链接 此次分享谢海滨老师首先介绍了查询优化的相关背景知识,并简单的分析了估算查询代价不准的几个原因。接下来,该分享介绍了三种在查询误差存在情况下的优化方法: 在执行结束后,我们可以根据查询时得到的真实信息去更新统计信息,以减少之后查询的估算误差。 在执行过程中,可以物化中间结果,并根据真实的信息重新优化执行计划;或者对于某些查询计划,可以直接调整而不会丢失中间结果。 在执行开始前,通过考虑估算结果的概率分布,或者执行计划的对估算误差的敏感度,选择更健壮的执行计划。 "}, {"url": "https://pingcap.com/blog-cn/support-ast-restore-to-sql-text/", "title": "十分钟成为 Contributor 系列 | 支持 AST 还原为 SQL", "content": " 背景知识 SQL 语句发送到 TiDB 后首先会经过 parser,从文本 parse 成为 AST(抽象语法树),AST 节点与 SQL 文本结构是一一对应的,我们通过遍历整个 AST 树就可以拼接出一个与 AST 语义相同的 SQL 文本。对 parser 不熟悉的小伙伴们可以看 TiDB 源码阅读系列文章(五)TiDB SQL Parser 的实现。为了控制 SQL 文本的输出格式,并且为方便未来新功能的加入(例如在 SQL 文本中用 “*” 替代密码),我们引入了 RestoreFlags 并封装了 RestoreCtx 结构(相关源码):// `RestoreFlags` 中的互斥组: // [RestoreStringSingleQuotes, RestoreStringDoubleQuotes] // [RestoreKeyWordUppercase, RestoreKeyWordLowercase] // [RestoreNameUppercase, RestoreNameLowercase] // [RestoreNameDoubleQuotes, RestoreNameBackQuotes] // 靠前的 flag 拥有更高的优先级。 const ( RestoreStringSingleQuotes RestoreFlags = 1 << iota ... ) // RestoreCtx is `Restore` context to hold flags and writer. type RestoreCtx struct { Flags RestoreFlags In io.Writer } // WriteKeyWord 用于向 `ctx` 中写入关键字(例如:SELECT)。 // 它的大小写受 `RestoreKeyWordUppercase`,`RestoreKeyWordLowercase` 控制 func (ctx *RestoreCtx) WriteKeyWord(keyWord string) { ... } // WriteString 用于向 `ctx` 中写入字符串。 // 它是否被引号包裹及转义规则受 `RestoreStringSingleQuotes`,`RestoreStringDoubleQuotes`,`RestoreStringEscapeBackslash` 控制。 func (ctx *RestoreCtx) WriteString(str string) { ... } // WriteName 用于向 `ctx` 中写入名称(库名,表名,列名等)。 // 它是否被引号包裹及转义规则受 `RestoreNameUppercase`,`RestoreNameLowercase`,`RestoreNameDoubleQuotes`,`RestoreNameBackQuotes` 控制。 func (ctx *RestoreCtx) WriteName(name string) { ... } // WritePlain 用于向 `ctx` 中写入普通文本。 // 它将被直接写入不受 flag 影响。 func (ctx *RestoreCtx) WritePlain(plainText string) { ... } // WritePlainf 用于向 `ctx` 中写入普通文本。 // 它将被直接写入不受 flag 影响。 func (ctx *RestoreCtx) WritePlainf(format string, a ...interface{}) { ... } 我们在 ast.Node 接口中添加了一个 Restore(ctx *RestoreCtx) error 函数,这个函数将当前节点对应的 SQL 文本追加至参数 ctx 中,如果节点无效则返回 error。type Node interface { // Restore AST to SQL text and append them to `ctx`. // return error when the AST is invalid. Restore(ctx *RestoreCtx) error ... } 以 SQL 语句 SELECT column0 FROM table0 UNION SELECT column1 FROM table1 WHERE a = 1 为例,如下图所示,我们通过遍历整个 AST 树,递归调用每个节点的 Restore() 方法,即可拼接成一个完整的 SQL 文本。值得注意的是,SQL 文本与 AST 是一个多对一的关系,我们不可能从 AST 结构中还原出与原 SQL 完全一致的文本, 因此我们只要保证还原出的 SQL 文本与原 SQL 语义相同 即可。所谓语义相同,指的是由 AST 还原出的 SQL 文本再被解析为 AST 后,两个 AST 是相等的。我们已经完成了接口设计和测试框架,具体的Restore() 函数留空。因此只需要选择一个留空的 Restore() 函数实现,并添加相应的测试数据,就可以提交一个 PR 了!实现 Restore() 函数的整体流程 请先阅读 Proposal、Issue 在 Issue 中找到未实现的函数 在 Issue-pingcap/tidb#8532 中找到一个没有被其他贡献者认领的任务,例如 ast/expressions.go: BetweenExpr。 在 pingcap/parser 中找到任务对应文件 ast/expressions.go。 在文件中找到 BetweenExpr 结构的 Restore 函数: // Restore implements Node interface. func (n *BetweenExpr) Restore(ctx *RestoreCtx) error { return errors.New("Not implemented") } 实现 Restore() 函数根据 Node 节点结构和 SQL 语法实现函数功能。 参考 MySQL 5.7 SQL Statement Syntax 写单元测试参考示例在相关文件下添加单元测试。 运行 make test,确保所有的 test case 都能跑过。 提交 PRPR 标题统一为:parser: implement Restore for XXX请在 PR 中关联 Issue: pingcap/tidb#8532 示例 这里以实现 BetweenExpr 的 Restore 函数 PR 为例,进行详细说明: 首先看 ast/expressions.go: 我们要实现一个 ast.Node 结构的 Restore 函数,首先清楚该结构代表什么短语,例如 BetweenExpr 代表 expr [NOT] BETWEEN expr AND expr (参见:MySQL 语法 - 比较函数和运算符)。 观察 BetweenExpr 结构: // BetweenExpr is for "between and" or "not between and" expression. type BetweenExpr struct { exprNode // 被检查的表达式 Expr ExprNode // AND 左侧的表达式 Left ExprNode // AND 右侧的表达式 Right ExprNode // 是否有 NOT 关键字 Not bool } 实现 BetweenExpr 的 Restore 函数: // Restore implements Node interface. func (n *BetweenExpr) Restore(ctx *RestoreCtx) error { // 调用 Expr 的 Restore,向 ctx 写入 Expr if err := n.Expr.Restore(ctx); err != nil { return errors.Annotate(err, "An error occurred while restore BetweenExpr.Expr") } // 判断是否有 NOT,并写入相应关键字 if n.Not { ctx.WriteKeyWord(" NOT BETWEEN ") } else { ctx.WriteKeyWord(" BETWEEN ") } // 调用 Left 的 Restore if err := n.Left.Restore(ctx); err != nil { return errors.Annotate(err, "An error occurred while restore BetweenExpr.Left") } // 写入 AND 关键字 ctx.WriteKeyWord(" AND ") // 调用 Right 的 Restore if err := n.Right.Restore(ctx); err != nil { return errors.Annotate(err, "An error occurred while restore BetweenExpr.Right ") } return nil } 接下来给函数实现添加单元测试, ast/expressions_test.go:// 添加测试函数 func (tc *testExpressionsSuite) TestBetweenExprRestore(c *C) { // 测试用例 testCases := []NodeRestoreTestCase{ {"b between 1 and 2", "`b` BETWEEN 1 AND 2"}, {"b not between 1 and 2", "`b` NOT BETWEEN 1 AND 2"}, {"b between a and b", "`b` BETWEEN `a` AND `b`"}, {"b between '' and 'b'", "`b` BETWEEN '' AND 'b'"}, {"b between '2018-11-01' and '2018-11-02'", "`b` BETWEEN '2018-11-01' AND '2018-11-02'"}, } // 为了不依赖父节点实现,通过 extractNodeFunc 抽取待测节点 extractNodeFunc := func(node Node) Node { return node.(*SelectStmt).Fields.Fields[0].Expr } // Run Test RunNodeRestoreTest(c, testCases, "select %s", extractNodeFunc) } 至此 BetweenExpr 的 Restore 函数实现完成,可以提交 PR 了。为了更好的理解测试逻辑,下面我们看 RunNodeRestoreTest:// 下面是测试逻辑,已经实现好了,不需要 contributor 实现 func RunNodeRestoreTest(c *C, nodeTestCases []NodeRestoreTestCase, template string, extractNodeFunc func(node Node) Node) { parser := parser.New() for _, testCase := range nodeTestCases { // 通过 template 将测试用例拼接为完整的 SQL sourceSQL := fmt.Sprintf(template, testCase.sourceSQL) expectSQL := fmt.Sprintf(template, testCase.expectSQL) stmt, err := parser.ParseOneStmt(sourceSQL, "", "") comment := Commentf("source %#v", testCase) c.Assert(err, IsNil, comment) var sb strings.Builder // 抽取指定节点并调用其 Restore 函数 err = extractNodeFunc(stmt).Restore(NewRestoreCtx(DefaultRestoreFlags, &sb)) c.Assert(err, IsNil, comment) // 通过 template 将 restore 结果拼接为完整的 SQL restoreSql := fmt.Sprintf(template, sb.String()) comment = Commentf("source %#v; restore %v", testCase, restoreSql) // 测试 restore 结果与预期一致 c.Assert(restoreSql, Equals, expectSQL, comment) stmt2, err := parser.ParseOneStmt(restoreSql, "", "") c.Assert(err, IsNil, comment) CleanNodeText(stmt) CleanNodeText(stmt2) // 测试解析的 stmt 与原 stmt 一致 c.Assert(stmt2, DeepEquals, stmt, comment) } } 不过对于 ast.StmtNode(例如:ast.SelectStmt)测试方法有些不一样, 由于这类节点可以还原为一个完整的 SQL,因此直接在 parser_test.go 中测试。下面以实现 UseStmt 的 Restore 函数 PR 为例,对测试进行说明: Restore 函数实现过程略。 给函数实现添加单元测试,参见 parser_test.go:在这个示例中,只添加了几行测试数据就完成了测试:// 添加 testCase 结构的测试数据 {"use `select`", true, "USE `select`"}, {"use `sel``ect`", true, "USE `sel``ect`"}, {"use select", false, "USE `select`"}, 我们看 testCase 结构声明:type testCase struct { // 原 SQL src string // 是否能被正确 parse ok bool // 预期的 restore SQL restore string } 测试代码会判断原 SQL parse 出 AST 后再还原的 SQL 是否与预期的 restore SQL 相等,具体的测试逻辑在 parser_test.go 中 RunTest()、RunRestoreTest() 函数,逻辑与前例类似,此处不再赘述。 加入 TiDB Contributor Club,无门槛参与开源项目,改变世界从这里开始吧(萌萌哒)。"}, {"url": "https://pingcap.com/blog-cn/tidb-ecosystem-tools-2/", "title": "TiDB Ecosystem Tools 原理解读系列(二)TiDB-Lightning Toolset 介绍", "content": " 简介 TiDB-Lightning Toolset 是一套快速全量导入 SQL dump 文件到 TiDB 集群的工具集,自 2.1.0 版本起随 TiDB 发布,速度可达到传统执行 SQL 导入方式的至少 3 倍、大约每小时 100 GB,适合在上线前用作迁移现有的大型数据库到全新的 TiDB 集群。设计 TiDB 从 2017 年开始提供全量导入工具 Loader,它以多线程操作、错误重试、断点续传以及修改一些 TiDB 专属配置来提升数据导入速度。然而,当我们全新初始化一个 TiDB 集群时,Loader 这种逐条 INSERT 指令在线上执行的方式从根本上是无法尽用性能的。原因在于 SQL 层的操作有太强的保证了。在整个导入过程中,TiDB 需要: 保证 ACID 特性,需要执行完整的事务流程。 保证各个 TiKV 服务器数据量平衡及有足够的副本,在数据增长的时候需要不断的分裂、调度 Regions。 这些动作确保 TiDB 整段导入的期间是稳定的,但在导入完毕前我们根本不会对外提供服务,这些保证就变成多此一举了。此外,多线程的线上导入也代表资料是乱序插入的,新的数据范围会与旧的重叠。TiKV 要求储存的数据是有序的,大量的乱序写入会令 TiKV 要不断地移动原有的数据(这称为 Compaction),这也会拖慢写入过程。TiKV 是使用 RocksDB 以 KV 对的形式储存数据,这些数据会压缩成一个个 SST 格式文件。TiDB-Lightning Toolset使用新的思路,绕过SQL层,在线下将整个 SQL dump 转化为 KV 对、生成排好序的 SST 文件,然后直接用 Ingestion 推送到 RocksDB 里面。这样批量处理的方法略过 ACID 和线上排序等耗时步骤,让我们提升最终的速度。架构 TiDB-Lightning Toolset 包含两个组件:tidb-lightning 和 tikv-importer。Lightning 负责解析 SQL 成为 KV 对,而 Importer 负责将 KV 对排序与调度、上传到 TiKV 服务器。为什么要把一个流程拆分成两个程式呢? Importer 与 TiKV 密不可分、Lightning 与 TiDB 密不可分,Toolset 的两者皆引用后者为库,而这样 Lightning 与 Importer 之间就出现语言冲突:TiKV 是使用 Rust 而 TiDB 是使用 Go 的。把它们拆分为独立的程式更方便开发,而双方都需要的 KV 对可以透过 gRPC 传递。 分开 Importer 和 Lightning 也使横向扩展的方式更为灵活,例如可以运行多个 Lightning,传送给同一个 Importer。 以下我们会详细分析每个组件的操作原理。Lightning Lightning 现时只支持经 mydumper 导出的 SQL 备份。mydumper 将每个表的内容分别储存到不同的文件,与 mysqldump 不同。这样不用解析整个数据库就能平行处理每个表。首先,Lightning 会扫描 SQL 备份,区分出结构文件(包含 CREATE TABLE 语句)和数据文件(包含 INSERT 语句)。结构文件的内容会直接发送到 TiDB,用以建立数据库构型。然后 Lightning 就会并发处理每一张表的数据。这里我们只集中看一张表的流程。每个数据文件的内容都是规律的 INSERT 语句,像是:INSERT INTO `tbl` VALUES (1, 2, 3), (4, 5, 6), (7, 8, 9); INSERT INTO `tbl` VALUES (10, 11, 12), (13, 14, 15), (16, 17, 18); INSERT INTO `tbl` VALUES (19, 20, 21), (22, 23, 24), (25, 26, 27); Lightning 会作初步分析,找出每行在文件的位置并分配一个行号,使得没有主键的表可以唯一的区分每一行。此外亦同时将文件分割为大小差不多的区块(默认 256 MiB)。这些区块也会并发处理,让数据量大的表也能快速导入。以下的例子把文件以 20 字节为限分割成 5 块:Lightning 会直接使用 TiDB 实例来把 SQL 转换为 KV 对,称为「KV 编码器」。与外部的 TiDB 集群不同,KV 编码器是寄存在 Lightning 进程内的,而且使用内存存储,所以每执行完一个 INSERT 之后,Lightning 可以直接读取内存获取转换后的 KV 对(这些 KV 对包含数据及索引)。得到 KV 对之后便可以发送到 Importer。Importer 因异步操作的缘故,Importer 得到的原始 KV 对注定是无序的。所以,Importer 要做的第一件事就是要排序。这需要给每个表划定准备排序的储存空间,我们称之为 engine file。对大数据排序是个解决了很多遍的问题,我们在此使用现有的答案:直接使用 RocksDB。一个 engine file 就相等于本地的 RocksDB,并设置为优化大量写入操作。而「排序」就相等于将 KV 对全写入到 engine file 里,RocksDB 就会帮我们合并、排序,并得到 SST 格式的文件。这个 SST 文件包含整个表的数据和索引,比起 TiKV 的储存单位 Regions 实在太大了。所以接下来就是要切分成合适的大小(默认为 96 MiB)。Importer 会根据要导入的数据范围预先把 Region 分裂好,然后让 PD 把这些分裂出来的 Region 分散调度到不同的 TiKV 实例上。最后,Importer 将 SST 上传到对应 Region 的每个副本上。然后通过 Leader 发起 Ingest 命令,把这个 SST 文件导入到 Raft group 里,完成一个 Region 的导入过程。我们传输大量数据时,需要自动检查数据完整,避免忽略掉错误。Lightning 会在整个表的 Region 全部导入后,对比传送到 Importer 之前这个表的 Checksum,以及在 TiKV 集群里面时的 Checksum。如果两者一样,我们就有信心说这个表的数据没有问题。一个表的 Checksum 是透过计算 KV 对的哈希值(Hash)产生的。因为 KV 对分布在不同的 TiKV 实例上,这个 Checksum 函数应该具备结合性;另外,Lightning 传送 KV 对之前它们是无序的,所以 Checksum 也不应该考虑顺序,即服从交换律。也就是说 Checksum 不是简单的把整个 SST 文件计算 SHA-256 这样就了事。我们的解决办法是这样的:先计算每个 KV 对的 CRC64,然后用 XOR 结合在一起,得出一个 64 位元的校验数字。为减低 Checksum 值冲突的概率,我们目前会计算 KV 对的数量和大小。若速度允许,将来会加入更先进的 Checksum 方式。总结和下一步计划 从这篇文章大家可以看到,Lightning 因为跳过了一些复杂、耗时的步骤使得整个导入进程更快,适合大数据量的初次导入,接下来我们还会做进一步的改进。提升导入速度 现时 Lightning 会原封不动把整条 SQL 命令抛给 KV 编码器。所以即使我们省去执行分布式 SQL 的开销,但仍需要进行解析、规划及优化语句这些不必要或未被专门化的步骤。Lightning 可以调用更底层的 TiDB API,缩短 SQL 转 KV 的行程。并行导入 另一方面,尽管我们可以不断的优化程序代码,单机的性能总是有限的。要突破这个界限就需要横向扩展:增加机器来同时导入。如前面所述,只要每套 TiDB-Lightning Toolset 操作不同的表,它们就能平行导进同一个集群。可是,现在的版本只支持读取本机文件系统上的 SQL dump,设置成多机版就显得比较麻烦了(要安装一个共享的网络盘,并且手动分配哪台机读取哪张表)。我们计划让 Lightning 能从网路获取 SQL dump(例如通过 S3 API),并提供一个工具自动分割数据库,降低设置成本。在线导入 TiDB-Lightning 在导入时会把集群切换到一个专供 Lightning 写入的模式。目前来说 Lightning 主要用于在进入生产环境之前导入全量数据,所以在此期间暂停对外提供服务还可以接受。但我们希望支持更多的应用场景,例如恢复备份、储存 OLAP 的大规模计算结果等等,这些都需要维持集群在线上。所以接下来的一大方向是考虑怎样降低 Lightning 对集群的影响。"}, {"url": "https://pingcap.com/weekly/2018-12-17-tidb-weekly/", "title": "Weekly update (December 10 ~ December 16, 2018)", "content": " Weekly update in TiDB Last week, we landed 47 PRs in the TiDB repository.Added Propose the design for the SQL plan management Support the interactive_timeout system variable Add a batch commit session variable for large transactions Improved Print the error log if the transaction state is unexpected when calling Commit Raise an error or warning for unsupported isolation levels Support ALTER TABLE TRUNCATE PARTITION Support altering the character set to utf8 or utf8mb4 Fixed Make the join reorder solver stateless Fix the unexpected data truncation error when the plan cache is enabled Do not build dual for AntiSemiJoin when the condition is constant false Fix the wrong result when the client uses comStmtSendLongData Fix the optimizer failure for generating a plan when the TIDB_SMJ hint is specified Fix the wrong result caused by the abs function pushdown Fix the panic when adding an index of the generated column Preserve the precision information of timestamp columns in the plan cache Fix data race caused by the flag of JSON columns Move some session variables to the statement context for the correct retry Weekly update in TiSpark Last week, we landed 2 PRs in the TiSpark repository.Added Support the desc table command Fixed Fix the issue that ScanIterator fails to read from adjacent empty Regions Weekly update in TiKV and PD Last week, we landed 36 PRs in the TiKV and PD repositories.Added Add rpad and rpad_binary built-in functions Add year_week_with_mode and year_week_without_mode built-in functions Improved Change the default PD node name to pd-{hostname} Fix the redundant header when PD sends commands to TiKV Check and report undefined PD configuration items Fixed Set retryable instead of the abort error when TiKV exits Fix the issue that PD RaftCluster cannot be stopped Fix a bug about Region merge when the Raftstore thread is very slow New contributors (Thanks!) tikv: AbnerDBFan parser: feloxx haplone "}, {"url": "https://pingcap.com/blog/5-key-differences-between-mysql-and-tidb-for-scaling-in-the-cloud/", "title": "5 Key Differences Between MySQL and TiDB for Scaling in the Cloud", "content": " As businesses adopt cloud-native architectures, conversations will naturally lead to what we can do to make the database horizontally scalable. The answer will likely be to take a closer look at TiDB.TiDB is an open source NewSQL database released under the Apache 2.0 License. Because it speaks the MySQL protocol, your existing applications will be able to connect to it using any MySQL connector, and most SQL functionality remains identical (joins, subqueries, transactions, etc.).Step under the covers, however, and there are differences. If your architecture is based on MySQL with Read Replicas, you’ll see things work a little bit differently with TiDB. In this post, I’ll go through the top five key differences I’ve found between TiDB and MySQL.1. TiDB natively distributes query execution and storage With MySQL, it is common to scale-out via replication. Typically you will have one MySQL master with many slaves, each with a complete copy of the data. Using either application logic or technology like ProxySQL, queries are routed to the appropriate server (offloading queries from the master to slaves whenever it is safe to do so).Scale-out replication works very well for read-heavy workloads, as the query execution can be divided between replication slaves. However, it becomes a bottleneck for write-heavy workloads, since each replica must have a full copy of the data. Another way to look at this is that MySQL Replication scales out SQL processing, but it does not scale out the storage. (By the way, this is true for traditional replication as well as newer solutions such as Galera Cluster and Group Replication.)TiDB works a little bit differently: Query execution is handled via a layer of TiDB servers. Scaling out SQL processing is possible by adding new TiDB servers, which is very easy to do using Kubernetes ReplicaSets. This is because TiDB servers are stateless; its TiKV storage layer is responsible for all of the data persistence. The data for tables is automatically sharded into small chunks and distributed among TiKV servers. Three copies of each data Region (the TiKV name for a shard) are kept in the TiKV cluster, but no TiKV server requires a full copy of the data. To use MySQL terminology: Each TiKV server is both a master and a slave at the same time, since for some data Regions it will contain the primary copy, and for others, it will be secondary. TiDB supports queries across data Regions or, in MySQL terminology, cross-shard queries. The metadata about where the different Regions are located is maintained by the Placement Driver, the management server component of any TiDB Cluster. All operations are fully ACID compliant, and an operation that modifies data across two Regions uses a two-phase commit. For MySQL users learning TiDB, a simpler explanation is the TiDB servers are like an intelligent proxy that translates SQL into batched key-value requests to be sent to TiKV. TiKV servers store your tables with range-based partitioning. The ranges automatically balance to keep each partition at 96MB (by default, but configurable), and each range can be stored on a different TiKV server. The Placement Driver server keeps track of which ranges are located where and automatically rebalances a range if it becomes too large or too hot.This design has several advantages of scale-out replication: It independently scales the SQL Processing and Data Storage tiers. For many workloads, you will hit one bottleneck before the other. It incrementally scales by adding nodes (for both SQL and Data Storage). It utilizes hardware better. To scale out MySQL to one master and four replicas, you would have five copies of the data. TiDB would use only three replicas, with hotspots automatically rebalanced via the Placement Driver. 2. TiDB’s storage engine is RocksDB MySQL’s default storage engine has been InnoDB since 2010. Internally, InnoDB uses a B+tree data structure, which is similar to what traditional commercial databases use.By contrast, TiDB uses RocksDB as the storage engine with TiKV. RocksDB has advantages for large datasets because it can compress data more effectively and insert performance does not degrade when indexes can no longer fit in memory.Note that both MySQL and TiDB support an API that allows new storage engines to be made available. For example, Percona Server and MariaDB both support RocksDB as an option.Download TiDB Subscribe to Blog 3. TiDB gathers metrics in Prometheus/Grafana Tracking key metrics is an important part of maintaining database health. MySQL centralizes these fast-changing metrics in Performance Schema. Performance Schema is a set of in-memory tables that can be queried via regular SQL queries.With TiDB, rather than retaining the metrics inside the server, a strategic choice was made to ship the information to a best-of-breed service. Prometheus+Grafana is a common technology stack among operations teams today, and the included graphs make it easy to create your own or configure thresholds for alarms.4. TiDB handles DDL significantly better If we ignore for a second that not all data definition language (DDL) changes in MySQL are online, a larger challenge when running a distributed MySQL system is externalizing schema changes on all nodes at the same time. Think about a scenario where you have 10 shards and add a column, but each shard takes a different length of time to complete the modification. This challenge still exists without sharding, since replicas will process DDL after a master.TiDB implements online DDL using the protocol introduced by the Google F1 paper. In short, DDL changes are broken up into smaller transition stages so they can prevent data corruption scenarios, and the system tolerates an individual node being behind up to one DDL version at a time.5. TiDB is designed for HTAP workloads The MySQL team has traditionally focused its attention on optimizing performance for online transaction processing (OLTP) queries. That is, the MySQL team spends more time making simpler queries perform better instead of making all or complex queries perform better. There is nothing wrong with this approach since many applications only use simple queries.TiDB is designed to perform well across hybrid transaction/analytical processing (HTAP) queries. This is a major selling point for those who want real-time analytics on their data because it eliminates the need for batch loads between their MySQL database and an analytics database.Conclusion These are my top five observations based on 15 years in the MySQL world and coming to TiDB. While many of them refer to internal differences, I recommend checking out the TiDB documentation on MySQL Compatibility. It describes some of the finer points about any differences that may affect your applications.Note: The original version of this article was published on opensource.com"}, {"url": "https://pingcap.com/blog-cn/tidb-source-code-reading-21/", "title": "TiDB 源码阅读系列文章(二十一)基于规则的优化 II", "content": " 在 TiDB 源码阅读系列文章(七)基于规则的优化 一文中,我们介绍了几种 TiDB 中的逻辑优化规则,包括列剪裁,最大最小消除,投影消除,谓词下推和构建节点属性,本篇将继续介绍更多的优化规则:聚合消除、外连接消除和子查询优化。聚合消除 聚合消除会检查 SQL 查询中 Group By 语句所使用的列是否具有唯一性属性,如果满足,则会将执行计划中相应的 LogicalAggregation 算子替换为 LogicalProjection 算子。这里的逻辑是当聚合函数按照具有唯一性属性的一列或多列分组时,下层算子输出的每一行都是一个单独的分组,这时就可以将聚合函数展开成具体的参数列或者包含参数列的普通函数表达式,具体的代码实现在 rule_aggregation_elimination.go 文件中。下面举一些具体的例子。例一:下面这个 Query 可以将聚合函数展开成列的查询:select max(a) from t group by t.pk; 被等价地改写成:select a from t; 例二:下面这个 Query 可以将聚合函数展开为包含参数列的内置函数的查询:select count(a) from t group by t.pk; 被等价地改写成:select if(isnull(a), 0, 1) from t; 这里其实还可以做进一步的优化:如果列 a 具有 Not Null 的属性,那么可以将 if(isnull(a), 0, 1) 直接替换为常量 1(目前 TiDB 还没做这个优化,感兴趣的同学可以来贡献一个 PR)。另外提一点,对于大部分聚合函数,参数的类型和返回结果的类型一般是不同的,所以在展开聚合函数的时候一般会在参数列上构造 cast 函数做类型转换,展开后的表达式会保存在作为替换 LogicalAggregation 算子的 LogicalProjection 算子中。这个优化过程中,有一点非常关键,就是如何知道 Group By 使用的列是否满足唯一性属性,尤其是当聚合算子的下层节点不是 DataSource 的时候?我们在 (七)基于规则的优化 一文中的“构建节点属性”章节提到过,执行计划中每个算子节点会维护这样一个信息:当前算子的输出会按照哪一列或者哪几列满足唯一性属性。因此,在聚合消除中,我们可以通过查看下层算子保存的这个信息,再结合 Group By 用到的列判断当前聚合算子是否可以被消除。外连接消除 不同于 (七)基于规则的优化 一文中“谓词下推”章节提到的将外连接转换为内连接,这里外连接消除指的是将整个连接操作从查询中移除。外连接消除需要满足一定条件: 条件 1 : LogicalJoin 的父亲算子只会用到 LogicalJoin 的 outer plan 所输出的列 条件 2 : 条件 2.1 : LogicalJoin 中的 join key 在 inner plan 的输出结果中满足唯一性属性 条件 2.2 : LogicalJoin 的父亲算子会对输入的记录去重 条件 1 和条件 2 必须同时满足,但条件 2.1 和条件 2.2 只需满足一条即可。满足条件 1 和 条件 2.1 的一个例子:select t1.a from t1 left join t2 on t1.b = t2.pk; 可以被改写成:select t1.a from t1; 满足条件 1 和条件 2.2 的一个例子:select distinct(t1.a) from t1 left join t2 on t1.b = t2.b; 可以被改写成:select distinct(t1.a) from t1; 具体的原理是,对于外连接,outer plan 的每一行记录肯定会在连接的结果集里出现一次或多次,当 outer plan 的行不能找到匹配时,或者只能找到一行匹配时,这行 outer plan 的记录在连接结果中只出现一次;当 outer plan 的行能找到多行匹配时,它会在连接结果中出现多次;那么如果 inner plan 在 join key 上满足唯一性属性,就不可能存在 outer plan 的行能够找到多行匹配,所以这时 outer plan 的每一行都会且仅会在连接结果中出现一次。同时,上层算子只需要 outer plan 的数据,那么外连接可以直接从查询中被去除掉。同理就可以很容易理解当上层算子只需要 outer plan 的去重后结果时,外连接也可以被消除。这部分优化的具体代码实现在 rule_join_elimination.go 文件中。子查询优化 / 去相关 子查询分为非相关子查询和相关子查询,例如:-- 非相关子查询 select * from t1 where t1.a > (select t2.a from t2 limit 1); -- 相关子查询 select * from t1 where t1.a > (select t2.a from t2 where t2.b > t1.b limit 1); 对于非相关子查询, TiDB 会在 expressionRewriter 的逻辑中做两类操作: 子查询展开即直接执行子查询获得结果,再利用这个结果改写原本包含子查询的表达式;比如上述的非相关子查询,如果其返回的结果为一行记录 “1” ,那么整个查询会被改写为:select * from t1 where t1.a > 1; 详细的代码逻辑可以参考 expression_rewriter.go 中的 handleScalarSubquery 和 handleExistSubquery 函数。 子查询转为 Join对于包含 IN (subquery) 的查询,比如:select * from t1 where t1.a in (select t2.a from t2); 会被改写成:select t1.* from t1 inner join (select distinct(t2.a) as a from t2) as sub on t1.a = sub.a; 如果 t2.a 满足唯一性属性,根据上面介绍的聚合消除规则,查询会被进一步改写成:select t1.* from t1 inner join t2 on t1.a = t2.a; 这里选择将子查询转化为 inner join 的 inner plan 而不是执行子查询的原因是:以上述查询为例,子查询的结果集可能会很大,展开子查询需要一次性将 t2 的全部数据从 TiKV 返回到 TiDB 中缓存,并作为 t1 扫描的过滤条件;如果将子查询转化为 inner join 的 inner plan ,我们可以更灵活地对 t2 选择访问方式,比如我们可以对 join 选择 IndexLookUpJoin 实现方式,那么对于拿到的每一条 t1 表数据,我们只需拿 t1.a 作为 range 对 t2 做一次索引扫描,如果 t1 表很小,相比于展开子查询返回 t2 全部数据,我们可能总共只需要从 t2 返回很少的几条数据。注意这个转换的结果不一定会比展开子查询更好,其具体情况会受 t1 表和 t2 表数据的影响,如果在上述查询中, t1 表很大而 t2 表很小,那么展开子查询再对 t1 选择索引扫描可能才是最好的方案,所以现在有参数控制这个转化是否打开,详细的代码可以参考 expression_rewriter.go 中的 handleInSubquery 函数。 对于相关子查询,TiDB 会在 expressionRewriter 中将整个包含相关子查询的表达式转化为 LogicalApply 算子。LogicalApply 算子是一类特殊的 LogicalJoin ,特殊之处体现在执行逻辑上:对于 outer plan 返回的每一行记录,取出相关列的具体值传递给子查询,再执行根据子查询生成的 inner plan ,即 LogicalApply 在执行时只能选择类似循环嵌套连接的方式,而普通的 LogicalJoin 则可以在物理优化阶段根据代价模型选择最合适的执行方式,包括 HashJoin,MergeJoin 和 IndexLookUpJoin,理论上后者生成的物理执行计划一定会比前者更优,所以在逻辑优化阶段我们会检查是否可以应用“去相关”这一优化规则,试图将 LogicalApply 转化为等价的 LogicalJoin 。其核心思想是将 LogicalApply 的 inner plan 中包含相关列的那些算子提升到 LogicalApply 之中或之上,在算子提升后如果 inner plan 中不再包含任何的相关列,即不再引用任何 outer plan 中的列,那么 LogicalApply 就会被转换为普通的 LogicalJoin ,这部分代码逻辑实现在 rule_decorrelate.go 文件中。具体的算子提升方式分为以下几种情况: inner plan 的根节点是 LogicalSelection则将其过滤条件添加到 LogicalApply 的 join condition 中,然后将该 LogicalSelection 从 inner plan 中删除,再递归地对 inner plan 提升算子。以如下查询为例:select * from t1 where t1.a in (select t2.a from t2 where t2.b = t1.b); 其生成的最初执行计划片段会是:LogicalSelection 提升后会变成如下片段:到此 inner plan 中不再包含相关列,于是 LogicalApply 会被转换为如下 LogicalJoin : inner plan 的根节点是 LogicalMaxOneRow即要求子查询最多输出一行记录,比如这个例子:select *, (select t2.a from t2 where t2.pk = t1.a) from t1; 因为子查询出现在整个查询的投影项里,所以 expressionRewriter 在处理子查询时会对其生成的执行计划在根节点上加一个 LogicalMaxOneRow 限制最多产生一行记录,如果在执行时发现下层输出多于一行记录,则会报错。在这个例子中,子查询的过滤条件是 t2 表的主键上的等值条件,所以子查询肯定最多只会输出一行记录,而这个信息在“构建节点属性”这一步时会被发掘出来并记录在算子节点的 MaxOneRow 属性中,所以这里的 LogicalMaxOneRow 节点实际上是冗余的,于是我们可以将其从 inner plan 中移除,然后再递归地对 inner plan 做算子提升。 inner plan 的根节点是 LogicalProjection则首先将这个投影算子从 inner plan 中移除,再根据 LogicalApply 的连接类型判断是否需要在 LogicalApply 之上再加上一个 LogicalProjection ,具体来说是:对于非 semi-join 这一类的连接(包括 inner join 和 left join ),inner plan 的输出列会保留在 LogicalApply 的结果中,所以这个投影操作需要保留,反之则不需要。最后,再递归地对删除投影后的 inner plan 提升下层算子。 inner plan 的根节点是 LogicalAggregation 首先我们会检查这个聚合算子是否可以被提升到 LogicalApply 之上再执行。以如下查询为例:select *, (select sum(t2.b) from t2 where t2.a = t1.pk) from t1; 其最初生成的执行计划片段会是:将聚合提升到 LogicalApply 后的执行计划片段会是:即先对 t1 和 t2 做连接,再在连接结果上按照 t1.pk 分组后做聚合。这里有两个关键变化:第一是不管提升前 LogicalApply 的连接类型是 inner join 还是 left join ,提升后必须被改为 left join ;第二是提升后的聚合新增了 Group By 的列,即要按照 outer plan 传进 inner plan 中的相关列做分组。这两个变化背后的原因都会在后面进行阐述。因为提升后 inner plan 不再包含相关列,去相关后最终生成的执行计划片段会是:聚合提升有很多限定条件: LogicalApply 的连接类型必须是 inner join 或者 left join 。 LogicalApply 是根据相关子查询生成的,只可能有 3 类连接类型,除了 inner join 和 left join 外,第三类是 semi join (包括 SemiJoin,LeftOuterSemiJoin,AntiSemiJoin,AntiLeftOuterSemiJoin),具体可以参考 expression_rewriter.go 中的代码,限于篇幅在这里就不对此做展开了。对于 semi join 类型的 LogicalApply ,因为 inner plan 的输出列不会出现在连接的结果中,所以很容易理解我们无法将聚合算子提升到 LogicalApply 之上。 LogicalApply 本身不能包含 join condition 。以上面给出的查询为例,可以看到聚合提升后会将子查询中包含相关列的过滤条件 (t2.a = t1.pk) 添加到 LogicalApply 的 join condition 中,如果 LogicalApply 本身存在 join condition ,那么聚合提升后聚合算子的输入(连接算子的输出)就会和在子查询中时聚合算子的输入不同,导致聚合算子结果不正确。 子查询中用到的相关列在 outer plan 输出里具有唯一性属性。以上面查询为例,如果 t1.pk 不满足唯一性,假设 t1 有两条记录满足 t1.pk = 1,t2 只有一条记录 { (t2.a: 1, t2.b: 2) },那么该查询会输出两行结果 { (sum(t2.b): 2), (sum(t2.b): 2) };但对于聚合提升后的执行计划,则会生成错误的一行结果{ (sum(t2.b): 4) }。当 t1.pk 满足唯一性后,每一行 outer plan 的记录都对应连接结果中的一个分组,所以其聚合结果会和在子查询中的聚合结果一致,这也解释了为什么聚合提升后需要按照 t1.pk 做分组。 聚合函数必须满足当输入为 null 时输出结果也一定是 null 。这是为了在子查询中没有匹配的特殊情况下保证结果的正确性,以上面查询为例,当 t2 表没有任何记录满足 t2.a = t1.pk 时,子查询中不管是什么聚合函数都会返回 null 结果,为了保留这种特殊情况,在聚合提升的同时, LogicalApply 的连接类型会被强制改为 left join(改之前可能是 inner join ),所以在这种没有匹配的情况下,LogicalApply 输出结果中 inner plan 部分会是 null ,而这个 null 会作为新添加的聚合算子的输入,为了和提升前结果一致,其结果也必须是 null 。 对于根据上述条件判定不能提升的聚合算子,我们再检查这个聚合算子的子节点是否为 LogicalSelection ,如果是,则将其从 inner plan 中移除并将过滤条件添加到 LogicalApply 的 join condition 中。这种情况下 LogicalAggregation 依然会被保留在 inner plan 中,但会将 LogicalSelection 过滤条件中涉及的 inner 表的列添加到聚合算子的 Group By 中。比如对于查询:select *, (select count(*) from t2 where t2.a = t1.a) from t1; 其生成的最初的执行计划片段会是:因为聚合函数是 count(*) ,不满足当输入为 null 时输出也为 null 的条件,所以它不能被提升到 LogicalApply 之上,但它可以被改写成:注意 LogicalAggregation 的 Group By 新加了 t2.a ,这一步将原本的先做过滤再做聚合转换为了先按照 t2.a 分组做聚合,再将聚合结果与 t1 做连接。 LogicalSelection 提升后 inner plan 已经不再依赖 outer plan 的结果了,整个查询去相关后将会变为: 总结 这是基于规则优化的第二篇文章,后续我们还将介绍更多逻辑优化规则:聚合下推,TopN 下推和 Join Reorder 。"}, {"url": "https://pingcap.com/weekly/2018-12-10-tidb-weekly/", "title": "Weekly update (December 03 ~ December 09, 2018)", "content": " Weekly update in TiDB Last week, we landed 48 PRs in the TiDB repository.Added Add the HTTP API to query the DDL history Add two metrics gauges to show CPU and memory usage Propose the design to restore the SQL text from the AST tree Add the implementation phase framework of the cascades planner Add the ddl_reorg_batch_size variable to control the batch size of the DDL worker Add the gc_enable variable to enable or disable GC Add the transform phase framework of the cascades planner Improved Speed up execution of unit tests for the statistics package Disable the global variable cache when running unit tests Do the DDL check for Create Index before putting it to the job queue Speed up execution of unit tests for the tikv package Improve compatibility when casting string as datetime Support Show Create Table for hash partitioned tables Support batchInsert of values for the Insert statement Improve the current greedy join reorder algorithm to leverage cost estimation Support Select/Insert for hash partitioned tables Support Truncate for hash partitioned tables Support Alter Table Add Partition for hash partitioned tables Support ? in Order By/Group By/Limit Offset clauses Fixed Set correct concurrency for Projection below Aggregation Handle corrupted length parameters for the uncompress built-in function Fix failure of Grant in the ANSI_QUOTES SQL mode Fix incorrect date arithmetics with the negative interval parameter Fix the bug of cancelling Drop Index Weekly update in TiSpark Last week, we landed 4 PRs in the TiSpark repository.Fixed Fix the unresolved column name when using table.column Fix the LockResolverTest error when sending requests using callWithRetry Weekly update in TiKV and PD Last week, we landed 22 PRs in the TiKV and PD repositories.Added Add StoreNotMatch details Add the strcmp built-in function Add the week_day and the week_of_year built-in functions Add the compress, uncompress and uncompressed_length built-in functions Add the end_key support for raw_scan Add the concat_ws built-in function Improved Replace FxHashMap with Hashbrown Make DeleteRange safe again Fixed Reject transferring the leader to the recently added peers Change the merge match peers strategy New contributors (Thanks!) tidb: lonnng ziyi-yan tikv: kg88 AbnerZheng docs-cn: beckxie "}, {"url": "https://pingcap.com/blog-cn/tidb-ecosystem-tools-1/", "title": "TiDB Ecosystem Tools 原理解读系列(一):TiDB-Binlog 架构演进与实现原理", "content": " 简介 TiDB-Binlog 组件用于收集 TiDB 的 binlog,并提供实时备份和同步功能。该组件在功能上类似于 MySQL 的主从复制,MySQL 的主从复制依赖于记录的 binlog 文件,TiDB-Binlog 组件也是如此,主要的不同点是 TiDB 是分布式的,因此需要收集各个 TiDB 实例产生的 binlog,并按照事务提交的时间排序后才能同步到下游。如果你需要部署 TiDB 集群的从库,或者想订阅 TiDB 数据的变更输出到其他的系统中,TiDB-Binlog 则是必不可少的工具。架构演进 TiDB-Binlog 这个组件已经发布了 2 年多时间,经历过几次架构演进,去年十月到现在大规模使用的是 Kafka 版本,架构图如下:Kafka 版本的 TiDB-Binlog 主要包括两个组件:Pump:一个守护进程,在每个 TiDB 主机的后台运行。其主要功能是实时记录 TiDB 产生的 binlog 并顺序写入 Kafka 中。Drainer: 从 Kafka 中收集 binlog,并按照 TiDB 中事务的提交顺序转化为指定数据库兼容的 SQL 语句或者指定格式的数据,最后同步到目的数据库或者写到顺序文件。这个架构的工作原理为: TiDB 需要与 Pump 绑定,即 TiDB 实例只能将它生成的 binlog 发送到一个指定的 Pump 中; Pump 将 binlog 先写到本地文件,再异步地写入到 Kafka; Drainer 从 Kafka 中读出 binlog,对 binlog 进行排序,对 binlog 解析后生成 SQL 或指定格式的数据再同步到下游。 根据用户的反馈,以及我们自己做的一些测试,发现该版本主要存在一些问题。首先,TiDB 的负载可能不均衡,部分 TiDB 业务较多,产生的 binlog 也比较多,对应的 Pump 的负载高,导致数据同步延迟高。其次,依赖 Kafka 集群,增加了运维成本;而且 TiDB 产生的单条 binlog 的大小可达 2G(例如批量删除数据、批量写入数据),需要配置 Kafka 的消息大小相关设置,而 Kafka 并不太适合单条数据较大的场景。最后,Drainer 需要读取 Kafka 中的 binlog、对 binlog 进行排序、解析 binlog,同步数据到下游等工作,可以看出 Drainer 的工作较多,而且 Drainer 是一个单点,所以往往同步数据的瓶颈都在 Drainer。以上这些问题我们很难在已有的框架下进行优化,因此我们对 TiDB-Binlog 进行了重构,最新版本的 TiDB-Binlog 的总体架构如下图所示:新版本 TiDB-Binlog 不再使用 Kafka 存储 binlog,仍然保留了 Pump 和 Drainer 两个组件,但是对功能进行了调整: Pump 用于实时记录 TiDB 产生的 binlog,并将 binlog 按照事务的提交时间进行排序,再提供给 Drainer 进行消费。 Drainer 从各个 Pump 中收集 binlog 进行归并,再将 binlog 转化成 SQL 或者指定格式的数据,最终同步到下游。 该版本的主要优点为: 多个 Pump 形成一个集群,可以水平扩容,各个 Pump 可以均匀地承担业务的压力。 TiDB 通过内置的 Pump Client 将 binlog 分发到各个 Pump,即使有部分 Pump 出现故障也不影响 TiDB 的业务。 Pump 内部实现了简单的 kv 来存储 binlog,方便对 binlog 数据的管理。 原来 Drainer 的 binlog 排序逻辑移到了 Pump 来做,而 Pump 是可扩展的,这样就能提高整体的同步性能。 Drainer 不再需要像原来一样读取一批 binlog 到内存里进行堆排序,只需要依次读取各个 Pump 的 binlog 进行归并排序,这样可以大大节省内存的使用,同时也更容易做内存控制。 由于该版本最大的特点是多个 Pump 组成了一个集群(cluster),因此该版本命名为 cluster 版本。下面我们以最新的 cluster 版本的架构来介绍 TiDB-Binlog 的实现原理。工作原理 binlog 首先我们先介绍一下 TiDB 中的 binlog,TiDB 的事务采用 2pc 算法,一个成功的事务会写两条 binlog,包括一条 Prewrite binlog 和 一条 Commit binlog;如果事务失败,会发一条 Rollback binlog。binlog 的结构定义为:// Binlog 记录事务中所有的变更,可以用 Binlog 构建 SQL message Binlog { // Binlog 的类型,包括 Prewrite、Commit、Rollback 等 optional BinlogType tp = 1 [(gogoproto.nullable) = false]; // Prewrite, Commit 和 Rollback 类型的 binlog 的 start_ts,记录事务开始的 ts optional int64 start_ts = 2 [(gogoproto.nullable) = false]; // commit_ts 记录事务结束的 ts,只记录在 commit 类型的 binlog 中 optional int64 commit_ts = 3 [(gogoproto.nullable) = false]; // prewrite key 只记录在 Prewrite 类型的 binlog 中, // 是一个事务的主键,用于查询该事务是否提交 optional bytes prewrite_key = 4; // prewrite_value 记录在 Prewrite 类型的 binlog 中,用于记录每一行数据的改变 optional bytes prewrite_value = 5; // ddl_query 记录 ddl 语句 optional bytes ddl_query = 6; // ddl_job_id 记录 ddl 的 job id optional int64 ddl_job_id = 7 [(gogoproto.nullable) = false]; } binlog 及相关的数据结构定义见:binlog.proto其中 start_ts 为事务开始时的 ts,commit_ts 为事务提交的 ts。ts 是由物理时间和逻辑时间转化而成的,在 TiDB 中是唯一的,由 PD 来统一提供。在开始一个事务时,TiDB 会请求 PD,获取一个 ts 作为事务的 start_ts,在事务提交时则再次请求 PD 获取一个 ts 作为 commit_ts。 我们在 Pump 和 Drainer 中就是根据 binlog 的 commit_ts 来对 binlog 进行排序的。TiDB 的 binlog 记录为 row 模式,即保存每一行数据的改变。数据的变化记录在 prewrite_value 字段中,该字段的数据主要由序列化后的 TableMutation 结构的数据组成。TableMutation 的结构如下所示:// TableMutation 存储表中数据的变化 message TableMutation { // 表的 id,唯一标识一个表 optional int64 table_id = 1 [(gogoproto.nullable) = false]; // 保存插入的每行数据 repeated bytes inserted_rows = 2; // 保存修改前和修改后的每行的数据 repeated bytes updated_rows = 3; // 已废弃 repeated int64 deleted_ids = 4; // 已废弃 repeated bytes deleted_pks = 5; // 删除行的数据 repeated bytes deleted_rows = 6; // 记录数据变更的顺序 repeated MutationType sequence = 7; } 下面以一个例子来说明 binlog 中是怎么存储数据的变化的。例如 table 的结构为:create table test (id int, name varchar(24), primary key id)按照顺序执行如下 SQL:begin; insert into test(id, name) values(1, "a"); insert into test(id, name) values(2, "b"); update test set name = "c" where id = 1; update test set name = "d" where id = 2; delete from test where id = 2; insert into test(id, name) values(2, "c"); commit; 则生成的 TableMutation 的数据如下所示:inserted_rows: 1, "a" 2, "b" 2, "c" updated_rows: 1, "a", 1, "c" 2, "b", 2, "d" deleted_rows: 2, "d" sequence: Insert, Insert, Update, Update, DeleteRow, Insert 可以从例子中看出,sequence 中保存的数据变更类型的顺序为执行 SQL 的顺序,具体变更的数据内容则保存到了相应的变量中。Drainer 在把 binlog 数据同步到下游前,就需要把上面的这些数据还原成 SQL,再同步到下游。另外需要说明的是,TiDB 在写 binlog 时,会同时向 TiKV 发起写数据请求和向 Pump 发送 Prewrite binlog,如果 TiKV 和 Pump 其中一个请求失败,则该事务失败。当 Prewrite 成功后,TiDB 向 TiKV 发起 Commit 消息,并异步地向 Pump 发送一条 Commit binlog。由于 TiDB 是同时向 TiKV 和 Pump 发送请求的,所以只要保证 Pump 处理 Prewrite binlog 请求的时间小于等于 TiKV 执行 Prewrite 的时间,开启 binlog 就不会对事务的延迟造成影响。Pump Client 从上面的介绍中我们知道由多个 Pump 组成一个集群,共同承担写 binlog 的请求,那么就需要保证 TiDB 能够将写 binlog 的请求尽可能均匀地分发到各个 Pump,并且需要识别不可用的 Pump,及时获取到新加入集群中 Pump 信息。这部分的工作是在 Pump Client 中实现的。Pump Client 以包的形式集成在 TiDB 中,代码链接:pump_client。Pump Client 维护 Pump 集群的信息,Pump 的信息主要来自于 PD 中保存的 Pump 的状态信息,状态信息的定义如下(代码链接:Status):type Status struct { // Pump/Drainer 实例的唯一标识 NodeID string `json:"nodeId"` // Pump/Drainer 的服务地址 Addr string `json:"host"` // Pump/Drainer 的状态,值可以为 online、pausing、paused、closing、offline State string `json:"state"` // Pump/Drainer 是否 alive(目前没有使用该字段) IsAlive bool `json:"isAlive"` // Pump的分数,该分数是由节点的负载、磁盘使用率、存储的数据量大小等因素计算得来的, // 这样 Pump Client 可以根据分数来选取合适的 Pump 发送 binlog(待实现) Score int64 `json:"score"` // Pump 的标签,可以通过 label 对 TiDB 和 Pump 进行分组, // TiDB 只能将 binlog 发送到相同 label 的 Pump(待实现) Label *Label `json:"label"` // Pump: 保存的 binlog 的最大的 commit_ts // Drainer:已消费的 binlog 的最大的 commit_ts MaxCommitTS int64 `json:"maxCommitTS"` // 该状态信息的更新时间对应的 ts. UpdateTS int64 `json:"updateTS"` } Pump Client 根据 Pump 上报到 PD 的信息以及写 binlog 请求的实际情况将 Pump 划分为可用 Pump 与不可用 Pump 两个部分。划分的方法包括: 初始化时从 PD 中获取所有 Pump 的信息,将状态为 online 的 Pump 加入到可用 Pump 列表中,其他 Pump 加入到非可用列表中。 Pump 每隔固定的时间会发送心跳到 PD,并更新自己的状态。Pump Client 监控 PD 中 Pump 上传的状态信息,及时更新内存中维护的 Pump 信息,如果状态由非 online 转换为 online 则将该 Pump 加入到可用 Pump 列表;反之加入到非可用列表中。 在写 binlog 到 Pump 时,如果该 Pump 在重试多次后仍然写 binlog 失败,则把该 Pump 加入到非可用 Pump 列表中。 定时发送探活请求(数据为空的 binlog 写请求)到非可用 Pump 列表中的状态为 online 的 Pump,如果返回成功,则把该 Pump 重新加入到可用 Pump 列表中。 通过上面的这些措施,Pump Client 就可以及时地更新所维护的 Pump 集群信息,保证将 binlog 发送到可用的 Pump 中。另外一个问题是,怎么保证 Pump Client 可以将 binlog 写请求均匀地分发到各个 Pump?我们目前提供了几种路由策略: range: 按照顺序依次选取 Pump 发送 binlog,即第一次选取第一个 Pump,第二次选取第二个 Pump… hash:对 binlog 的 start_ts 进行 hash,然后选取 hash 值对应的 Pump。 score:根据 Pump 上报的分数按照加权平均算法选取 Pump 发送 binlog(待实现)。 需要注意的地方是,以上的策略只是针对 Prewrite binlog,对于 Commit binlog,Pump Client 会将它发送到对应的 Prewrite binlog 所选择的 Pump,这样做是因为在 Pump 中需要将包含 Prewrite binlog 和 Commit binlog 的完整 binlog(即执行成功的事务的 binlog)提供给 Drainer,将 Commit binlog 发送到其他 Pump 没有意义。Pump Client 向 Pump 提交写 binlog 的请求接口为 pump.proto 中的 WriteBinlog,使用 grpc 发送 binlog 请求。Pump Pump 主要用来承担 binlog 的写请求,维护 binlog 数据,并将有序的 binlog 提供给 Drainer。我们将 Pump 抽象成了一个简单的 kv 数据库,key 为 binlog 的 start _ts(Priwrite binlog) 或者 commit_ts(Commit binlog),value 为 binlog 的元数据,binlog 的数据则存在数据文件中。Drainer 像查数据库一样的来获取所需要的 binlog。Pump 内置了 leveldb 用于存储 binlog 的元信息。在 Pump 收到 binlog 的写请求时,会首先将 binlog 数据以 append 的形式写到文件中,然后将 binlog 的 ts、类型、数据长度、所保存的文件以及在文件中的位置信息保存在 leveldb 中,如果为 Prewrite binlog,则以 start_ts作为 key;如果是 Commit binlog,则以 commit_ts 作为 key。当 Drainer 向 Pump 请求获取指定 ts 之后的 binlog 时,Pump 则查询 leveldb 中大于该 ts 的 binlog 的元数据,如果当前数据为 Prewrite binlog,则必须找到对应的 Commit binlog;如果为 Commit binlog 则继续向前推进。这里有个问题,在 binlog 一节中提到,如果 TiKV 成功写入了数据,并且 Pump 成功接收到了 Prewrite binlog,则该事务就提交成功了,那么如果在 TiDB 发送 Commit binlog 到 Pump 前发生了一些异常(例如 TiDB 异常退出,或者强制终止了 TiDB 进程),导致 Pump 没有接收到 Commit binlog,那么 Pump 中就会一直找不到某些 Prewrite binlog 对应的 Commit binlog。这里我们在 Pump 中做了处理,如果某个 Prewrite binlog 超过了十分钟都没有找到对应的 Commit binlog,则通过 binlog 数据中的 prewrite_key 去查询 TiKV 该事务是否提交,如果已经提交成功,则 TiKV 会返回该事务的 commit_ts;否则 Pump 就丢弃该条 Prewrite binlog。binlog 元数据中提供了数据存储的文件和位置,可以通过这些信息读取 binlog 文件的指定位置获取到数据。因为 binlog 数据基本上是按顺序写入到文件中的,因此我们只需要顺序地读 binlog 文件即可,这样就保证了不会因为频繁地读取文件而影响 Pump 的性能。最终,Pump 以 commit_ts 为排序标准将 binlog 数据传输给 Drainer。Drainer 向 Pump 请求 binlog 数据的接口为 pump.proto 中的 PullBinlogs,以 grpc streaming 的形式传输 binlog 数据。值得一提的是,Pump 中有一个 fake binlog 机制。Pump 会定时(默认三秒)向本地 …"}, {"url": "https://pingcap.com/success-stories/tidb-in-meituan-dianping/", "title": "TiDB, the Key to a Better Life for Meituan-Dianping’s 290 Million Monthly Users", "content": " Industry: Search and Ecommerce PlatformAuthors: Yinggang Zhao (Researcher at Meituan-Dianping), Kun Li (Database expert at Meituan-Dianping) and Changjun Piao (Database expert at Meituan-Dianping)Introduction In Chinese, Meituan-Dianping means “better buying, better life,” and since it was formed in 2015 by the merger of two companies, the platform has facilitated billions of purchases of goods and services with built-in discounts. By gross merchandise volume, Meituan-Dianping is China’s largest group-buying website. Part Groupon, part Yelp, and part Uber Eats, we offer a range of localized services and entertainment, such as food delivery, restaurant reviews, haircuts and manicures, ticket bookings, bike-sharing, and more. In April 2018, we had 290 million monthly active users, and last year we generated more than 5.8 billion transactions with over $51 billion in gross transaction volume. On September 20, 2018, our company debuted on the Hong Kong stock exchange at an IPO price of HK$69 per share.As our business has grown rapidly, our data volume has also surged. This has placed tremendous pressure on the MySQL database system in our backend. Burdened by handling this immense data, we began to explore a better data storage solution. Fortunately, we found TiDB, a MySQL-compatible NewSQL hybrid transactional and analytical processing (HTAP) database, built and supported by PingCAP. Now we can harness our data with more confidence than ever before and provide better services for our users to enjoy a better life.At the beginning of 2018, our DBA (database administrator) team worked together with the architecture storage team to choose and implement a distributed database solution. Since November 2018, 10 TiDB clusters have been deployed in our production environment, with nearly 200 physical nodes. These clusters are deployed for six product divisions or platforms: delivery, transport, quick pass, accommodation, the Meituan platform, and the core development platform. Most of these applications are pure OLTP (online transaction processing) workloads. We are happy to report that all the clusters have been running smoothly since their deployment.In this post, we will share two of the scenarios for which we chose TiDB, how we are using it, some issues we’ve had and the corresponding solutions, as well as our experiences collaborating with PingCAP.Challenges Scenario 1: Offline Analytical Workload with Huge Writes and High Read QPS The scenario at Meituan-Dianping with the largest data size for analysis has up to 500GB writes every day. This scenario features: Stable write, with each transaction operating on 100 to 200 rows, and 60,000 writes per second. The daily write data of 300GB to 500GB, which will gradually increase to 3TB in the future. Regular read job with 5,000 QPS every 15 minutes (high frequency but a small amount of data). Irregular query (low frequency but a large amount of data). Previously, we used MySQL for data storage, but we hit capacity and performance bottlenecks—and we expect the data volume of our service to increase ten times in the future. We tested ClickHouse but found that it failed to cope well with high-frequency SQL queries in high concurrency situations, although it satisfied our demands for storage capacity and running low-frequency SQL statements. Besides, we thought that it was kind of an overkill to use ClickHouse only for full low-frequency SQL queries.Scenario 2: OLTP Workload Sensitive to Response Time In addition to offline services with massive data to analyze and query, we have lots of sharded services. Although there are multiple sharding policies used in the industry to overcome the bottlenecks of standalone machine performance and storage, these policies do have some drawbacks: No application-friendly distributed transactions. For queries across databases, the results of the queries are aggregated on the middleware, which is troublesome. If a single database is short of storage space, it needs to split again. The applications should consider the rules of data distribution, and although the middleware is applied, the problem cannot really be solved. Many of the applications and services in our environment were sharded, and some of them would soon exceed the storage capacity of a standalone machine or were in need of a new sharding policy. All of these services embodied the following characteristics: There weren’t too many SQL queries, but the execution of SQL queries was frequent. They called for strong consistency of data. Generally, some data had a time property. Exploration To overcome these challenges and lay the foundation for the future of our infrastructure, we started to explore a database solution with the following requirements: Compatibility with our existing software stack and ecosystem to minimize the migration cost and efforts. At Meituan-Dianping, MySQL was our primary database solution supporting most of our business scenarios. So compatibility with the MySQL protocol with secondary index support was a must-have feature. The solution also had to be able to deliver performant OLTP services with high concurrency. There are many product lines in Meituan-Dianping. The services have a huge volume and demand high-quality service from the storage system. We needed minimal transformation of current software stacks, such as service access, monitoring and alert system, and automated operations platform. Online scalability. Data sharding, merging, and migration need to be automatic and transparent to the online business. The solution should support shard splitting and automatic migration for data sharding, and the services should not be interrupted during data migration. Distributed transactions with strong consistency. A transaction can be executed across shards and nodes and must be strongly consistent. Service availability across data centers. Services can be automatically switched over when any data center is down. Cross-data center write into one table across data centers. Although it’s a thorny problem, supporting writing one table across data centers is an important requirement to support our current and next-phase business plan. Before diving deep into all kinds of database solutions, we did our homework, unraveling the details of suitable data storage structure and transaction algorithm through different papers, especially the following ones from Google: Spanner: Google’s Globally-Distributed Database Large-scale Incremental Processing Using Distributed Transactions and Notifications In Search of an Understandable Consensus Algorithm Online, Asynchronous Schema Change in F1 Naturally, TiDB, an open source, NewSQL, scalable hybrid transactional and analytical processing (HTAP) database built by the PingCAP team and the open source community, caught our eye. It eventually became our database of choice because of its compatibility with MySQL, cutting-edge technical architecture and foresight, and vibrant community. In the nearly three years between the release of TiDB Alpha and the end of July 2018, at least 200 users had deployed TiDB in their production environments. Many of these users are the leading enterprises in their respective industries.More specifically, compared with traditional solutions in the industry, TiDB is a perfect match for all the above requirements in that it is: Compatible with the MySQL protocol; Flexible in online scaling in and scaling out; Supports ACID transactions with strong consistency; Supports deployment and fault-tolerance across data centers and multi-node write; Handles services on TiDB just like on a standalone MySQL. Thus we began to test TiDB for these features.Evaluation To evaluate if TiDB’s performance and features can meet our requirements, regular functional and performance tests were carried out and compared to those of MySQL. One special test worth mentioning was to validate if each data center has 1 of the 3 replicas so that the crash of one …"}, {"url": "https://pingcap.com/blog/tidb-2.1-ga-Battle-tested-to-handle-an-unpredictable-world/", "title": "TiDB 2.1 GA: Battle-Tested to Handle an Unpredictable World", "content": " Today, we are proud to announce that TiDB 2.1 is ready for General Availability. TiDB is an open-source NewSQL Hybrid Transactional and Analytical Processing (HTAP) database – one of the most popular and active database products on GitHub. It is designed to provide elastic horizontal scalability, strong consistency, and high availability. TiDB is MySQL-compatible and serves as a single relational database solution for both OLTP (Online Transactional Processing) and OLAP (Online Analytical Processing) workloads.From 2.0 to 2.1 Since we launched TiDB 2.0 back in April 2018, we’ve seen an incredible pace of adoption. Hundreds of companies now use TiDB in production, serving millions of users in industries from banking, fintech, and insurance, to food delivery, ridesharing, and gaming. The largest cluster has more than 100 nodes storing 100+ TB of data. Workloads range from pure OLTP, to pure OLAP, to a hybrid mixture of both. In addition, some customers are taking advantage of TiDB’s modular architecture by just using TiKV, the distributed transactional key-value store (now a Cloud Native Computing Foundation member project) to unify data storage.We’ve also launched TiDB Academy, a series of self-paced technical training courses and certifications on TiDB and distributed databases, and TiDB Cloud, to enable more customers to easily use TiDB in any cloud environment, either as a fully-managed service delivered by PingCAP or on a public marketplace.What’s exciting isn’t just the rate of adoption, but also the multitude of real-world scenarios that TiDB is exposed to and must navigate with resilience and grace. In the last few months, TiDB has had to handle challenges like cross-data center disaster recovery, six-figure throughput requirement, massive traffic spike on Singles’ Day (one of the largest online shopping holidays in the world), hardware and network failures, and more.In short, the PingCAP team has seen a lot of unpredictable unknowns as we supported TiDB users, and the technology is battle-tested than ever before. These experiences shaped what we built in 2.1, so TiDB would always have your back and help you handle an unpredictable world.What’s New in TiDB 2.1 There are many new features, improvements, and enhancements in 2.1. To check out the full list, please see the official 2.1 GA release notes, but here are some major highlights:Smarter Optimizer The optimizer is the brain of the database. In 2.1, we’ve made significant improvements to make TiDB’s brain, its cost-based optimizer (CBO), smarter so it knows how to speed up more complex queries without human intervention. For example, the new CBO can make smarter index choices on Index Join, along with optimizations on outer table and correlated subquery, to speed up complex queries automatically.We also added additional knobs that you can turn manually as “hint”, for Join, UPDATE, DELETE, and other queries, so even if the CBO does not generate a good query plan, you can intervene to keep performance up to par and consistent.Faster Executor TiDB 2.1 includes major improvements to the execution of certain physical plans to increase performance, especially for hash aggregation and projection, by adding multi-thread execution. We also re-architected the aggregation framework to support vectorized computation.Because of these improvements, TiDB 2.1 performances much better in OLAP scenarios than 2.0, as shown in our latest TPC-H benchmark in the “Performance Benchmark” section below. This performance boost further strengthens TiDB’s capabilities as an HTAP database.Download TiDB Subscribe to Blog New and Improved Raft The Raft consensus protocol inside TiKV is at the core of how TiDB ensures strong consistency and high availability of your data. Thus, constantly improving Raft has always been a high priority. In 2.1, we have incorporated three new Raft features: Raft PreVote – this feature pre-checks the likelihood of certain members of a Raft group becoming a Leader before voting takes place, which reduces performance fluctuation and improves stability when a TiKV node enters or returns to a cluster. Raft Learner – a Raft Learner node is a non-voting member of a Raft group. By adding a Learner first before other voting members are added, this feature improves data safety and availability, especially in a cross-data center deployment. Raft Region Merge – automatically merge many small regions into one larger region to reduce cost of cluster management, and improve overall performance and stability for large-scale clusters. Note: this feature is available in 2.1 but not enabled by default. Dynamic Statistics Update Timely update of the latest statistics to TiDB’s CBO is very important for generating the correct query plan. That’s why in 2.1, we added dynamic statistics update based on query feedback.Here’s how it works in brief: when TiDB first generates a query plan, it will do so based on existing statistics generated from previous queries to estimate the amount of data involved in the query to be processed. After this query is processed, TiDB will measure how much data was actually involved. Depending on the difference between the estimate and the actual, TiDB will automatically update the system, including both the histogram and the CM-sketch. Based on our internal testing, for a table with no previous statistics available, it takes about 10 rounds of queries worth of updates, before TiDB can generate the most optimal and efficient query plan consistently.Besides dynamic statistics update, 2.1 also adds additional support for auto analyze (see details in our documentation), so depending on the ratio of the number of modified rows to the total number of rows, TiDB can automatically execute ANALYZE to update the statistics of that table.Concurrent DDL In TiDB, all DDL is online. In previous versions, however, every DDL operation is serialized, even if DDL is performed on different tables with no relations to each other. That’s not ideal when you are doing Add Index on table A and also want to create table B, but table B creation must wait until Add Index on table A is finished.In 2.1, we have revamped TiDB’s online DDL process to completely separate Add Index with other DDL operations, so they can be executed concurrently. In TiDB, Add Index usually takes quite a long time, while other DDL operations can be completed within a few seconds, so with this improvements, most DDL operations no longer have to wait to be executed.Readable EXPLAIN and EXPLAIN ANALYZE EXPLAIN is a very important tool for debugging and diagnosing queries. Before 2.1, TiDB generally followed MySQL’s EXPLAIN format, but as queries become more complex, this format became less useful, because it doesn’t easily show each layer of subqueries to help with diagnosing issues.In 2.1, we improved TiDB’s EXPLAIN to display information in each layer of operation of a complex query in a format that’s more readable at a glance, so users can quickly spot, troubleshoot, and identify potential problems in their queries. This section of our documentation explains how the new EXPLAIN works in more detail.In addition, users can now also use EXPLAIN ANALYZE to see additional execution statistics while a query is being executed, to investigate every operation’s execution time and the amount of data involved. With these improvements, debugging and diagnosing query issues in TiDB is easier than ever before.Hotspot Scheduling Hotspot formation is one of the biggest enemies to a performant distributed database. It is also one of the most unpredictable – you never know when and how hotspots could form to create bottlenecks in your system.Thus, in this new version we did a lot of work to make TiDB smarter at detecting hotspot formation more quickly by aggregating additional system metadata to be monitored continuously. We also further optimized TiDB’s ability to execute hotspot scheduling policy – breaking up …"}, {"url": "https://pingcap.com/weekly/2018-12-03-tidb-weekly/", "title": "Weekly update (November 26 ~ December 02, 2018)", "content": " Weekly update in TiDB Last week, we landed 72 PRs in the TiDB repository.Added Visualize the trace output using the HTTP interface Add the tidb_parse_tso built-in function Add the json_depth built-in function Close the client connection when the server waiting time exceeds wait_timeout Add a constraint propagation framework to be used by partition pruning Improved Release the datum memory after the transaction is finished Simplify the session level domain pool Record the current database information in the CRUCIAL OPERATION log Kill all connections when the server is not gracefully shut down Support PREPARE FROM @var_name Implement GoString() for TxnState to make the generated log more user-friendly Improve the privilege check for set password Return scanned rows of Coprocessor operators in EXPLAIN ANALYZE Improve the privilege check for use DB Improve the MySQL compatibility of SHOW commands Do not push down filters containing SetVar or GetVar Add a limit to constrain the maximum number of prepared statements Prevent Order By table_name.column_name for the Union statement Make round work when converting a float string to an integer string Add the correctness check for the validate_password_* system variables Fixed Fix a bug of wrong duplicate results in left outer join Set a default value for the Enum type correctly when inserting rows Fix an error message when the updated value overflows Fix a panic when dumping statistics for a dropped column Fix wrong result length of Union Prohibit the DML execution when TiDB loses connection to etcd Fix data race caused by concurrent access to do.infoHandle Fix a server hang issue when canceling the Add Index DDL job Weekly update in TiSpark Last week, we landed 2 PRs in the TiSpark repository.Fixed Fix NullPointException when counting empty tables Weekly update in TiKV and PD Last week, we landed 25 PRs in the TiKV and PD repositories.Added Add support for adding the learner operator to PD Add the week_with_mode built-in function Add the space built-in function Add the SubstringBinary2Args and SubstringBinary3Args built-in functions Support modifying TiKV’s maximum background jobs 2.1 cherry pick: Support ambiguous time Improved Collect component logs Improve tombstone command’s error message Improve tests: Move the tests directory Fix test_follower_slow_split Add more seek and seek_for_prev tests Provide the seeded KV generator Improve PD documentation: Update the API document Update the API HTML file Abandon the PD vendor directory Fixed Fix the TiKV redundant raftstore heartbeat Fix the issue of watching leader with invalid revision in PD Fix small bugs of tikv-ctl 2.1 cherry pick: Fix tikv-ctl help messages New contributors (Thanks!) docs: lucperkins tidb: alapha23 exialin leoppro pingyu "}, {"url": "https://pingcap.com/blog-cn/tidb-21-ga-release-notes/", "title": "TiDB 2.1 GA Release Notes", "content": " 2018 年 11 月 30 日,TiDB 发布 2.1 GA 版。相比 2.0 版本,该版本对系统稳定性、性能、兼容性、易用性做了大量改进。TiDB SQL 优化器 优化 Index Join 选择范围,提升执行性能 优化 Index Join 外表选择,使用估算的行数较少的表作为外表 扩大 Join Hint TIDB_SMJ 的作用范围,在没有合适索引可用的情况下也可使用 Merge Join 加强 Join Hint TIDB_INLJ 的能力,可以指定 Join 中的内表 优化关联子查询,包括下推 Filter 和扩大索引选择范围,部分查询的效率有数量级的提升 支持在 UPDATE 和 DELETE 语句中使用 Index Hint 和 Join Hint 支持更多函数下推:ABS/CEIL/FLOOR/IS TRUE/IS FALSE 优化内建函数 IF 和 IFNULL 的常量折叠算法 优化 EXPLAIN 语句输出格式, 使用层级结构表示算子之间的上下游关系 SQL 执行引擎 重构所有聚合函数,提升 Stream 和 Hash 聚合算子的执行效率 实现并行 Hash Aggregate 算子,部分场景下有 350% 的性能提升 实现并行 Project 算子,部分场景有 74% 的性能提升 并发地读取 Hash Join 的 Inner 表和 Outer 表的数据,提升执行性能 优化 REPLACE INTO 语句的执行速度,性能提升 10x 优化时间类型的内存占用,时间类型数据的内存使用降低为原来的一半 优化点查的查询性能, Sysbench 点查效率提升 60% TiDB 插入和更新宽表,性能提升接近 20 倍 支持在配置文件中设置单个查询的内存使用上限 优化 Hash Join 的执行过程,当 Join 类型为 Inner Join 或者 Semi Join 时,如果内表为空,不再读取外表数据,快速返回结果 支持 EXPLAIN ANALYZE 语句,用于查看 Query 执行过程中各个算子的运行时间,返回结果行数等运行时统计信息 统计信息 支持只在一天中的某个时间段开启统计信息自动 ANALYZE 的功能 支持根据查询的反馈自动更新表的统计信息 支持通过 ANALYZE TABLE WITH BUCKETS 语句配置直方图中桶的个数 优化等值查询和范围查询混合的情况下使用直方图估算 Row Count 的算法 表达式 支持内建函数: json_contains json_contains_path encode/decode Server 支持在单个 tidb-server 实例内部对冲突事务排队,优化事务间冲突频繁的场景下的性能 支持 Server Side Cursor 新增 HTTP 管理接口 打散 table 的 regions 在 TiKV 集群中的分布 控制是否打开 general log 在线修改日志级别 查询 TiDB 集群信息 添加 auto_analyze_ratio 系统变量控制自动 Analyze 的阈值 添加 tidb_retry_limit 系统变量控制事务自动重试的次数 添加 tidb_disable_txn_auto_retry 系统变量控制事务是否自动重试 支持使用 admin show slow 语句来获取慢查询语句 增加环境变量 tidb_slow_log_threshold 动态设置 slow log 的阈值 增加环境变量 tidb_query_log_max_len 动态设置日志中被截断的原始 SQL 语句的长度 DDL 支持 Add Index 语句与其他 DDL 语句并行执行,避免耗时的 Add Index 操作阻塞其他操作 优化 Add Index 的速度,在某些场景下速度大幅提升 支持 select tidb_is_ddl_owner() 语句,方便判断 TiDB 是否为 DDL Owner 支持 ALTER TABLE FORCE 语法 支持 ALTER TABLE RENAME KEY TO 语法 Admin Show DDL Jobs 输出结果中添加表名、库名等信息 支持使用 ddl/owner/resign HTTP 接口释放 DDL Owner 并开启新一轮 DDL Owner 选举 兼容性 支持更多 MySQL 语法 BIT 聚合函数支持 ALL 参数 支持 SHOW PRIVILEGES 语句 支持 LOAD DATA 语句的 CHARACTER SET 语法 支持 CREATE USER 语句的 IDENTIFIED WITH 语法 支持 LOAD DATA IGNORE LINES 语句 Show ProcessList 语句返回更准确信息 PD 可用性优化 引入 TiKV 版本控制机制,支持集群滚动兼容升级 PD 节点间 开启 Raft PreVote,避免网络隔离后恢复时产生的重新选举 开启 raft learner 功能,降低调度时出现宕机导致数据不可用的风险 TSO 分配不再受系统时间回退影响 支持 Region merge 功能,减少元数据带来的开销 调度器优化 优化 Down Store 的处理流程,加快发生宕机后补副本的速度 优化热点调度器,在流量统计信息抖动时适应性更好 优化 Coordinator 的启动,减少重启 PD 时带来的不必要调度 优化 Balance Scheduler 频繁调度小 Region 的问题 优化 Region merge,调度时考虑 Region 中数据的行数 新增一些控制调度策略的开关 完善调度模拟器,添加调度场景模拟 API 及运维工具 新增 GetPrevRegion 接口,用于支持 TiDB reverse scan 功能 新增 BatchSplitRegion 接口,用于支持 TiKV 快速 Region 分裂 新增 GCSafePoint 接口,用于支持 TiDB 并发分布式 GC 新增 GetAllStores 接口,用于支持 TiDB 并发分布式 GC pd-ctl 新增: 使用统计信息进行 Region split 调用 jq 来格式化 JSON 输出 查询指定 store 的 Region 信息 查询按 version 排序的 topN 的 Region 列表 查询按 size 排序的 topN 的 Region 列表 更精确的 TSO 解码 pd-recover 不再需要提供 max-replica 参数 监控 增加 Filter相关的监控 新增 etcd Raft 状态机相关监控 性能优化 优化处理 Region heartbeat 的性能,减少 heartbeat 带来的内存开销 优化 Region tree 性能 优化计算热点统计的性能问题 TiKV Coprocessor 新增支持大量内建函数 新增 Coprocessor ReadPool,提高请求处理并发度 修复时间函数解析以及时区相关问题 优化下推聚合计算的内存使用 Transaction 优化 MVCC 读取逻辑以及内存使用效率,提高扫描操作的性能,Count 全表性能比 2.0 版本提升 1 倍 折叠 MVCC 中连续的 Rollback 记录,保证记录的读取性能 新增 UnsafeDestroyRange API 用于在 drop table/index 的情况下快速回收空间 GC 模块独立出来,减少对正常写入的影响 kv_scan 命令支持设置 upper bound Raftstore 优化 snapshot 文件写入流程避免导致 RocksDB stall 增加 LocalReader 线程专门处理读请求,降低读请求的延迟 支持 BatchSplit 避免大量写入导致产生特别大的 Region 支持按照统计信息进行 Region Split,减少 IO 开销 支持按照 Key 的数量进行 Region Split,提高索引扫描的并发度 优化部分 Raft 消息处理流程,避免 Region Split 带来不必要的延迟 启用 PreVote 功能,减少网络隔离对服务的影响 存储引擎 修复 RocksDB CompactFiles 的 bug,可能影响 Lightning 导入的数据 升级 RocksDB 到 v5.15,解决 snapshot 文件可能会被写坏的问题 优化 IngestExternalFile,避免 flush 卡住写入的问题 tikv-ctl 新增 ldb 命令,方便排查 RocksDB 相关问题 compact 命令支持指定是否 compact bottommost 层的数据 Tools 全量数据快速导入工具 TiDB-Lightning 支持新版本 TiDB-Binlog 升级兼容性说明 由于新版本存储引擎更新,不支持在升级后回退至 2.0.x 或更旧版本 新版本默认开启 raft learner 功能,如果从 1.x 版本集群升级至 2.1 版本,须停机升级或者先滚动升级 TiKV,完成后再滚动升级 PD 从 2.0.6 之前的版本升级到 2.1.0 之前,最好确认集群中是否存在正在运行中的 DDL 操作,特别是耗时的 Add Index 操作 因为 2.1 版本启用了并行 DDL,对于早于 2.0.1 版本的集群,无法滚动升级到 2.1,可以选择下面两种方案: 停机升级,直接从早于 2.0.1 的 TiDB 版本升级到 2.1 先滚动升级到 2.0.1 或者之后的 2.0.x 版本,再滚动升级到 2.1 版本 "}, {"url": "https://pingcap.com/blog-cn/tidb-21-battle-tested-for-an-unpredictable-world/", "title": "TiDB 2.1:Battle-Tested for an Unpredictable World", "content": " TiDB 是由 PingCAP 开发的分布式关系型数据库,今天我们很高兴地推出 TiDB 2.1 正式版,提供更丰富的功能、更好的性能以及更高的可靠性。回顾 2.0 版本 今年 4 月份我们发布了 TiDB 2.0 版本,提升了稳定性、性能以及可运维性,这个版本在接下来的半年中得到了广泛的关注和使用。迄今为止 TiDB 已经在 数百家用户 的生产环境中稳定运行,涉及互联网、游戏、金融、保险、制造业、银行、证券等多个行业,最大集群包含数百个节点及数百 TB 数据,业务场景包含纯 OLTP、纯 OLAP 以及混合负载。另外,既有使用 TiDB 当做关系数据库的场景,也有只用 TiKV 作为分布式 Key Value 存储的场景。这几个月,在这些场景中,我们亲历了跨机房容灾需求、亲历了几十万级别的高吞吐业务、亲历了双十一的流量激增、亲历了高并发点查、高并发写入与上百行复杂 SQL 的混合负载、见到过多次的硬件/网络故障、见到过操作系统内核/编译器的 Bug。简而言之,我们的世界充满了未知,而分布式关系型数据库这样一种应用广泛、功能丰富且非常关键的基础软件,最大的困难就是这些“未知”。在 2.1 版本中,我们引入了不少新的特性来抵御这些未知,适配各种复杂的场景,提升性能和稳定性,帮助我们的用户更好地支撑复杂的业务。新特性 更全面的 Optimizer 在 2.1 版本中,我们对 TiDB 的 Cost-based Optimizer 做了改进,希望这个优化器能够处理各种复杂的 Query,尽量少的需要人工介入去处理慢 SQL。例如对 Index Join 选择索引、外表的优化,对关联子查询的优化,显著地提升了复杂 SQL 的查询效率。当然,除了自动的查询优化之外,2.1 也增加了更多的手动干预机制,比如对 Join 算子的 Hint、Update/Delete 语句的 Hint。用户可以在优化器没有指定合适的计划时,手动干预结果或者是用来确保查询计划稳定。更强大的执行引擎 在 2.1 版本中,我们对部分物理算子的执行效率进行了优化,特别是对 Hash Aggregation 和 Projection 这两个算子进行了并行化改造,另外重构了聚合算子的运行框架,支持向量化计算。得益于这些优化,在 TPC-H 这种 OLAP 的测试集上,2.1 比 2.0 版本有了显著的性能提升,让 2.1 版本更好的面对 HTAP 应用场景。Raft 新特性 在 2.1 版本中,我们引入了 Raft PreVote、Raft Learner、Raft Region Merge 三个新特性: PreVote 是在 Raft Group Member 发起投票之前,预先检查是否能被其他成员所支持,以避免集群中被网络隔离的节点重新接入集群中的时候引发性能抖动,提升集群稳定性。2.1 版本已经支持 PreVote 功能,并默认打开。 Learner 是只同步数据不参与投票的 Raft Group Member。在新加副本的时候,首先增加 Learner 副本,以避免添加副本过程中,部分 TiKV 节点故障引发丢失多数副本的情况发生,以提升集群的安全性。2.1 版本已经支持 Learner 功能,并默认打开。 Region Merge 用于将多个过小的 Region 合并为一个大的 Region,降低集群的管理成本,对于长期运行的集群以及数据规模较大的集群的性能、稳定性有帮助。2.1 版本已经支持 Region Merge 功能,尚未默认打开。 这些新特性的引入,有助于提升存储集群尤其是大规模集群的稳定性和性能。自动更新统计信息 统计信息的及时性对查询计划的正确性非常重要。在 2.1 版本中,我们提供了基于 Query Feedback 的动态增量更新机制。在制定查询计划时,会根据现有的统计信息估算出需要处理的数据量;在执行查询计划时,会统计出真实处理的数据量。TiDB 会根据这两个值之间的差距来更新统计信息,包括直方图和 CM-Sketch。在我们的测试中,对于一个完全没有统计信息的表,经过十轮左右的更新,可以达到统计信息基本稳定的状态。这对于维持正确的查询计划非常重要。除了动态增量更新之外,我们对自动全量 Analyze 也提供了更多支持,可以通过 系统变量 指定做自动 Analyze 的时间段。并行 DDL TiDB 所有的 DDL 操作都是 Online 进行,不过在 2.0 以及之前的版本中,所有的 DDL 操作都是串行执行,即使 DDL 所操作的表之间没有关联。比如在对 A 表 Add Index 时候,想创建一个 B 表,需要等待 Add Index 操作结束。这在一些场景下对用户使用造成了困扰。在 2.1 版本中,我们对 DDL 流程进行拆分,将 Add Index 操作和其他的 DDL 操作的处理分开。由于在 TiDB 的 DDL 操作中,只有 Add Index 操作需要去回填数据,耗时较长,其他的 DDL 操作正常情况下都可以在秒级别完成,所以经过这个拆分,可以保证大多数 DDL 操作能够不需要等待,直接执行。Explain 和 Explain Analyze Explain 对于理解查询计划至关重要,2.1 之前的版本,TiDB 追随 MySQL 的 Explain 输出格式来展示查询计划。但是当 SQL 比较复杂时,MySQL 的格式并不利于展示算子之间的层级关系,不利于用户定位问题。2.1 版本中,我们使用缩进来展示算子之间的层级关系,对每个算子的详细信息也做了优化,希望整个查询计划一目了然,帮助用户尽快定位问题。这篇文档 可以帮助用户了解 TiDB 的查询计划。用户除了通过 Explain 语句查看查询计划之外,在 2.1 版本中还可以通过 Explain Analyze 语句查看语句的运行时信息,包括每个算子运行时的处理时间以及处理的数据量。这样可以通过实际的运行结果,拿到更加精确的信息。热点调度 热点是分布式系统最大的敌人之一,并且用户的业务场景复杂多变,让热点问题捉摸不定,也是最狡猾的敌人。2.1 版本中,我们一方面增强热点检测能力,尽可能详细地统计系统负载,更快的发现热点;另一方面优化热点调度策略,用尽可能小的代价,尽快地打散热点。同时我们也提供了手动分裂 Region 的接口,让用户在特殊场景下将单点瓶颈手动分裂开,再由 PD 进行负载均衡。高效的 GC 机制 2.1 版本对 GC(垃圾回收) 模块进行优化。一方面减少对线上的写入的影响,另一方面加快了空间回收速度。在内部测试场景中,删除一个 1TB 的表,新的 GC 机制能够在 10 秒内回收 99% 左右的空间。更好的性能 OLTP 我们针对 OLTP 场景中,点查占多数的特点进行了针对性的优化。当通过 Unique Key 或者 Primary Key 进行数据访问时,在优化器和执行引擎中都做了改进,使得语句的执行效率更高,通过 2.1 和 2.0 版本的 Sysbench 对比 可以看到,点查性能提升 50%。OLAP 发布 2.0 的时候,我们同时发布了在 TPC-H Scale 50 的场景中 2.0 和 1.0 的对比结果。其中大多数 Query 都有数量级的提升,部分 Query 在 1.0 中跑不出结果,在 2.0 中可以顺利运行。不过对于 Query17 和 Query18,运行时间依然很长。我们在相同的场景下,对 2.1 和 2.0 进行了 对比测试。从下图可以看到(纵坐标是 Query 的响应时间,越低越好),之前的两个慢 Query 的运行时间大幅缩短,其他的 Query 也有一定程度的提升。这些提升一方面得益于查询优化器以及执行引擎的改进,另一方面 得益于 TiKV 对连续数据扫描的性能优化。完善的生态工具 为了让用户更方便的使用 TiDB,我们提供了三个工具: TiDB Lightning 用于将全量数据导入到 TiDB 中,这个工具可以提升全量数据导入速度,目前内部测试场景中,一小时可以导入 100GB 数据。 TiDB Binlog 用于将 TiDB 中的数据更新实时同步到下游系统中,可以用于做主从集群同步或者是将 TiDB 中的数据同步回 MySQL。 TiDB DM(Data-Migration)用于将 MySQL/MariaDB 中的数据通过 Binlog 实时同步到 TiDB 集群中,并且提供 Binlog 数据转换功能,可以将 Binlog 中的表/库名称进行修改,或者是对数据内容本身做修改和裁剪。 上述三个工具可以将 TiDB 和周边的系统打通,既能将数据同步进 TiDB,又可以将数据同步出来。所以无论是迁移、回退还是做数据热备,都有完整的解决方案。Open Source Community 我们相信战胜“未知”最好的武器就是社区的力量,基础软件需要坚定地走开源路线。为了让社区更深入的了解 TiDB 的技术细节并且更好地参与到项目中来,我们今年已经完成超过 20 篇源码阅读文章,项目的设计文档(TiDB 和 TiKV)已经在 GitHub 上面公开出来,项目的开发过程也尽量通过 Github Issue/Project 向社区展示。一些 Feature 设计方案的讨论也会通过在线视频会议的方式方便社区参与进来,这里 可以看到会议安排。从 TiDB 2.0 版发布到现在的半年多时间,TiDB 开源社区新增了 87 位 Contributor,其中 杜川 成为了 TiDB Committer,他已经贡献了 76 次 PR,还有一些活跃的 Contributor 有希望成为下一批 Committer。在这里我们对社区贡献者表示由衷的感谢,希望更多志同道合的人能加入进来,也希望大家在 TiDB 这个开源社区能够有所收获!"}, {"url": "https://pingcap.com/meetup/meetup-81-20181127/", "title": "【Infra Meetup No.81】基于 TiKV 的 Redis 协议兼容层 Titan", "content": "在 Infra Meeutp No.81 上,来自美图的任勇全老师介绍了美图自研的基于 TiKV 的 Redis 协议兼容层—— Titan 的设计和实现思路。以下是现场视频&文字回顾,enjoy~ 视频 | Infra Meetup No.81:基于 TiKV 的 Redis 协议兼容层 Titan PPT 链接 Titan 是美图基于 TiKV 自主研发的 Redis 协议兼容层,通过将 Redis 丰富的数据类型,映射为 TiKV 中的扁平化的 Key-Value,实现了完整兼容 Redis 协议的分布式存储。Titan 创新的应用了浮点数作为下标索引实现了LIST 的插入,通过引入对象 ID,结合 GC 机制,实现了大对象的即时删除。另外,Titan 维护了 GC 和过期列表,通过额外的后台线程实现了数据的删除和主动过期。为了解决数据导入的性能瓶颈,Titan 设计并实现了 ZIPLIST,解决了原始设计 KEY 个数放大严重的问题。最后,任老师简单的介绍了 Titan 是如何实现为多个业务提供虚拟化 Redis 集群的多租户机制的。"}, {"url": "https://pingcap.com/meetup/meetup-82-20181127/", "title": "【Infra Meetup No.82】数据库统计信息的自动挖掘与维护 & What's New in TiDB", "content": " 上海社区小伙伴们又相聚啦!在 Infra Meetup No.82 上,我司 TiDB 核心开发工程师韩飞、技术 VP 申砾为大家带来了干货分享,以下是现场视频&文字回顾,enjoy~数据库统计信息的自动挖掘与维护 视频 | Infra Meetup No.82:数据库统计信息的自动挖掘与维护 PPT 链接 韩飞老师首先介绍了查询优化器的基本架构与执行流程,并重点介绍了统计信息模块在基于代价的优化(CBO)中的重要作用。在谓词选择率估计(Selectivity Estimation)中,常用的属性独立假设(attribute value independence assumption)在列相关(Column Correlation)的场景下会产生较大误差。在此次分享上,韩飞老师重点介绍了贝叶斯网络(Bayesian Networks)的解决方案,针对互相依赖的列,使用贝叶斯模型估计依赖关系,并建立多维直方图是一种非常有效的解决方案。另一个影响选择率估计的因素是统计信息的过期问题,根据查询结果的反馈更新直方图信息是一种行之有效的解决方案,但是通常会引入较大误差。通过引入最大熵原则(Max Entropy Principle)可以相对准确的解决直方图更新的问题,这种方法应用在 Informix 商业数据库中。What’s New in TiDB 视频 | Infra Meetup No.82:What’s New in TiDB PPT 链接 申砾老师介绍了 TiDB 2.1 版本的重要 Feature,包括这些 Feature 所解决的问题、背后的原理、达到的效果,特别是 TiDB 在优化器、计算引擎、存储引擎方面的改进,使得 2.1 版本成为更智能、更迅速、更稳定的数据库。接着,申砾老师展示了部分 Benchmark 结果,分别从 OLAP、OLTP 两个场景表明 TiDB 的性能提升。最后介绍了下一步工作的展望,让大家了解 TiDB 的演进方向。"}, {"url": "https://pingcap.com/weekly/2018-11-26-tidb-weekly/", "title": "Weekly update (November 19 ~ November 25, 2018)", "content": " Weekly update in TiDB Last week, we landed 46 PRs in the TiDB repository.Added Add the http API to get database and table information Propose the table partition design Support creating hash partitioned tables Improved Add missing columns for information_schema.files Impose user authentication for unix socket connections Redesign the trace statement with JSON output Support JSON as the return type for the case expression Preallocate the columns array in MutRow to avoid array resizing Support subqueries in the Do statement Add the memory guard to prevent OOM caused by the plan cache Clean up the plan cache entry in the Deallocate statement Support ADMIN CHECK TABLE/ADMIN CHECK INDEX using specified tidb_snapshot Fixed Fix incorrect privilege check for the UPDATE statement Fix the bootstrap error when the SQL mode is set to ANSI Make sure goroutines of HashJoin exit before its Close returns Remove partition IDs for partition tables in DROP DATABASE Fix the panic caused by EOF when Coprocessor is in the streaming mode Weekly update in TiSpark Last week, we landed 2 PRs in the TiSpark repository.Added Support raw partition Read Weekly update in TiKV and PD Last week, we landed 20 PRs in the TiKV and PD repositories.Added Support interactive Region scan in pd-ctl Add the history index to sync when stream is established Add normal index scan benchmarks Improved Improve Region key print Move unrelated benchmarks into individual directories Check the conf version for split Fixed Fix a deadlock in GetOpInfluence Fix the issue that configurations cannot be set to zero values Fix go mod tidy in PD New contributor (Thanks!) docs-cn: bugwz"}, {"url": "https://pingcap.com/weekly/2018-11-19-tidb-weekly/", "title": "Weekly update (November 12 ~ November 18, 2018)", "content": " Weekly update in TiDB Last week, we landed 54 PRs in the TiDB repository.Added Add a variable max-txn-time-use to make maxTxnTimeUse configurable Improved Validate the variable value when you set tx_isolation Refine HashJoinExec of a specific JoinType for the nil outer/inner case Add SQL type labels to commit and retry logs Delay initializing Txn() to the statements that really need it Adjust the TIDB_INLJ Hint to specify the inner table Support the NO_AUTO_CREATE_USER SQL mode Validate specified values for the enum/set type column in DDL Fixed Clone AggDesc before modifying its Mode in AggDesc.Split Close the child executor before calling ExplainExec.explain.RenderResult() Escape backquotes in identifiers in SHOW CREATE TABLE properly Convert Zero input correctly for column type year Eliminate extra columns introduced by OrderBy upon Union Fix the error caused by different lengths between embedded and outer OrderByItems Consider null for comparing operators in expression rewriting Impose specified OrderBy on the union result of TableDual Use Column.UniqueID in conditionChecker of ranger Weekly update in TiSpark Last week, we landed 3 PRs in the TiSpark repository.Added Add the partition information in TiTableInfo Fixed Fix isolation level settings Weekly update in TiKV and PD Last week, we landed 29 PRs in the TiKV and PD repositories.Added Add a memory BTreeEngine for the local test Support reversing raw_scan and raw_batch_scan in storage Add the ldb argument in tikv-ctl to support the rocksdb command Support more functions in Coprocessor: ToBase64 and FromBase64 Substring2Args and Substring3Args DayName, DayOfMouth, DayOfWeek and DayOfYear Improved Use a block strategy for the file logger Use lifetime to manage the start and the stop of Storage Optimize ingesting snapshots Avoid large Write batch during the GC process of Raft logs Omit some logs about compact log Check the store state before creating operators Fixed Fix the issue about parsing ambiguous time New contributor (Thanks!) tikv: kamijin-fanta"}, {"url": "https://pingcap.com/blog/lease-read/", "title": "How TiKV Uses "Lease Read" to Guarantee High Performances, Strong Consistency and Linearizability", "content": " TiKV, an open source distributed transactional key-value store (also a Cloud Native Computing Foundation member project), uses the Raft consensus algorithm to ensure strong data consistency and high availability. Raft bears many similarities to other consensus algorithms like Paxos in its ability to ensure fault-tolerance and its performance implementations, but generally easier to understand and implement. While our team has written extensively on how Raft is used in TiKV (some examples: Raft in TiKV, the Design and Implementation of Multi-raft, Raft Optimization), one topic we haven’t discussed is when stale read happens due to a brain split within a Raft group, and the old Raft leader is not aware that a new leader is elected, what should we do? This post discusses three different approaches to this problem: Raft Log Read, ReadIndex Read, and Lease Read, and why TiKV adopts the Lease Read approach.Raft Log Read Because Raft is a consensus algorithm that is designed for distributed environments, one way we can resolve the stale read issue is by using the Raft process itself. Inside Raft, we can make the read request go through the Raft log. After the log is committed, we can then read data from the state machine during apply. This approach helps us make sure that the data we read is strongly consistent.However, one significant drawback is each read request needs to go through the entire Raft process, which could lead to severe performance issues, thus very few projects apply this approach.ReadIndex Read ReadIndex Read is a different approach and was originally proposed in the Raft paper. We know that inside Raft, a Region can be in 1 of 3 states: leader, candidate and follower. Any Raft write operation must go through the leader. Only when the leader replicates the corresponding Raft log to a majority of the Regions inside the same Raft group will we consider this write successful.Simply put, as long as the leader really is the leader, then we can directly read data from it. But how do we confirm that fact while handling the read request? Here’s the process that ReadIndex Read will go through: Record its current commit index into the local variable ReadIndex. Send a heartbeat message to other Regions. If the majority of Regions replies with the corresponding heartbeat response, then the leader confirms its state. Wait for the execution of its state machine until the apply index equals to or exceeds ReadIndex. By then, the data can be read consistently. Execute the read request and return the result to the client. You can see that unlike reading through the Raft log, ReadIndex Read uses heartbeats to make leader confirm its state, which is much easier. Although there is still network overhead for the heartbeats, the performance is better than the Raft Log Read.Lease Read In TiKV, we adopt a third and optimized way – Lease Read – another approach introduced in the Raft paper. When the leader sends a heartbeat, it records a timestamp start. When the majority of Regions in the group reply the heartbeat response, we think that the lease validity of the leader can last till start + election timeout / clock drift bound.The Lease Read implementation of TiKV is the same as that in the Raft paper in principle, with some optimizations included. In TiKV, the lease is updated through write operations instead of heartbeats. Since any write operation goes through Raft log, when we propose this write request, we record the current timestamp as start and wait for the corresponding apply before renewing the leader’s lease. A couple of additional implementation details worth noting: The election timeout is 10 seconds by default and we use the fixed max time value of 9 seconds to renew the lease. So even in case of split brain, we can guarantee that the older leader lease is expired when the next leader is elected. We use the monotonic raw clock instead of the monotonic clock. The reason is that the rate of the latter will be influenced by factors like NTP, even though it will not have the time jump back problem. With this approach the response to the client within the lease is initiated by the leader’s state machine. As long as the leader’s state machine is strongly consistent, so will the data that is read from that leader, regardless of when the read occurs."}, {"url": "https://pingcap.com/weekly/2018-11-12-tidb-weekly/", "title": "Weekly update (November 05 ~ November 11, 2018)", "content": " Weekly update in TiDB Last week, we landed 54 PRs in the TiDB repository.Added Provide the ADMIN SHOW NEXT_ROW_ID SQL interface Propose the VIEW design Implement outer join elimination in the logical optimization Improved Distinguish internal/general SQL statements for transaction related metrics Recover the panic in parallel aggregation and projection Add a variable to control the maximum length of the logged query string Add a variable to control whether writing _tidb_rowid is allowed Return an error on check for the missing user Support the plan cache for prepared INSERT/DELETE/UPDATE statements Add a variable to control the slow log threshold Ensure scan of tikv-client is not out of range Fixed Fix the bug of comparison when a row contains NULL Make affected rows of INSERT consistent with MySQL Correct the field name when IfNull is eliminated Fix the unexpected error for ORDER BY in UNION statements Fix a deadlock in the latch scheduler Fix a bug of converting duration to timestamp in the statistics component Return NULL for the values function in the non-INSERT statement Change the default charset and collation from utf8 utf8_bin to utf8mb4 utf8mb4_bin Weekly update in TiSpark Last week, we landed 3 PRs in the TiSpark repository.Added Add resolveLock implementation Fixed Fix the incorrect tso request generated by PDClient Weekly update in TiKV and PD Last week, we landed 28 PRs in the TiKV and PD repositories.Added Support the Time function in Coprocessor Improved Add sanity check for the count field of a request Record start_ts in the write conflict error Add the end_key limit to the kv_scan interface Fixed Fix the issue about the regions/check API Fix ScheduleConfig data race New contributors (Thanks!) tidb: lindali0331tidb-operator: kirinsedocs-cn: bianxindong ppiao "}, {"url": "https://pingcap.com/meetup/meetup-80-20181112/", "title": "【Infra Meetup No.80】从实现角度看 Aurora", "content": "在Infra Meetup No.80 上,我司 TiDB 核心开发工程师、分布式数据库专家姚维老师为大家分享了 Aurora 相关论文,以下是现场视频&文字回顾,enjoy~ 视频 | Infra Meetup No.80:从实现角度看 Aurora PPT 链接 姚维老师根据论文《Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases》,从实现的角度分析了 Aurora 属于哪一种数据库,Aurora 的读操作、写操作分别是怎么维持数据一致性的,以及 Aurora 如何通过实现 “log is the database”,使得它拥有高于 MySQL 几十倍的性能优势,也客观的分析了 Aurora 存在的限制与局限。诚然,Aurora 有其适用的场景,在这个云时代,它作为与 NewSQL 完全不同的思路开拓了一条满足部分云用户需求的道路,但是软件世界里面没有银弹,未来还有很多的挑战需要克服。"}, {"url": "https://pingcap.com/blog-cn/tidb-community-guide-1/", "title": "TiDB 开源社区指南(上)", "content": " 本系列文章旨在帮助社区开发者了解 TiDB 项目的全貌,更好的参与 TiDB 项目开发。大致会分两个角度进行描述: 从社区参与者的角度描述如何更好的参与 TiDB 项目开发; 从 PingCAP 内部团队的角度展示 TiDB 的开发流程,包括版本规划、开发流程、Roadmap 制定等。 希望通过一内一外两条线的描述,读者能在技术之外对 TiDB 有更全面的了解。本篇将聚焦在社区参与者的角度进行描述,也就是“外线”。了解 TiDB 参与一个开源项目第一步总是了解它,特别是对 TiDB 这样一个大型的项目,了解的难度比较高,这里列出一些相关资料,帮助 newcomers 从架构设计到工程实现细节都能有所了解: Overview How we build TiDB TiDB 源码阅读系列文章 Deep Dive TiKV (Work-In-Process) 当然,最高效地熟悉 TiDB 的方式还是使用它,在某些场景下遇到了问题或者是想要新的 feature,去跟踪代码,找到相关的代码逻辑,在这个过程中很容易对相关模块有了解,不少 Contributor 就是这样完成了第一次贡献。 我们还有一系列的 Infra Meetup,大约两周一次,如果方便到现场的同学可以听到这些高质量的 Talk。除了北京之外,其他的城市(上海、广州、成都、杭州)也开始组织 Meetup,方便更多的同学到现场来面基。发现可以参与的事情 对 TiDB 有基本的了解之后,就可以选一个入手点。在 TiDB repo 中我们给一些简单的 issue 标记了 for-new-contributors 标签,这些 issue 都是我们评估过容易上手的事情,可以以此为切入点。另外我们也会定期举行一些活动,把框架搭好,教程写好,新 Contributor 按照固定的模式即可完成某一特性开发。当然除了那些标记为 for-new-contributors 的 issue 之外,也可以考虑其他的 issue,标记为 help-wanted 标签的 issue 可以优先考虑。除此之外的 issue 可能会比较难解决,需要对 TiDB 有较深入的了解或者是对完成时间有较高的要求,不适合第一次参与的同学。当然除了现有的 issue 之外,也欢迎将自己发现的问题或者是想要的特性提为新的 issue,然后自投自抢 :) 。 当你已经对 TiDB 有了深入的了解,那么可以尝试从 Roadmap 上找到感兴趣的事项,和我们讨论一下如何参与。讨论方案 找到一个感兴趣的点之后,可以在 issue 中进行讨论,如果是一个小的 bug-fix 或者是小的功能点,可以简单讨论之后开工。即使再简单的问题,也建议先进行讨论,以免出现解决方案有问题或者是对问题的理解出了偏差,做了无用功。但是如果要做的事情比较大,可以先写一个详细的设计文档,提交到 docs/design 目录下面,这个目录下有设计模板以及一些已有的设计方案供你参考。一篇好的设计方案要写清楚以下几点: 背景知识 解决什么问题 方案详细设计 对方案的解释说明,证明正确性和可行性 和现有系统的兼容性 方案的具体实现  用一句话来总结就是写清楚“你做了什么,为什么要做这个,怎么做的,为什么要这样做”。如果对自己的方案不太确定,可以先写一个 Google Doc,share 给我们简单评估一下,再提交 PR。提交 PR 按照方案完成代码编写后,就可以提交 PR。当然如果开发尚未完成,在某些情况下也可以先提交 PR,比如希望先让社区看一下大致的解决方案,这个时候请将 PR 标记为 WIP。对于 PR 我们有一些要求: 需要能通过 make dev 的测试,跑过基本的单元测试; 必须有测试,除非只是改动文档或者是依赖包,其他情况需要有充足的理由说明没有测试的原因; 代码以及注释的质量需要足够高,这里 有一些关于编码风格和 commit message 的 guide; 请尽可能详细的填写 PR 的描述,并打上合适的 label。 对于 PR 的描述,我们提供了一个模板,希望大家能够认真填写,一个好的描述能够加速 PR 的 review 过程。通过这个模板能够向 reviewers 以及社区讲明白: 这个PR 解决什么问题:相关的问题描述或者是 issue 链接; 如何解决:具体的解决方法,reviewers 会根据这里的描述去看代码变动,所以请将这一段写的尽可能详细且有帮助; 测试的情况; 其他相关信息(如果需要):benchmark 结果、兼容性问题、是否需要更新文档。 最后再说几句测试,正确性是数据库安身立命之本,怎么强调测试都不为过。PR 中的测试不但需要充足,覆盖到所做的变动,还需要足够清晰,通过代码或者注释来表达测试的目的,帮助 reviewer 以及今后可能变动/破坏相关逻辑的人能够容易的理解这段测试。一段完善且清晰的测试也有利于让 reviewer 相信这个 Patch 是正确的。PR review PR review 的过程就是 reviewer 不断地提出 comment,PR 作者持续解决 comment 的过程。每个 PR 在合并之前都需要至少得到两个 Committer/Maintainer 的 LGTM,一些重要的 PR 需要得到三个,比如对于 DDL 模块的修改,默认都需要得到三个 LGTM。Tips: 提了PR 之后,可以 at 一下相关的同学来 review; Address comment 之后可以 at 一下之前提过 comment 的同学,标准做法是 comment 一下 “PTAL @xxx”,这样我们内部的 Slack 中可以得到通知,相关的同学会受到提醒,让整个流程更紧凑高效。 与项目维护者之间的交流 目前标准的交流渠道是 GitHub issue,请大家优先使用这个渠道,我们有专门的同学来维护这个渠道,其他渠道不能保证得到研发同学的及时回复。这也是开源项目的标准做法。无论是遇到 bug、讨论具体某一功能如何做、提一些建议、产品使用中的疑惑,都可以来提 issue。在开发过程中遇到了问题,也可以在相关的 issue 中进行讨论,包括方案的设计、具体实现过程中遇到的问题等等。最后请大家注意一点,除了 pingcap/docs-cn 这个 repo 之外,请大家使用英文。更进一步 当你完成上面这些步骤的之后,恭喜你已经跨过第一个门槛,正式进入了 TiDB 开源社区,开始参与 TiDB 项目开发,成为 TiDB Contributor。如果想更进一步,深入了解 TiDB 的内部机制,掌握一个分布式数据库的核心模块,并能做出改进,那么可以了解更多的模块,提更多的 PR,进一步向 Committer 发展(这里 解释了什么是 Committer)。目前 TiDB 社区的 Committer 还非常少,我们希望今后能出现更多的 Committer 甚至是 Maintainer。从 Contributor 到 Committer 的门槛比较高,比如今年的新晋 Committer 杜川同学,在成为 Committer 的道路上给 tidb/tikv 项目提交了大约 80 个 PR,并且对一些模块有非常深入的了解。当然,成为 Committer 之后,会有一定的权利,比如对一些 PR 点 LGTM 的权利,参加 PingCAP 内部的技术事项、开发规划讨论的权利,参加定期举办的 TechDay/DevCon 的权利。目前社区中还有几位贡献者正走在从 Contributor 到 Committer 的道路上。"}, {"url": "https://pingcap.com/meetup/meetup-79-20181107/", "title": "【Infra Meetup No.79】小米开源 SQL 优化工具 SOAR 技术内幕", "content": "在 Infra Meetup No.79 上,来自小米的李鹏翔老师为大家分享了小米开源的智能 SQL 优化工具——SOAR,并进行了现场 Demo 演示,以下是现场视频 & 文字回顾,enjoy~在过去的几年间,小米互联网业务高速发展,数据库规模也在不断的增长。为了提供稳定、高效的数据库服务,进一步的提高 DBA 工作效率,解放生产力,小米 DBA 基于 Go 语言自主研发了智能 SQL 优化改写工具 SOAR。该工具在内部使用期间效果显著,小米运维部决定将其开源,为开源数据库生态助力。 在 10 月 20 日的开源先锋日(OSCAR)上,小米正式宣布开源自研的 SOAR(SQL Optimizer And Rewriter),开源后两周时间 GitHub 上的 Star 数便超过了 2700。 视频 | Infra Meetup No.79:小米开源 SQL 优化工具 SOAR 技术内幕 PPT 链接 SOAR 是一款智能 SQL 优化和改写工具,开发人员可以直接通过 SOAR 快速的对自己的 SQL 进行质量检查,生成评估报告,防止将问题 SQL 带到线上从而导致服务质量下降。它不仅能够尽可能地提高线上代码质量,还能避免一些由于人为疏漏而带来的隐患。在本期 Meetup 上,李鹏翔老师主要介绍了 SOAR 的基本使用场景和使用方式,介绍了在不同操作系统下如何快速上手 SOAR,并讲解了 SOAR 的常用配置,在现场 Demo 过程中对一些常见问题进行了解答。"}, {"url": "https://pingcap.com/weekly/2018-11-05-tidb-weekly/", "title": "Weekly update (October 29 ~ November 04, 2018)", "content": " Weekly update in TiDB Last week, we landed 49 PRs in the TiDB repository.Added Propose the window function design Introduce a new join reorder framework in the DP-subset algorithm Add a document about online DDL architecture Improved Try PointGetPlan when the plan cache is enabled Make a query plan containing non-deterministic functions cacheable Add validation check when setting the max_allowed_packet system variable Use module of Go 1.11 for dependency management Impose check when adding the foreign key Eliminate ifnull() if the argument can never be null Convert IN subquery to Aggregation with the inner join Fixed Specially handle HOUR in date arithmetic functions Treat _tidb_rowid conditions of index scan as index filters Avoid using columnEvaluator for Projection built by buildProjtion4Union Fix selectivity estimation inaccuracy for non-integer primary key Fix wrong index resolution for PhysicalIndexReader Fix a panic caused by the wrong default value for table information_schema.profiling Fix a panic caused by nil Backoff.vars Fix a panic caused by type mismatch in SHOW COLUMNS Refine ResolveIndices for sequential Projections to avoid panics Fix a panic caused by type mismatch in UPDATE Avoid precision loss when casting JSON to decimal Reset the statement context before PREPARE to avoid panics Change the privilege component to use Auth* portion of identity Weekly update in TiSpark Last week, we landed 5 PRs in the TiSpark repository.Added Support cache table commands Weekly update in TiKV and PD Last week, we landed 5 PRs in the TiKV and PD repositories.Added Support the SubstringIndex function in Coprocessor Improved Make pdctl escape the key before querying the corresponding Region New contributors (Thanks!) tidb: iamzhoug37 imiskolee docs: ppiao docs-cn: exialin tidb-operator: anywhy "}, {"url": "https://pingcap.com/blog/architecture-and-use-cases-of-a-cloud-native-newsql-database/", "title": "TiDB: Architecture and Use Cases of A Cloud-Native NewSQL Database", "content": " TiDB is an open source cloud-native distributed database that handles hybrid transactional and analytical processing (HTAP) workloads–a member of the NewSQL class of databases that’s reinventing how a relational database can be designed, built, and deployed at massive scale. (“Ti” for those of you wondering stands for “Titanium”.) Since PingCAP started building TiDB just three and a half years ago, it has become one of the fastest-growing databases in the world, supported by a strong, vibrant community (at time of writing: 15,000+ GitHub stars, 200+ contributors, 7200+ commits, 2000+ forks). TiDB was recently recognized by InfoWorld’s Bossie Awards 2018, as one of the best open source software projects in the data storage and analytics space.In this article, I’ll walk through the core features and architectural design of TiDB, the three main use cases that TiDB is fulfilling for its 300+ production users, and a preview of its cloud-based product offering.Core Features TiDB’s core features include elastic horizontal scalability, distributed transactions with ACID guarantee, high availability, and real-time analysis on live transaction data. While these terms are commonly thrown out as marketing speaks in the database industry, they are actually difficult technical challenges that our team and community have been working hard on since day 1. Here’s how TiDB’s unique architecture and implementation deliver on these features.The entire TiDB platform has the following components: TiDB: stateless SQL layer that is MySQL compatible, built in Go TiKV: distributed transactional key-value store (now a Cloud Native Computing Foundation (CNCF) project), built in Rust TiSpark: an Apache Spark plugin that works within the TiDB platform and can connect to TiKV or another specialized, columnar storage engine (something we are working on…stay tuned.) Placement Driver (PD): a metadata cluster powered by etcd that manages and schedules TiKV TiDB Platform Architecture How they work together TiKV is the foundational layer, where all the data is persistent, broken up into smaller chunks (we call them “Region”), and automatically replicated and made strongly consistent by executing the Raft consensus protocol. Working with PD, it can replicate data across nodes, datacenters, and geographical locations. It can also dynamically remove hotspots as them form, and split or merge Regions to improve performance and storage usage. We implement range-based sharding inside TiKV instead of hash-based, because our goal from the beginning is to support a full-featured relational database, thus must support various types of Scan operations, like Table Scan, Index Scan, etc.TiDB’s stateless SQL layer handles 100% of your Online Transaction Processing (OLTP) workloads and 80% of the common ad-hoc Online Analytical Processing (OLAP) workloads, with constant performance improvements (see our latest TPC-H benchmarks). This stateless SQL layer leverages the distributed nature of TiKV to execute parallel processing via a Coprocessor layer, by pushing down partial queries to different TiKV nodes simultaneously to retrieve results; that’s why our performance is so good!For more complex OLAP workloads, say constantly iterative analysis for training machine learning models or real-time business intelligence gathering throughout the day, TiSpark fills the void by drawing data directly from TiKV.The interface is good ol’ SQL that many people know, love, and miss. (TiDB speaks MySQL, TiSpark exposes SparkSQL).Modular, Cloud-Native Architecture As you might have noticed, the entire TiDB platform’s design is modularized–all components are in separate code bases and are loosely coupled. A user can deploy the entire TiDB platform as a complete package (most of our users do!), or just parts of it depending on their needs. This modular architecture is intentional: it empowers users to have maximum flexibility and fits perfectly with the standard of a cloud-native architecture (per the CNCF’s official definition of “cloud-native”, cloud-native techniques are ones that “enable loosely coupled systems that are resilient, manageable, and observable”).As a TiDB user, you can scale your stateless SQL server or TiSpark (i.e. your compute resources) out or in independent of scaling TiKV (i.e. your storage capacity), so you can make the most out of the resources you are consuming to fit your workloads. You can almost analogize TiDB stateless SQL servers as a microservice that sits on top of TiKV, which is a stateful application where your data is persisted. This design also makes isolating bugs easier, and rolling upgrades and maintenance quicker and less disruptive.One initial trade-off is some added complexity to deployment and monitoring–there are just more pieces to keep track of. However, with the rise of Kubernetes and the Operator pattern (pioneered by CoreOS), deploying and managing TiDB is simple, straightforward, and increasingly automated.That’s why we built TiDB Operator, and recently open-sourced it, so you can deploy, scale, upgrade, and maintain TiDB in any cloud environment–public, private or hybrid. TiDB installs Prometheus and Grafana by default, so monitoring comes “out of the box”. (Here’s a tutorial on TiDB Operator.) Sample Grafana Dashboard Monitoring a TiDB Deployment Ultimately, flexible scalability for your technical needs is crucial for business success. It’s the difference between becoming the next Facebook versus Friendster. That’s the value that TiDB brings to our users, so they can focus on delivering the best product for their users.And the three main use cases where TiDB shines are: MySQL Scalability, HTAP real-time analytics, and unifying data storage.Download TiDB Subscribe to Blog Use Case 1: MySQL Scalability for explosive business growth Because TiDB speaks MySQL–compatible with both its wire protocol and ecosystem tools like MyDumper and MyLoader–it’s a natural fit for MySQL users who have trouble scaling. To be clear, TiDB is not a replacement of MySQL; it complements MySQL. MySQL is still a great option as a single instance database, so if your data size or workload is small, stick with MySQL. But, as our co-founder and CTO Ed Huang talked about in another post, if you are scratching your heads thinking about the following: Considering how to replicate, migrate, or scale your database for extra capacity; Looking for ways to optimize your existing storage capacity; Getting concerned about slow query performance; Researching middleware scaling solutions or implementing manual sharding policy; That’s the right time to think about using TiDB, which takes care of all these concerns out of the box for you. That’s why Mobike, one of the world’s largest dockless bikesharing platforms, uses TiDB (read case study here). Operating 9 million smart bikes in 200+ cities serving 200 million users, it’s not hard to imagine the scaling bottlenecks their team was experiencing when their business took off like wildfire. Luckily, TiDB came to the rescue and by deploying TiDB along with PingCAP’s suite of enterprise tools like Syncer, which automatically syncs MySQL masters with a TiDB cluster (almost acting like MySQL slaves), the architects and infrastructure engineers at Mobike can now rest easy, knowing that their data management capacity will just grow as the business grows.Another key differentiator between TiDB and other MySQL compatible databases is that we built TiDB’s MySQL layer from scratch, instead of just using a MySQL fork which is what databases like AWS’s Aurora have done. We chose the hard way, because MySQL is a 20-year-plus technology that was never meant for and thus cannot take advantage of the power of a distributed system. For example, MySQL cannot generate query plans that push down partial queries into multiple machines simultaneously to do parallel …"}, {"url": "https://pingcap.com/blog/announcing-tidb-cloud-managed-as-a-service-and-in-the-marketplace/", "title": "Announcing TiDB Cloud, Managed as a Service and in the Marketplace", "content": " Today, we are excited to announce that TiDB Cloud is now available for public preview. It is a suite of product offerings that enables customers to easily use TiDB in any cloud environment, either as a fully-managed service delivered by PingCAP or on a self-served marketplace. TiDB is the first open source, NewSQL, hybrid transactional and analytical processing (HTAP) database in the market that’s available in either public, private, or hybrid cloud setting. TiDB Platform Architecture Product Offerings The TiDB Cloud offerings we are launching today have two options: PingCAP Managed TiDB Cloud (public preview): available on any public or private cloud. This is a comprehensive, fully-managed solution, where the PingCAP cloud team will deliver a full suite of services and enterprise-grade tools to deploy, manage, maintain, backup, and upgrade a TiDB deployment for you. This option is for companies who want to get the most value out of TiDB for their specific use cases, with the least amount of operational overhead and cost. TiDB on GCP Marketplace: available on Google Cloud Platform (GCP) Marketplace using Google Kubernetes Engine (GKE). This is a production-ready standard TiDB deployment that can be launched with just a few clicks on the GCP Marketplace. (We will bring this option to more public cloud marketplaces in the near future.) This option is simple, low-cost, and uses GCP’s persistent disks. Key Features Always Online: With TiDB Cloud, your database is always online, 24 / 7, with no need to schedule downtime for maintenance. While online, it supports rolling upgrades, online DDL, cluster resizing, and automatic data rebalancing with hotspots removal. Flexible Scaling: TiDB has a cloud-native architectural design, where the stateless computing resources (TiDB, TiSpark) and the stateful storage layer (TiKV) are loosely coupled and can be scaled out (or in) depending on your use case and workloads. Thus, in TiDB Cloud, you have the ultimate pay-per-usage control over your database resources. Cross-Cloud Experience: By leveraging Kubernetes and the TiDB Operator, TiDB Cloud delivers a consistent user experience and simple portability between different cloud platforms. There’s no cloud vendor lock-in; total independence. Cloud Native, Customer First As a NewSQL database with a cloud-native architecture, the cloud is TiDB’s natural habitat. In the future, more and more of our services, tools, and core technology innovation will be focused on making TiDB a joy to use in the cloud. As the core team building TiDB from day 1, PingCAP has accumulated a wealth of expertise in architecting, tuning, and operating a distributed database in the cloud. We are ready to deliver that expertise and value to all our customers, from e-commerce platforms to SaaS solutions to social networks. We will work hard to provide a world-class infrastructure solution that will set our customers up to become the next big thing. You can sign up for TiDB Cloud now, and we look forward to delivering TiDB in your favorite cloud environment."}, {"url": "https://pingcap.com/blog-cn/pingcap-university-tidb-dba-plan/", "title": "PingCAP University · TiDB DBA 官方培训认证计划启动", "content": " 伴随着产品的成熟,TiDB 在越来越多样化的应用场景中落地。在落地过程中,大家遇到问题会寻求官方的答疑和支持,但由于咨询量很大,我们有时无法及时响应。因此,为了赋能社区,提升社区用户满意度,避免因测试用户过多官方无法及时响应的问题,同时打造活跃的 TiDB 技术社区,培养熟悉分布式系统、能独立运维 TiDB 的一流 NewSQL 人才,我们宣布正式成立 PingCAP University。PingCAP University 是 PingCAP 官方设立的对企业和个人进行 TiDB 全线产品培训并认证的部门,其培训讲师团队由来自 PingCAP 官方的资深解决方案架构师、顶尖核心技术研发工程师和高级资深 TiDB DBA 组成,拥有丰富且专业的 TiDB 实践经验和培训经验。PingCAP University 也在今天正式启动 TiDB DBA 官方培训认证计划。通过该培训认证计划,大家可以: 深度理解 TiDB 架构、原理及最佳实践,具备独立部署、运维和调优 TiDB 的能力 提升分布式计算和存储领域的技术前沿视野 获得来自 PingCAP 官方的专业技术能力认可,提升个人技术竞争力 培训特色 理论与实践结合,强调动手能力(实践超过 50%),提供累计 4 个半天实战 课程滚动更新,包含大量前沿技术解读及实践分享 TiDB DBA 官方培训认证总览 初级 TiDB DBA:PCTA(PingCAP Certified TiDB Associate)培训及认证 高级 TiDB DBA:PCTP(PingCAP Certified TiDB Professional) 培训及认证 培训及考试安排 PCTA:线上培训及考试 PCTP:线下小班集中培训及考试,时间地点由 PingCAP 统一安排 垂询及报名方式 点击 这里 直接填写报名信息 或联系您的客户总监 或发送邮件至 university-cn@pingcap.com 交流 P.S. 2018 年 11 月 30 日前报名还有【官方技术支持服务礼包】赠送~"}, {"url": "https://pingcap.com/meetup/meetup-78-20181030/", "title": "【Infra Meetup No.78】Bigdata@Bilibili & Chaos Practice in TiDB", "content": " 上周六在上海举办的 Infra Meetup No.78 上,B 站数据平台技术经理薛赵明老师和我司首架唐刘老师带来了精彩分享,以下是视频&文字回顾~《B 站大数据建设实践》 视频 | Infra Meetup No.78 - 薛赵明 - B 站大数据建设实践 PPT 下载链接 本次分享薛赵明老师主要介绍了 B 站在大数据建设方面的历程及不同时期做的选择和其中犯的错误。主要涉及我们在存储、调度、计算、分布式队列方面的一些技术选型。我们在离线存储上采用的还是社区的 HDFS,大数据的 KV 存储上我们尝试了 HBase、ES 等组件,同时对于业务属性分为了 online 和 offline 集群。在随着集群规模的扩大上, namenode 也遇到了不少的挑战,例如内存过大,队列过长、存储空间等方面。调度层选择的 YARN,不过基于该组件我们在外围做了一些保障性的工作,例如队列资源的利用率,自动调整分配,作业执行成功率,提交成功率等。薛赵明,Bilibili(哔哩哔哩)数据平台技术经理计算层区分了批量计算(Hive,MR,Spark)、流式计算(Flink,Spark streaming)、ad-hoc(Presto)、OLAP(Kylin)。平台层提供计算方式,业务方自己选择符合合适的计算场景。消息队列上采用的是 kafka,在 0.10.1.1 这个版本上我们遇见了不少问题,例如 conusmer log skew, produce block , multiple Kafka controllers等。经过近两年的使用,最近计划迁移到最新的 2.0 版本。上层服务上,基于我们的大数据套件,针对不同的用户,我开发了相应的大数据工具和数据产品,例如开发 IDE,报表工具,监控系统,数据交换工具等等。《Chaos Practice in TiDB》 视频 | Infra Meetup No.78 - 唐刘 - Chaos Practice in TiDB PPT 下载链接 构造一个健壮的分布式数据库系统是一件非常困难的事情,因为我们需要做非常多的工作来保证用户数据安全,不允许数据丢失或者损坏。而在 TiDB 里面,我们是通过实践 Chaos Engineering 来保证。在本次分享中,我司首席架构师唐刘首先提出了 Chaos 测试的必要性:“虽然我们有 unit test,integration test 这些,但他们都是有局限性的,为了更好的模拟系统实际的情况,我们需要 Chaos。”唐刘,我司首席架构师那么在 TiDB 里面是如何做 Chaos 的?在这其中有三个关键技术,monitor,fault injection 以及 automation。现场重点讲解了 fault injection,包括进程干扰,网络干扰,文件干扰等,以及一些集群工具。同时介绍了在 TiDB 里面如何将所有这些进行整合,也就是 Schrodinger 平台,通过 Schrodinger,我们能自动化的进行 Chaos test。最后,唐刘老师还为大家介绍了一些 PingCAP 现在的研究方向,譬如使用 TLA+ 来证明我们程序的正确性,以及使用 automating fault injection 来自动的分析系统,进行 fault injection。"}, {"url": "https://pingcap.com/weekly/2018-10-29-tidb-weekly/", "title": "Weekly update (October 22 ~ October 28, 2018)", "content": " Weekly update in TiDB Last week, we landed 52 PRs in the TiDB repository.Added Support ShowStats for the partitioned tables Support := in the set syntax Enable range typed table partition Add the proposal for the column pool Add the log for the binary executed statement Add the slow log for the commit statement Add the proposal of maintaining histograms in the plan Improved Use the Pump client to write binlogs Export the function init() to Init() in the planner package Support _tidb_rowid for table scan range Move the parser package to a separate repository Update the error rate for the partitioned tables in statistics Refine the built-in function truncate to support the uint argument Split unit tests to the corresponding files in the executor/aggfuncs package Add the unit test for the executor/aggfuncs package Use the local feedback for the partitioned tables in statistics Support GC for partition table statistics Make INFORMATION_SCHEMA the first one in show databases Improve the Insert and Update performance for wide tables Use rowDecoder to decode data and evaluate the generated column for AdminCheck Fixed Disable the plan cache for queries containing SubqueryExpr Fix the issue of executing DDL after executing the SQL failure in a transaction Fix the wrong result when the index join operation is performed on union scan Fix a panic of the prepared statement with IndexScan when using the prepared plan cache Fix estimation for the out of range point queries in statistics Clone the schema of projection for different children in the buildProj4Union function Correct the schema after the execution of operations is cancelled halfway Fix the bug that the reassigned partition ID in truncate table does not take effect Weekly update in TiKV and PD Last week, we landed 31 PRs in the TiKV and PD repositories.Added Support more functions in Coprocessor: Trim Conv Support producing an Alpine Linux image Fixed Avoid unsafe mutation for scan_mode Fix data race in PD RaftCluster Improved Add on_stall_conditions_changed to EventListener in TiKV Support adding more schedulers by pd-ctl Add tikv_raftstore_event_duration to metrics Support more server configuration parameters in the PD simulator Support more Region key formats in the pd-ctl detach mode Use the same initial cluster configuration to restart the joined member Support the day format in ReadableDuration New contributors (Thanks!) tikv: killme2008 tidb: HaraldNordgren parastorli yu34po tidb-operator: liufuyang "}, {"url": "https://pingcap.com/blog-cn/tidb-source-code-reading-20/", "title": "TiDB 源码阅读系列文章(二十)Table Partition", "content": " Table Partition 什么是 Table Partition Table Partition 是指根据一定规则,将数据库中的一张表分解成多个更小的容易管理的部分。从逻辑上看只有一张表,但是底层却是由多个物理分区组成。相信对有关系型数据库使用背景的用户来说可能并不陌生。TiDB 正在支持分区表这一特性。在 TiDB 中分区表是一个独立的逻辑表,但是底层由多个物理子表组成。物理子表其实就是普通的表,数据按照一定的规则划分到不同的物理子表类内。程序读写的时候操作的还是逻辑表名字,TiDB 服务器自动去操作分区的数据。分区表有什么好处? 优化器可以使用分区信息做分区裁剪。在语句中包含分区条件时,可以只扫描一个或多个分区表来提高查询效率。 方便地进行数据生命周期管理。通过创建、删除分区、将过期的数据进行 高效的归档,比使用 Delete 语句删除数据更加优雅,打散写入热点,将一个表的写入分散到多个物理表,使得负载分散开,对于存在 Sequence 类型数据的表来说(比如 Auto Increament ID 或者是 create time 这类的索引)可以显著地提升写入吞吐。 分区表的限制 TiDB 默认一个表最多只能有 1024 个分区 ,默认是不区分表名大小写的。 Range, List, Hash 分区要求分区键必须是 INT 类型,或者通过表达式返回 INT 类型。但 Key 分区的时候,可以使用其他类型的列(BLOB,TEXT 类型除外)作为分区键。 如果分区字段中有主键或者唯一索引的列,那么有主键列和唯一索引的列都必须包含进来。 TiDB 的分区适用于一个表的所有数据和索引。不能只对表数据分区而不对索引分区,也不能只对索引分区而不对表数据分区,也不能只对表的一部分数据分区。 常见分区表的类型 Range 分区:按照分区表达式的范围来划分分区。通常用于对分区键需要按照范围的查询,分区表达式可以为列名或者表达式 ,下面的 employees 表当中 p0, p1, p2, p3 表示 Range 的访问分别是 (min, 1991), [1991, 1996), [1996, 2001), [2001, max) 这样一个范围。CREATE TABLE employees ( id INT NOT NULL, fname VARCHAR(30), separated DATE NOT NULL ) PARTITION BY RANGE ( YEAR(separated) ) ( PARTITION p0 VALUES LESS THAN (1991), PARTITION p1 VALUES LESS THAN (1996), PARTITION p2 VALUES LESS THAN (2001), PARTITION p3 VALUES LESS THAN MAXVALUE ); List 分区:按照 List 中的值分区,主要用于枚举类型,与 Range 分区的区别在于 Range 分区的区间范围值是连续的。 Hash 分区:Hash 分区需要指定分区键和分区个数。通过 Hash 的分区表达式计算得到一个 INT 类型的结果,这个结果再跟分区个数取模得到具体这行数据属于那个分区。通常用于给定分区键的点查询,Hash 分区主要用来分散热点读,确保数据在预先确定个数的分区中尽可能平均分布。 Key 分区:类似 Hash 分区,Hash 分区允许使用用户自定义的表达式,但 Key 分区不允许使用用户自定义的表达式。Hash 仅支持整数分区,而 Key 分区支持除了 Blob 和 Text 的其他类型的列作为分区键。 TiDB Table Partition 的实现 本文接下来按照 TiDB 源码的 release-2.1 分支讲解,部分讲解会在 source-code 分支代码,目前只支持 Range 分区所以这里只介绍 Range 类型分区 Table Partition 的源码实现,包括 create table、select 、add partition、insert 、drop partition 这五种语句。create table create table 会重点讲构建 Partition 的这部分,更详细的可以看 TiDB 源码阅读系列文章(十七)DDL 源码解析,当用户执行创建分区表的SQL语句,语法解析(Parser)阶段会把 SQL 语句中 Partition 相关信息转换成 ast.PartitionOptions,下文会介绍。接下来会做一系列 Check,分区名在当前的分区表中是否唯一、是否分区 Range 的值保持递增、如果分区键构成为表达式检查表达式里面是否是允许的函数、检查分区键必须是 INT 类型,或者通过表达式返回 INT 类型、检查分区键是否符合一些约束。解释下分区键,在分区表中用于计算这一行数据属于哪一个分区的列的集合叫做分区键。分区键构成可能是一个字段或多个字段也可以是表达式。// PartitionOptions specifies the partition options. type PartitionOptions struct { Tp model.PartitionType Expr ExprNode ColumnNames []*ColumnName Definitions []*PartitionDefinition } ​ // PartitionDefinition defines a single partition. type PartitionDefinition struct { Name model.CIStr LessThan []ExprNode MaxValue bool Comment string } PartitionOptions 结构中 Tp 字段表示分区类型,Expr 字段表示分区键,ColumnNames 字段表示 Columns 分区,这种类型分区又分为 Range columns 分区和 List columns 分区,这种分区目前先不展开介绍。PartitionDefinition 其中 Name 字段表示分区名,LessThan 表示分区 Range 值,MaxValue 字段表示 Range 值是否为最大值,Comment 字段表示分区的描述。CreateTable Partition 部分主要流程如下: 把上文提到语法解析阶段会把 SQL语句中 Partition 相关信息转换成 ast.PartitionOptions , 然后 buildTablePartitionInfo 负责把 PartitionOptions 结构转换 PartitionInfo, 即 Partition 的元信息。 checkPartitionNameUnique 检查分区名是否重复,分表名是不区分大小写的。 对于每一分区 Range 值进行 Check,checkAddPartitionValue 就是检查新增的 Partition 的 Range 需要比之前所有 Partition 的 Range 都更大。 TiDB 单表最多只能有 1024 个分区 ,超过最大分区的限制不会创建成功。 如果分区键构成是一个包含函数的表达式需要检查表达式里面是否是允许的函数 checkPartitionFuncValid。 检查分区键必须是 INT 类型,或者通过表达式返回 INT 类型,同时检查分区键中的字段在表中是否存在 checkPartitionFuncType。 如果分区字段中有主键或者唯一索引的列,那么多有主键列和唯一索引列都必须包含进来。即:分区字段要么不包含主键或者索引列,要么包含全部主键和索引列 checkRangePartitioningKeysConstraints。 通过以上对 PartitionInfo 的一系列 check 主要流程就讲完了,需要注意的是我们没有对 PartitionInfo 的元数据持久化单独存储而是附加在 TableInfo Partition 中。 add partition add partition 首先需要从 SQL 中解析出来 Partition 的元信息,然后对当前添加的分区会有一些 Check 和限制,主要检查是否是分区表、分区名是已存在、最大分区数限制、是否 Range 值保持递增,最后把 Partition 的元信息 PartitionInfo 追加到 Table 的元信息 TableInfo中,具体如下: 检查是否是分区表,若不是分区表则报错提示。 用户的 SQL 语句被解析成将 ast.PartitionDefinition 然后 buildPartitionInfo 做的事就是保存表原来已存在的分区信息例如分区类型,分区键,分区具体信息,每个新分区分配一个独立的 PartitionID。 TiDB 默认一个表最多只能有 1024 个分区,超过最大分区的限制会报错。 对于每新增一个分区需要检查 Range 值进行 Check,checkAddPartitionValue 简单说就是检查新增的 Partition 的 Range 需要比之前所有 Partition 的 Range 都更大。 checkPartitionNameUnique 检查分区名是否重复,分表名是不区分大小写的。 最后把 Partition 的元信息 PartitionInfo 追加到 Table 的元信息 TableInfo.Partition 中,具体实现在这里 updatePartitionInfo。 drop partition drop partition 和 drop table 类似,只不过需要先找到对应的 Partition ID,然后删除对应的数据,以及修改对应 Table 的 Partition 元信息,两者区别是如果是 drop table 则删除整个表数据和表的 TableInfo 元信息,如果是 drop partition 则需删除对应分区数据和 TableInfo 中的 Partition 元信息,删除分区之前会有一些 Check 具体如下: 只能对分区表做 drop partition 操作,若不是分区表则报错提示。 checkDropTablePartition 检查删除的分区是否存在,TiDB 默认是不能删除所有分区,如果想删除最后一个分区,要用 drop table 代替。 removePartitionInfo 会把要删除的分区从 Partition 元信息删除掉,删除前会做checkDropTablePartition 的检查。 对分区表数据则需要拿到 PartitionID 根据插入数据时候的编码规则构造出 StartKey 和 EndKey 便能包含对应分区 Range 内所有的数据,然后把这个范围内的数据删除,具体代码实现在这里。 编码规则:Key: tablePrefix_rowPrefix_partitionID_rowIDstartKey: tablePrefix_rowPrefix_partitionIDendKey: tablePrefix_rowPrefix_partitionID + 1 删除了分区,同时也将删除该分区中的所有数据。如果删除了分区导致分区不能覆盖所有值,那么插入数据的时候会报错。 Select 语句 Select 语句重点讲 Select Partition 如何查询的和分区裁剪(Partition Pruning),更详细的可以看 TiDB 源码阅读系列文章(六)Select 语句概览 。一条 SQL 语句的处理流程,从 Client 接收数据,MySQL 协议解析和转换,SQL 语法解析,逻辑查询计划和物理查询计划执行,到最后返回结果。那么对于分区表是如何查询表里的数据的,其实最主要的修改是 逻辑查询计划 阶段,举个例子:如果用上文中 employees 表作查询, 在 SQL 语句的处理流程前几个阶段没什么不同,但是在逻辑查询计划阶段,rewriteDataSource 将 DataSource 重写了变成 Union All 。每个 Partition id 对应一个 Table Reader。select * from employees 等价于:select * from (union all select * from p0 where id < 1991 select * from p1 where id < 1996 select * from p2 where id < 2001 select * from p3 where id < MAXVALUE) 通过观察 EXPLAIN 的结果可以证实上面的例子,如图 1,最终物理执行计划中有四个 Table Reader 因为 employees 表中有四个分区,Table Reader 表示在 TiDB 端从 TiKV 端读取,cop task 是指被下推到 TiKV 端分布式执行的计算任务。图 1:EXPLAIN 输出用户在使用分区表时,往往只需要访问其中部分的分区, 就像程序局部性原理一样,优化器分析 FROM 和 WHERE 子句来消除不必要的分区,具体还要优化器根据实际的 SQL 语句中所带的条件,避免访问无关分区的优化过程我们称之为分区裁剪(Partition Pruning),具体实现在 这里,分区裁剪是分区表提供的重要优化手段,通过分区的裁剪,避免访问无关数据,可以加速查询速度。当然用户可以刻意利用分区裁剪的特性在 SQL 加入定位分区的条件,优化查询性能。Insert 语句 Insert 语句 是怎么样写入 Table Partition ?其实解释这些问题就可以了: 普通表和分区表怎么区分? 插入数据应该插入哪个 Partition? 每个 Partition 的 RowKey 怎么编码的和普通表的区别是什么? 怎么将数据插入到相应的 Partition 里面? 普通 Table 和 Table Partition 也是实现了 Table 的接口,load schema 在初始化 Table 数据结构的时候,如果发现 tableInfo 里面没有 Partition 信息,则生成一个普通的 tables.Table,普通的 Table 跟以前处理逻辑保持不变,如果 tableInfo 里面有 Partition 信息,则会生成一个 tables.PartitionedTable,它们的区别是 RowKey 的编码方式: 每个分区有一个独立的 Partition ID,Partition ID 和 Table ID 地位平等,每个 Partition 的 Row 和 index 在编码的时候都使用这个 Partition 的 ID。 下面是 PartitionRecordKey 和普通表 RecordKey 区别。 分区表按照规则编码成 Key-Value pair: Key: tablePrefix_rowPrefix_partitionID_rowIDValue: [col1, col2, col3, col4] 普通表按照规则编码成 Key-Value pair: Key: tablePrefix_rowPrefix_tableID_rowIDValue: [col1, col2, col3, col4] 通过 locatePartition 操作查询到应该插入哪个 Partition,目前支持 RANGE 分区插入到那个分区主要是通过范围来判断,例如在 employees 表中插入下面的 sql,通过计算范围该条记录会插入到 p3 分区中,接着调用对应 Partition 上面的 AddRecord 方法,将数据插入到相应的 Partition 里面。 INSERT INTO employees VALUES (1, 'PingCAP TiDB', '2003-10-15'), 插入数据时,如果某行数据不属于任何 Partition,则该事务失败,所有操作回滚。如果 Partition 的 Key 算出来是一个 NULL,对于不同的 Partition 类型有不同的处理方式: 对于 Range Partition:该行数据被插入到最小的那个 Partition 对于 List partition:如果某个 Partition 的 Value List 中有 NULL,该行数据被插入那个 Partition,否则插入失败 对于 Hash 和 Key Partition:NULL 值视为 0,计算 Partition ID 将数据插入到对应的 Partition 在 TiDB 分区表中分区字段插入的值不能大于表中 Range 值最大的上界,否则会报错 End TiDB 目前支持 Range 分区类型,具体以及更细节的可以看 这里。剩余其它类型的分区类型正在开发中,后面陆续会和大家见面,敬请期待。它们的源码实现读者届时可以自行阅读,流程和文中上述描述类似。"}, {"url": "https://pingcap.com/meetup/meetup-76-20181023/", "title": "【Infra Meetup No.76】列式存储如何进行在线更新", "content": " 视频 | Infra Meetup No.76 - 韦万 - 列式存储如何进行在线更新 PPT 链接 时隔一月,我们又与广州的社区小伙伴们相聚啦~这次是由我司数据库核心研发工程师韦万老师带来的《列式存储如何进行在线更新》主题分享。他首先介绍了 OLAP 场景与 OLTP 的区别,以及为何列式数据库特别适合 OLAP 场景,并介绍了主流的对 OLAP 进行优化的技术。然后进入主题,韦万老师分别列举了目前流行的几种列式数据库的更新方案,包括 SQL Server, Vertica, Kudu 以及 VectorWise, 并分析了它们的优缺点。最后介绍了同学们比较关注的部分,即 TiDB 作为一款 HTAP(Hybrid Transactional/Analytical Processing)数据库,当前的架构以及最新进展(视频中剧透了“神秘武器”——TheFlash)。"}, {"url": "https://pingcap.com/meetup/meetup-77-20181023/", "title": "【Infra Meetup No.77】我司成都分舵第一次 Meetup", "content": "此次成都 Infra Meetup,恰逢我司成都 Office 正式成立,驻成都的所有 PingCAPer 及 Contributor,与到场小伙伴一起让这首场成都 Infra Meetup 充满了庆祝的热烈气氛:一场成都 TiDB 社区小伙伴的线下见面会开始了。崔秋,我司联合创始人在我司联合创始人崔秋做了感谢社区的开场白后,我司技术副总裁申砾老师带来《Deep Dive Into TiDB SQL Layer》的分享,他首先为大家介绍了 TiDB 的整体架构,重点分享了 SQL 层的架构和核心组件,包括 Query Optimizer 和 Execution Engine,并举例说明了其中的实现细节。最后申老师简要介绍了 TiDB 的 Roadmap,鼓励大家通过各种方式参与 TiDB 开源社区里来。(欢迎捞 issue 提 PR 成为 TiDB Contributor,我们会有神秘小礼物相送哦~) 视频 | 申砾-Deep Dive Into TiDB SQL Layer PPT 下载链接 申砾,我司技术副总裁茶歇时间,大家三三两两聚在一起讨论,现场 PingCAPer 很耐心的一一回答大家关于 TiDB 技术细节的问题,现场讨论氛围非常热烈。茶歇过后,马上消费金融 NewSQL 负责人李银龙老师为大家分享了 TiDB 实践经验。 视频 | Infra Meetup No.77 - 李银龙 - 马上消费金融 TiDB 实践分享 李银龙,马上消费金融 NewSQL 负责人李老师首先介绍了马上消费金融上一代数据库解决方案的核心痛点,然后介绍了对 NewSQL 产品的需求要点,以及一些 NewSQL 产品的对比选型,并深入的分享了 TiDB 解决方案体系的细节和最佳实践经验。最后,李老师表达了对 TiDB 云化方案和类 Redis 分布式方案的期待。"}, {"url": "https://pingcap.com/weekly/2018-10-22-tidb-weekly/", "title": "Weekly update (October 15 ~ October 21, 2018)", "content": " Weekly update in TiDB Last week, we landed 60 PRs in the TiDB repository.Added Add the slow log for the Commit statement Support cross-domain requests in the HTTP API Add the PreAlloc4Row and Insert functions for Chunk and List Implement Operand and Pattern of the cascades planner Add the TiDB version to DDL metrics Add a session variable to make Insert compatible with MySQL Add the json_keys built-in function Add an EXPLAIN test for partition pruning Improved Improve the MySQL compatibility by adding the USAGE privilege for the showGrants statement Log more information when OtherError occurs in coprocessor Update the Delta information for the partitioned table in statistics Limit the length of sample values in statistics Refine ColumnPrune for LogicalUnionAll Eliminate if null on the non-null column when building Projection Support Group and GroupExpr for the cascades planner Eliminate projection after aggregation pruning Handle the DDL event for the partitioned table in statistics Refine Explain Analyze Remove the kv.BypassLatch option and enable the latch scheduler by default Refine the error log when the DDL job is canceled Improve the MySQL compatibility for the current_user() built-in function Propagate constants over outer join Support changing null to not null in the alter table statement Fixed Fix an invalid DDL job which makes TiDB panic Fix a panic in limit N when N is too large and overflows an integer Fix a bug in point get Fix date time parsing for the YYYY-MM-DD HH format Maintain DeferredExpr in aggressive constant folding Fix a panic caused by the empty histogram in statistics Fix a panic caused by the empty schema of LogicalTableDual Fix data race when initializing the systemTZ variable Fix the admin check table bug of byte comparing Add the check in DDL when creating a table with a foreign key Fix the histogram boundaries overflow error in statistics Convert DataSource to TableDual only when the empty range is not derived from parameterized constants Weekly update in TiSpark Last week, we landed 2 PRs in the TiSpark repository.Fixed Fix a bug in pyspark in Spark 2.3 that does not initialize a session correctly Weekly update in TiKV and PD Last week, we landed 16 PRs in the TiKV and PD repositories.Added Support more functions in Coprocessor: Exp Radians Add the version information for tikv-ctl Add tikv raftstore event duration to metrics Fixed Fix the cluster test of PD Fix the building failure on Mac OS X Fix WaitGroup data race Improved Remove the PD source code and build binary from the docker image Update pd-ctl commands health and topsize Release TiKV v2.0.8 Use the implicit project layout Export ThreadCollector to use it later Introduce eval_func and eval_func_with helper Extract panic_hook Use go mod to manage dependency New contributor (Thanks!) tidb: tianjiqx"}, {"url": "https://pingcap.com/success-stories/tidb-in-iqiyi/", "title": "Always Fun, Always On: How TiDB Helps iQiyi Deliver Streaming Videos to Its 421 Million Monthly Users", "content": " Industry: Media and EntertainmentAuthor: Boshuai Zhu (Senior Infrastructure Engineer at iQiyi)iQiyi, formerly Qiyi, is the Netflix of China: the country’s largest online video-on-demand platform. With “Always Fun, Always Fine” as our brand’s motto, we are committed to providing users with high-resolution, authentic media content including movies, TV dramas, variety shows, documentaries, animations and travel programs. By the end of 2017, our monthly active users on mobile devices reached 421 million and daily active users hit 126 million; at the end of June 2018, the number of our subscribers jumped to 66 million. On March 29, 2018, our company IPO’ed on the NASDAQ and raised $2.25 billion.Since our business has grown rapidly, our data size has also soared. This has placed enormous pressure on our backend system, especially the MySQL cluster. We experienced the suffocating pain of tackling immense data until we found TiDB, a MySQL-compatible NewSQL hybrid transactional and analytical processing (HTAP) database, built and supported by PingCAP. Finally, we can properly manage our data.Currently, the TiDB cluster is deployed in the internal system of our database cloud team. With the April 2018 release of its 2.0 version, TiDB has proven to be much more mature, with increased system stability and query efficiency. In this post, we will share why we chose TiDB, how we are using it, and the lessons we learned working closely with the PingCAP team.Why We Chose TiDB Before TiDB, MySQL was our main database solution for the production environment. The business developed so quickly that our data size rocketed and many bottlenecks occurred in the MySQL cluster. For example: A standalone MySQL instance only supported a limited data volume. Consequently, we had to delete obsolete data to keep the database running. The number of rows within a table increased continuously, which led to reduced query performance. To support our fast-growing business, we were in urgent need of a database which would be: Horizontally scalable Highly available Compatible with MySQL TiDB checked all of those boxes, and in fact, its performance has exceeded our expectations.What Is TiDB? TiDB is an open source, NewSQL, scalable hybrid transactional and analytical processing (HTAP) database built by the PingCAP team and the open source community. It aims to break down the traditional separation between an OLTP database and an OLAP database, and offer a one-stop solution that enables real-time business analysis on live transactional data. Figure 1: TiDB Platform Architecture Inside the TiDB Platform, there are several components: TiDB cluster consists of stateless TiDB instances and serves as a stateless SQL layer that processes users’ SQL queries, accesses data in the storage layer, and returns corresponding results. It’s MySQL compatible and sits on top of TiKV. TiKV cluster, composed of TiKV instances, is the distributed transactional Key-Value storage layer where the data resides. Regardless of where the data comes from, it is stored in TiKV eventually. It uses the Raft consensus protocol for replication to ensure data consistency and disaster recovery. TiSpark cluster also sits on top of TiKV. It is an Apache Spark plugin that works with the TiDB platform to support complex OLAP queries for BI analysts and data scientists. The TiDB ecosystem also has a wealth of other enterprise-level tools, such as Ansible scripts for quick deployment, Syncer for seamless migration from MySQL, Wormhole for migrating heterogeneous data, and TiDB-Binlog, which is a tool to collect binlog files.How Are We Using TiDB? Scenario 1: TiDB in Risk Monitoring Center The Risk Monitoring Center stores machine security statistics, including the traffic information from different dimensions such as per DC (data center), per IP address, per port, etc. To gain timely insights into the system status, some complex queries are performed by the application from time to time.During the database evaluation process, we compared Apache Druid with TiDB and found: Druid provides SQL support via Apache Calcite adapter; TiDB supports SQL natively. Druid does not support ACID transactions; TiDB guarantees ACID compliance, so the data is accurate and reliable anytime, anywhere. Druid is actually a computing engine that depends on an additional storage engine; data cannot be updated in real time. TiDB as a whole project uses TiKV as its storage engine and provides real-time updating of data. Druid is not flexible in aggregate queries, which matters a great deal for data analysis of iQiyi services; TiDB performs well in aggregate queries and thus offers us a reliable foundation for analyzing data. TiDB is highly compatible with the MySQL protocol. Users can access TiDB via the existing MySQL connection pool component. This translates to low cost for service migration and high efficiency for development. Therefore, we decided to deploy TiDB in our Risk Monitoring Center.Deployment Process The Risk Monitoring Center was the first iQiyi project to use TiDB online in the production environment, so we came up with the following plan: To ensure the absolute data safety, we designed a plan B for the TiDB cluster: We replaced the InnoDB in MySQL with TokuDB for its write capability. Then we deployed MySQL with TokuDB as the slave for the TiDB cluster and synchronize the data in the TiDB cluster with TiDB-Binlog. Although this was not optimal because the MySQL with TokuDB solution cannot handle the data growth in the peak time, we designed this to be the disaster recovery plan regardless of the delay. Deployed an internally developed load balancer on the front end to make full use of the computing capability of multiple TiDB nodes and guarantee the high availability of the TiDB nodes. Deployed Prometheus and Grafana to monitor the TiDB cluster status. Connected Grafana to our internal alert platform to instantly inform the operations team of the alert information via short messages and emails. Issues and Solutions The following issues occurred during our adoption of TiDB, but were quickly resolved.Issue One: Connection timeout.Cause: This issue arose because of the failure to select the optimal query plan due to obsolete statistical information. This is a common problem for relational databases. Two common workarounds are available: to collect statistical information manually or to use hint for plan execution. Both are extra workloads for the application developers.Solution: This issue is fixed in the latest version of TiDB with improved statistics-collecting strategy and auto-analyze.Issue Two: Adding an index in the table took a long time.Causes: The DDL execution information was not updated in time. As a result, we got the obsolete information when checking the DDL operation progress. In some cases, large Regions also took extra time to add indexes. Solution: After we reported the issue to the PingCAP development team, they responded actively and quickly. This issue has been fixed in the latest version of TiDB with the addition of the Batch Split feature for large Regions.TiDB in production After we migrated to TiDB for the Risk Monitoring Center, we successfully upgraded the TiDB cluster and modified the parameters of TiKV nodes. Generally, these operations did not affect the online services.During the upgrade of TiKV nodes, some errors occurred such as “Region is unavailable [try again later]” and “TiKV server timeout.” This was due to the lag of cache information, which is unavoidable in a distributed system. But it does not affect the services as long as the application has a retry mechanism. We are amazed by the fact that no matter how much the data increases (as shown in Figure 2), the response time remains stable, thanks to the automatic Region splitting strategy of TiKV (as shown in Figure 3), the storage layer of TiDB. Tables in TiDB are split automatically to several parts of equal size (96 MB by default but …"}, {"url": "https://pingcap.com/blog-cn/linearizability-and-raft/", "title": "线性一致性和 Raft", "content": " 在讨论分布式系统时,共识算法(Consensus algorithm)和一致性(Consistency)通常是讨论热点,两者的联系很微妙,很容易搞混。一些常见的误解:使用了 Raft [0] 或者 paxos 的系统都是线性一致的(Linearizability [1],即强一致),其实不然,共识算法只能提供基础,要实现线性一致还需要在算法之上做出更多的努力。以 TiKV 为例,它的共识算法是 Raft,在 Raft 的保证下,TiKV 提供了满足线性一致性的服务。本篇文章会讨论一下线性一致性和 Raft,以及 TiKV 针对前者的一些优化。线性一致性 什么是一致性,简单的来说就是评判一个并发系统正确与否的标准。线性一致性是其中一种,CAP [2] 中的 C 一般就指它。什么是线性一致性,或者说怎样才能达到线性一致?在回答这个问题之前先了解一些背景知识。背景知识 为了回答上面的问题,我们需要一种表示方法描述分布式系统的行为。分布式系统可以抽象成几个部分: Client Server Events Invocation Response Operations Read Write 一个分布式系统通常有两种角色,Client 和 Server。Client 通过发起请求来获取 Server 的服务。一次完整请求由两个事件组成,Invocation(以下简称 Inv)和 Response(以下简称 Resp)。一个请求中包含一个 Operation,有两种类型 Read 和 Write,最终会在 Server 上执行。说了一堆不明所以的概念,现在来看如何用这些表示分布式系统的行为。上图展示了 Client A 的一个请求从发起到结束的过程。变量 x 的初始值是 1,“x R() A” 是一个事件 Inv 意思是 A 发起了读请求,相应的 “x OK(1) A” 就是事件 Resp,意思是 A 读到了 x 且值为 1,Server 执行读操作(Operation)。如何达到线性一致 背景知识介绍完了,怎样才能达到线性一致?这就要求 Server 在执行 Operations 时需要满足以下三点: 瞬间完成(或者原子性) 发生在 Inv 和 Resp 两个事件之间 反映出“最新”的值 下面我举一个例子,用以解释上面三点。例:先下结论,上图表示的行为满足线性一致。对于同一个对象 x,其初始值为 1,客户端 ABCD 并发地进行了请求,按照真实时间(real-time)顺序,各个事件的发生顺序如上图所示。对于任意一次请求都需要一段时间才能完成,例如 A,“x R() A” 到 “x Ok(1) A” 之间的那条线段就代表那次请求花费的时间,而请求中的读操作在 Server 上的执行时间是很短的,相对于整个请求可以认为瞬间,读操作表示为点,并且在该线段上。线性一致性中没有规定读操作发生的时刻,也就说该点可以在线段上的任意位置,可以在中点,也可以在最后,当然在最开始也无妨。第一点和第二点解释的差不多了,下面说第三点。反映出“最新”的值?我觉得三点中最难理解就是它了。先不急于对“最新”下定义,来看看上图中 x 所有可能的值,显然只有 1 和 2。四个次请求中只有 B 进行了写请求,改变了 x 的值,我们从 B 着手分析,明确 x 在各个时刻的值。由于不能确定 B 的 W(写操作)在哪个时刻发生,能确定的只有一个区间,因此可以引入上下限的概念。对于 x=1,它的上下限为开始到事件“x W(2) B”,在这个范围内所有的读操作必定读到 1。对于 x=2,它的上下限为 事件“x Ok() B” 到结束,在这个范围内所有的读操作必定读到 2。那么“x W(2) B”到“x Ok() B”这段范围,x 的值是什么?1 或者 2。由此可以将 x 分为三个阶段,各阶段”最新”的值如下图所示:清楚了 x 的变化后理解例子中 A C D 的读到结果就很容易了。最后返回的 D 读到了 1,看起来是 “stale read”,其实并不是,它仍满足线性一致性。D 请求横跨了三个阶段,而读可能发生在任意时刻,所以 1 或 2 都行。同理,A 读到的值也可以是 2。C 就不太一样了,C 只有读到了 2 才能满足线性一致。因为 “x R() C” 发生在 “x Ok() B” 之后(happen before [3]),可以推出 R 发生在 W 之后,那么 R 一定得读到 W 完成之后的结果:2。一句话概括:在分布式系统上实现寄存器语义。实现线性一致 如开头所说,一个分布式系统正确实现了共识算法并不意味着能线性一致。共识算法只能保证多个节点对某个对象的状态是一致的,以 Raft 为例,它只能保证不同节点对 Raft Log(以下简称 Log)能达成一致。那么 Log 后面的状态机(state machine)的一致性呢?并没有做详细规定,用户可以自由实现。Raft Raft 是一个强 Leader 的共识算法,只有 Leader 能处理客户端的请求,集群的数据(Log)的流向是从 Leader 流向 Follower。其他的细节在这就不赘述了,网上有很多资料 [4]。In Practice 以 TiKV 为例,TiKV 内部可分成多个模块,Raft 模块,RocksDB 模块,两者通过 Log 进行交互,整体架构如下图所示,consensus 就是 Raft 模块,state machine 就是 RocksDB 模块。Client 将请求发送到 Leader 后,Leader 将请求作为一个 Proposal 通过 Raft 复制到自身以及 Follower 的 Log 中,然后将其 commit。TiKV 将 commit 的 Log 应用到 RocksDB 上,由于 Input(即 Log)都一样,可推出各个 TiKV 的状态机(即 RocksDB)的状态能达成一致。但实际多个 TiKV 不能保证同时将某一个 Log 应用到 RocksDB 上,也就是说各个节点不能实时一致,加之 Leader 会在不同节点之间切换,所以 Leader 的状态机也不总有最新的状态。Leader 处理请求时稍有不慎,没有在最新的状态上进行,这会导致整个系统违反线性一致性。好在有一个很简单的解决方法:依次应用 Log,将应用后的结果返回给 Client。这方法不仅简单还通用,读写请求都可以这样实现。这个方法依据 commit index 对所有请求都做了排序,使得每个请求都能反映出状态机在执行完前一请求后的状态,可以认为 commit 决定了 R/W 事件发生的顺序。Log 是严格全序的(total order),那么自然所有 R/W 也是全序的,将这些 R/W 操作一个一个应用到状态机,所得的结果必定符合线性一致性。这个方法的缺点很明显,性能差,因为所有请求在 Log 那边就被序列化了,无法并发的操作状态机。这样的读简称 LogRead。由于读请求不改变状态机,这个实现就显得有些“重“,不仅有 RPC 开销,还有写 Log 开销。优化的方法大致有两种: ReadIndex LeaseRead ReadIndex 相比于 LogRead,ReadIndex 跳过了 Log,节省了磁盘开销,它能大幅提升读的吞吐,减小延时(但不显著)。Leader 执行 ReadIndex 大致的流程如下: 记录当前的 commit index,称为 ReadIndex 向 Follower 发起一次心跳,如果大多数节点回复了,那就能确定现在仍然是 Leader 等待状态机至少应用到 ReadIndex 记录的 Log 执行读请求,将结果返回给 Client 第 3 点中的“至少”是关键要求,它表明状态机应用到 ReadIndex 之后的状态都能使这个请求满足线性一致,不管过了多久,也不管 Leader 有没有飘走。为什么在 ReadIndex 只有就满足了线性一致性呢?之前 LogRead 的读发生点是 commit index,这个点能使 LogRead 满足线性一致,那显然发生这个点之后的 ReadIndex 也能满足。LeaseRead LeaseRead 与 ReadIndex 类似,但更进一步,不仅省去了 Log,还省去了网络交互。它可以大幅提升读的吞吐也能显著降低延时。基本的思路是 Leader 取一个比 Election Timeout 小的租期,在租期不会发生选举,确保 Leader 不会变,所以可以跳过 ReadIndex 的第二步,也就降低了延时。 LeaseRead 的正确性和时间挂钩,因此时间的实现至关重要,如果漂移严重,这套机制就会有问题。Wait Free 到此为止 Lease 省去了 ReadIndex 的第二步,实际能再进一步,省去第 3 步。这样的 LeaseRead 在收到请求后会立刻进行读请求,不取 commit index 也不等状态机。由于 Raft 的强 Leader 特性,在租期内的 Client 收到的 Resp 由 Leader 的状态机产生,所以只要状态机满足线性一致,那么在 Lease 内,不管何时发生读都能满足线性一致性。有一点需要注意,只有在 Leader 的状态机应用了当前 term 的第一个 Log 后才能进行 LeaseRead。因为新选举产生的 Leader,它虽然有全部 committed Log,但它的状态机可能落后于之前的 Leader,状态机应用到当前 term 的 Log 就保证了新 Leader 的状态机一定新于旧 Leader,之后肯定不会出现 stale read。写在最后 本文粗略地聊了聊线性一致性,以及 TiKV 内部的一些优化。最后留四个问题以便更好地理解本文: 对于线性一致中的例子,如果 A 读到了 2,那么 x 的各个阶段是怎样的呢? 对于下图,它符合线性一致吗?(温馨提示:请使用游标卡尺。;-P) Leader 的状态机在什么时候没有最新状态?要线性一致性,Raft 该如何解决这问题? FollowerRead 可以由 ReadIndex 实现,那么能由 LeaseRead 实现吗? 如有疑问或想交流,欢迎联系我:shentaining@pingcap.com[0].Ongaro, Diego. Consensus: Bridging theory and practice. Diss. Stanford University, 2014.[1].Herlihy, Maurice P., and Jeannette M. Wing. “Linearizability: A correctness condition for concurrent objects.” ACM Transactions on Programming Languages and Systems (TOPLAS) 12.3 (1990): 463-492.[2].Gilbert, Seth, and Nancy Lynch. “Brewer’s conjecture and the feasibility of consistent, available, partition-tolerant web services.” Acm Sigact News 33.2 (2002): 51-59.[3].Lamport, Leslie. “Time, clocks, and the ordering of events in a distributed system.” Communications of the ACM 21.7 (1978): 558-565.[4].https://raft.github.io/"}, {"url": "https://pingcap.com/blog/tidb-academy-announce/", "title": "Launching TiDB Academy, First Course-“Distributed Database with TiDB for MySQL DBAs”", "content": "Author: Morgan Tocker, Senior Product and Community ManagerTiDB AcademyToday, we are excited to launch TiDB Academy, a series of technical training courses and certifications on TiDB and distributed databases in general, taught by our senior technical team. At PingCAP, we firmly believe it’s our responsibility to deliver transparency and share knowledge to our customers and community, not just handing you a mysterious “black box” to use. As the world of database technology moves toward a cloud-native, NewSQL direction, learning how to properly use distributed databases like TiDB will become increasingly important and valuable for IT professionals; that’s why we built TiDB Academy.These courses are designed to help practitioners and professionals – DBAs, DevOps, System Architects – understand the architecture, design choices, strengths, and trade-offs of TiDB, from the team who built it. I’m the instructor for the first course, called “Distributed Database with TiDB for MySQL DBAs”. We chose this topic first because, as a MySQL-compatible NewSQL database, TiDB’s most common use case is enhancing the productivity and complementing the capabilities of MySQL DBAs. Thus, during this course you will find me often explaining TiDB concepts by using a direct comparison to MySQL, leveraging my own 15-plus years of MySQL product experience and the expertise of other PingCAP engineers, many of whom were former MySQL DBAs themselves.This self-paced course is full of hands-on exercises and labs, so you can get your fingers dirty and your mind working, as you explore the inner workings of TiDB. Taking the course is free, and there’s a certification exam you can take for a small fee to validate your newly acquired skill as a TiDB and distributed database expert! This course is available now to enroll (https://pingcap.com/tidb-academy/), and the first certification exam will be on December 1, 2018.As a teaser, you will learn (among many other topics): Architectural Design: how TiDB’s different components support hybrid transactional and analytical processing (HTAP) workloads; Horizontal Scaling: how TiDB can elastically scale while using its transactional model to keep data strongly consistent and highly available; Online DDL: how TiDB’s DDL algorithm and implementation makes online schema change a breeze. If you have any questions about this first course, or the TiDB Academy in general, you can reach our teaching staff anytime: academy@pingcap.com. Happy learning!"}, {"url": "https://pingcap.com/meetup/meetup-75-20181016/", "title": "【Infra Meetup No.75】这次我们聊了聊 Google F1 的最新论文", "content": " 上周六我们在「新根据地」举办了第 75 期 Infra Meetup,由于是在新办公室举行的第一场 Meetup,到场的小伙伴都收到了特别福利——社区 T 恤一件~很多小伙伴还好奇参观了一圈我们的工作环境。“你们公司为什么要这么大的会议室啊?” ——要为定期举办的社区交流活动准备空间呀~不过照本期火爆程度,这个空间恐怕不久就不够用了 haha言归正传,在本期 Meetup 上,我司 CTO 黄东旭从最新的论文出发,结合 Google F1 团队在今年 VLDB 上的演讲内容,为大家分享了 F1 Query 的架构原理。以下是文字 & 视频回顾,enjoy~ 视频回顾 视频 | Infra Meetup No.75 - F1 Query: Declarative Querying at Scale PPT 链接 Google 在今年的 VLDB 上发布了 F1 的新进展(《F1 Query: Declarative Querying at Scale》),距离 Google 的上一篇 F1 论文已经过去 5 年了。2013 年论文《F1: A Distributed SQL Database That Scales》中的 F1 是基于 Spanner 的,主要提供 OLTP 服务,而新的 F1 的定位则是大一统:旨在处理 OLTP/OLAP/ETL 等多种不同的 workload。本期 Meetup,东旭主要分享了 F1 Query 以下几个重要的设计点,从不同角度详细介绍了 F1 Query 的架构原理。首先,F1 Query 是面向多数据中心的高可用设计的,它并不太关心