主夫ときどきプログラマ

データベース、Webエンジニアリング、コミュニティ、etc

Mysqlでログ系テーブルを運用するときやっておきたいこと

SNSソーシャルゲーム、アドネットワークなどのシステムではいろいろなログ情報をDBに保存することもあると思います。
そのさい、日々増えつづけるデータやパフォーマンスをどの様にさばいていくかが重要になってきます。
今回はログ系のデータをMysqlでどのように運用していくか、をテーマにいくつかのノウハウをまとめました。

ログ系テーブルの特徴

ログ系のデータとは、つまり何かのアクションの履歴データのことです。
一般的にはこのような形になるかと思います。

CREATE TABLE `t_logs` (
  `id` bigint(20) unsigned NOT NULL,
  `user_id` int(10) unsigned NOT NULL DEFAULT '0',
  `event_id` int(10) unsigned NOT NULL DEFAULT '0',
  `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8


基本的にはどんどんInsertが発生していきます。そして要求に応じてそのデータを参照します。
時系列でデータは増えていきますから、運用が長くなればそれに比例してログの量も増えていきます。


1レコード単位で参照が必要なのであれば過去の参照に期限を設けるとか、
集約したデータが参照したいのであれば集計結果を別テーブルに保存しておくなど
どこかでデータを切り捨てる事を考えておく必要があります。

データサイズに注意する

上記のようにどこかでデータ捨てるということを決めたとしましょう。
しかしInsertされたログデータはどこかで削除しないとずっとテーブルに残り続けます。
アクセスログ系のデータの場合人気のソーシャルゲームやアドネットワークだと1日に数千万〜のPVが発生するので
あっという間に億単位のデータサイズになってしまいます。こうなると物理的な容量が問題になってきます。


こういった場合よく用いる方法が、深夜のバッチプログラムなどでDeleteする、ということなのですがこの規模になってくると、
Deleteは非常に重くなります。また、その間ログテーブルもロックされてしまいますのでこちらも注意が必要です。
Limitを使い、100件、1000件ずつ削除する方法もありますが、いずれInsertの方がスピードがうわまってきます。

innodb_file_per_tableでファイル分割

InnoDBでテーブルを運用する場合、そのデータはibdata1というバイナリファイルに蓄積されていき、
Mysqlで使われるすべてのInnoDBで共有されています。
このibdata1ファイルはデータが蓄積されるごとに自動拡張を続け、ひとたび成長してしまったデータファイルを小さくする方法はありません。
そのため深夜バッチなどでDeleteしてもディスクサイズはへりません。


innodb_file_per_tableオプションを設定すると、テーブルごとにデータファイルが作成されるようになります。
my.cnfファイルに設定します。

[mysqld]
innodb_data_file_path=ibdata1:1G
innodb_file_per_table


作成されるデータファイルは各スキーマのフォルダに格納され .ibd という拡張子になります。
こちらのファイルも要求に応じて自動拡張されますが、テーブルを削除するとファイルも削除されます。
そのためテーブルが大きくなってからでもディスクの空き容量を増やすことが可能になります。

パーティションを使う

Mysql5.1以降で実装された機能で、1つのテーブルを「ある規則」に基づいて分割し別テーブルのようにデータを格納させる仕組みです。
「ある規則」は4つに分類されますが、ログのデータ削除という意味でよく用いられるのが「RANGEパーティション」です。
前述のinnodb_file_per_tableオプションのもとで行うと、パーティション毎にもデータファイルが分割されます。
テーブルは以下のようになります。

CREATE TABLE `t_logs` (
  `id` bigint(20) unsigned NOT NULL,
  `user_id` int(10) unsigned NOT NULL DEFAULT '0',
  `event_id` int(10) unsigned NOT NULL DEFAULT '0',
  `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`id`,`created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
PARTITION BY RANGE (to_days(created))
(PARTITION p20120708 VALUES LESS THAN (to_days('2012-07-09')),
 PARTITION p20120709 VALUES LESS THAN (to_days('2012-07-10')),
 PARTITION p20120710 VALUES LESS THAN (to_days('2012-07-11')),
 PARTITION p20120711 VALUES LESS THAN (to_days('2012-07-12')),
 PARTITION p20120712 VALUES LESS THAN (to_days('2012-07-13')),
 PARTITION p20120713 VALUES LESS THAN (to_days('2012-07-14')),
 PARTITION pmax VALUES LESS THAN MAXVALUE)

実際のファイルはこのようになります。

-rw-rw---- 1 mysql mysql 98304  7月 10 11:36 2012 t_logs#P#p20120708.ibd
-rw-rw---- 1 mysql mysql 98304  7月 10 11:36 2012 t_logs#P#p20120709.ibd
-rw-rw---- 1 mysql mysql 98304  7月 10 11:36 2012 t_logs#P#p20120710.ibd
-rw-rw---- 1 mysql mysql 98304  7月 10 11:36 2012 t_logs#P#p20120711.ibd
-rw-rw---- 1 mysql mysql 98304  7月 10 11:36 2012 t_logs#P#p20120712.ibd
-rw-rw---- 1 mysql mysql 98304  7月 10 11:36 2012 t_logs#P#p20120713.ibd
-rw-rw---- 1 mysql mysql 98304  7月 10 11:36 2012 t_logs#P#pmax.ibd
-rw-rw---- 1 mysql mysql  8666  7月 10 11:35 2012 t_logs.frm
-rw-rw---- 1 mysql mysql    92  7月 10 11:35 2012 t_logs.par

この場合、pYYYYMMDD という名前のパーティションに、YYYY-MM-DDまでに作られたログデータが格納されます。
ここで一点注意が必要ですが、PARTITION節でしようされるカラムはPRIMARY KEYかそれに含まれている必要があります。
そのため、この例ではPARTITION節で使う ceratede カラムをPRIMARY KEYに含めています。
その他パーティションに関する詳しい内容はこちらを御覧ください。
http://dev.mysql.com/doc/refman/5.1/ja/partitioning-management.html


このようにパーティションを利用していると、例えば2012-07-08のデータはもう要らないから削除しよう、というときに

mysql> DELETE FROM t_logs WHERE created <= '2012-07-08';

としていたものが

mysql> ALTER TABLE t_logs DROP PARTITION p20120708;

このように書けます。前者の場合は巨大なInnoDBであるほど処理に時間がかかり非現実的な方法でしたが、
パーティションを使うことで後者のようにまるでテーブルをDropするかのようにデータを削除できます。


また、パーティションには「刈りこみ」という機能がMysql5.1.6から実装されています。
パーティション刈りこみのコンセプトは「合致する値が存在し得ないパーティションはスキャンしない」といものです。
詳しくはこちらを御覧ください。
http://dev.mysql.com/doc/refman/5.1/ja/partitioning-pruning.html

MEMORYテーブルでINSERTスピードアップ

やはりINSERTの実行速度はSELECTに比べると圧倒的に遅いのですが、
使用するテーブルのストレージエンジンによってもINSERTの実行速度は変化します。
以下の2つのテーブルで簡単な実験をしました。違いはInnoDBかMEMORYか、です。

CREATE TABLE `t_data_inno` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `value` int(11) DEFAULT NULL,
  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE `t_data_mem` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `value` int(11) DEFAULT NULL,
  `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=MEMORY DEFAULT CHARSET=utf8


これらにデータをINSERTするためのPHPは以下のとおりです。
それぞれのテーブルに10000件のINSERTを実行します。

# insert_inno.php
<?php
$link = mysql_connect('127.0.0.1:3306', 'root', '');
mysql_select_db('test', $link);
for ($i = 0; $i < 10000; $i++) {
    mysql_query("INSERT INTO t_data_inno (value) VALUES ($i)", $link);
}
mysql_close($link);
?>

# cat insert_mem.php
<?php
$link = mysql_connect('127.0.0.1:3306', 'root', '');
mysql_select_db('test', $link);
for ($i = 0; $i < 10000; $i++) {
    mysql_query("INSERT INTO t_data_mem (value) VALUES ($i)", $link);
}
mysql_close($link);
?>


これを実行すると以下のようになりました。

$ time  php insert_inno.php
real     0m3.983s
user     0m0.138s
sys     0m0.337s

$ time  php insert_mem.php
real     0m1.392s
user     0m0.130s
sys     0m0.321s

サーバ環境やMysqlのパラメータによって変化するでしょうが、この環境では3倍速いことがわかります。
このMemoryテーブルをうまく使えばINSERTにより処理時間を短縮することができます。
では、どの様に使用するのがいいのか?一つの使用方法を紹介します。

最終的にログを保存するテーブルはInnoDBで作成し、ログを直接書きこむテーブルをMemoryで作成します。
図のようにMemoryテーブルを2つ用意し、プログラムはログをMemoryテーブルの一方に書き込みます。
Switcherにより書きこむMemoryテーブルは切り替えられ、書き込みが起こっていない方のテーブルから
InnoDBにデータをコピーし、次の切り替えに備えます。


もう少し詳しく見ていきましょう。
まずSwitcherですがDBに設定用のテーブルを用意し、その値を参照し書きこむMemoryテーブルを切り替える、というふうにします。
以下のようにテーブルを作成しデータを設定します。

CREATE TABLE `settings` (
  `key` varchar(255) NOT NULL,
  `int_value` int NOT NULL,
  PRIMARY KEY (`key`)
) ENGINE=InnoDB 

mysql> select * From settings;
+----------+-----------+
| key      | int_value |
+----------+-----------+
| switcher |         1 |
+----------+-----------+


ログを書き込むプログラムでは、この値を参照してINSERTするテーブルを切り替えるようにします。
PHPだとこういう感じでしょうか。

<?php
/* Switcher の値を取得 */
$sql = "SELECT int_value FROM settings WHERE key = 'switcher'";
$result = $db->query($sql);
$switcher = $result['int_value'];

/* Switcher によってINSERT先を変更 */
if ($switcher == 1) {
    $sql = 'INSERT INTO t_logs_m1 VALUES(xxx, xxx, xxx)';
}
else if ($switcher == 2) {
    $sql = 'INSERT INTO t_logs_m2 VALUES(xxx, xxx, xxx)';
}
$db->query($sql);
?>


こうしておけばsettingsテーブルの値を変更するだけで、ログの書込み先を制御できます。
次は切り替えとログのコピーを自動化しましょう。
切り替えは settings テーブルのUPDATEで、コピーは t_logs へのINSERTで行います。
これらを一連に処理するためのストアドプロシージャを作成しましょう。

BEGIN
    /** switcher を参照して分岐 **/
    IF (SELECT int_value FROM settings WHERE key = 'switcher') = 1 THEN
        /* 出力先のテーブルを切り替え */
        UPDATE settings SET int_value = 2 WHERE key = 'switcher';
        /* データをコピーしてテーブルを空にする */
        INSERT INTO t_logs (SELECT * FROM t_logs_m1);
        TRUNCATE TABLE t_logs_m1;

    ELSE
        /* 出力先のテーブルを切り替え */
        UPDATE settings SET int_value = 2 WHERE key = 'switcher';
        /* データをコピーしてテーブルを空にする */
        INSERT INTO t_logs (SELECT * FROM t_logs_m2);
        TRUNCATE TABLE t_logs_m2;

    END IF;
END

このプロシージャを5分や10分毎にEventにて設定すれば自動でログの切り替えを行ってくれます。

ログ系テーブルのまとめ

  • データサイズに気をつける
    • ログの保存期間をきめましょう
  • innodb_file_per_table オプションを指定する
    • データファイルは分割しましょう
  • パーティションを使う
    • Mysql5.1以上がよいですね
  • Memoryテーブルを有効活用

最後に

いろいろとテクニックを紹介しましたが、どれもよく知られた方法だと思います。
またシステムの要件や環境によっていろいろ違ってくるのでだれにでも使えるわけではないですが、
だれかの助けになれば幸いですね。

ストアドプロシージャを作成しましょう

masayuki14.hatenablog.com

Slowquery を分析しましょう

masayuki14.hatenablog.com

PHPでmemcachedを使うときのモジュールパフォーマンス比較

PHPにはmemcachedを使うための主要モジュールが2種類あります。
機能的にいくつかの違いがありますが、今回は実行速度について比較してみました。

memcached の実行環境を整える

memcached のインストール

yum でインストールすることができます。今回のOSはFedora14です。

$ sudo yum install memcached
memcached サーバーの起動

インストールが完了すると、他のサービス同様に /etc/rc.d/init.d/memcached に起動スクリプトが設置されるので、

$ sudo /etc/rc.d/init.d/memcached start
$ sudo /sbin/service memcached start

など、各方法で memcached デーモンを起動することができます。一方コマンドからも起動することができます。

$ memcached -p 11211 -m 64m -vv

この状態だとmemcachedが起動しコマンドラインが返ってこないので
デーモン(バックグラウンド)で実行する -d オプションをつけます。

$ memcached -p 11211 -m 64m -d

これで準備完了です。


memcacheモジュールとmemcacedモジュールを比較する

PHPにはemcachedを利用するに当たって、memcacheモジュールとmemcachedモジュールの2種類があります。
これらもyumを使ってインストールできます。

$ sudo yum install php-pecl-memcache
$ sudo yum install php-pecl-memcached

どちらが早いのか検証してみましょう。以下のようなコードを用意しました。
100000件のデータの読み書き速度を比較します。

memcache module
store_mem.php
<?php
$memcache = new Memcache();
$memcache->addServer("localhost", 11211);
$memcache->flush();

for ($i = 0; $i < 100000; $i++) {
    $memcache->set(md5($i), crc32($i), 0, 1800);
}
load_mem.php
<?php
$memcache = new Memcache();
$memcache->addServer("localhost", 11211);

for ($i = 0; $i < 100000; $i++) {
    $memcache->get(md5($i));
}
実行結果
$ time php store_mem.php
real     0m5.773s
user     0m1.187s
sys     0m2.169s

$ time php load_mem.php
real     0m5.489s
user     0m1.259s
sys     0m2.103s
memcached module
store_memd.php
<?php
$memcache = new Memcached();
$memcache->addServer("localhost", 11211);
$memcache->flush();

for ($i = 0; $i < 100000; $i++) {
    $memcache->set(md5($i), crc32($i), 1800);
}
load_memd.php
<?php
$memcache = new Memcached();
$memcache->addServer("localhost", 11211);

for ($i = 0; $i < 100000; $i++) {
    $memcache->get(md5($i));
}
実行結果
$ time php store_memd.php
real     0m5.289s
user     0m1.310s
sys     0m1.497s

$ time php load_memd.php
real     0m4.667s
user     0m0.752s
sys     0m1.809s

memcached モジュールのほうが早いという結果になりました。

保存するデータ型の検証

PHPからmemcacheを使う場合、どのようなデータ型に対応しているのでしょうか。
int, string, array の3種類のデータ型について検証してみました。

<?php
$memcache = new Memcache();
$memcache->addServer('localhost', 11211);
$memcache->flush();

$v1 = 100;
$v2 = 'string';
$v3 = array(1, 2, 3);
$v4 = array('type' => 'reguler', 'color' => 'blue');

$memcache->set(1, $v1);
$memcache->set(2, $v2);
$memcache->set(3, $v3);
$memcache->set(4, $v4);

var_dump($memcache->get(1));
var_dump($memcache->get(2));
var_dump($memcache->get(3));
var_dump($memcache->get(4));
実行結果
int(100)
string(6) "string"
array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}
array(2) {
  ["type"]=>
  string(7) "reguler"
  ["color"]=>
  string(4) "blue"
}

