読者です 読者をやめる 読者になる 読者になる

MySQLv4.0(!)で、DISK容量の空きは十分なのにThe table is fullエラーが出た時の話

あるMyISAMテーブル(ここではtest1とします)を、スキーマ変更のため一時テーブル(test2)へコピーしようとした時、

mysql> create table test2 select * from test1;
ERROR 1114 (HY000): The table 'test2' is full

のようなエラーが発生した。

test1はそこそこ大きなテーブルなので、MySQLがDATA領域(tmp領域を含む)として使うDISKの空きを確認するが、十分なスペースがあった。

調べてみると、
どうやらcreate table A select * from B構文だとMAX_ROWSの値は受け継がれず、
デフォルトの領域しか割与えられない仕組みのよう。

試しに1件だけ挿入すると

mysql> show create table test1\G
*************************** 1. row ***************************
       Table: test1
Create Table: CREATE TABLE `test1` (
  `id` int(11) default NULL
) TYPE=MyISAM MAX_ROWS=100000000
1 row in set (0.00 sec)

mysql> create table test2 select * from test1 limit 1;
Query OK, 1 rows affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> show create table test2\G
*************************** 1. row ***************************
       Table: test2
Create Table: CREATE TABLE `test2` (
  `id` int(11) default NULL
) TYPE=MyISAM
1 row in set (0.00 sec)

test2からはMAX_ROWS=100000000の設定が消えてしまっている。

そこでcreate table A select * from B構文は使わず、

CREATE TABLE `test2` (
  `id` int(11) default NULL
) TYPE=MyISAM MAX_ROWS=100000000;

でテーブルを先に作成した上で、

INSERT INTO test2 SELECT * FROM test1;

とすると、無事コピーできた。

v4.0!

ActiveResourceでauth_token的なトークンで認証する方法

Rails4からは正式にプリインストール対象から外れたActiveResourceではありますが、アクセス先が絵に描いたようなRESTfulAPI、とりわけ開発者が内情を知ってるRailsアプリ間の連携等では今なお大いに力を発揮するもの。

しかし、

http://example.com/posts.json?auth_token=TOOOOKEN

このような形でのトークンの認証には対応していない。
そこで、

blog.mmmcorp.co.jp

を大変参考にさせていただき、モンキーパッチをあて、トークン認証に対応させる。

ActiveResourceが発行するURLのクエリーストリングを生成するメソッド、

def query_string(options)
        
  "?#{options.to_query}" unless options.nil? || options.empty?

end

というのがあるので、これをオーバーライドする。

全体ではこうなる。

class ActiveResource::Base
  class << selfdef query_string(options)

      if auth_token

        options.store(:auth_token, auth_token)

        "?#{options.to_query}" unless options.nil? || options.empty?

      elseraise ArgumentErrorendend
    def auth_tokenreturn @auth_token if defined?(@auth_token)

      nilend
    def auth_token=(auth_token)

      @auth_token = auth_token

    endend

end

下2つが単純な属性"auth_token"に対するゲッター、セッターメソッド。そしてquery_stringメソッドを、発行するURLのクエリーストリングになるハッシュ"options"にauth_tokenの要素を追加し、ない場合はArgumentErrorの例外を発生させるようにした。

このパッチを、Railsではlib配下等、読み込み対象ディレクトリに設置し、

class Post < ActiveResource::Base
  self.site = "http://example.com"
  self.auth_token = "TOOOOOOOOKEN"
  self.format = :xml
  self.timeout = 15
end

Post.all

このようにモデル側を実装すれば、トークンでの認証に対応が可能。

(当パッチはActiveResourceすべての処理において検証はしておりません。
ご利用の際はご注意ください。)

MySQLのv4.0からv5.6へダンプファイルをインポートする際にTIMESTAMPがずれる場合の対処

MySQLをバージョン4.0からバージョン5.6へ移行するため、mysqldumpを使ってダンプファイルをv4.0から取得し、v5.6に投入した際、timestamp型のカラムの時間がズレる事象が発生した。

タイムゾーンの設定はどちらもJSTである。

v4.0

mysql> show variables like '%time%zone%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| timezone      | JST   |
+---------------+-------+
1 row in set (0.00 sec)

v5.6

mysql> show variables like '%time%zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | JST    |
| time_zone        | SYSTEM |
+------------------+--------+
2 rows in set (0.00 sec)

原因はv4.0からダンプファイルを取得する際、v5.6のmysqldumpを使用したため、"tz-utc"という

異なるタイムゾーンのサーバ間でTIMESTAMPカラムをダンプしてリロードできる

https://dev.mysql.com/doc/refman/5.6/ja/mysqldump.html#option_mysqldump_tz-utc

機能が有効に働いたのにもかかわらず、
v4.0側はこの概念がないため、

/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;

ダンプファイル内に上記のようなタイムゾーンの設定が入っているが、実際のTIMESTAMPカラムは生データのままになってしまい、v5.6側に投入すると時間がズレていたもよう。

どちらのサーバも時間設定は同じなので、mysqldumpのオプションである"tz-utc"を無効にする"skip−tz-utc"を付与することで、無事時間のズレなく投入できた。

