背景
我们使用MySQL存储了FriendFeed的所有数据。数据库随着用户基数的增长而增长了很多。现在已经存储了超过2.5亿条记录与一堆涵盖了从评论和“喜欢”到好友列表的其他数据。
随着数据的增长,我们也曾迭代地解决了随着如此迅猛的增长而带来的扩展性问题。我们的尝试很有代表性,例如使用只读mysql从节点和memcache来增加读取吞吐量,对数据库进行分片来提高写入吞吐量。然而,随着业务的增长,添加新功能比扩展既有功能以迎合更多的流量变得更加困难。
特别的,对 schema 做改动或为超过 1000-2000 万行记录的数据库添加索引会将数据库锁住几个小时。删除旧索引也要占用这么多时间,但不删除它们会影响性能;因为数据库要持续地在每个INSERT上读写这些没用的区块,并将重要的区块挤出了内存。为避免这些问题需要采取一些复杂的措施(例如在从节点上设置新的索引,然后将从节点与主节点对调),但这些措施会引发错误并且实施起来比较困难,它们阻碍了需要改动 schema/索引才能实现的新功能。由于数据库的严重分散,MySQL 的关系特性(如join)对我们没用,所以我们决定脱离 RDBMS。
虽然已有许多用于解决灵活 schema 数据存储和运行时构建索引的问题(例如 CouchDB)的项目。但在大站点中却没有足够广泛地用到来说服人们使用。在我们看到和运行的测试中,这些项目要么不稳定,要么缺乏足够的测试(参见这个有点过时的关于 CouchDB 的文章)。MySQL 不错,它不会损坏数据;复制也没问题,我们已经了解了它的局限。我们喜欢将 MySQL 用于存储,仅仅是非关系型的存储。
几经思量,我们决定在 MySQL 上采用一种无模式的存储系统,而不是使用一个完全没接触过的存储系统。本文试图描述这个系统的高级细节。我们很好奇其他大型网站是如何处理这些问题的,另外也希望我们完成的某些设计会对其他开发者有所帮助。
综述
我们在数据库中存储的是无模式的属性集(例如JSON对象或python字典)。存储的记录只需一个名为id的16字节的UUID属性。对数据库而言实体的其他部分是不可见的。我们可以简单地存入新属性来改变schema(可以简单理解为数据表中只有两个字段:id,data;其中data存储的是实体的属性集)。
我们通过保存在不同表中的索引来检索数据。如果想检索每个实体中的三个属性,我们就需要三个数据表-每个表用于检索某一特定属性。如果不想再用某一索引了,我们要在代码中停止该索引对应表的写操作,并可选地删除那个表。如果想添加个新索引,只需要为该索引新建个MySQL表,并启动一个进程异步地为该表添加索引数据(不影响运行中的服务)。
新闻热点
疑难解答