株式会社クリアコード > ククログ

ククログ

タグ:

名古屋Ruby会議03:Apache ArrowのRubyバインディング(1) #nagoyark03

2017年2月11日に名古屋Ruby会議03が開催されます。そこで「Apache ArrowのRubyバインディングをGObject Introspectionで」という話をする予定です。

名古屋Ruby会議03とApache ArrowとGObject IntrospectionとRubyバインディングの宣伝も兼ねて関連情報をまとめていきます。

まずは、Apache Arrowについて簡単に紹介します。

Apache Arrowとは

Apache Arrowはインメモリーで高速にデータ分析をするためのデータフォーマットの仕様(たぶん)とその実装です。

Apache Arrowが開発されている目的は高速にデータ分析をすることです。そのために以下のことを大事にしています。

  • データ分析プロダクト間でのデータ交換コストを下げる
  • 複数のCPUコアを使って高速にデータ分析処理をできる

それぞれ少し補足します。

まず、データ交換コストについてです。

現在、データ分析用のプロダクトにはApache HBaseApache SparkPandasなどがあり、それらが協調してデータ分析をしています。協調するためには、それらのプロダクト間で分析対象のデータを交換する必要があります。現在は各プロダクトでそれぞれ別のデータフォーマットを使っているので、交換するときにはフォーマットを変換する必要があります。

この状況の課題は次の通りです。

  • 変換コストがムダ(Apache Arrowのサイトには70-80%のCPUが変換のために使われていると書いている)
  • それぞれのフォーマット毎に似たような処理が実装されている

Apache Arrowのように各プロダクトで共通で使えるデータフォーマットがあると変換は必要なくなりますし、そのデータに対する処理も同じ実装を共有できます。これがApache Arrowが解決しようとしているやり方です。Apache Arrowのサイトの「Advantages of a Common Data Layer」のところにこの状況のイメージがあるので、ピンとこない人はApache Arrowのサイトも見てみてください。

次に、複数のCPUコアの話です。

現在は1つのマシンで複数のCPUコアを利用できることは当たり前です。同時に複数のCPUコアを有効活用できれば処理速度を向上させることができ、より速くより多くのデータ分析処理を実現できます。

Apache Arrowはカラムベースのデータフォーマットを活用することによりこれを実現します。Apache Arrowのデータフォーマットを各プロダクトで共通に使えれば、この高速な処理も各プロダクトで共有できます。これがApache Arrowが実現しようとしていることです。これについてもApache Arrowのサイトの「Performance Advantage of Columnar In-Memory」のところにイメージがあるので、参照してください。

なお、Apache Arrowに賛同しているデータ分析プロダクトは現時点で13個あります。各プロダクトがApache Arrowを使う未来がきそうな気がしますね。プロダクトの詳細はApache Arrowのサイトを確認してください。

まとめ

名古屋Ruby会議03で話す内容の関連情報をまとめはじめました。

今回はApache Arrowの話だけでRubyのことは全然でてきませんでした。次はなぜApache ArrowのRubyバインディングがあるとよさそうなのかについて説明します。

2017-01-16

mrubyの例外のバックトレースの実装

2015年の12月に改良されるまで、mrubyの例外のバックトレースは壊れていることがありました。どういうときに壊れるかというと、たとえばrescueの中でメソッドを呼ぶと壊れました。

どうして壊れていたかと現在はどうやって壊れないようにしているかを説明します。

壊れていた理由

