Citymobil-创业公司在增长中提高稳定性的指南。 第2部分。事故类型是什么?



这是有关Citymobil如何提高服务稳定性的系列文章的第二篇(您可以在此处阅读第一篇)。 在本文中,我将深入研究事故分析的细节。 但是在此之前,我将介绍我必须事先考虑的一点,并在第一篇文章中进行介绍,但是我没有考虑过。 我从读者的反馈中学到了什么。 第二篇文章给了我消除这种烦人的缺陷的机会。

0.序幕


一位读者提出了一个非常公平的问题:“出租车服务的后端有什么困难?” 这个问题很好。 去年夏天,我问自己自己,然后开始在Citymobil工作。 然后我想到了“想一想,一辆出租车,一个带有三个按钮的应用程序”。 可能有什么复杂的呢? 但是事实证明,这是一项非常高科技的服务,也是一项复杂的产品。 为了至少清楚地说明它的含义以及它真正是一个巨大的技术巨无霸,我将讨论Citymobil产品活动的几个方面:

  • 定价。 定价团队会随时随地处理价格问题。 通过根据统计数据和其他数据预测供需平衡来确定价格。 所有这些都基于机器学习提供了庞大,复杂且不断发展的服务。
  • 定价。 实施各种付款方式,出行后附加费的逻辑,在银行卡上预扣资金,计费,与合作伙伴和驾驶员互动。
  • 订单分配。 将销售订单分配到哪台机器? 例如,就增加行程次数而言,最接近的分配选项不是最佳选择。 一个更正确的选择是比较客户和汽车,以使出行次数最大化,因为在这种情况下该特定客户有取消订单的可能性(因为花费很长的时间),并且该驾驶员取消或破坏了订单(因为花费的时间太长或太低)检查)。
  • 地理位置 与地址,着陆点的搜索和摘要,调整交付时间有关的所有事情(我们的合作伙伴,卡的供应商和交通拥堵并不总能在考虑到交通拥堵的情况下提供有关ETA的准确信息),从而提高了正向和反向地理编码的准确性,从而提高了机器的准确性。 基于机器学习的数据很多工作,分析很多,服务也很多。
  • 反欺诈。 乘客和驾驶员出行价格的差异(例如,短途出行)为试图窃取我们钱财的欺诈者创造了经济诱因。 打击欺诈在某种程度上类似于在电子邮件服务中打击垃圾邮件-完整性和准确性很重要。 有必要阻止欺诈者的最大数量(完整性),但不应将良好的用户误认为欺诈者(准确性)。
  • 司机的动力。 驾驶员动机团队致力于开发与各种动机相关的一切事物,这些事物与驾驶员增加驾驶员对我们平台的使用和忠诚度有关。 例如,进行X次旅行,并为此获得额外的Y卢布。 或者购买Z卢布的班次,而无需支付佣金。
  • 后端驱动程序应用程序。 订单清单,需求图(向驾驶员提供最大化收益的提示),prokidyvaniya状态更改,带有驾驶员的通讯系统等等。
  • 客户端应用程序的后端(这可能是最明显的部分,并且通常是出租车的后端所理解的):下订单,滚动有关更改订单状态的状态,确保汽车在订单和交货时在地图上的移动,后端提示和等

这就是冰山一角。 功能更多。 用户友好的界面隐藏了冰山的巨大水下部分。

现在回到事故。 在事故历史的六个月中,我们汇总了以下分类:

  • 发布不良,第500个错误;
  • 发布不佳,代码欠佳,基础负载;
  • 对系统的手动干预未成功;
  • 复活节彩蛋
  • 外部原因;
  • 发布不佳,功能受损。

下面,我将写下我们对最常见的事故类型得出的结论。

1.发行错误,第500个错误


我们几乎所有的后端都是用PHP编写的,PHP是一种弱类型的解释语言。 您偶然推出了代码,并且由于类或函数名称错误而崩溃。 这只是出现第500个错误时的一个示例。 如果代码中出现逻辑错误,它也可能出现; 舔错了分支; 不小心删除了带有代码的文件夹; 在代码中保留了测试所需的临时工件; 没有按照新代码更改表的结构; 没有重新启动或停止必要的cron脚本。

我们分几个阶段依次解决了这个问题。 由于释放不良导致的行程损失显然与使用时间成正比。 也就是说,我们必须尽力确保尽可能少地发布不良发布。 开发过程中的任何更改如果将使不良版本投入使用的平均时间减少至少1秒钟,对企业都是有利的,因此需要实施。