整数、文字列、配列などのデータ型を気にすることなく set/get できました。
内部的にはシリアライズされてmemcachedに保存されます。
ここでは試していませんがオブジェクトの保存にも対応しています。ただし読み出し側でもクラス定義の読み込みが必要です。
これは利用がとても簡単になりますね。


memcachedの状態を知る

memcached-tool コマンドでmemcachedの状態を知ることが出来ます。
オプション(引数)はdisplay stats dump の3つ。Host, Port を指定するときは以下のようにします。
memcached-tool [mode]

$ memcached-tool localhost:11211 display
  #  Item_Size  Max_age   Pages   Count   Full?  Evicted Evict_Time OOM
  2     120B     10827s       1    3874      no        0        0    0
  4     192B    588190s       1       2      no        0        0    0
  5     240B    946076s       2    1069      no        0        0    0
  6     304B    946076s      34   41925      no        0        0    0
  7     384B    946075s       2    2760      no        0        0    0

Full?:空きチャンクの有無
Evicted:期限切れ前に削除した回数

$ memcached-tool stats
#localhost:11211   Field       Value
         accepting_conns           1
               auth_cmds           0
             auth_errors           0
                   bytes    10950856
              bytes_read     6150939
           bytes_written      800696
              cas_badval           0
                cas_hits           0
              cas_misses           0
               cmd_flush           1
                 cmd_get           0
                 cmd_set      100000
             conn_yields           0
   connection_structures          11
        curr_connections          10
              curr_items      100000
               decr_hits           0
             decr_misses           0
             delete_hits           0
           delete_misses           0
               evictions           0
                get_hits           0
              get_misses           0
               incr_hits           0
             incr_misses           0
          limit_maxbytes    67108864
     listen_disabled_num           0
                     pid        5579
            pointer_size          64
               reclaimed           0
           rusage_system    1.773730
             rusage_user    0.308953
                 threads           4
                    time  1327019806
       total_connections          15
             total_items      100000
                  uptime         123
                 version       1.4.5