まず壊れていた理由を説明します。壊れていた理由は、バックトレースを取得するとき(Exception#backtraceを呼び出すとき)に「そのときのスタックからバックトレース情報を構築していた」からです。

この方法は例外発生時のスタックの状態とバックトレース取得時のスタックの状態が同じなら問題はありません。しかし、メソッドを呼び出す、例外を保存しておいて後でバックトレースを取得する、などスタックの状態が異なるときは壊れたバックトレースになります。

なお、このような実装になっていた理由は(おそらく)パフォーマンスです。このように(バックトレース取得リクエストがあるまでバックトレース構築を遅延)せずに、例外発生時にバックトレースを構築する実装にすると、例外をあげるコストが高くなります。例外のバックトレースは必ず使われるものではなく、使われないこともあります。そのため、バックトレースを構築しないで済ませられるならしないようにすることで例外をあげるコストを低くできます。実際、CRubyも2.0からバックトレース構築を遅延させるようにして例外をあげるコストを低くしています。

壊れないようにする方法

それではどのようにして壊れないようにしているかを説明します。

作戦は次の通りです。

  • バックトレース情報の構築を次の2段階に分ける
    • Rubyオブジェクトを生成しない生のバックトレース情報の取得(動的にメモリーを確保しないので軽い。実装でいうとmrb_save_backtrace()。)
    • ↑の生のバックトレース情報からRubyオブジェクトにする(動的にメモリーを確保するので重い。実装でいうとmrb_restore_backtrace()。)
  • 生のバックトレース情報の取得は例外発生時に実行する(実装でいうとmrb_exc_raise()
  • 生のバックトレース情報からRubyオブジェクトにするのは必要になるまで遅延する(実装でいうとmrb_exc_set()

Rubyオブジェクトにするところをいかに遅延させるかがパフォーマンスに影響します。具体的には以下のタイミングまで遅延させます。

  1. Exception#backtraceを呼び出したとき
  2. 次の例外が発生したとき

1つめのタイミングは自明です。必要とされているタイミングだからです。

2つめのタイミングは実装の制限です。動的にメモリーを確保しないで「Rubyオブジェクトを生成しない生のバックトレース情報の取得」を実現するために、あらかじめ用意しておいた領域(mrb_state::backtrace)に保存するようにしています。この領域が1つしかないので複数の生バックトレース情報を保存できないのです。そのため、次の例外が発生したときは前の例外の生バックトレース情報をRubyオブジェクトにして、代わりに次の例外の生バックトレース情報を保存するようにしています。

こうすることによりmrubyでは例外をあげるコストを低くしています。

まとめ

mrubyの例外のバックトレースの構築を遅延させている実装がどうしてこうなっているのか(mrb_exc_set()がどうして必要なのか)わからないという声を聞いたので、どうしてこのような実装になっているかを説明しました。

クリアコードではCRubyだけでなくmrubyに関する開発・開発支援も承っています。mrubyを使ったアプリケーションだけでなく、mruby本体の改良・修正まで対応できますので、興味のある方はお問い合わせください。

タグ: Ruby
2017-01-11

2017年にやりたいことと2016年のまとめ

年のはじめなので、2017年にやりたいことと2016年のまとめを書きます。

2017年にやりたいこと

まず、今年やりたいことです。

設立当時からクリアコードが大事にしていることは次の2つです。

  • 継続的にフリーソフトウェアの考えを実践すること
  • ソフトウェアを継続して改良してくいくためにクリアなコードを書くこと

これらをもっと実現するために2017年は仲間を増やしていきます。仲間は、クリアコードのメンバーという形はもちろん、そうでない形でもよいです。そうでない形というのは、たとえば、OSS開発支援サービスを通じて一緒に開発をする人たちやOSS Gate(サイトがカッコよくなりました!)で一緒に活動する人たちです。

クリアコードのメンバーとして取り組みたいという方は採用情報を確認してみてください。

採用情報には古くなっている情報があるので今月中に更新します。今はクリアコードのメンバーが開発に関わっているフリーソフトウェアを一緒に開発する、となっていますが、クリアコードのメンバーが現時点ではまだ開発に関わっていないフリーソフトウェアでもよいことにする予定です。やりたいことは「一緒に開発することでお互いに一緒にクリアコードで仕事をしたい人かを確認する」ことなので、それを達成できるならクリアコードのメンバーがすでに開発に関わっていなくてもよいのです。

2016年のまとめ

それでは、2016年のことを月ごとにふりかえります。

1月

2015年10月からはじめたワークライフバランス推進に関する取り組みをまとめました。この取り組みの目的は「クリアコードのメンバー全員が働きやすい環境を用意する」ことです。その結果として「クリアコードのメンバー全員が十分に能力を発揮できる」ことを目指しています。

この取り組みで導入した在宅勤務制度を一番活用しているのは週4日在宅勤務をしている者です。育児のために在宅勤務を利用しています。時短勤務も検討していましたが、通勤時間がなくなることにより勤務時間を減らさずに育児をできています。このケースではワークライフバランスの改善と能力の発揮を両立できています。

他の者は月に数回程度の在宅勤務でワークライフバランスを改善しています。

1月30日には1回目のOSS Gateワークショップを開催しました。ビギナー(OSS開発に参加したことがない人)が4名と少なめでしたがメンター(OSS開発経験者)にとってはワークショップを経験できたことが有意義でした。

2月

2月8日には「『nginx実践入門』出版記念!執筆者らが語る nginx Tech Talks」でgroonga-httpdの紹介をしました。「全文検索nginx」というタイトルを思いついたときは「しめた!」と思ったものでした。

次の日の2月9日(年に一度の肉の日!)には「MySQLとPostgreSQLと日本語全文検索」でMroongaとPGroongaの紹介をしました。このイベントは好評でこのあと続編を2回開催することになります。

3月

3月16日には「第1回 法人向けFirefox導入セミナー」でFirefoxの法人利用について紹介しました。

3月26日には2回目のOSS Gateワークショップを開催しました。この回まではビギナーを集めるのが大変という状況でした。次回以降はビギナーはたくさん集まるようになり、今度はメンターを集めるのが大変という状況に変わっていきます。

4月

Rubyist Magazine(るびま)0053号にクリアコード代表取締役の須藤へのインタビューが掲載されました。

Redmineの全文検索プラグインをフリーソフトウェアとして公開しました。MroongaPGroongaを活用しています。

WEB+DB PRESS Vol.86特集「1年目から身につけたい! チーム開発 6つの心得」が全文公開されました。クリエイティブ・コモンズのBY-NC-SA(表示・非営利・継承)で利用できます。営利目的に使う自由はありませんが、変更・再配布は自由です。技術評論社さんにお願いしてできるだけ自由に利用できるようにしてもらいました。

5月

Mozilla BlogにFirefoxアドオンのWebExtensions移行についての記事を寄稿しました。

5月28日には「東京Ruby会議11」でCアプリケーションへのRubyインタープリターの組み込み方を紹介しました。これがきっかけでRuby 2.4にrb_gc_adjust_memory_usage()が入りました

なお、東京Ruby会議11と同日・同建物で第3回目のOSS Gateワークショップが開催されました。この回からビギナーの参加が多数になりました。この結果、このワークショップの課題は、「ビギナーをどうやって集めるか」から「多数のビギナーにどう対応するか」に変わってきました。

6月

6月9日には「MySQLとPostgreSQLと日本語全文検索2」で初心者向けにMroongaとPGroongaを紹介しました。

OSS開発支援サービスを開始しました。去年からSpeeeさんにもサービスを提供しています。業務の中でエンジニアがOSS開発に参加することはSpeeeとして価値があるということで利用されています。Speeeさんのエンジニアの中にはこのサービスがきっかけでOSS開発に参加したエンジニアがいます。

自分の会社でもOSS開発に参加していきたいという方はお問い合わせください。

7月

7月22日には「MariaDBコミュニティイベント in Tokyo」でMroongaを紹介しました。これがきっかけとなり、その後、以下の展開がありました。

クリアコード設立記念日である7月25日にはクリアコード10周年祝いを開催しました。会場をOSS開発支援サービスを提供しているSpeeeさんに提供してもらうなどいろいろな方々に協力してもらって開催できました。ありがとうございました。

7月30日には4回目のOSS Gateワークショップを開催しました。この回から多数のビギナーに対応するために「サポートメンター」を導入しました。

8月

8月はTreasure Dataさんから依頼を受けて開発に参加しているFluentd関連の情報をまとめました。

以前はGroongaも未来検索ブラジルさんから依頼を受けて開発に参加していました。(今はクリアコードが自社の業務の一環として開発に参加しています。)

自社のOSSの開発に参加してほしいという方はお問い合わせください。

9月

9月1日には「Speee Cafe Meetup #02」で「OSS開発者を増やしたい!」という話をしました。

9月8日から10日はRubyKaigi 2016にスポンサーとして参加しました。クリアコードは次の2つの発表をしました。

9月24日には札幌で1回目のOSS Gateワークショップを開催しました。初の東京以外での開催です。なお、同日に東京でも5回目のOSS Gateワークショップを開催しました。

9月29日には「MySQLとPostgreSQLと日本語全文検索3」でMroongaとPGroongaの導入方法例を紹介しました。RubyとPythonでの例になっています。

10月

技術情報をいくつかまとめました。

11月

fluent-plugins-nurseryを始めました

11月26日には東京で6回目のOSS Gateワークショップを開催しました。なお、同日に札幌でも2回目のOSS Gateワークショップを開催しました。

12月

12月3日にはPGConf.ASIA 2016でPGroongaを紹介しました。PostgreSQLでビジネスをしている方々と話をできたのがよかったです。

まとめ

2017年にやりたいことは仲間を増やすということであると宣言しました。

2016年の活動もまとめました。

2016年はほぼ毎月発表していました。外向けの活動は今後も継続していきます。

2016年のはじめには知見の言語化を強化していきたいとしていたのですが、それはあまりできませんでした。発表内容紹介とツール紹介・機能紹介が多かったです。2017年こそは知見の言語化を強化していきたいです。

2017-01-05

PolicyKitを用いて適切に権限管理するには

はじめに

CentOS 7ではPolicyKitによりユーザーの権限昇格・拒否の方法を柔軟に指定することができます。 実際に、CentOS 7では/etc/polkit-1/rules.d/50-default.rulesにより、管理者の認証が必要になった時に必要となるメソッドを追加しています。

/* -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- */

// DO NOT EDIT THIS FILE, it will be overwritten on update
//
// Default rules for polkit
//
// See the polkit(8) man page for more information
// about configuring polkit.

polkit.addAdminRule(function(action, subject) {
    return ["unix-group:wheel"];
});

これにより、CentOS 7ではwheelグループにいるユーザーはrootのパスワードを用いることなく各自のパスワードにて管理操作用に認証されるようになりました。

CentOS 7では従来通りの.pkla, .confを用いる方法も引き続き利用できます。

PolicyKitを持ちいる権限管理の初歩

PolicyKitを用いて適切に権限管理するにはまず、void polkit.addRules(polkit.Result function(action, subject){..})void polkit.addAdminRules(string[] function(action, subject){..}) の働きを知る必要があります。他にもいくつか関数がありますが、ここでは解説しません。

返り値を見ると両方ともvoidとなっていることから内部で副作用を起こす関数となっていることが推測されます。

まず、一つ目の関数の polkit.addRules(polkit.Result function(action, subject){..}) ついてはあるアクション(subject)に対する認証・拒否の設定を行います。polkit.Result定数は以下から選択してfunctionオブジェクトの返り値とします。

polkit.Result = {
    NO              : "no",              // 無条件で拒否、アクションは実行されません。
    YES             : "yes",             // 無条件で認証、アクションを実行します。
    AUTH_SELF       : "auth_self",       // 認証が必要だが管理者ユーザーの必要なし。
    AUTH_SELF_KEEP  : "auth_self_keep",  // 認証が必要だが管理者ユーザーの必要なし。一定期間後無効。
    AUTH_ADMIN      : "auth_admin",      // 認証が必要で管理者ユーザーの必要あり。
    AUTH_ADMIN_KEEP : "auth_admin_keep", // 認証が必要で管理者ユーザーの必要あり。一定期間後無効。
    NOT_HANDLED     : null               // このルールでは何もしない
};

これらの値のうちどれかをfunctionオブジェクトの返り値にすることになります。

二つ目の void polkit.addAdminRules(string[] function(action, subject){..}) のfunctionオブジェクトではユーザーグループ、または、ユーザー名とその対になる文字列の配列を返すことになります。

例えば、はじめにの節で例に挙げていた /etc/polkit-1/rules.d/50-default.rules では以下のように使われています。

polkit.addAdminRule(function(action, subject) {
    return ["unix-group:wheel"];
});

これはつまり管理者としての認証を求められた時にはwheelグループに属するユーザーのパスワードを用いることで管理者として振る舞えるようにします、というルールを追加しています。

PolicyKitのルールはファイル名の辞書順に読み込まれるため、rulesファイルで優先して読み込みたいルールがある場合は10や20などの小さい数字をファイル名の先頭へつけると良いでしょう。

権限管理の実例

ここで、PolicyKitの権限管理のあらましを解説したところで実例を見ていきます。 シャットダウン・再起動などの電源に関わる操作について権限管理してみます。

GNOMEデスクトップ環境からwheelに属さない一般ユーザーに対してシャットダウン・再起動などの電源に関わる操作をGNOMEデスクトップ上での操作を禁止することを目標にルールを定めてみることにします。

まず、対象となるアクションIDを知る必要があります。 freedesktop.orgのlogindの項目 によると、シャットダウン・再起動などの電源に関わる操作については以下のアクションIDが該当します。

  • シャットダウン
    • org.freedesktop.login1.power-off
    • org.freedesktop.login1.power-off-multiple-sessions
    • org.freedesktop.login1.power-off-ignore-inhibit
  • 再起動
    • org.freedesktop.login1.reboot
    • org.freedesktop.login1.reboot-multiple-sessions
    • org.freedesktop.login1.reboot-ignore-inhibit
  • サスペンド
    • org.freedesktop.login1.suspend
    • org.freedesktop.login1.suspend-multiple-sessions
    • org.freedesktop.login1.suspend-ignore-inhibit
  • ハイバネート
    • org.freedesktop.login1.hibernate
    • org.freedesktop.login1.hibernate-multiple-sessions
    • org.freedesktop.login1.hibernate-ignore-inhibit

アクションIDがわかったところで、ルールを作成しします。 これらのルールをまとめると以下のようになります:

polkit.addRule(function(action, subject) {
    if ((action.id == "org.freedesktop.login1.power-off"
      || action.id == "org.freedesktop.login1.power-off-multiple-sessions"
      || action.id == "org.freedesktop.login1.power-off-ignore-inhibit"
      || action.id == "org.freedesktop.login1.reboot"
      || action.id == "org.freedesktop.login1.reboot-multiple-sessions"
      || action.id == "org.freedesktop.login1.reboot-ignore-inhibit"
      || action.id == "org.freedesktop.login1.suspend"
      || action.id == "org.freedesktop.login1.suspend-multiple-sessions"
      || action.id == "org.freedesktop.login1.suspend-ignore-inhibit"
      || action.id == "org.freedesktop.login1.hibernate"
      || action.id == "org.freedesktop.login1.hibernate-multiple-sessions"
      || action.id == "org.freedesktop.login1.hibernate-ignore-inhibit")
      && subject.isInGroup("wheel")) {
        return polkit.Result.YES;
    }
    else {
        return polkit.Result.NO;
    }
});

これを、 /etc/polkit-1/rules.d/55-disallow-power-related-rules-for-general-user.rules のような名前で保存します。その後、polkitを止め、再度起動させます。

$ sudo systemctl stop polkit
$ sudo systemctl start polkit

/var/log/messagesのようなsyslogのログに以下のようなログが出ていれば登録に成功しています。

Dec 26 16:37:50 localhost systemd: Stopped Authorization Manager.
Dec 26 16:37:50 localhost systemd: Starting Authorization Manager...
Dec 26 16:37:50 localhost polkitd[4129]: Started polkitd version 0.112
Dec 26 16:37:50 localhost dbus[686]: [system] Successfully activated service 'org.freedesktop.PolicyKit1'
Dec 26 16:37:50 localhost dbus-daemon: dbus[686]: [system] Successfully activated service 'org.freedesktop.PolicyKit1'
Dec 26 16:37:50 localhost systemd: Started Authorization Manager.
Dec 26 16:37:51 localhost gnome-session: PolicyKit daemon reconnected to bus.

このルールが適用されているかどうかを確認してみます。 一般ユーザーのnormaluser1ユーザーを作成します。

その後、GNOME環境にログインすると、シャットダウンや再起動操作関連のUIがなくなっていることがわかります。

一般ユーザーの電源管理画面: 一般ユーザーの電源管理画面

wheelユーザーの電源管理画面: wheelユーザーの電源管理画面

まとめ

PolicyKitのルールにより、一般ユーザーの電源管理の操作を禁止する実例を提示しました。このPolicyKitの仕組みはKDEでも一部利用されています。 この他利用用途としては、特権が要求される操作に対してのルールを作成し、一般ユーザーから操作できるようにすることもできます。 例えばドライブのマウントはハードウェアが絡むため、この操作はLinuxでは通常、管理者特権が要求されます。 これに対してもPolicyKitのルールにより一般ユーザーから操作可能にすることができます。例えば、udisksに対するPolkitのアクションのリストはここを参考にすると良いです。

2016-12-27

Debian GNU/LinuxでCMakeを使ってWindows用バイナリーをビルドする方法

2016-12-26時点でのDebian GNU/Linux sidでの話です。

以前、GNU Autotoolsを使ってWindows用バイナリーをビルドする方法を紹介しましたが、ここで紹介するのはCMakeを使う方法です。

CMakeでクロスコンパイルする方法はcmake-toolchains(7)に説明があります。CMakeはクロスコンパイル用にCMAKE_TOOLCHAIN_FILEという変数を用意しています。前述のドキュメントはこの変数を使う方法を説明しています。

ただ、ファイルを作るのは少し面倒なので、ここではコマンドラインオプションでWindows用バイナリーをビルドする方法を紹介します。

用意するもの

Debian GNU/Linux上でWindows用バイナリをビルドするためにはクロスコンパイルする必要があります。そのためにMinGW-w64を使います。MinGW-w64はGCCでWindows用バイナリをビルドするために必要なヘッダーファイルやライブラリなど一式を提供します。

cmakeパッケージとmingw-w64パッケージをインストールします。wineパッケージは動作確認用です。

% sudo apt install -V cmake mingw-w64 wine

準備はこれで完了です。

ビルド

ここではCMakeでのビルドに対応しているGroongaをクロスコンパイルします。

まず、ソースコードをダウンロードして展開します。

% wget http://packages.groonga.org/source/groonga/groonga-6.1.1.tar.gz
% tar xvf groonga-6.1.1.tar.gz
% cd groonga-6.1.1

CMakeを使うときはソースコードのあるディレクトリーではなく別のディレクトリーをビルドディレクトリーにすることが多いですが、ここでは説明を簡単にするためにソースコードのあるディレクトリーを使います。

GNU Autotoolsのときは--host=x86_64-w64-mingw32を指定するだけであとはいい感じに設定してくれますが、CMakeは次のようにいくつかのパラメーターを指定します。この中でクロスコンパイルに直接関係ないパラメーターはCMAKE_INSTALL_PREFIXだけです。これはインストール先を変更するために指定しているだけです。

% PKG_CONFIG_LIBDIR=/tmp/local.windows/lib/pkgconfig \
    cmake \
      -DCMAKE_INSTALL_PREFIX=/tmp/local.windows \
      -DCMAKE_SYSTEM_NAME=Windows \
      -DCMAKE_SYSTEM_PROCESSOR=x64 \
      -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \
      -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ \
      -DCMAKE_RC_COMPILER=x86_64-w64-mingw32-windres

各パラーメーターについて説明します。

  • PKG_CONFIG_LIBDIR: pkg-config.pcファイルを検索するときに使うパス。通常は(LIBDIRではなく)PKG_CONFIG_PATHを指定するが、クロスコンパイルのときはPKG_CONFIG_LIBDIRを指定して、ビルド環境(この場合はDebian GNU/Linux)の.pcファイルを一切検索しないようにする。
  • CMAKE_SYSTEM_NAME: Windowsを指定するとCMakeに組み込まれているWindows用のビルドの設定を使ってくれる。(/usr/share/cmake-3.7/Modules/Platform/Windows.cmakeなどを使ってくれる。)CMAKE_SYSTEM_NAMEを設定するときはあわせてCMAKE_SYSTEM_VERSIONも設定するべきだが、MinGW-w64でクロスコンパイルするときはCMAKE_SYSTEM_VERSIONを使っていなそうなので省略している。
  • CMAKE_SYSTEM_PROCESSOR: 32bitのバイナリーをビルドするか64bitのバイナリーをビルドするかを指定する。32bitならx86で64bitならx64になる。ここではx64を指定しているので64bitのバイナリーをビルドする。
  • CMAKE_C_COMPILER: MinGW-w64が提供するgccのコマンド名を指定する。PATHが通っていない場合はフルパスで指定する。
  • CMAKE_CXX_COMPILER: MinGW-w64が提供するg++のコマンド名を指定する。PATHが通っていない場合はフルパスで指定する。
  • CMAKE_RC_COMPILER: MinGW-w64が提供するwindresのコマンド名を指定する。PATHが通っていない場合はフルパスで指定する。.rcファイルを使わない場合は指定しなくても大丈夫。

CMAKE_FIND_ROOT_PATH_MODE_PROGRAMなども指定した方が間違ってビルド環境にあるプログラムやライブラリーなどを見つけてしまわなくてよいのですが、Groongaのようにpkg-configを使っている場合は設定しなくても大丈夫です。

あとはmakeでビルドできます。

% make

make installするとCMAKE_INSTALL_PREFIXで指定したディレクトリー以下にインストールされます。

% make install

Wineを使うとDebian GNU/Linux上でインストールしたバイナリーで動作確認できます。Groongaのようにg++を利用している場合はlibstdc++-6.dlllibgcc_s_seh-1sehはStructured Exception Handlingの略)がないとバイナリーを実行できません。動作確認する前に${CMAKE_INSTALL_PREFIX}/bin/以下にこれらのDLLをコピーします。

% cd /tmp/local.windows/bin
% cp $(x86_64-w64-mingw32-g++ -print-file-name=libstdc++-6.dll) ./
% cp $(x86_64-w64-mingw32-g++ -print-file-name=libgcc_s_seh-1.dll) ./

これで動作確認できます。

% wine groonga.exe --version
Groonga 6.1.1 [Windows,x64,utf8,match-escalation-threshold=0,nfkc,onigmo]

configure options: <>

CPackを使えばビルドしたバイナリーを含むアーカイブを作成することができます。PGroongaCPackを使ってバイナリー入りアーカイブを作成しています。

CMakeLists.txtにCPackの設定がある場合は次のようにすればアーカイブを作成できます。

% make package

まとめ

Groongaを例にしてDebian GNU/Linux上でCMakeを使ってWindows用のバイナリーをビルドする方法を紹介しました。CMakeを使っているプロダクトをクロスコンパイルしたいときは参考にしてください。

2016-12-26

Ruby on RailsでMySQL・PostgreSQL・SQLite3とGroongaを使って日本語全文検索を実現する方法

MySQL・PostgreSQL・SQLite3の標準機能では日本語テキストの全文検索に難があります。MySQL・PostgreSQLに高速・高機能な日本語全文検索機能を追加するMroongaPGroongaというプラグインがあります。これらを導入することによりSQLで高速・高機能な日本語全文検索機能を実現できます。詳細は以下を参照してください。

ここではMroonga・PGroongaを使わずに日本語全文検索を実現する方法を紹介します。それはGroongaを使う方法です。

GroongaはMroonga・PGroongaのバックエンドで使われている全文検索エンジンです。

Groongaを直接使うメリットは以下の通りです。

  • MySQL・PostgreSQLのオーバーヘッドがない分Mroonga・PGroongaよりもさらに速い
  • 1つのSQLでは表現できないような検索を1クエリーで実現できる(のでさらに速い)

一方、デメリットは以下の通りです。

  • Mroonga・PGroongaに比べて学習コストが増える(Mroonga・PGroongaはSELECTWHEREでの条件の書き方を学習するくらいでよいが、Groongaはインデックスの設計やクエリーの書き方について学習する必要がある)
  • MySQL・PostgreSQLだけでなくGroongaサーバーも管理する必要があるので運用コストが増える

このデメリットのうち学習コストの方をできるだけ抑えつつGroongaを使えるようにするためのライブラリーがあります。それがgroonga-client-railsです。groonga-client-railsがGroongaを使う部分の多くをフォローしてくれるため利用者は学習コストを抑えたままGroongaを使って高速な日本語全文検索システムを実現できます。

この記事ではRuby on Railsで作ったアプリケーションからGroongaを使って日本語全文検索機能を実現する方法を説明します。実際にドキュメント検索システムを開発する手順を示すことで説明します。ここではCentOS 7を用いますが、他の環境でも同様の手順で実現できます。

なお、この記事ではMySQL・PostgreSQLではなくSQLite3を使っていますが、アプリケーションのコードは変更せずにMySQL・PostgreSQLでも動くので気にしないでください。

@KitaitiMakotoさんが書いたgroonga-client-railsの使い方を紹介した記事もあるのでそちらもあわせて参照してください。違った視点で紹介しているので理解が深まるはずです。

Groongaのインストール

まずGroongaをインストールします。CentOS 7以外の場合にどうすればよいかはGroongaのインストールドキュメントを参照してください。

% sudo -H yum install -y http://packages.groonga.org/centos/groonga-release-1.2.0-1.noarch.rpm
% sudo -H yum install -y groonga-httpd
% sudo -H systemctl start groonga-httpd

Rubyのインストール

CentOS 7にはRuby 2.0のパッケージがありますが、Ruby on Rails 5.0.1はRuby 2.2以降が必要なのでrbenvとruby-buildでRuby 2.3をインストールします。

% sudo -H yum install -y git
% git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
% git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
% echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
% echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
% exec ${SHELL} --login
% sudo -H yum install -y gcc make patch openssl-devel readline-devel zlib-devel
% rbenv install 2.3.3
% rbenv global 2.3.3

Ruby on Railsのインストール

Ruby on Railsをインストールします。

% sudo -H yum install -y sqlite-devel nodejs
% gem install rails

ドキュメント検索システムの開発

いよいよ日本語全文検索機能を持ったドキュメント検索システムを開発します。

まずはrails newで雛形を作ります。

% rails new document_search
% cd document_search

データベースを作成します。

% bin/rails db:create

検索対象のドキュメントを格納するテーブルを作成します。

% bin/rails generate scaffold document title:text content:text
% bin/rails db:migrate

ここまでは(Groongaのインストール以外は)Groongaと関係ない手順です。

ここからはGroongaを使う場合に特有の手順になります。

まずGemfileにgroonga-client-rails gemを追加します。

gem 'groonga-client-rails'

groonga-client-rails gemをインストールします。

% bundle install

それではアプリケーション側に全文検索機能を実装します。

まず、サーチャーというオブジェクトを定義します。これはGroongaでいい感じに全文検索するための機能を提供するオブジェクトです。

サーチャー用のディレクトリーを作成します。

% mkdir -p app/searchers

app/searchers/application_searcher.rbApplicationSearcherを作成します。(ジェネレーターはまだ実装されていません。)

class ApplicationSearcher < Groonga::Client::Searcher
end

Documentモデル用のサーチャーDocumentsSearcherapp/searchers/documents_searcher.rbに作成します。

class DocumentsSearcher < ApplicationSearcher
  # Documentモデルのtitleカラムを全文検索するためのインデックスを作成
  schema.column :title, {
    type: "ShortText",
    index: true,
    index_type: :full_text_search,
  }
  # Documentモデルのcontentカラムを全文検索するためのインデックスを作成
  schema.column :content, {
    type: "Text",
    index: true,
    index_type: :full_text_search,
  }
end

モデルのカラムとサーチャーのインデックスを対応付けるコードをモデルに追加します。

app/models/document.rb:

class Document < ApplicationRecord
  # DocumentモデルをDocumentsSearcherの検索対象とする
  source = DocumentsSearcher.source(self)
  # Documentのtitleカラムと
  # DocumentsSearcherのtitleインデックスを対応付ける
  source.title = :title
  # Documentのcontentカラムと
  # DocumentsSearcherのcontentインデックスを対応付ける
  source.content = :content
end

この対応付けをGroongaのサーバーに反映します。

% bin/rails groonga:sync

動作を確認するためにQiitaから検索対象のドキュメントを取得するRakeタスクを作ります

lib/tasks/data.rake:

require "open-uri"
require "json"

namespace :data do
  namespace :load do
    desc "Load data from Qiita"
    task :qiita => :environment do
      tag = "groonga"
      url = "https://qiita.com/api/v2/items?page=1&per_page=100&query=tag:#{tag}"
      open(url) do |entries_json|
        entries = JSON.parse(entries_json.read)
        entries.each do |entry|
          Document.create(title:   entry["title"],
                          content: entry["body"])
        end
      end
    end
  end
end

実行して検索対象のドキュメントを作成します。

% bin/rails data:load:qiita

http://localhost:3000/documentsにアクセスし、データが入っていることを確認します。

Qiitaのデータをロード

ビューにヒット件数表示機能と検索フォームをつけてコントローラーで全文検索するようにします。

検索フォームではqueryというパラメーターに検索クエリーを指定することにします。

@documents@result_setに変更している理由はあとでわかります。端的に言うとヒットしたドキュメントだけでなくさらに情報も持っているので@result_set(結果セット)にしています。たとえば、「ヒット数」(@result_set.n_hits)も持っています。SQLでは別途SELECT COUNT(*)を実行しないといけませんが、Groongaでは1回の検索で検索結果もヒット数も両方取得できるので効率的です。

なお、ヒットしたドキュメントに対応するDocumentモデルは@result_set.records.each {|record| record.source}でアクセスできます。そのため、モデルが必要な処理(たとえばURLの生成)もこれまで通りの方法で使えます。

app/views/documents/index.html.erb:


 <h1>Documents</h1>

+<p><%= @result_set.n_hits %> records</p>
+
+<%= form_tag(documents_path, method: "get") do %>
+  <%= search_field_tag "query", @query %>
+  <%= submit_tag "Search" %>
+<% end %>
+
 <table>
   <thead>
     <tr>
@@ -12,7 +19,8 @@
   </thead>

   <tbody>
-    <% @documents.each do |document| %>
+    <% @result_set.records.each do |record| %>
+      <% document = record.source %>
       <tr>
         <td><%= document.title %></td>
         <td><%= document.content %></td>

app/controllers/documents_controller.rb:

@@ -4,7 +4,11 @@ class DocumentsController < ApplicationController
   # GET /documents
   # GET /documents.json
   def index
-    @documents = Document.all
+    searcher = DocumentsSearcher.new
+    @result_set = searcher.search.
+      query(@query).
+      result_set
   end

   # GET /documents/1

この状態で次のようにレコード数とフォームが表示されるようになります。

フォームを追加

また、この状態で日本語全文検索機能を実現できています。確認してみましょう。

フォームに「オブジェクト」と日本語のクエリーを入力します。元のドキュメントは100件あり、「オブジェクト」で絞り込んで11件になっています。日本語で全文検索できていますね。

「オブジェクト」で検索

次のようにOR検索もできます。「オブジェクト」単体で検索したときの11件よりも件数が増えているのでORが効いていることがわかります。

「オブジェクト OR API」で検索

全文検索エンジンならではの機能を利用

これで基本的な全文検索機能は実現できていますが、せっかく全文検索エンジンを直接使って検索しているので全文検索エンジンならではの機能も使ってみましょう。

ドリルダウン

まずはドリルダウン機能を使います。ドリルダウンとはある軸に注目して情報を絞り込んでいくことです。例えば、商品カテゴリーに注目して商品を絞り込む(例:家電→洗濯機→ドラム式)、タグに注目して記事を絞り込むといった具合です。

まずは各ドキュメントにタグを付けられるようにしましょう。

タグを作ります

% bin/rails generate scaffold tags name:string

ドキュメントとタグを結びつける関連テーブルを作ります

% bin/rails generate model tagging document:references tag:references

スキーマを更新します。

% bin/rails db:migrate

モデルに関連情報を追加します。

app/models/document.rb:

@@ -1,4 +1,7 @@
 class Document < ApplicationRecord
+  has_many :taggings
+  has_many :tags, through: :taggings
+
   source = DocumentsSearcher.source(self)
   source.title = :title
   source.content = :content

app/models/tag.rb:

@@ -1,2 +1,4 @@
 class Tag < ApplicationRecord
+  has_many :taggings
+  has_many :documents, through: :taggings
 end

Qiitaのデータからタグ情報もロードするようにします。

lib/tasks/data.rake:

@@ -10,8 +10,12 @@ namespace :data do
       open(url) do |entries_json|
         entries = JSON.parse(entries_json.read)
         entries.each do |entry|
+          tags = entry["tags"].collect do |tag|
+            Tag.find_or_create_by(name: tag["name"])
+          end
           Document.create(title:   entry["title"],
-                          content: entry["body"])
+                          content: entry["body"],
+                          tags:    tags)
         end
       end
     end

データベース内のデータを削除してQiitaのロードし直します。

% bin/rails runner Document.destroy_all
% bin/rails data:load:qiita

ビューにタグ情報も表示します。

app/views/documents/index.html.erb:

@@ -14,6 +14,7 @@
     <tr>
       <th>Title</th>
       <th>Content</th>
+      <th>Tags</th>
       <th colspan="3"></th>
     </tr>
   </thead>
@@ -24,6 +25,13 @@
       <tr>
         <td><%= document.title %></td>
         <td><%= document.content %></td>
+        <td>
+          <ul>
+          <% document.tags.each do |tag| %>
+            <li><%= tag.name %></li>
+          <% end %>
+          </ul>
+        </td>
         <td><%= link_to 'Show', document %></td>
         <td><%= link_to 'Edit', edit_document_path(document) %></td>
         <td><%= link_to 'Destroy', document, method: :delete, data: { confirm: 'Are you sure?' } %></td>

「Tags」カラムにタグがあるのでタグがロードされていることを確認できます。

タグがロードされている

それではこのタグ情報を使ってドリルダウンできるようにします。

Groongaでタグ情報を使えるようにするにはサーチャーとモデルにタグ情報を使うというコードを追加します。

app/searchers/documents_searcher.rb:

@@ -9,4 +9,11 @@ class DocumentsSearcher < ApplicationSearcher
     index: true,
     index_type: :full_text_search,
   }
