セッション管理の周辺知識まとめ

モチベーション

今更だけど,Webにおけるクッキー,セッション,セッション管理の意味を明確に説明できるようにしたい.

背景

Google先生に”クッキー セッション 違い”と尋ねたことがある人は多いのではないだろうか?多分に漏れず僕もその一人である.検索結果からは沢山,クッキーとセッションについて説明しているサイトが引っかかるのはいいのだが,皆説明が異なる.要するにわかりにくいものなんだと思う.これは,英語で”session cookie difference”と検索しても大量にひっかかるので世界共通のようだ.ということで,今回自分なりにWebにおけるクッキー,セッション,セッション管理を理解し,なぜ混乱しやすいのかをまとめたいと思う.

HTTPはステートレスなプトロコル

HTTPはステートレスなプロトコルであり,状態は持たない.クライアントがリクエストを投げてサーバがレスポンスを返す,このキャッチボールを1往復したところで通信は切れる.つまり,一度目の通信でクライアントから某かの情報を受け取ったとしても,二度目の通信は前の通信の状態はは引き継がない全く関係のない通信になる.これが,HTTPの通信の原則である.
一方,前の通信の状態がわからないと不都合なケースも有る.Amazonでショッピングをするのを例に考えてみよう.Amazonを利用するユーザは商品の検索,選択,購入を行う.これらのアクションはサーバから見るとクライアントからの異なるリクエストである.先ほど説明した通りHTTPはステートレスなプロトコルなので,本来は商品を選択する通信が終了した後,商品の購入のリクエストをサーバに投げても選択した商品の情報は保持しておらず,ユーザが商品を購入できないことになってしまう.しかし,現実は,ご存知の通り,選択した商品はそのまま購入できる.これはWebアプリケーションの開発者が複数のHTTP通信をステートフルな通信に見せかけるような実装をしているからである.

クッキーとセッション

HTTPをステートフルな通信に見せかける方法は大きく分けて2種類あり,1つはクッキーを利用する方法,もう一つはセッションを利用する方法である.(ステートレスだとか,ステートフルだとか良く説明に使われるが,平たく言えば,サーバが通信記録のあるクライアントを判別したいケースがWebアプリケーションではしばしば起こる.それを実現するために有るのがクッキーとセッションだ.)それぞれについて説明していこう.

クッキー(Cookie)

A cookie, also known as an HTTP cookie, web cookie, Internet cookie, or browser cookie, is a small piece of data sent from a website and stored in a user’s web browser while the user is browsing that website. Every time the user loads the website, the browser sends the cookie back to the server to notify the website of the user’s previous activity.[1] Cookies were designed to be a reliable mechanism for websites to remember stateful information (such as items in a shopping cart) or to record the >user’s browsing activity (including clicking particular buttons, logging in, or recording which pages were visited by the user as far back as months or years ago).

上に書いたのは,WikipediaのHTTP Cookieの説明だ.この説明文は簡潔にまとまっているのでそのまま引用させてもらった.自分の言葉で簡単に説明すると,クッキーの実態はHTTPヘッダーに格納できるテキストファイルであり,クライアントのブラウザに保存されるのが特徴である.Webにおいてサーバがクライアントに直接情報の書き込みをすることは許されていないが,唯一の例外がこのクッキーでサーバとの通信状態を保持するのにクッキーに格納された情報が利用される.クッキーを用いたステートフルな通信の流れは以下のとおりである..

  1. クライアントはサーバAに対するクッキーをもっていない状態でリクエスト1を投げる.
  2. サーバAはリクエスト1にクッキーが含まれていないことを判別し,通信の状態(ユーザのID,パスワード,サイトで行った動作,訪問回数など)を格納したクッキーをレスポンス1に乗せて返す
  3. クライアントのブラウザがこのクッキーを保持する
  4. サーバAにリクエスト2を投げる時にブラウザがクッキーをHTTPのヘッダーに格納して通信をする
  5. サーバAは受け取ったクッキーからクライアントを判別し関連した以前の通信状態と関連させる

20141205_cookie_only

先ほど,クッキーの一番の特徴はクライアント側に保存することと述べた,ユーザのログインを維持したい場合は,ユーザID,パスワード,通信の状態などをクッキーに保存する必要がある.しかし,機密性の高い情報をクッキーに保存すれば,サーバと通信する度にそれらをHTTPヘッダに設定することになる.また,クッキーはクライアント側で改ざん可能であることも推察できるだろう.従って,クッキーだけでステートフルを作るのはセキュリティの観点から望ましくない.そこで,一般的には次のセッションを使った方法でHTTPをステートフルな通信を作り出す.

