まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

2014/07

7月22~25日の4日間、大阪のインテックス大阪にて「下水道展2014」が開催され、「日本全国おもしろマンホール」を紹介するコーナーでマンホールマップを出展させていただく機会がありました。 関係者の皆様、貴重な機会をありがとうございました。


私が出展させていただいたのはGKP(下水道広報プラットフォーム)様の企画による「スイスイ下水道研究所」内の一企画として、夏休みを迎えた子供達に日本全国のいろいろなマンホールを紹介して知ってもらおう、というコーナーでした。PCを3台用意いただいて、マンホールマップだけではないのですが、オンライン上のマンホール紹介サイトやサービスを使って紹介し、実際にPC上での操作もしてもらおう、と考えていました。その参加報告を兼ねて、2日間に感じたことを書いてみます。

(1) 想定以上の人気だった

理由(作戦?)は後述しますが、私が参加した23、24日のマンホールマップはかなりの稼働率だったと思います。PC3台はほぼ常に子供達で埋まり、時間帯によっては順番待ちの列もできていました。事務局の皆様にお手伝いもいただきましたが、かなり大変でした。想定以上の人気で自分の休む時間が取れず、22日はお昼抜き、23日は表彰式もあったので、昼食と合わせて60分くらい場を離れましたが、2日間ほぼずっと出展場所で子供相手の説明をしていました。普段の仕事とは全く違う2日間だったこともありますが、正直かなり疲れました。でも嬉しい疲れです。

(中には一人でストリートビューまで操る子供達も!)
写真 3



(2) やはり子供にはゲーム(笑)

自画自賛ですが、今回の人気については「作戦勝ち」だと思っています(苦笑)。

実は昨年にもこのイベントに出展したのですが、その際は「小学校低学年向け」のつもりで「花」や「アニメキャラクター」などのカテゴリ毎に集めて作成したマンホールマップの子供向けスペシャルコンテンツを用意していました。が、これはあまり興味を持ってもらえず、ほとんど使うこともありませんでした。