不良释放或任何生产事故通常会经历两个状态,我们称之为“被动阶段”和“主动阶段”。 被动阶段是我们尚未意识到事故的时刻。 活跃阶段是我们已经知道的时候。 事故始于被动阶段,随着时间的流逝,一旦发现事故,事故便进入主动阶段-我们开始与之抗争:首先我们进行诊断,然后进行修复。

为了减少生产中任何事故的持续时间,有必要减少被动和主动阶段的平均持续时间。 不良发布也是如此,因为它本身就是一种意外。

我们开始分析我们目前的事故修复过程。 在分析开始时,我们遇到的不良释放导致空闲(全部或部分)平均空闲20-25分钟。 被动阶段通常需要15分钟,主动阶段需要10分钟。 在被动阶段,联系中心开始处理用户投诉,并且在达到一定阈值后,联系中心投诉了Slack中的一般聊天。 有时,其中一名员工抱怨无法订购出租车。 员工投诉为我们发出了一个严重问题的信号。 从不良的发行版过渡到活动的阶段后,我们开始诊断问题,分析最新的发行版,各种图表和日志以确定事故原因。 在找出原因之后,如果错误发布是最后一次被泵出,或者发生新的具有错误发布的回滚,则我们回退了代码。

这是一个处理不良版本的过程,我们必须改进。

1.1。 被动阶段减少


首先,我们注意到,如果发布不佳并伴有500个错误,那么我们可以毫无疑问地理解发生了问题。 幸运的是,所有第500个错误都记录在New Relic(这是我们使用的监视系统之一)中,仅保留了SMS和IVR通知中有关“五百”某特定频率超出的错误(随着时间的推移,阈值不断降低)。

这导致了这样一个事实:事故的活跃阶段,例如“不良释放,第500个错误”,在释放后几乎立即开始。 发生事故时的过程看起来像这样:

  1. 程序员部署代码。
  2. 释放会导致事故(大规模500s)。
  3. 短信到达。
  4. 程序员和管理员开始理解(有时不是立即,而是在2-3分钟后:SMS可能会延迟,手机上的声音可能会关闭,并且一天之内无法显示SMS后立即采取的措施)。
  5. 事故的活动阶段开始,持续与之前相同的10分钟。

因此,被动阶段从15分钟减少到3分钟。

1.2。 进一步减少被动阶段


尽管将被动阶段减少到了3分钟,但即使是短暂的被动阶段也比主动阶段更让我们感到困扰,因为在主动阶段我们已经做了一些解决问题的工作,并且在被动阶段,服务无法全部或部分工作,而是“男人不知道。”

为了进一步减少被动阶段,我们决定在每次发布后牺牲三分钟的开发时间。 这个想法非常简单:您展开代码,并查看三分钟内的New Relic,Sentry和Kibana,看是否有500个错误。 一旦您发现那里的问题,便会先验地假定它与您的代码有关,并且您开始了解。

我们根据统计数据选择了三分钟:有时问题在图表上出现的时间为1-2分钟,但从未超过三分钟。

此规则记录在“做与不做”中。 起初它并不总是执行,但是开发人员逐渐习惯了基本的卫生习惯:早上刷牙也浪费时间,但是您需要这样做。

结果,被动阶段减少到了1分钟(有时计划还很晚)。 令人惊讶的是,这同时减少了活动阶段。 毕竟,开发人员遇到了状况良好的问题,并准备立即回滚其代码。 尽管这并不总是有帮助,因为 该问题可能是由于并行发布其他人的代码而引起的。 但是,尽管如此,活动阶段平均减少到了5分钟。

1.3。 活动阶段的进一步减少


对于被动阶段的一分钟或多或少感到满意,我们开始考虑主动阶段的进一步减少。 首先,我们关注了问题的历史(这是建立稳定性的基石!),并且发现在许多情况下,我们不立即回滚,因为我们不了解要回滚到哪个版本,因为存在许多并行发行版。 为了解决此问题,我们引入了以下规则(并将其记录在do和not的记录中):在发布之前,您在Slack中写聊天记录,您打算做什么以及做什么,并且在发生事故的情况下将您写给聊天记录``意外,请勿滚动!''。 此外,我们开始通过SMS自动报告有关发布的事实,以通知未参加聊天的人。

