-
-
Save KitaitiMakoto/3cefda0e6c88698116a6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
table_create Apehuci TABLE_HASH_KEY ShortText | |
column_create Apehuci content COLUMN_SCALAR Text | |
column_create Apehuci tags COLUMN_VECTOR ShortText | |
column_create Apehuci title COLUMN_SCALAR ShortText | |
table_create ApehuciTerms TABLE_PAT_KEY ShortText --default_tokenizer TokenMecab --normalizer NormalizerAuto | |
load --table Apehuci | |
[ | |
["_key","content","tags","title"], | |
["2015/11/01.html","Sqale+tDiaryで書いていた前の日記から、GitHub Pages+Middleman-Blogに移行を始めた。はてなブログなんかのサービスにしようか、迷ったけれど結局静的サイトジェネレーターを選んだ。 サービスだと、更に移行する時にコンテンツを引き継ぐのが面倒そうだったからだ。あと、やるかやらないか分からないけど、というか、今までのtDiaryの運用を見ているとやらなさそうではあるけど、JavaScriptとか自由に使って遊べるほうがいいかな、というくらい。GitHub Pages使うとGitを使うわけで、携帯とかから更新できなくて時代に逆行している感じがあるけど、まあ、まあ。Werckerとか使って、特定のブランチで書いたら(そこまではGitHub上のエディターでできる)自動で更新、とかやってもいいしね、必要になったら。",[],"日記を移行し始めた"], | |
["2015/11/03.html","Rubyでは、文字列(String)クラスにlengthというメソッドがあって、これは文字列の長さを返してくれる。「文字列の長さ」というのは一体何なのだ、というのは実は自明ではない(「実は」とか言ってみたけど、みんな、知っている気がするな)。文字の数かも知れないし、バイト(オクテット)の長さかも知れない。RubyのString#lengthの場合は、文字の数を返す。バイト数が欲しければbytesizeメソッドを使う。余談だけど、Rubyで文字を扱おうと思ったら、るびまのRuby M17N の設計と実装をぜひ読んだほうがいい。さて長さに戻って、文字の長さというのは、例えばこういうことだ。# coding: utf-8\n# ソースコードファイルのエンコーディングをUTF-8とする\n\n\"a\".length #=> 1\n\"a\".bytesize #=> 1\n\n\"あいう\".length #=> 3 三文字の文字列\n\"あいう\".bytesize #=> 9 ソースコードファイルがUTF-8なので、文字列リテラルもUTF-8になり、バイト数は9になる\n\n\"あいう\".encode(\"UTF-16LE\").length #=> 3\n\"あいう\".encode(\"UTF-16LE\").bytesize #=> 6\n\n# UTF-16でエンディアンを明示しない場合は、(ファイルではなく文字列オブジェクト自体に)2バイトのBOMが付く\n\"あいう\".encode(\"UTF-16\").length #=> 4\n\"あいう\".encode(\"UTF-16\").bytesize #=> 8\n(今どき断らなくていいとは思うけど、ここでは1バイトは1オクテット=8ビット)UTF-16にはサロゲートペアという物があって、多くの文字は一文字あたり16ビット(2バイト)なんだけど、サロゲートペアを使って表す文字は一文字表すのに32ビット(4バイト)使う。\"𩸽\".bytesize #=> 4\nそれでも、Rubyはこれを「一文字」として数えてくれる。\"𩸽\".encode('UTF-16LE').length #=> 1\n人間にとってとても分かり易い。JavaScriptではこれは「長さ」が2となるらしい(JavaScriptでのサロゲートペア文字列のメモ)。16ビットが何個分か、という数え方のようだ。JavaScritpでは内部エンコーディングがUTF-16らしいから、処理系の設計者にとってこれが自然だったんだろう。ここまでなら、Rubyは人間に優しい言語ですね、よかったよかった、となる。しかしたまに困ることがある。この前気まぐれで、Nokogiri::XML::RangeというRubyGemを作った。これは、ブラウザーのマウスで選択した部分を表したりする時に使うDOM Rangeという仕様を、Nokogiriを使って実装してみた物だ。これを書く時に、文字列の「長さ」を扱う必要があった。長さとは一体何なのか、仕様書の中を探していくとThe length attribute must return the number of code units in data. (length属性はデータのcode unitの数を返す)という表現に行き着く(https://dom.spec.whatwg.org/#dom-characterdata-length)。更に、この「code unit」のリンクを踏むと、The value of the string token is the sequence of 16 bit unsigned integer code units (hereafter referred to just as code units) corresponding to the UTF-16 encoding of S. (文字列トークンの値は、文字列SのUTF-16エンコーディングに対応する16ビット符号なし整数のcode unitの列(以後、単にcode unitとする)である)という表現が現れる(https://heycam.github.io/webidl/#dfn-code-unit)。ここだけ切り取って翻訳するのは僕には難しかったので、できれば前後まとめて読んでほしいけど、要は「16ビットが何個あるか」を文字列の「長さ」とする、ということだ。UTF-16では多くの場合一文字が16ビットで表現されるので、この長さは直感と一致する。でもさっきの「𩸽(ほっけ、らしい)」の場合は32ビットなので、一文字でも「長さ」は2になる。どうもUnicodeか何かの規格でも、「UTF-16 length」という物が定義されていて、ここで言う「長さ」と同様の物らしい。正直あんま調べる気の起きないところなので教えてもらったツイートをそのまま貼る:@KitaitiMakoto ですね。サロゲ以外にも合成文字とかもあります。utf-16にエンコードしたときの2バイト単位の長さと考えるば良いかと。— OE Waku (@wakufactory) 2015, 11月 3Nokogiri::XML::Rangeで扱う対象はNokogiriで扱う対象なので、文字エンコーディングが何になるかは分からない、決め打ちできない。その前提で、途中で「長さ」を扱うために、一旦UTF-16に変換して長さを数える、という処理を入れざるを得なかった。多分、この「長さ」は実際には人間の感じる一文字、つまりRubyのString#lengthの値にしても殆どの場合問題ないだろうなと思いつつ、ライブラリーなのでそうではない場合も一応扱えないといけない、ということでパフォーマンスが落ちるの覚悟でこんなことをしないといけないのはもやもやした。もう一つ困ったことがある。EPUB CFIの仕様でも、文字を数えるのに「UTF-16 length」を扱うことだ(上のツイートの「UTF-16 length」というのはこの仕様の表現を使った)。EPUB CFIを非常に大雑把に説明すると、「EPUBファイルの中のある一点、もしくはある範囲を表現する物」だ。EPUBの読む部分は多くの場合XHTMLになっているので、テキスト中のある一点(一文字)を指す場合には、「DOMツリー中の親要素までのパス+文字オフセット」という物を使うことになる。例えばこういう風な見た目をしている。book.epub#epubcfi(/6/4[chap01ref]!/4[body01]/10[para05]/3:10)\n全体の意味を知りたい場合は仕様なり解説記事なりを読んでほしいけど、最後の「:10」というのが文字オフセットの部分だ。対象テキストノードの10文字目、ということになる。「文字目」と言ったが実際にはUTF-16 lengthなので、人間的な感覚の文字数とは限らない。EPUB CFIは表現の仕様であって、用途について決まった物があるわけではないけど、例えば、ウェブページのURIのフラグメントのように、文書の途中にリンクを貼る場合に使うことができる。このEPUB CFIを渡してやると、EPUBリーダーがその部分を頭出しして開いてくれる、というのは普通に期待される使い方だ(実際、BiB/iというEPUBリーダーはこれに対応している)。JavaScriptでこれを扱うなら(或いはJavaも?)簡単なんだろうけど、Rubyだとやはり不必要に思われる処理を入れないといけない。せっかく人間に優しく出来ているのに、仕様のほうがそうなってなかった(いや、UTF-16で暮らしてる人にはフレンドリーなんだろうけどね)。まあ技術文書なので、そういうもんなんだろうけど、愚痴りたくもなりますね。",["Ruby"],"Rubyでの文字列の「長さ」"], | |
["2015/11/04.html","Polymer 0.5、0.8の頃は、Polymerでマテリアルデザインするのが(少なくともその取っ掛かりは)簡単だったように思うけど、1.0になって難しくなったと感じていた。0.8でも1.0でも同様に、Paper Elementsというコンポーネントセット(paper-*というタグ集)が予め用意されていて、それを使うとマテリアルデザインが始められるようになっているのに、どこが違うんだろう。というところでググったりしていて思い付いたのは、チュートリアルがないからだ。Polymer 1.0自体のチュートリアルはある(Quick tour of Polymer)。でもこれは、Polymerを使って自分で要素を作り、それを使うというチュートリアルだ。名前もチュートリアルではなくクイックツアーになっている。他に公式サイトで探して見付かるのはPaper Elementsカタログの各要素のサンプルと、あとはPolymer Starter Kitくらい。前者はリファレンスなので全体の考え方が掴みにくいし、後者はあっさりしすぎている。でもPolymer 0.5は、自分でPaper Elementsを使いながら一つのウェブアプリケーションを作るチュートリアルがあった(Getting the starter project)。これをやっていたので0.5でPaper Elementsを使ってページを作るのに、細々したところはともかく、「全体としてはこういう流れてやるんだな」というところに躓いた憶えがなかったのだ(と、いうほど、使っていないけれど)。日記をこのGitHub Pages+Middlemanにするにあたって、フレームワークにPolymerを選んだところで改めて探して、ようやく、Paper Elementsの使い方にふさわしい公式ドキュメントを見付けた。Responsive Material Design layoutsだ。細かいステップを通じて、レイアウトする時の「枠」を教えてくれる。各ステップでデモンストレーションも用意されているので分かりやすい。おすすめ。Polymerの公式サイトを探しているのではだめで、Element Catalogの下の方にあるガイドを探すべきだった。上のドキュメントから参照しているFlexbox layout with iron-flex-layoutもほぼ必読のドキュメントだと言っていいと思う。まだ読んでいないけどneon-animationも気になっている。確か今策定中のWeb Animationsを使う要素のコレクションだったと思う。",["マテリアルデザイン","Polymer"],"Polymerでマテリアルデザインする時に読むといい物"], | |
["2015/11/06.html","Groongaで学ぶ全文検索 2015-11-06に参加してきた。今日のお題は「精度」。精度とは精度とは何か、参加者からはこんな意見が出た。ユーザーの満足度思った物が探せてるもれなく検索できてるごみがないここから@ktouさんの説明。精度に入る前に、再現率と適合率の説明があった。再現率検索した時に返すべき物があるはず。 例えば「京都」で検索した時、「京都」を含む文書は全部返って来てほしい(ここではそういうことにする)。ところがトークナイザーに形態素解析器を使っていて、「京都」で検索した時に「東京都」はヒットしないような設定にしていたりすることもある。こういう時、その「東京都」の文書は検索結果から漏れてしまうので、再現率を下げてしまうことになる。 再現率は、「検索結果で返ってくるべき物のうち、実際に検索エンジンが返した物の割合」。適合率上で「返すべき物」という話をしたので引き続きそれを使うと、「実際に返って来た検索結果のうち、返すべきだった物の割合」が適合率。 再現率を上げるためには、例えば登録している文書すべてを検索結果として返すことができる。そうすると「返すべき物」は全て含まれているので、再現率は100%になる。しかし、この結果はいらない物を多量に含んでいるだろうから、期待する結果からは遠い。この時適合率は低いということになる。精度さて、検索エンジンの精度の話をする時、この再現率と適合率が、よく指標として使われる。最初に上げた参加者みんなの精度のイメージのうち「ユーザーの満足度」「思った物が探せてる」はふわっとしてるけど、再現率と適合率は数値化できるので議論しやすい。ところが、一般に、どちらかを上げるとどちらかは下がってしまう(らしい)。 特許検索はふつう、適合率を下げても再現率が重要になる。特許出願を考える際には既存の特許とかぶらないことを確認する必要があるが、その時には、漏れがあっては困るからだ。現実の話再現率と適合率はよく指標として使われるが、現実的には、この二つだけで精度検索結果のよし悪しを議論できるわけではない。例えば検索して、結果が1000件あったとする。この時、ふつう、1000件全部は確認しない。全部を確認しないのであれば、「全体に対しての割合」である再現率や適合率は、その厳密な数値には意味がないことになる。 Googleでヒットした時に大事なのは、多くの場合1ページ目、せいぜい3ページ目くらいまでで、みんなそれを目指してSEOを頑張っていた。なので、4ページ目以降は、不要な検索結果ばかりで適合率が低かったとしても問題ない。もちろんこれはGoogle検索はそういう選択をした、またウェブの一般的な検索はそれでいいことが多いだろうということで、ケースバイケースである。精度を高めるよい検索結果を提示するには追記。始め「精度を高める」という書き方で書いていた。しかし、他の人のまとめ(前半、講師の@ktouさんが説明をして、後半で各々自分の言葉でまとめるという勉強会。この日記もそのまとめとして書いている)を聞いているうち、これがよくない表現だと気付いた。精度はやっぱり再現率と適合率のことで、この節で話したいのは「再現率と適合率を考えるだけではユーザによい検索結果を提示することはできない」ということだからだ。精度を上げるユーザーによい検索結果を提示するにはどうすればいいか、方法は一概には言えない。ユースケースごとに求められる精度のタイプが変わるからだ。上で見たような「上位n検の結果だけが特に重要」みたいなケースではスコア付けが大事になる。スコア付けには(昔の)GoogleのPageRankや、ほかにTF、TS・IDF、BM25などがある。それぞれが何なのかは省略する。確かPageRankは、各ドキュメントの被リンクの数などをスコア付けに使っていたと思う。それが「いい結果」ということでGoogleが受け入れられていった一因になっていた。 これはTF・IDFなどの、文書中のキーワードから導出した指標の限界を示唆している。@ktouさんはこのように、キーワードだけを使うのでは、精度はそれほどよくならないと思っているらしい。それよりも文書のメタデータを使ってスコアを考えるほうが現実的にいい結果が得られることが多い。メタデータには例えばタグがある。 TF・IDFでは、「文書中のTFが高いつまりある単語が多いという時、その単語はその文書を特徴づけている」と考えている。IDFについては、「ある語がたくさんの文書に含まれているほど、その語は文書の特徴を表現していない」と考える。つまり「その文書の特徴は何なのか」ということが知りたい、その知りたいことの導出に文書中のキーワードを利用していることになる。本質的には文書の特徴が知りたいだけなので、書き手の用意したタグというのは、多くの場合キーワード由来の指標よりも、その特徴を顕著に表していることになるだろう。そうしたわけでタグは、検索結果の重み付けに使う指標として有効と考えられる。他に位置情報も考えられる。渋谷を歩いててラーメンを食べたくなった時に、「ラーメン」と検索すると、渋谷のお店を扱った文書が出てきてほしい。仮に文書中に店舗名しかなくて、「渋谷」みたいな場所を示すキーワードがないとする。でも文書のメタデータに渋谷の経緯度があるとすると、それを利用して渋谷の店を検索結果の上位に出すことができる。こういった文書のメタデータの他に、検索する人の情報を使うこともできる。今、Googleの検索結果は、(ログインしていれば)訪問した回数の多いページが上位になるようになっている。フェイスブックでの検索も、自分に関連のあるユーザーなんかが結果の上位に来るようになっている。上のラーメンの例でも、スマホからGPSによる位置情報を検索クエリーに乗せて使うことができる。こういう風に、精度を高めるユーザーによい検索結果を提示するには、文書中のキーワードを使うだけでは、限界がありそうだ。Groongaでのスコア付けGroongaでは以下のスコア付けの方法が用意されているTFTF・IDFTFで、リミットありの物。TFは参考情報に使って、メインの重み付けの指標は別にあるといった場合。TFは文書の内容に応じて無限に増えていく(例えばヒットしやすい語を埋め込みまくった記事とか)ので、ある単語を書きまくるというスパムの餌食になりやすい。「その語を含んでいる」という事実は考慮しつつも、その影響を一定範囲に抑えるために、重みのリミットを、例えば2(数値はユーザーが決める)とかにしてしまうやり方。Groongaに特徴的で、@ktouさんはこの重み付け方法を持っている全文検索エンジンを他に知らないとのこと(ちょっと自身なさそうだったw)。メタデータに重みを付けてスコアに反映させるやり方(以下で説明)最後のは例えば、居酒屋のテーブルがあったとして、居酒屋A … 重みは海鮮:10居酒屋B … 海鮮:5居酒屋C … 中華:100たいなメタデータを入れておく。 グルメサイトで「海鮮が美味しいお店の中から探す」みたいなフィルタリングをしている時は、どんなにその値が高くても、居酒屋Cは上位には出てこないだろう。その重みは海鮮ではなくて中華なのだから。と、今日はここまで。参加者の一人から「では、他製品と競争するにはメタデータの扱いが重要になるのか」という質問が出ていて、鋭いなあと感心した。あと、自分の理解のまとめとしてこの日記を書いている間に、すごい面白そうな話が後ろで繰り広げられていたけど、書くのに忙しくて聞けなかった……。TF・IDFはAND検索、OR検索で使われる追記。参加者の一人のまとめ発表で、「TF・IDFはAND検索、OR検索で使われる」といった物があった。まとめを書いている時に出た疑問を@ktouさんに質問して、そのフィードバックを入れたらしい。これはぱっと見た時にすぐ意味が頭に入ってこなかったが、分かったらもやもやがすごいすっきりした。TFは分かるけどTF・IDFの話が全文検索で出てくるのがぴんとこなくてもやもやしていた。僕がTF・IDFを知ったのは全文検索ではなくて類似文書検索、なので定義やその意味を聞かれれば一応答えられるけど、それが全文検索でどういう意味を持つのか、と聞かれると詰まってしまう(はずだとこの時に気付いた)。IDFを考える気持ちは、ある文書とある文書を比べる時に「その二つに共通の特徴でも、そもそも全文書、多数の文書に共通の物は、さほど大事な特徴ではない」ということだ。二つ(以上)の文書を比べる時に出てくる概念なのである。でも全文検索では、ヒットした文書同士を比べるようなことはしないなのにDF・IDFを使っているのでもやもやしていた。これがAND検索、OR検索のことを考えると、つまりクエリーが複数あって、それぞれについてヒットする文書群があって、その文書群の文書について何らかのスコア付けをする、ということになる。そう考えるとTF・IDFを使うのは自然に思えた。またこういう風にも捉えられる。AND検索にせよOR検索にせよ、クエリーを「複数の単語からなるごく短い文書」だと考えられる。この「ごく短い文書」に似ている文書というのが、すなわち検索結果の文書なのだ。やはり、類似文書検索で使われる手法を使うことは、自然に思える。他の人の(ちゃんと疑問を疑問と認識できる)視点で出てきたフィードバックがその場でシェアされるの、ありがたいことである。BM25追記二。(この日記を含む)みんなのまとめの発表が終わった後、解説が省かれていたBM25というスコア付けの方法も説明してもらった。BM25はTFとIDFのほかに、文書の長さも考慮する指標。ある単語について「長い文書の中にたくさんあるとして、それはそういうものだろう。短い文書の中にたくさんあるようだと、それはその文書を特徴付けているているキーワードだろう」という考え方で計算する物らしい。 これはある種のスパムブログなんかを弾くことができる。スパムブログで「これ入れとけば検索上位になるっぽい」という単語をひたすら書き連ねているようなやつ、それは文書自体も長くなるので、その分スコアが下がる。ただ計算量は増えて、計算量としては「TF < TF・IDF < BM25」という順番になっている。",["全文検索","Groonga"],"全文検索の精度とスコアについて"], | |
["2015/11/07.html","Middlemanでシンタックスハイライトするにはmiddleman-syntaxがある。でもこの日記はMiddleman v4で作っていて、middleman-syntaxは今日時点ではv4に対応していない。のでフォークして対応させようかなと思ったけど、ふと思い出した。kramdownがそもそもシンタックスハイライトに対応しているはずだ。kramdownはRuby製のMarkdownパーサー+αのライブラリーで、MiddlemanでMarkdownを使う時のデフォルトエンジンにもなっている。MiddlemanでのMarkdownエンジンの設定方法、調整方法は公式ドキュメントにある(テンプレートエンジンオプション)。markdown_engine設定はデフォルトのままで構わないので、markdown設定だけ、config.rbにこう追加した。set :markdown, 'syntax_highlighter' => 'rouge'\nこれで、Markdownの記事でのコード部分にシンタックスハイライト用の<span>が追加される。ここではrougeを指定しているけどcoderayも使える(gemは自分でGemfileに書いてインストールする必要がある)。この設定ではブロックレベルのコードだけでなく、文中のspan要素もハイライトされる。この日記ではそのままにしているけど、嫌な場合にはset :markdown, 'syntax_highlighter' => 'rouge',\n 'syntax_highlighter_opts' => {\n 'span' => {'disable' => true}\n }\nと、spanの時だけ無効化してやればいい。その他、使えるオプションはkramdownサイトのSyntax Highlighting With Rougeというドキュメントにあるので参照されたい(Coderayの場合はSyntax Highlighting With Coderay)。これだけでは単に<span>が追加されるだけでスタリングはされない。CSSを追加する必要がある。Rougeで使えるテーマはRougeのthemesディレクトリーを見ればよくて、この日記ではGitHubテーマを使っている。stylesheets/highlight.css.erbというファイルを作って、こう書いた:<%= Rouge::Themes::Github.render(scope: '.highlighter-rouge') %>\nこの設定にはRoom 3100というブログのmiddleman-blogのハイライトにMonokaiテーマを使うという記事を大いに参考にさせてもらった。ありがとうございます。あとは「`…`」(インライン)やフェンストコードブロックでマークアップしてやればよい。注意が必要なのは、kramdownのコードブロックはGitHubのフェンストコードブロックとちょっと違って、「~」(チルダ)を三つ並べる(Language of Code Blocks)。~~~ ruby\nset :markdown, 'syntax_highlighter' => 'rouge'\n~~~\n",["Middleman","kramdown","Rouge"],"Middleman v4でシンタックスハイライト"], | |
["2015/11/20.html","Groongaで学ぶ全文検索 2015-11-20に参加して来た。遅れそうで「遅れます」って連絡してたら、15分くらい早く着いてしまって時間のお見積りが不正確で大変申し訳ございませんでした。今日のテーマは日本語での全文検索。以前、英語での全文検索の仕組みについてはやった(http://apehuci-kitaitimakoto.sqale.jp/apehuci/?date=20150918)。今回は軽くそれを復習した後、日本語では英語の場合と違ってどういうところを頑張る必要があるかという話だった。最初に、知ってるだろと説明役を振られそうになったけど「日本語の方は分からないんですよ」と言って断った。が、日本語で分かってない部分(分かち書きの仕方)まで辿り着かなかったので、引き受けておけばよかったなあ。さておき、まず、英語での全文検索のおさらい。以下、話を単純化するため、一語のみでの検索という前提にする。全文検索は検索語を入力として、それが含まれた文書を返すもの。単純に、検索語に対して、登録されている文書の一つ一つを調べていくと、文書が増えるにつれどんどん検索が遅くなってしまう。これを防ぐために、インデックスを作り、それに対して検索するようにする。例えば、「Groonga」で検索した時、登録されている文書から「Groonga」という言葉が含まれている文書(のIDなど)の一覧を返す。この時インデックスにはどういったデータが入っていると嬉しいか? どういう構造になっていると嬉しいか? キーが分かると値がすぐに分かる類のデータ構造がよい。配列やハッシュテーブルなどである。このデータ構造を使って、キーには検索でキーワードとしてヒットさせたい物(「Groonga」「Mroonga」……)が入っているようにする(逆に言うと、ここに入っている物だけが、キーワードとして検索可能になる)。値には、そのキーワードを含む文書(のIDなど)の一覧を入れておく。すると、「Groonga」で検索した時に、このハッシュテーブルなりを使えば、すぐに「『Groonga』を含む文書一覧」が手に入る。英語だろうが日本語だろうが、ここまでの考え方は同じ。英語ではここまでで大枠の話は尽きる。データ構造のキーに入れる物が、英単語と一致すること殆どだからだ。多くの場合、検索は、単語で行う。「Groonga」を含む文書が欲しい時に「Groo」みたいな中途半端な文字列で検索したりはしない。こうして検索語の種類(英単語)とインデックスのキーの種類(英単語)が一致するので、英語の場合は概ねこれで要求を満たせる。ところが日本語ではそうはいかない。例えば東京都について書かれた文書を探したい時に、検索キーワードとして「東京都」を使うこともあれば「東京」を使うこともある。インデックスのキーに「東京都」だけ入れておけばよいということにはならない。そうしてしまうと、「東京」で検索した時に、その語に一致するキーが無いわけなので、「東京を含む文書がない」という結果になってしまう。これは、日本語では、単語の区切りが明確ではないという性質に由来する。「単純に単語を入れておけばいいというわけではない。なぜなら単語で検索しない(複合語などで検索する)かも知れないからだ」といこと。(最初に「一単語で検索する」という前提を置いたけど、そもそも日本語では「一単語がどこまでか」が自明ではない、ということだと思う。ちょっとここ自信無い。)さて、この問題の解決には大きく分けて二種類のアプローチがある。一つは、英語同様単語をキーワードにすること(アプローチA)。「花が咲いた」という短い文書があった時、「花」「が」「咲いた」をキーとしてインデックスに入れる(「咲いた」は微妙かも知れないけどここではそうする)。こうしておくと、「花」で検索した時、「が」で検索した時……に、正しくこの文書を見付けられる。もう一つは、単語を気にせず何でもキーにしてしまうこと(アプローチB)。「花が」みたいな複合語も「咲い」みたいな単語になってない文字列(ということにしてください)も何でも、意味を気にせずキーにする。この二種類のアプローチがあって、両方よく使われている。なぜ一つでなく二つあるかというと、アプローチAにある種の難しさがあるからだ。どういう難しさかというと、「すもももももももものうち」みたいに、単語の切り方が難しい(「スモモも桃も桃のうち」)「ここではきもの」みたいに、切り方に複数の候補があって選ぶのが難しい(「ここでは、着物」「ここで、履物」)など(他にもある?)。アプローチAは、検索時にやることが少なくなりやすいという特徴がある。多くの場合、検索語は単語になる。今、インデックスのキーとしては単語を入れているので、単純にハッシュテーブルなどを引けばよくなり、速い。アプローチBは、例えば上で説明したように、文書を二文字ずつ区切ってキーにしている場合。この場合は、検索語が「咲いた」だと三文字なので、そんなキーは存在せず、(本来ヒットするべき)文書がヒットしない。これを防ぐために検索語の方も、インデックスのキーの長さ(二文字)に合わせてばらばらにする必要がある。(まず、この処理の分、検索時にはすることが増え、遅くなる。でも多分、これはあまり気にしなくていい遅さで、次の話のほうが支配的だろう。ということをまとめ発表で言ったら、@ktouさんが訂正してくれた。アプローチAでも分割しているらしい。検索クエリーが単語になってくれていれば、そういう制約を設けることができれば分割しなくていいが、そうでない場合がほとんどなので。……振り返ると、アプローチA=形態素解析を使った全文検索で、クエリーも解析しているのは知っていたはずだった……。)「咲いた」を二文字でバラバラにすると「咲い」と「いた」。このそれぞれのキーについて文書を検索する。すると、「『咲い』を含む文書一覧」と「『いた』を含む文書一覧」が手に入る。これらの文書には「『咲い』は含まれるが『いた』は含まれない、従って『咲いた』は含まれない」という文書と、この「咲い」と「いた」の関係をひっくり返した文書が含まれていて、これらはユーザー(プログラム)に渡す検索結果からは除きたい。しかも、除くだけでは不十分で、「花が咲いていた」という文書も、現時点での「正解」の文書リストには含まれてしまっている。でもここに「咲いた」の語はない。「咲い」と「いた」がこの順番で隣り合っていないといけないわけだ。今手に入っている文書のうち、この点も満たす文書を更に絞り込む必要がある。この絞り込みの方法は二つある。一つは、インデックスの、それぞれのキーに対応する情報に、(文書IDなどの識別子のほか)文書中の出現位置(何文字目に出現するキーか)という情報も入れておく方法。こうしておけば、検索時に「『咲い』と『いた』を含み、その出現位置が一文字違い」という文書を探せばよいことになる。(説明されなかったが、「『咲い』を含み、その出現位置の次の位置が『いた』である」という検索方法だと、集合としては同じ結果が得られるけど、だいぶ遅くなってしまうはず。「咲い」を含む文書一覧を取得した後、それぞれの中身を先頭から一文字ずつ調べていく必要があるので、文書自体を読み込んだり、文字検索用のカーソルを動かしたりする必要が出てきてしまう。)もう一つは、無駄を承知で、まず、「咲い」を含む文書一覧と「いた」を含む文書一覧を両方取得してしまう。「『咲い』と『いた』を両方含む文書一覧」を取得して、(これも訂正してもらった。)その後に文書それぞれを調べて、キーが隣り合っているか(「咲いた」と連続しているか)を調べる。アプローチBはこうして、アプローチAよりも処理が増えているので、難しさは減るが、検索時に遅くなってしまう。というのが理屈。ここまで説明したところで、専門用語が導入された。アプローチA … 形態素解析アプローチB … N-gram(Nのところは文字数。N=2でバイグラム、3でトリグラム)こうしてキーワードが連続しているかをチェックして、連続している物だけを返す検索方法をフレーズ検索と言う。一応形態素解析を使った検索で使われることもあるが、多くはN-gram検索で使われる。形態素解析を使う場合は、そもそもの形態素解析を使う目的から(インデックスのキーが単語になっているので)、単語で検索して問題ないことが殆ど。そして多くの人は単語で検索する。逆にN-gramの場合は、「いた」を含む文書とか基本的にノイズばっかりになるから、きちんと(二文字を越える)欲しい単語を含んでいるのかチェックしないと使い物にならない。以上、英語での検索の場合の他に、日本語で頑張らないといけない処理。余談。参加者の中に、Mroongaを使っていてMeCabの「too long sentence」といった内容のエラーに遭遇したという人がいた。これについても@ktouさんが解説してくれた。これはMeCabの制限に引っかかったために発生したエラーとのこと。MeCabでは入力された文書に対して、句読点などを見て文に分割しようとする。ところが、文を分割する目印を見付けられなくて一文が長くなりすぎるような文書があると、リソース不足でこのようなエラーになってしまう。最近のGroongaではこれの対策も実装されているらしい。オプションを指定することで、「一文が長くなり過ぎたら強制的に途中で切ってMeCabに渡す」ということができるようになる。これまで全体がエラーになっていた所が、この部分だけちょっとおかしな検索結果になるというだけなので、全体としてはまあまあうまくいっているらしい。",["Groonga","全文検索"],"日本語文書の全文検索"], | |
["2015/11/29.html","Groonga Meatup 2015で発表してきた。タイトルは「Rubyでプラグインを作れる分散全文検索エンジンDroonga」。発表を録画してもらえていて、以下で見られる。(あとで個別に切り分けたものが出てきそう出てきたので差し替えた。ありがとうございます!)資料は以下の通り。 (iframeで埋め込んでいるけど、HTTPS非対応なので埋め込み表示できていないかも知れない。リンク先に飛ぶか、このページのURIをHTTPにすると見られる。)http://www.storyboards.jp/viewer/yct228今年のテーマ(去年までは「全文検索エンジンGroongaを囲む夕べ」という名前でやっていたイベント)は「よいところ」なので、Droongaのよいところを発表してきた。発表の機会を貰えたことはとても価値のあることだった。本当にありがとうございました。反省点色々あった。ただ、これに関する色々なことを実際のDroonga開発者の方に聞けて、三つくらい疑問が解消したのがあって、僕にとってはとても有意義だった。ありがとうございました。あと、プレゼンに使ったStoboはそれなりに面白がってくれて、自分で作った物ではないけどこれも嬉しかった。本当はRabbitでスライド作ってRabbit Slide Showに上げたいなと思っていたのだけど時間無さ過ぎて慣れてるツールになったというのは秘密。",["Groonga","Ruby"],"Rubyでプラグインを作れる分散全文検索エンジンDroonga"], | |
["2015/11/30.html","とても久し振りにSendagaya.rbに参加して来た。第128回の今日は、三十分くらい雑談した後、QiitaのAction Cableの記事[Rails5]Action Cableのサンプルを読み解いてみるを読みながらああだこうだ言っていた。一通り見ての感想は「便利そう」「使いたい」。Action CableはRails 5から入るらしい新機能で、WebSocketをRailsに統合した形で扱える物らしい。まともに記事など読んだのは今日が初めてで、これがRais界隈でどれくらい認知されているかは分からない。僕が「RailsでWebSocket」と聞いて漠然と思い浮かべたのがActionController::Liveだったのだけど全然違う(ActionController::LiveについてはIs It Live?がよい紹介記事だ)。Action Cableでは、Railsのプロセスの他にAction Cable用のプロセスを立ち上げる。こいつがブラウザーとWebSocketで通信する。普通だ。Action Cableのいい所はここからで、Railsとセッション用のクッキー情報を共有できる(電子署名が付いているあれだ)。だから、WebSocketを使ってAction Cableに接続してきたクライアントが、Rails(のデータベース)で管理しているどのユーザーに相当するのか、見付けることができるのだ。更に、Railsのプロセスからブラウザーに、WebSocket経由でメッセージを送ることができる。例えば、フォームなどから普通にコメントを投稿した時に、そのことをWebSocketで繋がっている全ユーザーに通知できる。だがRailsがWebSocketを使ってAction Cableに接続しているわけではない。Active Jobを使ってRedisにメッセージを送信するのだ。Action CableはRedisのpubsub機能を使っていて、Rails(Active Job実装のワーカープロセス)がパブリッシャー、Action Cableプロセスがサブスクライバーになっている。Action Cableはサブスクライブしたメッセージを、予めAction Cable用に書かれたコードに従って、必要なクライアントに流す。もちろん、クライアント同士WebSocket経由での通信もできる。(そう言えば、はて、クライアントからDBのレコードを弄るような場合、Action Cableプロセスがやるのだろうか、Railsプロセスがやるのだろうか。後者はフォームなりAjaxなりでやることが自然に思い浮かべられるが、前者は逆方向のpubsubになる? と考えると、そういうことはなさそうだなと思う。)Rails --(Active Job)--> Worker --(Redis pubsub)--> Cable --(WebSocket)--> Clients\n副産物として、始めからRedisのpubsubでWebSocketサーバーをつなぐのが前提なので、プロセスを増やすだけで簡単にスケールアウトさせられそうだ。これは悪いことではない、というかむしろいいことだが、Railsはモノリシックなのが特徴の一つという印象を持っていたので、結構変わり種のコンポーネントだな、と感じた。繰り返すが悪いことではない。と、便利なところだったが、実は半分くらいは推測で書いている。件の記事の内容からは内部の動きは分からないからだ。だから今度はAction Cableのソースコードを読みたいと思っているし、もしかしたら次回のSendagaya.rbでソースコードリーディングができるかも知れない。余談。「RailsからRedisにパブリッシュするためにはSidekiqなどが必要で、更に別のプロセスを立てないといけない」といった話をしている時に、@tkawaさんにSucker Punchを教えてもらった。Railsプロセス内にCelluloidを使ってアクタースレッドを立て、それを使ってActive Jobのジョブを実行する物のようだ。ぱっと見本番で使っていいかは不安に思ったが、開発環境で使う分には便利だろう。",["Action Cable","Ruby","Ruby on Rails"],"Action Cableが便利そう"], | |
["2015/12/03.html","この日記はPolymer 1.2.1で作っているのだが、この前まで僕のメインブラウザーであるFirefox for Androidでは読めなかった。今でもPolymer Element Catalogのサイトを見るとそれが体験できる。Firefox for PCでは問題ない。Firefox for iOSは知らない。webcomponentsjsやPolymerにconsole.log()を仕込みながらプリントデバッグを頑張って原因を突き止めたところ、webcomponentsjsでのHTMLインポートの検出に問題があることが分かった。現時点でのwebcomponentsjsでは、ブラウザーにHTMLインポートの機能があるかどうかを、link要素の(JavaScriptの)オブジェクトにimportプロパティ(あれば関数)が存在するかどうか、in演算子で確認してチェックして判断している(該当箇所)。HTMLインポートをサポートしていないブラウザー(Firefox for PCなど)ではimportプロパティが存在せず、その場合はshimを使う。ところがFirefox for Androidでは、「link要素にimportプロパティが存在する」「しかしHTMLインポート機能はサポートしていない」ということになっている。link.importが、nullになっているのだ。たとえnullであっても、値が存在すればin演算子はtrueを返す。従ってFirefox for AndroidにはHTMLインポート機能が存在する、とwebcomponentsjsは判断しているわけだ。一応、バグレポートはした。プルリクエストはリクエストしなかった。コントリビューションページによると、コントリビュートするにはライセンスに同意する必要がある。それは構わなかったのだが、同意手続きの過程で住所を入力欄が現れた。それも必須項目として。漠然と不安を覚えてプルリクエストは躊躇ってしまった。webcomponentsjsでこの問題が対応されるかは分からない。だから今この日記ではこんなワークアラウンドを入れている。(function() {\n var ua = navigator.userAgent;\n if (ua.indexOf(\"Android\") !== -1 &&\n ua.indexOf(\"Firefox\") !== -1 &&\n document.createElement(\"link\").import === null) {\n delete HTMLLinkElement.prototype.import;\n }\n})();\nこれを、webcomponentsjsをロードするscriptタグの前に置いている。if節の条件はnullのチェックだけでよさそうだが、そうするとなぜかChromiumやChromeでページが読めなくなってしまったので、プラットフォームも判断している。なぜ読めなくなったかは調べていない。",["Webコンポーネント","Polymer"],"Firefox for AndroidでもPolymerが動作するようにする"], | |
["2015/12/05.html","今日の日記はGroonga Advent Calendar 2015の五日目です。昨日はcosmo0920さんのGroonga族のHomebrewの変遷を振り返るでした。やっぱりコマンド一つで簡単にインストールできるのはよい。しかし、そのためには陰で誰かが苦労しているということも伺える記事だった。今日は、Homebrewなどで一発インストールのできないGroonga族の一員、Droongaのインストールについて書く。Groongaにはレプリケーション機能がない。DroongaはGroongaを複数のマシンにレプリケーションさせるプロダクトだ。公式サイトのほか、去年のGroonga Advent Calendarにも記事があって、とても面白く読んだ。Droongaが何かということはこれらを見てほしい。公式サイトには勿論インストール手順も書かれているのだが、今日時点でこれはうまくいかない。そこで僕は、サーバーの構成管理ツールであるItamaeのレシピを作ってインストールしている。今日はそれを使ったインストール方法を書こうと思う。以下、Vagrantを使ってUbuntu 15.04の環境で実行している。マシンイメージはVagrant Cloudから公式のイメージを持って来た。Droongaのインストール時にはメモリーが必要なので、Vagrantfileに設定を書いて2GiBくらい確保しておく。Vagrant.configure(2) do |config|\n config.vm.define :ubuntu1504 do |node|\n node.vm.box = \"ubuntu/vivid64\"\n node.vm.provider \"virtualbox\" do |provider|\n provider.memory = 2048\n end\n end\n # :\n # :\nend\nバーチャルマシンを起動したら、まずログインして環境を更新する。[host]$ vagrant up ubuntu1504\n[host]$ vagrant ssh ubuntu1504\n[vm]$ sudo apt-get update\n[vm]$ sudo apt-get upgrade -y\nここでようやくレシピの登場。GitHubに上げている(https://github.com/KitaitiMakoto/itamae-plugin-recipe-droonga)。gemまたはItamaeプラグインの形をしているがrubygems.orgには上げていないので、git cloneで持って来る必要がある。もっと一般的にしてからリリースしたいなと思って、そのまま時間が過ぎてしまっているのだ……。[host]$ git clone https://github.com/KitaitiMakoto/itamae-plugin-recipe-droonga.git\nリポジトリーをクローンしたら、Itamaeのレシピファイル(ここではrecipe.rb)を用意して、以下のように一行書く。include_recipe \"./itamae-plugin-recipe-droonga\n/lib/itamae/plugin/recipe/droonga/default.rb\"\n\nそうしたらItamaeを実行すればよい。簡単だ。但し時間は掛かる。[host]$ itamae ssh --vagrant --host ubuntu1504 recipe.rb\n(上のイメージだとこれでいいが、DigitalOceanだと依存パッケージが足りなくてうまくいかなかったかも知れない。エラーメッセージを見ながら必要な物をインストールしてほしい。)Droongaは二つのコンポーネントからなっている。Groongaデータベースを操作したり、他ノードと連携してレプリケーションを実現するDroonga Engineと、そこへのHTTPインターフェイスを提供するDroonga HTTP Serverだ。それぞれそれ用のプロセスを起動する必要がある。公式サイトの記事ではserviceコマンドを使ってこれをコントロールすることになっているが、Ubuntuでは15.04からUpstartに代わってsystemdが導入されたので、レシピではsystemctlコマンドを使うようにしている。[vm]$ sudo systemctl status droong-engine\n● droonga-engine.service - Droonga Engine\nLoaded: loaded (/lib/systemd/system/droonga-engine.service; enabled; vendor preset: enabled)\nActive: active (running) since Fri 2015-12-04 20:09:56 UTC; 37s ago\nMain PID: 30190 (droonga-engine)\nCGroup: /system.slice/droonga-engine.service\n(snip)\n[vm]$ sudo systemctl status droonga-http-server\n● droonga-http-server.service - Droonga HTTP Server\nLoaded: loaded (/lib/systemd/system/droonga-http-server.service; enabled; vendor preset: enabled)\nActive: active (running) since Fri 2015-12-04 20:09:57 UTC; 2min 11s ago\nMain PID: 30228 (node)\nCGroup: /system.slice/droonga-http-server.service\n(snip)\nこれでDroongaが動くはずだし、実際EPUB Searcherデモサイトではこの方法でインストールして、現在でも動作している。尚、Droongaを動かすには、内部・外部から、hostnameで返って来るホスト名で名前解決できる必要がある。hostnameと違うホスト名を使いたい場合は、レシピのインストールの箇所(Droonga Engine該当箇所、Droonga HTTP Server該当箇所)の最後のbash実行時にHOST環境を設定し、また/etc/hostsでも設定する必要があるので書き換えること。# :\n# :\nexecute \"curl -sL https://deb.nodesource.com/setup_0.12 | bash HOST=...\" do\n not_if \"test -e /etc/apt/sources.list.d/nodesource.list\"\nend\n# :\n# :\nexecute \"curl https://raw.githubusercontent.com/droonga/droonga-engine/master/install.sh | bash HOST=...\" do\n not_if \"type droonga-engine\"\nend\n# :\n# :\n",["Droonga","Itamae"],"DroongaをインストールするItamaeレシピ"], | |
["2015/12/18.html","Groongaで学ぶ全文検索 2015-12-18に行って来た。今日のお題はドリルダウン(ファセット検索)。ドリルダウンある検索語で検索した時に、検索結果をさらに絞り込むと何件になるかなどの集計をする(発表した時に間違いを指摘してもらった。ドリルダウンは絞り込みではなく集計)ことを、予め(検索時に同時に)Groongaがやっておいてくれる、という機能だ。例えばるりまサーチで、「call」を検索した場合、callを含むページのほか、画面左にインスタンスメソッドが何件あるか、特異メソッドは何件あるか……といった結果も表示されている。これは、「callを含み、かつインスタンスメソッドのページ」は何件あるか、という数になっている。このように、検索結果に対して、既に(与えられた)キーで集計した結果を返すのがドリルダウン。まず、既に分類が終わっているので、検索結果を絞り込む作業が簡単に(クリックするだけで)できる。また、0件の時はそのことが分かるので、不要な絞りこみ作業をユーザーがわざわざする必要がない。というように、ドリルダウンは全文検索を補助する機能だ。全文検索とは関係ない文脈では集計機能ということになる。ドリルダウンを高速にするために、Groongaのデータ構造が活きている。RDBMSは行指向のテーブル型データベースだから、ストレージ上、一行のデータが複数カラム分、まとまった場所に置かれるようになっている。あるカラムに関する集計(インスタンスメソッドは何件で、特異メソッドは何件で……)をする場合には各行をループさせて、その中で注目しているカラムの場所まで移動して、内容に応じて集計結果を更新する必要がある。Groongaは列指向データベースだから、ストレージ上、あるカラムのデータがまとまった場所に置かれている。だから集計する場合には一箇所からデータをまとめて取って来て数えたりしていけばよい。集計に関してはこうなっていて、全文検索では更に「検索結果の中で」のそういった集計を行うことになるが、特別なことはない。一旦全文検索を行ってレコードを取得する。その中で更に集計する。ヒットしなかったレコード分はスキップして集計するのでややもったいないが、列指向でのやり方よりは効率がいい。多段ドリルダウン多段ドリルダウンはどうやっているのかも聞いた。本のデータベースがあった時に、雑誌 -> プログラミングというように下位分類を持つようなやつだ(こうじゃないタイプの「多段ドリルダウン」も考えられるそうだが思い出せないとのこと)。Groongaにはキーを二個使ってドリルダウンができる機能があるそうだ。列指向データベースなので大分類カラム(「雑誌」)、小分類カラム(「プログラミング」)のデータはそれぞれの場所にまとまって置かれているが、ドリルダウンする時に、それぞれから同じレコードの物を取り出してペアにしてまとめることができるのだ。例えば、何かで検索して結果セットが得られた後に、ドリルダウン結果を大分類と小分類のペアである[雑誌,プログラミング]、[ハードカバー,小説]……の集まりというデータとみなして作ることができる。つまり「大分類が雑誌で小分類がプログラミング」というレコード(本)が何件あるか、という結果を作ることができる。この結果を使えば、大分類+小分類での絞り込みをサポートすることができる。が、これだけだと大分類のみでの絞り込み結果を出さない。その結果も勿論欲しいので、更に大分類カラムのみの集計もする……ということはしないで、Groongaは頑張って、あるカラムの一回のスキャンで色んな結果用の処理を行うようになっているらしい(ということは、インデックスを調べる前の計画を頑張っているということかな?-> 別に頑張ると言うほど大変なことではなかった)。また、この話は(Groongaでは)二個に限らず、n個に一般化できるらしい。ユーザー定義の集約関数参加者から「平均、最大値……」といった予め用意された関数以外の、ユーザーが定義した関数は使えるのか、という質問がでた。結論は「できない」。一つはいいインターフェイスがないから。もう一つは、そういう機能を入れると遅くなってしまうから。ドリルダウン結果のデータ構造ドリルダウンの結果はハッシュテーブルになっている。分類カラムの集計処理中、「雑誌」という物を見付けた場合、 「雑誌」が、ドリルダウン結果に既にあるかどうかを素早く知る必要があるからだ。なければ結果データに「雑誌」を加えて「1件」というデータにすることになるし、あれば既存の値をインクリメントする必要がある。ハッシュのキーはカラムの値その物だとして、バリューの方は、Groongaでは「バリュー」という物になっているらしい。配列とかではない。どんなデータでもよい長さの決まったバイト列で、それを使用する機能の方で適宜解釈して使う、とのこと。このバリューの方は、ここまでの話だと合計や最大値などになるので、単独の数値でよさそう。だがもっと別の物を入れてもよくて、実際、全文検索結果レコードの内容を入れると便利になる。ドリルダウン結果に加えて、より詳細な結果を同時に見せられるからだ。例えばGoogle検索でたまに、サイトの下位ページが出ることがあるが、そういった情報を出すために検索結果データの内容を使うことができる。",["Groonga","全文検索"],"ドリルダウン(ファセット検索)の仕組み"], | |
["2015/12/25.html","この日記はGroonga Advent Calendar 2015の21日目の記事です。今日は25日です。大幅に遅れてしまって、本当に申し訳ありません。Middlemanでブログの類似記事一覧を作る拡張にMiddleman-Blog-Similarがある。類似判定のアルゴリズムを選べたりといい所もあるのだがこれは今のMiddleman v4に対応していない。それにGroonga Advent CalendarのネタとしてもGroongaが使いたかったので、Groongaの類似文書検索機能を使って同様のことをやってみた。https://github.com/KitaitiMakoto/middleman-blog-similar-groonga使うにはmiddleman-blog-similar-groongaをインストールし、config.rbでactivate :blog_similar_groonga\nと追記する。拡張を有効化するとブログ記事向けのレイアウトでsimilar_articlesというヘルパーが使えるようになる。ここでイテレートされるオブジェクトにはkey.key(パス)titlepathbody(タグを取り除いたもの)の属性があって、わざわざ他記事のBlogArticlesオブジェクトまで辿らなくても済むようになっている。類似記事のタイトルを表示しつつリンクにしたいだけなら充分だろう。<section>\n <h2>類似記事</h2>\n <ul>\n <% similar_articles.each do |article| %>\n <li><%= link_to article.title, article.key.key %></li>\n <% end %>\n </ul>\n</section>\nとして使うことができる。今は本文での類似しか見ていないが、タイトルやタグを使ったほうが人間の感覚に合うだろうから、改善を続けていきたい。名前は似ているがMiddleman-Blog-Similarとは全く互換性が無いので注意すること。",["Groonga","Middleman"],"Middleman Blogの類似記事をGroongaを使ってリストアップするRubyGemを作った"], | |
["2015/12/26.html","何度か書いたように、この日記はPolymerで作っている、つまりウェブコンポーネントを使っている。そこで、一般的に使いそうな機能をMiddleman拡張として書いていたのだが、今日RubyGemとしてリリースした。Middleman Web Componentsだ。もちろんまだまだ足りないことは多いんだろうと思うが、自分の日記を触りながら拡張していきたいと思う。",["Middleman","Webコンポーネント"],"Middleman Web Components"], | |
["2015/12/30.html","今日はちょっと思い出話でも。Groongaもくもく会@札幌 2015-12-30に参加して来た。帰札した翌日から熱を出して寝込んでいたのだが、直前で治って本当によかった。もくもく会ではDroonga HTTP Serverのインストールスクリプトの修正を試みていた。進捗はこんな感じ:https://github.com/droonga/droonga-http-server/compare/master…KitaitiMakoto:centos-systemdその後は忘年会@Sinatra札幌&Sapporoonga 2015-12-30。そこで話題になった一つに、Railsの躓きポイントの話があった。既にうろ覚えだがこんな感じだったと思う。ウェブアプリケーションを作ったことがない状態でRailsに入門すると、どこで何が起こっているか分からない。scaffoldしてアプリケーションが動くようにはできる。でも何がどうなっているか分からない。その後Sinatraをちょっと勉強して、get '/' do\n# ...\nend\n\nget '/entries' do\n# ...\nend\n\nget '/entries/1' do\n# ...\nend\nみたいなのに触れてみて初めて、「Railsはroutesをまず見るべきなんだ」と分かった、という話。そこから、Rails(のようなフルスタックのフレームワーク)に触れる前に、ウェブの基本的な仕組みを学ぶべきだ、という話になった。その場にはDjangoをやっている人もいて、やり始めはやはり同じような分からなさを感じていたらしい。僕は、自分のRailsの覚え方は普通ではないし人に勧めるような物ではないと感じていたのだけど、この話を聞いていると意外とよかったのかも知れないと思えてきた。僕がRailsを覚えたのは、Railsの本でも勉強会でもなくて(当時はプログラミングの話をする相手すらいなかった)、『RESTful Webサービス』だった(今はPDF版もあるようだ)。RESTful Webサービス著者 : Leonard Richardsonオライリー・ジャパン発売日 : 2007-12-21ブクログでレビューを見る»この本は、勿論Railsの入門書ではない。書名の通り、RESTful Webサービスの解説書だ。前半で、ウェブとは何か、HTTPとは、URIとは、アドレス可能性とは、RESTとは、といった基礎的な考え方と、実例として、Google Mapsのような地図サービスのサーバーサイドのリソース設計が紹介される。今実家にいて手元に実物が無いので記憶頼りだが(こういう時電子本だったら……)、どういうXMLにするべきか(当時はWeb APIはXMLを返すのが普通だった。そしてWeb APIでなくWebサービスと呼ばれていた)、どういうURIにするべきか、地図の拡大・縮小はURIとしてどのように表現するといいか、といったことが紹介される。そうしてみっちりと理論的なことを叩きこまれた後で後半、ようやくサンプルウェブアプリケーションとして、del.icio.usクローンのブックマークサービスを作ることになる。この時に採用されたフレームワークが、Ruby on Railsだったのだ。Railsのバージョンは2で、ようやくRESTfulルーティングが入ったような頃だったと思う。Rackはまだ入っていなかった。僕はこれを読みながら、HTTPメソッドとRailsのアクション名との対応表を何度も見返しながら、サンプルアプリケーションを写経することでRailsを覚えていった(Railsアプリケーションの作り方の簡単な解説もあった)。上で言っていた「ウェブアプリケーションの基本的な仕組み」を散々解説された後でRailsに触れることになったし、しかもこの本の順番だとURI設計をし、config/routes.rbに反映させて確認してからようやくコントローラーやモデルの実装に入った。期しないで、上で挙げた落とし穴に嵌らなかったわけだ。Rails Guides(日本語)やRails Tutorial(日本語)、またはRails用の本を使ったわけではないので、正当な覚え方ではないのだろうと思っていた(その後Railsの本も何冊か読んではいる)し、何より人に勧めるような覚え方ではないと感じていたのだが、意外とそうでもないのかも知れない。しかし、この本は多少古い(XML、del.icio.us、Rails 2……)ので、やっぱり人には勧めにくい。そこで、『Webを支える技術』の実践編という位置付けで、Railsを使ったハンズオンチュートリアルを書いてみるのはどうだろう。Webを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESS plus)著者 : 山本陽平技術評論社発売日 : 2010-04-08ブクログでレビューを見る»そこまでいかなくても、いきなりRailsで手を動かすのではなくて、先にこの本を読んでおくことにするのはどうだろう。なお、当たり前の断りとして、適した覚え方は人それぞれだ。特に、人は自分の習得方法がいい物だと思いがちなので、そういったバイアスもある。何より僕は今のRails 4、5は殆ど触っていないので、今でも通用する考え方かは分からない。",["ウェブアプリケーション","Ruby on Rails"],"『RESTful Webサービス』でRailsを覚える"], | |
["2016/01/02.html","田中哲さんの『APIデザインケーススタディ』を買ったので、前『Dockerエキスパート養成読本』でやったように(Dockerエキスパート養成読本を、ソースコードのシンタックスハイライトしながら読む)ソースコードの部分をシンタックスハイライトしようとしたところで、ただただしさんの「EPUB書籍に正誤表を反映する」という日記を読んだ。本の正誤表を見ながらEPUBファイルの中身を直接書き換えることで、誤りを正した状態で読み始められるようにする、という内容だ。これは素晴らしい、ぜひ真似しよう、と思って、スクリプトを書いた:https://gist.github.com/KitaitiMakoto/7b2286b61a0bafcc5926必要なのは『APIデザインケーススタディ』のEPUBファイル(path/to/book.epubにあることにする)Ruby幾つかのRubyGem:$ gem install nokogiri-xml-range epub-parser epub-makerGistにあるreflect-errata-api-design.rbのファイルで、全て揃ったら$ ruby reflect-errata-api-design.rb path/to/book.epub\nと実行すると正誤表を反映してくれる。「wirte」だった所が「write」と修正されている大掛かりである。上記の手順だけで充分大掛かりなのに、このスクリプトを書くには数時間を要している。今回程度の数、内容なら、たださんの日記にあるように、エディターを使って手作業で反映させるのが一番手間がないだろう。ちなみに正誤表はページ数指定なので、HTMLファイルを特定するのにPDF版を参照してページ数から章番号を突き止めるしかないのがやや面倒とのことで、EPUBでの対応箇所を探す多分一番簡単な方法はEPUBファイルを展開する$ unzip path/to/book.epub -d api-designgrepやThe Silver Searcher(ag)、The Platinum Searcher(pt)で展開したディレクトリーを探す$ ag wirte api-design$ ag をを api-design :ではなかろうかと思う(検索機能付きのパソコン向けEPUBリーダーってある?)。使っているgemの紹介さてこのスクリプト、これまで色々準備してたことのプチ決算な趣があるので、少し自慢話にお付き合い願いたい。動かすのに必要なgemを三つ挙げたが、全て僕が作ったgemで、こんなこともあろうかと準備してきた物々なのである。EPUB ParserのCFI実装前からずっとEPUB Parserという、EPUBファイルの中身を調べるgemを作っていた。このgemが扱っているEPUB 3仕様にはEPUB CFIという補足的な仕様がある。「本の中のある一点(一文字)」や「ある場所からある場所まで」といった範囲を指定するための記法を定義した仕様だ。epubcfi(/6/36!/4/2/16/5,:25,:27)\nのようなちょっと目を疑う読みにくさの記法なのでずっと敬遠してきたのだが、ちょうど今回の「EPUBパッチ」のような時に使えるかと、数か月前に重い腰を上げて実装したのだった。正確にはパッチで終わるのでなく、差分アップデートをやってみたいと思っている。対象もEPUBじゃなくてDOMにしたい、つまりウェブページも対象にしたい。ただ、まずは(要素の省略などが許されているHTMLでなく)必ずXHTMLを使うことになっているEPUBからと思っているし、EPUBでこれができると、Kindleのようなプラットフォームで、本につけたハイライトやメモ書きを保持したまま本の内容をアップデートできるはずだ。電子書籍のいいところに、配信側が気軽にアップデートできることがあるが、そのたびにメモ書きが消えてしまうのは避けたい。また、まんがなんか特にそうだが、不要な所も含めた本全体をアップデートしていると転送料ももったいないしユーザーも長く待たされる。差分アップデートならこれが避けられる。そんな思惑で実装していたCFIの機能が、今回役に立った。GistにあるスクリプトのERRATA = [\n {target: '/6/36!/4/2/16/5,:25,:27', operation: :replace, replace: 'ri'},\n {target: '/6/36!/4/2/18/7,:33,:35', operation: :replace, replace: 'ri'},\n {target: '/6/46!/4/2/70/6/1,:0,:4', operation: :replace, replace: 'send'},\n {target: '/6/52!/4/2/28/1,:61,:62', operation: :remove},\n {target: '/6/68!/4/2/44/1:267', operation: :add, add: 'が'},\n {target: '/6/98!/4/2/12/9:7', operation: :add, add: '*10'},\n {target: '/6/118!/4/2/50/3,:45,:46', operation: :remove}\n]\nという定数で使っている。targetプロパティのところがそれだ。カンマで区切られているやつが「範囲」で、区切られていないのが「一点」を表す。削除や差し替えは「どこを」という情報が必要だから範囲を使っているし、文字の追加は不要なので一点を使っている。このCFI、おもしろい特徴があって、CFI同士、順番をつけることができるのだ。まあ、(始めから終わりまで一次元に続く)本の一点や範囲を示しているんだから、数直線上の点や範囲と同じで、考えてみれば順序が付くのは当たり前なのだが。この順番を、XHTML文書その物は参照せずに決められるところがおもしろい。他に何も見ないでも、epubcfi(/6/36!/4/2/16/5:25)\nとepubcfi(/6/36!/4/2/18/7:33)\nなら前者(epubcfi(/6/36!/4/2/16/5:25))のほうが「先」にあるということが分かる。正誤表適用前に、適用箇所が後ろの方から前の方に並ぶように、正誤表を並び替えているのだが、その時に、この順番の機能を使った(Rubyのsort_byメソッドのブロックから返している)。なぜ後ろから前なのかと言うと、もし前からやってしまうと、適用の結果DOMツリーの構造が変わって、その後の操作の適用対象がずれてしまうことがあるからだ(DOMのNodeSetの中から複数ノードを消すときなんかと同じ)。これは同じくDOMツリー上の場所を示すのによく使われるCSSセレクターやXPathにはない特徴で、パッチ適用箇所の表現に(渋々ながら)EPUB CFIを採用した理由になっている(CSSセレクターやXPathでも一定の制限を掛けてみんな守るようにすれば順番付けはできる)。余談だけど、insertじゃなくてadd、deleteじゃなくてremoveといった用語はXML PatchとJSON Patch(日本語訳)から拝借した。Nokogiri::XML::RangeNokogiri::XML::Rangeについては以前にも書いた(NokogiriでHTML(XML)内の範囲を操作するgem作った)。DOMツリー上の範囲を扱うgemだ。ウェブブラウザーではJavaScript向けのAPIであるRangeオブジェクトとして見ることができる。EPUB CFIで正誤表適用箇所を指定できたとしても、そこに対して操作ができなければまるで意味がない。CFIからNokogiri gemで表現されたDOMツリー上の範囲へ変換し、それに対して追加・削除・差し替えを実施するのにNokogiri::XML::Rangeを使っている(長さ0の範囲に対する操作として、追加にも範囲を使っている)。こう使っている。range = Nokogiri::XML::Range.new(\"CPUB CFIから変換した「始点」と「終点」の情報\")\n\n## 追加操作 ##\n# rangeは追加するべき場所を示している\ntext_to_insert = Nokogiri::XML::Text.new(\"追加する文字列\", \"ドキュメントオブジェクト\")\nrange.insert_node text_to_insert\n\n## 削除操作 ##\n# rangeは削除するべき範囲(「をを」の「を」一つとか)を示している\nrange.delete_contents\n\n## 差し替え操作 ##\n# rangeは差し替えるべき範囲(誤字「wirte」の「ir」とか)を示している\nrange.delete_contents\ntext_to_replace Nokogiri::XML::Text.new(\"差し替え後の文字列\", \"ドキュメントオブジェクト\")\nrange.insert_node text_to_replace\ndelete_contentsやinsert_contentsがやっていることは実装すると結構めんどうなのだけど、きちんと仕様の存在する挙動なので、gemに切り出しておけば安心して使える。これも正に「差分アップデートやるなら必要になるはずだな」と思って作ったgemなので、狙い通りに役立って嬉しい。EPUB MakerEPUB MakerはEPUB Parserの拡張で、その名の通りEPUBを作成するためのgem……というのは表の顔で、これを作った一番の動機はEPUBのインプレース編集にあった(EPUBを作るならRe:VIEWやgepubなど他のgemのほうがいいと思う)。冒頭でちょっと触れたDocker本のシンタックスハイライトでも使っているが、EPUBファイルの中身を、Nokogiriを使ったDOM操作などで直接書き換えることができる。Nokogiri::XML::Rangeで正誤表を適用したあとは、単にEPUB Makerの保存用メソッドを呼べば、それでEPUBファイルに適用される。item.content = document.to_xml\nitem.save\nべんり。こうして、兼ねてから用意しておいたgemの組み合わせで、今回のパッチプログラムは比較的すんなり書くことができた。気持ちがいい(とは言え、今回の本に特化した方法ならもっとずっと簡単に書ける。unzip、zip、sedくらいで充分だ。明らかにオーバーエンジニアリング)。今回足りなくて自分で書かないといけなかった汎用パーツは「EPUB CFIからNokgiri::XML::Rangeに変換する」という処理だったので、これは一般化してEPUB Parserに入れておきたい。EPUBパッチの試みこうして、ある程度アドホックに、ある程度一般的にEPUBのパッチプログラムを書いてみた。書いてみて一番大変だったのは、正誤表のEPUB CFIを作るところだった。本の中から、適用対象のXHTMLファイルを探して、その中の適用箇所を探すまでは簡単だ(grepなどでできる)。でもその場所を表現するための要素の順番などを数えるのが面倒くさい。そして間違える。あと、複数の操作を一つのプログラムで行うときの順番の扱いは、色んなケースを集めて検討する必要があると感じた。今回は正誤表を逆順に並べて適用していったけど、前の方から順番でも、そうと決まっていれば別にいい。ある処理でDOM構造が変わるとしても、「ずれた」後のDOMツリーに対してその後の操作のCFIが書かれていればいいからだ。もう少し引いた視点で、パッチの適用対象のバージョンや順番も、取り決めを作ってみんなに周知する必要があると感じた。今回のパッチプログラムの後に正誤表が追加されたとする。すると、gihyo.jpからダウンロードしたファイルには、今回分と追加分がまとまったパッチを適用したい今回のパッチを適用して楽しんでいたファイルには、追加分だけ適用したいということになる。この辺、「二つをまとめたパッチ」を作るかどうか、作るにはどういう手順で作るか、それとも必ず一つずつ順番に適用することにするか(「久し振りに開いた本」は、「パッチの適用待ち」の時間が非常に長くなるかも知れない)、決めないといけない。また、サードパーティ製のパッチについても検討できると素晴らしい。識者による注釈や、出版社を通さない作者によるコメンタリーなど(小説へのコメンタリーは、吉野茉莉さんがやっていた)用意して配布できるといい。そうした物は「どのバージョンのパッチ適用後なら適用していいか」「それより後のパッチも適用した後だった場合、どうしたらいいのか」といった難しい検討が必要になる。差分アップデートも同様だけど、「(本文を参照しない)CFIだけでの演算」でいろいろ解決できると便利なのだけど、そういったことはできるのだろうか……(というか、これができるかどうか見てみたいので差分アップデートをやりたいのだ)。EPUB CFIについて思うことEPUB CFI、あまり好きではないのだけど、今回のようなことをやるには、順番が付くという性質が役に立った。今回分かった難点というか、改善点になるのかな、は、『APIデザインケーススタディ』では(XHTMLの)id属性を全然使っていないこともあって、ぱっと見、どのトピックに対する操作なのか全然分からない(idがある場合はその値がCFIに現れる仕様になっている)。idに限らず、classなど色んな属性についてもCFI表現に出せるようになってるといいのかなあ。乱用される危険も出るが。差分アップデートの仕組みに足りていない物今回一番大変だったこととしてCFIの作成を挙げたが、そこが、差分アップデートの仕組みに足りていない。(iBooks Authorなど)オーサリングツールで何か操作をして保存したりアイコンをタップすると、自動でパッチを書き出すようになっているとすごく便利だが、僕にはGUIは理解が追い付かない……。別の方法としてEPUB用のdiffコマンドを作るというのがある、と言うか、目指している。旧EPUB、新EPUBを並べて差分を計算し、パッチの形で書き出してくれるツールだ。これでネックになるのはDOMツリーの差分計算だ。調べたところ「NP困難」と呼ばれる類の問題らしく、一般的に解決するのは非常に難しいらしい。でも、DOMの差分が作れると、ウェブ開発者一般にもとても役立つと思うので、何か、妥当でうまく利く制約があるといい。アルゴリズムとしてはBULDアルゴリズムというのが速いらしい。C++の実装はあるけれど、僕には敷居が高いので、これのポーティングを目的として今Goを勉強している(速い言語がいい)。仮想DOM方面から何か出てきたりしないかな。終わりになんだか、実現できているのは小さなことだし、書いているコードは少ないのに、長く話してしまった。お恥ずかしい。最後に大事なことを一つ、たださんと同じく声を大にしてこう言いたい。ともあれ、こんなことができるのも、ちゃんと正誤表を公表してくれる著者と、DRMをかけない素のEPUBファイルを配信してくれる出版社があればこそ。感謝したい。追記なんか間違えてた。上で何度か「差分アップデートをやりたい」と言っているが、差分アップデートは、正に今回やったこれだ(パッチ作成の部分は今は人間がやってるのでそこは将来の話ではある)。やりたいけど遠いのは、「EPUBの差分アップデート時に、ブックマークやハイライト、メモ書きなども同時にアップデートする(EPUBアップデートによって場所が動いても追従する)」ということだった。",["EPUB","Ruby"],"EPUB書籍に正誤表を反映する(Rubyスクリプトで)、またはEPUBのパッチプログラムの試み"], | |
["2016/01/03.html","昨日(EPUB書籍に正誤表を反映する(Rubyスクリプトで)、またはEPUBのパッチプログラムの試み)に引き続き「ちゃんと正誤表を公表してくれる著者と、DRMをかけない素のEPUBファイルを配信してくれる出版社があればこそ」シリーズの第二弾(第三弾は多分無い)。以前Dockerエキスパート養成読本を、ソースコードのシンタックスハイライトしながら読むという日記を書いたが、その時のスクリプトを修正して、『APIデザインケーススタディ』にも対応させた。APIデザインケーススタディ ~Rubyの実例から学ぶ。問題に即したデザインと普遍の考え方 (WEB+DB PRESS plus)著者 : 田中哲技術評論社発売日 : 2015-12-16ブクログでレビューを見る»元々の本は(PDF版から類推するに)ソースコードの所も白黒のようだけど、こうしてハイライトして読むことができるようになる。スクリプトは前と同じ所に置いてある:https://gist.github.com/KitaitiMakoto/0779a34fd74bae96468fクローンなりダウンロードなりして$ ruby rougify-gdp-book.rb path/to/api-design.epub\nと実行すればよい。これで、ようやく本を読む準備が整った。ともあれ、こんなことができるのも、ちゃんと正誤表を公表してくれる著者と、DRMをかけない素のEPUBファイルを配信してくれる出版社があればこそ。感謝したい。(EPUB書籍に正誤表を反映する)である。スクリプトの修正にあたって、プログラミング言語の推測に苦労した(ハイライトに使っているRougeはshebangを見るくらいしかしてくれない)。主な方法は、「そのソースコードが含まれる節の見出し(h2やh3)と言語の対応表を作る」ということになった。「見出しにシステムコールという語が含まれていればCだろう」という具合である。これも中々うまい法則を見付けられず、結局一つ一つの見出しとcode要素を見て手作業で対応表を作った。途中、「こんなのは人間の仕事ではない!」と思ってgithub-linguistの使用も検討したが、Gitリポジトリー全体でなく個別のテキストに対して使う方法がすぐに分からなかったのでやめた。技評の方で<code>要素のclass属性やdata-*属性で言語名を書いておいてくれると、一番楽なんだけどなあ。ところで、確認中に、EPUB版の索引が全然機能しないことに気付いたんだけど、これも本文へのリンクにするスクリプトを配ったら喜ばれるものだろうか……。",["EPUB","Ruby"],"『APIデザインケーススタディ』を、ソースコードのシンタックスハイライトしながら読む"], | |
["2016/01/15.html","Groongaで学ぶ全文検索 2016-01-15に参加して来た。今日のテーマは運用、その中でもデータの守り方。RDBMSでのデータの守り方Groongaの話の前に、ファイルとRDBMSでのデータの守り方の話をしてもらった。ファイルにデータを書き込むとデータが永続化する。書き込みの時に、途中で何らかの理由で中断が起きると途中までしか書き込まれない。「Hello」と書き込むつもりだったのに「Hel」までしか書き込まれていない、といったことが起こる。これはデータが守られていない状態だと言える。不測の事態のせいでおかしなデータを持つことになってしまっている。RDBMSでは、こういった「おかしなデータ」が存在しないように、トランザクションを使うようになっている。トランザクションを使うと、場合によってはデータが書き込まれていないことがあるが、途中まで書き込まれているというおかしな状態になるのは防ぐことができる。RDBMSがこうして頑張っていても、ストレージデバイスが壊れるとデータは失われてしまう。これを防ぐのは「運用」の仕事になる。データ消失を防ぐためには、何らかの方法でコピーを取る。コピーがあればあるだけ、それら全部が同時に壊れる可能性は低くなるので、データは守られる。ただ、お金が掛かったり帯域を使用したりするというトレードオフがある。Groongaでのデータの守り方ここまでがデータを守る運用の一般的な話で、ここから全文検索エンジン、特にGroonga族の話。全文検索エンジンはトランザクションに対応している物は少なく、Groongaも対応していない。こうした時におかしなデータという状態を防ぐには大きく分けて二つの方法がある。一つはデータベースのデータのバックアップを取っておいて、データ損失が起こったら、バックアップ時点へ巻き戻す方法。この場合、バックアップ時点から障害時点までの間に新しく作られたデータは失われ、更新されたデータは更新前の物に戻る。二つ目は、Groongaでデータがおかしな状態になったり失われたりするのを防ぐのは諦めて、マスターデータの方が失われないよう仕組みを整える方法。マスターデータがあれば、いつでもGroongaのインデックスを復旧できる。Groongaの運用では、こちらのほうが現実的。マスターデータの守り方は色々あるので、データの性質やシステムの条件などで適切な物を選ぶ。RDBMS上のデータをマスターとしたり、ファイルとして保存したり、クラウド上のデータストアに置いたり。復旧に掛かる時間復旧することでデータを元に戻せるが、これに時間が掛かると、ダウンタイムがそれだけ長くなってしまう。上の二つ目、マスターデータを頑張って守る方法は、Groongaの復旧では一から全てのデータを入れ直すので、最も時間がかかる。一つ目の方法の時間の掛かり方は、バックアップ時点から障害時点までの間のデータの扱い次第。そこのデータを諦めるなら、バックアップが(コピーなどするだけで)そのまま使えるので復旧は速い。バックアップ時点から障害時点までは、コピーではなくて差分バックアップを取っておくようにする方法もある。この場合は、バックアップ時点までの復旧は速く、そこから差分バックアップの内容を適用する部分では時間が掛かる。また、一つ目の方法、コピーを取る方法のバリエーションとして、レプリケーションもある。レプリケーションは概ねリアルタイムに全データをバックアップ取れるが、デザイン上の問題やネットワーク遅延、マスターとレプリカ間の性能の違いなどで遅延はある。GroongaでのバックアップGroongaにはトランザクションがないので、書き込み中に処理が止まるとデータがおかしなことになってしまう(ことがある)。マスターデータを用意して復旧に備えるのが現実的な運用になる。この時、前述の通り復旧に時間が掛かってしまうことになるが、普段サービスしている時は一つ一つ順にデータを書き込んでいるような物でも、まとめて一度に書き込めるモードがあって、これを使うとより速く復旧できる。また、そもそもGroongaは書き込み性能が高いので、RDBMSに比べると復旧は速い。Groonga族のMroonga、PGroongaでのバックアップは、それぞれのRDBMSの仕組みに乗るのがよい。それぞれトランザクションやレプリケーションがある。また、MySQLの場合、InnoDBのレプリケーション先としてMroongaを選ぶことができる。こうすると、(データが守られやすい)InnoDBをマスターデータとして考えることができる。PostgreSQLにもレプリケーションの仕組みはあるが、MySQLのレプリケーションの仕組みとはだいぶ違う。MySQLではINSERTなどの命令をレプリケーションに流すので、その命令をGroongaの物として読み替えることができた。PostgreSQLはファイルパスやファイル内のバイトオフセットなど物理的な位置を流してレプリケーションに使うので、この情報をGroongaのインデックス更新のために読み替えることができない。そこで、復旧の際には、PostgreSQLにREINDEXを書けてGroongaのインデックスを作り直すことになる。レプリケーションではまりがちな罠また、全文検索エンジンに限らないがレプリケーションの運用について大事なことは、システムのキャパシティを考える時にレプリカを含んではいけないということだ。マスターとレプリカを含めた台数でちょうどさばけているようなシステムは、レプリカが一台落ちただけで、システム全体が検索をさばき切れなくなって、落ちたりする。レプリカは遊んでいるように見えるのでつい使いたくなるが使ってはいけない。正確には、レプリカを入れてちょうど検索を捌けるというようなプランニング、運用をしてはいけない。その他気になったので聞いてみた。マスターDBでSSDを使うのはありなのか? 実際SSDで運用している人がいた。のでありなんだろうなあ。Groongaのデータをコピーやdumpコマンドによってバックアップ取る際、中途半端な状態のコピーになってしまうことはないのか? ある。のでシステムから切り離してバックアップするとか、バックアップ専用にテーブルを作ってバックアップ取ったら消すとか、工夫して運用する必要がある。",["Groonga","MySQL","PostgreSQL"],"Groongaの運用(データの守り方)"], | |
["2016/01/16.html","『nginx実践入門』を買った。nginx実践入門 (WEB+DB PRESS plus)著者 : 久保達彦技術評論社発売日 : 2016-01-16ブクログでレビューを見る»早速設定ファイルの所などをシンタックスハイライトした。 以下の手順で再現可能。本のEPUBファイルがpath/to/nginx実践入門.epubにあるものとする。$ gem install epub-parser -v '>= 0.2.4'\n$ gem install epub-maker -v 0.0.3\n$ gem install rouge rouge-lexers-docker\n$ git clone https://gist.github.com/0779a34fd74bae96468f.git rougify-gdp-book\n$ cd rougify-gdp-book\n$ ruby rougify-gdp-book.rb path/to/nginx実践入門.epub\nEPUBファイルを上書きするので注意すること。『APIデザインケーススタディ』(『APIデザインケーススタディ』を、ソースコードのシンタックスハイライトしながら読む)とか『Dockerエキスパート養成読本』(Dockerエキスパート養成読本を、ソースコードのシンタックスハイライトしながら読む)とか、EPUBファイルに後から手を加えてシンタックスハイライトしているけど、別にこれがそれほどいいことだとは思っていない。こういうことができるように、DRMなしのEPUBを売ってくれている技術評論社には感謝しているが、できれば本を作る時にハイライトを入れてくれるのが一番いいと思っている。追記こんなコメントを貰った。“できれば本を作る時にハイライトを入れてくれるのが一番いいと思っている” モノクロ端末や誤認識を考えると入れるのに二の足を踏むのはあるかも / “『nginx実践入門』をシンタックスハイライトする” https://t.co/xvneTcDdwf— masayoshi takahashi (@takahashim) 2016, 1月 17なるほど、確かに。色の区別がつきにくい人もいるし、白黒のままのほうがよさそうだ。切り替えられるように作るべきかは、悩ましいところ。",["EPUB","Ruby","Nginx"],"『nginx実践入門』をシンタックスハイライトする"], | |
["2016/01/18.html","Sendagaya.rb #133に行って来た。今日は、前半『メタプログラミングRuby 第2版』を読んで、後半はAction Cableを読んだ。メタプログラミングRuby 第2版メタプログラミングRuby 第2版著者 : Paolo Perrottaオライリージャパン発売日 : 2015-10-10ブクログでレビューを見る»fukajunさんが「本を読むってどうやってやるんですかねえ?」って言ったけど誰も答えを持ち合わせていなかった。十五分みんな黙読し、その後気になったことを話すというスタイルになった。範囲は「2章 月曜日:オブジェクトモデル」の始めから「2.2.4 オブジェクトとクラスのまとめ」まで。みんなRubyを書けるので特に問題がなく、字が汚いとかmodule Rake\n class Task\n # ...\nをclass Rake::Task\n # ...\nって書いたらHoundCIに怒られるんだけどなんでだろう? といったことを話していた。次回は「2.2.5 ネームスペースを使う」から。毎週ちょっとずつは読むとのこと。また、ここのところ本にシンタックスハイライトを入れるのをやっていたので、この本も帰ったらやろうと思っていたが、始めからハイライトされていた。 Action CableApplicationCable::Channelのユーザー定義のメソッドのスタックトレースを遡って、どこから呼ばれるのかを見ていった。fukajunさんがプロジェクターで映しながらエディターを開いてソースを追い掛け、みんなで横からああだこおだと言っていた。読んだのはだいたいこの辺。action_cable/connection/subscriptions.rbaction_cable/connection/message_buffer.rbaction_cable/server/worker.rb一度CelluloidをやめてConcurrent Rubyにしたところ(3b7ccad)、それを巻き戻し(d0393fc)、更にまた巻き戻す(01c3200)ということをしていて、この辺の扱い大変なんだなあという話をした。使いたいのはCelluloidの方であるようだ(#22977)。ここを読んだおかげで、Rails5.0.0-beta1のActionCableを使って超簡易チャットを実装してみたにあるclass MessagesController < ApplicationController\n def create\n ActionCable.server.broadcast \"messages\",\n message: params[:message][:body],\n username: cookies.signed[:username]\n\n head :ok\n end\nend\nみたいなコードを見ても驚かずに「なるほどActionCable.server.broadcastに渡している\"messages\"コマンドは別スレッドで実行されるからここはブロックせずに、即座にブラウザーに応答することができるんだな」と考えることができるようになった(でも、僕は気にならないけど、コントローラー内でこういうことするのに違和感を覚える人もいた)。electron、rails-assets、JavaScript生態系なんかelectronが盛り上がってるらしく、夕食を頂きながら仕組みとかHTMLで作るのどうなの? とか、rails-assetsとか派生してJavaScriptの開発環境って今どんな感じなの? といった話をした。",["Ruby","Ruby On Rails","Action Cable"],"Sendagaya.rb #133"], | |
["2016/01/20.html","久し振りに渋谷.rb[:20160120]に行って来た。初参加の人が半分近くいて、すごいなあと思った。予め予告のあった主人がExcel方眼紙に殺されてRubyを書き始めてから5ヶ月が過ぎましたっていう発表のほか、みんな何かしら話すことがあって、九時過ぎまで発表が続いていた。中でもyuku-tさんの発表が印象に残っていて、最終的にはduck_testingっていうRubyGemの紹介と「いい感じにしてほしい」というお願いになったんだけど、このgemを作るに至る経緯の説明がよかった。基本的には、型でチェックしたいという話。だから、各メソッドにはYARD向けのコメントで引数の型と戻り値の型を書いている。でも当然ながらYARDのコメントをRubyは見てくれないので、contracts.rubyの導入を検討した。でも、実装方法が危なっかしい(中では、古い人は知っているかも知れない、MethodDecoratorsを使っている)のと、本番でオフにできないので、やめた。YARDのコメントを読み取って自動で型に関するテストを生成し実行する、というgemを作ったとのこと。YARDはコードの解析にRipperを使っていて、そのASTにプラグインからアクセスできるので、contarcts.ruby用のプラグインを書けばそこからドキュメントを自動生成できるはずだし、事実そういうYARDプラグインがあるらしい。でも既にYARD向けのコメントをたくさん書いていて、それを全部直して回るのは大変。さらに、YARDでドキュメントコメントを書いているgemは多いので、そこに一行足すだけで自動的にテスまでできるという物は、多くの人に役立つはずだ、という説明がされた。最後の、自分の問題を解決すると、より多くの人の問題も、労力無く解決できるという視点が素晴らしいなと思った。期待したい。僕は先日の日記に書いた『nginx実践入門』をシンタックスハイライトするの内容をデモしつつ、「コードからその言語を判定するところ、今は目視でやって設定をハードコードしていて辛いので、やってくれるgemを知っている人がいたら教えてください」という相談をしてきた。その後の夕食の時とかに聞くと、意外とこれが反響があって嬉しかった。もうちょっと抽象化してgemにしたいなという気持ちになってきた。あと、渋谷.rbとは関係ないけど日記なので書くと、itamae-plugin-resource-security_contextというItamaeプラグインをリリースした。今のところrestoreconをするだけだけど、仕事で必要になるに連れてほかのこともできるようにしていきたい。SELinux使っている人がもしいたら、一緒に開発していきましょう……。",["Ruby","Itamae"],"渋谷.rb[:20160120]"], | |
["2016/01/24.html","Droongaは、しばらくマニュアル通りにインストールできない状態だったので、インストーラーを直してプルリクエストを送った。https://github.com/droonga/droonga-engine/pull/39https://github.com/droonga/droonga-http-server/pull/13(マージしてもらった後に問題に気付いて追加でちょいちょいパッチを投げてもいる)マージされたものの、今はまだ修正版がリリースはされていないので、それぞれこういう風にVERSION環境変数を指定してインストールする必要がある。# curl https://raw.githubusercontent.com/droonga/droonga-engine/master/install.sh | \\\n VERSION=master bash\n# curl https://raw.githubusercontent.com/droonga/droonga-http-server/master/install.sh | \\\n VERSION=master bash\nリリースされたらVERSION=masterはなくてもインストールできるようになる。また、マニュアルではserviceコマンドでDroongaの二つのプロセスを管理しているけど、今回のパッチでsystemdを使うようになったので、今マニュアルを直しているところ。来週中にはパッチを送れるはず。マニュアル通りに入れられないこと自体は認識していて、ワークアラウンド入りのItamaeレシピを作って使っていた(DroongaをインストールするItamaeレシピ)。後ろめたさがあった。スキル的には自分で直せるはずなのにそうしていないのは愚か、問題報告すらしていなかった。今回修正して、受け入れてもらって、喉の小骨がようやく取り除けた気分だ。恥ずかしながらシェルスクリプトに不慣れで、プルリクエストを送ったあとで、色々指摘してもらいながら直していた。ありがとうございました。systemdのunitファイルにも不安があるので、こうしたほうがいいよというのがあったらぜひ教えてほしい。droonga-engine.servicedroonga-http-server.service",["Droonga"],"Droongaのインストーラーを直した"], | |
["2016/01/25.html","Sendagaya.rb #134に参加して『メタプログラミングRuby』の読書会をして来た。今日は初参加、それも「最近Ruby始めたばかりで……」とか「プログラミングを始めたばかりで……」という人がすごく多かった。何があったんだろう?先週に引き続き『メタプログラミングRuby 第2版』を読んだ。メタプログラミングRuby 第2版著者 : Paolo Perrottaオライリージャパン発売日 : 2015-10-10ブクログでレビューを見る»「2.2.5 ネームスペースを使う」から15分みんなで黙読して、その後に気になることなどを話した。この「気になること」が盛り上がって、今日は他のことはしなかった。結構知らないことが書いてあって(第1版読んだはずなんだけど殆ど憶えてない……)面白い。例えばコラムでloadに触れていた。普通loadは呼ぶ度にスクリプトが実行されるので、定数定義があると再定義の警告が表示されてしまう。ところが第二引数にtrueを渡してload('defining-constants.rb', true)\nと呼ぶと、この警告が避けられる。なぜかというと、「第二引数にtrueを渡すと、無名モジュールが作成され、スクリプトはその中で実行されるから」だと本では説明されている。「なるほどそうなのか、第二引数のこと知らなかった、勉強になったなあ」と、僕はあっさり流していたが、参加者から「無名モジュールってなんですか」っていう声が上がってそう言えば、知らないな、と思った。無名クラス(匿名クラス)はたまに使うのでそこからの類推と、文脈と合わせて勝手にイメージを作って納得していたが、知らない。ということで、デスクトップをプロジェクターで映していたfukajunさんがターミナルを出して、動かしてくれる。みんなで「こういうことかな」「じゃあこういう時はこうなるのかな」などと言っていると、それも全部実行してくれる。こうしてみんなで無名モジュール(anonymous module。僕は匿名モジュールと呼びたい)の理解を深めた。(翻訳の角さんから、無名、匿名についてこんなコメントを頂いた。https://twitter.com/kdmsnr/status/691839083266641920。うーんなるほど、勉強になる……。)その後Classのクラスは? Moduleのクラスは? Moduleのスーパークラスは? みたいな話が出ていて、Ruby始めたばかりの人がぴんとこないということだったのでその解説をしたりもした(tkawaさんが)。prependは第1版にはなかったこともあってかなり盛り上がった。次の記事を見て老害がノスタルジーに浸りながらalias_method_chainの仕組みを解説したりもした。Ruby2.0のModule#prependは如何にしてalias_method_chainを撲滅するのか!?本に戻って、文中、prependに関して、こういう風に動作を説明してくれる。module M1; end\n\nmodule M2\n include M1\nend\n\nmodule M3\n prepend M1\n include M2\nend\n\nM3.ancestors # => [M1, M3, M2]\nprependしたM1がM3の前に差し込まれて、includeしたM2が後に置かれている。一度継承ツリーに入ったモジュールは二度は入らない。説明の通りだ。では、module M3\n include M2\n prepend M1\nend\nと順番を変えたら、M3.ancestorsはどうなるだろうか。実行せずに答えられるだろうか? その答えに、どのくらい確信を持てるだろうか? これも、各々推測を述べた後、「fukajunターミナル」で実行して、回答を得たりした。こういう、一人で本を読んでいるだけだと見逃しがちなことも、誰かが気付いて声を上げてくれるので読書会中々いいです。ただ(と言っても別に悪いとは思ってないけど)、このやり方は時間は掛かる。ので今日のSendagaya.rbはここで終わった。次回は『メタプログラミングRuby』引き続きThe Rails Doctrineを読んで何か話すUpgrading to Rails 5 Beta - The Hard Wayを読んでRails 5へのアップグレードの辛さを感じるなんかがいいですかねえ、って話をしていたけど、来週その場で決まることでしょう。ちなみに、今回は開催前日にDoorkeeperのイベントが作成されたけど、次回分はその日のうちに作成されていたので、もう申し込める:https://sendagayarb.doorkeeper.jp/events/38208",["Ruby"],"『メタプログラミングRuby 第2版』@Sendagaya.rb #134"], | |
["2016/01/26.html","ようやく『APIデザインケーススタディ』を読み終えた。デザインの本なのだけど、僕はRubyの様々な組み込み・標準添付ライブラリーの解説書、またはエッセイとして、主に読んだ。楽しい時間で、読み終わって寂しい。APIデザインケーススタディ ――Rubyの実例から学ぶ。問題に即したデザインと普遍の考え方著者 : 田中哲著株式会社技術評論社発売日 : 2015-12-16ブクログでレビューを見る»本当は25日に読み終わったのだけど、昨日はSendagaya.rbのことを書いたので、話題を分けるために今日にした。こういう時、tDiaryが恋しくなる。日記の為のソフトウェアだから、一日に複数の話題を書くことが、自然にできる。この本は「I/O」「ソケット」「プロセス」「時刻」「数、文字列」の五章で構成されている。個人的にこれをOSとの間を埋める(「I/O」、「ソケット」、「プロセス」)人間社会との間を埋める(「時刻」)その他(「数、文字列」)と捉えた。OSとの間を埋めるRubyのI/Oやプロセスなどに関するクラスは、システムコールやglibcの関数を、インターフェイスは模倣して、内部では呼び出すのを原則としてデザインされている。その中でも、そのままglibcからRubyに移植せず、敢えて異なるインターフェイスや動作にしたほうがいい所があったのでそうした、や、ユースケースを考えるとメソッドを追加したほうがいいからそうした、などの事例が紹介されている。それぞれに調査結果、考察、決断が述べられていて、単なるカタログにはなっていなくてケーススタディする内容があり、面白い。僕はRubyのユーザーではあるけれど、Cは書けない。拡張ライブラリーは使う一方だし、OSのシステムコールなども直接触ることはまずない。IO#read_nonblockなども、使うことはあってリファレンスマニュアルは読むけど、そこに書いていることで満足して、それでも不明なところは(Rubyプログラムを)動かして確認してから使っていた。この本では、そういった部分のOS側の話なども解説した上で、Rubyではどうしたかと話してくれるので、デザインだけでなく、APIその物の勉強にもなる。「自分が触っている部分の一つ下のレイヤーも理解しておくべきだ」とはよく言われることで、でも中々実践は難しい、その部分を行えるので助かる。また、余談になるが、ノンブロッキングI/Oという言葉は非常によく聞くし、一応、EventMachineやNode.jsなどで使えてはいるつもりだ。でも、下のレイヤーでは、ファイルディスクリプターにブロッキングモードとノンブロッキングモードがあって、ノンブロッキングモードの時にアクセスして読み取り可能でなければこういうエラーになって、という仕組みになっていたことは全く知らなかった。この知識が、直後に読み始めた『nginx実践入門』の最初のほうで活きたので思わず顔がにやけた。人間社会との間を埋める第4章は時刻の話。ここはもう、人間側の都合に合わせる話が圧巻。閏秒があるから将来の日時を正確に表すことは不可能、というのは序の口で、日本にいると、UTCにタイムゾーンオフセットを足せばいいんでしょ? くらいに考えてしまうかも知れないけど夏時間があったらそうはいかない。夏時間、いつから夏時間で、いつまで夏時間か、どのように決まるのか、知っていますか? 僕は知らなかった。「毎年これこれの月のこれこれの曜日」みたいに決まっているのはいい方で、太陰暦に従うとか、その年にならないと政府が決めないとか、ほんと色々ある。夏時間の切り替わりのタイミングでは、存在しない日時や二重に存在する日時が存在するので、日時の扱いは慎重さが必要になってくる。その他ここは数値関連の色々な話。ここまで読んでいれば、APIデザインのパターンに少しは慣れているので、なるほどなるほどとさくさく進む章だった。「終わりに」でところで、意識的に使いやすさをデザインするにあたって、具体例と並んで存在して欲しいのは、使いやすさの理論です。理論があれば、個々の具体例がどのような理屈で使いやすさを実現しているか、理解しやすくなりますし、目の前の問題を解決するライブラリをどうデザインするのが良いのか、という見通しを与えてくれるでしょう。さらには、使いにくさという問題を発見することにも役に立つかもしれません。残念なことに、今のところ、そのような理論で満足できるものはなさそうです。ということが述べられていた。何気ない文のようだが、最後まで読んだ所で「意識的に使いやすさをデザイン」と言われると、とても重みがある。",["Ruby"],"『APIデザインケーススタディ』を読んだ"], | |
["2016/01/29.html","Groongaで学ぶ全文検索 2016-01-29に行って来た。今日は濃かった。その分面白かった。まずノーマライザーの話をして、その後形態素解析の話をした。ノーマライザーノーマライザーはノーマライズする物。ノーマライズは、半角「カレー」と全角「カレー」、ひらがな「りんご」とかたかな「リンゴ」など、厳密には違う物を同じ物に寄せるという処理。どういう基準でノーマライズするかというのは色々あって、その色々ごとにノーマライザーの種類がある。インデックスを作る時、インデックスのキーにノーマライズした物を選んでおくと、例えば「りんご」で検索した時に、「りんご」を含む文書と「リンゴ」を含む文書の両方を返すことができる。これがノーマライザーを使う理由。ただ、この状況下で「リンゴ」で検索すると、インデックスのキーワードには「りんご」しかないので、何もヒットしない(「リンゴ」というキーワードはインデックスを作る時にノーマライズされて「りんご」になっているから、入っていない)。だから、検索時にクエリーの方もノーマライズする。この時にはインデックス作成時と同じノーマライザーを使わないと、キーワードの集合が変わるので、いけない。(面白い質疑応答二つあった、後で書く。かも。)形態素解析形態素解析は、文などを形態素に分ける処理。形態素が何かは各自調べられたい。迂闊なことを言えない。日本語の文を構成する、意味的に分割できる一番小さな単位、とかそんな感じ。全文検索では、インデックスのキーワードをどのように選ぶかというやり方の一つとして、形態素解析器を使う(他にN-gramがあるのは以前書いた通り:日本語文書の全文検索)。例えばクエリーの中の「りんご」と「リンゴ」を同一視するには、文字単位で見ていって、かたかなを見付ける度に全部ひらがなにしていけばいい。こういうノーマライズは文字単位で処理すればできる。が、「PC」で検索した時に「パソコン」を含む文書を返す、二つを同一視するにはこの方法では不可能。「パソコン」を見つけたら「PC」にする必要がある。この時に「パソコン」が一つの単位になるように、クエリーを分割する方法(形態素に分割する方法)、「パソ」みたいに「パソコン」より細かく分割しないし、「パソコンを」みたいに他の語とくっついた形で大きな粒度で分割しない方法。「パソコン」と「PC」は同じ物だ、という日本語の知識(扱うアプリケーションによる。同じ物と扱いたくないアプリケーションもあるだろう)の知識が必要になる。前者が形態素解析と呼ばれる処理。形態素解析器は「『パソコン』というのは名詞である」「『で』というのは助詞である」「『で』は文の最初には現れない」といった知識(辞書)を持っている。この知識を元に文を解析して、日本語としてありそうな切り方を何パターンか出す。その中の一番ありそうな物を結果として返す(それを全文検索エンジンが使う)。ところで、どのように切るか、というのは、ドメインによって変わる。新聞で使われる言葉、ツイッターで使われる言葉、若者の言葉、年寄りの言葉……。 アプリケーションが扱うドメインが分かっているなら、一般的(?)な切り方でなく、それぞれに適した切り方ができるはずだ。この「それぞれに適した」は、それぞれ用の辞書を使うことで対応する(形態素解析器自体は変えない)。時代とともに検索結果が古びてきたようだと、辞書を新しくする必要も出てくるだろう。辞書を新しくするということは、インデックスのキーワードの選び方が変わるということだから、その時にはインデックスを作り直す必要がある。後者、単語(形態素)の同一視は、ノーマライズの前のクエリー展開というフェイズでやっている。そこは辞書を元にクエリーを見て、「パソコンほしい」 OR 「PCほしい」 OR 「パソコン欲しい」 OR 「PC欲しい」\nみたいなクエリーに変換して検索エンジンに投げている。「パソコン」と「PC」を同一視するには、形態素解析の結果が欲しいところだがクエリー展開はその前なので、ここは独自に頑張ったりする。その場合は「(二重に同じ処理をすることになのるで)トークナイズノーマライズ(勉強会中に発表し、直してもらった)はしないように」といった仕様上の注意が生まれる。今回は話題が広くて、また勉強会中に公開したいという制約とでまとめられないがこんな感じだった。あとで追記するかも知れない。",["全文検索","Groonga","形態素解析"],"ノーマライズと形態素解析"], | |
["2016/02/02.html","Sendagaya.rb #135に行って来た。今日も前半は『メタプログラミングRuby 第2版』を読んで、後半はRails 5のチェンジログからActive Record 5.0.0.beta1のチェンジログを眺めていた。メタプログラミングRuby Refinements今日は「2.4.2 メソッドの実行」から「2.4.3 Refinements」まで。メソッドの実行では他の言語から来るとprivateとかに戸惑うよねとか、protectedって何に使うんだろうねという話をした。お次はいよいよRefinements。class MyClass\n def my_method\n \"original my_method\"\n end\n\n def anothor_method\n my_method\n end\nend\n\nmodule MyClassRefinements\n refine MyClass do\n def my_method\n \"refined my_method\"\n end\n end\nend\n\nusing MyClassRefinements\nMyClass.new.my_method # => \"refined my_method\"\nMyClass.new.another_method # => \"original my_method\"\nこの最後の行は驚かないだろうか。僕は驚いた。この挙動を本ではRefinementsが有効になっているコードは、(略)インクルードやプリペンドしたモジュールのコードよりも優先される。と説明している。しかしこれだけで、メソッド探索の順番を覚えられるだろうか。@tkawaさんの説明が素晴らしかった。クラスやインクルードやプリペンドを考慮するより先に、まずRefinementsを探す次に通常のメソッド探索手順に従って、プリペンドされた物、インクルードした物、特異クラス、クラス……とメソッドを探すということだ。上の例で言うと、まずmy_methodを呼ぶ場合Refinementsを探すMyClassRefinementsが見付かるmy_methodが定義されているMyClassRefinements#my_methodを呼ぶで、結果は\"refined my_method\"になる。一方another_methodはRefinementsを探すMyClassRefinementsが見付かるanother_methodは定義されていないRefinementsの探索は終わり、今後二度と探索されないプリペンドされたモジュールやインクルードされたモジュールを探すが、ないクラスを見るMyClassであるanother_methodが定義されているanother_methodを実行するmy_methodが呼ばれているRefinementsの探索は終わっているので、クラスのmy_methodが見付かるMyClass#my_methodを実行するということで、結果が\"original my_method\"になる、というわけだ。子の説明を聞いて僕は「あ。」と声が漏れるくらい腹に落ちた。Active Record 5.0.0.beta1チェンジログその後もちょいちょいRefinementsと遊んでからはActive Record 5.0.0.beta1のチェンジログをざっと流しながら気になった所で止めて、あーだこーだ言っていた。結構RDBMSの個別機能に対応していたり、細かなユースケースを拾ったりしていて、「Active Recordは基本は既に成熟しているんだろうなあ」という感想を持った。次回は『メタプログラミングRuby』読むほか、fukajunさんがElectronについての発表をしたいということなのでそれは聞けるはずだ。あとはその場で決まるんだろう。その場で決まるので、聞きたいことを持って行けば、聞けると思う。次回分もすぐさまイベントが作られて、申し込める。https://sendagayarb.doorkeeper.jp/events/38655",["Ruby","Ruby on Rails"],"Sendagaya.rb #135"], | |
["2016/02/07.html","この日記に検索機能を付けてフッターに置いた。軽快にインクリメンタルサーチができていて、なかなかいい。Groongaを使った全文検索この際に、二つやったことのない作業があって、その一つは検索エンジンを使った検索機能の作成。正直、静的サイトの検索なんてGoogle Custom SearchやSwiftypeを使えばいいと思うが、自分で遊ぶための場所を持つというのも日記を移行した目的の一つだったから、自分でやってみた。Middlemanでサイトをビルドした後、Rakeタスクでビルド済みディレクトリーから日記本文を抽出して、Groongaに投げ込んでインデックスを作っている。始め、Middlemanのビルド時のidentical、updated、created、removedという状態変化の情報を利用して、必要な分だけGroonga上のインデックスを更新しようかと思っていた(そのための調査結果はQiitaのMiddlemanで「変更なし」「作成」「削除」「変更」の状態を取るという記事に書いた)。でも、実用上それで問題ないのだが、一応「ビルド環境が変わったらそういった状態変化の情報は変わる、ポータブルではない」ということに配慮して、毎回全日記の分をGroongaに投げるようにしている。今のところ、パフォーマンスが問題になったりということは全然ない。そのように毎回全部作り直すことにしたので、ローカルでインデックス構築済みgroonga-httpdのDockerイメージを作って、それをデプロイするようにしようかとも思った。実際イメージを作るまではやっていた。が、せっかくGroongaがなるべくOSの機能を引き出すように作られているのに、仮想環境で動かすのはもったいないと思って「普通」にインストールしてsystemdで起動して使っている(Linodeを使っているから、まあ、仮想環境ではあるが)。何故かsnippet_html()関数がうまく動いていないので、別途調査が必要だ。そう言えば、記事を削除した時の対応もRakeタスクにない。消すことがあったら考えよう。記事データをGroongaを投げる時に削除できるよう自動化するのでなく、PostgreSQLのVACUUMみたいに、ユーザー側で明示的に実行するのでいいと思う。Polymerコンポーネントの作成もう一つは、検索エリアを作るのに、Polymerを使って検索用のコンポーネントを作った(HTMLソースを見れば<blog-search>というタグが見付かるはずだ)こと。作り始め、全く何も表示できず、Polymerに慣れてもいないので自分が何か間違っているかと色々試しすごい時間を費したが、内部で使っているコンポーネントを読み込む<link>要素のimport属性をipmortと書き間違えていただけだった。分かった時には脱力した。ユーザー入力(<paper-input>)、Groongaとの通信(<iron-ajax>)、その二つの繋ぎ(<blog-search>)という構成で作ったが、<blog-search>は昔ながらのjQueryを使った素朴な同期処理のようになってしまった。Polymerにはデータバインディングの仕組みがあるのにもったいない。もう少しすっきりするよう書き換えたい。しかし、他のコンポーネント指向のライブラリーもそうだと思うが、閉じたスコープのことだけを考えてHTML、CSS、JavaScriptを書けばよいというのは大分ストレスフリーだ。前からずっと、「あの記事はどこだっけ」と思った時にgit grepしてURIを調べてからページを表示していたので、それが無くなって自分が嬉しい機能だ。追記と思いきや、このサイトは外にリンクを置く時はHTTPSで置いているのだけどgroonga-httpdのdebパッケージにはTLSモジュールが組み込まれていなかった。取り敢えずNginxを前に立てたけど、groonga-httpdのウェブサーバー機能もNginxなので、こちらのビルド時にTLSモジュールを組み込むのが正しいと思う。後でメーリングリストに送って入れてもらうか自分でビルドするかしよう。",["Groonga","Polymer","Middleman"],"日記に検索機能をつけた"], | |
["2016/02/12.html","Groongaで学ぶ全文検索 2016-02-12に行って来た。今日は、参加者が勉強にと国立国会図書館の書誌情報データを取って来てGroongaに入れてみている、だが今のテーブル設計がいいのか分からないということだったので、それをまず説明してもらって、@ktouさんが各テーブル作成時のオプションなどを説明してくれた。インデックスを作る前と後で検索結果を比較していて、インデックス作ってそっちで検索すると20倍速くなったということで、インデックスの有用性が証明されてた。そんなわけで個別に色々説明してくれたのだが、まとまっているわけではないので、幾つか取り上げることにする。WITH_POSITIONの活躍今回はインデックステーブルを作る時に、タイトルに対応するインデックスカラムを作る時にCOLUMN_INDEX|WITH_POSITIONと、WITH_POSITIONが指定されていた。これがうまく活きる状況の話があった。『一、二年生の基礎英作文』というタイトルの本があった。これに対して「一年生」で検索した時に、この本はヒットするだろうか。但し、実際のトークナイザーはTokenBigramが選ばれていたが、仮にTokenMecabにしたとする。タイトルは「一」「、」「二」「年生」「の」……とMeCabはトークナイズした。検索クエリーは「一」「年生」になる。とすると、このタイトルはクエリーのトークン「一」にも「年生」にもヒットするので、ヒットしそうだ。しかししない。なぜならGroongaはこのタイトルカラムではキーワードの位置情報も持っていて、「一」と「年生」がこの順番で隣り合っていないことを知っているからだ。同様の理由で、仮に『二年生の基礎英作文(一)』みたいなタイトルの本があったとしてもヒットしない。このように、「キーワードが文書中でどの位置にあるか」という情報も持たせるオプションがWITH_POSITION。なので逆に、これを指定していなければ上のクエリーでこの文書はヒットするだろう。MeCabみたいな形態素解析をせずbi-gramでWITH_POSITIONなし、みたいなカラムを作ると、色々な物がヒットし過ぎて検索には使い物にならないカラムになったりもする。bi-gramでトークナイズしている時は、一文字では検索できないのか?bi-gramでトークナイズしている時は、インデックスのキーには二文字の文字列が入っている(文末など場合によっては一文字もある)。そういう時に、検索クエリーが一文字の時には、何もヒットしなくなるのでは? という疑問が出た。答えはヒットする。今回の実例では、インデックスキーをパトリシリアトライで作っていた(=ハッシュにしていなかった)から。パトリシアトライなら前方一致検索ができるので、一文字のクエリーでも探すことができる。なぜパトリシアトライなら前方一致検索ができるのか? 忘れた。もうパトリシアトライの構造を忘れたので後で見ておく……。尚、MySQL 5.7から入ったInnoDBの全文検索機能では、bi-gramでトークナイズする設定にすると、実際に一文字での検索ではヒットしなくなるらしい。テーブルのキーに選ぶべき物今回は、国会図書館から提供されているTSVのうち、URL、タイトル、著者、出版社をGroongaに入れていた。テーブルとしてはハッシュテーブルを選び、キーにURIを入れていた。これは正しい設計だろうか?具体的なアプリケーションの要件によって、考慮するべきことがある。ハッシュテーブルでいいだろうか?いいとして、キーはURLでいいだろうか?ハッシュテーブルのいい所は、キーが存在していて、そのキーを使ってレコードを一意に特定できる所。逆に言うと特定する必要がない、つまり後からレコードを探して更新や削除をすることが無いのであれば、ハッシュテーブルを使う必要はない。追加しか無いのなら、配列を使ってもいい。ハッシュテーブルを選んだとして、そのキーカラムのサイズは(現在のGroongaでは)4KiBになっている。普通それならURLを入れても大丈夫だろうと思うが、溢れることがあると@ktouさんは主張していた(何か嫌な思い出がありそうだった)。ので、今回の場合だと、選択したカラムにはなかったが、ISBNを使うのがよさそうとのこと。また、そういう一意かつ短めの物が選べない時は、一意な物のハッシュ値を計算してキーとして入れるといいとのこと。",["全文検索","Groonga"],"実例を元にした全文検索エンジン(Groonga)のテーブル設計"], | |
["2016/02/15.html","Sendagaya.rb #137に参加して来た。『メタプログラミングRuby 第2版』と、Active Record enumsの話をして来た。メタプログラミングRuby 3章 メソッド今回もメタプログラミングRubyを読んだ。「3章 メソッド」から(こういう時、章にもリンクを貼りたいものだ)。同じようなメソッドの定義を繰り返すのではなく、動的に定義することで、重複した記述を減らす方法が紹介される。class Computer\n def initialize(computer_id, data_source)\n @id = computer_id\n @data_source = data_source\n end\n\n def self.define_component(name)\n define_method(name) do\n info = @data_source.send \"get_#{name}_info\", @id\n price = @data_source.send \"get_#{name}_price\", @id\n result = \"#{name.capitalize}: #{info} ($#{price})\"\n return \"* #{result}\" if price >= 100\n result\n end\n end\n\n define_component :mouse\n define_component :cpu\n define_component :keyboard\nend\nこれで def mouse\n info = @data_source.get_mouse_info(@id)\n price = @data_source.get_mouse_price(@id)\n result = \"Mouse: #{info} ($#{price})\"\n return \"* #{result}\" if price >= 100\n result\n end\nみたいなメソッドを幾つも書く作業から開放される。例によってふんふんなるほどと読んでいたが、例によって甘かった。十五分読んだ後にみんなで話している時に、このコードの「危なさ」を指摘する声が上がった。「initializeでdata_sourceを引数に受け取っているが、別々のインスタンス初期化時に別々のdata_sourceを受け取り得るから、クラス全体が和集合のような不要なメソッドも持った物になる」とのことだった。僕にはぴんとこなかった。その後、コードを使って説明してくれた。methods = [:moge, :hoge]\nmethods2 = [:moge, :hoge, :age]\n\nclass Computer\n def initialize(methods)\n methods.each do |method|\n self.class.define_kick method\n puts method\n end\n end\n def self.define_kick(name)\n define_method(\"#{name}_kick\") do\n puts 'name'\n end\n end\nend\nc1 = Computer.new(methods) # => \"moge\"、\"hoge\"を出力\np c1.class.instance_methods(false) # => [:moge_kick, :hoge_kick]\nc2 = Computer.new(methods2) # => \"moge\"、\"hoge\"、\"age\"を出力\np c2.class.instance_methods(false) # => [:moge_kick, :hoge_kick, :age_kick]\np c1.class == c2.class # => true\np c1.class.instance_methods(false) # => [:moge_kick, :hoge_kick, :age_kick]\n(少しコメントを足し、改変した)最後の行、c1でも期待しないメソッド#age_kickが使えることを示している(その前の行からも明らかだが)。こういう危なさがあることに、僕は気が付かなかった。勿論、これが「危ない」かどうかは作るアプリケーション次第だ。別にこれで構わないということも理論上ある。それは何かと話していて、「サンプル」かなとなった。ActiveRecord enumsのDB内での値を文字列にするその後、今日は何を話そうかと話していると、Rails 4.1から入ったActiveRecord enumsの機能についての相談が上がった。この機能はRDBMSなんかでも実装されているenum(列挙型)の機能をRailsのレイヤーで実装した物で、ユーザー(Railsを使うプログラマー)はモデルオブジェクトが提供する、人間が読みやすいインターフェイスだけ扱っていればいいようになる。バックエンドではActiveRecordが数値に変換してDBに格納し、ユーザーとの間を取り持ってくれる。今回の相談は、この機能ではDBに格納する値も文字列にすることができるが、それは普通ではないのだろうか? という物。始め、「何となく気持ち悪いですね」くらいしか言うことができなかった。一応、データサイズが増えるというのもあるが、このご時世、そこは気にするポイントではないだろうとのこと。しばらく話しながら思い付いたのは、文字列にするとインデックスを張ることになるだろうから、インサート時にインデックスを更新するコストが掛かって(遅くなって)しまうということだった。これはそれなりに妥当だと受け取られて、「じゃあ、やっぱり(ActiveRecordの)enumのバックエンドは数値にしておくのが普通なんですね」ということになった。ちなみに相談者が文字列を使いたい一番の背景は、Railsは分からないがSQLなら分かるという人のいる場(に、今いるらしい)では、値が数値で返って来ると人間の方で対応する文字列を参照しないといけないので、嫌だ、ということだった。これは勿論妥当だと思うので、この人のケースでは、文字列を使うのは正解だと思う(が、RailsAdminがエラーになるという問題があるらしかった)。それは置いておいても割と一般的に文字列を使いたいこだわりがありそうだったが、そこは深く話さなかった。今思うともったいなかったかも知れない。「そう言えば、Railsを使うとマスターテーブルを作ることをしなくなるな」という話もした。『エンタープライズRails』には「アプリケーションのフレームワークや言語よりも、データベースのデータのほうが長く残るから、データだけで完結できるようにしておくべきだ」と書いてあったように思うのだけど、時代も違うし、エンタープライズだとウェブのコンシューマー向けサービスとは領域も違うということだろうか。",["Ruby","Ruby on Rails"],"Sendagaya.rb #137"], | |
["2016/02/21.html","A Tour of Goのエクササイズをやって、GitHubに上げてみた:github.com/KitaitiMakoto/a-tour-of-go-exercises。ウェブクローラーの課題(exercise-web-crawler.go)が難しくて、まずgoroutineを使って非同期にクロールさせるのに手こずった。一つのHTTPリクエストに一つのgoroutineを割り当てた時、終了の待ち合わせはどうするのがいいんだろう。僕は、一々チャンネルを閉じるようにした。閉じるのの待ち合わせはfor range ch {}\nとやった。この他に、go呼び出しの辺りにラベルを付けて、groutineから戻ったところでbreakするというやり方もあるようだ。エクササイズは本当は教師がいて答え合わせしてもらえるととてもいいのだけど、残念ながらいないので、誰か、「こうしたらもっといいよ」というの教えてください。まあ、色んなソースを読むというのが、よいのだろうとは思うが。次に、「一度フェッチしたURIは二度フェッチしないようにする」というのも課題の一部で、ヒントに「その管理にマップを使うのはいいけど、マップは単独では並行処理に関して安全ではない」とあって、この排他制御にもちょっと困った。大きなロックを獲得して、その中でフェッチすると、並行処理させている意味が無くなっちゃう。でも、どのタイミングでロックを取ればいいのか、何のロックを取ればいいのか、というのに迷った。「一つのURIにつき一つのミューテックスを作る」ということを最初考えたのだけど、そもそもあるURIに対応するミューテックスが存在するかの確認処理と、その後ミューテックスを作るまでの間に他のgoroutineが同じ物を触る可能性があるわけで、うまくいかない。結局、単純にマップその物をロックするようにした。そうすると、deferを使わない実装になってしまったのだけど、もっといいやり方がないものだろうか。",["Go"],"A Tour of Goのエクササイズをやってみた"] | |
] | |
column_create ApehuciTerms title_index COLUMN_INDEX|WITH_POSITION Apehuci title | |
column_create ApehuciTerms tags_index COLUMN_INDEX|WITH_POSITION Apehuci tags | |
column_create ApehuciTerms content_index COLUMN_INDEX|WITH_POSITION Apehuci content |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment