[前][次][番号順一覧][スレッド一覧]

mysql:10655

From: "zen kishimoto" <"zen kishimoto" <zen@xxxxxxxxxx>>
Date: Mon, 13 Dec 2004 22:13:20 -0800
Subject: [mysql 10655] MySQLの新しいストレッジエンジンの実装

http://dev.mysql.com/tech-resources/articles/creating-new-storage-engine.html

John David Duncan

MySQLデータベースのサーバはモヂュール化されて構成されています。
ネットワーク層、SQLパーサー、クエリの最適ツールのような
「アッパー・レベル」はデータストレッジから、はっきりとした
インタフェースで隔離されています。大部分のMySQLデータベースは
データをMyISAMのテーブルかまたはトランスアクション InnoDB
のデータストアーに格納します。しかし、サーバーは種々のほかの
ストレッジエンジンを使用できます。MySQLにストレッジエンジン
を追加することは簡単です。

この記事では「ABC」という架空のストレッジエンジンを使って
新しいストレッジエンジンを追加するプロセスの概略を示します。
ABCのデータストアーを実際には定義せずに、生成されたり
変更される全てのファイルも含めてMySQLにそれを結合するに
必要なスステップを示します。ストレッジエンジンの実装の大部分は
新しいC++クラスハンドラーのサブクラスを実装することです。
ハンドラーのクラスは以下に簡単に述べます。もっとも
重要なドキュメントはMySQLのソースツリーにストレッジエンジン
の例の形式で存在します。

どの新しいストレッジエンジンでもある決まった形の変更
が必要です。その変更でコアのサーバーが新しいハンドラーとインターフェース
できるようになります。ここでこの変更を詳細に述べます。
基本的なステップのほかに必要とされることは、新しいエンジンが
提供する機能の量によります。最も重要な機能はファイルからデータ
を読む機能です。インデクスがない読むだけのストレッジ形式
では、この機能さえあれば開発の仕事は終わりです。書き込める
ストレッジの形式はもう少し作業が必要です。ファイルの
最後に順次的なステートメントを付加することです。更に
UPDATEとDELETEを処理することも必要です。インデクスに
よるアクセスやトランスアクションの機能は更にもっと仕事量が
増えます。

4.1.3版より、ソース・ディストリビューションのsql/examples
ディレクトリにはストレッジエンジンの実装の例があります。ha_example.hと
ha_example.cc にあるエンジンのコードと例はストレッジエンジンAPI
の主なドキュメントです。ストレッジエンジンの例は実際には
データを格納できませんが、コンパイルはできます。コンフィギュレーション
で--with-example-storage-engineのオプションを指定すれば
例のコードを含むmysqldのバイナリが生成されます。ストレッジ
エンジンの例は4.1.3版と5.0.1版で最初に紹介されました。しかし、
ファイルの例とインストラクションは4.0にも適用できます。5.1版の
後では新しいストレッジエンジンを ダイナミックにリンクできるように
なります。そのため、このインストラクションの一部は適用外に
なります。

ABCストレッジエンジンを生成するには以下の15のファイルを変更する必要があります。 



   1. ソースディレクトリーのトップにあるAutoconfファイル

config.h.in (またはacconfig.h、ソースの版による)
acinclude.m4
configure.in

   2. sql/の下にある基本的なサーバーのファイル

sql/Makefile.am
sql/handler.h
sql/handler.cc
sql/set_var.cc
sql/mysql_priv.h
sql/mysqld.cc

   3. sql/examples/の下にある実装ファイル

sql/examples/ha_abc.h
sql/examples/ha_abc.cc

   4. mysql-test/の下のテストケース

mysql-test/include/have_abc.inc
mysql-test/r/have_abc.require
mysql-test/t/abc.test
mysql-test/r/abc.result

1と2にある変更はビルドシステムとコアサーバーで新しい
ストレッジエンジンをサポートするのに必要なハウスキーピング
の変更です。3の.h と.cc ファイルはストレッジエンジンを
クラスハンドラーのC++のサブクラスとして実装します。
これらのファイルは実際の作業の根幹です。4のファイルは
リグレッション・テストのフレームワーク内の新しいテストを
示し、新しいハンドラーの機能を検証するのに使われます。

