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

ククログ


階層構造データ用のGroongaのスキーマ設計方法

Groongaを開発している須藤です。

GroongaにはRDBMS(MySQLやMariaDBやPostgreSQLなど)と同じようにテーブルとカラムがあり、それらに構造化したデータを保存します。しかし、用途が違う(Groongaは検索がメインでRDBMSは検索だけでなくデータの管理も大事)のでスキーマの設計方法はRDBMSでの設計方法と違う部分があります。そのため、RDBMSのスキーマの設計に慣れている人でもGroongaのスキーマをうまく設計できないことがあります。

この記事では少し複雑なデータをGroongaで検索するためのスキーマの設計方法を説明します。

なお、ここで使っている設計を実装したGroongaコマンドはgroonga/groonga-schema-design-exampleにあります。実際に試してみると理解が深まるはずなのでぜひ試してみてください。

データ

まず、検索対象のデータを説明します。

検索対象は次の2つです。

  • 論文
  • 書籍

それぞれのデータがもつ情報は同じものもあれば違うものもあります。

たとえば、「タイトル」は「論文」にも「書籍」にもあります。

しかし、「雑誌」は「論文」だけにあり、「書籍」にはありません。(論文が収録されている雑誌の情報です。)

また、次のような親子関係があります。「論文」はいくつも階層になった親があります。書籍は複数の親を持ちます。少し複雑なデータですね!

  • 出版元

    • 雑誌
        • 論文
  • 出版社

    • 書籍
  • 親カテゴリー

    • 子カテゴリー
      • 書籍
  • シリーズ

    • 書籍(シリーズに属さない書籍もある)

検索方法

この少し複雑なデータに対して次のような検索をするためのスキーマを設計します。

  • 「論文」と「書籍」を横断全文検索
  • 複数カラムで横断全文検索
    • たとえば「タイトル」と「著者」で横断全文検索
  • 「論文」だけを全文検索
  • 「書籍」だけを全文検索
  • 検索結果を任意の親でドリルダウン
    • たとえば「出版元」でドリルダウン
  • 指定した親に属する子レコードを検索
    • たとえば「出版元」が発行している「雑誌」を検索

設計の概要

Groongaでは検索対象を1つのテーブルに集めることが重要です。すごく重要です。本当に重要です。

今回のケースでは「論文」と「書籍」が検索対象なので、それらを同じテーブルに格納します。今回の設計では2つ合わせて「文献」と扱うことにし、Literatureテーブルを作成します。

Literatureテーブルの定義は次の通りです。

table_create Literature TABLE_HASH_KEY ShortText

主キーに設定する値は「論文」と「書籍」全体で一意にする必要があることに注意してください。「論文」ではISSNとなにかを使って、「書籍」ではISBNを使うと一意にできる気がします。ここでは、どうにかして一意にできる前提で設計を進めます。

LiteratureTABLE_HASH_KEYを使っているのは、今回のケースでは主キーで完全一致検索できれば十分だからです。

参考:TABLE_*の特徴の違い

「論文」と「書籍」を同じテーブルに入れるため、区別するための情報も格納します。この設計ではtypeカラムを追加し、そこに"paper"(「論文」)または"book"(「書籍」)を格納することにします。

typeの型はShortTextでもよいのですが、検索効率および空間効率を考慮してTypes型にします。Typesはこの後にすぐ定義しますが、ただのテーブルです。カラムの型にテーブルを指定すると実際のデータ("paper""book")はTypesテーブルの主キーに入ります。カラムにはTypesテーブルの該当レコードのID(12とか)が入ります。各カラムに入るデータは単なる数値なので比較も高速(検索効率がよい)ですし、サイズも小さい(空間効率がよい)です。

column_create Literature type COLUMN_SCALAR Types

Typesは次のように定義したテーブルです。主キーには"paper"または"book"を設定します。主キーは完全一致だけできれば十分なのでTABLE_HASH_KEYにしています。

# "paper"または"book"
table_create Types TABLE_HASH_KEY ShortText

Literatureテーブルにレコードを追加したときにこのテーブルにも自動でレコードが追加されるので明示的に管理する必要はありません。typeカラムに"paper"を格納しようとすれば自動でTypesテーブルに主キーが"paper"のレコードが追加されます。すでにレコードがあればそのレコードを使います。つまり、typeカラムの型にShortTextを使ったときと同じように使えます。

型にテーブルを指定する方法はGroongaではよく使う方法です。用途はいろいろありますが、この使い方はRDBMSでいうenum型のようなものを実現するための使い方です。enum型のように値を制限することはできませんが。。。他の用途は後ででてきます。

Literatureに「論文」と「書籍」の情報をすべて格納します。中には「論文」にしかない情報あるいは「書籍」にしかない情報も存在します。存在しない情報は該当カラムに値を設定しません。

たとえば、「子カテゴリー」情報は「書籍」にしか存在しないので「論文」用のレコードを格納するときは「子カテゴリー」情報のカラムに値を設定しません。

GroongaにはNULLはないので、値を設定しなかったカラムの値はその型の初期値になっています。たとえば、ShortTextなら空文字列ですし、Int32なら0です。

今回の設計ではLiteratureテーブルには次のカラムを用意します。

  • type (Types): 種類(「論文」("paper")か「書籍」("book"))
  • title (ShortText): タイトル
  • authors (Authors): 著者(複数)
  • volume (Volumes): 号(「論文」のみ)
  • book_publisher (BookPublishers): 出版社(「書籍」のみ)
  • child_category (ChildCategories): 子カテゴリー(「書籍」のみ)
  • series (Series): シリーズ(「書籍」のみ)

titleauthorsは全文検索のためのカラムです。

検索項目を増やす場合は単にカラムを増やしてインデックスを追加するだけです。追加方法はauthorsを例にして後述します。全文検索用のスキーマ設計の方法もあわせて説明します。

以下のカラムはドリルダウンのためのカラムです。

  • volume
  • book_publisher
  • child_category
  • series