セッション

セッションはクライアントとサーバの通信の状態を保持する.セッションを利用して複数のHTTP通信を1つのまとまりとして扱うことを”セッション管理”と呼ぶが,クッキーとの違いはセッションはサーバ側に保存されることにある.サーバ側ではセッションID(あるセッションと紐付いたキー)とセッションを保存しておき,セッションIDのみ何らかの手段でクライアントに渡す.セッションIDをクライアントに渡す手段としてはクッキーを利用する方法や,URLのクエリストリングに渡す方法,hidden属性に渡す方法があるが,セキュリティの観点から一般的にはクッキーを使うことが多い.クライアントはセッションIDをクッキー経由でサーバに渡すことで,以前の通信の状態を保持したセッションを取り出すことができる.

20141205_session_cookie

20141205_session_connection

何が混乱の元なのか?

僕が考えるに,クッキーとセッションが混在しやすい理由はセッション管理にクッキーも使われるからだと思う.セッション管理は概念の話でその実現方法は開発者に委ねられている.多くはサーバ側のセッションを引き出すためにクッキーにセッションIDを保存する方法が採られるが,この場合のクッキーは手段で,他の手段を利用しても構わない.僕が最初にセッション管理を勉強した時に混乱したのは,”セッションを使うか,クッキーを使うかじゃないの?”という状態になってしまったからで,クッキーのみを使う方法とセッション管理の手段としてクッキーを使う区別がついていなかったのだ.

なので,クッキーとセッションの違いを説明するときは基本的には,セッションはサーバ側に,クッキーはクライアント側に保存するという理解でOKだと思う.

余談だが,クッキーはきちんと仕様がRFC2965として定義されている.(もともと,NetScape社がセッション管理のために独自仕様のCookieを生み出したが,現在は標準化されたものがHTTPで利用されている.)

まとめ

  • クッキーとセッションはHTTPの通信をステートフルな通信に見せるためにある.
  • クッキーはクライアント側に,セッションはサーバ側にデータを保存する.
  • クッキーのみを使ったステートフルな通信の確立はセキュリティ的に問題があるので利用しない.
  • セッション管理ではクライアント側のクッキーにセッションIDを保存し,それ以外の情報はサーバ側のセッションに保存する
  • クッキーとセッションがわかりにくいのは,セッション管理でセッションIDをクライアントに渡す手段としてクッキーを使うため

参考情報

Git入門:Git初学習者のための効率的な学習方法を考えてみた

本記事は,Git Advent Calendar 2014の13日目に投稿させて頂いた記事です.

モチベーション

自分を成長させながらいかに効率的に技術を伝承するかが自分の中で課題になっており模索中なこの頃.試しに,社内でGitを使ったことのないエンジニアに1週間(合計7時間)で開発に必要なGitの知識を講義したので,その時に使用した教材や効率的な学習方法を初心者向けに共有する.

背景

一昔前はイケてるエンジニアはGitを使ってプログラムを管理してるみたいな感じだったが,今となってはGitはエンジニアにとって必要不可欠なツールになった.Gitがあるからコードの2重管理はなくなり,Gitがあるから継続的インテグレーションや継続的デリバリーが活きる,Gitがあるから変更に対してコメントを残せる.Gitが無いと開発が成り立たなくなって来ているのだ.特に,Githubのヒット以降,その流れは加速している.一方で,Gitを当たり前のように使いこなしている人がいる中で,そもそも使ったことがなかったり,SVNを使っていたためGitに乗り換えあぐねている技術者もいるだろう.僕の周りもそういう人が多く,チームの半分以上がGit未経験者だった.幸い彼らは学習意欲が高く,一週間お昼の時間に使い方を教えただけでGitの概念を理解し開発に足る知識を身に付けた.少しの時間を割くだけでチームの開発力は飛躍的に向上したと思う.せっかくなので,その時に考えた負担をかけずに効率的に勉強できる方法とそれをサポートする教材をGit初学習者に届けたいと思う.

効率的なGitの学習ステップ

