yoshida_eth0の日記 このページをアンテナに追加 RSSフィード

2010-10-24

Ruby on RailsでMySQL Clusterの使えるようにするプラグインを作ってみた

似たようなプラグインもいくつかあったけど、どれもしっくり来なかったから自分で作った。

その名も「Mysql Cluster Adapter」。

 

eth0jp's activerecord-mysql_cluster_adapter at master - GitHub

http://github.com/eth0jp/activerecord-mysql_cluster_adapter

 

MySQL Cluster Adapterの機能

複数Writableに対応

ただReadOnly機能がないだけだけど。

※マルチマスターレプリケーション対応ではない。

 

クラスタリング・自動復旧

リクエストが来た時にランダムで使うノードを選択する。

もし選択したノードが死んでいたら再接続を試みて、接続出来なかったら次のノードを選択する。

全てのノードが死んでたら500エラー。

 

トランザクション

トランザクションを有効にするために、1つのリクエストでは必ず同じノードに接続するようにする。

そのため、リクエストが送られてからノードが死んだ場合は500エラーをレスポンスする事になる。

 

ユーザビリティ

プラグインを使う人が意識せずに使える。

config/database.ymlが書き方が違うだけで、プログラム側は通常のMySQLアダプタと同じ。

 

設定ファイル

config/database.yml
production:
  adapter: mysql_cluster
  nodes:
    - database: clusterdb
      host: 192.168.1.10
      username: root
      password: pass
      encoding: utf8
    - database: clusterdb
      host: 192.168.1.20
      username: root
      password: pass
      encoding: utf8

development:
  adapter: mysql_cluster
  nodes:
    - database: clusterdb_dev
      host: 192.168.1.10
      username: root
      password: pass
      encoding: utf8
    - database: clusterdb_dev
      host: 192.168.1.20
      username: root
      password: pass
      encoding: utf8

 

インストール

./script/plugin install git://github.com/eth0jp/activerecord-mysql_cluster_adapter.git

 

動作確認バージョン

Ruby 1.8.5

ActiveRecord 2.3.5

 

参考URL

ActiveRecord

ActiveRecordのDBコネクションの接続切れと再接続について。reconnectオプションは危険だなーとかも - odeの開発メモ日記

http://d.hatena.ne.jp/ode/20100809/1281336400

 

データベースに接続できない場合の流れを追ってみた - remind::me

http://d.hatena.ne.jp/kenshoster/20090127/1233041004

 

トランザクション

トランザクション - うなの日記

http://d.hatena.ne.jp/unageanu/20070526/1180148785

 

Ruby on Rails - ActiveRecord - ? ありえるえりあ

http://dev.ariel-networks.com/articles/workshop/rails-activerecord

 

似たようなプラグイン

acts_as_readonlyable

マスタースレーブ構成を想定したプラグイン。

モデルごとにMaster/Slaveを決める。

class Fruit < ActiveRecord::Base
  acts_as_readonlyable :read_only
end

 

Revolution On Rails: [PLUGIN RELEASE] ActsAsReadonlyable

http://revolutiononrails.blogspot.com/2007/04/plugin-release-actsasreadonlyable.html

 

dbのマスタースレーブに対応するためにacts_as_readonlyableを使ってみる - odeの開発メモ日記

http://d.hatena.ne.jp/ode/20090109/1231496360

 

mysql_replication_adapter

マスタースレーブ構成を想定したプラグイン。

SQLごとにMaster/Slaveを決める。

MyModel.find(:all, :use_slave => true)

 

Mysql Replication Adapter | Engineering Rapleaf

http://blog.rapleaf.com/dev/2007/08/12/mysql-replication-adapter/

 

findchris's mysql_replication_adapter at master - GitHub

http://github.com/findchris/mysql_replication_adapter

 

magic_multi_connections

最近のバージョンのRailsでは動かないみたい。

 

Magic Multi-Connections

http://magicmodels.rubyforge.org/magic_multi_connections/

2010-10-16

rake db:migrateでcreate_tableする時のストレージエンジンを指定するプラグイン作った

データベースをrakeで管理する場合、ストレージエンジンを指定してテーブルを作る時は、いちいちこんなのを書かなきゃいけない。

ちょっとした事なんだけど、非常に面倒くさい。

create_table :table_name, :options => "engine=MyISAM"

 

何かいい方法はないか調べたけど解決策はなさそう。

同じ所で困ってる方がいて、その方の記事が詳しかった。

 

migrateでストレージエンジンを指定 - よかろうもん!

http://d.hatena.ne.jp/interu/20080331/1206977231

 

記事はちょっと古いけど、現在我が家で使ってるActiveRecord 2.3.5もInnoDBをべた書きなのは変わってなかった。

      def create_table(table_name, options = {}) #:nodoc:
        super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
      end

 

