C++でMeCabを使ってみる。

http://mecab.googlecode.com/svn/trunk/mecab/doc/libmecab.html のサンプルを参考にした。
ページの下にコンパイル方法が書いてある。

#include <iostream>
#include <mecab.h>

int main(int argc, char **argv){
  char input[1024] = "太郎は次郎が持っている本を花子に渡した。";

  MeCab::Tagger *tagger = MeCab::createTagger("");
  const char *result = tagger->parse(input);

  std::cout << "INPUT: " << input << std::endl;
  std::cout << "RESULT: " << result << std::endl;

  return 0;
}

コンパイルは「gcc -I/usr/local/include test.cpp -L/usr/local/lib -lmecab」で通した。
コンパイラのオプションの意味は下記。

-llibrary
名前が library であるライブラリをリンク時に使用します。

リ ンカは、標準のライブラリ用ディレクトリのリスト中から、実際のファイル名
が ‘liblibrary.a’ であるファイルを検索します。リンカはこのファイルを、 フ
ァイル名で直接指定した場合と同様に使用します。

検索するディレクトリには、いくつかの標準システムディレクトリと、‘-L’ によ
って指定したディレクトリが含まれます。

通常、この方法で発見されるファイルはライブラリファイル、つまりいくつか の
オ ブジェクトファイルをメンバとして含むアーカイブファイルです。リンカは、
アーカイブファイルの中を検索して、参照されているが定義されていないシン ボ
ル を定義しているメンバを探し出します。しかし、もしリンカがライブラリでな
く通常のオブジェクトファイルを発見した場合は、そのオブジェクトファイル を
通常の方法でリンクします。‘-l’ オプションを使用する場合とファイル名を直接
指定する場合の違いは、‘-l’ の場合が library を ‘lib’ と ‘.a’ で囲み、いく
つものディレクトリを検索することだけです。

-Ldir ディレクトリdir を ‘-l’ による検索が行なわれるディレクトリのリストに加 え
ます。

-I- ‘-I-’ オプション指定前に ‘-I’ オプションによって指定された全てのディレ ク
ト リは、‘#include "file"’ の形式によってのみ検索されます。これらのディレ
クトリは ‘#include ’ によっては検索されません。

‘ -I-’ オプション指定後に ‘-I’ で指定したディレクトリは、 全 て の ‘#in-
clude’ 命令によって検索されます。(通常は 全ての ‘-I’ で指定されたディレク
トリはこの方法で検索されます。)

これに加えて ‘-I-’ オプションは、カレントディレクトリ (現在の入力ファイル
が存在するディレクトリ) が ‘#include "file"’ に対する最初の検索対象となる
ことを抑制します。‘-I-’ によるこの効果を上書きする方法はありません。‘-I.’
を 指定することによって、コンパイラが起動されたディレクトリが検索されるこ
とを指定することは可能です。これはプリプロセッサが行なうデフォルトの動 作
とは異なりますが、たいていはこれで十分です。

‘-I-’ は、ヘッダファイルの検索に標準のシステムディレクトリを使うことを抑
制するわけではありません。従って、‘-I-’ と ‘-nostdinc’ は独立です

mecabのインストール

昨年末にバージョンがあがっていたので再度インストールしなおした。
makeの時点でエラーが出たが、コンパイラのヴァージョンを変更して無事インストールできた。
備忘録。

