奥林近5年前成立。由Ruby开发仪库制定的单一产品(Olery信誉)成为一组不同的产品和许多不同的应用程序,因为它们通过了许多不同的应用。今天,我们不仅是作为产品的声誉,还具有奥雷弗雷德,酒店评价数据API,小部件,可以嵌入网站和更多产品/服务不久的将来。
当涉及到申请量时,我们也会大大增加。今天我们部署了超过25种不同的应用程序(所有Ruby),其中一些是界面(轨道或SINATRA),但大多数是后台处理应用程序。
虽然我们对到目前为止我们所达到的东西非常自豪地,但是在黑暗中总是潜伏的潜伏:我们的主要数据库。从Olerywe的开始,有一个数据库设置,涉及MySQL的MySQL,用于储存评论和类似数据的重要数据(用户,合同等)和MongoDB(基本上我们可以在数据丢失的情况下轻松检索数据)。虽然这个设置很好,最初我们开始遇到各种各样的问题,因为我们的成长,不包括MongoDB。其中一些问题是由于与数据库的应用程序协议的方式,有些问题是由于数据库本身。
例如,在某个时间点,我们必须从MongoDB中删除大约一百万个文档,然后稍后重新插入它们。这个过程的结果是数据库的近几个小时的近锁定,导致了对抗性能。直到我们执行数据库修复(使用蒙版的ReveionDatabase命令)。由于数据库的大小,此修复本身也占用了完成。
在另一个例子中,我们注意到我们的应用程序的性能劣化,并追溯到追溯到我们的MongoDB集群。然而,在进一步检查我们无法找到问题的实际原因。无论安装METRICSWE,我们使用的工具或命令我们都找不到原因。在我们替换群体的初始返回到正常的群集的初始之前,ITWAS不是。
这些只是两个例子,我们在随着时间的推移时有许多情况。在这里,Thecore问题不仅仅是我们的数据库表演,而且我们还考虑到它,绝对没有迹象表明是什么都是为了解决问题。
另一个我们面临的核心问题是MongoDB(或任何其他概要存储引擎)的基本特征之一:缺乏模式。缺乏芦荟清马可能听起来很有意思,在某些情况下,它肯定会有它的束缚。然而,对于许多方法的使用情况,可以引发隐式模式的问题。这些模式没有由您的StorageEngine定义,而是根据应用程序行为和期望来定义。
例如,您可能有一个页面集合,其中应用程序期望具有类型字符串的标题字段。在这里,架构非常出现,尽管未明确定义。如果数据的结构是随着时间的推移的,则这是有问题的,特别是如果没有将旧数据迁移到新结构(在Smancealess Storage发动机中非常有问题)。 forexample,假设您有以下Ruby代码:
这将适用于具有返回字符串的标题字段的每个文档。这将突破使用不同字段名称(例如post_title)或根本没有标题的字段。要处理此类ACase,您需要调整代码如下:
另一种处理方式是在模型中定义架构。例如,Mongoid是一个受欢迎的MongoDB ODM for Ruby,让您这样做。然而,当使用这样的工具定义模式时,应该想知道为什么不在数据库本身中定义架构。这样做会解决另一个问题:重新使用。如果您只有单个应用程序,那么在代码中定义aschema并不是一个很大的交易。但是,当你有很多缺陷时,这很快就会成为一个大混乱。
概要存储引擎承诺通过删除担心架构来使您的生活更轻松。实际上,这些系统只是使其成为确保数据一致性的所有权。在某些情况下,这可能会锻炼,但我愿意为大多数人打赌,这将只有反馈。
这为我带来了一个好的数据库的要求,更具体地说,奥雷尔有。涉及系统,尤其是数据库,Wevalue以下内容:
一致性很重要,因为它有助于确定对系统的清晰期望。 IFData始终以某种方式存储,然后使用此数据的系统变得很多。如果在数据库级别需要某个字段,则无法检查此类字段的应用程序。一个数据库也应该是ableto保证完成某些操作,即使在高压下也没有比点开数据更令人沮丧,因为它不会在几分钟之后暂停。
可见性适用于两件事:系统本身以及将其纳入它的容易程度。如果一个系统不端行为,它应该易于调试。反过来,IFA用户想要查询数据,这也应该很容易。
正确性意味着系统按预期行为。如果某个字段作为数字值isdefined,则应该不能将文本插入字段中.Mysql臭名昭着的是,因为它允许您完成那个,并且随着结果,您可以最终与虚假数据结束。
可扩展性不仅适用于性能,还适用于财务方面,并且系统可以通过时间处理更改的要求。一个系统的变得非常好,但不是大量金钱的成本,或者根据它来阻碍系统的开发周期。
考虑到上述价值,我们向MongoDB寻找替代品。上面指出的高价值通常是传统RDBMS的核心特征集,并且Sowe将我们的眼睛放在两种候选人身上:MySQL和PostgreSQL。
MySQL是我们已经将其使用它的第一个候选人,因为我们已经为一些严重的数据提供了一些关键数据。然而,MySQL并非没有问题。例如,将一个字段作为int(11),您可以愉快地插入文本数据,MySQLWill尝试转换它。一些例子:
mysql>创建表格示例(`Number` int(11)不是null);查询OK,0行影响(0.08秒)MySQL>插入示例(数字)值(10);查询OK,1行受影响(0.08秒)mysql>插入示例(数字)值(' Wat');查询OK,1行受影响,1个警告(0.10秒)mysql>插入示例(数字)值('这10个废话和#39;);查询确定,1行受影响,1个警告(0.14秒)mysql>插入示例(数字)值(' 10 a');查询确定,1行受影响,1条警告(0.09秒)mysql>选择*从示例; + -------- + |号码| + -------- + | 10 || 0 || 0 || 10 | + -------- + + 4行集(0.00秒)
值得注意的是,MySQL将在这些情况下发出警告。然而,由于警告只是警告,他们经常(如果不是几乎总是总是)忽略了。
MySQL的另一个问题是任何表修改(例如,添加列)将导致表格锁定以供阅读和写入。这意味着使用这种表的任何操作都必须等到修改后完成。对于具有大量数据的表格,这可能需要数小时才能完成,可能导致应用程序停机时间。这具有索引公司此类Assoudcloud来开发诸如LHM等工具来处理此功能。
考虑到上面,我们开始研究PostgreSQL。 PostgreSQL MySQL没有。例如,您无法插入文本Datainto一个数字字段:
Olery_development =#创建表示例(number int not null);创建rabentolery_development =#插入到示例(数字)值(10);插入0 1oleRy_development =#插入到示例(数字)值(' wat') ;错误:整数的输入语法无效:" wat"第1行:插入示例(数字)值(' wat'); ^ oleery_development =#插入到示例(数字)值('这10个废话和#39;);错误:整数的输入语法:"这10个废话和#34;第1行:插入进入示例(数量)值('这10个nonsen ... ^ oleery_development =#插入到示例(数字)值(' 10 a');错误:整数的输入语法无效: " 10 A"第1行:插入示例(数字)值(' 10 a');
PostgreSQL还具有以各种方式更改表格的功能,无需锁定每次操作。例如,添加一个未使用默认值的列,可以设置为NULL可以快速完成,无锁整个表。
PostgreSQL Suchas还提供各种其他有趣的功能:Trigram基于索引和搜索,全文搜索,支持QueryingJSON,支持查询/存储键值对,PUB /子支持等。
最后,我们决定与PostgreSQL定居,以便在我们关心的各种科目之间提供平衡。将整个平台从MongoDB迁移到巨大不同数据库的过程并不容易。为了缓解切换过程,我们将在大约3个步骤中突破此过程:
更新依赖MongoDB以使用PostgreSQL的所有应用程序,而不是需要重构来支持此操作。
在我们甚至考虑迁移我们所需的所有数据之前,我们需要运行测试最终数据的小子集。如果你知道即使是一小块的数据,那么迁移就没有意义会给你带来很多麻烦。
虽然存在可以处理此操作的现有工具,但我们也必须转换数据(例如,被重命名的字段,类型不同,等等),并如此为此编写自己的工具。这些工具大多是一次性的Rubyscript,每个工具都执行了特定的任务,例如移动过度评论,清理编码,纠正主键序列等。
初始测试阶段没有揭示任何可能阻止分组过程的问题,尽管Oudata的某些部分存在一些问题。例如,某些用户提交的内容并非总是被编码,并且由于不得不进行清除,因此无法导入结果。需要的其他有趣的更改正在从全名(“荷兰语”,“英语”中更改为浏览的语言名称语言代码作为OURNEW情绪分析堆栈使用语言代码而不是全名。
到目前为止,在更新申请时花在更新的应用程序中,特别是那些对MongoDB的聚合框架大量的人。抛出一些具有低测试覆盖率的遗留轨道应用,并且您拥有几周的工作。更新这些应用程序的过程基本上如下:
对于非Rails应用程序,我们在使用续集时解决了续集,而我们追溯到Activerecord用于我们的Rails应用程序(至少现在)。续集是令人醒来的数据库工具包,支持我们可能想要使用的大多数(如果不是全部)PostgreSQL SpecialFeatures。与Activerecord相比,它的查询构建DSL也是我们的推动力,尽管它有时会有点冗长。
例如,假设您想计算有多少用户使用某个LocalAlong,其中每个区域设置的百分比(相对于整个集合)。在PlainsQL中,这样的查询可能如下所示:
#!sqlselect语言环境,计数(*)为金额,(count(*)/ sum(count(*))over())* 100.0用localeorder百分比代表的用户组成百分比;
区域设置|金额|百分比-------- + -------- + ----------------------- 2779 | 85.193133047210300429000 nl | 386 | 11.83231146535867566000它| 40 | 1.226241569589209074000 de | 25 | 0.766400980993255671000 ru | 17 | 0.521152667075413857000 | 7 | 0.214592274678111588000 fr | 4 | 0.122624156958920907000 ja | 1 | 0.030656039239730227000 AR-AE | 1 | 0.030656039239730227000 eng | 1 | 0.030656039239730227000 ZH-CN | 1 | 0.030656039239730227000(11行)
续集允许您使用普通Ruby在没有串片段的情况下使用普通Ruby(astiverecord通常需要)编写上述查询:
#!rubystar = sequel.lit(' *')user.select(:locale).select_append {count(star).as(:computa)} .select_append {((star)/ sum (计数(星))。over)* 100.0).as(:百分比)} .group(:locale).order(sequel.desc(:百分比))
如果您不喜欢使用sequel.lit(' *')您也可以使用以下语法:
#!rubyuser.select(:locale).select_append {count(用户。*)。作为(:金额)} .select_append {(count(用户。*)/ sum(count(用户。*)计数(count(用户。)。结束)* 100.0).as(:百分比)} .group(:locale).order(sequel.desc(:百分比))
虽然可能有点冗长,但这两个查询都使得重新使用它们更容易,而无需诉诸字符串连接。
在未来,我们也可能将我们的Rails应用程序转移到续集上,但是,如果这是值得的时间和精力,但是如果值得的时间和努力,那么我们的铁路轨道就会如此紧密耦合。
这最终为我们带来了迁移生产数据的过程。 Athereare基本上是两种方式:
关闭整个平台并在所有数据已被迁移后在线备份。
选项1有一个明显的下行:停机。选项2另一方面不会停机,但可能很难处理。例如,在此功能中,您必须考虑在您丢失数据的情况下使用数据时添加的任何数据。
幸运的是,奥林有一个相当独特的设置,因为它对我们的大多数写作操作仅以相当规则的间隔发生。经常(例如用户和合同信息)的数据是一个相当少量的数量,这意味着与我们的审核数据相比迁移的时间越来越少。
迁移用户,合同的关键数据,基本上所有的数据都无法以任何方式丢失。
重新迁移步骤1的数据,确保在与此同时创建的数据不会丢失。
步骤2到迄今为止,大约24小时的时间迈出了最长。另一方面,迁移步骤1和5中提到的数据只花了大约45分钟。
自从我们完成迁移并迄今为止,我们已经完成了几个月前几个月前。到目前为止的影响甚至是积极的,甚至导致了甚至导致了腹部申请的性能急剧增加。例如,我们的酒店评论数据API(正在运行Onsinatra)最终有甚至较低的响应时间,而不是Fhen Fhen Factor:
迁移发生在1月21日,大峰值只是进行硬重启的拍摄性(导致过程中稍微较慢的响应)。在第21次之后,平均响应时间为近一半。
另一种案例,我们看到表现的大幅增加是我们称之为“审查私人”。此应用程序(作为守护程序运行)具有相当简单的素质:保存审核数据(评论,评论,评分等等)。虽然我们最新着原地,但对迁移的迁移时,迈出了一些非常大的变化,结果是结果非常有益:
与审查普遍存在的差异并不像极端,但由于该折叠者只使用数据库检查存在审查(相对速度),这不是很令人惊讶。
最后暂整安排刮擦进程的应用程序(简称“调度程序”):
由于调度程序仅以某些间隔运行,因此图形是一个小型Bithard来理解,但迁移后的平均处理时间内有明显的下降。
最后,我们到目前为止,我们对结果非常满意,我们肯定不会MongoDB。性能很大,围绕它的植物围绕着比较和查询数据的工具更令人愉快(特别是对于非开发人员)更令人愉快。虽然我们有一个服务(OleryFeedback)仍然使用MongoDB(尽管是一个单独的,而不是一个单独的,而不是一个渺小的聚集),我们打算在将来迁移到PostgreSQL。