いろいろなマンホール(http://manholemap.juge.me/childbuttons.htm)
2014072501


昨年のこの失敗を反省して、今年はマンホールマップのゲーム性を前面に出して紹介しました。小学校低学年以下っぽいお子様には「マンホめくり(神経衰弱)」を、それ以上っぽいお子様には「スライドパズル」を見せて、挑戦してもらいました。
2014073001


スライドパズルは我々も昔よく遊んだ「15ゲーム」ですが、これは大人でも結構難しいと思ってます。1、2、3まではほぼ誰でも揃えることができますが、4では急に難易度が上がり、自力で揃えることが出来る子はだいたい2割程度でした。この絶妙な難易度がハマってくれる理由だったと思います。
写真 2

(実は大きいお友達にも人気だった)
写真 1


更に、飽きちゃった子向けに「マンホールさめがめ」も提供しました。これは2日間で解けた子はたった1人、というものでかなり難しかったと思います。でもみんな自分なりに戦略を考えながら挑戦してくれました。こういうアルゴリズム的な発想にも挑戦できるのは未来のシステムエンジニア候補だと思ってます。

と、マンホールマップにはこのような遊び機能をいくつか用意しておいたのですが、それが今回役立つ形になりました。 これは来年も使えるな(笑)、さめがめは少し難易度を下げておくように改良しよう。


(3) マンホールマップがGKP広報大賞・電子媒体部門賞をいただきました!

最後は雑感というか、宣伝というか(笑)、ご報告です。

この度、マンホールマップが第二回 GKP 広報大賞・電子媒体部門賞をいただくことができました。マンホールマップがこのような形で一般の表彰をいただいたのはこれが初めてでした。 今回の下水道展3日目(24日)にその授賞式があり、ユーザーの皆様を代表する形で私が参加させていただきました。
写真



マンホールマップは2010年の開発開始から丸4年を迎え、今でも多くの「マンホール蓋愛好家」の皆さんからの投稿によって日々成長しています。「マンホール蓋愛好家」と書きましたが、その意味は4年前とは全く異なり、当時は「一部のマニア」の領域でしたが、今では「蓋女(ふたじょ)」なる言葉が生まれるほど老若男女に広がり、その発見報告の場の1つとしてマンホールマップを選んでいただけているのだと思っています。 その結果、多くのマンホール蓋ファンの層を広げることが出来、それを今回の受賞理由の1つに挙げていただきました。

今回の受賞は日頃利用いただいているユーザーの皆様のおかげであり、またサービスの向上に役立つアドバイスをいただいた諸先輩方のおかげでもあります。

また昨年に引き続き、今年も出展の機会をご用意いただいたGKP様、事務局として色々手配いただいた日経ピーアール様、下水道展会場で「オッチャン」呼ばわりしてくれた子供達、そしてたまに意識不明になりながらも自宅でずっと稼働を続けてくれているサーバー、本当に本当にありがとうございました。


下水道展、来年は東京で開催される予定です。大人でも半日飽きずに勉強できるエンターテイメントだと思っています。次回参加させていただけるかどうかは分からないのですが、個人的にはまた出展する形で参加したいと思っています。来年の夏、皆様と下水道展でお会いできることを楽しみにしております。
写真(1)


 

CentOS にオープンソースな Microsoft Windows レイヤーアプリである Wine を導入する手順を紹介します。なお CentOS 6.5 64bit 環境を前提とします。

また Wine は各種 UNIX 環境向けに開発されており、Ubuntu ベースの Linux に対してはバイナリも提供されています。ただ今回は自分がメインで使っている CentOS に、ソースからビルドして導入する手順を紹介します。


まず、CentOS 側に必要なモジュールをあらかじめ導入しておきます(青字はインストールしている内容の説明であって、コマンドとしては入力しません):
# yum groupinstall "X Window System" "GNOME Desktop Environment" GNOMEウィンドウ環境
# yum groupinstall "Development Tools" コンパイル環境一式
# yum install libX11-devel freetype-devel 必須ライブラリ

次に Wine の最新版ソースコードを入手します。SourceForge を参照して最も新しいバージョンを確認し、そのアーカイブモジュールを wget で入手します。なお 2014.7.10 時点では安定バージョンの最新版は 1.6.2, 開発版の最新版は 1.7.21 でした。お好きな方をどうぞ(以下は1.7.21 での例):
# cd /usr/local/src
# wget http://citylan.dl.sourceforge.net/project/wine/Source/wine-1.7.21.tar.bz2
# tar xvf wine-1.7.21.tar.bz2
# rm wine-1.7.21.tar.bz2

ビルドして、インストールします:
# cd wine-1.7.21
# ./configure --enable-win64
# make
# make install

なお、上記は 64bit 環境用のコマンドです。参考程度ですが、32bit 環境の場合のコマンドは以下になります:
# cd wine-1.7.21
# ./tools/wineinstall

また 64bit 環境用では Wine の実行コマンド名が wine64 となります。このままだといくつか不具合が発生するので 32bit 環境と同じ wine というファイル名でシンボリックリンクを作成しておきます:
# cd /usr/local/bin
# ln -s wine64 wine

また、日本語環境で Wine を利用する場合、このまま次の初期設定コマンドを実行するとフォントの関係で文字化けしてしまいます。この文字化けを回避するためにフォントをコピーして用意しておきます:
# cp -R /usr/share/fonts/ipa-* ~/.wine/drive_c/windows/Fonts/


これで Wine のインストールができました。最後に(SSHなどのコマンドライン端末ではなく) GNOME 環境からWine の設定コマンドを実行して、初期設定を実行します。特に何かを変更するわけではなく、設定内容の確認を行うことと、上記のコマンドによって(文字化けなく)正しく日本語が表示されていることを確認してください:
# winecfg
2014071001


これで Wine の導入は完了です。例えば「メモ帳」を起動する場合はコマンドラインからこんな命令を実行します(最後の & を付けずに実行した場合は、起動したアプリが終了するまでコマンドラインはビジー状態になります):
# wine notepad &
2014071002


ちなみに、上記のコマンドは以下のコマンドと同じ意味です:
# wine notepad.exe &
# wine c:\\windows\\notepad.exe &

同様に、「コントロールボックス」や「レジストリエディタ」を起動するにはそれぞれこんな感じで:
# wine control & コントロールボックス
# wine regedit & レジストリエディタ
2014071003


理論上は Windows 用のアプリやインストーラーが入手できれば、それを指定して、
# wine XXX.exe &
のように実行することで CentOS 上で動くようになります。


でも上で「理論上は」と書いたように、実際は動かないアプリが多いです。動いても想定外の見栄えになることもあります。

うーん、ここで昔の色んなアプリが動いたら面白かったんだけどなあ・・・・
 

最近のマイブームの1つになっている、全文検索エンジン ElasticSearch に MySQL のデータを取り込んで、MySQL データベースの全文検索エンジンとして ElasticSearch を使う手順の紹介です。
2014070401


まず検索エンジンである ElasticSearch を導入します。日本語形態素解析エンジンである Kuromoji まで含めてのインストール手順を別エントリで紹介しているので、こちらを参照ください:
ElasticSearch に Kuromoji プラグインを導入する


また取り込み先である MySQL サーバーについても環境は構築済みであると仮定します。こちらの構築手順についても、こちらのエントリを参照ください:
CentOS に MySQL をインストール/セットアップする

なお、自分個人的には MySQL ではなく MariaDB を使って同じことをできているので、以下の内容に関しては MariaDB でも同様に可能だと思っています。


さて、ElasticSearch に MySQL データを取り込むための準備として ElasticSearch 自体のインストール後に以下のステップを行う必要があります:
1. (MySQL クライアントと)JDBC ドライバの導入
2. JDBC River プラグインのインストール


最終的には ElasticSearch の River プラグインと呼ばれる拡張機能を使って MySQL からのデータ取り込みを行います。このプラグインの動作に必要な MySQL JDBC ドライバを先にインストールしておく、というステップになります。


まず、これは必須ではありませんが、あると確認に便利なので MySQL のクライアント環境を ElasticSearch サーバー内に構築しておきます。MySQL サーバーに接続する機能があればいいので、MySQL サーバー機能は不要で、クライアント機能だけが必要、ということになります。もし MySQL サーバーと ElasticSearch サーバーが同じサーバーだとすると、既に MySQL クライアント環境は導入済みだと思うので、インストールは不要です。 MySQL クライアントが導入されていない場合は以下のコマンドで MySQL クライアントをインストールします:
# yum install mysql
# vi /etc/my.cnf
  :
(以下の2行を追加)
[mysql]
default-character-set=utf8

MySQL クライアントが導入できた所で、取り込み元の MySQL サーバーへ接続してみます。仮に今回取り込むデータの内容が以下であると仮定します:
 MySQL サーバー: mysql.mylocal.com
 ユーザー名: username
 パスワード: password
 データベース名: mydb
 取り込む内容: samples テーブル


実際に MySQL クライアントで目的のデータベースにアクセスしてみます。ここまでが出来るようであればファイアウォールなども含めて接続準備ができているといえます:
# mysql -h mysql.mylocal.com -u username -ppassword mydb
> select * from samples;
:
:
(samples テーブルの内容)
:
: > quit


次に MySQL サーバーへ Java 環境から接続するための JDBC ドライバー(MySQL Connector/J)を導入します。ドライバー自体はこちらのサイトからダウンロードできます:
MySQL :: Download Connector/J 


ダウンロードしたファイルを展開して JAR ファイルを取り出し、/usr/share/java にコピーします:
# unzip mysql-connector-java-5.1.30.zip
# cd mysql-connector-java-5.1.30
# cp mysql-connector-java-5.1.30-bin.jar /usr/share/java

環境変数 CLASSPATH に、この JAR ファイルを追加します:
# vi /etc/bashrc
  :
(以下の1行を追加)
export CLASSPATH=$CLASSPATH:/usr/share/java/mysql-connector-java-5.1.30-bin.jar
 

# source /etc/bashrc

JDBC ドライバの準備が出来た所で River プラグインをインストールします。リポジトリを確認したところ、2014/07/01 時点での最新バージョンは 1.2.1.1 だったので、このバージョンを指定して導入します:
# /usr/share/elasticsearch/bin/plugin --install jdbc --url http://xbib.org/repository/org/xbib/elasticsearch/plugin/elasticsearch-river-jdbc/1.2.1.1/elasticsearch-river-jdbc-1.2.1.1-plugin.zip
# cp /usr/share/java/mysql-connector-java-5.1.30-bin.jar /usr/share/elasticsearch/plugins/jdbc/

これで必要なソフトウェアは揃いました。では実際に MySQL からデータを取り込んでみましょう

まず ElasticSearch 側に kuromoji を使った検索インデックス(kuromoji_sample)を作成します。インデックスの作成については別エントリでも紹介しましたが、まだこの内容を実行していない場合は以下のコマンドを実行します(コマンドは黒字部分で、青字はレスポンスを表しています):
# curl -XPUT http://localhost:9200/kuromoji_sample -d '{ "index": { "analysis": { "tokenizer": { "kuromoji_user_dict" : { "type":"kuromoji_tokenizer" } }, "analyzer": { "analyzer": { "type":"custom", "tokenizer": "kuromoji_user_dict" } } } } }'
{"acknowledged":true}

次に River を使って、作成した kuromoji_sample インデックスに MySQL データベースサーバーからデータを取り込みます:
# curl -XPUT http://localhost:9200/_river/my_jdbc_river/_meta -d '{ "type": "jdbc", "jdbc": { "url": "jdbc:mysql://mysql.mylocal.com:3306/mydb", "user": "username", "password": "password", "sql": "select * from samples", "index": "kuromoji_sample", "type": "samples" } }'
{"_index": "_river", "_type": "my_jdbc_river", "_id": "_meta", "_version": 1, "created": true}

上記入力パラメータ(JSON)の中で選択(select)の SQL を発行しています。この SQL の実行結果が ElasticSearch に取り込まれることになります。

取り込みができたら、最後に検索してみます。この例では取り込んだデータの name フィールドに「ほげほげ」が含まれているデータを検索しています:
# curl -XPOST http://localhost:9200/kuromoji_sample/_search?pretty -d '{ "query": { "query_string": { "query": "name:ほげほげ" } } }'
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 29,
    "max_score" : 3.687642,
    "hits" : [ {
      "_index" : "kuromoji_sample",
      "_id" : "XXXXXXXXXXXXXX",
      "_score" : 3.687642,
      "_source":{"id":"1234","name":"ほげほげ"}
    }, {
      "_index" : "kuromoji_sample",
        :
    } ]
  }
}

