そーだいなるらくがき帳

そーだいが自由気侭に更新します。

PHPカンファレンス福岡2018は最高だし、だから僕は関西にも行くんだ。

PHPカンファレンス福岡2018に遊びに行ってきました。

phpcon.fukuoka.jp

今回は リジェクトされたので 登壇無しで久々に一般参加者としてカンファレンスに参加してきました。

登壇しなくても参加するメリット

先日、こんなQiitaの記事も盛り上がっていましたね。

qiita.com

私も記事の内容には同意です。 元記事を引用しながら実際に私がPHPカンファレンス福岡で感じた経験を書きます。

そもそもセッション受講が目的なのか?

1日のうち、全く身のないセッションに1回も出会えないなんてことは稀です。 私は後藤さんのセッションは自分の中でSOLIDの原則がもう一段深く理解できただけでなく、スピーカーとして学ぶところも沢山ありました。 さすがPHPerKaigiのベストスピーカーといった感じです。

PHPカンファレンス福岡2018で「SOLIDの原則ってどんなふうに使うの? オープン・クローズドの原則編(拡大版)」を話しました - HITORIGOTO

セッションには、聴講だけでなく参加した方が良い

実際に 後藤さんのセッションを聴き終わったら一目散にAsk The Speakerコーナーに向かい質問攻めしました。 そこで 未来のバリエーションを時間をかけて検討するよりも今、新たに現れたバリエーションを適切に対処することの方が大事 であることや、場合によっては 適切に捨てることが大切なので捨てやすさを意識する など色んなヒントをいただけました。 これは私の視点を大きく広げる経験ができ、これだけで充分すぎるほど価値があったと言えます。 書き出すとすべてのセッションを書くことになるのでこれはほんの一例ですがこの他にも素晴らしいセッションが目白押しでPHPカンファレンス福岡には感謝の気持ちでいっぱいです。

人の意識出来る範囲は(経験*コネクション)の量に比例する

これは私の持論です。 例えば先日、関西で大きな地震がありましたがすぐ知り合いの顔が沢山浮かびました。 これと同じように技術的にも多くのレイヤーを知っている方が想像出来る範囲は広がりますし、視野も広がります。 逆にコネクションを広げるには多くの技術に触り、沢山の人に出会い、そして色んな話を聴くことです。 その場にカンファレンスは最適ですし、他の地方のイベントならなおさらです。 だから私は地方のイベントに参加するし、色んな人に会いに行きます。 それが私の視野を大きく広げ、新しい選択肢に気付かせてくれるのです。

じゃあ次は何処にいくの?

PHPカンファレンス福岡2018が最高だったという記事を沢山みた皆さん、カンファレンスに行きたくなったでしょう? そんなみなさまに朗報です。 なんとPHPカンファレンス関西2018が7/14に開催されます。

2018.kphpug.jp

これを気に皆さんもカンファレンスに参加してみませんか? ちなみに前夜祭も非公式ですがあります。

dbstudychugoku.connpass.com

カンファレンス、登壇も楽しいけど一般参加もめっちゃ楽しいで最高です。

DBリファクタリングをやっているって話