これらの情報で親子関係を表現します。親の親がある場合でもGroongaでは各レコードは直接の親だけを格納していれば十分です。各レコードに親の情報だけでなく、親の親の情報も格納する必要はありません。これは正規化した状態のままでよいということです。正規化した状態のままで扱えるため情報の管理が楽です。たとえば、「雑誌」の名前を変更する時は雑誌テーブルの該当レコードを変更するだけでよく、「雑誌」情報を持っているすべてのレコードを変更する必要はないということです。

ドリルダウン用のスキーマ設計は後述します。

以上が設計の概要です。ポイントは次の通りです。

  • 横断検索対象の情報はすべて1つのテーブルにまとめる
  • 対象の種類を区別する必要がある場合はカラムにその情報を入れて区別する
  • 検索条件に使いたい情報を増やす場合はテーブルにカラムを追加する
  • 特定のレコードにしかない情報(「論文」にしかない情報や「書籍」にしかない情報)でもカラムを追加してよい
    • 情報が存在しないレコードでは単にカラムに値を設定しない
  • ドリルダウン用の情報は正規化したままでよい

検索項目の追加

著者情報を例に検索項目を追加する方法を示します。

著者は複数存在するので次のようにCOLUMN_VECTORで定義します。

column_create Literature authors COLUMN_VECTOR Authors

型はAuthorsテーブルにしていますがShortTextにしてもよいです。テーブルを使っている理由はtypeカラムのときと同じで検索効率および空間効率がよいからです。著者でドリルダウンするなら(今回は説明しません)テーブルにするべきです。計算効率が全然違います。

今回の設計では著者名を主キーにします。

table_create Authors TABLE_HASH_KEY ShortText

同姓同名の著者を別人として扱いたい場合は著者IDを振ってnameカラムを追加します。今回の説明ではそこは本質ではないので単に著者名を主キーにしています。

著者名で完全一致検索する場合は次のようにすれば効率よく検索できます。

select \
  --table Literature \
  --query 'authors:@山田太郎'

著者名で全文検索する場合は追加のインデックスが必要です。

まず、Authors._keyで全文検索するためのインデックスが必要です。

table_create Terms TABLE_PAT_KEY ShortText \
  --default_tokenizer TokenNgram \
  --normalizer NormalizerNFKC100

column_create Terms authors_key \
  COLUMN_INDEX|WITH_POSITION Authors _key

Termsテーブルは他の全文検索用インデックスでも共有可能です。共有するとトークン(全文検索用にテキストを分割したもの)の情報を共有でき、DB全体の空間効率がよくなります。

TermsテーブルではTokenNgramNormalizerNFKC100を使っています。他にも指定できるものはありますが、これらがバランスがよいので、まずはこれから始めるのがよいです。必要なら後で調整するとよいです。

Terms.authors_keyは全文検索用のインデックスなのでWITH_POSITIONを指定しています。

これで、著者名で全文検索して該当著者を検索できるようになります。しかし、その著者から該当「論文」を見つけることはまだできません。追加で次のインデックスが必要です。

column_create Authors literature_authors \
  COLUMN_INDEX Literature authors

このインデックスはどの著者がどの「論文」の著者かを高速に検索するためのインデックスです。このインデックスも作ることで「著者名で全文検索して著者を見つけ、さらに、その著者がどの論文の著者かを検索する」を実現できます。

検索クエリーは次のようになります。完全一致検索のときとの違いはauthors:@._keyが加わってauthors._key:@となっているところです。

select \
  --table Literature \
  --query 'authors._key:@山田'

各インデックスカラムの役割を図示すると次の通りです。

ネストした検索

authorsは複数の著者が存在するためCOLUMN_VECTORを使っています。また、重複した情報が多くなるため型にテーブルを利用しました。そのため、少し複雑になっています。

titleのように単純な情報の場合は次のようにするだけで十分です。

column_create Literature title COLUMN_SCALAR ShortText
column_create Terms literature_title \
  COLUMN_INDEX|WITH_POSITION Literature title

titleauthorsを両方検索対象にするには次のようにします。

select \
  --table Literature \
  --match_columns 'title || authors._key' \
  --query 'キーワード'

「論文」(type"paper")だけを検索する場合は次のように--filterで条件を追加します。selectでは--query--filterで条件を指定できますが、--queryはユーザーからの入力をそのまま入れる用のオプションで--filterはシステムでより詳細な条件を指定する用のオプションです。

select \
  --table Literature \
  --match_columns 'title || authors._key' \
  --query 'キーワード' \
  --filter 'type == "paper"'

参考:select

1段のドリルダウンの実現

検索対象のデータには2段以上の親子関係のドリルダウンがありますが、まずは1段の親子関係のドリルダウンの実現方法について説明します。

例として次の親子関係のドリルダウンの実現方法について説明します。

    • 論文

効率的なドリルダウンを実現するためにテーブルを型にしたカラムを作成します。(enum型っぽい使い方とは別の型にテーブルを使う使い方。)

今回の設計では「号」用にVolumesテーブルを作成します。

table_create Volumes TABLE_HASH_KEY ShortText

「論文」はLiteratureテーブルなので、Literatureテーブルにvolumeカラムを作成します。型はVolumesテーブルです。

column_create Literature volume COLUMN_SCALAR Volumes

これでvolumeカラムで効率的にドリルダウンできます。次のようにすれば、「号」でドリルダウンし、その「号」には何件の「論文」があるかを検索できます。

select \
  --table Literature \
  --drilldowns[volumes].keys 'volume' \
  --drilldowns[volumes].output_columns '_key,_nsubrecs'

図示すると次の通りです。

1段のドリルダウン

次の親子関係も同様に実現できます。

  • 出版社
    • 書籍
  • シリーズ
    • 書籍(シリーズに属さない書籍もある)

2段以上のドリルダウンの実現

続いて2段以上の親子関係のドリルダウンの実現方法について説明します。

まずは、次の2段のケースについて説明します。

  • 雑誌
      • 論文

その後、次の3段のケースについて説明します。

  • 出版元
    • 雑誌
        • 論文

2段の場合もテーブルを型にしたカラムを作成するのは同じです。

