リファクタリング
機械学習
新人プログラマ応援
サバイバルガイド
データエンジニアリング

ブラックボックス化したデータ基盤を作りなおすことを決意した貴方へ

ここ一年くらいデータパイプラインを基盤ごと作りなおしていました。毎時一億レコードくらいは捌くやつです。
わりと長く続いているプロダクトのため、いわゆる技術的負債が溜まっていたりブラックボックス化していたところも多く、当初はエンジニアを倍くらいに増やさないと対処できない見込みでしたが、みんなで奮闘した結果、チームサイズを変えず新規開発の手も止めずに、目立ったダウンタイムや障害なく移行することができました。
振り返って「こうしておいてよかった」「こうしておけばよかった」と思うところを書き残しておこうと思います。また同じようなことに直面した未来の私へ、もしくは貴方へ。

0. 不吉な匂いを嗅ぎわけよう

とあるプロダクトにジョインした貴方は、なにかしら不吉な匂いを感じました。
そうです、みんなお馴染み「不吉な匂い」です。たとえば次のようなことがあったのでしょう。

  • 使っているライブラリやフレームワークが長らくアップデートされていない。
  • ストレージやデータベースに、もう使われていないファイルやカラムの残骸がある。
  • 似たようなバリデーションや集計処理が、あちらこちらに散見される。
  • cronやスケジューラがお互いに空気を読んで設定されている。
  • スケールアップ・アウトしてみたけど、そろそろ天井にあたってサチりそう。
  • 「怖いので、もう触りたくない」「よく分からないけど、うまく動いている」という声が聞こえる。
  • and more...

パターンは色々ありますが、ありがちなのは度重なる改修によるキメラ化です。
たとえ初めは完璧に設計されていたアーキテクチャであっても、どんどん機能を追加してプロダクトの方向性が変わるうちに、やがて個々のサービスは明確な役割を見失い、ツギハギになっていきがちです。玩具のゼンマイカーを作っていたはずが、気づいたら実物大の空飛ぶキャンピングカーになっていて「あれ、この巨大なゼンマイはなんのためにあるんでしたっけ?」「んー。今となっては動力の役割を果たしてないんだけど、支柱になってて今さら抜けないし電線とかも絡んでるんだよね」「なるほど。でも空気抵抗ひどいから、竜頭だけ取っちゃいますね……」
まぁ保守するだけでいいのであれば、不吉な匂いには蓋をして何も起こらないことを祈りつづけるのも一つの手です。「動いているものには触るな」というのも一つの知恵ではあります。ただビジネスとしては現状維持という判断であっても、その場に留まるため開発ラインは走りつづけなければいけないことは往々にしてあり、とくに貴方はデータ基盤の作りなおしを決意するほどなので、現状のままでは解決できない大きな課題を抱えていることでしょう。

たとえば機械学習システムを組み込みたい。とか、そういうやつです。
ああ、そうです。この記事は『機械学習案件は本運用乗せきってからが本当の勝負、みたいなところあるので気をつけて』で辛みだけ拡散してしまった私の、セルフアンサー的な側面を持っています。今回は機会学習ではなくデータエンジニアリングの話をするわけですけれど、まともなデータエンジニアリングなしに機械学習システムなんて作れるはずもないので……。

1. 全貌を把握しよう

まずは現状あるデータフローの全貌を把握する必要があります。
不吉な匂いが放置されているということは、メンバーの異動や退職にともなって、なにかしらロストしてる可能性が高いからです。
とはいえ断片的なドキュメントやノウハウは存在しているでしょうけれど、大切なのは一つの大きな絵を描くことです。チーム総出で洗いざらい調査する時間でも取れればいいのですが、そんな余裕がない場合は下記のようなことを通して、自分なりの絵を少しずつ育てていきましょう。

障害対応しよう

