ひ孫

犬のこととか書いていきたい

solrでmultiValueなフィールドのunigram検索

solr3.3での話。
solrで単純にunigramがやりたいとなると

  <fieldType name="unigram" class="solr.TextField">
      <tokenizer class="solr.KeywordTokenizerFactory" />
      <!-- ↓NGram -->
      <filter class="solr.NGramFilterFactory" maxGramSize="1" minGramSize="1" />
  </fieldType>

のようにフィールドを定義するとさらっとできてしまう。
これが一つしかフィールドを持ってないようなフィールドであればそんなに問題ないのだけど
multiValuedなフィールドの場合だとちょっと面倒な問題が起こる。
例えば

 <fields>
    <field name="id"    type="int" indexed="true" stored="true" required="true" /> 
    <field name="title" type="unigram" indexed="true" stored="true" required="true" /> 
    <field name="tags"  type="unigram" indexed="true" stored="true" multiValued="ture" /> 
 </field>

というスキーマだったとして

 <doc>
  <int name="id">1</int>
  <str name="title">hoge</str>
  <arr name="tags">
    <str>バス</str>
    <str>ガス</str>
    <str>爆発</str>
  </arr>
 </doc>

的なデータが入っていたとする。

本題。例えば上記のような状態で「スガ」と検索するとどうなるか。
ひっかかってしまうのである。
multiValuedなバス、ガス、爆発という単語がひとつなぎに「バスガス爆発」という扱いでunigramされてしまう*1
この問題、bigramだと起きない。bigramだと「バス」「ス 」「ガス」「ス 」のようにインデックスされる。

じゃあbigramとunigramどっちも用意して検索語の長さで検索を切り替えればいいじゃん!*2
という解決もあるけどそれなりに文章量が多いとそれなりにインデックスのデータサイズが膨れちまうんでないかという不安もある。
なので一旦今回はbigramを使わないパターンでやってみる。

早速解決策

  <fieldType name="unigram2" class="solr.TextField">
    <analyzer type="index">
      <tokenizer class="solr.KeywordTokenizerFactory" />
      <filter class="solr.NGramFilterFactory" maxGramSize="1" minGramSize="1" />
      <!-- ↓各値の最後にスペースを入れる -->
      <charFilter class="solr.PatternReplaceCharFilterFactory" pattern="(.)$" replacement="$1 "/>
    </analyzer>
    <analyzer type="query">
      <tokenizer class="solr.KeywordTokenizerFactory" />
      <filter class="solr.NGramFilterFactory" maxGramSize="1" minGramSize="1" />
    </analyzer>
  </fieldType>

随分複雑になったけど以下詳細。
まずanalyzerをtype="index"とtype="query"に分けている。
indexはデータを取り入れてインデックス化するときに使われるフィールドの解析方法
queryはそのフィールドに対して検索を行うときに使うフィールドの解析方法
で、queryの方は従来通り変えていない。
変えているのはindex時の挙動。
indexするときにPatternReplaceCharFilterFactoryという正規表現による置換を行うルールによって
「バス 」「ガス 」「爆発 」*3のようにデータが入る。
こうするとひとつなぎにされても「バス ガス 爆発」となり「スガ」はひっかからなくなる。わーい。

おしまい。

*1:これがバグなのか仕様なのかはわからない。bigramで起きないとこを見るとなんとなくバグな気もするけど・・・

*2:http://www.slideshare.net/TakahiroMatsumiya/pixiv-solr-8650102あたりを参照。天下のpixivさんが2つ分ける手法とって問題ないならインデックスサイズなんて気にするもんじゃないかもしんないですけどね。はい。

*3:見やすいように全角にしているけどデータにはもちろん半角が入る

はてなブログからはてなダイアリーに戻した

はてなブログに移設してしばらく運用してみたけど、どうもgoogle検索さんと相性が悪いのか全然記事を拾ってくれないらしい。
別に個人のブログで検索最適性なんてそこまで気にするものかって感じもあるけど
同じ記事でもダイアリーがひっかかってはてなブログのほうは全く出てこないのはどうも気になる。

SEOはかじった程度なのでよくしらんけどd.hatena.ne.jpがすごいSEO力を持ってて独自にするとほとんどその効力がなくなってしまうのかも。
このブログをアホほどオープンしてほっといた効力があるのかもしらんけど。


しかしはてなダイアリーは扱いが難しいですね。
大見出しガンガン使いたいけど使っちゃうと別記事になるとかすごく苦戦しますね。

Redmineでsvnのリポジトリブラウザで空白を無視して差分表示する

redmineリポジトリ。コードレビュープラグインなんかを使っているとよく使うのだが
空白差分がさっくり無視できない。

とりあえず調べてみると
http://www.redmine.org/issues/1476

とかがあった。
しかしどうも中身のソースをいじるのはいろいろとあとで管理が微妙だと思ったので
subversion側の設定をいじってみる方向で解決してみた

~/.subversion/config

[helpers]
diff-cmd = diff 
diff-extensions = -u -w -p -b

