31
トレタのMySQL MySQL casual #8 (小規模限定回) 2015/11/20 Hiroaki Sano

トレタのMySQL MySQL casual #8

Embed Size (px)

Citation preview

Page 1: トレタのMySQL MySQL casual #8

トレタのMySQLMySQL casual #8 (小規模限定回)

2015/11/20 Hiroaki Sano

Page 2: トレタのMySQL MySQL casual #8

me• Name:

• 佐野裕章(Hiroaki Sano)

• Personal info: • https://hiroakis.com/blog/ • https://twitter.com/la_luna_azul • https://github.com/hiroakis

• Company: • SIer(2006/4-) • CyberAgent, Inc(2011/3-) • Toreta, Inc (2014/11-)

Page 3: トレタのMySQL MySQL casual #8
Page 4: トレタのMySQL MySQL casual #8
Page 5: トレタのMySQL MySQL casual #8

トレタ• 飲食店向け予約管理アプリケーション、一般ユーザ向けにウェブ予約も提供

• 顧客:レストラン、居酒屋などの飲食店

• 例えば…

• 俺の株式会社様(俺のイタリアン、俺のフレンチ…etc)

• 株式会社大庄様(庄や、やるき茶屋…etc)

• ローストホース様

• etc • ローンチは2013年12月

• 導入店舗数:3700店舗~

• 店舗あたりの月額課金

• オフィスは五反田

Page 6: トレタのMySQL MySQL casual #8

これを

Page 7: トレタのMySQL MySQL casual #8

こうじゃ

Page 8: トレタのMySQL MySQL casual #8

トレタのコンセプト

• 飲食店の予約管理、顧客管理をIT化

• 予約事故を防ぐ

Page 9: トレタのMySQL MySQL casual #8

トレタ

• アクセスのピーク

• 時刻:夕方6 pm JSTあたり

• 曜日:金曜日 > 他 > 土曜日 > 日曜日

• 夜の営業開始直後くらいに当日の予約確認をしていると思われる

Page 10: トレタのMySQL MySQL casual #8

トレタのMySQL

Page 11: トレタのMySQL MySQL casual #8

話すこと

• 経験上、大規模から小規模に変わった時に感じたことか、小規模ならでは(?)のことをば…

Page 12: トレタのMySQL MySQL casual #8

トレタ

MySQL Master

Engineyard

ブラウザiPad

MySQL Slave

MySQL Slave

Users

App

• サーバ側のコア機能はEngineyardで稼働

• 一部機能はAWS(S3, Route53..etc)やHerokuにも存在

• モニタリング、分析などはSaaSを利用Mackerel, NewRelic, BigQuery…etc

Page 13: トレタのMySQL MySQL casual #8

トレタのMySQL

MySQL Master

Appサーバ

MySQL Slave

MySQL Slave

Rails

HAProxy

Write

Read

Replication

TCP 127.0.0.1:3306

• MySQL on Engineyard

• MySQL5.6

• InnoDB

• m3.largeが3台(Master:Slave=1:2)

• ほとんどがRead

• スレーブは主にバッチの参照用、調査用、昇格用

• 小規模(だと思う)。

worker

Page 14: トレタのMySQL MySQL casual #8

小規模ならではのことってなんだろう

• トレタの場合…

• Likeフィルタによる全文検索が割と動く

• キューっぽい使い方をしても割と動く

• 大規模に比べると運用が楽(シンプルなので)

Page 15: トレタのMySQL MySQL casual #8

全文検索

Page 16: トレタのMySQL MySQL casual #8

全文検索

SELECT DISTINCT `reservations`.`id` FROM `reservations` LEFT OUTER JOIN `customers` ON `customers`.`id` = `reservations`.`customer_id` LEFT OUTER JOIN `customer_phones` ON `customer_phones`.`customer_id` = `customers`.`id` LEFT OUTER JOIN `customer_emails` ON `customer_emails`.`customer_id` = `customers`.`id` WHERE `reservations`.`restaurant_id` = XXXX AND ( concat(customers.last_name, customers.first_name) like '%さの%' OR concat(customers.last_name_reading,customers.first_name_reading) like '%さの%' OR customers.company_name like '%さの%' OR customers.note like '%さの%' OR reservations.note like '%さの%' OR customer_emails.email like '%さの%' OR ) ORDER BY `reservations`.`start_at` DESC LIMIT 10 OFFSET 0;

• Likeの部分一致検索

Page 17: トレタのMySQL MySQL casual #8

全文検索

• InnoDBのフルテキストインデックスは使っていない

• Mroongaも使っていない

• WHERE `reservations`.`restaurant_id` = XXXXでの絞り込み

• (レコード数が少なければ)案外なんとかなる

Page 18: トレタのMySQL MySQL casual #8

カジュアルな話:なぜ部分一致検索は遅くなりがちか

• ここで試しに本当にカジュアルな話も挟んでみます…

• select * from t where col like ‘%word%’ • 理由:インデックスが効かないから

• Indexing LIKE Filters • http://use-the-index-luke.com/sql/where-clause/searching-for-ranges/like-performance-