bytes:現在の使用メモリを表します。期限切れのデータも含まれるためこの値が容量いっぱいでもメモリ不足とは一概には言えません。
evictions:容量(メモリ)不足となり期限切れ前にデータを削除した回数。この回数が多いようだと容量不足となっています。
limit_maxbytes:memcacheの最大容量(バイト)
cmd_get:GETコマンド発行の累計
get_hits:リクエストでキーが見つかった数
get_misses:リクエストでキーが見つからなかった数

$ memcached-tool dump
Dumping memcache contents
  Number of buckets: 1
  Number of items  : 100000
Dumping bucket 2 - 100000 total items
add b3a8c9d7b4dd2c400ce3f7776f1f6cb8 768 1327021567 10
2027185355
add ba7c76b3377564c295f8afdfa298ca38 768 1327021568 10
3177254485
add 00c9157a614a13927382c42cc26dbfd4 768 1327021568 10
3244397292
add 1f376f49e57d4d7787a5b5b4489edd25 768 1327021567 10
1086221540
add 2a1d623c15bbdb68cf45130d7eefd312 768 1327021567 10
4031782350

memcachedのデータが表示されます。

はじめてのMysqlストアドプロシージャ・ストアドファンクション

ストアドプロシージャとストアドファンクションの違い

ストアドプロシージャとストアドファンクションの違いは以下のようになっています。