現存するストレッジエンジン
のコードは実装のモデルとして存在します。ストレッジエンジン
のファイルの例のほか、4.1版と5.0版のソースツリーには
アーカイブ・ストレッジと呼ばれる作動可能なストレッジエンジン
があります。これはzlib圧縮ライブラリーを使用します。
アーカイブ・ストレッジを使うとソースツリーで今どこに
いるか良く分かります。開発のそれぞれのステップで
ファイル内でストリングの"archive"、"ARCHIVE"や
"EXAMPLE" を探すと必要なコードを検索するのに便利です。

ソースディレクトリのトップにあるAutoconfファイル

MySQL GNU のautoconf を使いプラットフォーム間のコードの
ポータビリティとコンパイルのオプションを管理します。新しい
機能をソースツリーに追加するにはautoconfファイルの幾つかを
変更することから始めます。

ABC ストレッジエンジンを実装するには、config.h(または
ソースツリーによってはacconfig.h)の新しいコンフィギュレーション
の変数を "undefine" することから始めます。この変数は新しい
ストレッジエンジンをサーバーに含めるために使用されます。

/* Builds Example DB */
#undef HAVE_EXAMPLE_DB

/* Builds abc storage engine */
#undef HAVE_ABC_DB

次にacinclude.m4内で例のエンジンかアーカイブエンジン用
の新しいルールを既存のルールに従ってMYSQL_CHECK_ABCDBの
ために生成します。必要なのは以下をコピーして、検索・置換で
置き換えます。最終的には以下のように
なります。(実際のルールはソースツリーによって違いますが。)