で、それを解消するプラグインを作った。

 

出来る事は、

・指定したストレージエンジンをデフォルトとして使う。

・指定したストレージエンジンを強制する。

の2つ。

 

RedmineとかオープンソースのものでMySQL Clusterを使いたい時に、ndbclusterを強制すればいい感じに動く。(かも知れない)

まだRuby on Rails始めてから数週間だけど、なんだか楽しくなってきた。

 

eth0jp's activerecord-engine_spec at master - GitHub

http://github.com/eth0jp/activerecord-engine_spec

 

インストール

確かこんな感じだった気がする。

sudo yum install git
sudo gem install newgem
./script/plugin install git://github.com/eth0jp/activerecord-engine_spec.git

 

設定ファイルサンプル

config/initializersディレクトリの中にファイルを作る。

 

MyISAMをデフォルトのストレージエンジンとして指定する。

create_table呼び出し側でエンジンの指定があった場合は何もしない。

require 'active_record/engine_spec'

ActiveRecord::EngineSpec.engine = :MyISAM
ActiveRecord::EngineSpec.force = false

 

NDB Clusterをストレージエンジンとして使う事を強制する。

create_table呼び出し側でエンジンの指定があっても強制的に上書きする。

require 'active_record/engine_spec'

ActiveRecord::EngineSpec.engine = :ndbcluster
ActiveRecord::EngineSpec.force = true

 

migration実行結果

[root@localhost tmp]# rails testproj
[root@localhost tmp]# cd testproj/
[root@localhost testproj]# ./script/plugin install git://github.com/eth0jp/activerecord-engine_spec.git
[root@localhost testproj]# cat <<-'ENGINE_SPEC' > config/initializers/engine_spec.rb
> require 'active_record/engine_spec'
> ActiveRecord::EngineSpec.engine = :ndbcluster
> ActiveRecord::EngineSpec.force = true
> ENGINE_SPEC
[root@localhost testproj]# vi config/database.yml
[root@localhost testproj]# ./script/generate model test_model
[root@localhost testproj]# rake db:migrate
(in /tmp/testproj)
==  CreateTestModels: migrating ===============================================
-- create_table(:test_models, {:options=>" engine=ndbcluster"})
   -> 0.3325s
==  CreateTestModels: migrated (0.3327s) ======================================

[root@localhost testproj]# mysql -hxxx -uxxx -pxxx xxx
mysql> show create table test_models;
+-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table       | Create Table
|
+-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| test_models | CREATE TABLE `test_models` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=ndbcluster DEFAULT CHARSET=utf8 |
+-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

2010-08-28

MySQL Clusterのエラーについて調べてみた

設定自体は結構前に終わってたんだけど、改めてエラーについて調べてみた。

どうしてエラーが出るのか、何をどうすればエラーが出なくなるのか、の辺り。

運用中のエラーはまだ出た事がない。

今まで見た事のあるエラーはこれで全部。

あとは設定の確認方法。

 

NDBが起動出来ない

ndb_mgm> Node 11: Forced node shutdown completed. Occured during startphase 5. Caused by error 2355: 'Failure to restore schema(Resource configuration error). Permanent error, external action needed'.

 

config.iniで設定されているリソースの割り当てではNDBを起動出来ない。

MaxNoOfOrderedIndexesとかを上げる必要がありそう。

service ndbd restartした側ではOKと出るけど、実は起動失敗してるパターン。

 

SQLを実行出来ない

errno: 136
ERROR 1005 (HY000) at line 651: Can't create table 'test_db.test_51' (errno: 136)

 

MaxNoOfOrderedIndexesの値を大きくする。

デフォルトの値は128。

上げすぎるとメモリが足らなくて大変な事になる。

 

errno: 708
ERROR 1005 (HY000) at line 833: Can't create table 'test_db.test_65' (errno: 708)

 

MaxNoOfAttributesの値を大きくする。

デフォルトの値は1000で、32〜4294967039で指定する。

上げすぎるとメモリが足らなくて大変な事になる。

 

チューニングする時に確認するべき箇所

・Dataノードが最低1台は起動しているか。

・Dataノードのservice ndbd restartの出力。

・管理ノードのndb_mgmの出力。

・Dataノードのxvc0の出力。(メモリのエラーが出てないか)

 

設定確認

管理ノードからndb_mgmで起動状況を確認
# ndb_mgm
-- NDB Cluster -- Management Client --
ndb_mgm> show
Connected to Management Server at: 192.168.0.80:1186
Cluster Configuration
---------------------
[ndbd(NDB)]     2 node(s)
id=11   @192.168.0.81  (mysql-5.1.47 ndb-7.1.5, Nodegroup: 0, Master)
id=12   @192.168.0.82  (mysql-5.1.47 ndb-7.1.5, Nodegroup: 0)