こんな感じで実現できました。

MySQL の like 節を使った単純検索をしていた頃と比べると、以前は localhost 内の MySQL に対して検索していたのでネットワークによる遅延はほとんどなかったはずで、今回作ってみた環境はリモートの ElasticSearch 環境にアクセスしているので、ネットワークの遅延影響がでるはずです。

にも関わらず、検索パフォーマンスは 10 倍程度になりました。これはでかい!

ElasticSearch の検索パターンやその API 実行方法についてはいずれまたプログに書く予定です。


 

(注 この記事は古くなったので、新しい記事をこちらに記載しています)
http://dotnsf.blog.jp/archives/1059206837.html


全文検索エンジン ElasticSearch に日本語形態素解析ソフトウェアである Kuromoji のプラグインを導入して、日本語全文検索環境を構築します。


まずは ElasticSearch をインストールします。ElasticSearch のインストールそのものの手順については以前のエントリを参照してください:
ElasticSearch を導入して CouchBase サーバーの全文検索を行う


ElasticSearch の導入ができたら、この段階で動作確認をしておきます。まずはデータを登録します(青字は実行結果です):
# curl -XPUT http://localhost:9200/mytest/test/1 -d '{ "title":"memo", "text":"ほげほげ" }'

{"_index":"mytest","_type":"test","_id":"1","_version":1,"created":true}