ストアドプロシージャ
  • 戻り値がない
  • 実行中に使用テーブルにロックがかかる(実行が速い)
ストアドファンクション
  • 戻り値がある
  • 実行中に使用テーブルにロックがかからない(実行が遅い)

ストアドプロシージャとは?

ストアドプロシージャとは「1つの要求で複数SQLを実行出来る機能」と言えます。
そして以下のようなメリット、デメリットを持ちます。

メリット
  • 1つの要求で複数SQLを実行出来るためネットワーク負荷を軽減できる
  • 事前にサーバー側でSQL構文解析機械語変換を行うため、処理時間を短くできる
デメリット
  • データベースごとに記述する構文に規約があり互換性が低い
  • アプリケーションとのインタフェースが変更になるたびにストアドプロシージャも変更する必要があり、メンテナンス性が悪くなる

ストアドプロシージャの基本操作

ではストアドプロシージャの基本的な扱い方を見ていきましょう。

■定義
mysql> CREATE PROCEDURE procedure_name()
■実行
mysql> CALL procedure_name();
mysql> CALL other_schema.procedure_name();

異なるスキーマのストアドプロシージャも実行することができます。
その場合はスキーマ名をストアドプロシージャ名の前につけて呼び出します。

■入力引数があるストアドプロシージャの定義
mysql> DELIMITER //
mysql> CREATE PROCEDURE procedure_sqrt(IN input INT)
    -> BEGIN
    ->     SELECT SQRT(input);
    -> END;
    -> //
