10年モノのサービスをアーキテクチャから再設計─はてなブックマークがScalaとDDDを使う理由

10年以上運用されているサービスには、さまざまな技術的な負債が発生しています。今後の継続的な改善のため、いったん新規開発を止めて4年かけて全面的なリニューアルを実施した「はてなブックマーク」の開発者に、プロジェクトの課題や解決する手法などを聞きました。

10年モノのサービスをアーキテクチャから再設計─はてなブックマークがScalaとDDDを使う理由

インターネットの自由な情報流通を促進した「Web 2.0」とほぼ同時期、2005年に生まれた「はてなブックマーク」は、コメントを介してユーザーどうしのコミュニケーションを支援する、日本最大のソーシャルブックマークサービスとして提供され続けています。しかし、動きの速いWebの世界で、10年以上運用されているサービスを改善し、最適化し続けていくのは困難なことです。

はてなブックマークの開発チームでは、10周年を迎えた2015年、次の10年を見据えて完全にリライトし直すことを決定し、約4年をかけてプロジェクトを完遂させました。部分的なリファクタリングではなく全面的に書き直した狙いや、リニューアルプロジェクトで直面する課題や解決する手法について、アプリケーションエンジニアの伊奈林太郎さんと谷口力斗さんに聞きました。

1

伊奈 林太郎(いな・りんたろう) 2 oarat 3 tarao(写真右)
京都大学大学院情報学研究科修了。日本学術振興会特別研究員を経て、2013年株式会社はてな入社。2015年よりテックリードとして「はてなブックマーク」リニューアルプロジェクトを主導する。ブログ:貳佰伍拾陸夜日記
谷口 力斗(たにぐち・りきと) 4 tanishiking 5 tanishiking(写真左)
京都大学工学部卒。学生時代からアルバイトとして、また2017年の新卒入社後も一貫して「はてなブックマーク」リニューアルプロジェクトに携わる。現在はサービスプラットフォーム部に所属。ブログ:たにしきんぐダム

改善1つに数カ月かかるなら全てを書き換えられないか

──リニューアルを決定した時点で、サービスの開発はどういう状況だったのでしょうか?

伊奈 はてなブックマークのソースコードは、2008年に一度大きく書き変えられていますが、それ以降はモノリシックなコードを少しずつ拡張しながら使い続けていました。2005年に初めてローンチした際に書かれたコードも、管理画面の一部に残っていましたね。

そのため、ここ数年の大きな課題は、ちょっと改善のために手を加えるだけでも、けっこうな開発工数がかかってしまうことでした。パフォーマンスを改善するのに根本的なところを直さなくてはいけなかったり、何か新しい機能を追加するときにも、古いコードに手を付けないで実現できることは限られていたり……。

谷口 既存の機能に関係するところでコードを改変するとリグレッションが起きる恐れもあったため、できるだけ既存のコードに触らず、新たにコードを書こうという空気が強かったですね。

伊奈 その結果、似たような処理を行うメソッドを別に生やして……となるので、さらにコードが肥大化することになっていました。

谷口 新しくプロジェクトに加わった人が、複数の似たようなメソッドのうち「どっちが本物?」と悩むこともありましたね。はてなブログなど社内の別のサービスからも、APIなどではてなブックマークの機能を利用したい場合などに「もうちょっと何とかならないか」という声が上がったりもしていました。

2000年代にトレンドだった開発手法の負債

伊奈 開発サイドとしても、常にサービスをよりよいものにしていこうという思いがあります。ただ、10年以上運用してきたサービスに影響を与えずに実現しようとすると、どうしても時間がかかってしまうのも事実でした。

──どういったところに時間がかかる要因があったのでしょう?

谷口 当時のソースはPerlで書かれていましたが、コードベースが約40万行と非常に大きい上に、社内で独自に作成したWebフレームワークやO/Rマッパーを利用していたこともあって、コードの構造も独特でした。新たに「こんな機能を作ろう」となっても、関連するコードを探すだけで時間がかかっていました。

伊奈 独自フレームワークが悪いわけではなくて、2000年代半ばのトレンドとしては、それが便利で高速に開発できる方法でした。けれど10年~15年とたって、ソフトウェアの開発手法も変わっていく中で、それを継承するのにちょっと失敗してしまったのかなと反省しています。

過去の開発意図を探る考古学的手法