今回の設計では「雑誌」用にMagazinesテーブルを作成します。

table_create Magazines TABLE_HASH_KEY ShortText

「号」が所属する「雑誌」を格納するカラムをVolumesテーブルに追加します。

column_create Volumes magazine COLUMN_SCALAR Magazines

これで「号」から「雑誌」をたどることができます。

「号」と「雑誌」でドリルダウンするには次のようにします。ポイントは、.tablevolumesを指定しているところと、calc_targetcalc_typesです。

select \
  --table Literature \
  --drilldowns[volumes].keys 'volume' \
  --drilldowns[volumes].output_columns '_key,_nsubrecs' \
  --drilldowns[magazines].table 'volumes' \
  --drilldowns[magazines].keys 'magazine' \
  --drilldowns[magazines].calc_target '_nsubrecs' \
  --drilldowns[magazines].calc_types 'SUM' \
  --drilldowns[magazines].output_columns '_key,_sum'

--drilldowns[${LABEL}]は高度なドリルダウンのためのパラメーターです。

参考:高度なドリルダウン関連のパラメーター

このselectでは以下の2つのドリルダウンを実行します。

  • --drilldowns[volumes]: 「号」でドリルダウン
  • --drilldowns[magazines]: 「雑誌」でドリルダウン

--drilldowns[magazines].tableで他のドリルダウンの結果を指定できます。指定するとドリルダウン結果をさらにドリルダウンできます。今回のように親子関係がある場合は子のドリルダウン結果から親のドリルダウン結果を計算します。

ただ、普通にドリルダウンすると、カウントした件数は「論文」の件数ではなく、「号」の件数になります。孫(「論文」)でドリルダウンしているのではなく、子(「号」)でドリルダウンしているからです。孫(「論文」)の件数をカウントするには子(「号」)でカウントした件数をさらにカウントする。その設定が次のパラメーターです。

  • --drilldowns[magazines].calc_target '_nsubrecs'
  • --drilldowns[magazines].calc_types 'SUM'

_nsubrecsには子(「号」)でカウントした孫(「論文」)の件数が入っています。それのSUM(総計)を計算するので孫の件数になります。出力する時は_nsubrecsではなく_sumで参照します。

--drilldowns[magazines].output_columns '_key,_sum'

図示すると次の通りです。

2段のドリルダウン

3段になった次のケースも同様です。

  • 出版元
    • 雑誌
        • 論文

まず、出版元を効率よくドリルダウンするためにPaperPublishersテーブルを作ります。

table_create PaperPublishers TABLE_HASH_KEY ShortText

Magazinesテーブル(「雑誌」)に出版元を格納するカラムを追加します。

column_create Magazines publisher COLUMN_SCALAR PaperPublishers

これで「雑誌」から「出版元」をたどることができます。

「号」と「雑誌」と「出版元」でドリルダウンするには次のようにします。ポイントは、「出版元」のドリルダウンのcalc_target_nsubrecsではなく_sumを使っているところです。「出版元」のドリルダウンで「論文」の件数をカウントするには「雑誌」のドリルダウンでカウント済みの「論文」の件数の総計を計算します。そのカウント済みの「論文」の件数が_nsubrecsではなく_sumにあるので_sumを使います。

select \
  --table Literature \
  --drilldowns[volumes].keys 'volume' \
  --drilldowns[volumes].output_columns '_key,_nsubrecs' \
  --drilldowns[magazines].table 'volumes' \
  --drilldowns[magazines].keys 'magazine' \
  --drilldowns[magazines].calc_target '_nsubrecs' \
  --drilldowns[magazines].calc_types 'SUM' \
  --drilldowns[magazines].output_columns '_key,_sum' \
  --drilldowns[paper_publishers].table 'magazines' \
  --drilldowns[paper_publishers].keys 'publisher' \
  --drilldowns[paper_publishers].calc_target '_sum' \
  --drilldowns[paper_publishers].calc_types 'SUM' \
  --drilldowns[paper_publishers].output_columns '_key,_sum'

図示すると次の通りです。

3段のドリルダウン

次のケースも同様に実現できる。

  • 親カテゴリ
    • 子カテゴリ
      • 書籍

子の一覧

親子階層の情報を使って子のレコードを検索する方法を説明します。

ここでは、対象の「出版元」内の「雑誌」の一覧を返すケースを例にして説明します。

まず、対象の「出版元」を絞り込む必要があります。ここでは「出版元」の名前(主キーに入っています)を全文検索して絞り込むとします。

全文検索用のインデックスのテーブルはAuthors._key用に作ったTermsテーブルを流用します。

column_create Terms paper_publishers_key \
  COLUMN_INDEX|WITH_POSITION PaperPublishers _key

これで「出版元」の名前で全文検索できます。しかし、authorsのときと同じで、「出版元」は絞り込めますが、絞り込んだ「出版元」を元に「雑誌」を絞り込むことはできません。「雑誌」も絞り込めるようにするには追加で次のインデックスが必要です。

column_create PaperPublishers magazines_publisher \
  COLUMN_INDEX Magazines publisher

このインデックスは「出版元」をキーにどの「雑誌」がその「出版元」を参照しているかを高速に検索するためのインデックスです。このインデックスがあることで、絞り込んだ「出版元」を元に「雑誌」を絞り込めます。

次のようなクエリーで「出版元」の名前で全文検索し、絞り込んだ「出版元」が発行している「雑誌」を出力できます。

select \
  --table Magazines \
  --match_columns 'publisher._key' \
  --query 'おもしろ雑誌' \
  --output_columns '_key, publisher._key'

他の親子関係のケースも同様に実現できます。

まとめ

Groongaで以下の機能を効率的に実現するためのスキーマ設計方法について説明しました。

  • 「論文」と「書籍」を横断全文検索
  • 複数カラムで横断全文検索
    • たとえば「タイトル」と「著者」で横断全文検索
  • 「論文」だけを全文検索
  • 「書籍」だけを全文検索
  • 検索結果を任意の親でドリルダウン
    • たとえば「出版元」でドリルダウン
  • 指定した親に属する子レコードを検索
    • たとえば「出版元」が発行している「雑誌」を検索