[ndb_mgmd(MGM)] 1 node(s)
id=1    @192.168.0.80  (mysql-5.1.47 ndb-7.1.5)

[mysqld(API)]   2 node(s)
id=21   @192.168.0.81  (mysql-5.1.47 ndb-7.1.5)
id=22   @192.168.0.82  (mysql-5.1.47 ndb-7.1.5)

 

管理ノードからndb_configで設定を確認

queryパラメータは、config.iniで設定するキーと同じ。

 

全体の確認。

# ndb_config --query=id,type,HostName --fields=", " --rows="\n"
1, ndb_mgmd, 192.168.0.80
21, mysqld, 192.168.0.81
22, mysqld, 192.168.0.82
11, ndbd, 192.168.0.81
12, ndbd, 192.168.0.82

 

管理ノードの確認。

# ndb_config --type=ndb_mgmd --query=id,type,HostName,PortNumber,DataDir --fields=", " --rows="\n"
1, ndb_mgmd, 192.168.0.80, 1186, /var/lib/mysql-cluster

 

Dataノードの確認。

# ndb_config --type=ndbd --query=id,type,HostName,NoOfReplicas,DataMemory,IndexMemory,StringMemory,DataDir,FileSystemPath,BackupDataDir --fields=", " --rows="\n"
11, ndbd, 192.168.0.81, 2, 167772160, 37748736, 25, /usr/local/mysql/data, /usr/local/mysql/data, /usr/local/mysql/data
12, ndbd, 192.168.0.82, 2, 167772160, 37748736, 25, /usr/local/mysql/data, /usr/local/mysql/data, /usr/local/mysql/data

 

SQLノードの確認。

# ndb_config --type=mysqld --query=id,type,HostName,BatchByteSize,BatchSize,MaxScanBatchSize --fields=", " --rows="\n"
21, mysqld, 192.168.0.81, 32768, 64, 262144
22, mysqld, 192.168.0.82, 32768, 64, 262144

 

SQLノードからMySQL Clusterの設定を確認
$ mysql -h192.168.0.81 -uxxx -pxxx
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 26
Server version: 5.1.47-ndb-7.1.5-cluster-gpl MySQL Cluster Server (GPL)

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> SHOW STATUS LIKE 'ndb%';
+--------------------------------+--------------+
| Variable_name                  | Value        |
+--------------------------------+--------------+
| Ndb_cluster_node_id            | 21           |
| Ndb_config_from_host           | 192.168.0.80 |
| Ndb_config_from_port           | 1186         |
| Ndb_number_of_data_nodes       | 2            |
| Ndb_number_of_ready_data_nodes | 2            |
| Ndb_connect_count              | 1            |
| Ndb_execute_count              | 48           |
| Ndb_scan_count                 | 48           |
| Ndb_pruned_scan_count          | 0            |
| Ndb_cluster_connection_pool    | 1            |
| Ndb_conflict_fn_max            | 0            |
| Ndb_conflict_fn_old            | 0            |
+--------------------------------+--------------+
12 rows in set (0.00 sec)

 

参考URL

MySQL :: MySQL 5.1 リファレンスマニュアル :: 14.4.4.4 マネジメント サーバーの定義

http://dev.mysql.com/doc/refman/5.1/ja/mysql-cluster-mgm-definition.html

 

MySQL :: MySQL 5.1 リファレンスマニュアル :: 14.4.4.5 Defining Data Nodes

http://dev.mysql.com/doc/refman/5.1/ja/mysql-cluster-ndbd-definition.html

 

MySQL :: MySQL 5.1 リファレンスマニュアル :: 14.4.4.6 SQL および他の API ノードの定義

http://dev.mysql.com/doc/refman/5.1/ja/mysql-cluster-api-definition.html

 

MySQL :: MySQL 5.1 リファレンスマニュアル :: 14.9.1 ndb_config - NDB 設定情報の抽出

http://dev.mysql.com/doc/refman/5.1/ja/mysql-cluster-utilities-ndb-config.html

2010-06-28

MySQLでrootなのにgrantコマンドが実行できない場合の対処法

rootなのにgrantコマンドが実行できない…。

mysql> GRANT ALL PRIVILEGES ON *.* TO root@'%' with grant option;
ERROR 1045 (28000): Access denied for user 'root'@'%' (using password: YES)

 

そんな時の対処法。

これだけなのにかなり悩んだのでメモ。

 

grant option権限復活

grant有効

とりあえずrootのみにしとく。

mysql> update mysql.user set Grant_priv='Y' where user='root';

 

設定反映
mysql> FLUSH PRIVILEGES;

 

権限確認

rootのGrant_privがYになってるか確認。

mysql> select user,Grant_priv from mysql.user;

 

権限確認