習うより慣れろ!開発の現場に叩き込んでGitを使わせたほうが良い.エンジニアたるもの教わるのではなく,自分で学べという人もいるだろう.僕もその意見には概ね同意だが,経験や教え方次第でその後の勉強効率やチームの生産性がガラッと変わるものに関しては経験者が教える価値はあると考えている.Gitはまさにその類のツールで少しのポイントと少しのコマンドを覚えるだけですぐに使えるようになる(簡単だから世の中で普及しているのだ).今回お届けする学習方法は以下の通りだ.

  • ステップ1:Gitの概念がわかる資料を読む
  • ステップ2:Gitの簡単な使い方を説明した資料を読む
  • ステップ3:Gitの操作パターンを学べる問題集を解く
  • ステップ4:Gitを使った実践的な開発をする

何の変哲もない当たり前のことを書かせてもらった.当たり前だからかどのように勉強するべきかを言及している書籍やサイトは少ない.それらの多くはコマンドの使い方や概念を説明するのに徹しており,網羅性は高い.しかし,初学習者にそこまで教える必要があるのか?というくらい基礎から応用まで膨大な情報が書かれている.また,まとめサイトなどが散見されるがそもそも初学習者はまとめサイトに挙げられているどのサイトを読めばいいのかがわからない.途中で躓かないように,それぞれのステップで必要な情報をだけ与えるのが効率の良い学習方法だと思う.

じゃあ,何を使って勉強すればいいのか?

勉強方法は人それぞれ好みがあるので好みから外れてしまった場合は申し訳ないが,Git初心者に対してどのような書籍やドキュメントを読めばよいか各ステップでピックアップさせて頂いた.勉強教材を探す時間も惜しいので,初学習者は何も考えずに以下で推薦する資料を使って勉強して欲しい.(サイトや書籍内にあるリンクも無視して黙々とピックアップした資料を読むのがオススメ.また,提示した順番に読んで頂いた方が学習効果は高い.)

学習に必要な時間:平均15時間

本記事で提案する学習ステップをこなすに必要な学習時間は,10時間〜20時間だと見積もっている.バラつきがあるのは理解力の違いとステップ3.5をこなすかを加味した結果だ.これを少ないと見るか多いと見るかは個々人で判断して頂きたい.

ステップ1:Gitの概念がわかる資料を読む(所要時間:1hour)

はじめのステップはまずGitがなぜ流行っているのか,どれだけ便利なのか,その破壊力を感じ取れる資料を読むのが良い.そこで便利そうだと感じていただければ以降の学習も苦にせず進めるだろう.逆に良くわからなくても,めげずに次のステップに進んでもらいたい.そんなこんなで以下の2つを推薦させてもらう.

ステップ2:Gitの簡単な使い方を説明した資料を読む(所要時間:8hour)

ステップ1でなんとなくGitの雰囲気を掴んでもらえたと思う.ステップ2ではいきなり手を動かしながらGitの使い方を勉強して頂きたい.ここで,GitをサポートするGUIツールは沢山あるが,はじめは概念やコマンドを理解する意味でCUIでGitを学習するという前提で紹介させて頂いた.利用するコンテンツは以下の通りだ.

1つ目に挙げたGitを初めからていねいにはshinpei maruyamaさんが作られた物に僕が少しアレンジを加えたものになっている.Gitの全体の話から,実際手を動かしてGitの使い方を学べる素晴らしい教材だ.Git初心者に紹介すると好評だったので.初学習者にはとりあえずこのドキュメントを2周して欲しい.1周目でGitがどんなツールなのかを体験できるだろう.2周目は自分で空でコマンドを打てるようになってほしい.そして,覚えていないコマンドはメモって欲しい(全部メモっても使うコマンドが少ないことに気づくだろう).また,教材の5章からHEADという単語が出てくるが,Gitを使いこなす上で非常に重要な概念なので是非覚え頂きたい.

Gitを初めからていねいにが終わったら,次はサルでもわかるGit入門を読んで欲しい.この教材は少しGitを勉強した人の復習や,より深い理解の補強には向いているが全くGitを知らない初学習者にはおすすめしない.僕はサル以下だったので学生時代これを読んでもあまり使えるようにはなれなかった,だから,まずはGitを初めからていねいにを通読した上で読んで欲しい.きっと,次のステップに進む助けになるだろう.また,このサイトはePub形式の電子書籍も無料で公開しているのでそちらをダウンロードして読めるので嬉しい.

ステップ3:Gitの操作パターンを学べる問題集を解く(所要時間:2hour)