今回の設計を実装したGroongaコマンドはgroonga/groonga-schema-design-exampleにあります。実際に試してみると理解が深まるはずなのでぜひ試してみてください。

クリアコードではGroongaのスキーマ設計もサポートしています。Groongaをもっと活用したい方はお問い合わせください。

タグ: Groonga
2019-01-16

db tech showcase Tokyo 2018 - MySQL・PostgreSQLだけで作る高速あいまい全文検索システム #dbts2018

db tech showcase Toyo 2018で話すことを事前に宣伝をしておきたかったけど間に合わなかった須藤です。

関連リンク:

内容

去年は「MySQL・PostgreSQLだけで作る高速でリッチな全文検索システム」というタイトルで話しました。去年はMySQL(Mroonga)・PostgreSQL(PGroonga)で次のことを実現するための具体的なSQLを紹介しました。

  • 全文検索
  • キーワードハイライト
  • 周辺テキスト表示
  • 入力補完
  • 同義語展開
  • 関連文書の表示
  • 構造化データ(オフィス文書・HTML・PDFなど)対応

今年は「MySQL・PostgreSQLだけで作る高速あいまい全文検索システム」というタイトルで話しました。今年も話の流れは同じにしました。あることを実現する具体的なSQLを紹介するというスタイルです。今年はMySQL(Mroonga)・PostgreSQL(PGroonga)で次のことを実現するための具体的なSQLを紹介しました。

  • ヨミガナ検索
  • 同義語展開
  • 電話番号検索
  • ワイン名検索
  • fuzzy検索

今年は「あいまい検索」の実現方法にフォーカスした機能を選びました。同義語展開は去年も紹介したのですが、「あいまい検索」というテーマでは入っていたほうがよさそうだと思ったので入れました。「近傍検索」と「quorumマッチ」は入れられませんでした。

「あいまい検索」というテーマにしたのは今年はこのあたりの機能を強化したからです。「ヨミガナ検索」は今回の発表のために間に合わせました。

まとめ

去年の内容と組み合わせると全文検索まわりのかなりのことをMySQL・PostgreSQLで実現できます。ぜひ、Mroonga・PGroongaを使ってMySQL・PostgreSQLをさらに活用してください。

Mroonga・PGroongaのサポートが必要な方は問い合わせフォームからご相談ください。

タグ: Groonga
2018-09-20

Groongaで遅いクエリーを手軽に特定する方法

Groongaを使っていると、時々他の検索と比べて応答が遅い検索があることがあります。 そういった時は、ボトルネックを改善するために、どのクエリーの応答がどのくらい遅いのかを測定したくなります。

Groongaには、groonga-query-logという便利なツールがあります。 このgroonga-query-logには、クエリーログを解析して、どのクエリーの応答がどのくらい遅いのかを出力してくれるスクリプトが含まれています。 したがって、このツールを使えば、クエリーログを取得するだけでボトルネックの測定ができます。

ちなみに、groonga-query-logには、今回紹介する遅いクエリーを特定するスクリプトだけではなく、先日このブログで紹介されていた、クラッシュ時のログを解析するスクリプトクエリーログを手軽に再生するスクリプト、回帰テストを実行するスクリプト等、様々なスクリプトがありますので、興味が湧いたら他の機能についても使ってみて下さい。

遅いクエリーを特定するスクリプトの使い方は以下の通りです。

事前にRubyをインストールします。Rubyをインストールしたら以下のコマンドでgroonga-query-logをインストールします。

% gem install groonga-query-log

その後は、ボトルネックを測定するためにクエリーログを取得します。 クエリーログは以下のように--query-log-pathを指定してGroongaを起動することで取得できます。 例えば、サーバーモードでGroongaを起動して、クエリーログを取得する場合は、以下のように実行します。

% groonga -s --protocol http --query-log-path ~/benchmark/query.log ~/testdb/db

クエリーログを取得したら、取得したクエリーログを解析します。 クエリーログの解析は以下のコマンドで実行できます。

% groonga-query-log-analyze ~/benchmark/query.log

上記のコマンドを実行すると、標準出力に以下のような結果が出力されます。標準出力ではなく、ファイルに結果を出力したい場合は、--outputオプションで保存先を指定することができます。 情報が多いですが、1つずつ解説していきます。

Summary:
  Threshold:
    slow response     : 0.2
    slow operation    : 0.1
  # of responses      : 20
  # of slow responses : 1
  responses/sec       : 0.1351168407649807
  start time          : 2018-06-26T13:26:01.958965+09:00
  last time           : 2018-06-26T13:28:29.979003+09:00
  period(sec)         : 148.020038707
  slow response ratio : 5.000%
  total response time : 0.9940333010000002
  Slow Operations:

