NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

30 万行代码的平台升级:给跑着的汽车换轮胎

  • 2021-03-22
  • 本文字数:5091 字

    阅读完需:约 17 分钟

30万行代码的平台升级:给跑着的汽车换轮胎

本文最初发布于 Mahmoud Hashemi 的个人博客,经原作者授权由 InfoQ 中文站翻译并分享。


2020 年可谓反复无常。尽管一切都超出了人们的控制,但随着时间的推移,我发现自己把越来越多的时间地投入到一件感觉唾手可及的事情中:为我帮助构建的大型企业级 Web 应用程序SimpleLegal设计一个面向未来的解决方案。


现在已经完成了,这次平台升级很容易就可以在我最复杂的项目中名列前茅,此时此刻,最幸福的结局。幸福是要付出代价的,但是借助一些恰当的方法,代价可能不会像你想的那么高。

概述

我们将SimpleLegal的主要产品,一个 30 万行的 Django-1.11-Python 2.7-Redis-Postgres-10 代码库,移植到 Django 2.2-Python 3.8-Postgres-12 技术栈,如期完成,而且没有发生重大站点事件。这感觉很棒。


作为这个项目的技术主管,它看起来是什么样子?对我来说,是这样的:



但作为工程总监,它的成本是多少?3.5 年的开发时间,每行代码只需要 2 美元。


我对这个结果感到特别自豪,因为在这个过程中,我们也大大提高了网站和开发过程本身的速度和可靠性。现在,该产品有了一个光明的未来,已经准备好在销售征求建议书和合规调查问卷上大放异彩了。最重要的是,你不必担心怎样委婉地告诉潜在客户,他们将使用的是不受支持的技术。


简而言之,这是一笔巨大的、稳健的投资,而且已经取得了回报。如果你来这里只是为了看看我们自己对这项工作的估计,那就是上面这些了。这篇文章是介绍如何让你的团队达到同样的结果。


背景

故事开始于 2013 年,刚刚从 YC 孵化出来的 SimpleLegal 为一家新成立的 SaaS 法律技术公司做了所有正确的决定:Python、Django、Postgres 和 Redis。在典型的初创公司模式中,在技术不成障碍的情况下,功能是第一位的。软件包只是顺带升级。


到 2019 年,这条技术跑道的终点已经临近。虽然 Python 2 可能得到了来自不同供应商的扩展支持,但在 2021 年,Django 1 CVE 补丁的志愿者已经非常少了。Web 框架成了风险较大的攻击面,所以是时候偿还我们的技术债务了。


开端

因此,我们在 2019 年第 4 季度开始了 Tech Refresh 平台升级计划。其目标是:升级技术栈,同时仍然提供新特性,就像给跑着的汽车换轮胎。我们要小心谨慎,而那需要时间。以下是一些长期项目的基本原则:

  1. 任何每周工作 10 小时以上的项目都应该每周花 30 分钟进行同步。

  2. 每次定期会议都应该有记录。把它放在邀请函里。使用项目日志记录进度、阻碍因素和决策。

  3. 这是一场马拉松,不是短跑。要避免在晚上、周末和假期工作。


我们从一个计划草图开始,经过开放地讨论,最终只有一半正确。有一些早期的猜测成功实现:

  1. 转到pip-tools,并根据广泛的变更日志分析解除依赖关系。识别不兼容 py23 版本的包。(尽管我们已经转向poetry。)

  2. 在 CI 中加入行覆盖率报告。

  3. 改进内部测试框架,让开发者可以快速编写测试。


下面有更多相关内容。其他的计划就不那么现实了:

  1. 在 6 个月内将 CI 行覆盖率从大约 60%提升到 95%。

  2. 在三个月内并行转换 app 程序包。

  3. 利用美国节假日(感恩节、圣诞节、新年)期间的低流量时间,在 2021 年之前逐步切换到新应用。


我们年轻!虽然我们天真,但至少我们知道有很多工作要做。为了分担这项工作,我们寻找、雇佣并培训了三名敬业的海外开发人员。


导向问题

即使新增了开发人员,到 2020 年中期,我们越来越认识到,95%的覆盖率就是在做梦,更不用说 100%了。全部覆盖可能是最佳实践,但 3 个半开发人员没法做到这样的覆盖范围。我们做了有价值的测试,甚至发现了以前的 Bug,但如果我们坚持这个计划,Django 2 最终将成为一个 2022 年的项目。70%,我们决定修改目标。