mysql> DELIMITER;

ここで DELIMITER と呼ばれる終端文字を変更します。
通常Mysqlでは「;」がDELIMITERとなっており、これをSQLの最後として扱い実行されます。
しかし、ストアドプロシージャを登録・使用するとき必ずしも「;」が文の最後になるとは限りません。
そこで、DELIMITERコマンドを使い終端文字を「//」に変更してストアドプロシージャの登録を行います。

■入力引数があるストアドプロシージャの実行
mysql> CALL procedure_sqrt(2);
+-----------------+
| sqrt(input)     |
+-----------------+
| 1.4142135623731 |
+-----------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)
■出力引数があるストアドプロシージャの定義
mysql> DELIMITER //
mysql> CREATE PROCEDURE procedure_out(OUT output INT)
    ->BEGIN
    ->    SELECT COUNT(*) FROM table INTO output;
    ->END;
    -> //
mysql> DELIMITER;

SELECT ..... INTO ....; のところで取得した値を出力引数 output に代入しています。
この時 SELECT 結果が複数行や複数カラムになると実行時エラーになるので気をつけましょう。

■出力引数があるストアドプロシージャの実行
mysql> CALL procedure_out(@a);
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @a;
+------+
| @a   |
+------+
|    3 |
+------+
1 row in set (0.00 sec)

ストアドプロシージャを実行するときに変数 @a を引数に渡すことで、これに出力が確認されます。
格納された値はそのあとに SELECT することで参照することができます。

■ストアドプロシージャの確認
mysql> SHOW PROCEDURE STATUS;

登録されているストアドプロシージャを一覧表示します。

■ストアドプロシージャの内容確認
myswl> SHOW CREATE PROCEDIRE procedure_name;

ストアドプロシージャの内容を表示します。

■ストアドプロシージャの削除
mysql> DROP PROCEDURE procedure_name;

登録されているストアドプロシージャを削除します。

ストアドプロシージャの書き方

これまでストアドプロシージャの周辺操作を見てきましたが、ここからはストアドプロシージャ内で使えるいろいろな処理方法について見ていきます。

■変数宣言(ストアドプロシージャ内で有効)
#DECLARE 変数名 データ型;
DECLARE created_date DATETIME;

DECLARE文は全てBEGINの直後に書く必要があり、ルーチンの途中に書くことはできません。

■カーソル(結果セットを処理する場合に使用)
# DECLARE カーソル名 CURSOR FOR 処理する結果セットを取得するSQL文;
DECLARE curUsers CURSOR FOR SELECT id, name FROM users ORDER BY id;

SELECTの結果を1行毎に何か処理したい場合はカーソルを使用します。
Java, Ruby, PHPなど各種言語で用意されているIteratorのようなものです。
カーソルのDECLAREは通常変数のDECLAREの後に定義する必要があります。

■ハンドラ定義
DECLARE EXIT HANDLER FOR NOT FOUND SET done = 0;

ここではカーソル呼び出しの際に、データが存在しなくなったら変数doneに1をセットするというハンドラを定義しています。
これでカーソルで指定されたSELECT結果がすべて終了したかどうかを判定する。