Slow Queries:
 1) [2018-06-26T13:26:42.318254+09:00-2018-06-26T13:26:42.853803+09:00 (0.53554963)](0): load --table Site
  name: <load>
  parameters:
    <table>: <Site>
  1) 0.53550895:       load(     9)

 2) [2018-06-26T13:27:20.700551+09:00-2018-06-26T13:27:20.838092+09:00 (0.13754151)](0): column_create --table Terms --name blog_title --flags COLUMN_INDEX|WITH_POSITION --type Site --source title
  name: <column_create>
  parameters:
    <table>: <Terms>
    <name>: <blog_title>
    <flags>: <COLUMN_INDEX|WITH_POSITION>
    <type>: <Site>
    <source>: <title>

 3) [2018-06-26T13:26:30.927046+09:00-2018-06-26T13:26:31.062374+09:00 (0.13532895)](0): column_create --table Site --name title --type ShortText
  name: <column_create>
  parameters:
    <table>: <Site>
    <name>: <title>
    <type>: <ShortText>

 4) [2018-06-26T13:26:13.510750+09:00-2018-06-26T13:26:13.599616+09:00 (0.08886603)](0): table_create --name Site --flags TABLE_HASH_KEY --key_type ShortText
  name: <table_create>
  parameters:
    <name>: <Site>
    <flags>: <TABLE_HASH_KEY>
    <key_type>: <ShortText>

 5) [2018-06-26T13:27:12.821842+09:00-2018-06-26T13:27:12.909721+09:00 (0.08787940)](0): table_create --name Terms --flags TABLE_PAT_KEY --key_type ShortText --default_tokenizer TokenBigram --normalizer NormalizerAuto
  name: <table_create>
  parameters:
    <name>: <Terms>
    <flags>: <TABLE_PAT_KEY>
    <key_type>: <ShortText>
    <default_tokenizer>: <TokenBigram>
    <normalizer>: <NormalizerAuto>

 6) [2018-06-26T13:28:10.403229+09:00-2018-06-26T13:28:10.407243+09:00 (0.00401451)](0): select --table Site --offset 7 --limit 3
  name: <select>
  parameters:
    <table>: <Site>
    <offset>: <7>
    <limit>: <3>
  1) 0.00010690:     select(     9)
  2) 0.00387348:     output(     2)

 7) [2018-06-26T13:26:56.509382+09:00-2018-06-26T13:26:56.509947+09:00 (0.00056562)](0): select --table Site --query _id:1
  name: <select>
  parameters:
    <table>: <Site>
    <query>: <_id:1>
  1) 0.00037286:     filter(     1) query: _id:1
  2) 0.00001580:     select(     1)
  3) 0.00010979:     output(     1)

 8) [2018-06-26T13:27:45.307960+09:00-2018-06-26T13:27:45.308409+09:00 (0.00044912)](0): select --table Site --output_columns _key,title,_score --query title:@test
  name: <select>
  parameters:
    <table>: <Site>
    <output_columns>: <_key,title,_score>
    <query>: <title:@test>
  1) 0.00029620:     filter(     9) query: title:@test
  2) 0.00001499:     select(     9)
  3) 0.00008281:     output(     9) _key,title,_score

 9) [2018-06-26T13:28:24.234937+09:00-2018-06-26T13:28:24.235383+09:00 (0.00044695)](0): select --table Site --query title:@test --output_columns _id,_score,title --sort_keys -_score
  name: <select>
  parameters:
    <table>: <Site>
    <query>: <title:@test>
    <output_columns>: <_id,_score,title>
    <sort_keys>: <-_score>
  1) 0.00027925:     filter(     9) query: title:@test
  2) 0.00001296:     select(     9)
  3) 0.00004034:       sort(     9)
  4) 0.00005845:     output(     9) _id,_score,title

10) [2018-06-26T13:28:29.978590+09:00-2018-06-26T13:28:29.979003+09:00 (0.00041371)](0): select --table Site --query title:@test --output_columns _id,_score,title --sort_keys -_score,_id
  name: <select>
  parameters:
    <table>: <Site>
    <query>: <title:@test>
    <output_columns>: <_id,_score,title>
    <sort_keys>: <-_score,_id>
  1) 0.00023013:     filter(     9) query: title:@test
  2) 0.00000878:     select(     9)
  3) 0.00004086:       sort(     9)
  4) 0.00005566:     output(     9) _id,_score,title

Summary

まず、Summaryの内容について解説します。 Summaryには、以下の項目があります。

  • Threshold:実行に何秒かかったら、遅いクエリー、遅いオペレーションとするかのしきい値を表示しています。
    • slow responseに表示されている値より時間のかかったクエリーを遅いクエリーと判定します。
      • 単位は秒で、デフォルト値は、0.2秒ですが、groonga-query-log-analyze実行時に--slow-response-thresholdオプションを使って、しきい値を変更できます。
    • slow operationに表示されている値より時間のかかったオペレーションを、遅いオペレーションと判定します。
      • 単位は秒で、デフォルト値は、0.1秒ですが、groonga-query-log-analyze実行時に--slow-operation-thresholdオプションを使って、しきい値を変更できます。
    • 例えば、slow responseのしきい値を1.0秒、slow operationのしきい値を1.0秒にしたい場合は、以下のようにgroonga-query-log-analyzeを実行します。
% groonga-query-log-analyze --slow-response-threshold=1.0 --slow-operation-threshold=1.0 query.log
  • of responses:解析に使用したクエリーログに含まれる全てのクエリーの数を表します。
    • 例えば、of responsesが10だったとすると、query.logというクエリーログには、全部で10個のクエリーが記録されている事になります。
  • of slow responsesslow responseに設定したしきい値を超えたクエリーがいくつあったかを表します。
    • 例えば、of slow responsesが2だったとすると、slow responseに指定したしきい値を超えたクエリーが2つあった事になります。
  • responses/sec:平均応答時間を表します。解析したクエリーが平均してどのくらいの応答時間なのかを表示します。単位は秒です。
  • start timelast time:クエリーの実行開始時間と終了時間を表します。
  • period(sec):クエリーを流していた時間を表します。start timeend timeの差を表します。
  • slow response ratio:全体の中で遅いクエリーが占める割合を表します。例えば、slow response ratioの値が5.000%だった場合は、実行したクエリーのうち5%が遅いクエリーということになります。
  • total response time:クエリーの総実行時間を表します。単位は秒です。例えば、total response timeが0.9940333010000002だった場合は、全てのクエリーを実行するのに、約0.99秒かかったことになります。

Slow Operations

Slow Operationsは、具体的に遅いオペレーションを表示します。 例えば以下のような表示になります。

  Slow Operations:
    [56.021284]( 3.77%) [168](70.59%)    filter: title == "test"

この例の場合は、filter条件のtitle == "test"が遅いオペレーションと出ています。

  • 一番左の[56.021284]は、このオペレーションを実行した総実行時間を表します。単位は秒です。したがって、この場合は、filter == "test"というオペレーションを全部で約56秒実行したことになります。
  • 左から二番目の( 3.77%)は、他のオペレーションも含めた全実行時間のうちこのオペレーションが占める割合を表しています。したがって、この例の場合は、title == "test"というオペレーションが、全体のうち3.77%を占める事を表しています。
  • 左から3番目の[168]はこのオペレーションを実行した回数を表しています。この例の場合は、[168]となっているので、title == "test"というオペレーションが168回実行されています。
  • 左から4番目の(70.59%)は、他のオペレーションも含めた全実行回数のうちこのオペレーションが占める割合を表しています。この例の場合は、(70.59%)となっているので、全体のオペレーションのうち約70%は、title == testを実行していることになります。

