ククログ

株式会社クリアコード > ククログ > GroongaでApache Arrowフォーマットを使ってみるよ!

GroongaでApache Arrowフォーマットを使ってみるよ!

Apache Arrow東京ミートアップ2025に参加予定の阿部です。

GroongaはApache Arrowフォーマットでのデータロードと検索結果の取得に対応しています。 今回は実際にそれを使ってみる企画です。

Apache Arrowフォーマットのデータを作るところからやってみます。 次の流れで進めていきます。

  1. Apache Arrowフォーマットのデータを作る
  2. GroongaにApache Arrowフォーマットでデータをロードする
  3. Groongaで検索してApache Arrowフォーマットでデータを得る

はじめに

GroongaでApache Arrowフォーマットを使うには、HTTPインターフェイスを使わないといけない です。 なので、本記事ではGroongaに対する操作はHTTPでリクエストする方法を使っています。

参考:

また、もろもろの処理をJavaScriptで実装しました。 JavaScriptが好き、以外に選択した理由はないので、他の言語の実装例も見たい方はお問い合わせよりご連絡ください。

とりあえず使ってみることが目的の企画なので、各関数で何をやっているかなどの詳細は割愛しています。 要所要所に関連する公式ドキュメントへのリンクを記載しましたので、詳細はそちらでご確認ください。

1. Apache Arrowフォーマットのデータを作る

公式ドキュメント: Apache Arrow in JS にある例を使わせてもらいます。 Groonga用にちょっと変えてます。

const LENGTH = 2000;

// Apache Arrowは列指向なので列ごとにデータを作ります。
// (コードからはどのようなデータなのかわかりにくいので、
// 後述の"実行結果例"もご確認ください。)
const keys = Array.from(
  { length: LENGTH },
  (_, i) => i);

const rainAmounts = Float32Array.from(
  { length: LENGTH },
  () => Number((Math.random() * 20).toFixed(1)));

const rainDates = Array.from(
  { length: LENGTH },
  (_, i) => new Date(Date.now() - 1000 * 60 * 60 * 24 * i));

const rainfall = tableFromArrays({
  _key: keys,
  precipitation: rainAmounts,
  date: rainDates
});

console.table([...rainfall]);

// このbufferがGroongaに送るデータです。
// GroongaへはApache Arrow IPC Streaming Format で送信する必要があります。
// https://groonga.org/ja/docs/reference/commands/load.html#values
const buffer = tableToIPC(rainfall, 'stream');
console.log(buffer);

実行結果例:

┌─────────┬──────┬─────────────────────┬───────────────┐
│ (index) │ _key │ precipitation       │ date          │
├─────────┼──────┼─────────────────────┼───────────────┤
│ 0       │ 0    │ 17.899999618530273  │ 1743489785919 │
│ 1       │ 1    │ 18.600000381469727  │ 1743403385919 │
│ 2       │ 2    │ 19.200000762939453  │ 1743316985919 │
│ 3       │ 3    │ 17.200000762939453  │ 1743230585919 │
│ 4       │ 4    │ 17.700000762939453  │ 1743144185919 │
│ 5       │ 5    │ 12.5                │ 1743057785919 │
│ 6       │ 6    │ 5.199999809265137   │ 1742971385919 │
│ 7       │ 7    │ 18                  │ 1742884985919 │
│ 8       │ 8    │ 17.600000381469727  │ 1742798585919 │
│ 9       │ 9    │ 7.800000190734863   │ 1742712185919 │
│ 10      │ 10   │ 3.5999999046325684  │ 1742625785919 │
(...長いので割愛)
│ 1996    │ 1996 │ 14.899999618530273  │ 1571035385919 │
│ 1997    │ 1997 │ 1.899999976158142   │ 1570948985919 │
│ 1998    │ 1998 │ 17.100000381469727  │ 1570862585919 │
│ 1999    │ 1999 │ 19                  │ 1570776185919 │
└─────────┴──────┴─────────────────────┴───────────────┘
Uint8Array(40512) [
  255, 255, 255, 255,  0, 1,  0,  0, 16, 0, 0, 0,
    0,   0,  10,   0, 16, 0, 14,  0,  7, 0, 8, 0,
   10,   0,   0,   0,  0, 0,  0,  1, 16, 0, 0, 0,
    0,   0,   4,   0,  8, 0,  8,  0,  0, 0, 4, 0,
    8,   0,   0,   0,  4, 0,  0,  0,  3, 0, 0, 0,
  144,   0,   0,   0, 72, 0,  0,  0,  4, 0, 0, 0,
  140, 255, 255, 255, 20, 0,  0,  0,  0, 0, 0, 1,
   24,   0,   0,   0,  0, 0,  0, 10, 20, 0, 0, 0,
    4,   0,   0,   0,
  ... 40412 more items
]