バッチが落ちた、キューが詰まった、転送がこけた、そんな障害が起きた時は大変ですが、それぞれのデータ処理の立ち位置を知るチャンスでもあります。その処理がどのデータに依存しているのか、いつまでに復旧しないと後続処理が死ぬのか、代替の手順は存在するのか、復旧できない場合のビジネスインパクトはどのくらいなのか、汚れたり欠けたデータの混入はどこまで波及するのか。
もちろん本来はこのあたりのことを頭に叩きこんでおかないと復旧作業はできないので、まずはできる人の横に付くかマニュアル通りにやりましょう。そして終わったら、自分なりにメモを纏めて共有しましょう。
もしも残念なことに復旧対応できる人もマニュアルも存在しない場合は、勘と経験を頼りに分かる範囲でなんとかするしかないです。この時は慌てると二次被害を出してしまう可能性が高いので、いつも以上にダブルチェックしたり声出し確認したりするヒューマンエラー対策がそれなりに有効です。それでも運が悪いと復旧しきれないこともありますが、それはそうなってしまった状況が悪いので気を病まないでください。ただし、対応中なるべく早めに関係者へ最悪ケース込みの連絡をすることと、対応が終わった後に現状どのくらい再発可能性があるのか報告することは忘れないようにしましょう。

改修してみよう

既存の機能を改修することになった時は、棚卸しのチャンスです。
まずはAs-Isの仕様を細かいところまで洗いだしながら、「ボーイスカウトの規則」に従って単体テストを追加したりリファクタリングをしたりして、システムの複雑さをなるべく減らしておきましょう。この手の良いことは、品質向上の時間を明確に確保できなくても、現場判断で改修の見積もりやバッファに混ぜこんでしまえることです。
なのですが、改修対象には今となっては不要な仕様が紛れこんでいることがあります。要件の複雑さは設計・開発・テスト・運用の全てを鈍化させるため、本当に必要なのか怪しいなと思ったら、どのくらいのユーザーに使われているか、どのくらいビジネスインパクトあるのか調査して、適宜クローズの提案をしていきましょう。
これは日常的に取捨選択ができるチームになるための習慣づけに繋がります。そのため提案が却下されたとしても、その提案をしたこと自体に価値があるので、へこたれないでください。

データの行きて帰りし物語を追ってみよう

さて、そろそろデータフローの大きな絵が書けてきたでしょうか。
どのマスターデータがどんな画面から投入され、どのログデータがどんなユーザ行動から発生して、どういうネットワーク経路でサーバに到達して、どこのストレージあるいはデータベースに永続化されて、各サーバでキャッシュされ、どういったデータ処理に掛けられながらデータフローが分岐したり合流したりするか、だいたい想像が付くようになりましたか? 
完璧でなくていいので、まぁだいたいこんなものかなという気になってきたら、データの気持ちになってその長い旅を追ってみましょう。データの量や質や粒度がどう変化していって、どこのキューやバッチでどのくらい待たされて処理されるのか、観点を変えながら何度も追ってみましょう。
どんなデータもそれが生きているのであれば、ユーザから生まれそしてユーザの元へ帰っていきます。もしもその間に途切れてしまう流れがあるならば、そこは自動化されてなく人の判断が挟まっている部分かもしれません。データの分析結果を観た人が施策を打つとか、そういうのも含めて一つの系なので追加していきましょう。
はい。ここまで来られれば、貴方が解決したいこと対して、現状どこがボトルネックになっていて作りなおさなければいけないのか、明らかになってきましたね。

2. 作りなおしの必要性を共有しよう

貴方一人の裁量で作りなおせるものであればよいのですが。
そうでなければ、貴方の決意をじわじわとチームの決意に変えていきましょう。

ことあるごとにアピールしよう

お昼時の雑談で、期初のキックオフで、もしくは面談の場で、隙あらば貴方の切実さを訴えてみましょう。
ただ世知辛いことに、切実さというものはどうにも伝わりにくく、その本当のところは当人にしか分からないものだと思います。それでも誠実に訴えていれば「あいつがそんなに言うなら一つやらせてみるか」という向きになることがあります。そうなるように日々の仕事で信頼を築けているとよいですね。

他の人の課題も吸いあげよう

せっかく作りなおそうとしているのであれば、他の人が抱えている課題にも目を配っておきましょう。もしかしたら一緒に解決できるかもしれません。
また、そういう姿勢を見せることで、それが解決できるならと協力してくれる人が現れるかもしれませんね。

相手によってメリットを言いかえよう

経験あるエンジニア同士であれば「技術的負債の返済」と表現できるメリットも、そうでない相手にはまずピンと来ないです。ソフトウェアが外から見えれば「ほらここに九龍城塞があるじゃろ」といった感じで済むのですけれどね……。
相手によりますが、返済というより投資といったポジティブなニュアンスに言いかえた方が響くことは多いです。データ基盤を作りかえて新機能を搭載した際に得られるであろうビジネスインパクトなど、なるべく具体的に提示できると分かりやすいですね。
ここをうまく主張できない場合、サービスの質向上につながらないエンジニア欲求だけの作りなおし、いわゆる「セカンドシステム症候群」に陥っている可能性があります。気をつけましょう。