Slow Queries

Slow Queriesは、具体的に遅いクエリーを遅い順に表示します。表示件数は、--n-entriesまたは-nオプションで変更でき、デフォルトでは10件表示されます。 例えば、5件表示させたい場合は、以下のように実行します。

% groonga-query-log-analyze --n-entries=5 ~/benchmark/query.log

or

% groonga-query-log-analyze -n 5 ~/benchmark/query.log

クエリーに複数の条件が含まれている場合は、それぞれの条件にどのくらい時間がかかったかも表示されます。 例えば以下のような表示の場合は、filterselectoutputの実行にそれぞれ0.00037286秒0.00001580秒0.00010979秒かかったことになります。

クエリー全体の実行時間は、最初の行に出力されます。以下の表示の場合は、7) [2018-06-26T13:26:56.509382+09:00-2018-06-26T13:26:56.509947+09:00 (0.00056562)](0): select --table Site --query _id:1(0.00056562)の部分がクエリー全体の実行時間を表しています。

 7) [2018-06-26T13:26:56.509382+09:00-2018-06-26T13:26:56.509947+09:00 (0.00056562)](0): select --table Site --query _id:1
  name: <select>
  parameters:
    <table>: <Site>
    <query>: <_id:1>
  1) 0.00037286:     filter(     1) query: _id:1
  2) 0.00001580:     select(     1)
  3) 0.00010979:     output(     1)

まとめ

groonga-query-log-analyzeを使うことで、このようにして遅いクエリーや、オペレーションを特定することができます。 どのくらい遅いのかの他に、全体に占める割合も出力することができ、また、どの条件に時間がかかっているかも出力できるので、チューニングのポイントを探すのに役に立ちます。

例えば、とても遅いクエリーだが、全体に占める割合が少ないクエリーをチューニングするよりも、そこそこ遅いクエリーで全体に占める割合が多いクエリーをチューニングしたほうが、性能向上に寄与します。 どのクエリー、オペレーションが遅いかだけの情報だと、上記のような判断はできませんが、groonga-query-log-analyzeを使った解析なら、上記のような判断もでき、より効果的なチューニングが実施できます。

タグ: Groonga
2018-06-26

Groongaのクエリーログを手軽に再生する方法

Groongaを使っていて問題が起きた時に、問題を再現させるために、問題が起きた時に実行していたクエリーを手元の環境でも実行したくなります。 実行するクエリーが少ない場合(1つか2つくらい)であれば、手作業で実行してもそんなに苦ではありませんが、実行するクエリーが大量にある場合は、手作業では限界があります。

Groongaには、groonga-query-log という便利なツールがあります。 この groonga-query-log には、クエリーログを手軽に再生するスクリプトが含まれています。 このスクリプトを使うことで、クエリーログに記載されているクエリーをログに記録されている順番で再生することができます。つまり、クエリーログさえあれば、簡単にログに記録されているクエリーを実行できます。

Groongaのクエリーログは、以下のようにGroongaを起動する際に--query-log-pathを指定することで、指定されたパスにクエリーログを作成できます。 (以下の例ですと、ホームディレクトリの直下にgroonga.query.logという名前でクエリーログができます。)

% groonga --query-log-path ~/groonga.query.log ~/db/db

ちなみに、groonga-query-log には、今回紹介するクエリーログを手軽に再生するスクリプトだけではなく、先日このブログで紹介されていた、クラッシュ時のログを解析するスクリプトや スロークエリーを特定するスクリプト、回帰テストを実行するスクリプト等、様々なスクリプトがありますので、興味が湧いたら他の機能についても使ってみて下さい。

クエリーログを再生するスクリプトの使い方は以下の通りです。

事前にRubyをインストールします。Rubyをインストールしたら以下のコマンドで groonga-query-log をインストールします。

% gem install groonga-query-log

次は、再生するクエリーを実行するためにデータベースを準備します。

ログを取得したマシンのデータベースが使える場合は、そちらを使うと新たにデータベースを準備しなくてすみますが、問題が起きたときには問題が発生したデータベースは使えないこともあるので、問題が発生したデータベースのダンプ等から、新しく手元の環境でデータベースを生成するほうが多いかもしれません。

今回の例では、以下のコマンドで、データベースのダンプから、新しくデータベースを作成します。

% groonga --file dump.grn -n ~/testdb/db 

次は再生するクエリーを実行するGroongaを起動します。Groongaはサーバーモードで起動します。プロトコルは、gqtp, httpのどちらでも問題ありません。 例えば、httpプロトコルを使用する場合は、以下のように起動します。

% groonga -s --protocol http ~/testdb/db

Groongaをサーバーモードで起動したら、準備完了です。 以下のように、 groonga-query-log-replay コマンドを実行して、クエリーログを再生できます。

% groonga-query-log-replay  --n-clients=1 --output-responses=./response.log replay-query.log

--n-clients はクエリーを実行するクライアント数を設定できます。 --output-responces は実行したクエリーの結果を保存するファイルを指定します。最後に、再生するクエリーログ(上の例では、replay-query.log)を指定して実行します。

これで、指定したクエリーログに記録されているクエリーが実行され、実行結果が、--output-responsesで指定したファイルに記録されます。

デフォルトでは、localhostの10041ポートに接続しますが、--host オプションと --port オプションを使うことで、それぞれ変更することができます。

タグ: Groonga
2018-06-19

Groongaクラッシュ時のログの解析方法

Groonga周辺を便利にするツールもいろいろと作っている須藤です。

Groongaはクラッシュセーフではないので、クラッシュするタイミングによってはデータベースが壊れてしまうことがあります。データベースが壊れていそうかどうかはログを解析することで判断できます。この記事ではクラッシュ時のログを解析するためのスクリプトを紹介します。