我们意识到,对于大多数站点来说,CI 比大多数用户更敏感。所以我们专注于测试影响最大的代码。怎么才算影响大?1)失败了最易被察觉的代码;2)最难重试的代码。通过查看流量统计数据、批处理作业计划和询问支持人员,你可以在一周内构建出高影响代码清单。


大约 80%的代码库都不在这个高流量/高影响列表中。那 80%该怎么办呢?利用错误检测和快速修复。


转换 Sentry 的角色



创业生活的一个好处是,尝试新工具很容易。我们在 SimpleLegal 所采用的一种做法是,把每 5 个周的最后一周(即 20%的时间)留给开发人员,让他们专注于开发过程本身。即使是最好的厨师也不能在脏乱的厨房里做出五星级的食物。这是我们改进工作的方法,最终加快了交付速度。


在这样一个时期,有人想出了一个天才的主意,使用Sentry将专门的错误报告添加到系统中。在一两天内,我们就有了一个网站,你可以访问并获取堆栈跟踪。这非常神奇,但直到 Tech Refresh 计划开始我们才意识到,虽然集成只需要一天的开发时间,但完全采用却需要团队几个月的时间。


你看,在一个成熟但快速运转的系统上增加 Sentry 意味着一件事:噪音。我们的网站一直在出错。大多数错误是不可见的,也没有妨碍用户使用,有些用户已经悄悄学会了如何处理长期存在的网站怪癖。很快,我们的开发人员就学会了把 Sentry 当作调试信息的存储库。2019 年,Sentry 事件本身并不值得认真对待。2020 年,情况发生了变化,负责将平台无缝升级的团队需要把 Sentry 变成另一种东西:响应性网站质量工具。


我们是怎么做到的呢?第一步,通过以下最佳实践增强流入 Sentry 的数据:

  1. 将产品拆分成单独的Sentry项目。这包括前端和后端。

  2. 标记版本。不要用分支来标记开发环境部署,这会导致 Releases UI 混乱。添加一个单独的分支标签用于搜索。

  3. 把环境分开。这对于定向报警至关重要。Sentry 客户端环境是通过域约定和 Django 的sites框架来配置的。为了便于理解,这里有一个基线,我们使用这些环境:

  4. 生产环境:当前正式版本。DevOps 监控。

  5. 沙箱环境:当前正式版本(部分公司会做下一次发布)。供用户测试变更使用。DevOps 监控。

  6. 演示/销售环境:上一个正式版本。主要是内部流量,但在前景演示时外部也可见。DevOps 监控。

  7. 金丝雀环境:下一个正式版本。也称为过渡环境。内部流量。Dev 监控。

  8. ProdQA 环境:当前正式版本。内部用于重现技术支持问题。Dev 监控。

  9. QA 环境:Dev 分支、dev 发布、内部流量。未监控调试数据。

  10. 本地测试/CI 环境:默认不发布到 Sentry。


