MySQLでパーティショニング機能を試す

MySQL 5.5 から強化されたパーティショニング機能を試してみたのですが、パーティションに関する制約が多くて苦戦したので、やり方をメモしておきます。

今回やりたいこと – ログデータの肥大化を防ぐ(ログローテート)

日々溜まる膨大なログテーブルのレコードをパーティショニングして、古くなったログを削除してみます。

レコードの削除は DELETE FROM でも出来ますが、パーティショニングを活用すると高速にレコードを削除できます!内部的には DROP TABLE と似たような動作で高速にレコードを削除しているようです。

まずはパーティションの追加から削除まで、ひと通り試してみます。

パーティション確認用のテーブルを生成

まずはログを保存する logs というテーブルを作成します。

CREATE TABLE `logs` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `log` varchar(255) NOT NULL,
  `created` datetime NOT NULL,
  PRIMARY KEY (`id`,`created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

このとき、パーティショニングのキーになる項目は全てユニークキーの一部になっている必要があります。そうしておかないとパーティションを作れません。

なので今回は id と created カラムを主キーにしました。

created カラムがレコード追加日を格納するカラムになります。
このカラムのデータを使ってパーティショニングしていきます。

まずは初期パーティションを作成

pmax という名前のパーティションを作ります。

ALTER TABLE logs PARTITION BY RANGE COLUMNS (`created`) (
  PARTITION pmax VALUES LESS THAN MAXVALUE
);

パーティション振り分け条件として LESS THAN MAXVALUE を指定していますので、このパーティションに全てのレコードが追加されることになります。これだけだと意味がないですが、何かしら一つパーティションを作っておかないとパーティションの追加が出来ませんので何も考えずに作っておきます。

パーティションを追加

パーティションを再編成する REORGANIZE PARTITION 文を使ってパーティションを追加します。

先ほど作成した pmax パーティションを、 p20131114 と pmax パーティションの二つに分割してパーティションを追加します。

ALTER TABLE logs REORGANIZE PARTITION pmax INTO (
  PARTITION p20131114 VALUES LESS THAN ('2013-11-14') ENGINE = InnoDB,
  PARTITION pmax VALUES LESS THAN MAXVALUE
);

パーティション分割なんかせずに ADD PARTITION でパーティションを追加すればいいじゃんと思うかもしれないですが、既存のパーティションの後ろにしか追加できない仕様になってるので pmax パーティションがある以上は追加できないです。

なので、pmax パーティションを分割する形でパーティションを追加していきます。

試しにログレコードを挿入してパーティションごとに振り分けられるか確認してみる

この状態で試しにログデータを三件追加します。

INSERT INTO `logs` (`id`, `log`, `created`) VALUES(null, 'sample log message', '2013-11-13 00:00:00');
INSERT INTO `logs` (`id`, `log`, `created`) VALUES(null, 'sample log message', '2013-11-14 00:00:00');
INSERT INTO `logs` (`id`, `log`, `created`) VALUES(null, 'sample log message', '2013-11-15 00:00:00');

データを挿入したら各パーティションに何件のデータが格納されているか確認します。確認したいテーブル名が logs の場合 WHERE TABLE_NAME = ‘logs’ とします。

SELECT TABLE_SCHEMA,TABLE_NAME,PARTITION_NAME,PARTITION_ORDINAL_POSITION,TABLE_ROWS
FROM INFORMATION_SCHEMA.PARTITIONS
WHERE TABLE_NAME = 'logs';

上記 SQL の結果です。

TABLE_SCHEMA TABLE_NAME PARTITION_NAME PARTITION_ORDINAL_POSITION TABLE_ROWS
test_partition logs p20131114 1 1
test_partition logs pmax 2 2

p20131114 パーティションに 1レコード(2013-11-13 のレコード)、
pmax パーティションに 2レコード(2013-11-14 と 2013-11-15のレコード)が属していることが分かりました。

パーティション操作をしていて状態が分からなくなったりしたら見てみるといいと思います。

パーティションとそれに属するレコードを削除

ALTER TABLE logs DROP PARTITION p20131114;

これで created が 2013-11-14 のレコードが削除されます。

MySQL パーティショニングのハマりどころ

  • パーティショニングキーのカラムはユニークキーの一部になっている必要がある
  • パーティションに属するレコードしか存在できない(INSERT できない)
  • パーティションを追加する場合、既にあるパーティションの後ろにしか追加できない
  • パーティションが一つもない場合 ADD PARTITION できない

主な制約としてこのような制約があります。知らない苦戦すると思います(しましたw)。なのでパーティショニングを使った運用する場合はテーブル設計の段階からよく考えて行うべきだと思います。

参考ドキュメント

MySQL のドキュメントはここらへんが参考になりました。

【Android】現在のActivityスタックの状態を確認したい

Androidアプリ開発をしていると、現在のActivityスタック(画面の重なり)を確認したくなることありませんか。

Androidは画面を開いていくとスタック方式でどんどん上に画面が重なっていき、バックキーを押すと上から順に画面が破棄されていく仕組みになっています。

実際に複雑な画面遷移を実現してみようと試行錯誤していると今のActivityスタックの状態が分からなくなったりします。そんな時は取りあえずコマンドラインの adbコマンドで現在の Task と Activity スタックの状態をダンプしてみるといいと思います。

USBで端末を繋いで以下のコマンドを叩くとダンプできます。

adb shell dumpsys activity

ただ、このコマンドは出力される情報量が膨大です。そこで、上記コマンドの結果に対して更にgrepをかけて、起動しているタスクとアクティビティの一覧だけ表示されるようフィルタリングすると見やすくなります!コマンド例は以下です。最近多用しているコマンドの一つです:)

adb shell dumpsys activity | grep -B 1 "Run #[0-9]*:"

標準のマップアプリを起動して上記コマンドを実行したときの結果はこんな感じになります。

TaskRecord{42466fe0 #7 A com.google.android.apps.maps U 0}
 Run #6: ActivityRecord{4238bb10 u0 com.google.android.apps.maps/com.google.android.maps.MapsActivity}
TaskRecord{42954380 #8 A com.android.systemui U 0}
 Run #5: ActivityRecord{42953138 u0 com.android.systemui/.recent.RecentsActivity}
TaskRecord{424a50d8 #2 A com.android.launcher U 0}
 Run #4: ActivityRecord{424a32b0 u0 com.android.launcher/com.android.launcher2.Launcher}
TaskRecord{425c3758 #6 A com.google.android.gm U 0}
 Run #3: ActivityRecord{42598ed0 u0 com.google.android.gm/.ConversationListActivityGmail}
TaskRecord{423acbd8 #5 A com.google.android.youtube U 0}
 Run #2: ActivityRecord{42350d90 u0 com.google.android.youtube/.app.honeycomb.phone.HomeActivity}
TaskRecord{4254bc50 #4 A com.android.chrome U 0}
 Run #1: ActivityRecord{423e3720 u0 com.android.chrome/com.google.android.apps.chrome.Main}
TaskRecord{42354ec8 #3 A com.android.settings U 0}
 Run #0: ActivityRecord{423b2af0 u0 com.android.settings/.Settings}

上に表示されている Activity が最近開いた画面です。

アクティビティはタスクに属しますが、上記の出力結果は TaskRecord #7 に対して ActivityRecord #6 MapsActivity(GoogleMap) が所属していることを表しています。

次へ進んでいくため成功体験は無くてはならない

今年に入ってからずっと携わっていたアプリ開発プロジェクトの納品が一つ終わり、無事に区切りを迎えることが出来ました。受託なのでここにアプリ名は書けないですが、イノベーションを感じることが出来るアプリだと思います!

今回の開発では初めて リードエンジニアというポジションでやらせてもらいましたが、至らない点も多々ありプロジェクトのメンバーやディレクターに何度も助けられながらやってきました。ぶつかったこともありました。

苦労も多かっただけに、お客さんから長文のお礼メールをもらった時は本当にまじで嬉しかったです。それを読んでいて感じたことは、次へ進んでいくためには成功体験は無くてはならない ということ。

成功体験がないまま、炎上案件に携わったりし続けていると身も心も疲弊していきます。モチベーションを維持することも難しくなってきます。

極論かもしれませんが実際の成功体験からしか正しいプロセスは学べません。成功体験からくる成長の実感さえあれば、少しくらい苦しいプロジェクトであっても頑張ることができます。

プロジェクト成功のために現場が頑張ることはもちろんのこと、経営陣や営業陣も現場の人間に対して成功体験の機会を与えられるよう出来る限り配慮すべきだと思います。

その一つとして、今の現場のレベルに見合ったプロジェクトを用意することがあると思います。多少困難なプロジェクトを用意するくらいでも構わないと思います。それは良質な成功体験を得るための機会となります。あとは現場の人間がその機会をものに出来るかどうかです。

ただ、あまりに現場のレベルをオーバーしすぎたプロジェクトを用意しても、当然のことながら炎上して現場は疲弊していき、結果として赤字になります。

ドラクエだって最初の町の段階で、強すぎるモンスターが出てきたってどうしようもありません。今のレベルで何とか倒せるモンスターやボスを倒しながら仲間を増やして主人公たちはレベルアップしていきます。

現場のレベルに見合ったモンスター(仕事)を用意し、現場の人間が頑張って成功体験を得ながらレベルアップしていけるといいですね。倒せるモンスターが強くなっていくと獲得できるゴールドも増えていきますし、会社としても前に進むことが出来るはずです!