这个简单的规则大大减少了事故发生时已经释放的次数,并将活动阶段从5分钟减少到3分钟。

1.4。 活动阶段的减少更大


尽管我们在聊天室中警告过所有发行版和崩溃的事实,但有时还是出现了竞争情况-一个关于发行版的文章,而另一个当时已经发布了; 或事故开始时,他们在聊天中写了这个,有人刚推出了新代码。 这些情况延长了诊断时间。 为了解决这个问题,我们实现了自动禁止并行发布。 这个想法很简单:每次发布之后,CI / CD系统都禁止所有人在接下来的5分钟内推出新版本,除了上一个版本的作者(以便他可以在必要时滚动或滚动修补程序)和一些特别有经验的开发人员(在紧急情况下)。 此外,CI / CD系统禁止在事故发生期间(即,从收到事故开始的通知之日起至收到其完成的通知之时)推出。

这样,过程就变成了这样:开发人员推出,监视图表三分钟,然后再过两分钟,没人能推出任何东西。 如果有问题,则开发人员回滚该版本。 该规则从根本上简化了诊断,主动和被动阶段的总持续时间从3 +1 = 4分钟减少到1 +1 = 2分钟。

但是两分钟的事故很多。 因此,我们继续优化流程。

1.5。 自动崩溃检测和回滚


我们很久以来一直在考虑如何减少因释放不良而导致的事故持续时间。 他们甚至试图强迫自己查看tail -f error_log | grep 500 tail -f error_log | grep 500 。 但是最后,他们都选择了基本的自动解决方案。

简而言之,这是自动回滚。 我们设置了一个单独的Web服务器,在该服务器上,负载均衡器的负载是其他Web服务器上负载的10倍。 CI / CD系统将每个版本自动部署到该单独的服务器(我们将其称为preprod,尽管尽管名称如此,但实际用户的实际负载却在那儿)。 然后自动化做了tail -f error_log | grep 500 tail -f error_log | grep 500 。 如果在一分钟内未发生第500个错误,则CI / CD会在生产中部署新代码。 如果出现错误,则系统立即回滚所有内容。 同时,在平衡器级别,所有在preprod上完成500个错误的请求都被复制到其中一个生产服务器。

该措施将第500个发行版的影响降低为零。 同时,如果发生自动化错误,我们三分钟都没有取消该规则来监视图表。 这些都是关于不良版本和第500个错误的。 我们进行下一类事故。

2.发布不良,代码欠佳,基本负载


我将立即从此类事故的具体示例开始。 我们进行了优化:在测试中,在生产中加快了短查询的速度,我们在SQL查询中添加了USE INDEX ,但长查询却变慢了。 仅在生产中才注意到长查询的速度变慢。 结果,漫长的请求流使整个主机库花费了一个小时。 我们彻底了解USE INDEX工作原理,并在do&&not的文件中对其进行了描述,并警告开发人员请勿滥用。 我们还分析了查询,并意识到它主要返回历史数据,这意味着它可以在单独的副本上运行以进行历史查询。 即使此副本处于负载状态,业务也不会停止。

在此事件之后,我们仍然遇到类似的问题,并决定在某个时候系统地解决该问题。 我们使用频繁的梳理扫描了整个代码,并在不影响服务质量的情况下将所有可以在其中进行的请求执行到副本。 同时,我们根据关键程度对副本本身进行了划分,以使其中任何一个副本的崩溃都不会停止服务。 结果,我们得出了一个具有以下基础的体系结构:

  • 掌握基础(用于写操作和对数据新鲜度至关重要的查询);
  • 生产副本(用于对数据新鲜度不太重要的短查询);
  • 用于计算价格比率的副本,即所谓的激增定价。 此副本可能会滞后30-60秒-这不是很关键,系数不会经常变化,如果该副本掉下来,服务将不会停止,只是价格不会与供需平衡完全一致;
  • 业务用户和联系中心管理面板的副本(如果下降,主业务将不会增加,但是支持将无法工作,并且我们将无法临时查看和更改设置);
  • 用于分析的许多副本;
  • MPP数据库用于根据历史数据进行完整切片的大量分析。

这种架构为我们提供了更多的增长空间,并且由于次优的SQL查询而使崩溃数量减少了一个数量级。 但是她仍然远非理想。 计划进行分片,以便您可以扩展更新和删除以及对这些数据的更新至关重要的短查询。 MySQL的安全范围不是无限的。 很快,我们将需要以塔兰工具为形式的重型火炮。 在以下文章中将对此有要求!