dnl -----------------------------------------------------
dnl Macro: MYSQL_CHECK_ABCDB
dnl Sets HAVE_ABC_DB if --with-abc-storage-engine is used
dnl -----------------------------------------------------
AC_DEFUN([MYSQL_CHECK_ABCDB], [
  AC_ARG_WITH([abc-storage-engine],
              [
 ...   何行か省略
      ;;
  esac
])

最後に、configure.inにMYSQL_CHECK_ABCDBように新しいルールを追加します。

MYSQL_CHECK_ISAM
MYSQL_CHECK_BDB
MYSQL_CHECK_INNODB
MYSQL_CHECK_EXAMPLEDB
MYSQL_CHECK_ARCHIVEDB
MYSQL_CHECK_NDBCLUSTER

... 行を追加

MYSQL_CHECK_ABCDB

sql/の下で変更されるベーシック・サーバーファイル

ソースツリーの sql/ サブディレクトにはクエリを
パースして処理するコアーソースファイルがたくさんあります。
Makefile.am ファイルには少なくとも2つもしくは3つ
の変更が必要です。

    * $noinst_HEADERSの定義に新しいストレッジエンジン
(例えばexamples/ha_abc.h)用のヘッダーファイルの名前を加えます。
    * $mysqld_SOURCESの定義に新しいストレッジエンジン
(例えばexamples/ha_abc.cc)用に実装ファイルを加えます。
    * ストレッジエンジンが必要とされているライブラリファイル
にリンクされていると、それを$LDADDに追加します。

handler.h ファイルはハンドラーのクラスを定義します。
これはサーバーとストレッジの間にある層です。このファイルは
1つの変更を必要とします。 DB_TYPE_ABC_DB とリストの定義の
最後であるdb_typeに追加。リストの中の項目の順番は非常に
重要です。それはリストの順番で定義される定数は.frm ファイルで
使用されてテーブルのストレッジエンジンを指定
するのに使います。

ハンドラークラスのメインの実装ファイルであるhandler.cc,
は3つの変更を必要とします。

    * ビルド用にコンフィギュアーされている時ストレッジエンジン
のヘッダーファイルを含むためにファイルの上層部に近いところ
に3行を追加してください。

#ifdef HAVE_ABC_DB
#include "examples/ha_abc.h"
#endif

    * ABC を含んだストレッジエンジンのアレー(
sys_table_types[])の最後の方に追加します。

struct show_table_type_st sys_table_types[]=
{
  {"MyISAM",    &have_yes,
 "性能の良い3.23版からのデフォルトのタイプ", DB_TYPE_MYISAM},
...
   {"ABC",&have_abc_db,
   "ABC storage engine", DB_TYPE_ABC_DB},
};

    * get_new_handler()関数のswitch にcase DB_TYPE_ABC_DBを追加。

#ifdef HAVE_ABC_DB
 case DB_TYPE_ABC_DB:
    return new ha_abc(table);
#endif

have_abc SQL変数を実装 

ABC ストレッジエンジンでhave_abcというSQL変数を
作る必要があります。実行時にこれはSHOW VARIABLES に
認識され、その値はサーバーでABCエンジンがオンになれば
YESにセットされます。この変数は新しいコードを開発したり
デバッグしたりする際に重要な役割を果たします。ABCストレッジ
エンジンをテストするユニットテストのテストケースを
開発する際、MySQLのテスト群を実行するかどうか決める
ためにhave_abcの値をチェックするようにコンフィギュア
すべきです。

更に、sql/ ディレクトリでSQLの変数を管理するコードは
set_var.ccファイルにあります。このファイルではアルファベット順に
並べられたinit_vars[]アレーの適当な場所に追加することで
変数を作ります。

struct show_var_st init_vars[]= {
  ...
  {"have_abc",  (char*) &have_abc_db, SHOW_HAVE },
}

変数のユーザから見える名前はストリングはhave_abcですが、内部ストレッジ
は変数have_abc_dbです。次にhave_abc を生成するステップはコアサーバーに
have_abc_dbを知らしめることです。mysql_priv.h ファイルはコアサーバー
にとっての"private" ヘッダーファイルです。mysql_priv.hに, have_abc_db
をSHOW_COMP_OPTIONタイプでexternal variableとして宣言します。
この宣言は以下のようです。

extern SHOW_COMP_OPTION have_abc_db;

これは他のストレッジエンジンの定義を含むファイルの
セクションにおきます。

/* オプション, have_* 変数 */

extern SHOW_COMP_OPTION have_isam, have_innodb, have_berkeley_db;
extern SHOW_COMP_OPTION have_example_db, have_archive_db;
extern SHOW_COMP_OPTION have_raid, have_openssl, have_symlink;

最後にhave_abc_db はコア実装ファイルのmysqld.cchのなかで
2箇所触れられます。

    * 最初にファイルの最初の方に他の似通った変数と共に
宣言します。

SHOW_COMP_OPTION have_example_db, have_archive_db, have_abc_db ;

    * 次にmysql_init_variables()関する内でその値を初期化します。

#ifdef HAVE_ABC_DB
  have_abc_db= SHOW_OPTION_YES;
#else
  have_abc_db= SHOW_OPTION_NO;
#endif

sql/examplesディレクトリ下で実装ファイルを生成

最後に実際の作業です。ストレッジエンジンは(または
少なくともMySQLとデータストアーとのインタフェースは)
2つのファイルで定義されています。これらの
テンプレートはsql/examples/ディレクトリーにあるha_example.hと
ha_example.cc ファイルです。

ストレッジエンジンのビルドにはha_example.hをコピーして
ha_abc.hと名づけ、またha_example.ccをコピーしてha_abc.cc
と名づけます。それから検索と置換で全てEXAMPLEをABC に
全てのexampleをabcと置き換えます。

sedを知っているのであれば、このようにすれば一度で出来ます。

sed s/EXAMPLE/ABC/g ha_example.h | sed s/example/abc/g > ha_abc.h
sed s/EXAMPLE/ABC/g ha_example.cc | sed s/example/abc/g > ha_abc.cc

ビルド

ここまで、全て順調に進めばABCを含んだサーバーをビルドできます。
新しいハンドラーはなにもしません(これはha_exampleファイルから
コピーされて全てスタブで実装されていまs。)がクリーンコンパイル
します。

autoconfファイルを編集したので、新しいサーバをビルドする
ときに最初にすることはautoconfコマンドを実行して新たな
依存性をアップデートすることです。4つのコマンドが必要です。

aclocal
autoheader
autoconf
automake

それからビルドをコンフィギュレーションします。

./configure --with-abc-storage-engine \
            --prefix=/home/jdd/mysql-builds/abc \
            --without-innodb

このコマンドの3つのパラメータを見てください。
--with-abc-storage-engine はin-progressストレッジエンジン
をオンにします。(./configure --helpを実行すると
ドキュメント内に表れます。)これはacinclude.m4を
編集した際に生成しました。
--prefix=/home/jdd/mysql-builds/abc はビルドを特別の開発
ディレクトリーにインストールし、同じマシン上のほか
のMySQLのインストレーションと問題を起こしません。
最後に --without-innodb はInnoDBをオフにします。これを
使えば、開発中にテストサーバーをスタートさせたりシャットダウン
するのを迅速に行うことができます。

コンフィギュレーションが終わるとmakeを実行します。全てがうまく
行けば、サーバーはエラーなしにコンパイルします。そして
makeは指示されたbuildディレクトリーコピーをインストール
します。buildディレクトリーに行って、サーバーをスタートさせて
have_abc変数をチェックしてみてください。

% cd ~/myqsl-builds/abc
% share/mysql/mysql.server start
% bin/mysql

Welcome to the MySQL monitor...

mysql> show variables like 'have_abc';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| have_abc      | YES   |
+---------------+-------+
1 row in set (0.01 sec)

ベースハンドラークラスはテーブル用に.frmファイルを生成できるので
type=ABCでテーブルを生成することも出来ます。しかし、データを
挿入することはできません。

mysql> use test;
Database changed
mysql> create table t1 (a int not null) type=abc;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> insert into t1 values (1);
ERROR 1031 (HY000): Table storage engine for 't1'
doesn't have this option

mysql-test/ディレクトリー下でテストケースを生成

ソースディレクトリーでいくつかのテストケースを作ってみましょう。
これはテストを元にした開発方です。新しい機能に対してテスト
ケースを作ります、そしてそれをパスするまでコードを書きます。
mysql-test/にある README ファイルにMySQLのリグレッションテスト
のフレームワークの概要が記されています。それは特別の
mysqltestというMySQLのクライアントに基づきます。mysqltestは
クエリを実行して既知の結果ファイルと比較するように設計されて
います。

テスト群はどの機能がコンパイルされているかによってどのテストを
選択するため、条件つき実行をします。実際、最初に生成する
ファイルはABCがサーバーにコンパイルされているかどうかを
テストするファイルです。mysql-test/include/でhave_archive.inc
というファイルをみてください。(アーカイブ・ストレッジエンジン
を例に引いています。それは例のエンジンにはテストケースがないからです).
このファイルの大きさは数行です。そのコピーを作って
have_abc.incと呼びます。それは have_archiveではなく、
have_abcをテストするためだからです。同様に結果を含む
mysql-test/include/r/でhave_archive.requireのコピーを作り
have_abc.requireと呼び同様の変更を加えます。

次にABCのために本当のテストケースをつくります。テストケース
のディレクトリーのmysql-test/t/にabc.testというファイルを
以下のように生成します。

-- source include/have_abc.inc

--disable_warnings
drop table if exists t1;
--enable_warnings

CREATE TABLE t1 (
  col1 int unsigned NOT NULL
) ENGINE=abc;

INSERT INTO t1 VALUES (42);

このファイルで2つのダッシュで始まる行はmysqltestの特別の
コマンドを示します。他の全ての行はSQLのステートメントです。
一旦テストがあれば、mysql-test-runコマンドを使って
実行できます。

% ./mysql-test-run abc

テストはINSERTステートメントを正しく実装するまで
失敗し続けます。一旦成功するとmysql-test-runに--recordスイッチを
使って将来のために結果を記録できます。
mysql-test-run --record abc を実行すると結果はr/サブディレクトリー
の下のabc.resultというファイルに格納されます。

ha_abcファイルにハンドラーを実装

次に実際にha_example.h とha_example.ccファイルを読んで
見ましょう。これらは新しいハンドラーコードのテンプレート
というだけでなく、ハンドラーの実装のドキュメントとも
なります。これらは、他のドキュメントの続きともいえます。

ハンドラーのコーディングをする準備ができたら、最初のステップは
ha_abc.hにtable_flags()とindex_flags()を定義します。
それにはABCで正確にどの機能がサポートされているかを
描写するために、handler.h で定義されているフラッグを使用します。
CSV ストレッジエンジンは例えば、ha_tina.h は以下のように
定義します。

ulong table_flags() const
{
  return (HA_REC_NOT_IN_SEQ | HA_NOT_EXACT_COUNT |
    HA_NO_AUTO_INCREMENT );
}
ulong index_flags(uint idx, uint part, bool all_parts) const
{
  /* インデクスはありません。*/
  return 0;
}

ハンドラークラスで実装される関数はデータに対する行レベル
インターフェースです。サーバースタックのハンドラーのすぐ
上には実行プランがあります。これはSQLのクエリをパースして
オプティマイザーによって生成されます。その下には
データアクセスのための関数コールであるBerkeley DB, zlib, また
は標準ファイルシステムのI/Oがあります。ハンドラーのメソッドの
多くは I/O コールに似ています。それらは、open()はテーブル
のオープン、update_row()はデータの行を変更、store_lock()
は特定のロックを入手します。

テーブルの完全スキャンを行うために最低次の5つの関数を
実装する必要があります。

関数    説明
open()     テーブルのオープン
info()     オプティマイザーにテーブルの情報を提供
rnd_init() テーブルのスキャンを準備
rnd_next() 次の行をバッファーに読み込む
extra()     スキャンが完了するとHA_EXTRA_RESETと共に呼ばれ
        テーブルをリセットする。

最も重要なのはrnd_next()で行バッファにポインターを渡され
内部データ形式でエンコードされたデータ行でバッファを満たす
ことになります。ハンドラーの下のストレッジのメカニズムに
アクセスを提供する際、ハンドラーの一番大きな仕事はそのデータ
形式とサーバが持っているデータ形式との変換です。

内部行形式

サーバーは3つのデータ形式を使用します。それは長さの決まった
行、可変の行、と可変でBLOBポインターつきの行です。それぞれ
の形式でCREATE TABLEステートメントで定義された順番に
列は並べられます。(テーブルの定義は.frm ファイルに格納されます。また
オプティマイザーとハンドラーはどちらもテーブルストラクチャーと
いう同じソースからテーブルのメタデータにアクセスできます。)

それぞれの形式は列毎に1ビットの"NULL bitmap"で始まります。
8つ列のあるテーブルでは1バイトビットマップがあります。
9から16列のテーブルは2バイトのビットマップがあると
いう風になります。ある値がNULLであるということを示すには
列のNULLビットを1にセットします。

NULL ビットマップの後には1つづつの列がきます。
それぞれの列はMySQLマニュアルの「MySQLデータタイプ」
の章に示された大きさです。サーバーでは、列データタイプは
sql/field.ccで定義されています。長さが一定の行形式では列は
単に1つ1つ並べられるだけです。可変行ではVARCHAR 列は1バイトと
して実装され文字のストリングが続きます。 BLOB列がある可変長の行
では、それぞれのblobは2つの部分で表されます。まずBLOB
の実際の大きさを示す整数とメモリー内の BLOB へのポインターです。

行変換(またはパッキング) はテーブルハンドラーのrnd_next()
を見ることで分かります。例えば、ha_tina.ccでfind_current_row()の
コードはTABLEのストラクチャー(テーブルからポイントされた)と
Stringオブジェクト(named buffer)がCSVファイルから文字データ
をパックする方法を示します。行をディスクに書き込むには
反対の変換が必要です。つまり内部形式からアンパックします。

結論

ここまで読むと、ストレッジエンジンの概要がお分かりになったでしょう。
これは新しいストレッジエンジンを実装するのに必要なステップへのレファレンスガイド
です。更に詳細なハンドラーのAPIや内部のデータ形式への
情報を探すにはどこを見れば良いかということも分かったと思います。
---------------------
Zen Kishimoto                        zen@xxxxxxxxxx
IP Devices, Inc.                       (408) 567-9391
2175 De La Cruz Blvd., Suite 10  (801) 720-8847 (FAX)
Santa Clara, CA 95050



[前][次][番号順一覧][スレッド一覧]