今天讨论了一个传统的问题,问题自己比较简单,就是针对key-list类型的数据,如何优化方案作到性能与成本的tradeoff。Key-list在用户类型的产品中很是广泛,如一个用户的好友关系 {“uid”:{1,2,3,4,5}},一条微博下面的评论id列表,一个用户发表的微博id列表等。mysql
根据经验,在大部分场景下,单个业务的list数据长度99%在1000条如下,但剩下的1%的数据可能多达100万条,所以在实现方案的时候不能忽视这些超大数据集的问题,这也体现了常说的80%+的时间在优化20%-的功能。sql
List数据访问模型常见的有两种方式
1. 扶梯方式
扶梯方式在导航上一般只提供上一页/下一页这两种模式,部分产品甚至不提供上一页功能,只提供一种“更多/more”的方式,也有下拉自动加载更多的方式,在技术上均可以概括成扶梯方式。缓存
扶梯方式在技术实现上比较简单及高效,根据当前页最后一条的偏移日后获取一页便可。写成SQL可能相似服务器
SELECT * FROM LIST_TABLE WHERE id > offset_id LIMIT n;
2. 电梯方式
另一种数据获取方式在产品上体现成精确的翻页方式,如1,2,3……n,同时在导航上也能够由用户输入直达n页。国内大部分场景采用电梯方式,但电梯方式在技术实现上相对成本较高。
在MySQL中,一般提到的b-tree,在存储引擎实现上,一般都是b+tree,如图nosql
从图中能够看到,使用电梯方式时候,当用户指定翻到第n页时候,并无直接方法寻址到该位置,而是须要从第一楼逐个count,scan到count*page时候,获取数据才真正开始,因此致使效率不高。而在数据存在新增及删除的状况下,偏移量也不能有效的缓存,一个10万条的list,只要有一条变化,原先的楼层可能会所有发生变化。post
以上描述的场景属于单机版本,在数据规模较大时候,互联网系统一般使用分库的方式来保存,实现方法更为复杂。
在面向用户的产品中,数据分片一般会将同一用户的数据存在相同的分区,以便更有效率的获取当前用户的数据。以下图所示性能
但上述方案在常见的场景中存在很大不足,大部分产品用户只访问最近产生的数据,历史的数据只有极小的几率被访问到,所以同一个区域内部的数据访问是很是不均匀,如图中2014年生成的属于热数据,2012年之前的属于冷数据,只有极低的几率被访问到。所以简单的解决方案是按时间远近将数据进行进一步分区,如图。大数据
注意在上图中使用时间方式sharding以后,在一个时间分区内,也须要用前一种方案将数据进行sharding,由于一个时间片区一般也没法用一台服务器容纳。优化
上面的方案较好的解决了具体场景对于key list访问性能及成本的tradeoff,可是它存在如下不足ui
- 数据按时间进行滚动没法全自动,须要较多人为介入或干预
- 数据时间维度须要根据访问数据及模型进行精巧的设计,没法简单重用
- 为了实现电梯直达功能,须要增长额外的二级索引,好比2013年某用户总共有多少条记录
因为以上问题,尤为是二级索引的引入,显然它不是理想中的key list实现,后文继续介绍适合长尾翻页key list设计的一些思路及尝试。