在使用非最佳代码和请求进行试验的过程中,我们意识到以下几点:最好在发行前而不是发行后消除所有非最佳性。 这样可以减少发生事故的风险,并减少开发人员在优化上花费的时间。 因为如果代码已经被下载并且上面有新版本,那么优化起来就困难得多。 因此,我们引入了强制性代码检查以确保最佳性。 它由最有经验的开发人员(实际上是我们的特种部队)进行。

此外,我们开始收集在现实生活中可以使用的最佳代码优化方法,以下列出了这些方法。 请不要将这些做法视为绝对真理,也不要试图盲目地重复自己的做法。 每种方法仅对特定情况和特定业务有意义。 仅在此处给出它们,以使细节清楚:

  • 如果SQL查询不依赖当前用户(例如,驾驶员的需求图指示最小行程和多边形的系数),则此查询必须由cron以一定频率进行(在我们的情况下,每分钟一次就足够了)。 将结果写入生产代码中已使用的缓存(Memcached或Redis)。
  • 如果SQL查询处理的积压对业务而言不是很关键的数据,则应将其结果缓存一些TTL(例如30秒)。 然后在后续请求中从缓存中读取。
  • 如果在处理Web请求的上下文中(在我们的示例中,是在PHP中实现特定服务器方法的上下文中)想要进行SQL查询,则需要确保此数据未与其他任何SQL查询“到达” (以及它们是否会通过代码进一步发展)。 访问高速缓存也是如此:如果您愿意,它也可以被请求充斥,因此,如果数据已经从高速缓存中“到达”,那么您就不必转到高速缓存中去了,就可以从家中取走。
  • 如果要在Web上进行查询处理,并且要调用任何函数,则需要确保不会在其内脏项中进行额外的SQL查询或缓存访问。 如果无法避免调用此函数,则需要确保不能对其进行修改或破坏其逻辑,以免对数据库/缓存进行不必要的查询。
  • 如果仍然需要使用SQL,则需要确保不能将代码中必要的字段添加到代码中已经存在的查询的上方或下方。

3.系统中的手动干预失败


此类事故的示例:失败的ALTER(导致数据库超载或导致副本滞后)或失败的DROP(在MySQL中遇到错误,在删除新表时阻止了数据库); 对手工错误的主人提出的大量要求; 尽管我们认为服务器已无法使用,但我们在负载下对服务器执行了工作。

为了最大程度地减少由于这些原因造成的跌倒,不幸的是,有必要每次都了解事故的性质。 我们尚未找到一般规则。 再次尝试这些示例。 说,在某些时候,喘振系数停止工作(它们乘以旅行价格在需求增加的地点和时间)。 原因是在数据库副本上,用于计算系数的数据来自该数据库副本,Python脚本工作了,它耗尽了所有内存,副本副本掉了。 该脚本已经运行了很长时间,为方便起见,它在副本上运行。 通过重新启动脚本解决了问题。 结论如下:不要在具有数据库的机器上运行第三方脚本(记录在do和don's中,否则这是一个空白镜头!),监视具有副本的机器上的内存不足,并在内存快用完时通过SMS发出警报。

始终得出结论,不要陷入轻松的境地,“他们看到了问题,解决了问题,却忘记了它”,这一点非常重要。 只有得出结论,才能建立优质的服务。 此外,SMS警报非常重要-它们将服务质量设置为比其更高的水平,防止其下降并进一步提高可靠性。 作为每个稳定状态下的登山者,他会站起来并被固定在另一个稳定状态下,但海拔更高。

用看不见但坚固的铁钩进行监视和警报,切入不确定性的岩石,并且决不让我们跌落到我们设定的稳定水平以下,我们不断提高这一水平。

4.复活节彩蛋


我们称之为“复活节彩蛋”的定时炸弹已经存在了很长时间,但是我们还没有遇到。 在本文之外,该术语指的是故意制作的未记录功能。 在我们的案例中,这根本不是一个功能,而是一个错误,但它的工作原理类似于定时炸弹,并且是良好意图的副作用。

例如:溢出32位auto_increment ; 代码/配置中的非最优性,由于负载而“失败”; 副本滞后(通常是由于新使用模式触发了对副本的次优请求,或者是负载较高,或者是由于新加载模式触发了对主副本的次佳更新并加载了副本)。

复活节彩蛋的另一种流行类型是非最佳代码,更具体地说是非最佳SQL查询。 以前,表较小,负载较小-查询效果很好。 随着表的增加,时间呈线性增长,负载呈线性增长,DBMS资源消耗呈二次增长。 通常,这会导致严重的负面影响:一切都“正常”,然后爆炸。

更罕见的情况是虫子和复活节彩蛋的结合。 带有错误的发行版导致表的大小增加或某种类型的表中的记录数量增加,并且已经存在的复活节彩蛋由于对该增长过度的表的查询变慢而导致数据库上的负载过大。

虽然,我们也有复活节彩蛋,与负载无关。 例如,32位auto increment :在表中有两十亿条记录之后,将不再执行插入操作。 因此,现代世界中的auto increment字段必须设为64位。 我们很好地学习了这一课。

如何处理复活节彩蛋? 答案很简单:a)寻找旧的“鸡蛋”,b)防止出现新的“鸡蛋”。 我们试图实现这两点。 在我们国家寻找旧的“鸡蛋”与不断的代码优化有关。 我们确定了两个最有经验的开发人员进行近乎全时的优化。 他们在慢速日志查询中找到消耗最多数据库资源的资源,优化了这些查询及其周围的代码。 通过检查上述Sensei rezrabotchiki每次提交的最优代码,我们减少了出现新鸡蛋的可能性。 他们的任务是指出影响性能的错误。 告诉您如何做得更好,并将知识转移给其他开发人员。