谷口 ただ、はてな社内には日記文化が根強くあるので、「どういう経緯でこうしたのか」「どういう理由でこうなっているのか」といったことは、みんな社内Wikiなどに書いてあるんですね。なので、Gitのコミットログを見てだいたいの時期を特定して、既に退職してるエンジニアの当時の社内ブログを読んだりしていました。

伊奈 そういった作業を僕らは「考古学」と呼んでますが、サービスの立ち上げ当初にはWebエンジニア界隈を巻き込んだ技術的な議論がはてなダイアリーなどでも繰り広げられていたので、それも追いかけました。

何かを作り直すときには、そうやって過去の経緯を調べてまとめなおし、当該の日記やコメントを参照しながら「こういう経緯なんだから、現代ではもう忘れていい事柄なんじゃないか」と議論することもありましたね。

──コードの意図を探るだけでも手間がかかっていたんですね。ただ、完全なリニューアルではなく、部分的なリファクタリングを積み重ねて改善していくこともできたのではと思いますが?

伊奈 例えば、はてなブックマークのデータを利用するアプリを改善しようとして「サーバサイドにこんなAPIが必要だ」となり、あわせて大規模なリファクタリングをしたこともありました。ただ、1つ機能を作るたびにそれと同じことが起きていたんです。

こうした悩みは以前から認識しており、過去にも何度かWebサーバやデータベースへの接続周りで大規模リファクタリングに取り組んだこともあったのですが、残念ながら部分的な改修ではうまくいきませんでした。

データセンター移行も見据えて刷新しよう

──とはいえ、実際に動いているサービスを全て書き直す判断は難しかったのではないですか?

伊奈 当時の開発ディレクターに「パフォーマンスやシステム安定性のための改善について、既存のイシューを個別に全て対応したらどのくらいの期間がかかるか?」と相談されて、率直に「どうしても3年はかかるし、その間は新機能の開発もできなくなる」と答えたことがありました。ちょうどデータセンター移設のプロジェクトが動いていたこともあって「いずれどこかでやらなければならない話ならば、ここで刷新しよう」という判断が下りました。

──少しずつ修正しても同じ開発期間とコストがかかるのなら、全て置き換えようということですね。

6 (2)

ドメインモデル設計とScalaとマイクロサービス化

──プロジェクトを開始した当時のことからお聞きしたいのですが。

伊奈 プロジェクトの開始は2015年です。全面移行を私ともう1人で進めることになり、後に谷口など2人が加わって、最終的にチームのエンジニアは4人になりました。

谷口 僕がアルバイトとして入社したのは2016年6月ですが、それ以降ずっとプロジェクトに関わってきました。

──先ほど、サービスのローンチ当時と比べて開発のトレンドも変わってきているという話がありましたが、全体を書き直すために採用した開発手法などを教えてください。

伊奈 まず、マイクロサービスです。はてなブックマークには「Webページを表示する部分」「裏側で人気エントリの計算やコメント集約を行う部分」「外部のサイトにHTMLを取りにいく部分」などいくつかのシステムがあり、それぞれ性質が異なります。毛色の違うシステムをマイクロサービスとして分けた方がいいなということは、旧システムの頃から思っていました。

コアロジックにはScalaを採用

──2015年のイベントで、開発言語にScalaを採用することを早々に発表していますね。

はてなブックマーク in ScalaScala関西 Summit 2015 発表資料)

伊奈 マイクロサービスとして分けるなら、システムごとに言語が違っていてもかまわないわけです。そこでコアロジックの部分は、リファクタリングしやすく、継続的にメンテナンスしやすく、ドメインモデルをきちんと表現できる方がいいと判断し、他のサービスで採用した実績もあることからScalaにしました。

Scalaで書いていると、壊れた書き変え方をするとコンパイルが通らなくなるので、すごくリファクタリングしやすいんですね。

一方、フロントエンドのWebページを表示する部分は、文言を変えたり、デザイナーさんが手を加えたりと、ちょっとした変更をたくさんすることになるため、ライトウェイトな言語の方がやりやすいだろうという理由で、Perlにしました。Perlなら社内に開発者もたくさんいますから。

ただ、はてな社内にはマルチリンガルなエンジニアが多いですし、1つの言語を知っていれば他の言語の習得も早いですから、特定の言語を優先するのではなく、適材適所で決めています。一部のマイクロサービスでは、Goも使っています。

きちんとしたドメインモデルによる設計と実装を継続したい