+  schema.column :tags, {
+    type: "ShortText",
+    reference: true,   # 文字列でドリルダウンをするときは指定すると高速になる
+    normalizer: false, # タグそのもので検索する
+    vector: true,      # 値が複数あるときは指定する
+    index: true,
+  }
 end

app/models/document.rb:

@@ -5,4 +5,7 @@ class Document < ApplicationRecord
   source = DocumentsSearcher.source(self)
   source.title = :title
   source.content = :content
+  source.tags = ->(model) do
+    model.tags.collect(&:name) # タグモデルではなくタグ名をGroongaに渡す
+  end
 end

マッピングを変更したらgroonga:syncで同期します。

% bin/rails groonga:sync

これでGroongaでタグ情報を使えるようになりました。フォームに「tags:@全文検索」と入力すると「全文検索」タグで絞り込めます。(tags:@...は「tagsカラムの値を検索する」というGroongaの構文です。Googleのsite:...に似せた構文です。)

「全文検索」タグで検索

ユーザーにとっては、タグをキーボードから入力して絞り込む(ドリルダウンする)のは面倒なので、クリックでドリルダウンできるようにします

コントローラーには次の2つの処理を追加しています。

  • クエリーパラメーターとしてtagが指定されていたらfilter("tags @ %{tag}", tag: tag)でタグ検索をする条件を追加する。
  • タグでドリルダウンするための情報(どのタグ名で絞りこめるのか、また、絞り込んだらどのくらいの件数になるのか、という情報)を取得する