tuning • インデックスとは索引。つまりそのままの意味。

• 前方一致のみインデックスが効く。

Page 19: トレタのMySQL MySQL casual #8

Likeフィルタで発生した事案• select * from t where col like ’{POST_DATA}%’

• 空のPOST_DATA

• select * from t where col like ’%’ • DBマスタのCPUが100%になってしまった

• バリデーション漏れ

Page 20: トレタのMySQL MySQL casual #8

MySQLでキューのようなことをする

Page 21: トレタのMySQL MySQL casual #8

MySQLでキューのようなことをする

MySQL Master

Appサーバ

MySQL Slave

MySQL Slave

Rails

HAProxy

Write

Read

Replication

TCP 127.0.0.1:3306

• 現地時間の午後3時に一部の顧客にメールを送る機能

• 定期的に各appサーバのworkerが一斉に動き出してMySQLに参照/更新を行う

• あるworkerが処理中のレコードは他のworkerに処理させたくない

• workerはsidekiq(非同期処理を行うgem)で実装

• 基本的には非同期的な処理を行っているがこのようなバッチ処理ぽいこともworkerにやらせている

worker

Page 22: トレタのMySQL MySQL casual #8

MySQLでキューのようなことをする

MySQL

WorkerWorkerWorkerWorker

CREATE TABLE `queues` ( `id` int(11) NOT NULL AUTO_INCREMENT, `reservation_id` int(11) NOT NULL, `status` tinyint(4) NOT NULL DEFAULT '0', `reminded_at` datetime DEFAULT NULL, `created_at` datetime DEFAULT NULL, `updated_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `index_reservation_remind_emails_on_reservation_id` (`reservation_id`), KEY `index_reservation_remind_emails_on_status_and_reminded_at` (`status`,`reminded_at`) ) ENGINE=InnoDB AUTO_INCREMENT=343046 DEFAULT CHARSET=utf8

select * from queues where status = 0 and reminded_at < now() => レコードが未処理(status=0) かつ現地時間午後3:00を過ぎているもの(reminded_at < now())を取得

update queues set status = 1 where id = ? and status = 0 => レコードを処理中としてマーキング(status=1) => ここでaffected_rowsが1なら自分が処理する 0だと他のworkerが処理するので何もしない => 処理に成功したらレコードを消す

• 参考

• http://qiita.com/masuyama/items/645b25f03dc9e321cb96

Page 23: トレタのMySQL MySQL casual #8

MySQLでキューのようなことをする

• 下記の記事で指摘があるように、正直、悪手ではある

• http://www.engineyard.co.jp/blog/2013/5-subtle-ways-youre-using-mysql-as-a-queue-and-why-itll-bite-you/

• が、レコードが大量でなければ問題なく動く。

Page 24: トレタのMySQL MySQL casual #8

大規模に比べると運用が楽

Page 25: トレタのMySQL MySQL casual #8

大規模と小規模の構成の違い(経験上)

大規模時代 今

バージョン 4.1, 5.0, 5.1, 5.5, 5.6 5.6

ストレージエンジン MyISAM, InnoDB, Blackhole InnoDB

インフラ大量のハイエンド物理サーバWrite Heavyな箇所にはフラッシュを活用

クラウドに3台

特徴

種々の課題に対応するためにインフラ、MySQL側やることが多くなりがち(だった)。シャーディング、レプリケーションフィルタによるテーブルの分散、InnoDBの圧縮の活用、カスケード構成…etc

業務アプリケーションのバックエンドなのでトラフィックはある程度予測できる。ウェブ予約で多少のスパイクあり。 コンフィグとしては基本的な設定をケアすれば良い。

Page 26: トレタのMySQL MySQL casual #8

大規模に比べるとシンプルなので運用が楽

• 小規模だから楽、というよりは構成がシンプルだから楽。

• 大規模になってもシンプルなアーキテクチャを保つべき。

• Railsのmigrationで運用が回る

Page 27: トレタのMySQL MySQL casual #8

まとめ• 小規模だと…

• 多少の悪手でも大丈夫

• Likeフィルタの部分一致検索

• キューもどき

• かといってインデックスが全く効かないのは危険

• ちゃんとインデックスを張る。インデックスを使うようなSQL投げる。

• ちゃんとモニタリングする

• 大規模よりは運用は楽

• 構成がシンプル

• 大規模になってもシンプルな構成を維持すべき

• ある程度の規模まではMySQL+著名なフルスタックフレームワークでビジネス要件が満たせるシステムが構築できている

Page 28: トレタのMySQL MySQL casual #8

迫り来る課題

• 悪手からの脱却

• その用途に最適化されたミドルウェアの導入etc

• Railsのmigrationに依存した運用からの脱却

• 生のalterがだんだんキツくなってくる

Page 29: トレタのMySQL MySQL casual #8

最後にサラリーマンとして言わせてください

Page 30: トレタのMySQL MySQL casual #8

We are hiring!https://toreta.in/recruit

Page 31: トレタのMySQL MySQL casual #8

おわり