SmartHRのインフラの変遷

内藤研介氏:開発言語とコミュニティへの貢献に続きまして、ここからはインフラとか開発環境について、お金と技術的選択を軸にお話ししたいと思っております。

一般的に、私たちのようなB to Bサービスは、B to Cサービスに比べるとユーザー数は少ない傾向にあります。その中でもSmartHRは、主に社内の管理者が使うサービスなので、サービス自体の特性としてインフラの投資がめちゃくちゃ必要なものでもないです。

こちらの数字は、2015年11月に本番リリースした際のサーバ費用です。ステージング環境も含めた費用ですね。非常に経済的なアーキテクチャだったわけです。料金のうちのほとんどはAWSの、RDSというデータベースのサービスです。あとはElastiCacheというキャッシュのサービスとか、そういった部分が占めていました。

詳しい方が聞かれるとちょっと驚かれるかと思うんですが、Webサーバーはt2.microとかt2.smallとか、あとDBはt2.smallとか。非常に「経済的な」サービスを使ってました。

ユーザー数は、最初はもちろん少ないので、あんまりリソースがないとはいえ、本番環境ではまあまあ心もとない環境だったんですね。でも、これはこれでいいところもあったと思うことがあります。

ちょっと話が変わるんですけど「トヨタ生産方式」という言葉をご存知ですかね。いろんな取組みの総称なんですが、すごくざっくり言うと「現場の無理とか無駄をなくして効率的にしよう」という生産方式です。個人的にその中でお気に入りの概念が「在庫を持たない」です。在庫を持たないことで、ボトルネックを見つけて改善をしていくという発想です。

この発想がよく遠足に例えられるので、紹介したいと思います。遠足で1列に並んで、長い道のりを子どもたちに歩かせる場合に、先頭を足の速い子にして、最後尾を足の遅い子にする。そうするとどんどん列の幅が広がっていって、結局管理コストが上がって到着が遅くなる。

それよりも、列の先頭を1番足の遅い子にすると、その子を改善していけば最終的な到着時間が早くなる。工場のライン生産も同じことが言えるんですね。「在庫を持たない」というのは、一番能率の悪い機械のスピードに合わせて製品を生産することを意味しています。「一番足の遅い子」は、つまり一番能率の悪い機械ですね。それを改善していくと、全体の効率が良くなるという発想です。

ちょっと話が脱線しちゃったんですが、システムアーキテクチャもわりと、これに近いことが言えると思っています。ユーザーが満足するギリギリのラインのパフォーマンスでアーキテクチャを構成することで、ボトルネックが明らかになりやすくなる。改善ポイントが見つけやすくなるんじゃないかと考えております。

緊張感は多少あるんですけど(笑)、ギリギリのラインですね。実際そうやって見つかったパフォーマンスのボトルネックは、わりと早い段階で潰せてきました。人件費と比べればサーバー費用は安いもんですし、スケールアウトも簡単な時代ですし。「とりあえずサーバー追加しとこう」ってなりがちですけど、それはそれでボトルネックを見つけづらくして、負債の先送りになってしまう傾向があるのかなと感じることはあります。

サービスの成長とアーキテクチャの変化

今年の11月のサーバー費用です。サービス開始から丸3年経ちました。おかげさまで順調にユーザーさんも増えてきて、売上は1ヶ月目の売上なんで微々たるものですけど、それと比べてかなり伸びてきましたし、サーバーの費用も20倍ぐらいになりました。アーキテクチャも随分変わりましたね。とくに大きかったところがDBまわりです。

SmartHRでは、サービスリリース当初より「マルチテナントアーキテクチャ」というアーキテクチャを使ってサービスを提供しています。マルチテナントアーキテクチャってなんだろう、と思われるかもしれませんが、ざっくりいうと「1つの環境を複数のお客様で利用しよう」というアーキテクチャです。

お金に糸目をつけないなら、お客様ごとに環境をつければいいんですけど、なかなかそういうわけにもいかないので。世の中のクラウド型サービスといわれている部分の多くは、こういったマルチテナントアーキテクチャを採用しています。

マルチテナントアーキテクチャの中でもさまざまな形態があるんですが、SmartHRでは apartmentというgemを使って、MySQLでいうところのデータベース、PostgreSQLでいうところのスキーマ単位で、お客様のDBを作っていました。

テナントごとにわざわざDBインスタンスを作らないということは、まぁ理解できると思うんです。RDSインスタンスをお客様ごとに立てるのはなかなか、コストもかかることですし。でも「テナントごとに作る必要もないんじゃないか」というような発想もあると思うんです。確かに、それも1つの方法です。

ただ、私たちが一番避けたかったことは、開発者の考慮漏れで他のテナントのデータが別のテナントから見えるとか、ほかのテナントのデータを更新しちゃうといったことです。開発者のミスでデータが漏洩することを、仕組みで防ぎたかったんですね。