「タグでドリルダウンするための情報を取得する」とはSQLでいうと「GROUP BY tagの結果も取得する」という処理になります。SQLではGROUP BYの結果も取得すると追加でSQLを実行しないといけませんが、Groongaでは1回のクエリーで検索もヒット数の取得もドリルダウン用の情報も取得できるので効率的です。

app/controllers/documents_controller.rb:

@@ -5,9 +5,16 @@ class DocumentsController < ApplicationController
   # GET /documents.json
   def index
     @query = params[:query]
+    @tag = params[:tag]
+
     searcher = DocumentsSearcher.new
-    @result_set = searcher.search.
-      query(@query).
+    request = searcher.search.query(@query)
+    if @tag.present?
+      request = request.filter("tags @ %{tag}", tag: @tag)
+    end
+    @result_set = request.
+      drilldowns("tag").keys("tags").
+      drilldowns("tag").sort_keys("-_nsubrecs").
       result_set
   end

ビューではクリックでドリルダウンできる(タグで絞り込める)ようにリンクを表示します。

app/views/documents/index.html.erb:

@@ -5,10 +5,21 @@
 <p><%= @result_set.n_hits %> records</p>

 <%= form_tag(documents_path, method: "get") do %>
+  <%= hidden_field_tag "tag", @tag %>
   <%= search_field_tag "query", @query %>
   <%= submit_tag "Search" %>
 <% end %>