インストール

クラッシュ時のログを解析するスクリプトはgroonga-query-log gemの中に入っています。このスクリプトを使うと以下のことがわかります。

  • いつクラッシュしたか
  • クラッシュしたときに実行していたクエリーはなにか
  • flushの変更コマンド(loadtable_createなど)(これらがあるとデータベースが壊れている可能性が高い)
  • 解析対象のログ中にある重要度が高いメッセージ
  • (クラッシュ時ではなく)正常終了時にメモリーリークしていたか

事前にRubyをインストールします。Rubyをインストールしたら以下のコマンドでインストールできます。

% gem install groonga-query-log

使い方

インストールするとgroonga-query-log-check-crashというコマンドが使えるようになります。これでクラッシュ時のログを解析すると前述のこと(いつクラッシュしたかなど)がわかります。

使う時は次のように通常のログ(--log-path ...を指定すると出力されるログ)とクエリーログ(--query-log-path ...を指定すると出力されるログ)を「すべて」指定します。結果が多くなることがあるので、標準出力をリダイレクトして結果をファイルに保存しておくと便利です。

% groonga-query-log-check-crash groonga.log* groonga-query-log* > analyze.log

指定する順番は気にしなくてよいです。ファイル名に含まれているタイムスタンプ情報を元にコマンド内部で自動で並び替えるからです。なお、タイムスタンプ情報のフォーマットはstrftime(3)で言うと%Y-%m-%d-%H-%M-%S-%6N%6Nstrftime(3)にはないけど、6桁のミリ秒のつもり)です。このフォーマットは--log-rotate-threshold-size--query-log-rotate-threshold-sizeを使ったときに使われているフォーマットなので、これらのオプションを使ってログローテーションしている場合はとくに気にする必要はありません。