id = 1 のデータとして、title = "memo", text = "ほげほげ" の JSON データを Index = mytest, Type = test で登録しました。次にこのデータを GET メソッドで検索します:
# curl -XGET http://localhost:9200/mytest/test/_search -d '{ "query": { "match": { "title":"memo" } } }'
{"took":54,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":1,"max_score":0.30685282,"hits":[{"_index":"mytest","_type":"test","_id":"1","_score":0.30685282,"_source":{"title":"memo","text":"ほげほげ"}}]}}


title = "memo" のデータを mytest/test で検索した所、"text" = "ほげほげ" の期待通りの結果が得られました!(ただし、この時点ではまだ Kuromoji を使っていません) 動作確認ができたので、このデータを削除しておきます:
# curl -XDELETE http://localhost:9200/mytest/test/1
{"found":true,"_index":"mytest","_type":"test","_id":"1","_version":1}

ElasticSearch が正しく導入できて、動作も確認できた所で Kuromoji プラグインを導入して ElasticSearch を再起動します:
# /usr/share/elasticsearch/bin/plugin --install elasticsearch/elasticsearch-analysis-kuromoji/2.2.0
# /etc/init.d/elasticsearch restart

そして Kuromoji を有効にして、再度簡単な動作確認をしてみます。まずは新しいインデックスを作成して、Kuromoji をアナライザとして指定します:
# curl -XPUT http://localhost:9200/kuromoji_sample -d '{ "index": { "analysis": { "tokenizer": { "kuromoji_user_dict" : { "type":"kuromoji_tokenizer" } }, "analyzer": { "analyzer": { "type":"custom", "tokenizer": "kuromoji_user_dict" } } } } }'
{"acknowledged":true}

この新しく作成したインデックスに対して日本語文字列を POST して、形態素解析が有効になっているかどうかを確認します:
# curl -XPOST 'http://localhost:9200/kuromoji_sample/_analyze?analyzer=analyzer&pretty' -d '東京スカイツリー'
{
"tokens":[{
"token":"東京",
"start_offset":0,
"end_offset":2,
"type":"word",
"position":1
},{
"token":"スカイ",
"start_offset":2,
"end_offset":5,
"type":"word",
"position":2
},{
"token":"ツリー",
"start_offset":5,
"end_offset":8,
"type":"word",
"position":3
}]
}

「東京スカイツリー」が3つの単語に分割できていることがわかります。これが kuromoji による拡張機能です。


この kuromoji を使ったインデックスを ElasticSearch のデフォルトアナライザとして指定し、ElasticSearch を再起動します:
# vi /etc/elasticsearch/elasticsearch.yml
  :
index.analysis.analyzer.default.type: custom
index.analysis.analyzer.default.tokenizer: kuromoji_user_dict
  :
# /etc/init.d/elasticsearch restart

では改めて2つの日本語データを登録しておきます:
# curl -XPUT http://localhost:9200/kuromoji_sample/test/1 -d '{ "title":"メモ1", "text":"カレーは飲み物" }'
{"_index":"kuromoji_sample","_type":"test","_id":"1","_version":1,"created":true}

# curl -XPUT http://localhost:9200/kuromoji_sample/test/2 -d '{ "title":"メモ2", "text":"カレーライスは和食" }' {"_index":"kuromoji_sample","_type":"test","_id":"2","_version":1,"created":true}

そして、まずは「カレー」で検索してみます:
# curl -XGET http://localhost:9200/kuromoji_sample/test/_search -d '{"query":{"match":{"text":"カレー"}}}'
{
"took":67,"timed_out":false,"_shards":{
"total":5,"successful":5,"failed":0
},"hits":{
"total":1,"max_score":0.15342641,"hits":[{
"_index":"kuromoji_sample","_type":"test","_id":"1","_score":0.15342641,"_source":{
"title":"メモ1","text":"カレーは飲み物"
}
}]
}
}

正しく「カレーは飲み物」のデータがヒットすることが確認できます。気づいていただきたいのは、この時に2番目の「カレーライスは和食」のデータがヒットしていないということです。Kuromoji は「カレー」と「ライス」ではなく、「カレーライス」という単語を認識しているのだと想像できます。

次に「カレーは」で検索します:
# curl -XGET http://localhost:9200/kuromoji_sample/test/_search -d '{"query":{"match":{"text":"カレーは"}}}'
{
"took":8,"timed_out":false,"_shards":{
"total":5,"successful":5,"failed":0
},"hits":{
"total":2,"max_score":0.2169777,"hits":[{
"_index":"kuromoji_sample","_type":"test","_id":"1",
"_score":0.2169777,"_source":{
"title":"メモ1","text":"カレーは飲み物"
}},{
"_index":"kuromoji_sample","_type":"test","_id":"2",
"_score":0.02250402,"_source":{
"title":"メモ2","text":"カレーライスは和食"
}}]
}
}

2つのデータがヒットしています。ここでは "_score" の値に注目します。

_id = 1 のデータでは「カレーは飲み物」という7文字のうち4文字が一致しているため、そのスコアが高くなっています。一方、_id = 2 のデータでは「カレーライスは和食」という9文字のうち「カレー」という3文字と「は」という1文字しか一致していないこともあり、そのスコアが低くなっています。その結果、前者の方がより高い精度で一致していると判断されていることになります。これによってスコア付きの日本語検索も有効に行われていることが分かります。


結構簡単に日本語検索エンジンが作れてしまいました。ElasticSearch は REST でデータの読み書きができるし、Input/Output のフォーマットが JSON なので、プログラマ的にも便利で楽しそうな検索エンジンです。



 

このページのトップヘ