+<nav>
+  <% @result_set.drilldowns["tag"].records.each do |record| %>
+  <%= link_to_unless @tag == record._key,
+                     "#{record._key} (#{record._nsubrecs})",
+                     url_for(query: @query, tag: record._key) %>
+  <% end %>
+  <%= link_to "タグ絞り込み解除",
+              url_for(query: @query) %>
+</nav>
+
 <table>
   <thead>
     <tr>
@@ -27,8 +38,10 @@
         <td><%= document.content %></td>
         <td>
           <ul>
-          <% document.tags.each do |tag| %>
-            <li><%= tag.name %></li>
+          <% record.tags.each do |tag| %>
+            <li><%= link_to_unless @tag == tag,
+                                   tag,
+                                   url_for(query: @query, tag: tag) %></li>
           <% end %>
           </ul>
         </td>

これで次のような画面になります。「全文検索 (20)」というリンクがあるので、「全文検索」タグでドリルダウンすると「20件」ヒットすることがわかります。

タグでドリルダウンできる

「全文検索 (20)」のリンクをクリックすると「全文検索」タグでドリルダウンできます。たしかに20件ヒットしています。

「全文検索」タグでドリルダウン

ここからさらにキーワードで絞り込むこともできます。以下はさらに「ruby」で絞り込んだ結果です。ヒット数がさらに減って3件になっています。

