
インターンシップで全社のMongoDBをアップデートした話
Posted on
初めまして、プレイドでインターンをしている小林と申します。
プレイドでは2023年の11月からインターンとして勤務していて、現在はリアルタイムユーザー解析基盤のチームにて日々さまざまなタスクに取り組んでいます。今回は最近まで所属していたプロダクト基盤チームで携わっていた大きなプロジェクトである MongoDB のアップデートについて、インターン生の目線からお話していきたいと思います。
2024年2月の MongoDB 4.4 のEOLに伴って MongoDB 5.0 へのバージョンアップ作業が必要になり、さまざまな苦労の末短い期間でしたが大きな影響を出すことなく全てのプロダクトの本番環境までバージョンアップを完了させることができました。メンターの方とチームの方、そしてインターン生の自分の3人で MongoDB Vup チームを結成しバージョンアップを進めていきました。今回の記事では、プレイドでのインターンシップの雰囲気やどれだけ実務に近いタスクに携われるかについてお伝えできればと思います。
自己紹介
学部の4年生で、大学では現代芸術やメディアアートを学んでいます。プログラミングは独学で小学生の頃から取り組んでいて、大学に入ってからは授業の傍らモバイルアプリを作ったりハッカソンに出たりしてきました。
プレイドのインターンに参加した理由
大学3年時にインターンシップに参加するべくさまざまな企業の募集を確認しており、プレイドは探し始めた当初から知っていたのですが、募集要項を見て技術的な要求水準が高そうだなと思いそのときには応募には至りませんでした。しかしその後面談イベントでプレイドの方とお話する機会があり事業に興味を持ったこと、そして自分の経験を評価していただいたことから最終的にインターンへと進むことになりました。事業を成立させるのに大量のリクエストを低レイテンシーで捌くことが必要不可欠であり、そのため技術を非常に大事にしているというお話を面談で伺い、また過去のインターン生のブログを拝見し、インターン生とは思えない高度なタスクに取り組んでるのを見て、自分もその技術の一端を体感できるのではないかと少し緊張しながら、とても興味を惹かれたことを覚えています。
MongoDB とは
MongoDB とはドキュメント指向データベースの一つで、MySQLやPostgreSQLなどのRDBMSとは異なり、事前でのスキーマの定義が必要なく、JSONのような形式でさまざまなデータを保管することができます。プレイドではこのあとからスキーマを変更することのできる柔軟さや Node.js との相性から MongoDB を主に利用しています。
また、社内ではMongoDB へ Node.js からアクセスするための MongoDB Driver と、Mongoose という ODM(Object Document Mapper) が利用されています。ODMとはRDBMSでいうORM(Object Relational Mapper) のことで、データベースから読み取ったデータを Node.js のオブジェクトとして扱えるように取り込んでくれます。
3ヶ月後の強制アップデートによってプロダクトが正常に動作しなくなる?
インターンを始めて3週間ほど経ち段々と新しい環境に慣れてきた昨年11月の後半、メンターの方から次は MongoDB に関連するタスクに取り組んでいかないかというお話がありました。なんでも社内のプロダクトで使っている MongoDB 4.4 は2024年の2月にEOLを迎え、 MongoDB Atlas のクラスターが強制アップデートされてしまうのでアップデート対応をしないとこの上で動いているシステムは全て動かなくなってしまう可能性があるそうなのです。いやいや、2月ってあと3ヶ月しかありませんよ……!?というところから MongoDB vup 4.4→5.0 プロジェクトがスタートしていきました。
所属していた基盤チームにてメンターの方と社員の方もう一名、そして自分を加えた3人で Mongo Vup チームを結成し、アップデートに向けて作業を進めていくことになりました。
目標: ユーザーへの影響が出ないバージョンアップ
今回のバージョンアップの目標は、まずプロダクトへの影響を出さないことが第一でした。多くのお客様にご利用いただいている中で、強制アップデートによりさまざまな機能が一挙に止まるという状態はなんとしても避ける必要があり、手動でのバージョンアップ時にもなるべく本番環境を止めることなく、影響を出さずにアップデートしていくことが求められました。もしもアップデート対応が終わる前に強制的にアップデートされてしまうと、当然システムのほとんどは動かなくなってしまうため避ける必要がありますし、幅広くMongoDBが使われているためアップデート対応作業を進める際もさまざまな部分に気を使い、何かあった場合のロールバックもすぐに行えるようにするなど迅速かつ慎重な作業が求められました。
作業の流れ: 基盤チームからプロダクトチームへ
必要な作業
移行の作業は全体として、私たち基盤の Vup チームで取り組む作業とそれぞれのプロダクトの方々に取り組んでいただく部分の2つに分けることができます。基盤チームでは事前の調査や社内ライブラリをアップデートし、それからプロダクトチームの方々に作業を進めていただくという手順を踏むことになりました。
1. 対応箇所見積もり
MongoDB のアップデートに伴って対応が必要な箇所をリストアップしていきました。MongoDB Driver と Mongoose のマイグレーションドキュメントを読み込んで、社内で使われている機能や特に注意が必要なものをまとめていきました。もちろんこれだけで全ての対応箇所がわかるわけではありませんが、やはり最初に概要を把握しておくのは大切です。こちらの作業は最初にメンターの方がメインで行ってくださったのですが、自分でもマイグレーションドキュメントを読み込んでおいたことにより、後々問題が発生した際にどこが悪そうかあたりをつけることができました。
2. 社内ライブラリのアップデート
社内には MongoDB を触っているライブラリがいくつかあり、プロダクトそれぞれでの対応の前にこのライブラリたちをアップデートする必要がありました。MongoDB Driver と Mongoose を MongoDB 4.4 と 5.0 に対応したバージョンへ上げるために随所を修正し、MongoDB 5.0 対応版のライブラリを用意する必要があります。
3. 各プロダクトのアップデート
次に社内のプロダクトのアップデートが必要になります。ライブラリのアップデートとそれに対応したコードの修正が必要になるのですが、プレイド社内には多くのプロダクトが存在するので、ここは各プロダクトチームの方々に対応していただくことにしました。ただ、一からプロダクトチームの方々に対応していただくよりは、何か参照できるドキュメントがあった方が良いだろうということで、プロダクトのうち一つをVupチームで対応した上で必要だった修正などをドキュメントにまとめ、対応箇所見積りで作成したドキュメントと併せて参考にして貰いながら各チームに対応していただくことにしました。
4. DBのアップデート
今までの作業でプロダクトが MongoDB 4.4と5.0双方で動作するようになっているはずなので、DBのアップデートを行なっても動作させ続けることができるはずです。評価用の Evaluation でアップデートした上で動作を確認し、問題がなければ本番の Production 環境もアップデートすることで MongoDB vup が完了します。この本番環境のDBの強制アップデートが行われるのが2月末なので、それまでにアップデートを終わらせることが必要となります。
立ちはだかる課題
全社で使っているとはいえ、データベースのアップデートにそこまでの問題は発生しないだろうと最初は楽観的に捉えていました。MongoDB やそれに依存しているライブラリにも移行のためのマイグレーションドキュメントが用意されていることを確認していたので順調に進むだろうと思っていたのですが、現実はそう甘くはありませんでした。
課題1: 大量のシステム
前述の通り、マルチプロダクトで30以上のシステムが社内に存在し、どれがどのように MongoDB を扱っていてどの程度修正が必要なのかがすぐにはわかりませんでした。また、修正も各プロダクトチームのメンバーにお願いする必要がありますが、なにぶん多くの人が関わるので情報の共有や認識のすり合わせがなかなか難しくなってしまいます。期日に間に合うように作業を進めるためには、なるべくプロダクトチームの方々に負担をかけずに、かつ作業を素早く進めてもらう必要がありました。
課題2: 複雑な依存関係
元々対応が必要だろうと目星をつけていた社内ライブラリがあったのですが、そこの修正を進めていくうちに、そのライブラリに依存している別の社内ライブラリが芋づる式に発見されていきました。さらにこの中にはhttps://github.com/plaidev/link-local-dependenciesという symlink を貼ることによってローカルの Node.js ライブラリをインポートする仕組みでプロダクトに導入されているものがあり、ライブラリのアップデートが即座にプロダクトに反映されてしまう状態になっていることがわかりました。
そのため、まずは link-local-dependencies の利用をやめた上で npm にパブリッシュしたものをインポートするようにしてからライブラリのアップデートに取り掛かる必要がありました。
課題3: 外部ライブラリvupの落とし穴
利用している mongoose というライブラリにおいて、使用しているバージョンが MongoDB 5.0 に対応していなかったのでこちらのバージョンもアップデートする必要がありました。前述の通りマイグレーションドキュメントが用意されてはいたのですが、実際に作業を進めるとこのドキュメントに記載のない修正が必要になったりパフォーマンス上の問題が浮上したりと、想定していなかったさまざまな対応が追加で必要になっていきました。
実際の作業
自分が担当したもの
今回の MongoDB のバージョンアップは結構大掛かりかつ重要度の高いプロジェクトで、一般的にはインターン生が任されるものではあまりないように思うのですが、プレイドではインターン生も社員の一員のような扱いで実際のプロダクトで、重要度の高いタスクにも取り組むことができます。今回のタスクを行う上で必要な箇所を幅広く触ることになりました。
ローカルの移行スクリプトの作成
まずはローカルの開発環境にてMongoDB 5.0に4.4のデータを移行できるようにスクリプトを書きローカルで MongoDB 5.0 を立ち上げられるようにしました。docker-compose.yml を書く必要があり、触ったことがなかったので調べながらではありましたが、無事ローカルに MongoDB 5.0 を用意することができました。
link-local-dependencies をやめる
symlink を用いて npm の依存を解決する link-local-dependencies は、ライブラリのアップデートがブランチにマージされると即刻反映されるので便利なのですが、バージョンが管理できないため今回の移行時にはパブリッシュされたライブラリを用いるように変更する必要がありました。npm の依存関係の解決は複雑で、重複しているライブラリを削除したりなどさまざまな処理を行なっています。そのせいもあってか、今回は依存の方法を変更するだけなのに挙動が変化してしまうという事象が発生しました。恐らく依存の解決の際に意図しないバージョンの mongoose plugin が入ってしまっていたことが解消され、それによりDBから取得するデータが変化してしまうことが原因だということがわかり、修正を施すことで社内ライブラリのアップデートへと進むことができるようになったのですが、想定以上に時間がかかってしまったことと、年末年始に重なってしまったことや障害対応などによりプロジェクトに遅れが生じてしまいました。
社内ライブラリのアップデート
MongoDB のアップデートに対応するためのアップデートが必要な社内ライブラリは3つあり、MongoDB へ接続するためのライブラリ、解析基盤のキャッシュを管理・参照するためのライブラリ、プロダクト間での通信のためのライブラリと重要なものばかりだったのですが、全て作業を任せてもらえました。とはいえライブラリの修正自体はそこまで難しいことはなく、割とすんなり進めることができました。動く状態に持っていくことが大変だったというよりかは、本当にその修正で想定通り動くようになるのかという証拠を探してくることに苦心していた覚えがあります。メンターの Brown さんはそういう時にコードを読むと良いよとおっしゃっていて、GitHub のコメントなどの他に Mongoose や MongoDB のコードを時々読みに行ったりしていました。
例えば Mongoose をアップデートしたことにより MongoDB への接続の認証がうまくいかないという問題が発生しました。エラーの内容で検索をかけてみていくと、どうやら新しい Mongoose が利用している MongoDB Driver のバージョンで証明書を渡す際に、ファイルの内容ではなくファイルパスを渡すような変更が入っていたのです。これにより、証明書の内容をファイルパスとして解釈しようとしてファイルパスが長すぎるというエラーが出ていました。これは明らかに破壊的変更なのですが、なぜかマイグレーションドキュメントなどへの記載はなく、ようやく探し出したのはこの mongodb/mongoose のコミッターの方のPRへのコメントでした。
https://github.com/mongodb/node-mongodb-native/commit/b573fe159605f0a8e3d52a330de6699534b48a90#commitcomment-57051897
型の変更など細かい変更もあれば、このような重大な変更がサイレントで入っていたりして、対応していくうちに情報を探す力がかなりつきました。
プロダクトのアップデート対応
基盤チームでサンプルとして実施するプロダクトに加え、手が空いたのでいくつかのプロダクトのアップデートを担当しました。基本的には非対応となった構文やオプションに対応したり、型の変更に対応したりということが多かったです。対応していくにつれてTSの型に詳しくなったような気がします。
特殊な対応が必要になった例で言うと、プロダクトのうちの一つに MongoDB Driver のコミュニティの型との互換性が保たれないという問題が発生しているものがありました。Mongoose のバージョンを上げることができれば依存している MongoDB Driver も上がり問題は解消されるのですが、他のシステムとの兼ね合いによりバージョンを上げるわけにはいかず、結局この修正に対応したパッチを当てることになりました。パッチを当てるのは初めてのことだったのですが、結構簡単に修正することができました。
このプロダクトはそもそもが大きかったこともあって、メンターの方のコミットと合わせて80を超えるファイルに変更を入れて、レビュー時には力作と呼ばれていました。インターン生でもこの量の変更を任せてもらえるとは驚きです。
他の作業
ドキュメント作成
プロダクトのアップデートを本格的に実施する時のための作業手順や得られた知見を Notion のページにどんどんまとめていきました。必要な改修や注意点、アサイン表など全てをこのページに集めることで何か困ったことがあれば、とりあえずここのページを見れば良いという場所になり、作業が随分と進めやすくなったように思います。他チームの方も作業中に気になった点などをフィードバックしてくださったりしたので、大変進めやすかったです。
プロダクトのアップデート
私が進めた以外のプロダクトは、基本的にプロダクトチームの方々に対応していただいていました。気になって自分でも他のプロダクトのPRを見に行ったりしていたのですが、見たことないエラーが出ていたりしていて、やはり単なるバージョンアップとはいえ、これだけさまざまな部分で使われていると苦労も大きいなと実感しました。皆様スムーズに進めてくださったので大変ありがたかったです。Kubernetes の Pod 1つのみアップデートして様子を見たり、デプロイする時間帯にも配慮したり、万が一何か問題が発生したとしても影響を最小限にするよう努力するなどの対応が取られていました。
発生した問題のうち厄介だったものとして Mongoose のバージョンアップによるパフォーマンスの劣化が挙げられます。https://github.com/Automattic/mongoose/issues/13456
MongoDB ではデータをBSONというバイナリ形式のJSONのような形で保管しているのですが、Mongoose 6と7ではこのBSONの deserialize 処理により段々とCPU使用率が上がりパフォーマンスが劣化していってしまうという問題がありました。Mongoose の lean オプションを利用することで改善されたのですが、このパフォーマンスの劣化によりサービスのレイテンシが上がっていってしまっていました。そのプロダクトの Vup 担当の方が調査に当たってくださったのですが、段々と上がるレイテンシの原因がどこにあるのかをトレースや Datadog のプロファイリングを用いて突き止め、仮説を立てて検証し改善するという一連の流れを Slack などで見ていてとても勉強になりました。
DBのアップデート
いよいよ最後の段階にあたるDBのアップデートです。DBのアップデートは権限的にインターン生はできることが少なく、この頃には私自身はあまり関わっていなかったのですが Notion の表を見るとどんどんアップデートされたプロダクトが増えていっており、その頃何か問題が発生したという話は報告されなかったのでほっとしていました。実は今までの作業が想定より時間がかかってしまったので、 MongoDB 社にコンタクトを取り強制アップデートの日時を1ヶ月後ろ倒しにしていただいていました。紆余曲折あったものの、強制アップデートによりシステムが壊れることなく、伸ばしていただいた期限に間に合うようにアップデートを完了させることができました。
プロジェクトを振り返って
大変だったこと
Node.js
実はインターンを始める前は、 Node.js をほとんど触ったことがなかったので学びながら作業を進めていきました。特に、TypeScript の柔軟な型システムは大変興味深かったです。今まで直和型や直積型が存在しない言語ばかり触ってきたので、こういった型の表現は新鮮で面白かったのですが慣れるまではなかなか理解に苦労しました。
システム・プロダクトの理解
いくつものプロダクトが存在し、その役割を理解するためにはどのようなサービスを提供していてそれがどのような仕組みで動いているのかという部分を理解する必要があり、最初はなかなかプロダクトの役割や仕組みが理解できずにいました。今回のプロジェクトでは多くのプロダクトに手を入れる必要があり、それぞれのプロダクトについてどのような部分に MongoDB が使われていてどのように動作確認ができるのかということを考える必要があったので、普段の開発と比較してもよりこのプロダクトの理解が大変でした。
ドキュメンテーション
プロダクトチームの方々にバージョンアップ対応をしていただくためにドキュメントに手順や注意点をまとめたり、Slack 上で呼びかけをしたりしていたのですが、どのような順番で、どう表現したらわかりやすいかというところに注意しながら文章を書くのに苦労しました。私は MongoDB のバージョンアップに時間を割いて取り組んでいましたが、プロダクトチームの方々はそうではないので前提知識の差に気を付けて書く必要がありましたし、どこまで詳しく書けば良いのか、また情報に更新があった際はどのように周知すれば良いかなど考えることがたくさんありました。やはりプログラムを書く時間が多いといはえ文章を書く時間も同じくらい大切で、文章を書く力の大切さを実感しました。
学び
組織の中での協業
今回のタスクは基盤全体のアップデートだったこともあって、普段の業務ではあまりないようなチーム間での協力が必要でした。関わるメンバーが増えると情報共有も格段に難しくなりますし、全体の進捗を把握するのも一苦労でした。Mongo Vup チームでは大人数の関わるプロジェクトでどのようにして全体の進捗を管理したり、情報を共有したりすれば良いのかということを見ることができました。Notion でプロダクト別の進捗を管理したり、全体の手順書や対応するべき箇所をまとめたドキュメントを作成したりと、個人ではもちろん就業型インターンシップでもなかなか経験できない貴重な体験でした。
ライブラリの管理の大切さ
個人開発だと自分で作ったライブラリのバージョンやドキュメントに気を使うシチュエーションはあまりありませんが、組織での開発だと自前のライブラリのバージョンもきちんと管理しないと色々なところで不都合が出てきてしまいます。また、それらのライブラリのドキュメントやバージョンアップ作業で必要となるライブラリのアップデートなどの情報がわかりやすく書かれていないとコミュニケーションが必要になり、関わる人が多くなればなるほど全体の作業効率が落ちてしまいます。今回のVup作業では社内ライブラリに起因する困難がさまざまありましたが、一方で社内ライブラリを有効に活用するこで得られるメリットもまた多くあります。そのメリットを享受するためにも、ライブラリのバージョンをきちんと管理して、ドキュメントを整備しメンテナンスし続けることが必要です。
最後に
今回のプロジェクトでは色々なチームの方々と協力し、さまざまなことを学びながらバージョンアップの作業を進めていきました。今回のプロジェクトを含めインターン全体を通してとても学びが多く、社員の方々の助けを借りながらさまざまなことに挑戦することができました。このような経験をしてみたい方、プレイドに興味を持った方は下記からプレイドの開発インターンに応募してみてください!