ステップ2でGitの簡単な使い方を学んだ後は,ステップ3では開発で使うであろうGitの利用パターンを問題集を使って繰り返し解いてもらう.無料で使える教材を探したが有名なものは見つけられなかった.強いて言えば,Learning Git Branchingというサイトがブランチの使い方の学習に役立ちそうだ.しかし,繰り返し解くには使いづらいし,最低限のことを覚えてもらうのに焦点を置きたいので,問題集は僕が用意した.特に4章は,初学習者が苦手とするであろうブランチの様々な使い方を学べるように構成したので繰り返し解いて頂きたい.

Git quizを見ていただくと問題が少ないのに気づくだろう.開発に入る前に必要な最低限の作法はそれだけ少ない.応用的な使い方や~ flowのような開発方針はチームで異なるのでその辺りはチームでメンバーに教えてもらうと良い.ステップ2でも述べたが,Gitを使いこなす上でHEADの概念を理解するのは非常に重要なので,それぞれの問題であるべきHEADの位置を図示させて頂いた.まだ,基本的な使い方をマスターする問題しかないけど,好評だったら応用編も作ろうと検討中.

ステップ3.5: リモートレポジトリの管理にGithubを使いたい場合(所要時間:3hour)

リモートレポジトリの管理はGithubやBitbucketなどのレポジトリホスティングサービスをを利用するケースががほとんである.もし,Githubの使い方を学びたいなら以下の本をおすすめする.

この本は実践的なGithubの使い方がまとめられている書籍で非常に評価も高い.色々書籍を読んで見たが,もし,Githubの使い方を学びたいのであればこの本が一番だと思う.

ステップ4:Gitを使った実践的な開発をする(所要時間:0 ~ ∞)

ここまできたら後は実際の開発に入ってGitを使いまくろう.躓くポイントは人それぞれ違うので教材は必要ない.わからないことが出てきたらGitを使っている人やGoogle先生に聞いて都度解決していけばいい.大事なのはGitを使いまくることだ.コードを書くにも,議事録を取るにも,日々のToDoリストを管理するのにも,機会があればGitを使おう.Gitの学習をしているとブランチのマージとrebaseの違いがわかりにくいという人が多い.その場合は,こわくないgitを一読して頂きたい.mergeとrebaseの違いがわかりやすくまとめられている.

おわりに

本記事はQiitaのGit Advent Calendar 2014 13日目に投稿させて頂いた記事です.社内のエンジニアに短期間にいかに負担なくGitを教えるか考えた勉強方法のアウトプットとしてまとめさせて頂きました.もっと良い勉強方法がある,知ってるという人がいればぜひ初学習者にそれらを共有していただきたいです.それでは,楽しいGitライフを送って下さい!!

セッション管理の周辺知識まとめ

モチベーション

今更だけど,Webにおけるクッキー,セッション,セッション管理の意味を明確に説明できるようにしたい.

背景

Google先生に”クッキー セッション 違い”と尋ねたことがある人は多いのではないだろうか?多分に漏れず僕もその一人である.検索結果からは沢山,クッキーとセッションについて説明しているサイトが引っかかるのはいいのだが,皆説明が異なる.要するにわかりにくいものなんだと思う.これは,英語で”session cookie difference”と検索しても大量にひっかかるので世界共通のようだ.ということで,今回自分なりにWebにおけるクッキー,セッション,セッション管理を理解し,なぜ混乱しやすいのかをまとめたいと思う.

HTTPはステートレスなプトロコル

HTTPはステートレスなプロトコルであり,状態は持たない.クライアントがリクエストを投げてサーバがレスポンスを返す,このキャッチボールを1往復したところで通信は切れる.つまり,一度目の通信でクライアントから某かの情報を受け取ったとしても,二度目の通信は前の通信の状態はは引き継がない全く関係のない通信になる.これが,HTTPの通信の原則である.
一方,前の通信の状態がわからないと不都合なケースも有る.Amazonでショッピングをするのを例に考えてみよう.Amazonを利用するユーザは商品の検索,選択,購入を行う.これらのアクションはサーバから見るとクライアントからの異なるリクエストである.先ほど説明した通りHTTPはステートレスなプロトコルなので,本来は商品を選択する通信が終了した後,商品の購入のリクエストをサーバに投げても選択した商品の情報は保持しておらず,ユーザが商品を購入できないことになってしまう.しかし,現実は,ご存知の通り,選択した商品はそのまま購入できる.これはWebアプリケーションの開発者が複数のHTTP通信をステートフルな通信に見せかけるような実装をしているからである.

