食べチョク開発者ブログ

食べチョクエンジニアによるプロダクト開発ブログ

データベース設計の際に気をつけていること

皆さんこんにちは、エンジニアの西尾です。

新しい機能・サービスを開発する際、私は特にデータベース設計に気をつかいます。

データベースはシステムの土台です。 土台が不安定だと、その上に積み上げていくアプリケーションコードがいびつなものになり、つらい思いをします。 また、一度動き出してしまったシステムのデータベース設計を変えるのは、容易なことではありません。

データベース設計には”これだ!”という正解はないと思っています。 サービスの特徴、システムの性質、toB向け/toC向け、Readが多い・少ない、Writeが多い・少ない。 その他もろもろの背景により、データベース設計の仕方も変わってきます。

このテーブルは正規化していないから駄目だ、この設計はいわゆるポリモーフィック関連だから使ってはいけない、などということはありません。 アンチパターンと呼ばれるものも時と場合によっては正解になります。

今回は、食べチョクのデータベースを設計する際に気をつけていることを共有いたします。 なお、食べチョクではRDBとしてMySQL5.7を利用しています。

1. 制約をつける

データベース設計において重要なのは、いかにして不整合を起こさないようにするかです。

「データを引いてみたら関連先のレコードが無くなっている」、「このレコードはユーザーごとに1つだけ持つはずだけど、2レコードある」など。 不整合は往々にして発生します。

データを挿入・更新・削除してもよいかのチェックはアプリケーションレベルで防ぐだけではなく、可能ならばデータベースレベルで行います。 そのために、以下制約をつける努力をします。

1.1 外部キー制約をつける

外部キー制約は、可能な限りつけるようにしています。 DBが別れている場合、外部キーはもちろん貼れないのですが、そうでない場合はとにかく何も考えず貼っています。

テスト時のテストデータが入れにくいから貼りたくない、とかいってる場合じゃないです。本番環境で不整合が起こる方が怖いですよね。 テストデータ入れるだけなら、 SET FOREIGN_KEY_CHECKS=0; とかでレコードいれればよいだけですし(本番環境ではやらないでください)。

1.2 ユニークキー制約をつける

ユニークキーも可能な限りつけるようにしています。

例えば以下のような注文(orders)テーブルと、支払い(payments)テーブルがあるとして、 注文に対する支払いは1つしか存在しないことがわかっている場合。

CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `created_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  CONSTRAINT `orders_user_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB ROW_FORMAT=dynamic DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE `payments` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_id` int(11) NOT NULL,
  `transaction_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  CONSTRAINT `payments_order_id_fk` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`)
) ENGINE=InnoDB ROW_FORMAT=dynamic DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

このときは、paymentsテーブルのorder_idにユニークキー制約をつけます。

CREATE UNIQUE INDEX `index_payments_on_order_id` ON `payments`(`order_id`);

そうすれば、注文に対する有効な支払いがなぜか2件できてしまっていた、というバグを未然に防ぐことができます。

別のパターンも見てみます。 例えば、paymentsテーブルに以下のようにactiveというカラムを足します。 有効な支払いは1つ(active=1の場合)だけだが、支払いを変更したなどで履歴情報として過去の支払いをactive=0として持っておきたい場合。

CREATE TABLE `payments` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `active` tinyint(1) NOT NULL DEFAULT '1',
  `order_id` int(11) NOT NULL,
  `transaction_code` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `created_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `payments_order_id_fk` (`order_id`),
  CONSTRAINT `payments_order_id_fk` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;

この場合、(active, order_id)にユニークキー制約を貼るわけにはいきません。 支払いを2回変更したら、active=0が2レコードできてしまうのですが、そういうレコードは入れられません。

この場合は以下2つのうち、どちらかで修正します。

そもそもactiveとかいうカラムを持たず、別途payment_historiesなどのテーブルを作り、そちらにレコードを移す

履歴として取っておく必要があるのなら、元のテーブルにレコードを残しておくのではなく、別のテーブルに移動させます。 こちらの設計の方がスマートです。元のユニークキー制約を残したまま、レコードの行数も抑えられるし、paymentsを引くときの余計な条件式も減らせます。

activeをNULL許容にして、過去データは active=NULL としてデータを入れる

NULLは必ずユニーク扱いになります。そのため、activeカラムをnull許可することで、(active, order_id)にユニークキーを貼っていても、 (MySQLでは)以下のようにデータを入れることが可能です。

INSERT INTO payments(active, order_id, transaction_code, created_at) VALUES (1, 1, "AAA1", "2020-06-15 00:00:00");
INSERT INTO payments(active, order_id, transaction_code, created_at) VALUES (NULL, 1, "AAA2", "2020-06-15 00:00:00");
INSERT INTO payments(active, order_id, transaction_code, created_at) VALUES (NULL, 1, "AAA3", "2020-06-15 00:00:00");

この設計は賛否両論あると思いますが、これはNULLの正しい使い方ではないかと思っています。

別テーブルに分けるか、NULL許容して入れるか、実装コストを踏まえて選択しています。

1.3 NOT NULL制約をつける

なるべくNULLが入らないようにテーブルを設計しています。 例えば以下のようなテーブルがあったとします。

CREATE TABLE `something` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `note` varchar(255),
  `created_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
) ENGINE=InnoDB ROW_FORMAT=dynamic DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

このテーブルからnoteが空のレコードを抽出する場合、どういうSQLを書くでしょうか。

SELECT * FROM something WHERE note = "";

いやいや、もしかしたらnoteにはNULLが入っているかもしれません。

SELECT * FROM something WHERE note IS NULL OR note = "";

NULLが入っていることで、SQLが複雑化する可能性があります。 また、SQLの書き方を間違えると意図したレコードを抽出できない可能性があります。 そのため、極力NULLを入れなくて良い箇所はNOT NULL制約を入れるようにしています。

ただし、なんでもかんでもNULLを排除すれば良いというわけではありません。 例えば、以下のようにuser_idを持つテーブルがあるとします。

CREATE TABLE `something` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11),
  `created_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
) ENGINE=InnoDB ROW_FORMAT=dynamic DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