在发现下一个复活节彩蛋后的某个时刻,我们意识到搜索慢速查询是不错的选择,但是另外搜索看起来很慢但可以快速运行的查询也是值得的。 这些只是下一张桌子爆炸性增长时放置所有内容的下一个候选人。

5.外部原因


这就是我们认为控制不力的原因。 例如:

  • 谷歌地图小跑。 您可以通过监视此服务的使用,观察其上的一定负载水平,提前计划负载的增长并购买该服务的扩展来解决该问题。
  • 数据中心网络的崩溃。 您可以通过在备份数据中心放置服务副本来解决此问题。
  • 付款服务事故。 您可以绕过付款服务的预订。
  • DDoS保护服务错误阻止了流量。 您可以通过禁用默认的DDoS保护服务并仅在DDoS攻击时启用它来解决问题。

由于消除外部原因是一项漫长而昂贵的工作(根据定义),我们才刚刚开始收集由于外部原因导致的事故统计信息,并等待临界质量的累积。 没有确定临界质量的方法。 这只是直觉。 例如,如果我们由于DDoS控制服务的问题而导致完全停机时间是5倍,那么随着下一次下降,我们将变得越来越尖锐,从而提出了替代方案的问题。

另一方面,如果您可以通过某种方式使它与无法访问的外部服务一起使用,那么我们肯定会这样做。 这有助于我们对每个秋天进行事后分析。 必须总有一个结论。 这意味着您总是不想要,但是您可以想出一种解决方法。

6.发行错误,功能受损


这是最不愉快的事故类型。 除用户/业务投诉以外,任何症状都看不到的唯一类型的事故。 因此,这种事故,特别是如果事故不大的话,在生产中可能会长时间不被注意。

所有其他类型的事故或多或少类似于“不良释放,第500个错误”。 只是触发器不是释放,而是负载,手动操作或外部服务方面的问题。

要描述处理此类事故的方法,只需回想一下有胡子的轶事即可:

给数学和物理提供相同的任务:烧水壶。 提供了辅助工具:炉灶,水壶,带水的水龙头,火柴。 二者交替地将水倒入水壶中,打开燃气,将其点燃,然后将水壶着火。 然后简化了任务:提出了装满水的水壶和装有可燃气体的炉子。 目标是相同的-烧开水。 物理学家把水壶烧了。 数学家从水壶里倒水,关掉煤气,然后说:“任务已经减少到上一个。” anekdotov.net

必须将这种事故减少为“释放不良,第500个错误”。 理想情况下,如果代码中的错误已作为错误保存到日志中。 好吧,或者至少在数据库中留下了痕迹。 从这些跟踪信息中,您可以了解已发生错误,并立即发出警报。 如何为此做出贡献? 我们开始分析每个主要的错误并提供解决方案,可以执行哪种监视/ SMS警报,以便使该错误立即以与第500个错误相同的方式显现出来。