(違うタイムスタンプ情報のフォーマットもサポートして欲しい場合は https://github.com/groonga/groonga-query-log/issues で相談してください。)

また、gzip・ZIPで圧縮されていても構いません。自動で伸張して解析します。

改めてまとめると、持っている通常のログ・クエリーログをすべて渡してください、ということです。

結果

コマンドを実行するとログを解析し、クラッシュを検出すると随時解析結果を出力します。ログが大きいほど時間がかかります。ログはストリームで解析しているので大量のログを解析してもメモリー使用量が比例して大きくなることはありません。

出力結果は特に決まったフォーマットになっているわけではなく(たとえばMarkdownでマークアップされているわけではない)、解析結果が1つずつ表示されるだけです。フォーマットは今後も変わっていきます。(改良していきます。)そのため、ここで現時点のフォーマットの説明はしません。

以下の情報がわかるような出力になっています。

  • いつクラッシュしたか
  • クラッシュしたときに実行していたクエリーはなにか
  • flushの変更コマンド(loadtable_createなど)(これらがあるとデータベースが壊れている可能性が高い)
  • 解析対象のログ中にある重要度が高いメッセージ
  • (クラッシュ時ではなく)正常終了時にメモリーリークしていたか

また、より詳しく知りたい場合はどのログファイルを確認すればよいかもわかるようになっています。

まとめ

Groongaがクラッシュしたときのログ解析を支援するツールがあることを紹介しました。Groongaを運用している人はぜひ活用してください。

ログの解析ロジックは私が手動で解析するときのロジックです。そのため、未対応なパターンも十分にありえます。未対応のパターンがあった場合は https://github.com/groonga/groonga-query-log/issues で教えてください。

タグ: Groonga
2018-06-11

【徳丸浩と学ぶビジネスセミナー】WordPressのセキュリティと全文検索について学ぶ!【Mroonga対応】- Mroongaの高速全文検索機能でWordPress内のコンテンツを有効活用!

1ヶ月半前の話をようやくまとめている須藤です。

2月9日(年に一度の肉の日!)に【徳丸浩と学ぶビジネスセミナー】WordPressのセキュリティと全文検索について学ぶ!【Mroonga対応】でWordPressでMroongaをどう活用できるかを紹介してきました。

関連リンク:

内容

対象は全文検索をあまり知らないWordPressユーザーです。そのため、全文検索とは?からまとめています。

単に全文検索とは?だけでなく、全文検索をどうやってWordPressで活用するかについてもまとめています。ザックリ言うとサイト内回遊率向上ですが、どう活用すれば向上につながるかをいろいろまとめています。

まとめ

WordPressで全文検索を活用することについて紹介しました。ぜひ、この資料を参考にWordPressで全文検索を活用してみてください。

なんと、この続編として、2018年3月29日にWordPressのMroongaプラグインを改良しよう!というイベントがあります。

「WordPressのMroongaプラグイン」というのはWordPressからMroongaを使うための「WordPressのプラグイン」です。WordPressのプラグインページにもMroongaとして登録してあります。

Mroonga」というのは(WordPressではなく)MySQL・MariaDBのプラグインで、MySQL・MariaDBに高速な全文検索機能を追加します。

つまり、WordPressのMroongaプラグインとはWordPressに高速な全文検索機能を追加するもの、ということです。

WordPressの全文検索で困ったことがあり、有償サポートが必要な場合は、お問い合わせください。

タグ: Groonga
2018-03-29

第一回 JPMUG DB勉強会 - MariaDBとMroongaで作る全言語対応超高速全文検索システム #JPMUG

1ヶ月半前の話をようやくまとめている須藤です。

1月30日に第一回 JPMUG DB勉強会でMroongaの使い方を紹介してきました。

関連リンク:

内容

対象は全文検索システムを作ったことがないMariaDBユーザーです。そのため、「全文検索システム」にはどんな機能が必要なのか、それを実現するには具体的にどんなSQLを書くことになるのか、ということをまとめた内容になっています。

また、事前に「日本語以外の言語にも対応するにはどうすればいいの?」という質問をもらっていたので、MariaDB・MroongaでのCOLLATION関連の話やUnicodeの話も少し含めています。

まとめ

第一回 JPMUG DB勉強会でMariaDBとMroongaを使って全文検索システムを実現する方法を説明しました。ぜひ、この資料を参考に全文検索システムを実現してみてください。

困ったことがあり、有償サポートが必要な場合は、お問い合わせください。

タグ: Groonga
2018-03-13

【KUSANAGI × Mroonga ハンズオン】KUSANAGIでWordPressのインストールからMroongaプラグインの実装まで学べる!【中級者向け】

WordPress+Mroongaプラグインを使った全文検索開発のハンズオンが開催されます。2018年2月より、KUSANAGIという超高速WordPress仮想マシンでMroongaプラグインがサポートされることになりました。KUSANAGIはAWSやAzureなどの多くのパブリッククラウドサービスに対応しており、無償で利用可能な仮想マシンです。Mroongaサポート開始を記念して、実際どのように(予定)開発すればMroongaプラグインを活用して全文検索機能を実装できるのか体験できるイベントが開催されます。

KUSANAGI + WordPress上で、Mroongaプラグインに「オートコンプリート」「関連記事検索」機能を追加し、動く状態まで皆さんとご一緒に開発します。2チームに分けて開発いたしますので、事前にどちらの機能を作りたいかをご検討のうえ来場ください。KUSANAGIやMroongaの経験がない方でも、スタッフと一緒に開発作業を行い、プラグイン機能を動く状態にできます。

詳しくはイベントページを参照してください。

イベントの概要は次の通りです。

  • 開催場所 : 日本マイクロソフト株式会社 品川本社 セミナールーム
  • 開催日程 : 3月29日(木) 13時30分~17時00分(開場13時00分)
  • 参加料金 : 無料
  • 共催 : 株式会社クリアコード、日本マイクロソフト株式会社、プライム・ストラテジー株式会社
  • 定員 : 20名
  • 協賛 : 一般社団法人PHP技術者認定機構
  • 当日は名刺を2枚お持ちください。

予定している実施内容は次の通りです。

  • プライム・ストラテジー ハンズオン(90分)
  • クリアコード代表 須藤 開発実践(90分)
タグ: Groonga
2018-03-12

PGConf.ASIA 2017 - PGroonga 2 – Make PostgreSQL rich full text search system backend! #pgconfasia

PGConf.ASIA 2017RUMの存在を知った須藤です。RUMはGINと違って完全転置索引にできるので全文検索用途によさそう。(Groongaは元から完全転置索引にできるのでずっと前からよかった。)

関連リンク:

内容

PostgreSQL Conference Japan 2017での内容にPGroonga 1.0.0からPGroonga 2へのアップグレード関連の話を盛り込んだ内容になっています。なお、PostgreSQL Conference Japan 2017での内容は次の昨日の実現方法を紹介でした。

  • 高速全文検索
  • それっぽい順でのソート
  • 検索結果表示画面で検索キーワードをハイライト
  • 検索結果表示画面で検索キーワード周辺テキストだけを表示
  • オートコンプリート(検索キーワードを少し入力したら補完する機能)
  • 類似文書検索(ブログの検索システムなら関連エントリーの表示に使える機能)
  • 同義語展開(表記揺れの吸収とかに使える機能)

これらの機能の実現方法はPostgreSQL Conference Japan 2017用の資料の方が参考にしやすいかもしれません。PGConf.ASIA 2017用の資料は英語(と日本語訳)でまとめていますが、PostgreSQL Conference Japan 2017用の資料は日本語でまとめているからです。

PGroongaを使うと全文検索システムのバックエンドとしてもPostgreSQLを活用できます。ぜひ活用してください!

まとめ

PGConf.ASIA 2017で、先日リリースしたPGroonga 2を紹介しました。PGroongaも使ってPostgreSQLをどんどん活用してください!もし、PGroonga関連でなにか相談したいことがある場合はお問い合わせください。

タグ: Groonga
2017-12-07

PostgreSQL Conference Japan 2017 - PGroonga 2 - PostgreSQLでの全文検索の決定版 #pgcon17j

PostgreSQL Conference Japan 2017の前にPGroonga 2のリリースアナウンスを出せた須藤です。間に合ってよかった。

PostgreSQL Conference Japan 2017でPGroonga 2を紹介しました。PGroongaを使ったことがない人向けの内容です。実際、聞いてくれた人たちはほとんど使ったことがない人ばかりでした。

関連リンク:

内容

PostgreSQLをバックエンドに「今っぽい」全文検索システムを作るためのPGroongaの使い方を紹介するという内容になっています。具体的には次の機能を実現する方法を紹介しています。

  • 高速全文検索
  • それっぽい順でのソート
  • 検索結果表示画面で検索キーワードをハイライト
  • 検索結果表示画面で検索キーワード周辺テキストだけを表示
  • オートコンプリート(検索キーワードを少し入力したら補完する機能)
  • 類似文書検索(ブログの検索システムなら関連エントリーの表示に使える機能)
  • 同義語展開(表記揺れの吸収とかに使える機能)

また、次のステップとして次の機能の実現方法にも触れています。

  • PDF・オフィス文書を検索対象にする方法
    • ファイルサーバーの全文検索システムで使える
  • ロジカルレプリケーション
    • 参照のスケールアウトで使える

PGroongaを使うとPostgreSQLをもっと活用できます。ぜひ活用してください!

おまけ

今回のスライド公開のタイミングでスライド公開サイトRabbit Slide Showにスライド中のリンクをクリックできる機能を追加しました。

たとえば、今回のスライドの最後のページにはお問い合わせ先のリンクがあるのですが、このリンクをクリックするとリンクを辿れるようになっています。

これはPopplerというPDFレンダリングライブラリー(フリーソフトウェア)とHTMLの<map>要素を組み合わせて実現しています。

まとめ

PostgreSQL Conference Japan 2017で、先日リリースしたPGroonga 2を紹介しました。PGroongaも使ってPostgreSQLをどんどん活用してください!もし、PGroonga関連でなにか相談したいことがある場合はお問い合わせください。

タグ: Groonga
2017-11-07

タグ:
年・日ごとに見る
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|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|