当问题最终被正确标记并且可以搜索之后,我们使用 Sentry 新增的Discover工具每周导出问题,并对遗留错误进行优先级排序。我们首先关注的是对于非内部人类用户高可见的生产错误。具体查询是:has:user !transaction:/api/* event.type:error !user.username:*@simplelegal.*


我们将其分为 4 类:快速修复(小漏洞)、快速错误(将一个含糊的 500 错误转变成某种形式的可操作的 400 错误)、Spike(比较大的漏洞,需要研究)和 Silence(使用 Sentry 的忽略功能)。在 6 周的时间里,每周事件量由每周超过 2500 次下降到了不到 500 次。


通过进一步的努力,每周的事件量已经少于 100 次,并且分散在几个问题上,对于一个精益团队来说,这非常容易管理。虽然“Sentry Zero”是最理想的,但我们实现并维持了响应流的真正目标,这在很大程度上要归功于Slack集成。我们的团队不再从支持团队那里获取服务器错误信息。事实上,现在,当客户遇到麻烦时,我们会告诉他们,而我们已经有了一个处理中的工单。


和支持团队建立紧密的联系非常重要。在上面的策略中,我们嵌入了比真实用户更敏感的 CI。虽然完美很诱人,但要求企业用户有一点耐心也是可以的,前提是支持团队已经做好了准备。每周都和他们同步,这样惊喜就少了。如果他们干劲十足,你也可以教他们一些 Sentry 基础知识。


新征程



随着噪音的消除,我们已准备好快速行动。以下是我们在做出这些改变时积累的一些经验。


诉诸事务

如果使用得当,回滚可以使错误看起来像从未发生过,这是快速修复策略的完美补充。


真正的原子请求

把操作尽可能地放入事务中。打开ATOMIC_REQUESTS(如果没打开的话)。但是,有些请求所做的不仅仅是更改数据库,比如它们会发送通知,将后台任务入队。


在 SimpleLegal,我们重新设计了架构,将所有副作用(除了日志记录)推迟到成功返回响应时。中间件可以提供帮助,但我们主要是通过将 Redis 队列切换到基于 PostgreSQL 的任务队列/代理来实现的。这种配置可以确保,如果发生错误,事务将被回滚,任务不会进入队列,用户将得到一个干净的失败。我们在 Sentry 中定位故障,切换到旧站点进行消除,他们下一次重试就会成功。


事务性测试设置

事实证明,事务性对我们的测试策略来说也很关键。SimpleLegal 早已超过了 Django 原始的 fixture 系统。大多数测试都需要复杂的 Python 设置,这使得编写测试和运行测试都很慢。为了加快编写和运行的速度,我们将整个测试会话封装到一个事务中,然后,在运行任何测试用例之前,我们设置了示例性的基本状态。测试用例使用这些基本状态作为fixture,并在每个测试用例之后回滚到基本状态。详情请参阅contest.py摘录


有些最佳实践并不适合你

软件场景的差别如此之大,知道哪些建议不适合你是一门艺术。以下是我们亲身了解到的各种死胡同。


命名空间的运用

考虑到代码被划分成模块、包、Django 应用等的方式,把它们作为工作单元可能很有诱惑力。开始时不要这样。代码划分可能非常随意,很难知道你何时就进入了一个有风险的思路。


假如有自动重构,就像在2to3转换中一样,首先要按转换类型进行移植。这样,你只需要查看一个命令和受影响的路径列表。另外,自动修复必须遵循一种模式,这意味着更多的人可以修复重构导致的错误。


覆盖率工具



覆盖率对我们来说是好坏参半。显然,覆盖率优先策略是站不住脚的,但对优先级划分和状态检查,它仍然有用。就单次变更来说,我们发现覆盖率工具有些不可靠。我们从来没有弄清楚为什么覆盖率的作用有不确定性,我们得出了这样的结论:“像 codecov 这样的现成工具可能并不是针对我们这种规模的 monorepos。”


在撞上覆盖率墙的过程中,我们研究了其他许多关于覆盖率的解释。对我们来说,“路由覆盖”(即每个 URL 至少有一个集成测试)和“模型表示覆盖”(即每个模型对象都有一个有用的文本表示,可以用于 Sentry 调试)比行覆盖优先级高得多。如果有更多的时间,我们会希望围绕这些构建工具,甚至是围绕基于在线分析的覆盖率统计,从而优先考虑流量最高的路由,而不仅仅是流量最高的代码行。如果你听说过这些方法,我们很想和你讨论一下。


扁平化数据库迁移

从表面上看,减少需要升级的文件数量似乎是合理的。事实证明,扁平化迁移是一种消除文件的低收益策略。更改历史迁移文件结构会使上线过程变得复杂,而升级没有扁平化的迁移文件则很简单。更不用说,如果只是想要加速 CI,你可以像我们在Open edX平台上所做的那样:建立一个基本的DB缓存,每隔几个月检查一次


事实证明,你可以从开源应用程序中学到很多东西


慢慢适应新技术栈

如果你有多个应用程序,请使用相对比较小也比较简单的应用程序来试验更改。幸运的是,我们有一个独立的应用,它的测试运行速度更快,这让我们能够更紧凑地了解开发循环。同样地,如果你有多个生产环境,则从影响最小的一个环境开始推出。


把 CI 作业复制到新的技术栈中,它们都会失败,但要克制住把它们标记为可选项的冲动。相反,构建一个包含所有测试及其当前测试状态的单文件清单。我们为测试运行程序pytest构建了一个小扩展,它基于状态清单文件批量跳过测试。然后,ratchet:取消并修复测试,更新文件,检查测试是否通过,然后重复。这比遍布代码库的pytest标记装饰器更方便和可扫描。详情请参阅contest .py摘录


上线试运行

在 2020 年第四季度,我们增加了基础设施,以在相同的数据库支持下并行运行新旧站点。我们进入了这样一个循环,使流量到达新技术栈,构建一个需要修复的 Sentry 问题队列,然后关闭它,并跟踪时间。使用新技术栈大约 120 个小时后,经过昼夜不停地策略性扩展,组织已经建立起足够的信心,我们可以在最关键的时间让站点继续运行:在月初的周一和周二。


唯一的问题是AWS在感恩节周的宕机。此时我们已经提前完成了计划,并且对快速修复工作流建立起了足够的信心,不再需要最初的假日测试窗口。为此,我们感谢了很多人。


我们一直用快速修复的方法,直到我们完成。“完成”不是指新系统没有错误,而是指流量在新系统上时事件比旧系统少。然后,继续修复,并开始安排时间删除脚手架。



后记

所以,一旦你使用了 Django、Python、Linux 和 Postgres 当前的 LTS 版本,任务就完成了,对吧?


谢天谢地,技术债务从不会到 0。虽然按期更新并更换核心技术不是一件小事,但用闪亮的部件替换生锈的部件并不会改变设计。架构技术债务——抽象中的错误,包括缺乏抽象——可能会带来更大的挑战。这些问题的解决方案并不能在项目之间完全推广,但它们确实会受益于这个最新的、无错误的基础。


对于所有希望更换轮胎的项目,我们希望这次回顾能够帮助你在未来几年充满信心地、务实地改进技术栈。


查看英文原文:

Changing the Tires on a Moving Codebase

2021-03-22 18:563007

评论

发布
暂无评论
发现更多内容

【译】日志:每个软件工程师都应该了解实时数据的统一抽象【三】

Rae

kafka 日志 原理

葡萄城受邀参加WOT全球技术创新大会

葡萄城技术团队

给你一本武林秘籍,和KeeWiDB一起登顶高性能

腾讯云数据库

redis 腾讯云 NoSQL 数据库 腾讯云数据库 KeeWiDB

新品速递|海泰边缘安全网关护航工控数据采集

电子信息发烧客

无线网络安全技术中的王牌标准:WPA到底是个什么东东?解决了什么问题?

wljslmz

网络安全 无线技术 9月月更 WAP

Alluxio与北京大学计算机学院签署合作框架协议,推动产学研深度融合

Alluxio

开源 云原生 产学研用 Alluxio 北京大学

使用WIX 进行商业智能OEM打包

葡萄城技术团队

云数据库技术行业动态@2022-09-30

数据库 数据复制 数据管理 数据备份 数据对比

分享|破世界纪录的OceanBase,如今入选了国际顶会VLDB 2022

OceanBase 数据库

工业4.0时代IIoT存储面临哪些挑战

CnosDB

IoT 时序数据库 开源社区 CnosDB infra

NFTScan 与 PANews 在 NFT 数据层面进行战略合作

NFT Research

API NFT 合作 MetaMask

打破线上社交“不可能三角”,语音社交可以做到既要、又要、还要

擎声科技

音视频 sdk 语音社交 实时互动 擎声Qtt

第56届世乒赛团体赛开幕!三思近900㎡ LED显示点燃赛事激情

电子信息发烧客

国庆数字游,融云都为您准备好了

融云 RongCloud

leetcode 513. Find Bottom Left Tree Value 找树左下角的值 (简单)

okokabcd

LeetCode 数据结构与算法

字符串哈希

留白的艺术

重磅发布!Orbit 云原生应用全生命周期管理工具上线啦!

CODING DevOps

云原生 Orbit CODING

基于边缘计算的渲染新应用

火山引擎边缘云

边缘计算 渲染 边缘云 渲染性能 渲染服务

还在为产品的客户服务而烦恼?来搭建在线客服中心!

Baklib

Databricks Data Science&Engineering模块介绍

Jackchang234987

大数据 数据产品经理 数据产品 大数据开发 Databricks

iMazing传输 iPhone 备忘录和通话记录功能

淋雨

ios iphone

Java: 压缩PDF文档

Geek_249eec

Java PDF 压缩

GPU是AI时代的算力核心

Finovy Cloud

人工智能 云渲染

zookeeper集群之间如何通讯

浅羽技术

zookeeper 通信 集群 ZooKeeper原理 9月月更

面试官问我 JS 中 foreach 能不能跳出循环

茶无味的一天

JavaScript js foreach for

大数据开发应用场景解读

Jackchang234987

大数据 数据开发

OptaPlanner场景和示例

成长兔🐇

安利几款简单好用的帮助文档制作工具

Baklib

帮助文档

还不知道产品帮助中心怎样制作?,来看看这个吧

Baklib

产品的帮助中心怎么建设?关于编辑帮助文档的几个小技巧~

Baklib

好的代码是优质资产、莫让代码成为负债

葡萄城技术团队

30万行代码的平台升级:给跑着的汽车换轮胎_架构_Mahmoud Hashemi_InfoQ精选文章