MySQL では 「InnoDB Memcached」プラグインを使うことで、Memcached プロトコルを使ってテーブルデータに直接アクセスすることができるようになります。
参考:MySQL :: MySQL 5.7 Reference Manual :: 14.20 InnoDB memcached Plugin
このシリーズではセットアップから基本的な使用方法、本家MemcachedとRedisとのベンチマーク比較、そしてSQLとMemcachedプロトコルのベンチマーク比較を行なっていきたいと思います。
Memcached プラグインの利点
MySQLのMemcachedプラグインを利用することの利点は主に以下の通りです。
InnoDB ストレージエンジンにSQLを介さずに直接アクセスするため高速です
SQLステートメントの場合、MySQLではクエリパーサーで解析処理が必要ですが、Memcachedプロトコルではその部分が不要になるため高速に動作します。
データはMySQLのデータベースとして保存されるため、クラッシュセーフ
本家Memcachedはデータはメモリ上にしかないため、停止するとデータは消えてしまいますが、Memcachedプラグインの場合、データは通常のテーブルに保存されているため、MySQLが停止してもデータは守られます。
データはSQLによる操作も可能です
Memcachedプロトコルで書き込まれたデータも、通常のテーブルに保存されているので、SQLを使ってアクセスすることも可能なので、他のテーブルとJOINして使うこともできます。
マスタースレーブによる高可用性構成も可能です
本家Memcachedではレプリカを持つような構成は組めませんが、Memcachedプロトコルでの書き込みはバイナリログに記録させることができるため、マスタースレーブ構成を組むことも可能です。
参考:MySQL :: MySQL 5.7 Reference Manual :: 14.20.1 Benefits of the InnoDB memcached Plugin
セットアップ
事前準備として、CentOS 7.3 上に MySQL 5.7.20 がインストール済とします。
Memcamedプラグインを動かすために必要なテーブルをセットアップするためのSQLがデフォルトでは /usr/share/mysql/innodb_memcached_config.sql に配置されているのでこれを実行します。
1 2 |
$ mysql -u root -p mysql> source /usr/share/mysql/innodb_memcached_config.sql |
上記のSQLを実行すると、 innodb_memcache と test という2つのデータベースが作成されます。
1 2 3 4 5 6 7 8 9 10 11 12 |
mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | innodb_memcache | | mysql | | performance_schema | | sys | | test | +--------------------+ 6 rows in set (0.00 sec) |
innodb_memcache には以下の3つのテーブルが作成されています。
1 2 3 4 5 6 7 8 9 |
mysql> show tables from innodb_memcache; +---------------------------+ | Tables_in_innodb_memcache | +---------------------------+ | cache_policies | | config_options | | containers | +---------------------------+ 3 rows in set (0.00 sec) |
test データベースには demo_test テーブルが作成されます。
1 2 3 4 5 6 7 |
mysql> show tables from test; +----------------+ | Tables_in_test | +----------------+ | demo_test | +----------------+ 1 row in set (0.00 sec) |
memcachedプラグインをインストールします。
1 |
mysql> INSTALL PLUGIN daemon_memcached soname "libmemcached.so"; |
公式ドキュメントのセットアップ手順には記載が無いですが、リッスンするポート番号を設定する必要があります。
1 2 3 4 |
$ sudo vi /etc/my.cnf [mysqld] daemon_memcached_option="-p11211" # これを追加 |
MySQLを再起動します。
1 |
$ sudo systemctl restart mysqld.service |
Memcachedプラグインの動作確認
初期設定では test.demo_test が動作するようになっているのでこれを使って動作確認を行なってみます。
また、Memcachedプロトコルでは特別なクライアントツールは不要で、telnetを使って操作することができます。
参考:memcachedプロトコルについて | さくらインターネット研究所
telnetをインストールします。
1 |
$ sudo yum install -y telnet |
MySQLのMemcachedに接続します。
1 2 3 4 |
$ telnet localhost 11211 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. |
Memcachedプロトコル経由でデータを取得します。
ここでは既にデータが登録済の「AA」というキー名で取得します。
1 2 3 4 |
get AA VALUE AA 8 12 HELLO, HELLO END |
「HELLO,HELLO」という登録済データが取得できました。
Memcachedプロトコル経由でデータをセットします。
1 2 3 |
set BB 10 0 16 GOODBYE, GOODBYE STORED |
SQLでテーブルデータを確認すると、 demo_test にデータが格納されていることがわかります。
1 2 3 4 5 6 7 8 |
mysql> select * from demo_test; +----+------------------+------+------+------+ | c1 | c2 | c3 | c4 | c5 | +----+------------------+------+------+------+ | AA | HELLO, HELLO | 8 | 0 | 0 | | BB | GOODBYE, GOODBYE | 10 | 2 | 0 | +----+------------------+------+------+------+ 2 rows in set (0.00 sec) |
参考:MySQL :: MySQL 5.7 Reference Manual :: 14.20.3 Setting Up the InnoDB memcached Plugin
新しいテーブルをMemcachedで使えるようにする
初期設定ではMemcachedプロトコルで読み書きされるテーブルは demo_test ですが、新しくテーブルを追加したいと思います。
test データベースに以下の users テーブルを作成します。
1 2 3 4 5 6 7 8 9 10 |
mysql> CREATE TABLE `users` ( `user_id` varchar(32) NOT NULL, `name` varchar(255) DEFAULT NULL, `height` int(11) DEFAULT NULL, `age` int(11) DEFAULT NULL, `flags` int(11) DEFAULT NULL, `cas` bigint(20) unsigned DEFAULT NULL, `expiry` int(11) DEFAULT NULL, PRIMARY KEY (`user_id`) ) ENGINE=InnoDB; |
memcache のデータストアとしてマッピングするには以下のカラムが必要になります。
- PRIMARY KEY もしくは UNIQUE INDEX が付与された VARCHAR カラム
- memcached が使用するフラグ INT カラム
- memcached が使用する比較とスワップ値を格納する BIGINT カラム
- memcached が使用する有効期限の値を格納する INT カラム
参考:14.18.7 InnoDB memcached プラグインの内部構造 ページ内「containers テーブルカラムの制約」箇所
users テーブルをmemcacheのテーブルとしてマッピングします。
1 2 3 4 5 |
mysql> INSERT INTO `innodb_memcache`.`containers` ( `name`, `db_schema`, `db_table`, `key_columns`, `value_columns`, `flags`, `cas_column`, `expire_time_column`, `unique_idx_name_on_key`) VALUES ('default', 'test', 'users', 'user_id', 'name|height|age', 'flags','cas','expiry','PRIMARY'); |
設定値をリロードするにはMySQLの再起動か、以下のようにプラグインをアンインストールしてからインストールし直します。
1 2 |
mysql> UNINSTALL PLUGIN daemon_memcached; mysql> INSTALL PLUGIN daemon_memcached soname "libmemcached.so"; |
操作するテーブルを指定する
containers テーブルに複数レコード設定されているときは、 name カラムに default が指定されているレコードのマッピングが使用されます。
それ以外のマッピングを利用してSETやGETを行う場合は、キー名を以下のように指定します。
1 |
@@[name].[キー名] |
1 2 3 4 5 6 7 8 |
$ telnet localhost 11211 Trying ::1... Connected to localhost. Escape character is '^]'. get @@aaa.BB VALUE @@aaa.BB 10 16 GOODBYE, GOODBYE END |
複数のカラムに入力値を分割して格納する
複数のカラムに入力値を分割して格納したい場合は、value_columns に | で区切って指定してください。
入力値を | で区切る
1 2 3 4 5 6 |
$ telnet localhost 11211 Trying ::1... Connected to localhost. set B 0 0 11 John|173|25 STORED |
分割されて格納されます
1 2 3 4 5 6 7 |
mysql> select * from test.users; +---------+------+--------+------+-------+------+--------+ | user_id | name | height | age | flags | cas | expiry | +---------+------+--------+------+-------+------+--------+ | B | John | 173 | 25 | 0 | 1 | 0 | +---------+------+--------+------+-------+------+--------+ 1 row in set (0.00 sec) |
MySQL側の更新をMemcached経由で読み出す
SQLを使ったテーブルデータの更新情報はMemcachedでも反映されます。
SQLを使ってデータを挿入
1 2 |
mysql> INSERT INTO users VALUES ('C','Peter', 174, 32, 0, 0 ,0); Query OK, 1 row affected (0.00 sec) |
Memcached経由でデータを取得する
1 2 3 4 5 6 7 8 |
$ telnet localhost 11211 Trying ::1... Connected to localhost. Escape character is '^]'. get C VALUE C 0 12 Peter|174|32 END |
デフォルトのMemcachedプロトコルの分離レベルは READ UNCOMMITTED
利用する上で注意が必要なのは、Memcachedプロトコルの分離レベルは READ UNCOMMITTED なので、他のトランザクション途中の更新データが見えてしまうことです。
MySQL側でデータを挿入する
1 2 3 4 5 |
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> INSERT INTO users VALUES ('D','James', 167, 28, 0, 0, 0); Query OK, 1 row affected (0.01 sec) |
この段階ではコミットしていないため、他のコネクションからはDは見えません。
1 2 3 4 5 6 7 8 |
mysql> select * from test.users; +---------+-------+--------+------+-------+------+--------+ | user_id | name | height | age | flags | cas | expiry | +---------+-------+--------+------+-------+------+--------+ | B | John | 173 | 25 | 0 | 1 | 0 | | C | Peter | 174 | 32 | 0 | 0 | 0 | +---------+-------+--------+------+-------+------+--------+ 2 rows in set (0.00 sec) |
Memcachedからは見えてしまいます。
1 2 3 4 5 6 7 8 |
$ telnet localhost 11211 Trying ::1... Connected to localhost. Escape character is '^]'. get D VALUE D 0 12 James|167|28 END |
ロールバックすると、
1 2 |
mysql> rollback; Query OK, 0 rows affected (0.00 sec) |
Memcachedからも見えなくなります。
1 2 |
get D END |
これらの問題は、innodb_api_trx_level で分離レベルの変更は可能です。
my.cnf に以下の設定をすることで REPEATABLE READ に設定できます。
1 2 |
[mysqld] innodb_api_trx_level = 2 |
ただし、弊社の検証環境では、innodb_api_trx_level を変更するとMySQLが極端に不安定な状態になってしまいました。
もし変更する際は十分に検証を行った上で利用してください。
まとめ
Memcachedで読み書きするデータをMySQLでも利用したい場合など、本家Memcachedでは難しかった使い方や、Memcachedで課題になりやすい永続化の部分もMySQLそのままの機構に任せることができるなど利点も多いです。
次回はこのMemcachedプラグインと本家Memcached、最近キャッシュサーバーとしてよく利用されているRedisとのベンチマーク比較を行いたいと思います。