diff-extensionsはdiffを行う歳に使われるオプションみたいな感じで実際には

svn diff -x -u -w -p -b

みたいなコマンドが投げられるらしい。
ただしdiff-extensionsはsubversion1.6だとまだ無いらしく動かなかった。
なのでsvnは1.7にする必要があった。


もし色々な都合で単純にアップデートできない環境だったりしたら別途インストールして
redmineのconfig/configuration.ymlで利用するsvnを指定してやるといいかもしれない。

FuelPHP2.0への不安

結構よさそうなので気になっているFuelPHP。
でも2.0(https://github.com/fuelphp)を見るとなんかちょっと雲行き怖くなる。

ファイル名のルール変更(lowercase揃えじゃなくなる)

とりあえず気になるのがこれ。
今のコーディングルール(http://docs.fuelphp.com/general/coding_standards.html)だと

File Naming
All file names must be all lower case. No exceptions.

つまり例外なくぜーんぶ小文字にしろってなっている。しかし
https://github.com/fuelphp/fuelphp/wiki/Coding-Standards をみると

File name

The casing of filenames and directory names inside the classes directory should match the classnames/namespaces exactly. All other files and directories are fully lowercase by convention but will follow the casing you configure/input.

classesディレクトリ下のファイルは名前空間にそろえて大文字にせよ。それ以外は慣例に従って小文字にせよってことらしい

全部小文字ってルールは気に入っていた部分なのにだいぶ残念。
多分使い分けるとき忘れてしまいそう。あとwindowsの環境で動作させたかったりすると大文字小文字の問題が出てウワーってなりそう。
PSR-0というautoloader用の規約のためっぽいのでそれに慣れれば気にならなくなるもんなんだろうか?

Validate::forge() → $this->app->forge('Validate')

今のfuelphpだとだいたいの関数がstaticになってて、例えばValidationの呼び出しの時は

<?php
    $validate = Validate::forge()

みたいにstaticにしているのをやめるということっぽい。

staticだとテストがやりづらいとかそういう理由っぽいけど
komodo editなんかだとうまく補完効いてくれるのがありがたかったので残念。

http://fuelphp.com/blog/2012/03/why-the-20-changes
ここを見ると
「IDE用に@var書くよ」
とか
「Validate::forge()みたいなのもLegacyとして使えるよ」(それってほぼ非サポートって意味じゃないの・・?)
ということを言っているけど、@var書かれたところでIDE側はそれがValidateなのかViewなのかとかは判断不能。
なので結果IDEにはやさしくないフレームワークになりそうな予感がしている。
(IDEに頼るのはゆとりだけど、本家のブログのコメント欄でも議論されてるから思ってるのは僕だけではなさそう)


$this->app->forge_validate()とか $this->app->forger->validater()とか
HMVCの利点生かして自分で拡張してやればちょっと使いやすくなったりするかな、とかは思っていたりする。


でもViewModelがPresenterって名前になるとかは良いのですぐにでも取り込んで欲しい。

DataImportHandlerにGETリクエストから変数を与える

Solrでデータを入れるときに使うDataImportHandler
例えばDBなんかを叩いてその結果をSolr内に格納したりできる。


サンプルとしては下記のような感じ。

<dataSource type="JdbcDataSource" driver="com.mysql.jdbc.Driver"
 url="jdbc:mysql://localhost/dbname" user="db_username" password="db_password"/>

しかしそうなるとDBのパスワードだのユーザー名とかをどこで管理する?って話しになる。
パスワードなんかはこのままコミットするわけにはいかないだろう。

ここで2つ方法がある。
1.propertyファイルを使う。
2.リクエストURLを使う

1のpropertyファイルというのはsolrcore.properiesというプロパティファイルのこと。
たとえばこれに

#solrcore.properties
dataimport.user = db_user
dataimport.pass = db_pass

と書いておくとDataImportHandlerのスキーマ

<dataSource type="JdbcDataSource" driver="com.mysql.jdbc.Driver"
 url="jdbc:mysql://localhost/dbname" 
 user="${solr.dataimport.user}" password="${solr.dataimport.pass}"/>

とすることでデータを読んでくれる。


これはこれでよいけど例えばsolrを複数台立てていたり台数を増やしたりとか言う時を考えるとちょっとだけ管理が面倒だったりする(大した手間でもないけど)

そこで活躍してくれるのが2のリクエストパラメータを使う方法。

とりあえずスキーマには

<dataSource type="JdbcDataSource" driver="com.mysql.jdbc.Driver"
 url="jdbc:mysql://localhost/dbname" 
 user="${dataimporter.request.user}" 
 password="${dataimporter.request.pass}"/>

というように書き、DataimportHandlerへ同期命令をするときに下記のようなURLを叩く

curl http://localhost:8983/solr/dataimport?command=delta-import&pass=db_pass&user=db_user

するとDataImportHandlerは&pass=db_pass&user=db_userの部分を${dataimporter.request.hoge}を解析して使ってくれる。

ただパスワードとかをhttpでやりとりするわけなのでSolrとパスワードを保存する環境が閉じたネットワーク内だったりしない場合は1のほうが多少手間があっても安全かもしれない(正直インフラのことはよくわかっていない)。

あとリクエストパラメータとして使えるので例えばsolrごとにそれぞれインポートさせる条件を変えたいなんて場合があったとしたら

<entity name="item" 
query="SELECT * FROM item WHERE type=${dataimporter.request.item_type}">

なんてことにも使えると思う。あんまり使うケースが想定できないけど。

TortoiseHGでgithubと連携するまでがのっぴきならなかったお話

最近、gitを使うようなことが増えてきたけどどうにもmsysgitは扱いづらい。
で、なんか調べるとmercurialというバージョン管理システムがある。
こっちのwindowsクライアントのTortoiseHGは結構いい感じ。
更にhg-git(http://hg-git.github.com/)というのがあるらしい。
ということでなんでもTortoiseHGでいけるようにすることに。

しかしやったら苦戦。

結果的には
1.同梱されているpageantが悪かった
2.使っていたsshキーが悪かった

ということが原因らしかった。

1.同梱されているpagentが悪かった

まず手順どおり[ファイル]->[設定]でダイアログ開いて[ファイルを開く]ボタンで開いたmercurial.iniに

[ui]
ssh = "TortoisePlink.exe" -ssh -2 -batch -C -v

と書いて、sshのキーはもともと作っていたものがあるので、それをputtygenで変換して、pageantを開いて、いざclone!

Disconnected: No supported authentication methods available

・・・・あ?

なんだこれ

おっかしーぞこれといろいろ探ってみるとpaeantのバージョンが古いっぽい。置き換えてみたら
phpfogのgitがめでたくcloneできました
おしまい

2.使っていたsshキーが悪かった

とはならない。現実は非常である。
githubの方もためしてみるかとやってみると

abort: git remote error: The remote server unexpectedly closed the connection.

いけない。githubだけ行けない。
また迷走すること数時間。

vmのubuntuの方からはいけるのに・・・
とりあえずsshまわりな感じはするのえキーを作りなおして
古いキーをPageantから排除して(←これ重要。最終的に外さないとうまくいかなかった)
としたら無事cloneできた!

ほんとうにおしまい。




pagiantに関してはよくわからんけどバージョンが0.60→0.62にあげたらなおったので同梱のをなんとかしてほしいなー。
sshキーの方はこないだgithubで脆弱性があったどうのこうのという奴と関係あるんだろうか?ぼくはくわしくないのでわかりません。


あとスタートメニューのほうのHG Workbenchからcloneすると「ハンドルが無効」ってなるバグがあるらしい。これもちょっとハマった。

FuelPHPのCoreを書き換えるようなパッケージの作り方

FuelPHPでどうも気になったLogクラス。
日別で分けてくれるけどログはだらだら1ファイルにまとめたい。
で、単純にcoreを上書きするのは面倒ではなくて

/fuel/app/classes/log.php

<?php
class Log extends \Fuel\Core\Log{
    public funtion customlog($msg){
        echo $msg;
    }
}

/fuel/app/bootstrap.php

<?php
Autoloader::add_classes(array(
    'Log' => APPPATH.'classes/log.php' //クラスを置き換える
));

ってな感じで2ファイルを変更するとめでたく\LogはFuel\Core\Logから上記のLogになる。
(参考:http://madroom-project.blogspot.jp/2011/12/fuelphpcore.html

ただ、これパッケージ化して使いまわせるようにしたいとなったときになんかちょっと厄介だったのでメモ

とりあえずパッケージにクラスを作って
/fuel/packages/customlog/classes/log.php

<?php
namespace 'Customlog';

class Log extends \Fuel\Core\Log{
    public funtion customlog($msg){
        echo $msg;
    }
}

bootstrapの中でクラスを認識させて
/fuel/packages/customlog/bootstrap.php

<?php

Autoloader::add_core_namespace('Customlog'); //これ重要
Autoloader::add_classes(array(
	'Customlog\\Log' => __DIR__.DS.'classes'.DS.'log.php', //クラスを置き換える。appの時と違って名前空間付きで書く
));

app側に設定追加して
/fuel/app/config/config.php

<?php

return array(
		:
		:
		:
    'always_load'  => array(
		:
		:
		:
		//188行目ぐらい
		'packages'  => array(
			//'orm',
			'customlog'
		),
		
		:
		:

って感じにしたら動いた。

最初はnamespaceなしでやってみてうまく入ったけど「果たしてこれでいいのか?」みたいな疑念が湧いていろいろ試した結果多分正しそうな感じに落ち着いた。
途中に書いたAutoloader::add_core_namespace('Customlog');
というのが\Fuel\Coreみたいにcoreクラスの名前空間として認識させるものっぽい。

よく考えたらほかのパッケージちゃんと見てれば問題ないよねって話だよね。

おしまい。

2012-07-08 追記
どうもCoreを書き換えるのってあんまよくないらしいっすね。
まあ冷静に考えればそうですよねって感じですね。