末尾にWITH GRANT OPTIONが付いてるか確認。

mysql> show grants;

 

MySQLリロード

bashに戻ってから。

# service mysqld reload

2010-05-20

MySQLの基礎コマンドをまとめてみた

すぐ忘れるからまとめてみた。

 

データベース

データベース作成

データベース「testdb」を作成。

CREATE DATABASE testdb;

 

データベース削除

データベース「testdb」を削除。

DROP DATABASE testdb;

 

データベース一覧
SHOW DATABASES;

 

データベースの作成情報
SHOW CREATE DATABASE testdb;

 

ユーザ・権限

ユーザ作成

何の権限も持たないユーザ「testuser」作成。

CREATE USER testuser;

 

パスワードも一緒に設定。

CREATE USER testuser IDENTIFIED BY 'password';

 

ユーザ作成・権限追加

全ての権限を持つユーザ「testuser」を作成。

GRANT ALL PRIVILEGES ON *.* TO testuser@localhost;

 

データベース「testdb」に対して全ての権限を持つユーザ「testuser」を作成。

GRANT ALL PRIVILEGES ON testdb.* TO testuser@localhost;

 

データベース「testdb」に対してSELECTをする権限を持つユーザ「testuser」を作成。

GRANT SELECT ON testdb.* TO testuser@localhost;

 

何の権限も持たないユーザ「testuser」を作成。

GRANT USAGE ON *.* TO testuser@localhost;

 

パスワードも一緒に設定。

GRANT ALL PRIVILEGES ON *.* TO testuser@localhost IDENTIFIED BY 'password';

 

パスワード設定

パスワードを「password」に設定する。

SET PASSWORD FOR testuser@localhost=PASSWORD('password');

 

ユーザ削除

ユーザ「testuser」を削除。

DELETE FROM mysql.user WHERE user='testuser' AND host='localhost';

 

ホストが「localhost」のユーザを削除。

DELETE FROM mysql.user WHERE host='localhost';

 

ユーザの権限削除
REVOKE ALL PRIVILEGES ON *.* FROM testuser@localhost;

 

ユーザ確認
SELECT user, host, password FROM mysql.user;

 

ユーザの権限確認
SHOW GRANTS FOR testuser@localhost;

 

テーブル

テーブル作成
CREATE TABLE testtable (
    id serial,
    data varchar(255)
);

 

ストレージエンジンを指定してテーブル作成
CREATE TABLE testtable (
    id SERIAL,
    data VARCHAR(255)
) ENGINE=InnoDB;

 

テーブル削除
DROP TABLE testtable;

 

テーブルが存在すれば削除。

DROP TABLE IF EXISTS testtable;

 

テーブル一覧
SHOW TABLES;

 

テーブルの作成情報
SHOW CREATE TABLE testtable;

 

カラム

カラム確認
DESC testtable;

 

カラム追加
ALTER TABLE testtable ADD newcolumn INTEGER;

 

追加場所を指定。

ALTER TABLE testtable ADD newcolumn INTEGER AFTER id;

 

先頭に追加。

ALTER TABLE testtable ADD newcolumn INTEGER FIRST;

 

カラム削除
ALTER TABLE testtable DROP newcolumn;

 

NotNull制約追加
ALTER TABLE testtable MODIFY newcolumn INTEGER NOT NULL;

 

NotNull制約削除
ALTER TABLE testtable MODIFY newcolumn INTEGER;

 

Default追加
ALTER TABLE testtable ALTER COLUMN newcolumn SET DEFAULT 0;

 

Default削除
ALTER TABLE testtable ALTER COLUMN newcolumn DROP DEFAULT;

 

インデックス

デフォルトで作られるインデックスは、インデックス名とカラム名が一緒になってるみたいだから、それに合わせとく。

 

インデックス確認
SHOW INDEX FROM testtable;

 

インデックス作成

CREATE INDEX インデックス名 ON テーブル名 (カラム名);

CREATE INDEX newcolumn ON testtable (newcolumn);

 

ALTER TABLE テーブル名 ADD INDEX インデックス名 (カラム名);

ALTER TABLE testtable ADD INDEX newcolumn (newcolumn);

 

インデックス削除

DROP INDEX インデックス名 ON テーブル名;

DROP INDEX newcolumn ON testtable;

 

ALTER TABLE テーブル名 DROP INDEX インデックス名;

ALTER TABLE testtable DROP INDEX newcolumn;

 

確認環境

# cat /etc/redhat-release 
CentOS release 5.4 (Final)

# mysql --version
mysql  Ver 14.12 Distrib 5.0.77, for redhat-linux-gnu (i686) using readline 5.1

# rpm -aq | grep ^mysql
mysql-5.0.77-4.el5_4.2
mysql-server-5.0.77-4.el5_4.2