──いま「ドメインモデル」という言葉がありましたが、当初からDDD(Domain-Driven Design、ドメイン駆動設計)で進めたということでしょうか。

伊奈 はい。設計時には、ドメインモデルをきれいにするところに非常に気を使いました。古いシステムでは、例えば同じ「interest」という名前なのに、出てくる場所によってそれぞれ別の機能、違う意味を持つことがありました。

そういったことをなくしたかったので、この機能の「興味」とは何を意味するのか? それはコード上のどの名前と対応するのか? といった「語彙」をしっかりと定義しました。

また、長年開発を続けてきたコードベースがぐちゃぐちゃになってしまう理由の1つに、最初はきちんとレイヤを分けて「何のロジックはどこに書くのか」を決めていたのに、より処理しやすくしたいとか、いろいろな理由でそのルールが崩れていってしまうことがあります。

そこで移行作業の最初に、「ドメイン層とは何か」「ドメイン層とアプリケーション層(ユースケース層)とは何が違い、何を入れるべきなのか」「コントローラ層がファットにならないためにどうしたらいいか」といった事柄をかなり時間をかけて議論し、決めていきました。

新たに加わってきたメンバーにものその結果を共有し、一緒に議論しています。それを踏まえて、「こういう機能を実装する際は、ここにこういうコードを書きます」といった事柄をチュートリアル風にまとめた「開発の手引き」的なドキュメントをしっかり書いて、Gitで管理しています。

谷口 僕が最初にチームに加わった時点で既に、こういったルールやドキュメント類の整備が徹底されていたので、ドメイン知識の習得は容易でした。エンジニアが何か機能を追加しようと思ったときに、そのコードをアプリケーションレイヤに書くべきか、ドメインレイヤに書くべきかといった事柄を皆がちゃんと意識していますし、コードレビューのときも議論になったりします。

8

段階的なリリースとデータの移行という2つの大きな課題

──移行プロジェクトはどのように進めたのでしょう?

伊奈 まず、ベースの方針を固めながら、基盤部分やプロトタイプを作ってみるPoC(Proof of Concept、概念実証)フェーズを、1年ほどやりました。一般には、プロトタイプは捨てて作り直しになるんですが、これがけっこううまくできたので、そのまま使い続けることにして開発を進めていきました。

谷口もジョインして実装を進め、あるページをブックマークしたり、コメント一覧ページを閲覧できたりするプロトタイプを社内向けにリリースしたのが2016年末でした。あわせて、人気エントリーのランキングアルゴリズムも一新しています。チューニングしているので外からは分からないと思いますが。

──実際に書き換えを進める上で気づいたことなどありますか?

伊奈 ありがちですが「いつかどこかで使うかもしれない」という感じで、残っているけれど実は使っていないコードが旧システムには多かったですね。たくさん残っていたデッドコードも、書き換えでは徹底的に消しました。

求められる機能に沿ったデータベーススキーマに再構築

──リプレース期間にはデータの移行も必要になりますね。

伊奈 はい。データの移行では苦労しました。これまでの10数年でブックマークされたデータを旧システムから新システムにコピーするんですが、それが何億件とあります。

さらに、古いデータにはhttp:///が一個しかないものから、まったく文字化けしているものまで、さまざまなひどい状態のデータも含まれていて、移行作業の途中でつっかえてやり直すこともありました。

谷口 新システムの設計にあたって、データベースのスキーマ自体も本来あるべき構造に変更していました。そのため、古いデータを新システムのデータベーススキーマにマッチするよう変換するのですが、そこでも工夫が必要でしたね。

──スキーマにはどういう問題があったのですか?

伊奈 旧システムでは、URLが一意になっておらず、URLが同じ複数のデータレコードが存在できたんです。これに対して、新システムではURLがユニークで、同じURLはデータベースに一件しか入れられないようにしました。これは旧システムで指摘されることが多かった、同一の記事なのにコメント一覧のページが分散してしまう問題の解決にも必要でした。

そこで、旧システムで重複して入っていた同一のURLを何とかしてマージする作業を、データ移行ではひたすら続けました。

ただ、段階的な移行を進めたので、ひどい状態のデータの除去やURLのマージといった作業を何度かこなすうちに、データ移行そのものの勘所もだんだん分かってきて、かなり手早くできるようにはなりました。

新旧の2システムを維持しながら段階的にリプレース