言語の勉強会でその言語の話をしない人ランキング堂々の第一位、そーだいです(当社比

控えめに言っても最高な毎度おなじみ #kichijojipm で今日LTする話の補足です。

kichijojipm.connpass.com

speakerdeck.com

タイトルは出落ちです。 全然最強じゃなくて頑張ってやってるよって話です。 資料がかなり薄いので補足します。

DBリファクタリングについて

26Pは現状です。

f:id:Soudai:20180525103435p:plain

MySQLって書いてますが本番はAurora1を使っています。 以降、このDBを 現行DB と呼びます。 ここではオミカレとみんなの婚活はWebサービス名です。 つまり2つのサービスから現行DBを見ていますし、機能によっては現行DBの同じテーブルを参照・更新・削除などを行います。 この2つ以外にも社内システムなどでこの現行DBは利用されており、メインのテーブルを変更すると影響範囲が広い状態です。 このようなモノリスな状態は世の中では悪の権化のように言われますがスタートアップが取る戦略としては悪くないと思っています。 なぜならばRDSは会社の運用で考えると激安ですが個人の支払いと考えると安くなく、オミカレもサービス開始当初は社長のポケットマネーで運用されていたわけですし、インフラコストを抑えることは重要な課題でした。 しかしながら時は経ち、サービスが大きくなるとこの状態は気軽な変更を許さない状況になります。 特に主要なテーブルは多くのサービスが参照しているのでALTERを流すのは職人芸になりますし、マスタ系に長時間テーブルの共有ロックを取るようなSELECTを流すとサービス障害の呼び水になります。 なのでこの状態から脱していくために現在進めているのが下記です。

f:id:Soudai:20180525105034p:plain

右側にあるPostgreSQLのバージョンは10でRDSで運用されています。 これを以降は 新DB と呼びます。 現行DBから新DBのデータはリアルタイムで行っています。 MySQLならレプリケーションでいいのですが異種DBなのでここはAWS DMSを活用しています。 他にもOracle GoldenGateも調べましたがRDSに移すと決めたのでAWS DMSを採用しています。 現行DBから新DBには同じスキーマ構造(テーブル構造)でコピーします。 その際に新DB側にINSERT・UPDATEなどのEventをトリガーに更に自分たちが使いたい新テーブル構造にコピーしています。 つまり新DB側には現行DBにあるスキーマリファクタリング後の形である新スキーマがあります。 WebAPIはこの新スキーマを参照します。 しかしWebAPIは新DBに書き込むと現行DBを参照しているシステムに影響があるため、現行DBに書き込みを行います。 するとDMS経由でコピーされ、トリガー経由で新DBの新スキーマに反映されるという流れです。 こうやって少しずつ既存のDBアクセスをWebAPIに寄せていき、最終的にはリファクタリングされたDBに切り替えていこうというのが主旨です。

この手法のメリット・デメリット

メリットは次のような点です。

  • 停止時間を極小化できる
  • 既存の仕組みを残したまま、アグレッシブにリファクタリング
  • カナリアリリースのような感じ少しずつ切り換えていける
  • 新規開発が既存の仕組みに与える影響が少ない
  • 既存からの切り換えはリポジトリパターンなどを使えばブルーグリーンデプロイメントっぽく参照先を切り替えるだけに出来る

このように本番の仕組みの影響範囲を小さく出来るのがメリットです。 しかしながら大きなデメリットもあります。

  • ローカルの開発環境で再現できない
    今まではVagrantVMを一つ立ち上げるだけで済んでた
  • 関係するシステムが多いので開発環境を用意する手順が多い
    AWSに開発環境を作る場合も手順が多く、1人1環境を用意するのが大変
  • DMS経由のコピーに遅延が発生した時の考慮が難しい
  • リファクタリングが終わるまで、AWSに依存する
    例えばDMSの仕様変更とか食らうと死ぬ可能性がある

特に開発フローに与える影響はめちゃめちゃ大きいです。 ですのでDMSに依存した状態をずっと続けることは難しいのここからはスピード勝負だなと思っています。

なぜPostgreSQLに移行するのか

色んな理由がありますが決め手はリファクタリングの時に書くトリガーの柔軟性とストアドです。 PostgreSQLPL/pgsql 以外にも PL/perl を始めとした代表的な言語で書く仕組みがあります。 当初は PL/Python で書こうと思っていたのですがRDSがサポートしていなかったため、 PL/v8(JavaScript) を採用しています。

今後

まだまだ移行途中ですので、辛い思いなどはこれから色々とあると思います。 それはそれで定期的にアウトプットしていくし、実際に手を動かしているチームメンバーが生の声をアウトプット出来るように勧めていくのでお楽しみに

最後

ここからはスピード勝負だなと思っています。

つまりオミカレは仲間を大募集中なのじゃ!!

f:id:Soudai:20180525120638p:plain

現状は常時リモートを推奨はしてないので東京か岡山の採用になります。 ご興味があればお気軽にご連絡ください!! ご連絡いただければカジュアルランチもリモート面接も調整します。 お給料とか包み隠さず出せる金額もお応えしますし、出勤時間や働き方など柔軟ご提案できます。

party-calendar.net

それではご応募お待ちしております。

MySQLの0000-00-00 00:00:00は使ってはならない

結論

色々困るので使わない。

理由

以下に理由を述べる

SQL標準ではない

正論で殴った場合。

0000-00-00 00:00:00の仕様が難しい

0000-00-00 00:00:00MySQLの独自な仕様で NOT NULL制約のカラムではNULLと等価であり、NULLではない という仕様がある。

この仕様を知らないと意図しない結果を取得することになる。

ORMなどが対応してない

アプリケーションを作る時、ORMだったりDatetimeオブジェクトを操作することはよくあることだ。 しかし 0000-00-00 00:00:00 に対応していないことが多く、不正な値として扱われる。

DB移行で困る

なんらかの理由でMySQL以外のRDBMSに移行しようとした時、 0000-00-00 00:00:00 をサポートしていないことで値の修正が必要になる。 具体的な例だとMySQLからPostgreSQLにデータを移そうとした時にerrorでコケる。 AWS DMSもFDW for MySQLもそこを意図してないので自分で値を修正してやる必要がある。 なかなか異種間DB移行はないかもしれないけど、いざ必要となった時にめちゃくちゃ困る。

まとめ

せめてNULLだったり 0001-01-01 00:00:00 を使いましょう。

って7年前の俺に言いたい。

追記(2018/05/14)

MySQLの 0000-00-00 00:00:00 は使ってはならない - そーだいなるらくがき帳

zero date問題はMySQLerにはあるあるネタだけど、SQLモードでzero dateを禁止できるんで、SQLモードにも触れて欲しかった。5.6からデフォルトでNO_ZERO_DATE, NO_ZERO_IN_DATEモードがONになっている。

2018/05/14 17:06
b.hatena.ne.jp

nippondanji.blogspot.jp

SQLモードを正しく設定していればこの問題にはぶち当たらないのでSQLモードはちゃんと kamipo TRADITIONAL を使うべし。

www.songmu.jp

ユーザ情報を保存する時のテーブル設計

はじめに

※この発言は個人の見解であり、所属する組織の公式見解ではありません

用法用量を守り、個人の責任で業務に投入してください

要件

User情報を保存するときにどのようなテーブル設計を行うか

今北産業で頼む

  • テーブルに状態を持たせず状態毎のテーブルを作る
  • 状態が変わればレコードを消して別のtableに作る
  • tableの普遍的な情報は別に持たせる

僕の考えた最強のDB設計

PostgreSQLをベースの雑なER図を作った。 これを元に話を進める。

f:id:Soudai:20180501192200p:plain

table構成

users

親tableであり、すべてのユーザはここに属する。 基本はINSERTのみでUPDATE、DELETEを考慮しない。

user_detail

userに付随する詳細の情報がここに登録される。 一般的にusersにカラムを増やしたいような内容はここに登録する。 なぜusersにカラムを増やさないのか? それはusersは親tableであり、親tableの更新は常にデッドロックのリスクがあるから。 こちらはMySQLの例だが外部キー制約とデッドロックについては下記の話がわかりやすい

www.tree-tips.com

もちろん子tableの更新の際に適切に親tableのxlock(排他ロック)を取ればデッドロックは防げる。 それではトランザクションの管理が1段階複雑になるし、そもそも適宜トランザクションを利用する必要がある。 しかしここでは適宜トランザクション利用することは人類には難しいという大前提で話をする。 トランザクションを適宜発行することが難しいと考えた場合、子tableのみを更新するようにすれば子tableのトランザクションだけを意識すれば良い。 特定のtableのみを1回更新する処理であればRDBMSが適宜対応してくれるのでロックをとる必要も無い。 その他の項目については自分たちに必要なカラムを追加してくれれば良い。

propertiesとoptionについては今回の本質ではないが説明する。 この2つのカラムはJSONB型を採用している。 PostgreSQLの性質として式INDEXの機能があるため参照の速度はJSONB型を利用しても問題ない。 これにより簡単にスキーマレスの設計を実現することができる。 ただし大きな問題点として 部分更新に弱い値に対する制約が弱い いう問題がある。 出来ない ではなく 弱い と表現した理由は部分更新も値に対する制約もjsonb_replaceや || などで行うことができるし(ともに9.5から)CHECK制約を駆使すれば値に対する制約もすることは可能。 あくまで可能なだけで部分更新や値に対する制約が必要な場合は適切にカラムに切り出し、または正規化を行った方が良い。

propertiesは主に住所や電話番号など 本来は必ず登録するユーザ情報 を想定している。 本当に必ず登録するならカラムに切り出し、NOT NULL制約をつけるべきである。 しかし我々は楽がしたい、そんな時の逃げ道としてこういう使い方もあるという話である。 ただし、私がインターネットで相談を受けたら 絶対にカラムに分けましょう と回答するが実際にはJSONを入れたいときは使うかもしれないと言う例だ。 あくまで例として紹介したが10回中9回はカラムにしたほうがいい。

optionはその名の通り 必須ではない項目 である。 本来のスキーマレスなJSONB型を使いたいケースは多くはこちらのケースだ。 しかし本来、そのようなケースであれば下記のようなuser_optionsを作るべきだ。

user_id option_name value
1 仕事1 格ゲー
1 仕事2 篠崎愛
1 趣味 プログラミング

しかしこれは EAV (エンティティ・アトリビュート・バリュー) と呼ばれるアンチパターン であることは気をつけなければならない。

qiita.com

クロス集計(俗に言うPivot)はOracleDBのような関数としてはPostgreSQLにもMySQLにも無く(SQL標準では定義されてるけど)CASEやGROUP BYで とても頑張る 必要がある。 一応、PostgreSQLの拡張である tablefunc を有効化すればクロス集計をSQLで書くことは出来るが簡単ではない。 そこで設計の選択肢としてJSONB型を検討するのは妥当な選択肢の一つとなるのだ。 このような設計は自分は実際に何度か採用したことがある。 この違いを紹介するために敢えてpropertiesとoptionを紹介した。

user_token&user_auth_logとuser_active&user_leave

user_token&user_auth_logはよくあるtableである。 このようなtableの際に問題になるのはusersのレコードを削除すると外部キー制約としてこれらのtableが紐付いているため、usersのレコードを削除出来ないことだ。 そのため、読者のみんなも泣く泣く外部キー制約を外したり、usersに delete_flag というカラムを追加したことがあるのではないだろうか? それを防ぐための仕組みがuser_active&user_leaveである。 これはつまりユーザの状態に合わせてtableを作り、状態が変わればtableを移行させてやることで対応することができる。 SQLやシステム的にも有効なアカウントを見るときはuser_activeだけを見れば良い。 必要な情報は多くはuser_activeとuser_detailをJOINすることで実現するだろう。 JOINのコストはお互いに主キーなので多くのケースは1対1だ。 ここが今回の本質である。

追記(2018/05/01 20:57)

まさにそのとおりだが説明してなかったので追記する。 多くのケースはusersにレコード作った場合に、その後はuser_activeに登録される。 ただしユーザ登録にメール認証などがある場合は最初はuser_tmpにレコードが作られ、認証が終わったらuser_activeにレコードを移動させる。 このようにactive、leave以外の状態が新たに増えた場合は user_状態名 のテーブルを新たに追加する。

この設計の問題

この設計は状態毎にtableができるが親tableのusersは基本的に減ることが無い。 そのためusersはどんどん肥大化していく。 そしてusersをいざ消したいというときに関連する子tableが多いので削除の手順が煩雑になりがちでusersにdelete_flagカラムを作るという本末転倒な状況が稀によくある。 これの対策としては外部キー制約のCASCADEを利用することだが多くの場合はlog系のtableを削除する時に処理時間がかかりシステムを圧迫する。 1段階踏み込んだ対策は1対1のtableのみCASCADEし、log系のtableはRESTRICTにしておき、先に消すか SET NULL を設定する方法だ。 どちらも銀の弾丸とは言い難く、logは別途残したい場合は同じテーブル構成のoldスキーマなどを作ってそちらに移すなど運用になってしまう。 usersを消したいという要望は多くはサービスが大きくなっており、シャーディングを検討するフェーズだったりするので更に問題が大きくなる。 この場合の答えはケースバイケースとしか言いようがないでここでは割愛するがこのような問題はあるため、類似のケースで例えばissueやBlogのような場合で親tablegが肥大化する場合は最初に検討したほうがいい。

その他

今回は取り扱っていないがありそうなケースを簡単に補足する

user_detailの更新履歴を取りたい

user_detail_old_logという名のtableを作ってそちらに前回の情報をINSERTしてからuser_detailを更新する。 この場合はuser_detailにもidをつけてuser_detail_old_logは子tableにしたほうが扱いやすいケースが多い

usersのカウントが重い

SELECT count(*) が重い案件。 トリガーでサマリーテーブルを作る。

yoku0825.blogspot.jp

activeやleaveなどの状態別も同様である。 トリガーを使うかどうかには諸説ある。

ざっとだが書いた。 読者の皆様のご意見を待っている。

ありがたいご意見をもらった(2018/05/01 21:33)

概ね指摘は次のようなもの。

状態が排他に制御できない

状態の変更履歴が持てない

パフォーマンスの問題

詳細は各Tweetのリプ履歴とか見るとめちゃめちゃ参考になる。 フィードバックは尊い(圧倒的感謝

追記(2018/05/02 12:44)

退会した人のデータは物理削除してほしい

説明を割愛してたけど削除はすべき。

背景

昼間

CDN(CloudFront)がGooglebotを認識してくれない場合はCDNにrobots.txtを置くと解決する

Google Search Consoleでモバイル表示についてerrorを吐いてる場合、モバイルフレンドリーテストで実際のGoogle botがどのようにサイトを認識しているか見ることが出来る。

でそーだいさんは今、オミカレってサイトのCTOをしてるのだけど直近でERRORが出てて、調べるとCSSを読み込めてないことがわかった。 自分で下記のサイトにアクセスすると普通に見えるし、開発者ツールで見ても特に問題がなくて四苦八苦。 で上部のツイートになってる。

party-calendar.net

それでもう少し調べるとCDNのコンテンツ全てをGoogle botが認識できてなかった。 ということで表題のとおり、結論としてrobots.txtを置いてアクセスできるようにしてやると解決した。 CDNがなぜGoogle botを弾いているかわかったかと言うと、CDNドメイン自体もGoogle Search Consoleに登録したところ、robots.txtを取得できてないerrorが出てたことから判明した。

調べても何も情報が出てこなかったので当たり前の事かもしれないけど他に困った人が居た時のためにメモとして残す。

Github projectsが実際に使えるレベルになっていたのでみんな使っていいと思う

GithubのカンバンツールであるGithub Projectsはリリースされて1年以上経っている(2018/04/10現在) 僕が当時、使えるかなって思って試した感想は下記の人とほとんど同じような感想だった。

qiita.com

以下、引用。

projectページ内でissueを作成することができないことも率直に不便を感じた :thought_balloon: issueをcloseしたり、PRがmergeされたら自動でclosedのカラムへ移動してほしい。 「自分の担当issueのみ進捗管理したい」などのニーズは容易に想定できるので、projects内のフィルタリング機能がほしい

上記に対して改善しているポイントを述べていく。

Projectsの中で作ったカードをissueに登録できる

該当のカラムの中でカードを作ることが出来る。 これはissueとは別の独立した存在でissueには登録されない。 そのため技術的な課題とは別の課題、例えば経費精算みたいなタスクでもProjectsの中で一緒に管理できる。 しかしそれとは別にカンバンを見ている中で技術的な課題に気付き、issueを作りたい事は多々ある。 その場合に画面遷移をすることなく、作ったカードをissueにすることが出来る。

issueをcloseしたり、PRがmergeされたら自動でclosedのカラムへ移動する

Githubのヘルプにもある通り、issueとプルリクのイベントに合わせて自動的にカラムを移動させることが出来る。

Configuring automation for project boards - User Documentation

Projectsを作る時、既にイベントと組み合わせられたtemplateも用意されている。 templateを使ってProjectsを作るとオーソドックスな以下のカラムができる。

  • To do
  • In progress
  • Done

もちろん、issueを作った時にProjectsを指定すればそのカードはTo doに入る。
issueをreopenしたり、プルリクと紐付けるとIn progressに入る。
issueをcloseしたり、プルリクをMargeするとDoneに入る。

Simpleなタスクの可視化としては十分なのではないだろうか。 もっと細かく自動的に移動させたいって用途には対応してない。 個人的には細かい状態はラベルを適切に貼る方がマッチするのではないかと思っている。 とりあえずこれでTrelloなどを使う理由がベンダーロックイン以外には無くなった。

projects内のフィルタリング機能が出来る

真ん中の検索窓から自分にアサインされたissueや状態に合わせて検索が出来る。 これはissueの検索のときと同じように出来るので違和感なく使えるし、やりたいことは全てできた。 ラベルでもフィルタリング出来るのでラベルを柔軟に貼るほうが使いやすくなるなと言うのはここを触った所感でもある。

f:id:Soudai:20180410101306p:plain

まとめ

バーンチャートとかは無いけどマイルストーンを設定できるし(設定するのがだるくて使ってないけど)思ったより、Githubで完結出来る感触を得た。 例えば受託開発とかならGithubのProject単位で作れるし、チーム開発ならOrganizationに属した複数のリポジトリをまとめる統括的なProjectsも作れる。 普通に使えそうだなって印象を受けたのだけど検索すると日本語情報少ないので使われてないのか既に一般的なのかわからないので記事にした。 今から選ぶ選択肢として十分入ると思う。 ということで前職ではZenhubを使っていて、個人ではTrelloを使っていたのだけど今回はGithub Projectsを使ってみる。

株式会社はてなに入社しました

待たせたな。

帰ってきたやで。

株式会社はてなに入社しました - hitode909の日記

ホント、はてな良い会社よ。