STORES Product Blog

こだわりを持ったお商売を支える「STORES」のテクノロジー部門のメンバーによるブログです。

STORESってMongoDBを使ってるらしいけど正直どうなの?

f:id:morihirok:20201005113654p:plain:w300

STORESのECサービスを開発している@morihirokです。

STORES ECはRuby on Railsで開発されているWebアプリケーションですが、データベースにはMySQLPostgreSQLといったリレーショナルデータベースではなく、MongoDBを採用しております。

この記事ではカジュアル面談等で必ず聞かれる「MongoDBって正直どうなの?」といったところを、ストレートにお伝えできればと思います。

なぜMongoDBを採用しているのか

そもそもなぜMongoDBを採用しているのか。それは考古学になるのでフィールドワークが必要です。筆者も開発に携わるようになったのは2018年の終わり頃からなので、まずは一緒にSTORES ECの歴史について紐解いていきましょう。

STORES EC(旧STORES.jp)は、heyグループとなるずっと前の2012年、会社名がブラケットだった時代にリリースされたWebサービスです。 2012年のブラケットがどういう会社だったかというと、いくつかのWebサービスを運営しながら受託開発を行っていた少人数のベンチャー企業で、STORES.jpはそんなブラケットの新サービスとして開発されました。

f:id:stores-tech:20201002174559p:plain
STORES ECの歴史年表

当時のことを知るエンジニアは社内に誰もいないので真相は定かではありませんが、2012年といえばNoSQLに勢いがあった時代なので、挑戦的な技術選定をしてみよう!ということだったんじゃないかなと想像しています。 ひとつのサービスに社運をかけリソースを全集中させる、という状況の会社であれば技術選定も枯れた方向に倒れそうですが、いくつかのサービスを運営している状況ではチャレンジングな方向に倒れそうというのは理解できます。

とにもかくにも、STORES.jpは2012年リリース前の日付で残された mongoに移行(途中)というコミットメッセージを皮切りにMongoDBを採用しました。 STORES.jpはリリース後すぐに市場から高い反応を受け、ブラケットは次第にSTORES.jpにリソースを集中していき、最終的にはSTORES.jp一本に絞った経営へと舵を切ります。

そしてheyグループとなり今日まで成長してきました。その間もずっとMongoDBが側にいました。

MongoDBを採用していてちょっと大変なこと

弊社プロダクト開発チームでは「KPTみたいな振り返りをするときにポジティブなこと、ネガティブなこと、という順番でやると振り返り後に気持ちが暗くなる」という学びを持っているので、先に大変なことから話します。

設計の感覚がちょっと変わる

RDBを使っているRailsアプリケーションはORマッパーとしてActive Recordを採用していると思います。 Active Recordがどういうライブラリかというと、「RDBを適切に設計しそのテーブルの名前をモデルクラス名、カラムをオブジェクトのプロパティとして扱うと、モデルクラスとしても良いものになる」という思想のもと作られているライブラリだと理解しています。

一方MongoDBを使っているSTORES ECはODマッパー(MongoDBはRDBのレコードに該当する概念としてドキュメントという言葉を使用しているのでO"D"マッパーとなる)としてMongoidというライブラリを採用しています。 これは思想としてはActive Recordと同じくそのコレクション(RDBのテーブルに該当する概念)の名前をモデルクラス名とし、フィールド(RDBのカラムに該当する概念)をオブジェクトのプロパティとして扱うというものになります。

github.com

しかし、MongoDBとして適切にデータモデリングするとモデルクラスとしても良いクラスになるかと言われるとそうではありません。 それはRDBとNoSQLの設計方法の違いに起因していると考えます。

RDBでは適切にデータモデリングできると柔軟なクエリが発行できるという観点から設計がスタートします。 一方MongoDBなどのNoSQLでは「どのようなユースケースに対してどのような値を返すか」という観点から設計がスタートするため、場合によってはRDBでは行われていたであろう正規化が行われないこともあります。 (設計を行うにあたってはこちらや同じくスキーマレスDBであるDynamoDBのドキュメントを参考にすることが多いです)

そうすると、そのドキュメントをそのままモデルクラスにマッピングするとモデルクラスにあるには冗長と感じるプロパティが出てくることになり、モデルクラスの責務ではないロジックが含まれるようになってしまいます。

こういったケースに対応するため、Embedded Document(入れ子になっているドキュメント)をひとつのモデルクラスとして扱える機能を使用するなどの工夫が必要になります。

この辺りの工夫については、それだけで1記事になってしまうのでまた別の機会に...

結果整合性を採用しているため油断すると古いデータを読み込んでしまう

MongoDBは結果整合性を採用しています。そのため気をつけないと古いデータを読み込んでしまう危険があります。

以下にSidekiqを利用してMongoDBに書き込みを行ったあとタスクキューにドキュメントのIDを登録し、別スレッドで非同期処理を行うコードがあります。

class HardWorker
  include Sidekiq::Worker

  def perform(id)
    user = User.find(id)
    user.alert! if user.status == 'hard'
  end
end

user = User.sample
user.update_attributes!(status: 'hard')
HardWorker.perform_async(user.id)

このコードは正しく動かない可能性があります。 usersコレクションへの書き込みは結果整合となり、プライマリノードが書き込みクエリを受け付けたのちリードレプリカへの反映が完了するまでにはラグがあります。 そのためWorkerに記述された User.find(id) が処理されるタイミングとリードレプリカ次第で書き込みが反映される以前のデータを読み込んでしまう可能性があるからです。

これを避けるために、Worker側では以下のようにプライマリノードを明示的に指定する必要があります。

