クックパッド開発者ブログスマートフォン用の表示で見るクックパッド開発者ブログ2014-10-03開発環境のデータをできるだけ本番に近づけるこんにちは。今回はクックパッドの開発環境構成、特に開発用データベースの構成についてご紹介します。開発環境の構成クックパッドのシステム環境は以下のようなフェイズに分かれています。これはcookpad.comの構成で、サブシステムや個別のサービスはその規模や特性に応じて構成が異なります。development開発者が実際に開発を行う環境です。クックパッドでは仮想環境は用いず、手元のマシンでRailsアプリケーションを動かして開発を行っています。データベースはローカルではなく、開発者全体で共通の開発用データベースに接続しています。test手元でテストを実行する場合は、ローカルマシンのデータベースを利用します。CI(rrrspec)などの場合も同様で、テスト実行サーバーのデータベースが利用されます。stagingstagingといえば準本番環境として、本番前の最終確認用の環境として位置づけられるのが一般的かと思います。クックパッドでは後述するproduction-test環境がそれに近い位置づけで、stagingは少し独特な使い方をしています。開発者は任意のブランチを独立した専用のインスタンスに対して任意のホスト名でデプロイすることができます。一般的な利用方法としては、開発者のフォークリポジトリの特定のブランチをデプロイして利用します。指定したホスト名が社内からアクセスできるURLになるため、あるチーム専用のクックパッド環境ができあがります。通常の開発フローでは、stagingへのデプロイは必須ではありません。基本的にはローカルマシンで動作確認し、またchankoを利用してスタッフベータ公開とすることでproductionで動作確認を行います。連携するシステムからのAPIコールを受けたい場合に、常時利用できる環境としてstaging環境が利用されています。このstaging環境もdevelopment環境と同じ共通の開発用DBに接続されています。production-test準本番環境・・・というかデータベースはproductionのものなので実アクセスから切り離された本番環境、といった方が正しいかもしれません。CIを通ったリビジョンは自動的にこのproduction-testにデプロイされ、常に最新のリビジョンがデプロイされた状態です。productionへのデプロイ前に開発者はここで最終確認を行います。説明した通り、クックパッドでは手元で開発する場合も共通の開発用データベースを利用しています。一般的には手元の開発環境でのデータベースはローカルのデータベースに接続する構成が多いのではないでしょうか。クックパッドの開発用データベースは、productionのデータベースと同期されています。手元で動かせば、常に本番の最新のレシピが表示されるようになっているのです。ような適当な入力でテストしたり、それらしいデータを自動生成して用意しておく場合が多いのではないでしょうか。それはユーザーが実際に体験するクックパッドではないのです。ようなレシピが並ぶのと、実際にユーザーが投稿してくれたレシピが並ぶのでは、見た目の印象や感じ方だけではなく、操作感も変わってきます。本番と同じ状態のクックパッドで開発することで、ユーザーと同等の体験のもとに開発ができるのです。予期せぬデータによるバグに気づきやすい前述したようなテストデータはあくまで開発者が予期したデータにすぎません。実際のデータはもっと複雑で、想定していないデータが存在してバグの原因になったりします。開発の時点で本番データで開発を行っていれば、そういった環境間の差異による問題に気づきやすくなります。重いクエリに気づきやすいindexなどに気を使っていても、本番と開発環境でcardinalityが異なっていて思った通りにindexが働かなかった、ということはないでしょうか。事前に予期できていれば検証環境を用意するといったこともできると思いますが、副次的に発生したクエリだったりすると本番にデプロイして初めて気づくということもあるのではないでしょうか。これも初めから本番データで開発していれば、本番で重いクエリは手元で動かしても重いため、すぐに気がつけます。開発者全体でデータベースを共有しているため、たまに誰かが気付かず重いクエリを発行していると他の誰かが気づいたりします。本番と同期するためにMySQLのレプリケーションを利用しています。staging-dbはproduction-dbからレプリケーションしており、その状態でstaging-dbへの開発用書き込み・参照を行っています。いえ、実際にはこれを実現するためには様々な問題があります。更新時のコンフリクトslaveに書き込んだら速攻でレプリが止まるのでは・・・と思いきや、クエリベースのレプリケーションを行っていると案外止まりません。主キーがAUTO_INCREMENTなテーブルに対してそれぞれでINSERTが走ってもコンフリクトしないのです。実際にはまともに動作しません。INSERTに成功していても、コンフリクトしたレコードの主キーはproduction-dbとstaging-dbで異なってしまいます。staging-dbでINSERTすると、同じidに対するUPDATE文がレプリされてくるため、何も触ってないのに勝手にデータが変更されていきます。production-dbとstaging-dbで対象データに齟齬が生じてしまっているわけです。そこでMySQLの行ベースレプリを利用します。行レプリであればレコードが正確に複製されるため、コンフリクトしたINSERTは失敗します。失敗されては困るので、あわせてstaging-dbのテーブルはAUTO_INCREMENTのオフセットをidが衝突しない程度に進めておきます。3...と進み、同じidを持つレコードがstaging-dbに複製されていきます。これでコンフリクトせず、本番データと開発データが共存できます。これを用いてテーブル作成するとこのAUTO_INCREMENTのオフセット設定は自動でやってくれるようになっています。通常のmaster-slave間でのレプリケーションにはステートメントベースレプリケーションを利用しているため、そのままでは行レプリできません。staging-dbの間に1台コンバーターを挟んで、そこで行レプリ用のバイナリログを出力するようにしています。テーブル定義のコンフリクト行レプリを使って、レコードの更新がコンフリクトしないようにすることができました。一方でカラムが削除されて開発環境にはなかったり、カラム名が変更されたりして本番と開発環境でテーブル定義が異なっていると行レプリといえどもさすがにレプリケーションできません。本番と開発環境でテーブル定義が異なる状態は通常一時的なものです。長期間テーブル定義が異なったままの状態であるとしたらそれは健全な状態ではありません。そこでその一時については目をつぶって、コンフリクトした場合はその更新をスキップするようにしています。具体的にはstaging-db監視デーモンがレプリケーション状態を常に監視しており、もしレプリケーションが停止したらそのクエリをスキップして再開させるようになっています。MySQLならslave_skip_errorsでよいのでは?と思った方もいらっしゃるでしょう。実はslave_skip_errorsではスキップできないケースがあるのです。行レプリを使っている場合に発生する1677エラーなどがそれです。いうか監視デーモンの目的はほぼこの1677エラーをスキップするのが目的です。セキュリティ開発環境のデータベースはテーブル追加など開発者が自由に行えるようになっています。いうことは強めの権限が開放されているということです。ローカルだけで閉じていれば問題ないのですが、本番のデータが入ってくるとなると気になるのはセキュアな情報です。それに該当するデータは自動でフィルタリングされstaging-dbにはレプリケーションされないようになっています。余談ですが、社内ではこの命名規則に従っていればログ出力などが自動でフィルターされるgemが利用されています。まとめクックパッドにおける開発環境構成と、開発用データベースを本番データベースと同期する方法について紹介しました。本番環境にできるだけ近づけることで、よりユーザーに近い目線で開発することができます。本番構成や開発フローについてはよく紹介にあがりますが、こういった開発環境の構成は意外と情報が少なかったりするので、参考となれば幸いです。11:08開発環境のデータをできるだけ本番に近づけるTweet関連記事2019-11-18サービス特性にあった検索システムの設計戦略こんにちは!研究開発部ソフトウェアエンジニアの林田千瑛(@chi...2019-06-17レシピ検索を支えるレガシーでクリティカルな大規模バッチを刷新した話こんにちは、会員事業部の新井です。新...2016-10-17"使える"プロトタイプ主導の開発プロセス検索事業部の須藤です。使ったセキュリティログ検索基盤の構築サービス特性にあった検索システムの設計戦略クックパッド採用説明会「クックパッドはサービスの作り手を採用したいんです。/*}*/スターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。