私たちのサービスは、人事労務の情報を扱うわけです。給与の情報があったり、マイナンバーの情報があったりと、かなりセンシティブな情報を扱っている。万が一に備えて「うっかりミスで漏れる」を仕組みで解決したかったので、こういったアーキテクチャを決めました。

アーキテクチャが抱えていた問題

RDBMSに詳しい方でしたら察しがつくかもしれないんですが、こういった技術的選択は、非常に大きなリスクを持っていました。それは「データのマイグレーションに非常に時間がかかる」ということです。

どういうことかといいますと、テナントごとにスキーマを用意しているので、SaaSの特性上、すべてのスキーマを同じ状態に保つ必要があるんですね。いざテーブル定義を変更しようとした場合、テナントの数だけalter tableが走るわけです。

1つのalter tableに0.5秒かかるとして、例えば100テナントあったら50秒かかる。数百~数千くらいまでだったら、まぁメンテナンス時間をもってやればできるんじゃないかな、みたいな感じですけど。

ただ、それ以上にテナントが増えてくると、だんだん管理コストが積み上がってくるわけです。

先ほどもちょっとあったんですけど、私たちのサービスの特徴として、様式の決まった書類を作るというものがあるんですね。DB上には大量の書類用のカラムがあるわけです。なので、しょっちゅうテーブルの更新が走る。それこそ週に数回、テーブルの更新が走ったりもするわけです。

結局、あるときトラブルが発生して。マイグレーションが終わらなくて、サーバーのメモリとかCPUとかリソースを逼迫してしまって、結果的に数時間のサービスダウンが発生してしまいました。

それ以前も、アーキテクチャの問題には気付いていたんですけど、本腰を入れて対応できてなくて。実際にトラブルが起きたあとにようやく、本格的なアーキテクチャの見直しを検討することになりました。

アーキテクチャをCitus Cloudへ移行

ただ、アーキテクチャの見直しから実際に刷新されるまでには、けっこう時間が必要でした。その間もサービスは成長して、ユーザーも増え続けて。データマイグレーション含むサービスのメンテナンスを、深夜にエンジニアが張り付いてやる。かなりしんどい状況になっていました。

結局、どうやって問題を解決したかといいますと、これまでよりちょっとだけお金を余分に払って、外部のDBサービスを利用することにしました。

Citus CloudというDB as a serviceです。こちらはPostgreSQLを拡張したサービスで、テナントごとにデータを分割してくれるという、まさに私たちの望んでいたようなサービスだったんですね。

実現したかったことは2点あります。もともとこういったアーキテクチャを採用した理由である、開発者がテナントのデータを意識しなくても済む、データの漏洩を考えなくても済むという状態。それから、今回発生した、マイグレーションに時間がかからないという問題。この2点をうまいこと解決してくれるサービスなわけです。

とはいえ、普通にAWSのDBサービスを使うよりはお金がかかるわけです。ただ、サービスも十分に成長していましたし、今後の成長も見込めましたし。深夜メンテナンスが不要になるということで、エンジニアの負荷も下がる。結果的に、迅速にお客様に価値を届けられるということで、十二分にペイすると判断しました。

振り返ってみて、初期の技術的選択、マルチテナントアーキテクチャでテナントごとにスキーマを用意するという選択が、適切なものだったかどうかは議論の余地があると思っております。そもそも先ほどのCitusというサービスはリリース当初にはなかったんですけど、それは置いておいて。

売上を生み出していないサービスに対して、はじめから商用のがっつりしたサービスを入れることはオーバーエンジニアリングになりがちですし、開発のスピードを殺してしまう恐れもあるわけです。一方で、まずい技術的選択は将来に大きな負債を残すことになります。

私たちエンジニアは常に、オーバーエンジニアリングと、こういった将来にわたる技術的負債、そういったバランスを取りながら技術的選択をしていく必要があると考えております。そして、結果的にサービスがうまく成長して、ユーザーが増えて、売上が上がると、取りうる技術的選択は広がると思っております。

最後に、これまでのまとめです。

お金のないスタートアップにとって、素早く開発・検証できる言語が正義だというお話をしました。そして、お金があってもなくてもコミュニティへの貢献はできるというお話をしました。最後に、オーバーエンジニアリングと技術的負債とのバランスを取りながら、技術的選択はなされるというお話をしました。

トヨタの生産方式の例を挙げて、最小限のアーキテクチャでボトルネックを明らかにする方法があるという紹介もしました。最後に、サービスが成長するとより良い選択を取りやすくなるというお話をしました。

ちょっと駆け足だったんですが、以上です。みなさまの参考になれば幸いです。ご清聴ありがとうございました。

(会場拍手)