クッキーとセッション

HTTPをステートフルな通信に見せかける方法は大きく分けて2種類あり,1つはクッキーを利用する方法,もう一つはセッションを利用する方法である.(ステートレスだとか,ステートフルだとか良く説明に使われるが,平たく言えば,サーバが通信記録のあるクライアントを判別したいケースがWebアプリケーションではしばしば起こる.それを実現するために有るのがクッキーとセッションだ.)それぞれについて説明していこう.

クッキー(Cookie)

A cookie, also known as an HTTP cookie, web cookie, Internet cookie, or browser cookie, is a small piece of data sent from a website and stored in a user’s web browser while the user is browsing that website. Every time the user loads the website, the browser sends the cookie back to the server to notify the website of the user’s previous activity.[1] Cookies were designed to be a reliable mechanism for websites to remember stateful information (such as items in a shopping cart) or to record the >user’s browsing activity (including clicking particular buttons, logging in, or recording which pages were visited by the user as far back as months or years ago).

上に書いたのは,WikipediaのHTTP Cookieの説明だ.この説明文は簡潔にまとまっているのでそのまま引用させてもらった.自分の言葉で簡単に説明すると,クッキーの実態はHTTPヘッダーに格納できるテキストファイルであり,クライアントのブラウザに保存されるのが特徴である.Webにおいてサーバがクライアントに直接情報の書き込みをすることは許されていないが,唯一の例外がこのクッキーでサーバとの通信状態を保持するのにクッキーに格納された情報が利用される.クッキーを用いたステートフルな通信の流れは以下のとおりである..

  1. クライアントはサーバAに対するクッキーをもっていない状態でリクエスト1を投げる.
  2. サーバAはリクエスト1にクッキーが含まれていないことを判別し,通信の状態(ユーザのID,パスワード,サイトで行った動作,訪問回数など)を格納したクッキーをレスポンス1に乗せて返す
  3. クライアントのブラウザがこのクッキーを保持する
  4. サーバAにリクエスト2を投げる時にブラウザがクッキーをHTTPのヘッダーに格納して通信をする
  5. サーバAは受け取ったクッキーからクライアントを判別し関連した以前の通信状態と関連させる

20141205_cookie_only

先ほど,クッキーの一番の特徴はクライアント側に保存することと述べた,ユーザのログインを維持したい場合は,ユーザID,パスワード,通信の状態などをクッキーに保存する必要がある.しかし,機密性の高い情報をクッキーに保存すれば,サーバと通信する度にそれらをHTTPヘッダに設定することになる.また,クッキーはクライアント側で改ざん可能であることも推察できるだろう.従って,クッキーだけでステートフルを作るのはセキュリティの観点から望ましくない.そこで,一般的には次のセッションを使った方法でHTTPをステートフルな通信を作り出す.

セッション

セッションはクライアントとサーバの通信の状態を保持する.セッションを利用して複数のHTTP通信を1つのまとまりとして扱うことを”セッション管理”と呼ぶが,クッキーとの違いはセッションはサーバ側に保存されることにある.サーバ側ではセッションID(あるセッションと紐付いたキー)とセッションを保存しておき,セッションIDのみ何らかの手段でクライアントに渡す.セッションIDをクライアントに渡す手段としてはクッキーを利用する方法や,URLのクエリストリングに渡す方法,hidden属性に渡す方法があるが,セキュリティの観点から一般的にはクッキーを使うことが多い.クライアントはセッションIDをクッキー経由でサーバに渡すことで,以前の通信の状態を保持したセッションを取り出すことができる.

20141205_session_cookie

20141205_session_connection

何が混乱の元なのか?

僕が考えるに,クッキーとセッションが混在しやすい理由はセッション管理にクッキーも使われるからだと思う.セッション管理は概念の話でその実現方法は開発者に委ねられている.多くはサーバ側のセッションを引き出すためにクッキーにセッションIDを保存する方法が採られるが,この場合のクッキーは手段で,他の手段を利用しても構わない.僕が最初にセッション管理を勉強した時に混乱したのは,”セッションを使うか,クッキーを使うかじゃないの?”という状態になってしまったからで,クッキーのみを使う方法とセッション管理の手段としてクッキーを使う区別がついていなかったのだ.