mysqldumpでオプション"--master-data"を実行する際に必要な権限

GRANT SELECT, RELOAD, SUPER, REPLICATION CLIENT ON *.* TO 'dump'@'%' IDENTIFIED BY 'PASSWORD';

だ!

pagerdutyで復旧通知メールによってインシデントを発生しないようにしたり自動的にインシデントをResolved(リゾルブ)に変えたりする方法

pagerdutyとは?

言わずと知れたシステムアラートの通知管理サービスです。メールやAPIサードパーティの監視システムからの信号をきっかけに、担当者に電話やSMS、メール等で優しく、時に厳しく連絡を送ってくれるものです。連絡する担当者をローテーションや順番等を細かく設定できるのも大きな特徴です。pagerduty側に送られた信号はインシデントという単位で、それぞれの状態を、"Triggered"(発砲)、"Acknowledged"(認知)、"Resolved"(解消)などのステータスで管理できます。

www.pagerduty.com

復旧通知メールを無視する方法

システムのメトリクス監視の中には、ロードアベレージやセッション数など、閾値を超えた場合発砲通知メールを送信し、その後は発砲せず、閾値を下回ったら復旧通知メールを送信して、その後は通常通り発砲するタイプのものがあります。この場合、まず復旧通知メールをpagerduty側でインシデントとして認識させないようにする必要があります。pagerdutyにはメールやAPI等で送られた信号を条件に合致した場合にインシデントを生成する、またはインシデントを解消するルール設定が可能です。Edit Serviceページにあります。

f:id:kazukiyunoue:20160115170946p:plain
f:id:kazukiyunoue:20160115171004p:plain
f:id:kazukiyunoue:20160115171013p:plain

例えば、復旧通知メールに必ず"[Resolved]"というキーワードが入る場合は、以下のような条件を設定すると、復旧通知メールを無視できます。

f:id:kazukiyunoue:20160115172819p:plain

厳密には無視をする機能ではありません。"[Resolved]"のキーワードが入った件名の文言全てと合致する発生済みインシデントをResolved状態にさせようとしていて、当然そのようなインシデントはありえないので、実質無視する形になります。

復旧通知メールでインシデントをResolvedにする方法

こうすると、監視システム側では解消となっているのにもかかわらず、pagerduty側ではまだResolvedになっていません。pagerdutyには、親切にも設定された時間を経過すると改めて通知する機能があって、深夜に障害は解消しているのに、電話でまた起こされるなんていう羽目になってしまいます。そこで、先ほど説明したように、条件に合致した発生済みインシデントをResolvedにする機能を使って、自動的にResolvedに変えたいと思います。合致の条件は以下のような種類が選べます。

f:id:kazukiyunoue:20160115171116p:plain

例えば発砲通知メールを、

[Alert] Xxxxxx is Xxxxxx

として、復旧通知メールを、

[Resolved][Alert] Xxxxxx is Xxxxxx

とします。この場合、以下のようにルールを設定すると、

f:id:kazukiyunoue:20160115172840p:plain

あら不思議、復旧通知メールをきっかけにpagerduty側のインシデントもResolved状態になりました。

まとめ

これでpagerdutyの更新忘れで無駄に起こされることがなくなりましたね。サードパーティ製の監視ツールの場合、pagerduty側での受け入れ体制が整っているので、もともと対応している場合もあります。独自の監視システムをお持ちの方のご参考になれば幸いです。

activerecordの関連するモデルをまとめて取得するメソッドのincludesが多段の関連モデルでも使えた

例えば、以下のような3層にまたがる関連モデルがあるとすると、

class Team
  has_many :members
end

class Member
  belongs_to :team
  has_many :tasks
end

class Task
  belongs_to :member
end

この場合、Taskのインスタンスオブジェクトから、
関連するTeamオブジェクトの情報を呼び出す場合、

task = Task.last
task.member.team.name
=> "A team"

こうなって、SQL的には、

SELECT `tasks`.* FROM `tasks` LIMIT 1
SELECT `members`.* FROM `members` WHERE `members`.`id` = 6 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 5 LIMIT 1

こうなります。
一見問題ないようにも見えますが、

仮にTaskオブジェクトを

Task.all

のように取得してeach等でループする場合、
Taskオブジェクトの数だけ、membersテーブルとteamsテーブルにアクセスしてしまいます。

tasks = Task.limit 10
tasks.each {|task| p task.member.team.name }
SELECT `tasks`.* FROM `tasks` LIMIT 10
SELECT `members`.* FROM `members` WHERE `members`.`id` = 6 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 5 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 4 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 4 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 4 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 4 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 6 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 5 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 6 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 5 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 6 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 5 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 4 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 4 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 3 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 3 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 2 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 2 LIMIT 1