■カーソルオープン
OPEN カーソル名;
■結果セットの一行から値を取得して変数に代入
FETCH カーソル名 INTO 変数,変数,...;
■カーソルクローズ
CLOSE カーソル名;
ストアドプロシージャ例
CREATE PROCEDURE action_count (IN _opFlag INT)
BEGIN

    /* 変数宣言 */
    DECLARE done INT;
    DECLARE _pageNo INT;
    DECLARE _userNo INT;
    DECLARE _actCnt INT;

    /* カーソル定義 */
    DECLARE cur CURSOR FOR
        SELECT `fanpage_id`, `owner_id`, COUNT(*) AS `cnt` FROM `t_action`
        WHERE `created` BETWEEN DATE_SUB(CURRENT_DATE, INTERVAL x DAY) AND CURRENT_DATE
        GROUP BY `fanpage_id`, `owner_id` ;

    /* イベント制御規定 */
    DECLARE EXIT HANDLER FOR NOT FOUND SET done = 0;

    /* デイリーかウィークリーか判断しトランケイトを実行 */
    IF x = _opFlag THEN
        TRUNCATE TABLE `t_daily_total`;
    ELSE
        TRUNCATE TABLE `t_weekly_total`;
    END IF;

    SET done = 1;
    OPEN cur;
    WHILE done DO
        /* 結果セットの一行から値を取得して変数に割当 */
        FETCH cur INTO _pageNo, _userNo, _actCnt;
        IF x = 1 THEN
            INSERT INTO `t_daily_total` VALUES (_pageNo, _userNo, _actCnt);
        ELSE
            INSERT INTO `t_weekly_total` VALUES (_pageNo, _userNo, _actCnt);
        END IF;
    END WHILE;
    CLOSE cur;

END;

ストアドファンクショとは?

基本的にはストアドプロシージャと同じですが、いくつかの点で違いがあります。

引数にIN, ONTが不要

ストアドプロシージャの引数の先頭にはIN、OUTなどの識別子の指定があり「この値は入力値です」というのを指示する必要があります。
これはC言語で言うところの参照渡しに近いものです。
ストアドファンクションの場合はRETURNで戻り値を指定するので不要になります。

戻り値の型の指定

CREATE FUNCTIONの引数リストの後に、「RETURNS INT」というのがあり、ここで戻り値の型を指定します。

DETERMINISTIC

これを付けないで定義しようとすると、以下のようなエラーが発生する場合があります。

ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)

入力値が同じ場合に出力値が同じ場合はDETERMINISTIC、そうでない場合はNOT DETERMINISTICを指定しましょう。
ストアドファンクション生成に対するこの規制を緩和するには、グローバルシステム変数を1に設定します。

SET GLOBAL log_bin_trust_function_creators = 1;
ストアドファンクション例
mysql> DELIMITER //
mysql> CREATE FUNCTION test_func(f INT) RETURNS INT DETERMINISTIC
   -> BEGIN
   ->     RETURN (f + 2);
   -> END;
   -> //
Query OK, 0 rows affected (0.00 sec)

mysql> DELIMITER;
mysql> SELECT test_func(1);
+--------------+
| func_test(1) |
+--------------+
|            3 |
+--------------+
1 row in set (0.00 sec)

Mysql slow queryの設定と解析方法

Mysql5.5でスロークエリに関して調べたのでまとめました。
Mysql5.1からスロークエリのパラメータが変わっているので注意しましょう。
そのためMysql5.1より前のバージョンでは内容が異なります。


設定を確認する

Mysqlのコンソールからshow variablesコマンドで、スロークエリの設定を確認できます。

mysql> show variables like 'slow%';
+---------------------+----------------+
| Variable_name       | Value          |
+---------------------+----------------+
| slow_launch_time    | 2              |
| slow_query_log      | OFF            |
| slow_query_log_file | mysql-slow.log |
+---------------------+----------------+

slow_query_log がOFFになっているとスロークエリの出力は行われません。

mysql> show variables like 'long%';
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+------------+
| long_query_time | 10.000000 |
+-----------------+-----------+

slow_query_logがONのとき、long_query_timeを超えるクエリがスロークエリとして出力されます。


スロークエリを出力する(コンソールからの設定)


コンソールから設定を変更する場合はset globalコマンドで変更することができます。

mysql> set global slow_query_log_file = '/tmp/mysql-slow.log';
mysql> set global long_query_time = 5;
mysql> set global slow_query_log = ON;


show vairables コマンドで設定を確認すると、変更されていることが確認できます。
long_query_time の値が変更されていない場合がありますが、その時は一度コンソールを閉じ
最ログインすると変更されていることが確認できます。
変更には相当のユーザー権限が必要です。rootでログイン出来る場合はrootで作業しましょう。


スロークエリを出力する(my.cnfの設定)

my.cnf にパラメータを設定してスロークエリのログを出力します。
root権限で設定ファイルを編集します。

$ sudo vim /etc/my.cnf
[mysqld]
slow_query_log
slow_query_log-file = /var/log/mysql/mysql-slow.sql
long_query_time = 5

slow_query_log = ONを記述することでスロークエリの出力を有効にします。
デフォルトでは無効になっています。
mysql5.0以前ではlog_slow_queriesというオプションなので、記述してある場合はコメントアウトしてください。


log_output=FILEが指定されているとき、slow_query_log_fileオプションで指定されているファイルへログが記録されます。
long_query_timeで何秒以上かかったスロークエリをログへ記録するかを指定します。
Mysql5.1から、1秒未満の秒数(マイクロ秒まで)を小数で指定できるようになりました。


mysqldumpslowを使って集計する