##以下のようなエラーが出るときは。。
/bin/sh ../libtool --tag=CXX --mode=link g++ -O3 -Wall -o mecab mecab.o libmecab.la -lpthread -lpthread -lstdc++
libtool: link: g++ -O3 -Wall -o .libs/mecab mecab.o ./.libs/libmecab.so -lpthread -lstdc++
./.libs/libmecab.so: undefined reference to `__sync_val_compare_and_swap_4'

##コンパイラをg++44に変更して事なきを得た。
./configure --enable-utf8-only CXX='g++44'
ldconfigを忘れずに。

iptables

さくらVPSのプランを乗り換えたので、これを機にiptablesの設定もやっておくかと思い調べた。
だが結局自分のケースではSSHのポート変更とhosts.allowやhosts.denyの設定で事足りることが分かった。
備忘録的に書いておく。

Netfilterとは

パケットフィルタリング、アドレス変換、パケットの操作の処理のサポート。
カーネルに組み込まれるモジュール部分と設定・管理を行うiptablesコマンドの2つの部分に分かれる。

ネットワークパケット流路とフック地点


iptablesOSIの第3階層(ネットワーク層)で動作する。第2階層(データリンク層)を扱いたい場合はebtablesなるものを使うらしい。

以下メモ

>>概念

・テーブル
filter, nat, mangleの3つがある。
それぞれパケットフィルタリング、アドレス変換パケット処理に対応
画像の各フローにリストしたテーブルが、各フローで使える

・チェイン
組み込みチェインとユーザ定義チェインの2つがある。
画像の各フローに対応した組み込みチェインがある。


・ターゲット
ルールに合致したパケットに行う処理のことをターゲットと呼ぶ。

・ルール
パケットの内容との比較条件

                                                                                                            • -

>>チェインの作成・削除・変更
iptables -t filter -N MYTEST
iptables -t filter -X MYTEST
iptables -t filter -E MYTEST HOGE

                                                                                                            • -

>>チェインのルールの操作
・ポリシーの設定
 iptables -P
 chainにはINPUT, OUTPUT, FORWARDのみ指定できる。ユーザ定義チェインは指定できない
 targetにはACCEPT, DROPのみ
 原則ルールの決定です。デフォルトではACCEPTになっていて、何も設定しなければ
 全てを通過させるようになっています。

・ルールの追加
 iptables -A

                                                                                                            • -

>>パケットフィルタリング
2つのモデル
 1.厳密に必要な通信だけを許可する
 2.サーバ内部から外部への通信は全て許可。外部からサーバ内部への通信は
  必要なものだけを許可する。
  
だいたいは2のケースでいいやってなるのかな。個人的な用途のサーバであれば
そんな感じ。

                                                                                                            • -

>>実例
# iptables -N MYCHAIN
# iptables -A INPUT -j MYCHAIN
# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
MYCHAIN all -- anywhere anywhere

Chain FORWARD (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

Chain MYCHAIN (1 references)
target prot opt source destination

//policyはACCEPTとなっていてユルユルの設定。
//これがモデル1であればDROPにしてstrictな設定にする。

# iptables -A MYCHAIN -s 123.123.123.123 -p tcp --dport 22 -j ACCEPT
# iptables -A MYCHAIN -s 0.0.0.0/0 -p tcp --dport 22 -j DROP
//指定したIPからのSSHアクセスをACCEPT
//全てのIPからのSSHアクセスをDROP

# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
MYCHAIN all -- anywhere anywhere

Chain FORWARD (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

Chain MYCHAIN (1 references)
target prot opt source destination
ACCEPT tcp -- 123.123.123.123 anywhere tcp dpt:22
DROP tcp -- anywhere anywhere tcp dpt:22

ひとまず結論

ここまでやって、自宅の固定IPをやめたいのでアクセス元のIPをドメイン指定でできないかと調べたが、全てのパケットに対して名前解決をするのはとても非効率だということが書いてあったのでiptablesで設定するのはやめにした。。
ドメイン指定はやってできなくはないみたい。でもhosts.allowやhosts.denyレベルでいいのかなと。

MeCabのソースを読んでみる。

C++とかCの勉強は中途半端にやってはやめを繰り返していた。
やっぱり仕事でがしがし使わないのでなかなかモチベーションを維持できないのが大きい。

でも幅を広げたいのでちょっと頑張ってみることにした。会社で技術発表が2ヶ月おきくらいに
回ってくるのでそれに間に合うように形態素解析の薀蓄をコードを読んで、C++がある程度
書けるようになってプレゼンできればなと思う。

いきなりコードを読むのではなく下調べとして自然言語処理というジャンルにおける形態素解析
位置づけみたいなのをざっくりとまとめた。それが以下。


形態素解析は4つの解析ステップの内の一つということになる。「自然言語処理の基礎」(コロナ社)を読んだ限りでは、
形態素解析の役割は大きくわけて3つでマインドマップに書いた通り、「単語分割」・「品詞付与」・「原型の復元」となる。
MeCabのソースを見るとクラス名も役割に応じた名前になっていたりするのでなんとなくイメージしやすい印象があった。
アルゴリズムとかも鼻血が出るほど難しいというわけでもなさそうなので、なんとかなるかな。

2ヶ月かけてC++のお勉強とともにやっていきます。毎日1.5時間を当てられたらスゴイなー。
頑張れ自分。

SSHトンネリング

お仕事でデータセンターからデータセンターへサーバのお引越しをするという作業がありました。言い換えると、あるシステム系を別のネットワークへ移行するということですが、大抵の場合はその系に収まっていない外部システムへの依存をどう処理するかが悩ましいところになります。
そこでお題の通り、SSHトンネリングを使って処理しましたというお話です。

MySQLレプリケーションの問題


データセンターを移行する前はdb101をdb201にレプリケーションしていたので移行後も同様にしたい。

SSHトンネルを掘ってみる-1


トンネルを掘ってみたけどレプリケーションするにはdb201からdb101へ接続できなければなりません。

SSHトンネルを掘ってみる-2


デキた!と思ったら監視の都合でgw101からトンネルを掘ってねと。。

SSHトンネルのRで張ればいいのか。。


ただし、sshd_configの設定が必要となってくる。セキュリティ的には本当は望ましくないんでしょうけれど。。

SSHトンネルのコマンドの整理

下記の通り。

$ ssh -f -N -g -o serveraliveinterval=10 -A -L 13306:db201:3306 user_hoge@gw201.yourdomain.net

オプション
-A : Agent Forwading
-N : ログインシェルを起動しない(リモートでコマンド実行しない)
-f : バックグラウンドで実行
-g : GatewayPortsの指定。(リモートのホストにローカルのポートへのフォワードを許可)
-L(-R) : トンネル設定

さくらVPSにSubversionとTracをインストール。

自宅サーバではバックアップはかなり適当な感じだったので、しこしこ書き溜めているソースの管理はさくらのVPSに移した。月額980円の年間1万ちょっとで安心が買えるのはとても安いと思った。メモリが512Mのプランなので他にはネームサーバとかを移したぐらい。
以下はコマンドの発行手順。自宅サーバから手軽にチェックアウトしたいというような場合は秘密鍵をほげほげして.bashrcに「export SVN_SSH ...」を記述してみたいな感じの作業があった。備忘録。
git、使えるようにならないといけないな。めんどくせ。

tracのインストール方法

useradd taro
groupadd svn


##wget http://pypi.python.org/packages/2.4/s/setuptools/setuptools-0.6c11-py2.4.egg
##sh setuptools-0.6c11-py2.4.egg

yum -y install httpd-devel
yum -y install mod_python
yum -y install python-setuptools
easy_install --always-unzip Genshi
yum -y install mod_dav_svn

wget http://www.i-act.co.jp/project/products/downloads/Trac-0.11.7.ja1.zip
unzip Trac-0.11.7.ja1.zip
cd Trac-0.11.7.ja1
python ./setup.py install

mkdir /home/svn
svnadmin create /home/svn/repos
chown -R root.svn /home/svn
chmod -R 775 /home/svn

svnグループに参加させる
usermod -G taro,svn taro
usermod -G apache,svn apache

su - taro
cd ~
mkdir tmp
echo "TEST" > tmp/test.html
svn import tmp file:///home/svn/repos -m "import"
exit


cd /etc/httpd/conf.d
mv proxy_ajp.conf proxy_ajp.conf.org
mv python.conf python.conf.org
mv subversion.conf subversion.conf.org
mv welcome.conf welcome.conf.org

htpasswd -c /var/www/.htpasswd taro
emacs /etc/httpd/conf.d/trac.conf
###################################################
LoadModule python_module modules/mod_python.so
<Location /trac>
   SetHandler mod_python
   PythonHandler trac.web.modpython_frontend
   PythonOption TracEnvParentDir /home/trac
   SetEnv PYTHON_EGG_CACHE /home/trac/.egg-cache
</Location>

<LocationMatch "/[[:alnum:]]+/login">
   AuthType Basic
   AuthName "trac"
   AuthUserFile /var/www/.htpasswd
   Require valid-user
</LocationMatch>
###################################################

mkdir /home/trac
chown -R apache.apache /home/trac

su -s /bin/bash - apache
cd /home/trac
trac-admin /home/trac/test initenv
>>Project Name [My Project]> TEST
>>Repository type [svn]>
>>Path to repository [/path/to/repos]> /home/svn/repos

ブラウザから http://yourdoman/trac を確認

<管理画面を見れるようにする>
cd /home/trac/test
trac-admin ./ permission add anonymous TRAC_ADMIN
logo_bannerを /home/trac/test/htdocs以下にコピペ
exit

MySQL Spiderエンジンを使ってみた。〜データベースシャーディング(sharding)とは〜

もしかしたら使うかもしれないので調べてみた。

shard

  • 日本語に訳すと(ガラスや貝殻の) 「破片」といったような意味
  • データベースをshardに分解して複数のサーバに分散して運用するのがDB sharding
  • データベースパーティショニングとも言えるのかしら?
    • 単一のサーバのDBテーブルを複数のファイルに分割するのをパーティショニングとも呼ぶが、「パーティショニング」という言葉を使ってDB shardingのことを言っているブログ等もちらほら見かけます。
  • より突っ込んでみたい人は「shared nothing」でぐぐってネ。

なぜデータベースを分散処理するのか


横軸にはサービス運用開始からの時間経過を設定し、縦軸には「DBの応答時間」、「DBへの問い合わせ数」、「DBサイズ」の各数量をとります。サービスが順調に利用されていっているものとし、「DBサイズ」や「DBへの問い合わせ数」は時間経過とともに線形に増えていきますが、「DBの応答時間」はある時を境に指数的に増えていくことが想定できます。要するにDBのレスポンスは急激に悪化することがあります。原因としては以下が考えられます。

  • メモリに乗り切らないデータはディスクを読み込んで探さなければならない。
  • ディスクからデータを探すのとメモリからデータを探すのでは速度差に10の5乗〜6乗近くの差が出る(10万倍〜100万倍)

スケールアップ・スケールアウトという枠組みで解決法が考えられますが、今回の主題から言うと次のようになります。

スケールアウト戦略

DB shardingは単一のサーバでの処理能力が著しく落ちない程度にデータの大きさを調節する(データベースサイズをスケールする)ことでDBの性能を最大限に引き出せるようにする手法です。MySQLのSpiderエンジンは手軽にDBを分散処理できるようにしてくれています。
これに対してレプリケーションはDBへの参照問い合わせをスケールしてくれていると言えるでしょう。

Spiderを使った時のイメージ


上記の図はサーバ4台の構成で、黄色のサーバはアプリケーションサーバでDBサーバも兼ねています。が、DBのデータの実体は各オレンジ色のDBサーバに分散されて黄色のサーバはデータは保持していません。

実際に使ってみる前に

  • テーブルパーティショニングについて
  • MySQL Sandboxについて

テーブルパーティショニング

  • 5.1からサポートされた(水平パーティショニングのみ)。
  • 水平パーティショニングと垂直パーティショニング
    • 水平・・・テーブル内の異なる行(row)が異なるパーティションに割り当てられる
    • 垂直・・・テーブル内の異なる列(column)が異なるパーティションに割り当てられる (SPIDERエンジンの開発者がVPエンジンなるものも開発している。)
  • 分割方法

RANGE分割

下記のようなCREATE文を発行することで通常は一つのMYDファイルに保存されるところをp0.MYD, p1.MYD, p2.MYD, p3.MYDのように4つのファイルに分割してデータを保存することができるようになります。ちなみにこのSQL文だけだと単に同一のサーバにファイル分割しただけとなります。同一のサーバでも異なるHDに分割できるようにすればデータの読み込み速度も上がるのかなと思ったりしますが、単純にファイル分割しただけで何がどう変わるとかまでは調べておりません。。

CREATE TABLE t_range (
  name text,
  hight int,
  birthday date
) ENGINE=MYISAM
PARTITION BY RANGE(hight) (
  PARTITION p0 VALUES LESS THAN (150),
  PARTITION p1 VALUES LESS THAN (170),
  PARTITION p2 VALUES LESS THAN (180),
  PARTITION p3 VALUES LESS THAN MAXVALUE
)

LIST分割

CREATE TABLE t_list (
  name text,
  hight int,
  birthday date
) ENGINE=MYISAM
PARTITION BY LIST(MONTH(birthday)) (
  PARTITION p0 VALUES IN (1, 2, 3),
  PARTITION p1 VALUES IN (4, 5, 6),
  PARTITION p2 VALUES IN (7, 8, 9),
  PARTITION p3 VALUES IN (10, 11, 12)
);

HASH分割

CREATE TABLE t_hash (
  name text,
  hight int,
  birthday date
) ENGINE=MYISAM
PARTITION BY HASH(MONTH(birthday)) PARTITIONS 4;

KEY分割

CREATE TABLE t_key (
  id int,  
  name text,  
  hight int,
  birthday date
) ENGINE=MYISAM 
PARTITION BY KEY(id) PARTITIONS 4;

MySQL Sandboxとは

Spiderを実際に使ってみる。

  1. Spiderエンジンのインストール
  2. MySQL::Sandboxでの環境構築
  3. Spiderエンジンのインストール
  4. Spiderテーブルとリンク先のテーブルを作成
  5. データ操作してみる

Spiderエンジンのインストール

  • 5.5系は./configureの代わりにcmakeを使うようになった。PREFIXを指定して自分のホームディレクトリにインストール
    • tar zxvf mysql-5.5.14-spider-2.26-vp-0.15-hs-1.0.tgz
    • cd mysql-5.5.14-spider-2.26-vp-0.15-hs-1.0
    • cmake . -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DCMAKE_INSTALL_PREFIX=/home/you/mysql_spider_5.5.14
    • make
    • make install

MySQL::Sandboxでの環境構築

  • /home/opt/mysql以下にインストールした各バージョンのエイリアスを設置
  • make_multiple_sandbox --how_many_nodes=4 5.5.14-spider

Spiderエンジンのインストール

  • spider同梱のMySQLのソースにscripts/install_spider.sqlがあるのでそれをspiderエンジンを使いたいノードにインストールします。
    • cd ~/sandbox/multi_msb_5_5_14
    • ./n1 < install_spider.sql
  • 上記のスクリプトを実行するとmysqlデータベース以下にspiderの管理テーブルが生成され、spiderエンジンが使用可能となる。
  • ./use_all -e 'create database spider_test'などとしてテスト用のDBを作っておくといいかも。

Spiderテーブルとリンク先のテーブルを作成

mysqlのドキュメントページからダウンロード(http://dev.mysql.com/doc/index-other.html)できるemployeeデータベースを使ってサンプルを作成する。

  • spiderノードにはspiderテーブルを作成する
    • ./n1 spider_test < table_for_spider_node.sql
CREATE TABLE salaries (
  emp_no int(11) NOT NULL,
  salary int(11) NOT NULL,
  from_date date NOT NULL,
  to_date date NOT NULL,
  PRIMARY KEY (emp_no,from_date),
  KEY emp_no (emp_no)
) ENGINE=SPIDER DEFAULT CHARSET=utf8
PARTITION BY HASH(emp_no) (
  PARTITION p1 COMMENT 'user "msandbox", password "msandbox",  host "127.0.0.1", port "13916", table "salaries",',
  PARTITION p2 COMMENT 'user "msandbox", password "msandbox",  host "127.0.0.1", port "13917", table "salaries",',
  PARTITION p3 COMMENT 'user "msandbox", password "msandbox",  host "127.0.0.1", port "13918", table "salaries",'
);
  • node2〜node4までは通常のテーブルをインストールする
    • ./n2 spider_test < table_for_normal_node.sql
CREATE TABLE salaries (
    emp_no int(11) NOT NULL,
    salary int(11) NOT NULL,
    from_date date NOT NULL,
    to_date date NOT NULL,
    PRIMARY KEY (emp_no,from_date),
    KEY emp_no (emp_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
  • 先ほどのサンプルのemployeesデータベースを解凍し、salariesテーブルをspiderノードにロードしてきちんと分散処理されているかを確認します。

データ操作してみる

  • ./n1 spider_test < load_salaries.dump (topで様子を見ると並列処理している様子がみれて面白いかなと)
$ ./n1 spider_test -e 'select count(*) from salaries';
                      • +
count(*)
                      • +
2844047
                      • +
$ ./use_all -e 'use spider_test; select count(*) from salaries';
# server: 1: 
count(*)
2844047
# server: 2: 
count(*)
947747
# server: 3: 
count(*)
947505
# server: 4: 
count(*)
948795
  • 既存のテーブルをshardingしたいという場合もspiderを使うと簡単に処理できます。
    • 先ほどは直接spiderノードにデータをロードしましたが、既存のテーブルにspiderのリンクを貼ってそこからデータを直接引っ張ってくることもできます。下記のようなSQLで一発です。
insert into salaries select * from salaries_datasource


  • 集約関数は機能する?
    • 単一のサーバにロードされたsalariesテーブルからデータを間引いてサラリーの平均を確認し、spiderで運用しているデータにも同様の操作をしてきちんと数値が一致するかを確認します。
まずは単一のサーバにロードされたsalariesテーブル
mysql > desc salaries;
 +-----------+---------+------+-----+---------+-------+
 | Field     | Type    | Null | Key | Default | Extra |
 +-----------+---------+------+-----+---------+-------+
 | emp_no    | int(11) | NO   | PRI | NULL    |       |
 | salary    | int(11) | NO   |     | NULL    |       |
 | from_date | date    | NO   | PRI | NULL    |       |
 | to_date   | date    | NO   |     | NULL    |       |
 +-----------+---------+------+-----+---------+-------+
 4 rows in set (0.00 sec)

mysql > select AVG(salary) from salaries;
                            • +
AVG(salary)
                            • +
63810.7448
                            • +
1 row in set (1.17 sec) mysql > select count(*) from salaries;
                      • +
count(*)
                      • +
2844047
                      • +
1 row in set (0.74 sec) mysql > select count(*) from salaries where emp_no in (10011, 10012, 10013);
                      • +
count(*)
                      • +
34
                      • +
mysql > delete from salaries where emp_no in (10011, 10012, 10013); Query OK, 34 rows affected (0.01 sec) mysql > select AVG(salary) from salaries;
                            • +
AVG(salary)
                            • +
63810.9068
                            • +
mysql > select count(*) from salaries;
                      • +
count(*)
                      • +
2844013
                      • +
1 row in set (0.87 sec)
次いでspiderテーブル
node1 > select AVG(salary) from salaries;
                            • +
AVG(salary)
                            • +
63810.7448
                            • +
1 row in set (3.33 sec) node1 > select count(*) from salaries where emp_no in (10011, 10012, 10013);
                      • +
count(*)
                      • +
34
                      • +
node1 > delete from salaries where emp_no in (10011, 10012, 10013); Query OK, 34 rows affected (0.02 sec) node1 > select AVG(salary) from salaries;
                            • +
AVG(salary)
                            • +
63810.9068
                            • +
1 row in set (3.31 sec)

おわりに

とまあ以上のような感じで検証しました。参考にさせてもらったのは主にhttp://nippondanji.blogspot.com/2010/04/spider.htmlでした。
あと、レプリケーションして使いたいとか、そういうケースも調べてみたのでまた別エントリで書きたいと思います。
自宅サーバ派の自分としては結構使い道があるなと思ったりもしています。