なので,クッキーとセッションの違いを説明するときは基本的には,セッションはサーバ側に,クッキーはクライアント側に保存するという理解でOKだと思う.

余談だが,クッキーはきちんと仕様がRFC2965として定義されている.(もともと,NetScape社がセッション管理のために独自仕様のCookieを生み出したが,現在は標準化されたものがHTTPで利用されている.)

まとめ

  • クッキーとセッションはHTTPの通信をステートフルな通信に見せるためにある.
  • クッキーはクライアント側に,セッションはサーバ側にデータを保存する.
  • クッキーのみを使ったステートフルな通信の確立はセキュリティ的に問題があるので利用しない.
  • セッション管理ではクライアント側のクッキーにセッションIDを保存し,それ以外の情報はサーバ側のセッションに保存する
  • クッキーとセッションがわかりにくいのは,セッション管理でセッションIDをクライアントに渡す手段としてクッキーを使うため

参考情報

セッション,クッキーについて

Rails4 db:migrateでid以外のカラムにプライマリキーの設定を行う

モチベーション

Railsでテーブルのidカラム以外にプライマリキーを設定したい。インターネットに転がっている情報で色々やってみたが上手く行かなったので自分なりに調べてまとめる。

背景

Railsはその設計からidをテーブルのプライマリキーに自動的に設定してくれる。しかし、時にはid以外のカラムにプライマリキーの設定をしたい場合もあるだろう。公式ドキュメントやブログ情報によると、そういう時はオプションを指定することで任意のカラムにプライマリキーの設定が可能とのこと。

当初は下に挙げたドキュメントやブログを参考にテーブルのプライマリキーを設定しようとした。しかし、idが自動でプライマリキーに設定されることはなくなったが、
肝心の任意のキーをプライマリキーにするという要件を満たせなかった。

  1. Ruby on Rails 4.1.8
  2. RailsGuides Active Record Basics
  3. 「ActiveRecord」の基本とデータの参照
  4. Railsで規約に沿わない古いデータを扱う
  5. Ruby on Rails – ActiveRecord で規約外のプライマリキーを使用する方法!

どうやら、公式ドキュメント通りだと思った通りの動作をしないようだ。公式ドキュメントが信頼出来ないとなると、どのように記述すればこちらの希望する処理になるかはフレームワークのコードを読まないとわからない。そうなると、フレームワークの意味が無いのだが・・・ただ、これだけ世で使われているフレームワークなので自分の勘違いの可能性もある。動作検証の結果と追加調査をを以下にまとめる。

検証してみた

環境

  • MacOSX 10.9.4
  • Ruby 2.1.2
  • Ruby on Rails 4.18

作りたいテーブル

MySQLで以下のクエリを実行した時に作成されるテーブルと同様のものをrake db:migrateで作成する。

CREATE TABLE books(
book_id INTEGER NOT NULL,
title VARCHAR(250) NOT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
PRIMARY KEY(book_id)
);

通常の手順で作成したスキーマの確認

まずは、何も考えずに普通の手順で作成したテーブルスキーマを確認する。
手順は以下のとおり。

$ rails new book -d mysql
$ rails g model book book_id:integer title:string
$ rake db:create
$ rake db:migrate

作成されたテーブルのスキーマは以下の通り。

mysql> describe books;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| book_id    | int(11)      | YES  |     | NULL    |                |
| title      | varchar(255) | YES  |     | NULL    |                |
| created_at | datetime     | YES  |     | NULL    |                |
| updated_at | datetime     | YES  |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
5 rows in set (0.01 sec)

これを見ると、自動的にidカラムが作成されており、idカラムのみプライマリキーになっている。

公式ドキュメントの設定を検証する

Ruby on Rails 4.1.8には以下のように記述されている。

The options hash can include the following keys:

:id
Whether to automatically add a primary key column. Defaults to true. Join tables for has_and_belongs_to_many should set it to false.

:primary_key
The name of the primary key, if one is to be added automatically. Defaults to id. If :id is false this option is ignored.
Note that Active Record models will automatically detect their primary key. This can be avoided by using self.primary_key= on the model to define the key explicitly.

最後の2行に、”modelを定義しているファイルでself.primary_keyを設定とすると自動でActiveRecordがプライマリキーを検知する”と書かれているので、
以下のようにしてrake db:migrateを実行する。