mysqldumpslow コマンドを使ってスロークエリの集計を行うことができます。
-s オプションでどの項目でソートするかを指定することができます。
例では t: query timeを指定しました。その他のオプションは -h でヘルプを確認して下さい。

$  mysqldumpslow -s t /var/log/mysql/mysql-slow.sql

Count: 93  Time=4.40s (409s)  Lock=0.00s (0s)  Rows=5.0 (465), ebisu[ebisu]@2hosts
  show index from `bom`.`logs`

Count: 86  Time=3.76s (323s)  Lock=0.00s (0s)  Rows=0.4 (34), meguro[meguro]@localhost
  SELECT `hash`.`id` FROM `hash` WHERE (`key` = 'S')

スロークエリとなっているSQLが種類別に集計され、実行時間の遅いものから順番にレポートされました。
これをもとに上から順に対策を行っていけば良いというわけです。


これを応用すると、スロークエリだけでなくすべてのクエリの実行状況を集計することができます。
先に説明したパラメータをコンソールから変更してすべてのクエリをログに出力するように設定しましょう。

mysql > set global slow_query_log_file = '/tmp/slow.log';
mysql > set global long_query_time = 0;

このように設定することで、/tmp/slow.logに実行されたすべてのSQLが出力されます。
こうすると相応の負荷がサーバーにかかるので、ディスクやCPUを観察しながら数十秒〜数分間待ってから設定をもとに戻します。
出力されたログファイルを分析しましょう。
今回はソートオプションに c: count を指定します。

$ mysqldumpslow -s  c /tmp/slow.log

Count: 27153  Time=0.00s (1s)  Lock=0.00s (0s)  Rows=0.0 (0), ebisu[ebisu]@localhost
  SELECT `m`.`id`, report.ratio  AS `ratio` FROM `l` AS `l`  LEFT JOIN `report` AS `report` ON l.id = report.l_id AND report.d = 'S' WHERE (l.p_id = 'S') AND (l.kind = 'S') AND (l.status = N) AND (l.deleted = N) AND (l.id not in (N))

Count: 18762  Time=0.00s (1s)  Lock=0.00s (0s)  Rows=0.0 (0), sugamo[sugamo]@localhost
  SELECT `settings`.`value` FROM `settings` WHERE (`key` = 'S')

Count: 4032  Time=0.00s (0s)  Lock=0.00s (0s)  Rows=0.0 (0), 2users@2hosts
  #

Count: 4031  Time=0.00s (0s)  Lock=0.00s (0s)  Rows=0.0 (0), tamachi[tamachi]@localhost
  SELECT `l`.* FROM `l` WHERE (id = N) AND (deleted = N)

Count: 4026  Time=0.00s (0s)  Lock=0.00s (0s)  Rows=0.0 (0), 0users@0hosts
  administrator command: Quit

Count: 4012  Time=0.00s (0s)  Lock=0.00s (0s)  Rows=0.0 (0), mejiro[mejiro]@localhost
  SELECT `filter`.`l_id` FROM `filter` WHERE (c_id = 'S') ORDER BY `l_id` asc

Count: 4012  Time=0.00s (3s)  Lock=0.00s (0s)  Rows=0.1 (563), yoyogi[yoyogi]@localhost
  SELECT `hash`.`id` FROM `hash` WHERE (`key` = 'S')

Count: 4012  Time=0.00s (0s)  Lock=0.00s (0s)  Rows=0.0 (0), osaki[osaki]@localhost
  set names 'S'


この方法をとれば、1回の実行には0.1秒しかかからないけれど毎分1万回実行されるため負荷の大半を占めている、
といったSQLを簡単に特定することができます。


スロークエリをDBに出力する

Mysql5.1からはスロークエリをログファイルではなくDBに保存することができるようになりました。
先にも少し出てきましたが、log_output オプションに TABLE を指定します。
my.cnf の場合は

[mysqld]
log_output = TABLE

コンソールからの場合は

mysql> set global log_output = 'TABLE';

そうするとスロークエリログが mysql.slow_log というテーブルに出力されるようになります。
また、あわせて一般ログも mysql.general_log というテーブルに出用されるのでこちらも意識しておいてください。

これらのテーブルはCSVストレージエンジンを用いて作成され、以下のような構成になります。

mysql> show create table mysql.slow_log \G
*************************** 1. row ***************************
       Table: slow_log