SELECT `members`.* FROM `members` WHERE `members`.`id` = 4 LIMIT 1
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` = 4 LIMIT 1

そこで登場するのが関連するモデルをまとめて取得するメソッドのincludesです。
includesメソッドを多段の関連モデルで呼び出す場合は、以下のように記述します。

tasks = Task.limit(10).includes(:member => :team)
tasks.each {|task| p task.member.team.name }
SELECT `tasks`.* FROM `tasks` LIMIT 10
SELECT `members`.* FROM `members` WHERE `members`.`id` IN (6, 4, 3, 2)
SELECT `teams`.* FROM `teams` WHERE `teams`.`id` IN (5, 4, 3, 2)

MariaDB v10.0.3とMroonga v4.0.5インストール時にmakeでERRORになった話

MariaDBって

MySQLから派生したRDBMSのひとつ。MySQLのオリジナルコードの作成者であるモンティーさんがオラクル管理の現在のMySQLコードをフォークして始めた。

MySQLに比べて各処理のアーキテクチャがより高速に変更されていたり、Thread_poolや並列レプリケーションなどのRDBMSにしては前衛的な機能が盛り込まれていたりする。

大手ウェブサービスも移行していたり、各LinuxディストリビューションのデータベースもMariaDBに変更していたり、RDBMS界隈でわりとホットな話題。

Mroongaって

MySQLMariaDBでも)で全文検索を実現するストレージエンジンのひとつ。
全文検索エンジンであるGroongaをベースとしており、MySQLプラグインとしてインストールできるので設置が容易。

インストール

MariaDBのインストール

tarfileや各種パッケージからインストールする。特につまずくポイントはなし。
https://downloads.mariadb.org/mariadb/10.0.13/

Groonga install

rpm -ivh http://packages.groonga.org/centos/groonga-release-1.1.0-1.noarch.rpm
yum makecache
yum install -y groonga
yum install -y groonga-devel

yum install -y groonga-normalizer-mysql

※このときはdevelも必要でした。

Mroongaのインストール

上記のMariaDBを任意のディレクトリに配置するなどしていたので、ソースからビルド。
http://mroonga.org/ja/docs/install/others.html#build-from-source

cd /usr/local/src/
wget http://packages.groonga.org/source/mroonga/mroonga-4.05.tar.gz
wget https://downloads.mariadb.org/interstitial/mariadb-10.0.13/source/mariadb-10.0.13.tar.gz/from/http%3A//ftp.yz.yamagata-u.ac.jp/pub/dbms/mariadb
tar xvzf mroonga-4.05.tar.gz 
tar zxvf mariadb-10.0.13.tar.gz 
cd mroonga-4.05

./configure \
--with-mysql-source=/usr/local/src/mariadb-10.0.13 \
--with-mysql-config=/MARIA/product/bin/mysql_config
  • --with-mysql-source:インストール対象のソースのパスを記載。今回は先ほどダウンロードしたMariaDB
  • --with-mysql-config:実際にインストールされたバイナリーディレクトリにあるmysql_configファイル。

そんで、make。

make

このとき以下のようなエラーが出た。

/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:29:57: error: pcre.h: No such file or directory
In file included from /usr/local/src/mariadb-10.0.13/sql/item.h:3745,
                 from /usr/local/src/mariadb-10.0.13/sql/sql_lex.h:26,
                 from /usr/local/src/mariadb-10.0.13/sql/sql_class.h:474,
                 from ../mrn_mysql.h:51,
                 from mrn_path_mapper.cpp:30:
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1494: error: ISO C++ forbids declaration of 'pcre' with no type
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1494: error: expected ';' before '*' token
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h: In constructor 'Regexp_processor_pcre::Regexp_processor_pcre()':
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1510: error: class 'Regexp_processor_pcre' does not have any field named 'm_pcre'
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h: In member function 'void Regexp_processor_pcre::init(const CHARSET_INFO*, int, uint)':
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1521: error: 'PCRE_UTF8' was not declared in this scope
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1521: error: 'PCRE_UCP' was not declared in this scope
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1523: error: 'PCRE_CASELESS' was not declared in this scope
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h: In member function 'void Regexp_processor_pcre::cleanup()':
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1559: error: 'm_pcre' was not declared in this scope
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1561: error: 'pcre_free' was not declared in this scope
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h: In member function 'bool Regexp_processor_pcre::is_compiled() const':
/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:1566: error: 'm_pcre' was not declared in this scope
make[2]: *** [mrn_path_mapper.lo] Error 1
make[2]: Leaving directory `/usr/local/src/mroonga-4.05/lib'
make[1]: *** [all-recursive] Error 1
make[1]: Leaving directory `/usr/local/src/mroonga-4.05'
make: *** [all] Error 2

/usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h:29にて、pcre.hというヘッダファイルが見つからないようだ。
調べてみると、

ls -lh /usr/local/src/mariadb-10.0.13/pcre/pcre.h.in 
-rw-r--r-- 1 1001 1001 32K Aug  8 18:13 /usr/local/src/mariadb-10.0.13/pcre/pcre.h.in

.in付いてる。
仕方がないので、正しいかどうかは分かりませんが、

vi /usr/local/src/mariadb-10.0.13/sql/item_cmpfunc.h

にて、

#include "pcre.h"                 /* pcre header file */

#include "pcre.h.in"                 /* pcre header file */

に変更。

make

うまくいった!

あとは、make installして、設定用のSQLを実行するだけ。

make install
mysql -u root < /usr/local/share/mroonga/install.sql