3. 現実的な計画を立てよう

紆余曲折ありつつも、データ基盤の作りなおしが認められました。やりましたね。
おそらくは言い出しっぺの法則よろしく、貴方が主導して行うことになっているでしょう。実際、この手のことは切実さを実感してる人がやらないと根本は解決されないので、そうするべきだと思います。

理想のデータ基盤上でパイプラインを描こう

まずは最新の技術を踏まえて、一から作るならこうしたいという理想図を描きましょう。この段階では、いったん移行の難易度は置いておきます。
骨子がある程度できたら多くの人にレビューしてもらいましょう。この依頼はなるべくメールやチャットで投げつけるのではなく、対面でホワイトボードなどに絵を描きながら行うと、相手も順を追ってイメージしやすいですし、その場でフィードバックを反映できます。

漸進的な移行計画を立てよう

ここまで来れば、現状と理想の図を横並びにできるようになったので、そのギャップを埋めていく移行計画を立てていきましょう。そして妥協しながら、どこまでを目標とするか決めます。
重要なのはできるかぎり漸進的にすること、すなわち移行が止まってしまってもシステムは壊れず何かしら改善されたところはあるようなチェックポイントをできるだけ増やしておくことです。とかく想定外なことは起こりうるもので、いつ約束された開発リソースが不足しないとも限りません。これが新規開発であれば新機能を諦めれば済むといえば済むのですが、移行中は旧基盤と新基盤の二重系などが発生するので、中途半端に止めてしまうと技術的負債をむしろ膨らませてしまうことが往々にしてあります。
ここではネガティブチェックが得意なエンジニアに協力してもらって、移行にまつわる障害ケースを洗いだし、それぞれテストで担保するのか切り戻しで対応するのか一つ一つ潰していきましょう。

ふたたび仕様削減と品質向上を試みよう

ここは「改修してみよう」と被るのですが、このタイミングで不要な仕様があればできるだけ削ぎ落としておきましょう。
また現状のものはいずれ捨てることになるわけですが、それでもここで品質向上を図っておくことには意味があります。作りなおす時はテスト駆動的な開発スタイルになりやすいので、ここで単体テストなどを追加しておくことが、移行時には二重系になりやすいため、ここで冪等性などを担保しておくことが、後々に効いてきます。

4. 作りなおそう

さてさて、いよいよスーパー作りなおしタイムです!
貴方は「今より良いものを作るぞ」と意気込んでいることでしょう。ただ、ここも新規開発とは異なる難しさが潜んでいるので、気をつけないといけないです。
ただ作りなおしならではの良いこともあって、それは作った後の需要は保証されていて確実にサービスの役に立つということですね。時間をかけすぎて状況が変わらないかぎりは……。

ワークアラウンドの背景を想像しよう

あらためて既存コードを読みながら作りなおしていると、一見して筋の悪いコードが見つかるかもしれません。だんだんうんざりしてきますが、なぜそんなコードが書かれたかの背景を想像する努力は怠らないようにしましょう。
というのは単にそれを書いたプログラマのレベルが低かった可能性や、時代の移り変わりによるもののほか、その筋の悪いコードが何かしらの問題を回避するためのワークアラウンドであることも儘あるからです。特定バージョンで起きるコンパイラやライブラリのバグとか、パフォーマンス上の問題であるとか、別システムとの競合とか、そういう類のものですね。
このあたりの切り分けは色んなチームでの開発経験が生きてくるところで、慣れると「ああこれは昔ながらのJava出身のプログラマで関数型の手法を知らなかったんだろうな」「そういうえばこれが書かれた頃はC++11の一部機能しか実装されてなかったな」といったところは、ぱっと読めるようになってきます。
そうした上で実際の課題に対するワークアラウンドである場合は、それが新基盤でも起こりうるのかどうか検討したのち、引き継ぐかどうか決めていきましょう。

データ不一致の許容ラインを見極めよう