「全文検索」タグでドリルダウンして「ruby」で全文検索

全文検索エンジンの機能を使うと簡単・高速にドリルダウンできるようになります。

キーワードハイライト

検索結果を確認しているとき、キーワードがどこに含まれているかがパッとわかると目的のドキュメントかどうかを判断しやすくなります。そのための機能も全文検索エンジンならではの機能です。

highlight_html()を使うとキーワードを<span class="keyword">...</span>で囲んだ結果を取得できます。

snippet_html()を使うとキーワード周辺のテキストを取得できます。

これらを使ってキーワードをハイライトするには次のようにします。

app/controllers/documents_controller.rb:

@@ -13,6 +13,12 @@ class DocumentsController < ApplicationController
       request = request.filter("tags @ %{tag}", tag: @tag)
     end
     @result_set = request.
+      output_columns([
+                       "_key",
+                       "*",
+                       "highlight_html(title)",
+                       "snippet_html(content)",
+                     ]).
       drilldowns("tag").keys("tags").
       drilldowns("tag").sort_keys("-_nsubrecs").
       result_set

app/views/documents/index.html.erb:

@@ -34,8 +34,16 @@
     <% @result_set.records.each do |record| %>
       <% document = record.source %>
       <tr>
-        <td><%= document.title %></td>
-        <td><%= document.content %></td>
+        <td><%= record.highlight_html.html_safe %></td>
+        <td>
+          <% if record.snippet_html.present? %>
+            <% record.snippet_html.each do |chunk| %>
+              <div>...<%= chunk.html_safe %>...</div>
+            <% end %>
+          <% else %>
+            <%= document.content %>
+          <% end %>
+        </td>
         <td>
           <ul>
           <% record.tags.each do |tag| %>

app/assets/stylesheets/documents.scss:

@@ -1,3 +1,7 @@
 // Place all the styles related to the documents controller here.
 // They will automatically be included in application.css.
 // You can use Sass (SCSS) here: http://sass-lang.com/
+
+.keyword {
+  color: red;
+}

「全文検索」タグでドリルダウンして「ruby」で全文検索した状態では次のようになります。どこにキーワードがあるかすぐにわかりますね。

「全文検索」タグでドリルダウンして「ruby」で全文検索した結果をハイライト

スコアでソート

検索結果の表示順はユーザーが求めていそうな順番にするとユーザーはうれしいです。

Groongaはスコアという数値でどれだけ検索条件にマッチしていそうかという情報を返します。スコアでソートすることでユーザーが求めていそうな順番にできます。

@@ -8,7 +8,12 @@ class DocumentsController < ApplicationController
     @tag = params[:tag]

     searcher = DocumentsSearcher.new
-    request = searcher.search.query(@query)
+    request = searcher.search
+    if @query.present?
+      request = request.
+        query(@query).
+        sort_keys("-_score")
+    end
     if @tag.present?
       request = request.filter("tags @ %{tag}", tag: @tag)
     end
ページネーション

groonga-client-railsは標準でページネーション機能を提供しています。Kaminariと連携することでページネーションのUIもすぐに作れます。

Gemfile:

@@ -53,3 +53,4 @@ end
 gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

 gem 'groonga-client-rails'
+gem 'kaminari'

app/controllers/documents_controller.rb:

@@ -26,6 +26,7 @@ class DocumentsController < ApplicationController
                      ]).
       drilldowns("tag").keys("tags").
       drilldowns("tag").sort_keys("-_nsubrecs").
+      paginate(params[:page]).
       result_set
   end

app/views/documents/index.html.erb:

@@ -2,7 +2,7 @@

 <h1>Documents</h1>

-<p><%= @result_set.n_hits %> records</p>
+<p><%= page_entries_info(@result_set, entry_name: "documents") %></p>

 <%= form_tag(documents_path, method: "get") do %>
   <%= hidden_field_tag "tag", @tag %>
@@ -63,4 +63,6 @@

 <br>

+<%= paginate(@result_set) %>
+
 <%= link_to 'New Document', new_document_path %>

画面の上にはページの情報が表示されます。

ページの情報

画面の下にはページを移動するためのリンクが表示されます。

ページネーション

まとめ

MySQL・PostgreSQL・SQLite3とGroongaを使ってRuby on Railsアプリケーションで日本語全文検索機能を実現する方法を説明しました。単に全文検索できるようにするだけではなく、ドリルダウンやハイライトといった全文検索ならではの機能の実現方法も紹介しました。

Groongaを使いたいけど学習コストが増えそうだなぁと思っていた人は試してみてください。実際に試してみて詰まった場合や、ここには書いていないこういうことをしたいけどどうすればいいの?ということがでてきた場合は以下の場所で相談してください。

Groongaを用いた全文検索アプリケーションの開発に関するご相談は問い合わせフォームからご連絡ください。

Groonga関連の開発・サポートを仕事にしたい方は採用情報を確認の上ご応募ください。

タグ: Ruby | Groonga
2016-12-22

PGConf.ASIA 2016:PGroonga – PostgreSQLを全言語対応高速全文検索プラットフォームに! #pgconfasia

2016年12月1日から12月3日にかけて開催されたPostgreSQLの国際カンファレンスPGConf.ASIA 2016PGroongaというPostgreSQLの全文検索モジュールの話をしました。

関連リンク:

内容

前半はPostgreSQLの全文検索まわりの課題の説明とPGroongaがどうやってそれらの課題を解決するかという内容になっています。