Create Table: CREATE TABLE `slow_log` (
  `start_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `user_host` mediumtext NOT NULL,
  `query_time` time NOT NULL,
  `lock_time` time NOT NULL,
  `rows_sent` int(11) NOT NULL,
  `rows_examined` int(11) NOT NULL,
  `db` varchar(512) NOT NULL,
  `last_insert_id` int(11) NOT NULL,
  `insert_id` int(11) NOT NULL,
  `server_id` int(10) unsigned NOT NULL,
  `sql_text` mediumtext NOT NULL
) ENGINE=CSV DEFAULT CHARSET=utf8 COMMENT='Slow log'
1 row in set (0.00 sec)


テーブルに出力することで、SQLによる集計作業が行えるため柔軟にスロークエリを解析することができます。



参考
http://nippondanji.blogspot.jp/2009/01/mysql-51.html
http://www.kakashi.biz/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0/mysql5-5-%E3%81%A7-slow-query-log-%E3%82%B9%E3%83%AD%E3%83%BC%E3%82%AF%E3%82%A8%E3%83%AA%E3%83%BC%E3%83%AD%E3%82%B0-%E3%82%92%E6%8E%A1%E3%82%8B/
http://d.hatena.ne.jp/sh2/20090414



―知って得するクラウドの動向―というセミナーに参加したけどあんまり得しなかったはなし

近畿情報通信講座① ―知って得するクラウドの動向―
http://telecon.or.jp/240627/seminar/syousai.html
というセミナーに行ってきたので、その内容をピックアップしてまとめた。

総務省クラウド政策/オープンデータ政策の現状と課題

日本のICT(Information and Communication Technology 情報通信技術)について
インフラの整備状況や普及率は世界のトップクラスであるにもかかわらず、
国際競争力の評価は低い。ICTビジネスにおける仕組みが悪く、ビジネスが育たない環境である。

中小企業でのクラウド利用状況が低く、まだまだ認知度も低い。
これからは中小企業こそクラウド活用が望ましい。

総務省では「スマートクラウド戦略」を進めている。

中小企業のクラウド導入7つの壁

  • 規制
  • 費用
  • 効果(可視化されていない)
  • 人材
  • 標準化
  • セキュリティ
  • 知的財産
感想
国のやっていることは規模が大きすぎて、身近に感じることが難しいです。
「フューチャースクール」という原口元大臣の始めた事業事業仕分けで3回廃止が決定したけど、政治判断で現状も続いている、とのこと。
事業仕分けって意味無いじゃん。政治家も分かっててやってんだろうなぁ。

京都におけるクラウドの挑戦

京都クラウド・ビジネス研究会のチームKの活動について発表


京都機械金属中小企業青年連絡会(機青連)との勉強会を経て
中小製造業がつかえるクラウドシステムを開発
「京都クラウドGOZAN」を開発->デモ

感想
これから中小企業向け(IT業界以外への)が増えていくのだろうけど、
オープン化と標準化がうまくできれば成功しそう。
ひとつのビジネスチャンスではあるのだろう。

Amazon Cloud で可能になる新しいITの世界

堀内さん AWSテクニカルエバンジェリスト

JAWS-UG #jawsug
アマゾンのユーザーグループ 九州がさかん(全県ある)


1次元蟻、2次元あり、3次元蟻
新たな道をきりはらくはなし。(by宇宙兄弟


Amazonの3つのビジネス


クラウドコンピューティングとは?
「いつでも、必要なだけ、安価に」
電気インフラのイメージに近い。


AWSにおけるクラウドコンピューティングの定義

  • 初期投資不要
  • 自由自在なスケールアップ、ダウン
  • 定額な従量課金
  • 市場投入時間の短縮
  • インフラ管理からの解放 -> 本来の業務への集中。ビジネスの差別化への集中をはかれる。


AWSリージョンとアベイラビリティゾーン
availability-zone は物理的に離れている。専用線で繋がっている。<事例>
東大空間情報科学センター

  • 数十万規模のデータセット作成に利用
  • データ処理の発生に応じて必要分のEC2インスタンスの作成
  • データ受け渡しにS3

トーカ堂
既存システムを「えびすマート」を使ってコスト削減
http://www.ebisumart.com/
http://www.tokado.jp/


東京リージョンの利用が急拡大中
世界一の初年度成長速度。
IaaSの世界シェア60%
Hadoop利用の世界リーダー
S3に1兆オブジェクトの利用 65万/day


・分散アーキテクチャをカンタンに構築できる
疎結合なプロセスフロー


RDS:DBインストールされている
最適化不要、自動バックアップ、
マルチAZを使って自動レプリケーション、自動フェイルオーバー


S3データ保存の基板
リージョン選択、バケット作成 そこにデータを放り込む
保存データの暗号化されてる。
裏側では自動で3箇所にコピーされているので、耐久性が99.999999999%
1GB/month 10円
Dropbox -> S3 に保存されている。

感想
詳しいことが知りたければユーザーグループのイベントに参加する方が良いでしょう。
あとちゃんと英語のドキュメントを読めるようになりましょう。

KDDIクラウド構想と取り組み

スマートホンのセキュリティ対策に力をいれている。
ビジネスに使えるアプリケーションを提供中。

感想
疲労と集中力低下のためあんまり覚えていません。

総評

自分はおそらくこのセミナーのターゲットとずれていたと思われるのであまり興味を持てる内容ではなかった。
技術情報を得るにはもっとジャンルが絞られたカンファレンスや勉強会などに参加したほうが良い情報を得られるだろう。
Amazonエバンジェリストの方とFacebookでつながれたのがよかった。