データ処理を作りなおしたら、なるべく早めに本番と同じ入力データを食わせて、本番の出力データと比較してみましょう。
既存と同様の単体テストが通っていても、ここで出力が一致しないことは珍しくありません。旧コードと新コードどちらのバグということもありえますし、エッジケースのテスト漏れかもしれませんし、基盤の特性によることもあります。そもそも冪等性が守られてないのかもしれませんね。
一つ一つ潰して完全一致させられれば幸せなのですが、そもそも移行の要件として本当に完全一致させる必要があるかどうかは頭に留めておきましょう。誤差が0.1%内に収まれば良いというふうに許容ラインを決められるなら、それ以上の誤差を詰めるのに時間をかけすぎるべきではないかもしれません。

スケジュールの見通しは小まめに共有しよう

そもそもソフトウェア開発の長期見積もりというものは恐ろしく難しくて、正確なところは神ならぬ人間には無理です。
なので一般的に言えることなのですが、とくに作りなおしは外部から成果が見えにくいので、当初のスケジュールから遅れが目立ってくると、その意義が問いなおされてしまうことがあります。
そのためスケジュールを伝える時点で、そもそも見積もりは不確実なものであること、そして遅れそうになった場合はどんな困難に直面していてどう対処しようとしているのか、なるべく真摯に伝えるようにしましょう。

5. メンタルを腐らせないようにしよう

作りなおしは外部から意義が問いなおされてしまう、と書きましたが、実は携わっている当人たちも目的を見失ってしまうことがあります。
ときには貴方自身を含めた、チームメンバーのメンタルにも気をかけてみましょう。

先人に敬意を払おう

旧システムと格闘していると、いわゆる「クソコード」のような言葉が頭に過ぎるかもしれません。
実際、客観的に見てそうだったとしても、どうか先人を否定しないようにしてください。その開発者は無茶ぶりされながらも奮闘した新人だったかもしれませんし、その頃には今となっては周知のベストプラクティスが存在しなかったのかもしれません。何より「役に立たないコードより、役に立つクソコード」は一つの真実です。
それでも、これはないだろうと思うこともあるでしょう。おっけーです。ソフトウェア開発は総体として進化していくものです。今度はもっとうまくやりましょう。
ただ、それはそれとして辛みを貯め込んでしまうと辛いので、あくまで事象について愚痴は適度に共感しあったり昇華できるといいですね。

前に進むたびに喜ぼう

モチベーションが擦り切れてしまわないように、チェックポイントを通過するたびに喜び、もしくは褒めるようにしましょう。やはり論理的なソフトウェア開発といえど、人間が為すことである以上エモーショナルなところは大切です。
貴方は作りなおしを初めた張本人であるため、その意義についてはよく理解していることと思いますが、どうしても他のメンバーは腹落ちしないまま参加している場合もありますので。

たまには小さな新規開発をしよう

作りなおしが長期化した場合、少し手を休めてちょっとした新規開発に携わると、良い気分転換になります。
デプロイ用ボットなど開発ツールの類もいいですね。

6. 移行しよう

配管工事の始まりです。慎重かつ大胆にやっていきましょう。

いつでも切り戻せるようにしよう

どれだけ入念に手順を作って、ステージング環境で練習したとしても、やはり移行作業中に予期せぬことは起こりえます。
繰り返しになりますが、なるべく手順を小さく小さく切り分けて、どの段階でも切り戻せるように備えましょう。
その基本パターンについては、別記事を起こしたのでそちらをご覧ください。

中間データのスキーマを安全に変更する手順

インフラコストの一時的な上昇を許容しよう

たとえ新基盤の方がインフラコスト安いとしても、移行中は二重系になるためコストは上がります。
コスト管理者が驚かないよう、事前によく説明して許容してもらいましょう。

後片付けをしよう

せっかく技術的負債の返済をしたのですから、新たなる技術的負債を生みだしていないか振り返ってみましょう。
旧いインスタンスを潰したりデータをアーカイブしたりするほか、忘れがちなのが新しい方に埋めこまれた移行用のデータやコードの後片付けです。ウィニングランの勢いでやりとげましょう。

7. 全てが終わったら

お疲れ様でした。関わった人全員で、やりとげたことに胸を張ってください。
でも、なぜ作りなおしたかの理由に立ち返ってみれば「本当のはじまりはこれから♪」なので、おそらくはだいぶ品質重視に寄ってるであろう気持ちを切り替えて、新基盤の上で色々やっていきましょう\(^o^)/