6.1。 例子


有大量的投诉:通过Apple Pay支付的订单没有关闭。 他们开始理解,问题反复出现。 我们找到了原因:与收款进行互动时,我们改进了银行卡的expire date格式,因此,银行卡开始专门通过Apple Pay以付款处理服务所期望的格式进行转移(实际上,一种可以治疗,致残),因此通过Apple Pay支付的所有款项开始减少。 快速修复,推出,问题消失了。 但是他们“忍受”了这个问题长达45分钟。

跟踪此问题的痕迹后,我们通过Apple Pay监视了失败的付款次数,并发出了一些非零阈值的SMS / IVR警报(因为从服务的角度来看,失败的付款是正常的,例如,客户的卡上没有钱或卡被冻结) 。 从这一刻起,当超过阈值时,我们立即了解该问题。 如果新版本在Apple Pay处理中引入了任何问题,这将导致服务无法操作,甚至导致部分无法操作,我们将立即从监控中了解到该问题,并在三分钟内回滚该版本(上面介绍了手动滚动过程的工作方式)。 这是45分钟的部分停机时间,变为3分钟。 获利

6.2。 其他例子


我们推出了优化提供给驾驶员的订单清单。 一个错误潜入了代码。 结果,在某些情况下,驱动程序看不到订单列表(该列表为空)。 他们偶然发现了该错误-一名员工调查了驾驶员的应用程序。 快速回滚。 作为事故的结论,我们根据数据库绘制了驱动程序列表中平均订单数的图表,追溯查看了该图一个月,发现那里出现故障并为SQL查询发出了SMS警报,当SQL查询中的平均订单数形成此图。根据该月的历史最小值选择的阈值以下的列表。

更改了将现金返还给用户进行旅行的逻辑。 包括分发给错误的用户组。 我们解决了这个问题,制定了发放现金返还的时间表,看到那里急剧增加,还看到从未有过这样的增长,发出了SMS警报。

在该版本中,关闭订单的功能被打破(订单被永久关闭,无法使用卡付款,驾驶员要求从客户处以现金付款)。 问题是1.5小时(被动和主动阶段共计)。 我们从投诉联络中心了解到了这个问题。 他们进行了更正,对订单的关闭时间进行了监控并发出警报,并通过研究历史图表发现了阈值。

如您所见,此类事故的处理方法始终相同:

  1. 推出发行版。
  2. 了解有关问题。
  3. 修复它。
  4. 我们通过发现哪些痕迹(在数据库,日志,Kiban中)来确定问题的征兆。
  5. 我们绘制这些标志。
  6. 我们将其倒带到过去,并观察突发/跌落。
  7. 我们为警报选择正确的阈值。
  8. 当问题再次出现时,我们会通过警报立即了解它。

这种方法的优点是:可以用一张图表和警报立即关闭大量问题(问题类的示例:未关闭订单,额外奖金,不通过Apple Pay付款等)。

随着时间的流逝,我们将构建警报和监视每个主要错误作为开发文化的一部分。 为了防止这种文化迷路,我们将其正式化。 对于每次事故,他们开始要求自己提供报告。 报告是填写完整的表格,其中包含以下问题的答案:根本原因,消除方法,对业务的影响和结论。 所有项目均为必填项。 因此,如果需要,您将写下结论。 当然,此过程更改是由do's&don's写下的。

7.高丹


, , -, . - ( , ) «». «». :-)

«» :

. — , . , ( ), ( ) , . ( , ).

. , . , : — , — . , « 500- 1 %» — . « 500- 1 %, - , - , - » — . , . ( ). , : , «», , , , . — . ( , ). .

. . , ( , , ), , : , , , .

. , , ( , ).

8. ?


— . . : , . , , . , , , .. — , — ! , . , , ? , , .. , , .

. . ( , ), , : , , , . , , . . . -, , . , , , — : .

9.


, .

??
.
.
( ) post-mortem.
.
do's & dont's.
, , .
, 5 .
.
, .
.
.
.

.
.
.
.
.
SMS/IVR- .
.
( ) .
.
.
- .
( — slow.log).
- « ».
.
.
.
.
.
, , .
«» — .
, .
.
.

, ! , , , , !

Source: https://habr.com/ru/post/zh-CN445704/


All Articles