2. GroongaにApache Arrowフォーマットでデータをロードする

ロードする前の準備

ロード先のテーブルがないとロードできないので、「1. Apache Arrowフォーマットのデータを作る」で作ったデータに合わせてテーブルとカラムを作ります。

// ロード用のテーブルとカラムを作ります。
response = await fetch('http://localhost:10041/d/table_create?name=Rainfall&key_type=UInt32');
console.log(await response.json());
response = await fetch('http://localhost:10041/d/column_create?table=Rainfall&name=precipitation&type=Float');
console.log(await response.json());
response = await fetch('http://localhost:10041/d/column_create?table=Rainfall&name=date&type=UInt64');
console.log(await response.json());

この記事の本題ではないので詳細は割愛します。

参考:

ロードする

response = await fetch(
  // input_type=apache-arrow を指定するとApache Arrowフォーマットで
  // ロードできます。
  'http://localhost:10041/d/load?table=Rainfall&input_type=apache-arrow',
  {
    method: 'POST',
    headers: {
      // Apache Arrowフォーマットを使う場合は次のContent-Typeを指定します。
      'Content-Type': 'application/x-apache-arrow-streaming'
    },
    // bodyに作ったデータを指定します。
    body: buffer
  }
);
console.log(await response.json());

実行結果例:

[ [ 0, 1743489786.001868, 0.001583337783813477 ], 2000 ]

3. Groongaで検索してApache Arrowフォーマットでデータを得る

// output_type=apache-arrow を指定するとApache Arrowフォーマットで
// 結果が得られます。
response = await fetch('http://localhost:10041/d/select?table=Rainfall&output_type=apache-arrow');
// レスポンスにはHEADERと呼ばれるメタデータのテーブルと、
// BODYと呼ばれる結果のレコードのテーブルが含まれます。
// https://groonga.org/ja/docs/reference/command/output_format.html
for await (const reader of RecordBatchReader.readAll(await response.bytes())) {
  const table = tableFromIPC(reader);
  if (reader.schema.metadata.get('GROONGA:data_type') === 'metadata') {
    console.log('HEADER');
    console.log(table.toArray()[0]);
  } else {
    // Groongaのデフォルトでは10件のみ取得。
    console.log('BODY');
    console.table(table.toArray());
  }
}

実行結果例:

HEADER
{"return_code": 0, "start_time": 1743489786004.923, "elapsed_time": 0.00026607513427734375, "error_message": null, "error_file": null, "error_line": null, "error_function": null, "error_input_file": null, "error_input_line": null, "error_input_command": null}
BODY
┌─────────┬─────┬──────┬───────────────────┬────────────────────┐
│ (index) │ _id │ _key │ date              │ precipitation      │
├─────────┼─────┼──────┼───────────────────┼────────────────────┤
│ 0       │ 1   │ 0    │ 1743489785919000n │ 17.899999618530273 │
│ 1       │ 2   │ 1    │ 1743403385919000n │ 18.600000381469727 │
│ 2       │ 3   │ 2    │ 1743316985919000n │ 19.200000762939453 │
│ 3       │ 4   │ 3    │ 1743230585919000n │ 17.200000762939453 │
│ 4       │ 5   │ 4    │ 1743144185919000n │ 17.700000762939453 │
│ 5       │ 6   │ 5    │ 1743057785919000n │ 12.5               │
│ 6       │ 7   │ 6    │ 1742971385919000n │ 5.199999809265137  │
│ 7       │ 8   │ 7    │ 1742884985919000n │ 18                 │
│ 8       │ 9   │ 8    │ 1742798585919000n │ 17.600000381469727 │
│ 9       │ 10  │ 9    │ 1742712185919000n │ 7.800000190734863  │
└─────────┴─────┴──────┴───────────────────┴────────────────────┘

まとめと注意

GroongaにてApache Arrowフォーマットでのデータロードと検索結果の取得を実際に使ってみました。

今回は使ってみる企画だったのでApache Arrowフォーマットのデータを作るところからやりましたが、Groongaへデータを投入することだけを目的にApache Arrowフォーマットのデータを作るのはおすすめしません。

Apache Arrowフォーマットへの変換コストがそれなりにあるためです。 この機能はすでに手元にApache Arrowのデータがあり、それをGroongaへ投入して全文検索を行いたい、といったケースで役立ちます。

Apache Arrowといえば分析データ! Groongaには集計機能もあるので、全文検索と集計で分析データの前処理を行うことに活用できるはずです! Groongaのそのような活用方法にご興味がありましたら、お問い合わせよりご連絡ください。

補足: 説明に使ったソースコードなど

こちらに置きました。 https://github.com/abetomo/apache-arrow-format-in-groonga-example