Instagram技术文章导读系列-2

在上一篇提到Instagram开发团队在面临大量资料时,将其分成shard并透过postgres的schema来将资料进行储存。在2012年时Ig每秒接收到90个like的储存请求,而短短不到一年的时间,Ig就要面临每秒超过1万个赞的储存请求到postgres中。面临针对DB的大量请求,Ig开发团队提出了几点可以优化的方向:

  • 部分索引(Partial Indexes)
  • 函式索引(Functional Indexes)
  • 使用pg_reorg 进行表压缩(pg_reorg For Compaction)
  • 归档和备份(WAL-E for WAL archiving and backups)
  • psycopg2的自动提交模式与非同步模式(Autocommit mode and async mode in psycopg2)
  • Partial Indexes:

    如果经常根据特定条件过滤查询,且这些条件只存在于少数行时,部分索引可以带来很大的效能提升。通过建立部分索引,根据文中的查询需求案例中可以让需要处理15000行资料、耗时215ms的查询语句变成只需处理169行与耗时3ms

    个人观点:要找出这样的筛选逻辑,需要观察常常出现在where的条件以及最花时间的查询并且条件能够有效过滤掉大部分不需要的数据。建立索引的同时,定期观察资料范围,更新索引并删除不必要的索引

    Functional Indexes:

    如果要索引的资料是很长的字符串(如64字符的base64令牌),可以只索引字符串的一小部分当然这样还是会筛出很多符合前缀的资料,但是此时要做后续的筛选速度就会很快。在保持相对较好的查询性能时,同时将索引大小减少到原本的1/10

    pg_reorg For Compaction:

    现在pg_reorg已经没有在维护,取而代之的是pg_repack,但这边只说他的核心概念。postgres的MVCC(Multi-Version Concurrency Control)机制可能会造成资料膨胀,并且插入资料的顺序跟我们理想的查询状况可能是不相同的。例如理想中我想要查有关特定用户的资料,但是插入资料时不可能该用户的资料刚好都储存在一起,但是相关资料散布在磁盘各处,表碎片化的问题会导致查询的浪费,可以透过几个步骤来做表的压缩

  • 对需要压缩的表格建立一个排他锁,主要是阻止建立索引与修改表格
  • 建立日誌表与触发器,这段时间对原始表格的操作透过触发器储存到日誌表
  • 建立新表(资料与旧表相同),但是利用order by来控制资料排序
  • 将日誌表的内容应用到新表
  • 新旧表进行替换
  • 要做以上操作时一定要注意剩余空间是否足够,而关于锁的细节文章中就没有特别展开了,可能需要深入了解pg_repack机制

    WAL-E for WAL archiving and backups:

    Ig开发团队将postgres预写日誌档透过Heroku工具包中的WAL-E进行归档,WAL-E将PG伺服器产生的WAL档案存在Amazon S3。透过将WAL档与基本备份结合使用,在修复故障或是转移时可以将资料库还原到备份以后的任意时间点

    Autocommit mode and async mode in psycopg2:

    因为使用Django这个python框架做开发,因此使用更多基于python驱动引擎的psycopg2。而postgres具备transaction特性,所以许多操作具备BEGIN/COMMIT来维持一制性。而在许多只读操作中,就可以设置autocommit mode来伺服器与资料库的通讯量,减轻CPU压力

    另一个psycopg2的实用功能是能够注册wait_callback以支援协程。在先前的文章提到Ig团队透过postgres的schema将资料进行分布式储存,因此这样的fan-out查询方式比起传统轮流查询每个shard的方式会节省更多时间。同时使用协程也能够帮助建立多节点的socket通讯(python中的select套件),并且也能搭配像gevent这个支援协程的python套件

    总结这篇文章中Ig团队透过几个面向来进行资料库的优化与维持稳定性:

  • 优化索引与减少查询范围来提升效率
  • 定期压缩表格来减少资料膨胀与优化资料分布
  • 透过WAL档与资料备份来增加系统稳定
  • 透过自动提交与使用协程来增进查询效率