オープンソースのカラムストア機能付き全文検索エンジンといえば、Groongaがあります。Groongaを使うと全文検索機能付き高性能アプリケーションを開発することができます。
Groongaプロジェクトでは各種ディストリビューション*1向けに独自にパッケージを提供しています。そのため、比較的簡単にインストールできるようになっています。
ただ、パッケージをインストールするのに、あらかじめ独自に提供しているリポジトリを登録しないといけないのがちょっと面倒です。できれば、それぞれのディストリビューションの公式リポジトリからインストールできるととても楽です。
昨年の全文検索エンジンGroongaを囲む夕べ4の懇親会で、Debian開発者のやまねさんとVine Linux開発メンバーの岩井さんとお話する機会がありました。GroongaをDebianに入れたいのだけれどと相談したところ、やまねさんからわからないところはサポートしますよというありがたい一言をいただきました。そこで、現状のGroongaのパッケージについてコメントをもらうところからGroongaをDebianプロジェクトでリリースできるようにする作業ははじまりました。
今回は、現在進行中であるGroongaのDebianリポジトリ入りを目指す作業の中からバグ登録について紹介します。
そもそも、Debianプロジェクトでパッケージをリリースできるようにするのに必要な手順はどんなものがあるのでしょうか。
それを簡単に説明している文書の一つに、Introduction for maintainers: How will my package get into Debianがあります。
簡単に紹介すると以下の通りです。
今回はこのうち、WNPPにバグ登録するところを説明します。
WNPPとは、「作業が望まれるパッケージ(Work-Needing and Prospective Packages; WNPP)」を意味します。今回はGroongaが新たなパッケージ化が望まれているDebianパッケージということになります。
このWNPPに属するパッケージについてはDebian Bug report logs: Bugs in package wnpp in unstableで参照できます。
バグ登録する場合には、定型的なメールを送信します。[groonga-dev,01967] Debianパッケージの登録作業(ITP)でやまねさんに教えてもらいました。
そこで、教えてもらった雛形を参考に以下のようなメールをsubmit@bugs.debian.org宛てに送りました。
Subject: ITP: groonga -- Fulltext search engine From: HAYASHI Kentaro <hayashi@clear-code.com> To: submit@bugs.debian.org Date: Fri, 13 Dec 2013 19:05:17 +0900 X-Mailer: Sylpheed 3.4.0beta7 (GTK+ 2.24.20; x86_64-unknown-linux-gnu) Package: wnpp Severity: wishlist Owner: Groonga Project <packages@groonga.org> X-Debbugs-CC: debian-devel@lists.debian.org, debian-devel@lists.debian.or.jp Package name: groonga Version: 3.1.0 Upstream Author: Daijiro MORI <morita at razil. jp>, Tasuku SUENAGA <a at razil. jp>, Yutaro Shimamura <yu at razil. jp>, Kouhei Sutou <kou at cozmixng. org>, Kazuho Oku <kazuhooku at gmail. com>, Moriyoshi Koizumi <moriyoshi at gmail. com> URL: https://github.com/groonga/groonga License: LGPL-2.1 Description: Groonga is an open-source fulltext search engine and column store. It lets you write high-performance applications that requires fulltext search.
件名に入れているITPというのはなんでしょうか。これは、Intent To Packageの略語で、新たなパッケージを作成したいという宣言を意味します。その際にはX-Debbugs-CC:にdebian-devel@lists.debian.orgを指定して、Debian開発者へと周知します。
メールを送信してしばらくすると、次のようにバグが採番され、BTSへと登録できます。Groongaの場合は#732055となりました。
とても簡単ですね。
今回はDebianでパッケージをリリースするための最初の一歩であるバグ登録について紹介しました。バグ登録をしてからも、まだまだしなければいけない作業はたくさんあります。それらについては、またの機会に記事にしたいと思います。
*1 Debian/Ubuntu/CentOSでは独自のリポジトリからインストールできるようになっています。Fedoraの場合はFedoraプロジェクト公式リポジトリからインストールできます。Fedoraだけ公式リポジトリからインストールできるようになっているのは「Fedoraプロジェクトで新規パッケージをリリースする方法」や「Fedoraプロジェクトでパッケージを更新するには」の成果です。
Groongaには可変長データを削除・更新しつづけるとデータベースのサイズが大きくなり続けてしまうという問題があります。次回のリリースではこの問題が解消される見込みで、現在、ユーザーにテストをお願いしています。(詳細は[groonga-dev,02173] データベース肥大化に悩むみなさんへテストのお願いを参照。)
ここでは、Groongaがどうやって可変長データを管理していて、どのようにして肥大化を抑えるようにしたかを説明します。
Groongaは新しい鮮度のよい情報をすぐに検索できることを重視しています。そのため、データ更新時も検索性能を落とさないことを重視した設計になっています。具体的には参照ロックフリーという性質を実現しています。
参照ロックフリーとはデータを更新している最中でもデータを読み込める性質のことです。この性質のおかげでデータ更新中でもデータを参照できるため、検索処理がブロックしません。そのため、検索性能を落とさずに済みます。
参照ロックフリーを実現するための基本的な考えは次の通りです。
32bit整数の値は前者の考えを使います。
文字列は後者の考えを使います。
文字列を含む可変長データはアトミックに変更できない値なので後者の方法で参照ロックフリーを実現しています。
データの更新方法の概要を説明したので、次はデータの管理方法を説明します。
Groongaはデータのサイズで可変長データの格納方法を変えています。
小さなサイズのデータの格納方法を図示するとこうなります。
大きなサイズのデータの格納方法を図示するとこうなります。
データベースの肥大化の要因は大きなサイズのデータの格納方法にあります。
単に追加するだけの場合は大きなサイズのデータの格納方法の方が空間効率がよく、順番にデータにアクセスする場合は速いです。しかし、データの削除・更新を実行するとそのメリットが崩れていきます。
Groongaは参照ロックフリーを実現するために、可変長データを更新するときはインプレースで書き換えません。新しい値を用意してそこへの参照を書き換え、古いデータには削除マークをつけます。
図示するとこうなります。
削除する場合は単に削除マークをつけるだけなので更新の特別な場合と考えることができます。よって、ここでは更新操作についてのみ説明します。
更新操作をするたびに削除マークがついた未使用の領域が増えます。未使用の領域が増えると空間効率が悪くなり、順番にデータにアクセする速度も遅くなります。データが連続していないからです。フラグメンテーションが起きている状態です。
削除マークをつけた領域を使わないのはもったいないので、しばらくすると再利用します。しかし、再利用する条件がシビアです。そのため、再利用はなかなか進みません。これが、データベースが肥大化する原因です。
大きなサイズのデータを格納する領域は一定のサイズごとに塊になっています。具体的には4MiBごとの塊になっています。たとえば、合計12MiBのデータを利用する場合は、きっちり隙間なく詰められたとすると3つの塊が必要だということです。
削除マークがついた領域を削除する条件はこの4MiBの塊全部の領域に削除マークがついた場合です。1つでも使っている領域が残っていればその塊の中の削除マークつきの領域は再利用されません。すべて削除マークがついてようやく再利用対象になります。
図示するとこうなります。
つまり、1つでもずっと更新されないレコードがあると再利用されないということです。
それでは、どうやってこの「大きなサイズのデータを更新しつづけるとデータベースが肥大化していく」問題を解決したかを説明します。
「小さなサイズ」と扱う範囲を広げました。
後述しますが、小さなサイズのデータの格納方法は比較的再利用されやすい方法になっています。そこで、よく使われるサイズのデータを小さなサイズのデータと扱うようにしました。
具体的には、これまでは128バイト未満のデータを小さなサイズのデータと扱っていたものを64KiB未満のデータを小さなサイズのデータと扱うようにしました。Groongaでは一番小さな文字列型のサイズが4KiBなので、そのサイズをカバーしています。また、多くのテキストデータは64KiB未満になることが多いため、多くのデータが小さなサイズのデータ扱いとなります。
小さなサイズのデータの格納方法は大きなサイズのデータの格納方法に比べて空間効率がよくないので追加のみのユースケースではこれまでよりもデータベースサイズが大きくなります。
Groongaは新しい情報を提供することに価値をおいているので、更新するユースケースでのメリットが非常に大きいと考えて、次のリリースからこの肥大化を抑える方法を有効にする予定です。
それでは、小さいサイズのデータの更新方法を説明して終わりにします。
小さいサイズのデータも、新しい値を作ってからその参照を書き換えて古い値に削除マークをつける、という更新方法は同じです。そのため、それについての説明は省略します*1。削除マークがついた領域の再利用条件についてだけ説明します。
小さいサイズのデータも4MiBごとの塊の中にデータを格納しているのは同じです。塊の中で10個以上削除マークがついた領域ができると削除マークがついた領域を再利用します。大きなサイズのデータよりもだいぶ緩い条件です。
図示するとこうなります。
レコードが10件更新されるだけで再利用条件を満たすので、再利用頻度がだいぶあがります。再利用が増えるのでデータベースの肥大化を抑制できるというわけです。
*1 データのサイズごとに固定長の領域のサイズを変えて隙間をできるだけ小さくしようとしているということは説明したほうがよいのですが、省略します。
RSpec 3の新機能であるComposable Matchersの使い方の例をtest-unitならどう書くか紹介します。リンク先のコードを示し、それのtest-unitバージョンを示す、という流れを繰り返します。
テスト対象は次のコードです。
1 2 3 4 5 6 7 8 9 10 11 |
class BackgroundWorker attr_reader :queue def initialize @queue = [] end def enqueue(job_data) queue << job_data.merge(:enqueued_at => Time.now) end end |
RSpec 3ではComposable Matchersを使ってこう書くそうです。(リンク先から引用。以下同様。)
1 2 3 4 5 6 7 8 9 10 11 12 |
describe BackgroundWorker do it 'puts enqueued jobs onto the queue in order' do worker = BackgroundWorker.new worker.enqueue(:klass => "Class1", :id => 37) worker.enqueue(:klass => "Class2", :id => 42) expect(worker.queue).to match [ a_hash_including(:klass => "Class1", :id => 37), a_hash_including(:klass => "Class2", :id => 42) ] end end |
test-unitではこう書きます。RSpecは「フレームワーク側が」必要な値だけ比較するという方針ですが、test-unitは「テストを書く側が」必要な値だけ取り出して比較するという方針です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class BackgroundWorkerTest < Test::Unit::TestCase class EnqueueTest < self def test_order worker = BackgroundWorker.new worker.enqueue(:klass => "Class1", :id => 37) worker.enqueue(:klass => "Class2", :id => 42) assert_equal([ {:klass => "Class1", :id => 37}, {:klass => "Class2", :id => 42} ], normalize_queue(worker.queue)) end private def normalize_queue(queue) queue.collect do |job_data| { :klass => job_data[:klass], :id => job_data[:id], } end end end end |
リンク先ではテスト結果の失敗時にどのように報告するかについても触れています。enqueue
の実装がコメントアウトされていたときを例にしています。
1 2 3 4 5 6 |
class BackgroundWorker # ... def enqueue(job_data) # queue << job_data.merge(:enqueued_at => Time.now) end end |
RSpec 3の場合は次のようになって読みやすい、ということです。英語で読みくだせるのがポイントですね。
1) BackgroundWorker puts enqueued jobs onto the queue in order Failure/Error: expect(worker.queue).to match [ expected [] to match [(a hash including {:klass => "Class1", :id => 37}), (a hash including {:klass => "Class2", :id => 42})] Diff: @@ -1,3 +1,2 @@ -[(a hash including {:klass => "Class1", :id => 37}), - (a hash including {:klass => "Class2", :id => 42})] +[] # ./spec/background_worker_spec.rb:19:in `block (2 levels) in <top (required)>'
test-unitの場合は次のようになります。RSpecとは対照的に、英語を極力排除して実際のプログラムとデータを見せる方に注力しています。
Failure:
test_order(BackgroundWorkerTest::EnqueueTest)
test-worker.rb:22:in `test_order'
19: worker.enqueue(:klass => "Class1", :id => 37)
20: worker.enqueue(:klass => "Class2", :id => 42)
21:
=> 22: assert_equal([
23: {:klass => "Class1", :id => 37},
24: {:klass => "Class2", :id => 42}
25: ],
<[{:id=>37, :klass=>"Class1"}, {:id=>42, :klass=>"Class2"}]> expected but was
<[]>
diff:
? [{:id=>37, :klass=>"Class1"}, {:id=>42, :klass=>"Class2"}]
Compound Matcher Expressionsという機能は次のように書ける機能ということです。これまではstart_with
のチェックとend_with
のチェックを別に書かなれければいけなかったのに、一緒に書けるようになったということです。
1 |
expect(alphabet).to start_with("a").and end_with("z") |
test-unitではこう書きます。期待するパターンなら特定の文字列に置換します。一回で比較するという方針は同じです。
1 2 3 |
alphabet = "abcxyz" a_z = "a...z" assert_equal(a_z, alphabet.gsub(/\Aa.*z\z/, a_z)) |
次のように期待したパターンでない場合は元の文字列が変わらないので失敗します。
1 2 3 |
alphabet = "abcxyZ" a_z = "a...z" assert_equal(a_z, alphabet.gsub(/\Aa.*z\z/, a_z)) |
失敗時のメッセージにはちゃんと元の文字列がでるので、実際の値はなんだったのか、という情報が失われることはありません。
Failure:
test_a_z(AlphabetTest)
test-alphabet.rb:7:in `test_a_z'
4: def test_a_z
5: alphabet = "abcxyZ"
6: a_z = "a...z"
=> 7: assert_equal(a_z, alphabet.gsub(/\Aa.*z\z/, a_z))
8: end
9: end
<"a...z"> expected but was
<"abcxyZ">
diff:
? a...z
? bcxyZ
RSpec 3の例はもっとたくさんありますが、2個だけ紹介しました。
値の比較の仕方に方針の違いがでていました。
RSpec 3はより英語らしく読み書きできるようになりそうですね。
test-unitはあいかわらずRubyらしく読み書きできるテスティングフレームワークですね。
企業や団体などにおいて、組織内標準のWebブラウザとしてFirefoxの導入を検討されているお客様や、Firefoxを既に導入済みであるというお客様から、「Firefoxの設定を管理者が一括して管理したい(ユーザに管理させたくない)」という要望をよく伺います。
FirefoxとThunderbirdでは、設定を含むカスタマイズの内容を管理者の手で管理する方法にはいくつかの選択肢があり、それぞれメリットとデメリットがあります。できることの幅が狭い物から広い物へと順番に並べたものが次のリストです。
以下、それぞれについてどのようなメリットとデメリットがあるのか、どのような目的・場合に対して適切な選択肢となるのかを簡単に紹介します。自力での運用を検討されている方は、カスタマイズ手法の選定の参考にしてみて下さい。
FirefoxやThunderbirdは、プリファレンスという仕組みによって多くの設定を管理しています。user.jsは、プリファレンスに基づく設定について「固定の設定」「複数ユーザで共通の設定」らしき挙動を実現する最も手軽な手段です。詳細はabout:config と user.js による Firefox のカスタマイズ - えむもじらなどの記事を参照して下さい。
user_pref("設定名", 値);
の形で設定名と値のペアを列挙したファイル「user.js」を作成する。
ファイル形式は、文字エンコーディングがUTF-8のプレーンテキストとする。複数ユーザや他のコンピュータに設定を展開する際は、作成済みのuser.jsをコピーして3から4の手順を繰り返すことになります。
プロファイルフォルダの位置はFirefoxから辿ることができます。about:support
を開き、「プロファイルフォルダ」欄の「フォルダを開く」ボタンを押すと、現在利用中のプロファイルのプロファイルフォルダが開かれます。
MCD(Mission Control Desktop)は、Netscape由来の集中管理機構です。詳細はMozilla 製品の集中管理 - 基本編 - MCDなどを参照して下さい。
元々は、設定ファイルを自動生成する管理ツールとのセットで運用する事を前提とした仕組みでしたが、Netscapeが消滅した現在では、設定ファイルの自動生成用の管理ツールがなくなってしまっています。 それでも、設定ファイルを自力で作成しさえすれば、この仕組みは今でも利用できます。
defaultPref("設定名", 値);
または lockPref("設定名", 値);
の形で設定名と値のペアを列挙したファイル「autoconfig.cfg」を作成する。
ファイル形式は、文字エンコーディングがUTF-8のプレーンテキストとする。
また、ファイルの1行目は必ずコメント行とする(読み込み時に1行目だけは無視されるため)。以下の内容のプレーンテキストファイル autoconfig.js を作成する。
1 2 3 |
pref("general.config.filename", "autoconfig.cfg"); pref("general.config.vendor", "autoconfig"); pref("general.config.obscure_value", 0); |
C:\Program Files (x86)\Mozilla Firefox\autoconfig.cfg
などの位置)C:\Program Files (x86)\Mozilla Firefox\defaults\prefs\autoconfig.js
などの位置)設定は、そのコンピュータでFirefox(またはThunderbird)を使うユーザすべてに影響します。 他のコンピュータに設定を展開する際は、作成済みのautoconfig.jsとautoconfig.cfgをコピーして4から5の手順を繰り返すことになります。
for
ループやfunction
などのJavaScriptの機能が使えるため、似たような設定を大量に施したい場合に労力やミスを減らすことができる。if
文と組み合わせるなどして、設定をある程度自動的に振り分けられる。lockPref()
を使うことで、ユーザによる設定の変更を禁止できる。MCDでは、autoconfig.cfg相当の内容のファイルをWebサーバやファイル共有サーバなどに設置しておき、それを毎回起動時に動的に読み込むという事ができます。これにより、設定の本格的な集中管理が可能となります。
ローカル設置用の autoconifg.js と autoconfig.cfg を作成する。 autoconfig.cfg は以下の内容で作成する。
1 2 |
// 1行目は必ずコメントとしてください。 lockPref("autoadmin.global_config_url", "3で控えたURL"); |
autoadmin.global_config_url
で指定されたURLから設定ファイルをダウンロードできなかった場合、過去に正常にダウンロードできたことがあればその時のキャッシュが読み込まれます。
キャッシュがない場合はエラーとなって、FirefoxおよびThunderbirdを起動できません。
Firefox本体にはActive Directoryと連携するための機能は含まれていませんが、アドオンを使うことによって、グループポリシーによる設定の集中管理が可能となります。
以降は、ドメインに参加したWindows PC上でFirefoxを起動する度に、グループポリシーで変更された設定が読み込まれ、自動的に反映されるようになります。
for
やfunction
などのJavaScriptの機能、環境変数とif
を使った設定の自動振り分けなどが、グループポリシー経由での管理では行えない。
(代わりに、設定の振り分けはグループポリシーの適用範囲をの制御を通じて行う。)CCK2 Wizardは、Firefoxのカスタマイズ用のファイルを作成するためのウィザードを提供するアドオンです。このウィザードに従って操作を行い、作成された設定用ファイルを各クライアントにインストールすることで、様々なカスタマイズ内容を一度に反映することができます。
ここまでで挙げた中ではCCK2 Wizardが最も柔軟性の高いカスタマイズ手段となりますが、CCK2 Wizardでもできないようなカスタマイズを行いたい場合には、現実的にはやはり、「そのためのアドオンを使用する」ということになります。例えば、以下のような例があります。
法人向けFAQでも、組織内での運用で便利と思われるアドオンをいくつか紹介しています。また、既存のアドオンでニーズを満たせない場合は、独自にアドオンを開発するという選択肢もあります。
アドオンを全ユーザ向けに管理者権限でインストールする方法も、目的に応じていくつかの選択肢があります。法人向けFAQに詳しい記述がありますので、そちらも併せて参照して下さい。
以上、FirefoxやThunderbirdを組織内で利用するにあたって管理者の手でカスタマイズ内容を管理するいくつかの選択肢を比較する形で紹介しました。
この記事では、それぞれのカスタマイズ手法のメリットとデメリットという所に焦点を当てて紹介しましたが、そもそもどんな点をカスタマイズできるのか?という事自体に関心があるという方もいらっしゃることでしょう。上記文中でも紹介していますが、組織内での利用で行いたくなりがちな様々なカスタマイズについて、現在Mozilla Japanと共同でFAQの編纂を進めています。概要を把握するためにも、まずはFAQをざっと眺めてみて下さい。
また、クリアコードでは、FirefoxおよびThunderbirdの利用にあたってお困りのお客様について、問題を解決するお手伝いをしています。FirefoxやThunderbirdの導入やカスタマイズでお困りで、自力での解決が難しいという場合には、弊社有償サポート窓口までぜひ一度ご相談下さい。
ところで、4月9日に行われるIE6はEOLだが、これからのWeb系システムは本当にブラウザでいいのか? - エンタープライズWebプラットフォーム大戦争というイベントで、Firefoxの企業での利用について弊社の結城が発表させていただくことになりました。イベントのタイトルからは少し離れた話題となりますが、上記の話も含めて、FirefoxやThunderbirdの企業利用のカスタマイズ事例について紹介させていただく予定です。定員は既に満席のようですが、直前になるとキャンセルが出ることが予想されますので、このような話題に関心があるという方は、諦めず補欠登録しておいてみてはいかがでしょうか?