後半はPGroongaの説明になっています。

PostgreSQLの全文検索まわりの課題は次の通りです。

  • PostgreSQLが標準で提供する全文検索機能(textesarchcontrib/pg_trgm)は日本語や中国語などアジア圏の言語をサポートしていない。(アジア圏の言語以外にもサポートしていない言語はある。)
  • pg_bigmというモジュールを導入することで全言語をサポートできるがヒット数が増えると遅くなりがち。

PGroongaはこれらの課題を解決します。PGroongaは全言語対応でヒット数が増えても高速な全文検索機能を提供します。

以下はpg_bigmとの検索時間の比較です。棒が短いほど速いです。pg_bigm(青い棒)は極端に遅くなるケースがありますが、PGroonga(紫の棒)は安定して高速です。

PGroongaとpg_bigmの検索時間

この検索時間の比較はデータとして日本語版Wikipediaを用いています。他にもデータとして英語版Wikipediaを用いて、PGroongaとPostgreSQLが標準で提供する全文検索機能を比較したデータもあります。詳細はスライドの内容あるいは以下のドキュメントを参照してください。

後半のPGroongaの説明では以下のことに触れました。詳細はスライドと以下のリストに含まれているリンクを参考にしてください。

まとめ

PGConf.ASIA 2016でPGroongaについて話しました。現在のPostgreSQLの全文検索機能には課題がありますが、PGroongaを組み込むことで、PostgreSQLでアジア圏の言語(もちろん日本語も含む!)でも実用的な全文検索機能を実現することができます。PostgreSQLでの全文検索機能でこまったらPGroongaを検討してみてください。

つづき: 2017-01-05
タグ: Groonga
2016-12-06

SourceForge.netからOSDNへ移行する方法

以前、OSDNのファイルリリース機能をAPI経由で使うにはという記事で機能を紹介したきりになっていましたが、milter managerというプロジェクトで全面的にSourceForge.netからOSDNに移行したので備忘録を兼ねて記事にします。

移行の理由は以下の通りです。

  • SourceForge.netの度重なる仕様変更に追従するのが大変だった
    • ファイルのダウンロードリンクが変わってもリダイレクトもされないので辛かった
  • SourceForge.netのミラーが減った
    • ミラー先にファイルがないことがある
    • リダイレクト先が国内にサーバーがあるのに海外のサーバーになることがある
  • SourceForge.net運営による乗っ取りやファイル改竄が複数あった

milter managerは最初のバージョンをリリースした2009年から7年間SourceForge.netにお世話になってきました。SourceForge.netがなかったらmilter managerの開発にはもっと手間と費用がかかっていたはずです。SourceForge.netには大変感謝しています。

前提として、ソースコードリポジトリは既にGitHubに移行済みとします*1

SourceForge.net で使用していた機能

機能 移行可否 備考
メーリングリスト ok 英語のメーリングリストを作るときは、管理画面から言語設定を変更する。データの移行はできない。
フォーラム ok データの移行はできない。ほとんど使用されていなかった
Webサイトホスティング ok rsyncが使えるのでそのままのディレクトリ構造で丸ごと移行できる。SourceForge.net側に.htaccessでリダイレクト設定ができる。
ファイルリリース ok 自動化もできた
ニュース ok なし
apt/yumリポジトリ ng OSDNにはapt/yumをホストできる機能がないのでpackagecloudに移行することにした

機能的にはほとんどの部分で代替できることがわかったので、移行することにしました。

メーリングリストは以下の4つを作成していました。

  • milter-manager-users-ja: 日本語のわかる人向け
  • milter-manager-users-en: 英語のわかる人向け
  • milter-manager-commit: コミットメール用
  • milter-manager-bugs: バグ報告用

milter-manager-bugsはほとんど使用されていなかったので移行しないことに決めました。それ以外の3つはOSDNでメーリングリストを作成しました。このときmilter-manager-users-enは英語のわかる人向けなので入会用ページの表示言語を英語にしました。なお、メーリングリストに登録されているメンバーは数が少なかったので手動で移行しました。

Webサイトのホスティングは問題なくできました。rsyncを使ってSourceForge.netのときと同じディレクトリ構成でアップロードすることができました。 SourceForge.net側で.htaccessにリダイレクトの設定をすることができるので、301リダイレクトの設定をしておきました。

Redirect permanent / https://milter-manager.osdn.jp/

ファイルリリースの自動化については以前の記事を参照してください。

SourceForge.netのときもニュース機能を使用していましたが、この記事を書くまで存在を忘れていました*2

packagecloudについて

OSDNにはapt/yumリポジトリを置けない問題があるのでpackagecloudにapt/yumリポジトリについてはpackagecloudに移行することにしました。

packagecloudではRubyGems,deb,RPMなどいくつかのパッケージを配布することができます。価格を見るとオープンソースのプロジェクトだと25GBの容量を無料で使えることがわかりました。milter managerで使用するために申し込んだところ、ほぼ即日使えるようになりました。無料で使わせてもらうかわりに、Assets and Brand Guidelinesにある画像を使ってリンクを張る必要がありました。

パッケージのアップロードもコマンド一発で簡単にできました。

一つだけ問題があってDebian GNU/Linuxのsid(unstable)向けのパッケージを配布できるようにはなっていませんでした。理由をサポートに問い合わせたところ

sidは日々パッケージ構成が変わるのでpackagecloud側で動作環境を保証できないため提供していない

とのことでした*3。この件については、milter managerをなるべく早くDebianのオフィシャルパッケージにすることで対応しようと考えています。なお、サポートの返信はとても早く対応もとても丁寧でした。

Launchpadなどと違ってローカルでパッケージをビルドする必要がありますが、ビルドしたパッケージの動作確認をしてからアップロードできるという利点があります。パッケージのビルド部分はそのままで、アップロード部分のみの変更で済んだのもmilter managerプロジェクトとしては利点でした。Launchpadのようにビルドサーバーも提供しているようなサービスの場合、必要なファイルをアップロードしてからパッケージをビルドするため、ビルドに失敗したり、ビルドしたパッケージに問題があった場合に再アップロードが必要となるため修正するのに時間がかかるという欠点があります。

具体的にやったこと

  1. OSDNにプロジェクト作成
  2. packagecloudをOSSプランで使えるように手続き
  3. 古いファイル(deb/rpm)をpackagecloudにアップロード
  4. ファイルリリースに置いていた古いファイルをOSDNにアップロード
  5. メーリングリスト開設・設定変更
  6. メーリングリストメンバー移行
  7. SourceForge.netに置く .htaccess のテスト
  8. ビルドスクリプトの修正
  9. ドキュメントの修正
  10. 移行と新しいバージョンのリリース

まとめ

他の作業と並行して進めていたので、2ヶ月弱くらいかかりましたが特にハマることなく移行できました。 メーリングリストや rsync によるウェブサイトホスティング等、GitHub や Bitbucket 等にはない便利な機能があるのでSourceForge.netからの移行先としてOSDNを検討してみるのはいかがでしょうか。

*1 OSDNではGit以外にMercurial,Subversion,Bazaarが使えるようです

*2 リリース時はスクリプトによる自動投稿だったため

*3 現時点で次の次のstable(Debian 10)になる予定のbuster向けのパッケージは置けるようになっていました

2016-12-05

新装版 達人プログラマー

2016年10月にオーム社から「新装版 達人プログラマー 職人から名匠への道」というプログラマーとしてよい仕事をするための本が出版されました。

新装版 達人プログラマー 職人から名匠への道
Andrew Hunt/David Thomas/村上雅章
オーム社
¥ 3,456

内容

本書は1999年に出版された英語の本の翻訳です。そのため、内容は1999年当時のものです。そう聞くと時代遅れの内容だと感じます。しかし、驚くべきことに2016年のいま読んでも時代遅れになっている内容がほとんどありません。これは、本書の内容が一過性のものではなく時代に左右されにくい本質的なことだったということです。

そのため、これからプログラマーとしてよい仕事をしたい若い人にオススメしたい本です。(一過性の内容ではなく)本質的な内容を学べることはすばやくレベルアップすることにつながるからです。