──そう言えば、新しいシステムはいわゆるビッグバンリリースを避けて、段階的にリプレースしていますね。

伊奈 はい。コスト的には一発置き換えもあり得たのですが、段階的なリリースの方がチームとしても進めやすかったので、選択できてよかったです。

本番リリースは、2017年に入ってから少しずつ進めていきました。8月にまずコメント一覧ページを置き換えましたが、そのリリースのエンターキーは谷口に押してもらったんです。

谷口 エンジニアとしての初めてのリリースが新旧のシステム切り替えで、さすがに緊張しました。

──システムを止めることなくリプレースを進めていますが、どういった点に注意しましたか?

谷口 リプレースの期間は、新システムと旧システムが並行して動いていることになるので、データを同期させる必要がありました。コメント一覧ページだけを新システム側に切り替えたときも、2つのシステム上でデータの整合性がうまく取れているか、機械的な整合性チェックだけでなく、CSチームにQAを依頼するなどして、2カ月ほどしっかり検証した上で移行しています。

開発のスピードと妥協しない実装の兼ね合いを探る

──お話を聞いていると、何億件もある状態が必ずしも良くないデータをきれいに移行するには、かなりトライアル&エラーで時間がかかるように思いますが。

伊奈 はい。そういったところではスピードだけを重視せず、妥協しないでちゃんと実装しようという方針で進めました。スタートアップが新規サービスを立ち上げるときにはスピードが大切だと思いますが、はてなブックマークのように古くからあって、たくさんのユーザーさんがいて、たくさんのデータ資産もあるサービスでそうはいきません。

谷口 長期運用に耐える形でちゃんと設計しようという意識は、チームで共有していました。

伊奈 特に基盤寄りのところを中心に「スピード命」ではなく、できるところは落ち着いてきちんと実装するようにしてましたね。

──とはいえリソースは有限ですから、実装を取捨選択することもあったかと思いますが。

伊奈 はい。新しく作り直しているのに旧システムで新機能を開発してしまうと、作り直すべきものが増えていくだけで、一向に開発が終わらなくなってしまいます。そのため新機能もリリースできず、HTTPのままで提供する期間も続いてしまいました。

また、旧システムに10年間で追加されてきた機能にしても、全てを移植していくと開発の工数が必要なだけでなく、特定の機能でしか使わないアルゴリズムを実装することになったり、全体としてバランスがよくないことが起こります。

ですから、その機能が「本当に使われているのか?」「これから拡張できる余地があるのか?」などを考えて、残す機能と削る機能を分けることになりました。そのため機能を終了したり仕様変更することになり、ユーザーさんにはご心配をおかけしました。

しかし、新システムへの移行は2019年3月にほぼ完了し、5月には旧システムのサーバが全て撤退しました。サービスのレスポンスも早くなり、開発していても非常に快適ですから、ここからまた新たな価値を届けられると考えています。

年月 出来事
2015年8月 公式リリース10周年。Scala関西 Summit 2015で発表
2015年9月 ドメインモデルを整理し、PoCフェーズを進める
2016年12月 新システムを社内リリースする
2017年8月 新システムのうちコメント一覧をリリース。段階的な置き換え開始
2017年11月 ブックマーク一覧を新システムに置き換える
2018年2月 お気に入りフィードを新システムに置き換える
2018年3月 トップページやカテゴリーを新システムにし、UIの置き換えが完了
2019年2月 データ移行が完了し、複数URLのコメント一覧が統合される
2019年5月 リニューアルとそれに伴うサービス全体の常時SSL化が完了

はてなブックマークのリニューアルのタイムライン

9

HTTPS化、パフォーマンス向上、継続的に改善できる構造

──リニューアルして良かったことは何でしょうか?

谷口 例えば「WebサイトをHTTPS化したら、HTTPで付いていたはてなブックマークがゼロになってしまった」といった声をいただいていたんですが、そういったときにも自然な形で統合できるようになりました。

伊奈 新システムでは、それまでHTTPでブックマークされていたページがHTTPS化しても、リダイレクトされていればコメント一覧ページが統合されるようになっています。旧システムでは、先ほども説明したようにデータベースのスキーマからそういった設計になっておらず、実現が難しい状態でした。

──そういえば、サービスそのものもHTTPS化されていますね。

伊奈 新システムへの移行に伴って、はてなブックマーク全体のHTTPS化もスムーズに実現できました。これも、新たにシステムを作り直したかった理由の1つです。