なんとしてもNULLを入れたくないのか、user_idデータが無い場合はuser_id=0としてレコードを作成する、 といったように無理やりダミーレコードをいれてNULL回避をする方がいます。 データが存在しないのならば、素直にNULLを入れるべきです。

余談ですが、datetime型に0000-00-00 00:00:00を入れるとえらい目にあうので注意が必要です。 ( 参照: https://soudai.hatenablog.com/entry/2018/05/12/191050 )

2. 適切なデータ型を使う

MySQL5.7では、https://dev.mysql.com/doc/refman/5.7/en/data-types.html にあるデータ型を利用できます。

本来数値しか入らないレコードはint型を使うなど、適切な型を設定するよう努力します。 数値カラムに全角数字が入っていた、そんなことはデータ型を正しく選択していれば起こり得ないことです。 なんでもかんでもvarcharで定義しないよう注意します。

以下、型を選ぶ時に気をつけていることを記載します。

  • bool値を入れるカラムには tinyint(1)を利用する
  • 長くない可変文字列を入れる場合(大体1024文字より下くらいかな?)はvarcharを利用します
  • 長い可変文字列を入れる場合はtext型を利用する、またtext型を使う場合はテーブルのROW_FORMATがDynamic(Barracuda)であることを確認する
    • MySQL5.6まではデフォルトのフォーマットはCompact(Antelope)でしたが、これは1レコードあたり8KBまでしかデータを入れることができません。
    • テキスト型を使うと8KB制限を突破してしまうこともあるため、テーブルのフォーマットがDynamic(Barracuda)であることを確認します。
  • 整数値を入れる場合はint型かbigint型を使う
  • float型は使わない
    • 精度のトラブルに巻き込まれたくないためfloatは使いません。多くの場合、doubleかdecimalで問題ありません。
  • 金額情報など、精度を求められる小数値にはdecimalを使う
    • doubleも小数点以下の精度に悩まされることがあります。金額を扱う、精度が必要な計算は必ずDecimalを利用します。
  • 日付を入れる場合はDATE型を使う
    • 商品のお届け日など、日付を入れる場合は DatetimeやTimestamp型ではなくDATE型を使うようにします。DatetimeやTimestampはタイムゾーンの影響を受けるためです。
  • JSON型を使ったら負け

注意点として、数字に見えるデータを入れる際、必ずしもint型が正解ではない可能性もあります。 郵便番号(例: 0100011) とか、電話番号(例: 08000000000) は一見数値に見えますが、これはvarcharで保存すべきです。 なぜなら、数値の場合は先頭の0の値が抜け落ちてしまうためです。

3. 三角関係のリレーションを持つテーブルは(できるだけ)作らない

三角関係という表現が正しいかはさておき、下図ERのようなテーブルは(できるだけ)作らないようにしています。

f:id:vividgarden-tech:20200614205008p:plain

この構造では、注文からグループを引く場合に注文 → ユーザー → グループというたどり方をした場合と、注文 → グループと 直接レコードを引いた際に違うレコードが取れる可能性はあります。 例えば、ユーザーが所属するグループが途中で変わった場合は、整合性が崩れる可能性があります。

三角関係にならないよう、以下のように設計を修正します。

f:id:vividgarden-tech:20200614205031p:plain

性能問題により、注文からグループテーブルへのショートカットを貼る場合もあります。 その場合、ユーザーが所属するグループが変わった場合は、注文レコードが保持しているグループへのリレーションも貼り直す必要があります。 整合性が崩れやすいので、このような設計はできるだけしないようにしています。

4. 一時的なレコードと永続化が必要なレコードを同じテーブルには入れない

一時的に利用するレコードと、永続化すべきレコードは同じテーブルに入れないようにしています。

例えば、以下のような受注(orders)テーブルがあるとします。 これは SpreeというオープンソースのECプラットフォームのテーブル設計です。 (実際はもう少し複雑なのですが、簡略化しています)

f:id:vividgarden-tech:20200614205054p:plain

Spreeにはカート機能があるのですが、ユーザーが一時的にカートに入れた注文も、その後実際に受注したデータも同じordersテーブルに入っています。 カートに入っている未発注の注文は、注文ステータスをcartという状態にして、関連テーブルと共にレコードを挿入してます。

確かにカートの情報をそのままordersに入れれば実装上は楽なのですが、この設計は性能に問題がでる可能性があります。 ユーザーがカートに入れたまま放置したら、ordersテーブルには不要なレコードがたまり続けることになります。

構造が同じでも意味が違うデータは分けて管理すべきだと、私は思います。 その方がレコードの行数も少なく抑えられて、注文テーブルを引くコストが下がります。

5. 定期的に性能を見て設計を検討し直す

データベースは生き物です。

日々データ量が増え続けていき、設計時には思いもよらないようなパフォーマンス問題にぶち当たることがあります。 一度設計したら終わりではなく、日々パフォーマンスを監視し、重いクエリがあればチューニングが必要です。 ときにはテーブル構造自体を見直す必要があるかもしれません。

昔はSlowQueryを吐き出してそれを見る、ということをしていましたが、 最近では性能を監視するためのツールが充実しています。 おすすめのツールはNewRelicとAWSのRDSパフォーマンスインサイトです。

NewRelicでは、下図のようにパフォーマンスを可視化してくれます。 例えば、以下を見るとDeliveryPossibilityテーブルを引くのに時間がかかっていますね。パフォーマンスチューニングが必要そうです。

f:id:vividgarden-tech:20200614205115p:plain

AWSのパフォーマンスインサイトは、下図のように更に細かい単位でパフォーマンスの可視化をしてくれます。

f:id:vividgarden-tech:20200614205126p:plain

f:id:vividgarden-tech:20200614205138p:plain

6. インデックスの性質を知り、適切に貼る

パフォーマンスが出ないクエリは、インデックスを貼れば早くなる!わけではありません。 インデックスは銀の弾丸ではなく、性質を知り適切に貼る必要があります。

6.1 カーディナリティを考慮してインデックスを貼る

MySQLでインデックスを利用する場合、多くはBツリーインデックスを利用するかと思います。 Bツリーだろうがハッシュだろうが、カーディナリティの低いカラムにインデックスを張っても性能はでません。

カーディナリティは、入っている値の種類がどのくらいあるかを表しています。

例えば、レコードが1千万行あるテーブルがあり、activeという1と0の値を取るカラムがあるとします。 このactiveカラムのカーディナリティは2であり、ここにindexを貼っても多くの場合クエリは早くなりません。 逆にカーディナリティが1千万のカラム(すなわち重複の無いデータ)にindexを貼れば、早くデータを引いてこられる可能性があります。

カーディナリティを確認するには、show indexクエリを利用します。 以下のテーブルでは複合indexとして(faxable_type, faxable_id)にインデックスを張ってます。 faxable_typeはカーディナリティ2であり、(ユニークキー制約がある場合は別として)ここに貼る意味はあまりなさそうですね。 faxable_idはカーディナリティが高く、このカラムにインデックスを貼るのは正解です。

f:id:vividgarden-tech:20200614205159p:plain

6.2 インデックスが効いているかをExplainで見る

インデックスが効いているかはExplainを使えばわかります。 Explainの解説をするとそれだけで長文がかけてしまうのでここでは省略します。 クエリにexplainをかけてtypeを見る、possible_keysに意図したインデックスが入っているか、引いてくるレコードの行(rows)が少なくなっているか(フルスキャンしていないか)、Using Indexがでているか、コストが高くないかなどを確認します。

MySQL Workbenchを使うと、グラフィカルにExplain結果が見られるのでおすすめです。

f:id:vividgarden-tech:20200614205211p:plain

6.3 インデックスを貼る順序に気をつける

複合インデックスの場合は、インデックスを貼るカラムの順序も重要です。

(column1, column2, column3) という順に貼ったindexと、(column3, column2, column1) のように貼ったインデックスは別のものとして扱われます。 前者のindexを貼った場合、条件に(column1), (column1, column2), (column1, column2, column3)が入っている場合のみ、インデックスが有効です。 where column3=xxxxx のようにクエリを書いてもindexは使われないことに注意します。

6.4 インデックスショットガンをしない

インデックスを全部のカラムに貼ったら早くなるんじゃないか。 誰しもが思う疑問かもしれませんが、そんなに都合良くは行きません。

まずMySQLのインデックスは1クエリにつき基本的に1つしか利用できません。 (インデックスが1つだけ使われるということで、1つのカラムにだけ効く、という意味ではありません。複合インデックスは有効です。)

index1, index2をいい感じに合体して高速なクエリを作り出してくれる、そんな高度な機能は... インデックスマージという機能はなくはないのですが、基本は1つだけ使うと考えて良いでしょう。

またインデックスを貼るとテーブルの更新処理が遅くなり、ディスク容量も膨れ上がります。 それはそのとおりで、通常のレコードとは別にインデックス用のデータを作るわけですから、遅くなります。

すべてのカラムにインデックスを貼る、インデックスショットガンはしないようにします。インデックスを貼るにもコストがかかるのです。

7. 正規化が必要なところ、不要なところを見極めてテーブル設計をする

MySQLのjoinはとても遅いです。Oracleなら高速で返ってくるであろうクエリも、MySQLでは遅いです。

toC向けのRead負荷が高いページを表示するのに、joinを多用したクエリを実行するべきではありません。 アクセス負荷の高いページではjoinしたら負けです。理想は1テーブルのみのアクセスです。

そういう場合は、各種テーブルのデータをサマった正規化していないテーブルを別途作成し、そのテーブルにのみアクセスするようにします。 いわゆるマテビュー(マテリアライズド・ビュー)のようなものを別途作成することで、アクセスを高速にさばくことができます。

逆にtoB向けのページでは、できるだけ正規化したテーブルにアクセスするようにします。 こちらは整合性の崩れた(キャッシュのような)レコードにアクセスしてデータが間違っていた、という事件が起きるほうがリスクが高いです。

正規化する/しないはアクセスの性質・サイトの性質に合わせて柔軟に検討します。

8. リレーショナルデータベース(RDB)が苦手な表現を理解し設計する

RDBは万能ではありません。RDBでは表現が難しいデータ構造・表現も存在します。 以下はRDBで扱いにくいものの一例です。

  • ツリー構造など階層を持ったデータ構造
  • カラムが動的に変化するデータ構造
  • GIS(地理情報)を扱う場合
  • 全文検索が必要な場合
  • データ量が多すぎる場合(アクセスログデータを入れて計算するなど)

ツリー構造(階層構造)を持ったデータは頻出ですが、RDBでは扱いにくいものの部類にはいります。 そのため、ツリーを表現するための設計パターンが存在します。 隣接リスト・Nested Set・クロージャーテーブルなどなど。どれも一長一短があります。それぞれの設計の利点欠点を理解し、適切なデータ構造を選びます。

カラムが動的に変化する構造も苦手です。GISも苦手、全文検索も苦手です。データ量が多すぎる場合もRDBでは対応できないかもしれません。

RDBに向いていないデータを入れる場合は、素直にRDB以外を選択したほうが良いでしょう。

まとめ

今回はデータベース設計を行う際に気をつけていることをまとめました。

システムの土台となるデータベースの設計は、とても重要です。 DBの寿命はアプリより長い といわれていますが、そのとおりだと思います。

あとでつらい思いをしないためにも、今後も慎重にテーブル設計をしていきたいものです。

www.wantedly.com

www.wantedly.com

コンビニ決済が利用できるようになりました! 2020年1月のカイゼン内容のご紹介

こんにちは、エンジニアの鹿倉です。

2020年もどうぞ食べチョクをよろしくお願いいたします。

食べチョクは、2020年もお客様・生産者さまからご意見を頂戴しながら、日々サービスのカイゼンを行なって参ります。
早速、1月もたくさんのカイゼン行いましたので、いくつかカイゼン内容をご紹介いたします。

1月の改善サマリー

  • カイゼン数: 61 項目
    • 新機能: 40 項目
    • バグ改修: 18 項目
    • その他: 3 項目

上記の中から是非お伝えしたいカイゼン内容をご紹介いたします!

1. コンビニ決済が使用できるようになりました!

これまでお支払い方法はクレジットカード決済のみでしたが、1月よりコンビニ決済もご利用いただけるようになりました!

コンビニ決済で購入したい場合の手順を簡単にご説明いたします。

1. 注文画面にてお支払い方法からコンビニ払いをお選びください

f:id:vividgarden-tech:20200203180007j:plain:w300

2. コンビニ払いに必要な情報をご入力ください。

f:id:vividgarden-tech:20200203180250j:plain:w300

1月時点では、ご利用いただけるコンビニは、ローソン、ファミリーマート、ミニストップの3社となります。
今後、セブンイレブンも追加される予定です。

注文が完了すると、以下のようなページが表示されます。

f:id:vividgarden-tech:20200127191256j:plain:w300

3. お店でお支払い

注文が完了するとコンビニ決済依頼完了のお知らせという件名でメールが届きます。
メールの内容に従って、ご指定のコンビニからお支払いを行ってください。
※お支払い期限についてはメールに記載されております。

お支払いが完了できましたら、確認完了のメールをお送りさせていただきます。

コンビニ払いはこれまでも多くのご要望があった機能です。
是非、食べチョクの利用を見送っていたお客様にもご利用いただきたいと思います。

2. 生産者の新規登録画面のカイゼンを行いました

生産者の新規登録時は入力項目が多いため、生産者様が入力しやすいように細かいカイゼンを行っております。

  • 記述例の提示
    商品名や紹介文など各所にお客様に伝わりやすい記述例を具体的に提示し、生産者様がスムーズに入力できるようにしています。
  • 写真の例を提示
    プロフィール写真やこだわり写真についても、具体例を提示して、生産者様がスムーズに入力できるようにしています。
  • プレビュー画面の追加
    入力しながら販売開始をイメージできるようにプレビュー画面を追加しています。

3. よくあるご質問ページを作成しました

お客様からのお問い合わせの多い内容をまとめた、よくあるご質問のページを作成しております。

是非、お問い合わせの前に、よくあるご質問ページをチェックしてみてください。

www.tabechoku.com

最後に

今後もユーザーの皆様・生産者の皆様のお声を積極的に取り入れていきたいと考えております。

食べチョクトップページの下部にご意見フォームを設置しておりますので、なにか気になることがございましたら、ささいなことでもお伝えいただければ幸いです。

また、食べチョクではデザイナー・エンジニアを切に募集しています。
興味のある方は、是非Wantedlyよりご連絡ください!

www.wantedly.com

2019年11月のリリース内容をご紹介!

こんにちは、エンジニアの鹿倉です。

食べチョクでは、お客様、生産者さまからご意見を頂戴しながら、日々サービスのカイゼンを行なっております。
11月もたくさんのご意見を頂きカイゼンを行いました!

11月のカイゼンサマリーはこちらです。

11月の改善サマリー

  • カイゼン数: 80 項目
    • 新機能: 46 項目
    • バグ改修: 28 項目
    • その他: 6 項目

上記の中から是非お伝えしたいカイゼン内容をご紹介いたします!

1. 検索精度をカイゼンしました

今までのフリーワード検索は検索の対象が広域であるため、意図しない検索結果となることがありました。
例えば、「トマト」と検索した場合に、「メロン」の商品が表示されるといった具合です。
複数品目を栽培している生産者様の場合、商品説明文に他の品目についても記載されている場合があるため意図しない品目が引っかかってしまうというのが大きな理由でした。

以前は商品数が少なかったため、なるべくヒットしやすいように検索対象を広くしていましたが、商品数も増えてきて十分な結果を返せるようになってきたため、商品説明文を検索対象から外しています。

2. お問い合わせページと会員登録画面にてスパム対策を行いました

サービスの成長に伴い、ボットからの不正アクセスが増えてきたため、reCAPTCHAを導入しました。
ユーザの皆様に影響のある改善ではございませんが、セキュリティ向上のためにも今回の対策を行いました。

f:id:vividgarden-tech:20191209195107j:plain:w300

3. お歳暮特集をリリースしました!

お歳暮シーズンに向けて、特集ページをリリースしました。 ギフトとしても喜ばれることはもちろんですし、自分へのご褒美としてもいかがでしょうか!

www.tabechoku.com

私のオススメは椎茸です。肉厚で香り高く、1口食べただけで幸せになります。是非食べてみてください。

www.tabechoku.com

4. フルーツセレクトに関する投稿が出来る様になりました

10月にリリースしたフルーツセレクトですが、初回発送が11月ということもあり、実はまだ10月時点ではフルーツセレクトに関する投稿が出来ませんでした。
初回の発送に合わせて、フルーツセレクトに関する投稿も出来るようになっています。

すでに、ご投稿いただいているユーザ様もいます。

www.tabechoku.com

www.tabechoku.com

是非、フルーツセレクトをご利用の際はお客様の声を生産者に届けてみてください。

5. [生産者向けリリース]配送方法にクリックポストを追加しました

生産者からのご意見で、クリックポストで発送する場合は、通常の郵便扱いとなるためお届け日の指定が出来ないが、 システム上は、お届け日の登録が必須のため、発送完了が出来ない、または無理にお届け日を登録してもお客様に不適切なご案内になるとのご指摘を頂きました。

上記のご指摘を受けて配送方法(配送業者)にクリックポストを追加しております。
クリックポストを指定した場合もお届け日の入力はシステム上必要ではありますが、あくまで目安の扱いとなりますので、お客様にお届け日をご案内することはございません。

f:id:vividgarden-tech:20191209194156p:plain:w300

6. [生産者向けリリース]出荷リストをCSVダウンロード出来るようになりました

生産者から出荷リストをCSVでも扱えるようにしたいとのご意見がございました。
一覧ページの右上の操作ボタンからダウンロードが出来るように対応しています。

f:id:vividgarden-tech:20191209202413j:plain:w300

7. Rails6にアップデートしました!

直接お客様に関係あることではございませんが、食べチョクもようやくRails6へアップデートしました!!!

こちらについては別途ブログを書こうと思っています。

最後に

今後もユーザーの皆様・生産者の皆様のお声を積極的に取り入れていきたいと考えております。

食べチョクトップページの下部にご意見フォームを設置しておりますので、なにか気になることがございましたら、ささいなことでもお伝えいただければ幸いです。

また、食べチョクではデザイナー・エンジニアを切に募集しています。
興味のある方は、是非Wantedlyよりご連絡ください!

www.wantedly.com

www.wantedly.com

www.wantedly.com

新サービス「食べチョク フルーツセレクト」をリリース!2019年10月のカイゼン内容をご紹介!

こんにちは、食べチョクの鹿倉です。

食べチョクでは、お客様、生産者さまからご意見を頂戴しながら、日々サービスのカイゼンを行なっております。 10月もたくさんのご意見を頂きカイゼンを行いました!

10月のカイゼンサマリーはこちらです。

10月の改善サマリー

  • カイゼン数: 81 項目
    • 新機能: 40 項目
    • バグ改修: 22 項目
    • その他: 19 項目

上記の中から是非お伝えしたいカイゼン内容をご紹介いたします!

1. 新サービス「食べチョク フルーツセレクト」をリリースしました!!

www.tabechoku.com

サービス概要

「食べチョク フルーツセレクト」は毎月、旬の美味しい果物をお届けするサブスクリプションサービスです。

もちろん農家さんから直送です!!

ご契約プランは「1ヶ月」「3ヶ月」「6ヶ月」の3つのプランからお選びすることができ、契約期間が長くなるほど料金がお安くなる料金体系です。

下記の通り、6ヶ月プランだと1ヶ月プランに比べて3000円お得になります!

f:id:vividgarden-tech:20191106213430j:plain:w300

お届けする果物は毎月1日にメールにてご案内いたします。(マイページでもご確認頂けます)

そして、11月は北東農園さんの完熟極早生みかんを予定しております!

私も食べてみましたが、とっても美味しいので是非皆さんのも食べていただきたい一品です。

note.mu

美味しすぎてnoteも書いてますので、よろしければ是非ご覧ください。

2. 記事機能をリリースしました!!

www.tabechoku.com

運営からオススメの商品や情報をお届けする記事機能をリリースしています。

スタッフのオススメや人気商品まとめなど楽しい記事をどんどん配信していきますので是非チェックしてみてください!!

3. トップページに品目の一覧を追加しました!!

f:id:vividgarden-tech:20191107010439p:plain:w300

以前からサイドメニューに品目から探せるリンクを設置していましたが、スマホだとサイドメニューが閉じた状態のため気づきにくく、ユーザーの方々より品目から探せることを知らなかったというご意見を頂きました。

そこでトップページにも品目の一覧を追加してユーザーが分かりやすいようにしています。

4. [生産者向けカイゼン] 注文リストに並び替え条件&絞り込み機能を追加しました!!

これまで注文リストはお届け日順で並んでいるだけで、生産者様の作業に合わせて一覧を表示することが出来ていませんでした。

そのため、生産者様より注文数増加によりご注文の受付業務が煩雑になってきたとのご意見を頂きました。

そこで、生産者様の作業に合わせて注文リストを表示できるように並び替え条件の変更と絞り込み機能を追加しています。

f:id:vividgarden-tech:20191107170435p:plain:w300

並び替え条件

以下の条件で並び替えができるようになりました。

  • 注文日 昇順/降順
  • お届け日 降順/昇順
  • 商品名 降順/昇順

絞り込み機能

以下のステータスで絞り込みができるようになりました。

  • 注文未確定 ... まだ未確認の注文だけを表示します。
  • 発送準備中 ... 確認済みで発送待ちの注文だけを表示します。

最後に

今後もユーザーの皆様・生産者の皆様のお声を積極的に取り入れていきたいと考えております。

食べチョクトップページの下部にご意見フォームを設置しておりますので、なにか気になることがございましたら、ささいなことでもお伝えいただければ幸いです。

また、食べチョクではデザイナー・エンジニアを切に募集しています。
興味のある方は、是非Wantedlyよりご連絡ください!

www.wantedly.com

www.wantedly.com

www.wantedly.com

テレビ放送向けてパフォーマンスを大幅改善しました!2019年9月のカイゼン内容のご紹介

こんにちは、食べチョクエンジニアの鹿倉です。

9月はテレビ番組 「セブンルール」に弊社代表およびサービスを取り上げていただくということで劇的なユーザ増加が予想されました。
予想通り、今までにはないアクセスがあり、たくさんのお客様にサービスをご利用していただくことができました。
その裏で、9月もたくさんのカイゼンを行なっておりますのでカイゼン内容をご紹介いたします。

まずは全体の改善サマリーは以下の通りです。

9月の改善サマリー

  • カイゼン数: 71 項目
    • 新機能: 30 項目
    • バグ改修: 15 項目
    • その他: 26 項目

この中からいくつかピックアップしてご紹介いたします!

1. セブンルール(テレビ放送)に向けてパフォーマンスをカイゼンしました!

見出しの通り、9月はテレビ番組 「セブンルール」の影響で急激なアクセス増加が予想されました。
そのためテレビ放送中であってもサービスをダウンさせることなく快適にご利用頂くためのカイゼンを行なっております。
こちらの内容については弊社エンジニア西尾が別途記事を書いておりますので是非ご覧ください!

tech.tabechoku.com

2. 「酒チョク」をリリースしました!

9/30よりお酒の取り扱いも開始いたしました。珍しいお酒も取り揃えております。
是非、チェックしてみてください!

www.tabechoku.com

ちなみにこのリリースにより10月からカイゼンスピードは爆上がりする予定です。

3. 消費税率の変更に伴う対応を行いました

2019年10月より一部の消費税率が10%に変更になることに伴い内部的な改修を行なっております。
食品は軽減税率が適用され消費税率8%となります。ただし、配送料や酒類に関しては消費税率10%となります。
もともと食べチョク上の表記は全て税込のため、お客様への影響はありません が発行している領収書の内訳については税率ごとに区分した内容を記載するように変更しております。

4. 食べチョクコンシェルジュ(おまかせ定期便)の配送曜日の追加&申し込みフォームをカイゼンしました!

これまで食べチョクコンシェルジュのお届け可能曜日は水曜日と土曜日のみでしたが、木曜日と金曜日もお届け可能となりました。

お届け希望曜日の変更は[マイページ->コンシェルジュ情報->申込変更]より行うことが可能ですので、水曜日と土曜日以外をご希望のお客様は変更をお願いいたします。

f:id:vividgarden-tech:20191001223359j:plain:w300

また、初回お申し込みフォームについてもお客様の入力の手間を減らすようカイゼンを行なっております。

5. 商品ページで生産者のこだわりが見れるようにしました!

これまでお客様が商品を選ぶために最も重要な「生産者のこだわり」が商品ページでは見ることができず、生産者ページでしか確認することが出来ませんでした。
お客様がスムーズに購入が出来るように、商品ページでも「生産者のこだわり」が見れるようにしました。

f:id:vividgarden-tech:20191001224938j:plain:w300

6. ページの表示速度をカイゼンしました!

ページの初期表示の速度改善のために画像の遅延読み込みを導入しました。 これまではページ内にある画像を全て初回に読み込むようになっており、ページがリッチになっていくにつれて初期表示が遅くなる問題がありました。
今回の対応により、画面に表示されている画像だけを読み込むようになり、初期表示速度がカイゼンしております。

以下、PageSpeed Insightsの改善前と改善後の結果です

Before
f:id:vividgarden-tech:20191001232639j:plain:w500

After
f:id:vividgarden-tech:20191001232707j:plain:w500

今まで、遅すぎないか、、、(汗)
というのもありますが、大きく改善することが出来ました。
速度はユーザビリティに大きく影響しますので、引き続きカイゼンを行なっていきます。

最後に

9月はテレビ放送という1つの山場を越えることが出来ました。
10月以降も引き続き、大きなカイゼン予定が詰まっていますので気を引き締めて頑張ります。

また、食べチョクではまだまだエンジニアを募集しています。
興味のある方は、是非Wantedlyよりご連絡ください。

www.wantedly.com

www.wantedly.com

食べチョク開発チームがセブンルール(テレビ放送)に向けて行った負荷対策

皆さんこんにちは、エンジニアの西尾です。

2019年9月24日(火)、フジテレビ系列のテレビ番組 「セブンルール」 にて弊社代表およびサービスを取り上げていただきました。

テレビ放送開始直後、サイトにはいままでにないアクセスが押し寄せましたが、 システムを落とすことなく、かつパフォーマンスも落とすことなく無事に稼働させることができました。

今回はテレビ放送前に、食べチョク開発チームが行ったパフォーマンス対策を紹介いたします。

開発チームのミッション

放送日が決まった時、代表が一言、Slackにつぶやきました。

f:id:vividgarden-tech:20190925223927p:plain
サーバーを落とさない

放送中にサーバーは絶対落とさない。 この日から少しずつパフォーマンス対策を始めることにしました。

食べチョクのシステム構成

食べチョクはAWS上で運用しており、構成はとてもオーソドックスなものとなっています。

EC2,ELB,RDS,ElasticCache(Redis),S3,CloudFrontを主に利用しています。

フレームワークはRuby on Rails + React、DBはMySQLです。 データベースのレプリケーションは行っていません。

ボトルネックがどこになりそうか

遅そうだなあと、なんとなく思っている箇所はいくつもありましたが、 闇雲にパフォーマンス・チューニングをはじめても本当に効果があるのかはわからないものです。

ステージング環境にApache Benchをかけて、どこがボトルネックになりそうかを探ります。

f:id:vividgarden-tech:20190925224100p:plain
アプリサーバー、重い!

アプリサーバーがとにかく重くて、性能がでない。

放送日前までに行ったパフォーマンス対策

めちゃくちゃ高性能なインスタンスとDBを立てれば、高負荷にも耐えられる可能性があります。 しかしお金が無限にあるわけでもなく、アクセス数の見積もりが間違っていたらサーバーが落ちてしまう可能性があるため、 アプリのパフォーマンス改善も行いました。

NewrelicとPrometheus(Grafana)を見ながら、1つずつ重い箇所を改善していくことにしました。

改善1. N+1 クエリ と N+1 partial rendering の撲滅

N+1クエリ、気をつけているつもりですが、ちょいちょいいました。 また一覧系のViewに N+1 partial renderingもいて、パフォーマンスを悪化させていました。

まずは簡単なところから改善。

改善2. 重い処理を分割・改善する

たとえば、以下は改善前の商品情報ページのコードですが、もうなんというか見ただけで重そうで、実際重かったです。

f:id:vividgarden-tech:20190925224130p:plain
改善前の商品詳細のコード

重い処理があるとサーバーが詰まってしまうので、一部を別処理・別APIに切り出したりしました。 Fat Controllerも改善できて、よかった。

改善3. ムダにデータを読み込んでいるところを無くす

不要なデータを読み込んで、かつレンダリングまでしている箇所がいくつもありました。 たとえば生産者一覧画面。HTMLをよくよく見てみると...

f:id:vividgarden-tech:20190925224202p:plain
無駄なデータ読み込みを排除

データは全部読んで、CSSで隠してました。ひえぇ。

改善4. Fragment Cacheを利用する

Railsのビューレンダリングは遅いし、重いです。

Fragment Cache を入れられるところは入れていき、 処理速度を改善していきました。

改善5. 共通ヘッダーを軽くする

ヘッダーは基本全画面に表示されますが、微妙に処理が遅かったです。 ここを少し早くすれば全ページの読み込み速度が改善されるので、キャッシュを部分的に入れるなどして速度改善をしました。

改善6. 静的ファイルをCDNで配信する

画像ファイルはCDNを経由して配信していたのですが、 jsファイルや一部バナー画像などはWebサーバーから直接配信していました。

Webサーバーの負荷は少しでも軽くしたいので、これらをCDN経由で配信するように改修しました。

改善7. RailsでDBレコード取得部分のキャッシュを入れる

アプリサーバーはスケールアウトできるのですが、 データベースは現状レプリケーションを行ってないため、スケールアウトができない状態です。

データベース(RDS)のスケールアップはすぐにはできず、 テレビ放送中にデータベースがパンクしたらシステムダウンを発生させてしまう可能性がありました。

マルチDB対応は時間的にもすぐにはできないと判断し、 今回はDBのレコードを部分的にRailsでキャッシュさせ、 データベースアクセスをできるだけ少なくするような改修をしました。

このキャッシュ機能は基本的にはオフにしているのと、重要な処理では使わないようにしています。 例えば商品詳細で商品レコードをキャッシュして、 実際には商品が売り切れているのに売出し中と表示されてしまう、みたいな問題が起きないように注意しました。 またキャッシュクリアの仕組みも作りました。

テレビ放送中は、キャッシュをTTL=15分でONにすることでデータベースの負荷を下げることにしました。

改善8. アプリサーバーのパフォーマンス・チューニング

普段はアプリサーバーにはEC2のmediumインスタンスを使っていますが、 テレビ放送当日はc5.2xlargeを使いました。

最初はxlargeくらいにしようと思っていましたが、負荷試験を再度したところ、 思いの外CPUがいっぱいいっぱいでレスポンスが悪くなりがちだったので、2xlargeを使うことにしました。

f:id:vividgarden-tech:20190925224230p:plain
2xlargeは高いけど、使っちゃうか

2xlargeのインスタンスに合わせて、puma(アプリサーバー)のworker数とthread数を調整しました。 また、DBのconnection poolの値もworker/thread数に合わせて増加させました。

改善9. ELBの暖気申請を出す

これはパフォーマンス・チューニングではないのですが、 ELB(ロードバランサー)は突然の高負荷でアクセス不可になってしまう可能性があります。

DevelopersIOの記事 を参考に、AWSにあらかじめELB暖気申請を出しておきました。

サーバーが落ちた時のために

放送日前日までパフォーマンス対策を行っていましたが、 システムが落ちない保証はありません。

最悪の事態に備えて、Sorryページ(エラーページ)も作ることにしました。 使う事態にならなくて助かりましたが、一応以下ページを用意していました。

f:id:vividgarden-tech:20190925224258j:plain
Sorryページ

そして迎えたテレビ放送

9月24日のセブンルール(テレビ放送)は、夜の23:20から始まりました。 本番開始直後の開発チームのSlackを張っておきます。焦っていて日本語がおかしい。

f:id:vividgarden-tech:20190925224314p:plain
放送が始まった時のSlack

システムが落ちないか心配で、放送時間中にはちゃんとテレビは見られませんでした。 Grafana(モニタリングツール)とGoogle Analyticsを見ながらサーバー落ちるなと祈っていましたが、 放送開始から終了までの30分間、アクセスを無事さばき切ることができました。

パフォーマンスはどうだったか

番組放送中の食べチョクトップページと商品詳細ページのパフォーマンスは以下のとおりです。

放送中でめちゃくちゃアクセスが来ているにもかかわらず、問題ないパフォーマンスで アクセスをさばけました。

f:id:vividgarden-tech:20190925224345p:plain
放送中のトップページのパフォーマンス

トップページ (User::TopController#show) のパフォーマンスが0時30分過ぎから落ちているのは、 改善7でいれたDBのレコードキャッシュ機能をオフにしたためです。

f:id:vividgarden-tech:20190925224405p:plain
放送中の商品詳細ページのパフォーマンス

商品詳細も高速にレスポンスを返しています。

おわりに

食べチョクが地上波に30分間取り上げられるのは初めてで、 サイトにはかつて無いアクセスが押し寄せましたが、無事乗り切ることができました。

パフォーマンス対策にあたり、ISUCON 参加の経験が役に立ったなと感じました。 「推測するな、計測せよ」が重要で、Newrelic+Grafana(モニタリングツール) と実装を見比べながら、地道に改善を行いました。 ISUCON、今年は忙しくて参加できなかったのですが、来年は開発チームのメンバーと出場できたらと目論んでいます。

これからも食べチョクには、どんどん新しい機能を盛り込んでいく予定ですが、 パフォーマンス対策も忘れずに実装いけたらと思います。

放送を見逃した方へ

以下サイトで10/1(火)22:30まで見逃し配信をしています。

tver.jp

ktv-smart.jp

関連リンク

よろしければこちらも御覧ください。

note.mu

www.wantedly.com

www.wantedly.com

Serverless Frameworkで進捗レポーティングツールを作成した話

皆さんこんにちは、エンジニアの西尾です。

今日は食べチョク開発チーム内で利用している、進捗レポーティングツールのご紹介をいたします。

TL;DR

  • 食べチョクの開発チームはJIRAを使ってタスク管理をしているが、JIRAのレポートが見づらい & 使いづらい
  • JIRAのレポートは使わずに、独自にGoogle Spreadsheet上にレポートを出力して進捗を見ている
  • Google spreadsheet上にいい感じのレポートを生成するツールをServerless Framework + Google App Scriptで作成した
  • Serverless Frameworkで作成したツールは、オープンソースで公開します(本文参照)

JIRAのレポートが見づらい & 使いづらい

食べチョクの開発チームでは、スクラムを組んで開発を行っています。 スプリント期間は1週間、短く回してなるべく素早く開発をこなすようにしています。

今回はスクラムの話は置いておいて、この開発にかかせないツールがJIRAです。 JIRAにはスクラムボード & カンバン機能など、アジャイルツールが充実しています。

JIRAはタスク管理ツールとしては申し分ないのですが、レポート機能に関しては不満があります。

たとえばベロシティチャート。各スプリントの消化ポイントはだせますが、 スプリント内でどのタスク・エピックをどのくらいこなしたのかパット見でわかりません。 開発チームでは、大きな機能の改修にはエピックをつけて進捗のトラッキングをしています。

f:id:vividgarden-tech:20190909210919p:plain
JIRAのベロシティチャート。スプリントのポイントブレブレじゃん、っていうツッコミは、なしで

ベロシティもエピック毎、担当者毎、タスク種別(バグ or not)毎に見て振り返りをしたいのですが、簡単に出力できないのが辛い。

エピックレポートもいちいち1つずつレポートを見ないといけないのですが、 これも複数タスクを俯瞰して見られたらなあという思いがありました。

欲しかったレポート

JIRAのレポートが見づらいのならば、別途Google spreadsheetなりなんなりで出力すればいいのでは。 欲しかったレポートは以下のとおりです。

ベロシティチャート (エピック毎、担当者ごと、タスク種別毎)

f:id:vividgarden-tech:20190909214614p:plain
ベロシティの内訳は? どのタスクをどのくらい消費したの?が一発でわかるグラフ

エピックの進捗

f:id:vividgarden-tech:20190909214430p:plain
このエピック、進捗悪すぎじゃない? っていうのがひと目で分かるグラフ。ごめんなさい

Serverless Frameworkでサクッとツールを作る

このくらいのレポートを出力するだけならば、 正直Google App ScriptをJIRAにつないで、ごにょごにょするだけで作れそうだなあという気がします。

でもせっかくだから、レポートの元データを作る処理を今流行りのサーバーレスアーキテクチャで作ってみようかなと思い、 以下技術を使って作ってみることにしました。

  • Serverless Framework
    • インフラメンテとかしたくないので、作るならサーバーレスかなぁ。フレームワークはメジャーっぽいやつ選んでみるか、くらいのノリで採用しました。
  • Node.js
    • ローカル環境で開発したいです。Nodeがツール周りで一番充実してそうだったので選択。
  • Typescript
    • 型がほしい
  • DynamoDB
    • ServerlessならDynamoDBだろうくらいのノリで選んだ
  • AWS
    • 食べチョクはAWSで動いている
  • Google App Script + Google Spreadsheet
    • グラフ描画を独自に作るのは辛いし、そこまで工数かけたくないので、結果出力はGoogle Spreadsheetに。

サーバーレスアプリケーション側は、シンプルに以下のようなJSONを返すような作りにしました。 Google spreadsheet(Google App Script)側では、この結果をグラフに落とし込むところだけを実装しています。

[
    {
        sprintName: "dev-sprint-66",
        epicId: "ここにエピックID",
        epicName: "ここにエピック名",
        point: 10
    },
    {
        sprintName: "dev-sprint-66",
        epicId: "別のエピック",
        epicName: "ここにエピック名",
        point: 8
    },
]

作ったツールを公開します

完成したツールは以下のとおりです。

github.com

Google App Script側のコードは公開してないのですが、レポートの元データを作り出すツール側のみ公開してます。

このツールは、弊社開発チームのレポート出力機能に特化しており、そのまま他の会社・プロジェクトで使えるものではないと思っています。 しかし、Serverless Frameworkでアプリを作るとこういう感じになるよ、というexampleにはなるかなと思い、今回公開することにしました。

おわりに

開発チーム内で使っているツールを、サーバーレスアプリケーションとして作成しました。 せっかく作ったので、サーバーレスでアプリ作るとこうなるよ、というサンプルには使えるかなと思い、ソースも公開することにしました。

サーバーレスアプリケーションは、インフラ管理の手間もなく、サクッとアプリを作れるのは楽だなと感じました。 今後簡単な社内ツールは、サーバーレスで作っていけたらなと思っています。