ただ、若い人はまだピンとこない内容かもしれません。ある程度経験がないとピンとこないこともあるためです。ピンとくるかどうか不安な人は、本屋で立ち読みでもよいので「第1章 達人の哲学」を読んでみてください。30ページくらいです。ここだけでピンとくるか判断できるくらいの量の本質的な内容があります。不安な人はまずは第1章を確認するとよいでしょう。オーム社の新装版 達人プログラマーのページでサンプルファイルをダウンロードできるのですが、そのファイルでは第1章の途中まで確認できます。こちらも活用してください。

参考までに第1章の最初の段落を引用します。

常人と達人プログラマーとの違いは何でしょうか? それは、問題に対するアプローチとその解決手段についての考え方、スタイル、哲学であると言っていいでしょう。達人プログラマーは、眼前の問題を考えるだけでなく、常にその問題をより大きな背景の中で捉え、常により大きな問題を見つけ出そうとするのです。要するに、大きな背景を捉えることなしに、達人たり得る方法はありませんし、知的な解決、見識のある決定を行う方法もあり得ないのです。

若い人はこれからどんなプログラマーとしてやっていきたいか考えてみてください。与えられた作業を指示された方法で実現するプログラマーとしてやっていきたいでしょうか。なぜその作業をする必要があるのか、問題を解決する方法は提示された方法で妥当なのか、そもそもその問題設定は妥当なのか、そんなことを考え、よりよい決定をしながら問題を解決していく、そんなプログラマーとしてやっていきたいでしょうか。

2000年にもこの本の翻訳書が出版されていました。当時からよい内容という評価だったため、10年以上前にその本を読んだことがある人もいるでしょう。そんな人はぜひもう一度本書を読んでみてください。10年以上前にこうあるべきだよなぁと思ったことをいま実現できているかを確認する機会になるはずです。もしかしたら、当時はピンときていなかった内容が今はとてもグッとくる内容になっているかもしれません。

ぜひ再び本書を読んでみてください。

まとめ

よい仕事をしたいプログラマー向けの本、「新装版 達人プログラマー 職人から名匠への道」を紹介しました。よい仕事をしたいプログラマーは若い人でも若くない人でもぜひ読んでみてください。本質的な内容が書いてあります。

2016-12-02

OSS Gateワークショップ2016-11-26を開催 #oss_gate

2016年11月26日にクラウドワークスさんOSS Gateワークショップを開催しました。東京でのワークショップ開催は今回で6回目です。今回は24名の参加でした。

同日に札幌でもOSS Gateワークショップを開催していましたが、ここでの話は東京の話です。

以下、ワークショップ運営視点で今回のワークショップについてまとめます。

今回のチャレンジ

OSS Gateワークショップはもっとよくしていくために毎回課題を解決することにチャレンジしています。

今回のチャレンジは次の2点です。

  1. 「進行役」未経験者がほとんど事前準備なしで「進行役」をやってみる
  2. ワークショップ後に公式の「懇親会のような会」をやってみる
進行役

OSS Gateワークショップは特定のだれかがいなくても開催できるように整備を進めています。今回はほとんど事前準備なしで「進行役」をできるかチャレンジしました。

OSS Gateワークショップではいくつかの役割があります。そのうちの1つが「進行役」です。進行役はワークショップ内容の説明や時間の管理などをします。

ワークショップの内容はシナリオという形でテキストでまとめたものとそれをかいつまんで説明用のスライドにしたものを用意してあります。それらの既存の資料を使うことでほとんど事前準備なしで進行役をできるかチャレンジしました。

今回はたださんが進行役にチャレンジしました。たださんは以下の状態です。

  • これまで1度も進行役をやったことがない
  • ワークショップには数回参加している
  • ワークショップでは数回「サポートメンター」をやっている

たださんがほとんど事前準備なしで進行役をできれば、「サポートメンター」(これも役割の1つ)を数回やった人はほとんど事前準備なしで進行役をできそうです。それを検証しました。

結果はほとんど事前準備なしで進行役をできることがわかりました。これは、参加者からのアンケート結果とたださん自身の感想から判断しました。

よって、「サポートメンター」経験者を増やすようにワークショップを続けていけば「進行役」不足にはならなそう(= 「進行役」に関しては特定のだれかがいなくても大丈夫そう)です。ということで、次回からは「サポートメンター」経験者を増やすチャレンジをします。

なお、同日開催の札幌でのワークショップも「進行役」未経験者が「進行役」をしましたが、アンケート結果によると札幌もうまく「進行役」をできていたようです。

懇親会のような会

今回からワークショップ後に公式な懇親会のような会を実施しました。これまでも何度か数人でワークショップ後にご飯を食べながら反省会のようなことをしたことがありました。そういう集まりが有用なら公式に開催するのがよいかもしれないということで今回は公式の懇親会の開催をチャレンジしました。

懇親会には13名が参加しました。懇親会では今後どうしていこうかということをざっくばらんに話すことができたり、次回のワークショップ参加を促せたりしたので有用なことがわかりました。

そのため、次回以降も何度か開催して検証する予定です。念のため、今回だけが有用だったのか、それとも多くの場合で有用かを検証します。

今後のOSS Gate

OSS Gateは2015年(去年)の10月から始まった取り組みです。10月は始めることを宣言した月で、実際に活動を始めたのは同年12月のOSS Gate を立ち上げようからです。そのため、今年の12月で活動開始から1年経ちます。

そこで、この1年の活動をふりかえり、次の1年をどうやっていくかを相談する集まりOSS Gate ふりかえり 2016を開催します。OSS Gateに興味ある方はぜひ参加して一緒にOSS Gateの今後のことを相談しましょう。なお、ワークショップに1度も参加したことがない人でも「OSS開発に参加する人を増やしたい!」という人なら問題ありません。また、ワークショップに(「メンター」ではなく)「ビギナー」で参加した人でも問題ないのでぜひ参加してください。

2年目はワークショップの開催地域が増えそうです。現在は、東京と札幌で開催していますが、2年目は沖縄と関西でも開催できそうです。まだ開催されていない地域での開催に興味のある方はぜひGitterで相談してください。開催に向けて調整しましょう。

ワークショップはこれからも奇数月の最終土曜日に開催します。次回は2017年1月28日です。現時点で空きは2名です。興味のある方は予定を空けて早めに申し込んでください。次回もビギナーの数に対してメンターが不足しているので、OSS開発経験者(これまでのワークショップに参加したビギナーの人も経験者ですよ!)はぜひメンターとして参加してください。

まとめ

11月26日に東京では6回目になるOSS Gateワークショップを開催しました。

今回は次のことにチャレンジしました。

  1. 「進行役」未経験者がほとんど事前準備なしで「進行役」をやる
  2. ワークショップ後に懇親会を開催する

どちらもよさそうな結果だったので今後も継続します。なお、前者に関連するので、今後は「サポートメンター」の増加にも取り組みます。

今後は次のようなイベントがあるので興味のある方はぜひお越しください。

なお、札幌でのワークショップは次回は2017年1月21日に開催される予定です。イベントページは近日公開予定です。

つづき: 2017-01-05
2016-11-28

2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|
タグ:
RubyKaigi 2015 sponsor RubyKaigi 2015 speaker RubyKaigi 2015 committer RubyKaigi 2014 official-sponsor RubyKaigi 2014 speaker RubyKaigi 2014 committer RubyKaigi 2013 OfficialSponsor RubyKaigi 2013 Speaker RubyKaigi 2013 Committer SapporoRubyKaigi 2012 OfficialSponsor SapporoRubyKaigi 2012 Speaker RubyKaigi2010 Sponsor RubyKaigi2010 Speaker RubyKaigi2010 Committer badge_speaker.gif RubyKaigi2010 Sponsor RubyKaigi2010 Speaker RubyKaigi2010 Committer
SapporoRubyKaigi02Sponsor
SapporoRubyKaigi02Speaker
RubyKaigi2009Sponsor
RubyKaigi2009Speaker
RubyKaigi2008Speaker