激減したサーバ負荷

──サービスの負荷という面ではどうでしょう?

谷口 かなり減りました。やはり、あまりよくないデータ構造の上に立ったシステムは、実際の要求とのギャップを埋めるためにアプリケーション側で余計な処理を挟むことになり、CPU負荷が高くなります。

新システムでは正しいデータベース設計にできましたから、それだけでパフォーマンスがかなり上がります。その分、CPU使用率やメモリ使用率が大きく下がりました。

このあたりは、旧システムから新システムに切り替える際に目に見えて変化したんです。

例えば、コメント一覧ページのレスポンスを見ると、旧システムでは250msec以内にレスポンスを返すことができたリクエストは40%くらいしかなかったのですが、新システムでは90%以上、ほとんどが250msec以内でレスポンスを返せるようになっています。

10 いかにして我々は10年もののPerlプロダクトをScalaでリプレースしたかScalaMatsuri2019 発表資料)

伊奈 Webサーバの台数もかなり減らすことができました。以前と比べて約半分の台数で処理をまかなうことができます。サーバのスペックにも違いがあるので一概には言えませんが、データセンターの使用料をはじめ、コストメリットもずいぶんありますね。

谷口 リリース頻度もかなり上がって、今ではほぼ毎日リリースできるようになっています。リリース作業に要する時間もずいぶん短くなりました。

伊奈 サーバ台数が減ってリリースが改善したので、デプロイも一瞬でできるようになっています。

細かくリファクタリングし続けられる状態に

伊奈 振り返ってみると、HTTPS対応も含めて、新システムへの移行はかなりうまくいったのではないかと思います。この先もよりよいサービスを提供し続けるため、細かくリファクタリングして改善しやすい状態にできましたし、それだけでなく、チームのメンバーもパワーアップし、強力に育ってくれたという面でも非常によかったです。

信頼できるコミュニケーションの場を提供し続けるために

──この先、大規模なリファクタリングに取り組む方がいて、もしアドバイスするとしたら何を伝えますか?

伊奈 まず、データ移行は片手間ではできないので、ちゃんと時間を確保しておくといいですね。

──先ほど聞いた話ですね。確かに大切です。

伊奈 もう1つは組織の話ですが、関係者全員を巻き込む体制を早めに作っておいた方がいいと思います。例えば、はてなブックマークはサービスがある程度成熟し、収益を上げている状態ですから、カスタマーサポートやネイティブ広告の営業、特集を編成する編集者といったステークホルダーが多く、「この仕様はこう変更してもいいだろうか?」といったことを、そのつど調整する必要がありました。

谷口 「どんな機能にしましょうか」「どういう名前にしましょうか」といったことを、例えばサポートの人も巻き込んで、一緒に議論していきました。

伊奈 先ほども説明しましたが、ドメインモデルをどう設計するかについてのイメージは、僕らよりもそういった人たちの方が専門家ですから、例えば「サポートがここで言う『非表示』とは何を意味するのか?」などをちゃんとヒアリングして反映しないといけません。

調整コストや時間はけっこうかかりましたが、旧システムをそのまま移行するのではなく、よりよいものにしていくには、こうしていったん整理することが必要だと思います。

──組織として取り組む上で大切だったことは他にありますか?

伊奈 はてなブックマークは、常に新機能の開発が求められるスタートアップのフェーズではないので、会社の理解もあり、旧システムから新システムへの切り替えを一気にやるのではなく、同時並行で動かしながら段階リリースすることができたのはありがたかったです。

谷口 スタートアップのように競争が激しいと、サービスの成長を鈍らせて時間をかけてまでシステムの刷新を行うという決断を、会社に納得してもらうのは難しいかもしれませんね。

──お二人がこれから取り組んでいきたいことは何でしょう?

伊奈 やはり、信頼あるコメント、信頼あるコンテンツをうまくピックアップして見せるようにしていきたいですね。健全な議論が行われる場、コミュニケーションの場としての信頼性を高めていく、そこをどんどん強化していきたいなと思っています。

谷口 はてな全体では、新たに「サービスプラットフォーム部」という組織を立ち上げました。僕もその一員になっていますが、さまざまなサービス基盤を整備することで、その上に乗っているサービスを安心して利用してもらえるようにしたいですね。

11

取材・執筆:高橋睦美

若手ハイキャリアのスカウト転職