在过去的一年里,由于资本市场的意外反弹,Zerodha 的客户群增加了两倍多,显着增加了我们平台上的并发用户数量以及它们在后台众多系统上产生的流量和负载。就上下文而言,在 2020 年 1 月,我们每天处理 2 多万笔零售交易。到2020年4月,它已经上升到7+百万。今天,它上升到 12+ 百万。我们的用户群从去年的 2+ 百万增加到 6+ 百万。我们从未预见到我们在十年内缓慢且可预测地增长的客户群会在不到一年的时间内增加两倍。也不是我们曾经尝试过这样做。然而,当它确实发生时,我们能够调整我们的软件、基础设施和组织,而对我们的堆栈只有名义上的影响,并且技术和基础设施成本的增加可以忽略不计。这包括由于 2020 年的 Covid 封锁,我们的 1000 多名员工几乎在一夜之间就过渡到全职远程工作。与此同时,我们去年有 30 名成员的技术团队现在已经达到 31 名。媒体和社交媒体对增长的狂热在商业领域引起了相当大的兴趣,并且有许多疑问:我们如何管理我们的系统和成本以应对资本市场活动的隔夜爆炸?这篇文章是关于我们在 Zerodha 使用的现实生活实践的各种笔记,以及来自该行业的许多第一手见解,对我们来说,这些都是关于如何不运营技术组织的重要教训。这些说明表明,技术组织及其系统应对未来不确定性和极端波动的能力可以植根于常识第一原则。这些原则可以增加事物在适当的、几乎是偶然的时刻自然而然地落入正确位置的几率。这里的许多因素更人性化,更哲学而不是技术,这些笔记也是如此。并不是说这些是普遍适用的,但由于它们对我们有用,因此也可能对其他人有用。与往常一样,阅读这些轶事时应该对上下文和所涉及的权衡有健康和理性的认识。
事实上,“打样”是一种边缘矛盾。软件可以迅速变化并很快过时。就像物理对象一样,闪亮的新软件可以使旧软件看起来过时,偏向技术决策并显着改变其未来的准备情况。矛盾的是,当一个人缩小一点时,软件在很长一段时间内几乎没有变化。半个世纪以来的 C 语言仍然被广泛使用,并且在句法或语义上几乎没有变化,新的语言继续大量借用它。与 SQL 相同。与函数式和面向对象编程等范式相同;同步和异步网络通信; RPC 和 IPC;内核; RDBM 和基于行和列的数据建模;列表、字典和地图;等等……虽然计算概念很少变化且缓慢,但它们的具体化身和表现形式可能会迅速变化,并时时流行。多年来变得无聊的东西突然可以重新发明并再次流行起来,例如在服务器端呈现 HTML 并将结果通过网络发送到浏览器(!),或共享中的无状态“功能”(程序)环境(还记得 /*.php 和 /cgi-bin/?)。软件趋势往往具有惊人的周期性。让我想起科幻系列黑暗中的一句话“开始是结束,结束是开始”,卡兰在看了一集后随意地试图冒充他自己。他实际上并没有,但正如俗话所说,“All Threads Digress To Karan”。这种基本的认识,即计算概念与其特定实现不同,很少发生变化,这是构建更有可能为未来做好准备的系统的第一步。重要的是要了解软件的过去和现在的格局,以了解什么是真正的新事物,什么是旧的,以及影响技术决策的重要因素。在评估技术时,无论是 Javascript 框架还是全新的技术,我们都以此作为衡量标准。例如,我们决定在 2018 年将我们的移动应用程序移植到 Flutter(在两周内编写了一个快速原型之后),当时 Flutter 还处于 alpha 阶段——经过计算的风险得到了很好的回报。在构建某些东西时,知道何时何地划清界限是很困难的。开发人员——我当然是这样——倾向于无休止地调整系统,使它们进入“完美”状态,以消除不完整感; “还没准备好。再多一点”。这种感觉永远不会真正消失,但通过实践和成败的生产经验,可以凭直觉判断出什么东西已经足够好,可以通过所有实际方式进行交付。此外,在瞬息万变的商业环境中,追求“完美”状态可能是一件傻事。在像资本市场这样受到严格监管的行业中,监管可能会突然出现并在一夜之间改变企业的运营方式,迫使软件在几乎没有时间计划的情况下进行重构和重写。例如,在 2020 年年中,印度资本市场监管机构规定了大约四个星期的最后期限,以彻底改变在此之前在交易平台上出售股票的方式。曾经是当用户单击卖出按钮时发送到交易所的实时交易,现在需要在由称为存管机构的市场机构运营的外部网关上获得基于 PIN 的授权。毋庸置疑,这迫使在几乎没有时间进行测试的情况下,通过关键的实时金融系统进行一系列重大的级联更改。销售证券的 UI 和 UX 必须在跨网络的交易平台、移动平台和构建在我们系统之上的多个外部平台上进行更改。不仅如此,数以百万计的用户需要登录到这个外部网关来设置他们的 PIN 并在短时间内了解这个全新的流程。此后不久,法规再次发生变化,除了 PIN 之外,还为此网关引入了新的 SMS OTP。紧接着,另一项法规改变了这些授权的有效性,迫使跨系统进行更多更改。虽然这些符合散户投资者的利益,但对于开发商来说,在令人难以置信的紧迫期限内,它们是极具风险的变化。因此,在许多环境中,务实不是一种选择,而是一种必要。
这种实用主义必须在核心特性和功能、代码质量可扩展性、运行和服务其功能的稳定性、市场条件、法律法规以及定义明确且不太可能快速变化的 API/UI 规范之间取得适当的平衡.这里的 API/UI 是在抽象意义上使用的。它可以表示命令行界面、文件或有线格式、网络调用、RPC 或 ABI——任何在与其他系统交互时做出某些承诺的层。关注界面设计给我们带来了意想不到的好处。当业务需求以意想不到的方式发生变化时,系统必须不可避免地重构或重写时,通常,唯一的一线希望是一个稳定的 API 规范,它保持与其他系统的兼容性,允许在不危及依赖项的情况下交换某些东西。稳定的 API 在同一程序的不同部分之间、不同系统之间,甚至组织中的整个部门之间建立了一定程度的关注分离——良好的旧模块化,通常错误地与微服务混为一谈。这通常意味着组织中的一个部门可以采取一次计划中的打击而不是同时进行多个打击,这是我们已经做过无数次的事情,如果多个部门和他们使用的重叠系统不是模块化的,这是不可能的够了。内部 API 和架构可以轻松更改和替换是一种常见的误解,这通常会导致内部 API 和集成的设计没有得到通常面向公众的 API 的关注和关注。随着内部系统变得复杂,深思熟虑的集成成为技术债务的可能性非常高。因此,尽早尝试使 API 规范尽可能正确非常重要,即使对于内部系统也是如此。常识性的软件设计、架构和服务分离可以产生远远超出性能优势的意想不到的好处。我在 Zerodha 最喜欢的例子是 Kite Connect,我们的 HTTP/JSON API 平台允许在我们的基础设施上构建成熟的交易和投资平台。整个 2013 年和 2014 年,在我们技术团队的早期,我们都忙于将组织内部的事物数字化,主要是编写 Python 脚本来自动化平凡的手动流程。建立交易平台甚至不是一个想法。 2014 年的某个时候,我们的 OMS(订单管理系统)供应商推出了一个基于网络的白标交易平台,我们可以将其提供给我们的用户。基于网络的交易平台是一个很小的利基市场,只有 Windows 的桌面交易平台主导了这个行业。当我亲眼看到它时,我对 Web 应用程序所传递的内容感到震惊。如果我没记错的话,它的突出特点是 IE6 不再是 2014 年的要求 &mldr。我们突然决定我们需要构建一个可用的基于 Web 的交易平台。一项关键的、计划外的举措,最终将 Zerodha 转变为一家成熟的技术公司。当时我们的技术团队只有大约四五个人,我们之前都没有构建交易平台的经验,更不用说熟悉资本市场或金融了。幸运的是,OMS 供应商在 OMS 引擎之上有一组类似 SOAP 的 HTTP/XML API。这将为交易平台的买卖功能提供动力。在接下来的几周内,我将尝试了解文档记录不佳的 API 中高度不一致的路由和命名约定,将调用一一包装到 Python Web 服务器中具有一致字段名称的 HTTP/JSON 调用中。编写这个中间件以将供应商的怪异隐藏在我们愿意使用的一致 API 背后是有意义的。当然,这需要与供应商之间的几个月来回实际使上游 API 处于可用状态,然后进行数年的跟进以进行增量改进。这个新的 API 规范易于使用、理解,并且有足够的文档记录,让 Vivek 开始将它集成到他基于我的 UI“设计”开始构建的 Angular Web 前端中。我们正在发挥作用。 “风筝”这个名字暗示着操作简单、重量轻,可以达到隐喻的高度。
大约在同一时间,我们开始意识到行业中必须存在高质量的终端用户软件,生态系统才能发展壮大,无论我们是否构建了它。虽然我们每天都看到印度推出了许多创新的最终用户应用程序,但由于繁琐的繁文缛节和由于百年行业的传统和总体现状而导致的极高进入壁垒,资本市场却一无所获。例如,直到 2017 年,由于法规不允许在线开户,用户必须打印、签署和快递约 40 页的表格才能在经纪商处开立账户。谁愿意在这样的行业中进行试验!在印度创办一家经纪公司过去(现在仍然如此)极其复杂,而邀请创新的唯一方法就是帮助技术人员摆脱繁文缛节,在现有经纪公司的基础上再接再厉。巧合的是,我们发现 Kite 的 API 是通用且清晰的,足以在其上构建任何交易平台,而不仅仅是 Kite。作为开发人员,我们对一组干净的、有据可查的 API 感到兴奋,这对行业来说是陌生的。希望最终会有人在它之上构建,我们将其命名为 Kite Connect,设计了一个类似 OAuth 的身份验证流程,编写了更好的文档,并围绕此构建了一个自助服务开发者门户,而 Kite 本身正在建设。需要注意的是,这样的事情只会发生在技术决策权掌握在技术人员手中的组织中。当 smallcase 向我们提出建立主题投资平台的建议时,这个疯狂的、自发的副项目很快就取得了成果。我们给了他们 Kite Connect API 密钥,帮助他们获得监管批准,并从我们赚取的微薄利润中获得种子资金。曾经煞费苦心地在我们这里开设账户进行投资和交易的用户现在只需单击一下即可登录 smallcase。我们会处理合法性、合规性、财务流程,当然还有底层技术和基础设施,smallcase 可以专注于构建他们的平台。这启动了我们的金融科技投资基金 Rainmatter,而 Kite Connect API 成为“经纪即平台”(BaaP) 产品,面向希望在用户入职仍需要签署和运送 40 多页的情况下构建定制金融平台的公司。鉴于行业的这种状态,投入时间和精力,更不用说设想一个带有开发人员门户的 API 平台可能没有意义。但是,Kite Connect 不是计划中的,它是良好 API 设计的一个偶然的副作用。今天,有许多应用程序和几家公司建立在 Kite Connect 之上。我们通过其 API 无缝集成了我们所有的最终用户投资和交易应用程序,以及多个内部系统。它是我们业务和产品战略的重要组成部分的基础。有趣的是,风筝连接平台本身就是一个有利可图的垂直领域。所有这一切使我们能够启动 Rainmatter 基金会,Zerodha 已将其大部分利润用于资助环境保护工作。当我努力阅读和理解我几周前编写的代码时,我哭了笑(🥲 我们在技术团队中最喜欢的新表情符号,它准确地捕捉了广泛的开发人员情绪)——“我为什么要这样做?我在想什么?”。想象一下在进入复杂系统时必须阅读、解析和在精神上编译别人的代码的认知负担。聪明的软件需要聪明的代码是一种误解。是否有单行嵌套Python 代码中的列表推导式和 lambdas 与它生成的最终软件的智能和实用性无关。然而,此类密码对代码库的可读性和可维护性有很强的影响,影响其未来的发展。未来的软件准备好,它的源代码在未来应该很容易被它自己的作者和未来的维护者阅读和解析,无论他们是谁。自相矛盾的是,原始作者自己往往最终成为未来开发人员的软件的心智模型。写随着时间的推移而枯竭。这花了很长时间才沉入其中,但是当它发生时,它改变了我编写代码的方式并扼杀了我对神奇的多层抽象的迷恋d 聪明。开发人员易于掌握和处理的简单、几乎是愚蠢的、明确的代码的价值怎么强调都不为过。同样,编写解释开发人员意图的注释,为什么要在程序中完成某事,比解释什么的注释更有价值,这些注释是明确的、不言自明的代码无论如何都会做的。
在 Zerodha 的代码审查中,我们标记了“聪明的代码”,虽然功能强大,但其他人不易阅读和理解。这些代码审查会议让团队中的任何人都可以参加,无论他们的主要项目是什么,这有助于我们在开始不时偏离这些最佳实践时保持自我检查。可能会有宇宙的大统一理论,生命意义的发现,与智能外星生物的接触,然后才会有一个准确估算软件开发复杂性和时间线的框架。我只是半开玩笑。令人震惊的是,软件时间表的错误通常是多么可笑——忘记大型政府项目,但在报告或复选框中添加一个新的数字来改变特定的 UI 行为。我们估计需要数天的事情可能需要数周甚至数月。在我写这一段的时候,我进入了调试、测试和回滚的第四天,这个看起来微不足道的更改我估计需要几个小时。就其本身而言,这种变化是微不足道的,但由于涉及的多个系统的细微差别和某些业务流程的时间安排,它引入的微妙边缘情况的数量我们无法预见。与代码相比,软件复杂性和时间线估计往往受到依赖关系、业务和用户行为中无数微妙之处的阻碍。实际上,与弄清楚要写什么,然后处理所写内容的所有意外副作用相比,编写代码通常只需要一小部分时间。重要的是要意识到这一事实并积极将其纳入项目计划,为具有讽刺意味的意外延迟留出足够的空间。不幸的是,通常负责软件开发时间表的非技术决策者很少理解这一点。技术债务是不可避免的。债务越老,就越难摆脱。在快速开发和迭代软件以满足功能目标和截止日期的组织中,几乎没有时间暂停、重构、以正确的方式做事,为技术债务铺平道路。在一个季度内错过一个功能(是的,这听起来很荒谬,我们知道每个季度有 X 个“功能”作为目标的组织)可能意味着失去竞争优势,对吧?这是一个神话。组织经常高估他们持续发布的功能的重要性(并低估他们没有发布的功能的重要性)。事实上,不是不断添加“功能”和不断变化的软件在其未来就绪的前景中起着重要作用。提高现有软件的质量和性能本身就是一个重要的隐含“功能”,这是许多人都失去的事实。我们定期推迟发布新功能,以减缓和清理技术债务。不幸的是,在许多组织中,此类技术决策通常不在开发人员手中(第 8 节中的哭声)。 7.1.不要修复没有损坏的东西,而是修复可能很快损坏的东西。我们的交易平台 Kite 的第一个版本是用 Python 编写的。当它在 2015 年推出时,它的并发用户只有不到一千。今天,在任何特定时刻都有超过一百万。 2016 年发布了版本 2,一个重要的重构,2017 年发布了版本 3,一个完全重写的版本。在这些版本之间,我们用尽了 Python 性能优化的可能途径,并用 Go 重写了整个后端。 Angular 前端很快就变得难以维护,在 Vue 中被废弃并重写。最初用原生代码编写的 Kite 移动应用程序(成为多代码库维护难题),然后是 React Native(成为性能难题),第三次被废弃,取而代之的是在 Flutter 中重写。这些决定对我们来说效果非常好。
另一个例子是ticker,该组件每秒向最终用户传输数千万个实时市场数据包。在过去的六年里,随着我们越来越擅长编写 Go 并且我们开始感觉到imp ......,它已经被重构和重写了至少五次。