user = User.read(mode: :primary).find(id)

トランザクションがない

STORES ECで使用しているMongoDB 3.6にはトランザクションがありません。そのため、ロールバックが必要なケースではアプリケーションレイヤーにて自前でロールバック処理を書く必要があります。

また、排他制御も自前で行う必要があるためSTORES ECでは別途Redisを使ってロックを取るといった工夫をしています。 RDSに慣れている身からすると、これらは正直データベース側で担保してもらえると安心できるのになあという気持ちがあります。

なお、MongoDB 4.xからはトランザクションが使えるようになります。STORES ECでも近々4.0にアップデートする予定なので、これらの課題をクリアできないか試すのが楽しみです。

MongoDBを採用していてよかったこと

スキーマレスなのでDBマイグレーションスキーマ管理がない

STORES ECでは金土日を除く毎日複数回のリリースを行っています。 素早いリリースサイクルを実現できているのにはいくつか要因があると思いますが、そのうちのひとつとして基本的にデータベースのマイグレーションが必要ないことがあげられます。比較的気軽にお互いのコードの動作確認ができ、リリース時もデータベースのマイグレーションにまつわる心配事を回避できるためです。

また、一定以上の規模のRailsアプリケーションになってくると発生しがちな db/schema.rb とか db/migrate/* の管理に悩まされることもありません。

もちろんスキーマが欲しいと思うこともあるのでこの辺は一長一短ですね。

データベースのパフォーマンスに苦労していない

RDBもMongoDBも適切に設計しインデキシングしてインフラを構成しないと遅くなるのは一緒なので単純なパフォーマンス比較は難しいのですが、今のところSTORES ECではデータベース自体のパフォーマンスに苦労しているといった状況ではありません。

2019年にそれまでEC2上で起動していたMongoDBを、MongoDBのマネージドサービスであるMongoDB Atlasに移行し、かなり余裕があるクラスターに載せ替えました。 www.mongodb.com

どれくらい余裕があるかというと、新型コロナウイルスの影響でEC需要が高まり、STORES ECの平均リクエスト数も以前の数倍以上に膨れ上がり、アクセススパイクも多々発生する中でひたすらアプリケーションのインスタンスを横に並べて対応してきましたが、データベースがボトルネックになることはなかったレベルです。

もちろんまだまだ最適に設計・インデキシングされてない箇所は残っており改善していくつもりですが、パフォーマンスに関してはある程度の安心感をもってMongoDBを使用できています。

レプリケーションやシャーディング、MongoDBならではのインデキシングを活用したパフォーマンス改善については、それだけで1記事になってしまうのでまた別の機会に...

Railsのバージョンアップが多少やりやすい

STORES ECは2019年にそれまで4.2だったRailsのバージョンを5.2に上げました。 MongoidはActive Recordと違ってRails本体にバンドルされているわけではないので、Webフレームワークのバージョンアップとデータベースを扱うライブラリのバージョンアップを切り分けて実施できたのは、多少のやりやすさに貢献したかもしれません。

なお最近この辺りの古かったフレームワークやライブラリのバージョンを頑張って最新にアップデートし、さらに常に最新のライブラリにアップデートし続ける仕組みが導入されたのですが、これは話が逸れるのでまた別の機会に....

チーム全体で技術に向き合うきっかけになった

私がSTORES ECの開発に携わるようになってから、上記に挙げたようなことからもっと細かいところまで、RDSとMongoDBの違い、ActiveRecordとMongoidの違いにつまずいて来ました。 これらのつまずきに対し、面白半分で悪態をつくような言動をしたこともあります。

しかし、STORES ECを始めheyの各サービスが成長し続ける中で、そんなことしていてもなんの解決にもならないと思いました。

そこで、エンジニアチームを横串してMongoDBに対する知見を共有する場を作りました。すると、各チームで持っていたハマりどころやノウハウといった知見が多く集まり、盛り上がった結果、定期開催されるようになりました。 ここで集まった知見はドキュメント化されレビューやオンボーディングで生かされるようになりました。

そのうち今度はMongoDBに関してだけでなく、STORES EC内で属人化が進んでいた機能の勉強会が始まり、技術書の輪読会、これから導入しようとしている技術のドキュメントの輪読会など、次々と様々な勉強会がチームを跨って開催されるようになってきました。 今や社内で行われている勉強会は結構なボリュームになってきているので、これはこれで別の機会に...

もしかしたら、MongoDBは私たちエンジニアチームが技術的な課題と正面から向き合う姿勢を作るきっかけになってくれたのかもしれません。

おわりに

8月にプレスリリースがあったように、heyは予約サービスのクービックと一緒になり、さらに大きくなっていくことを決めました。

note.com

これからheyとしてまた新たなサービスを作ることは当然あるでしょう。さらにクービックと一緒になりEC、ターミナルと連携するためのサービスを開発することも見据えています。

それらを新たに開発する際にMongoDBを採用するかといわれると、RDBのほうがマッチする事の方が多そうに感じているのが正直なところです。

とはいえ、STORES ECではMongoDBをRDBに移行させた方がよいのでは?という話が上がるほどの課題にはなっていません。 (もちろん未来はどうなるかわからないので、もしかしたら将来になって壮大なマイグレート祭りが開催されるかもしれません)

どちらにせよ、これからも目の前の課題に対して未来を見据える視点を持ちながら全力で向き合っていこうと思います。

ということでheyではMongoDBを使った開発に興味がある方もそうで無いない方も、さらにはデザイナー、PM、CS、セールス、コーポレートなど全職種で採用活動をやっています。

興味のある方は是非とも採用ページをご確認ください!

hello.hey.jp