class Book < ActiveRecord::Base
        self.primary_key = "book_id"
end

primary_keyオプションは任意のプライマリキーを指定するためにあり、デフォルトだとidがプライマリキーになる。
また、id: falseの設定が入っているとこのオプションは無視されるらしい。なので、idオプションをfalseにしないで、primary_keyの値を変更し、Modelの定義でself.primary_key = :book_idを設定しrake db:migrateを実行した。

➤  rake db:migrate
== 20141128014807 CreateBooks: migrating ======================================
-- create_table(:books, {:primary_key=>"book_id"})
rake aborted!
StandardError: An error has occurred, all later migrations canceled:

you can't redefine the primary key column 'book_id'. To define a custom primary key, pass { id: false } to create_table./Users/takayuki/Dropbox/002_Study/ruby_on_rails_study/test/books3/db/migrate/20141128014807_create_books.rb:4:in `block in change'

エラーがでてきた。なになに、idをfalseにしてください・・・?
idをfalseにしたらprimary_keyオプション無視するんじゃないの?英語の解釈間違えているのだろうか?

マイグレーションファイルを書き換えて、

class CreateBooks  describe books;
+------------+--------------+------+-----+---------+-------+
| Field      | Type         | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| book_id    | int(11)      | YES  |     | NULL    |       |
| title      | varchar(255) | YES  |     | NULL    |       |
| created_at | datetime     | YES  |     | NULL    |       |
| updated_at | datetime     | YES  |     | NULL    |       |
+------------+--------------+------+-----+---------+-------+
4 rows in set (0.01 sec)

プライマリーキーが設定されてない!!!

プライマリキーを設定する方法の調査2

公式ドキュメントの方法では任意のカラムにプライマリキーを設定できなかった。
多くのドキュメントやブログでプライマリキーの設定方法を載せているが、ほとんど上で試した設定方法が紹介されている。

一方、以下の情報サイトでは違う方法でプライマリキーを設定している。

その中から、プライマリキーの設定が正常にできたものを2つ挙げる。

プライマリキーの設定が上手く行った例1

このパターンではexecuteによりSQL文を発行してプライマリーキーの設定を行っている。
試していないが、他のSQLの発行にも使えると思うのでSQL書くのに慣れている人はこの形式が良いのではないだろうか。

Migrationファイル

class CreateBooks < ActiveRecord::Migration
  def change
    create_table :books, id: false do |t|
      t.integer :book_id
      t.string :title

      t.timestamps
    end
    execute "ALTER TABLE books ADD PRIMARY KEY (book_id);"
  end
end

テーブルスキーマの確認

mysql> describe books;
+------------+--------------+------+-----+---------+-------+
| Field      | Type         | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| book_id    | int(11)      | NO   | PRI | 0       |       |
| title      | varchar(255) | YES  |     | NULL    |       |
| created_at | datetime     | YES  |     | NULL    |       |
| updated_at | datetime     | YES  |     | NULL    |       |
+------------+--------------+------+-----+---------+-------+

プライマリキーの設定が上手く行った例2

このパターンではt.columnの後にカラム名とオプションを指定することで、
任意のカラムにプライマリーキーを紐付けている。しかし、直感的にわかりにくいので、個人的にはexecuteの後にプライマリキーを指定する方法を採用したい。

Migrationファイル

class CreateBooks < ActiveRecord::Migration
  def change
    create_table :books, id: false do |t|
      t.column :book_id, 'INTEGER PRIMARY KEY AUTO_INCREMENT'
      t.string :title

      t.timestamps
    end
  end
end

テーブルスキーマの確認

mysql> describe books;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| book_id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| title      | varchar(255) | YES  |     | NULL    |                |
| created_at | datetime     | YES  |     | NULL    |                |
| updated_at | datetime     | YES  |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)

注意すべきなのはデータベースごとにSQLの書き方が異なるので、'INTEGER PRIMARY KEY AUTO_INCREMENT'のように直接指定するときは意識すること。
例えば、オートインクリメントオプションを付けるときは、AUTOINCREMENT(sqlite3)なのかAUTO_INCREMENT(MySQL)なのかを意識すること。

結論

プライマリキーやauto_incrementの設定などはexecute "ALTER TABLE books ADD PRIMARY KEY (book_id);"のようにSQLを実行させる方法を採用した方が間違いがない。
ちょっと後でコードを読んで処理内容を確認しよう。

MySQLのインデックスの役割

モチベーション

MySQLの基本的な使い方はわかっているがインデックスの役割が曖昧だったので今一度整理する。

記事概要

MySQLに詳しい人は”インデックスを設定しろ!検索が早くなるから!”と言う。一方、自分はSQLでDBの簡単な操作はできるけど、インデックスに関しては”ふーん、インデックスを使うと早くなるのね。”程度の理解。しかし、DBの設計をするならインデックスの理解は必要不可欠である。そこで、DB、特にMySQLにおけるインデックスの役割となぜ検索が早くなるのかを計算量の観点から整理する。

MySQLのクエリに対する応答

MySQLのインデックスを理解するには、MySQLのクエリに対する応答をまず知らなくてはならない。

1つ例を挙げてみよう。今、Twitterのようなアプリを作成しており、ユーザ情報を格納するusers、全ユーザのtweet情報を格納するtweetsテーブルを以下のクエリで作成したとする。

CREATE TABLE twitter_modoki.users
  (id integer, 
   name varchar(250),
 );

CREATE TABLE twitter_modoki.tweets
  (tweet_id integer,
   uid integer, 
   content varchar(250),
);

この時、ユーザTaroのtweetを全て取得するには、Taroが持つusers.idをキーとしてtweetsテーブルから該当するレコードを全て抽出すれば良い。tweetsテーブルのuidがTaroのレコードを全て抽出するクエリは以下のようになる。

SELECT content FROM tweets WHERE uid = 'Taroのusers.id'

上記、クエリを実行した時のMySQLの応答は,

  1. tweetsテーブル内の全てのレコードを読み込む
  2. uidフィールドを調査し、文字列’Taroのusers.id’と一致するか比較する

となるが、これだとtweetsテーブルのレコードの数が増加するにつれて抜き出したいレコードの探索時間が増加する。この探索の計算量はtweetsテーブルにあるレコード数をnとした場合、O(n)となる(=線形探索の計算量)。

インデックスを設定した場合の計算量

それでは本題のインデックスの説明に入ろう。

インデックスはもともと、索引や見出しの意味を持つ。”データベース”という単語を国語辞典で引くときに、1ページ目から順に”データベース”という単語を調べる人はいないだろう。”て”の索引がついたページから”データベース”のページを探すのか普通だ。

DBでも同様に探索時間を効率的にするためのインデックスをつけることができる。

Twitterの場合、ユーザ情報をまとめたusersテーブルとユーザのtweetをまとめたtweetsテーブルが有り、ユーザ”Taro”のtweetをtweetテーブルから抽出するとき、普通にクエリを投げると全ユーザのtweetレコードに比例した探索時間が必要になることは既に説明した。

この時、以下のようにtweetテーブルのuidをインデックスとして設定すれば、Taroのusers.idとtweets.uid一致するレコードのみ探索することができる。

CREATE TABLE twitter_modoki.users
  (id integer, 
   name varchar(250),
 );

CREATE TABLE twitter_modoki.tweets
  (tweet_id integer,
   uid integer, 
   conent varchar(250),
   INDEX (uid)
);

MySQLのインデックスはB-Treeと呼ばれるデータ構造で実現されている。そのため、Taroのtweetを抽出するために必要な探索時間はO(log n)になる。

インデックスの有無による計算量の違い

tweetsテーブルのuidにインデックスを設定しない場合の計算量はO(n),インデックスを設定する場合の計算量はO(logn)であることは説明した。前者の計算量はレコードが増えるほど線形に増加するため効率が悪いことがわかる。

試しに具体的な計算量を算出してみよう。レコード数をn=1024,n=1048576とした時のそれぞれの計算量は以下のようになる。

  • tweetsテーブルに登録されているレコード数 n = 1,024の時
    • インデックスなしの計算量はO(1,024)
    • インデックスありの計算量O(10)
  • tweetsテーブルに登録されているレコード数 n = 104,8576の時
    • インデックスなしの計算量はO(104,8576)
    • インデックスありの計算量O(20)

このように、レコード数が増加すればするほどインデックスの有無が計算量に効いてくることがわかる。 なるほど、レコード数が多い場合、確かにインデックスを設定した方が検索が早い。

補足: MySQLのB-Tree

MySQLで利用されているB-Treeの記事も書いてみたので後日別記事でまとめる。

参考情報