コースApache Doris Hackathon自分のペースで進めるタスク · いつでも開始 · 登録不要

トラック A · タスク A1

ハイブリッド検索 アプリ

キーワード、意味、フィルターを 1 つの SQL クエリで扱います。Apache Doris ドキュメントを検索する検索ボックスを作りましょう。

  • 60~120 分
  • 初心者向け
  • 言語自由 · CLI または Web
  • AI ツールの利用歓迎

困ったときは? 質問はこちら: #dev 参加先: Apache Doris Slack。メンテナーやほかの参加者がサポートします。

1

環境を準備

何を作るか、Doris ならどう簡単になるかを学び、クラスターを起動します。

作成するもの

単語と意味の両方を理解する検索ボックスを作ります。

BM25 で順位付けしたキーワードの完全一致、ベクトルインデックスによる意味的な近傍、構造化フィルターを、1 つの Doris テーブルと 1 つの SQL 文で実現します。Elasticsearch や別のベクトルデータベースは不要です。

小さな検索アプリを作ります(CLI、ノートブック、Web ページのいずれでも構いません)。対象は Apache Doris ドキュメントの 299 セクションです。ユーザーは、たとえば次のような質問を入力し、 "how do I store messy JSON?"カテゴリを選び、次の 3 つのモードを切り替えます:

  • キーワードBM25 で順位付けした全文一致。
  • ベクトル埋め込みの類似度による最近傍。
  • ハイブリッドキーワードによる事前フィルターとベクトル順位付け(追加の挑戦として Reciprocal Rank Fusion)。

各結果には、ページ、セクション、スコア(キーワード)または距離(ベクトル・ハイブリッド)、ドキュメントへのリンクを表示します。「SQL を表示」を切り替えると、Doris が実行したクエリを確認できます。

概略図作成するもの · ワイヤーフレーム(スクリーンショットではありません)

概略図はコードパネルの上部にあります

Doris で取り組む利点

  • 1 つのテーブルに 2 つのインデックス。 テキストの転置インデックスとベクトルの HNSW ANN インデックスを同じテーブルに保持します。プランナーは転置インデックスで事前フィルターし、残った候補を ANN インデックスで順位付けします。

  • SQL で検索エンジンの順位付け。 score() で、Lucene と同じ関連度関数 BM25 を通常の次の文で利用できます: SELECT.

  • フィルターは通常の SQL。 カテゴリ、ページ、 IN のリスト、範囲条件を次の隣に記述します: MATCH_ANY と同じ WHERE 句内に記述します。

パネル:テーブル定義のインデックス行

始める前に

  • docker pull apache/doris:all-in-one-4.1.3
  • docker run -d --name doris -p 9030:9030 -p 8030:8030 -p 8040:8040 apache/doris:all-in-one-4.1.3
  • docker exec -it doris mysql -uroot -h127.0.0.1 -P9030
  • curl -LO https://github.com/morningman/demo-env/releases/download/for-hackathon/doris-hackathon-glasgow-2026.zip && unzip doris-hackathon-glasgow-2026.zip && cd doris-hackathon-glasgow-2026
ホスト
127.0.0.1
ポート
9030
ユーザー
rootパスワードなし
データベース
hackathon

データ

hackathon.doc_chunks :Doris 4.x ドキュメントの 47 の主要機能ページです。セクションごとに 1 行です:

テーブルhackathon.doc_chunks · sql
CREATE TABLE doc_chunks (  id          INT           NOT NULL,  page        VARCHAR(64)   NOT NULL,   -- doc slug, e.g. 'hybrid-search'  page_title  VARCHAR(128)  NOT NULL,  section     VARCHAR(256)  NOT NULL,  category    VARCHAR(32)   NOT NULL,   -- see the 7 values below  url         VARCHAR(256)  NOT NULL,  body        STRING        NOT NULL,  embedding   ARRAY<FLOAT>  NOT NULL,   -- 8-dim toy vector, unit length  INDEX idx_body (body) USING INVERTED PROPERTIES ("parser" = "english", "support_phrase" = "true"),  INDEX idx_category (category) USING INVERTED,  INDEX idx_emb (embedding) USING ANN PROPERTIES (    "index_type" = "hnsw", "metric_type" = "l2_distance", "dim" = "8"))DUPLICATE KEY(id)DISTRIBUTED BY HASH(id) BUCKETS 1PROPERTIES ("replication_num" = "1");

category の値 (フィルタードロップダウン用): search, ai, lakehouse, ingestion, performance, table-design, operations.

ベクトルについて。 完全にオフラインで動かせるよう、スターターキットには英語専用の小さな 簡易埋め込み関数, a1/toy_embed.py および a1/toy_embed.js が含まれています(出力は桁単位で同じです)。テキストを、手作業で定義した 8 つの概念次元に対応付けます: 検索、ベクトルと AI、JSON、レイクハウス、取り込み、性能、データモデル、運用 。同義語リストを使い、結果を単位長に正規化します。テーブル内のベクトルも同じ関数で作成しているため、クエリベクトルは同じ空間にあります。意図的に簡素な仕組みにしていますが、これだけでもベクトル検索の利点を示せます: "store messy nested records whose schema keeps changing" は、JSON や VARIANT と書かなくても VARIANT のページに到達します。追加の挑戦では本格的なモデルに置き換えます。

パネル → テーブル:テーブル定義の全体

AI を使って作る

AI コーディングエージェントとの共同作業を推奨しています。 Doris 4.x の検索機能と AI 機能は、多くのモデルの学習データより新しいため、SQL を書かせる前に適切な情報を渡してください:

  1. 次の コンテキストプロンプト をエージェントに貼り付けます。

  2. 参照先として次を指定: このページに掲載した参考資料 および AGENTS.md (スターターキット内)。多くのコーディングエージェントはこのファイルを自動で読み込みます。

  3. コードに組み込む前に、すべての SQL 文を実際のクラスターで実行させてください。

  4. さらに、次を接続すると便利です: Doris MCP Server (参照: タスク A2)。エージェント自身でスキーマを確認し、クエリをテストできるようになります。

コンテキストプロンプトエージェントに貼り付け
I'm building a hybrid search app on Apache Doris 4.1 for a hackathon.Connection: MySQL protocol, host 127.0.0.1, port 9030, user root, empty password,database `hackathon`. Use a plain MySQL driver and raw SQL (no ORM).Table `doc_chunks` (299 rows): id, page, page_title, section, url,category (search / ai / lakehouse / ingestion / performance / table-design / operations),body (STRING, inverted index, english parser, phrase support),embedding (ARRAY<FLOAT> NOT NULL, 8 dims, unit length, HNSW index, l2_distance).Doris SQL rules — follow them exactly:1. Keyword search uses `body MATCH_ANY 'a b'` (OR), `MATCH_ALL` (AND), `MATCH_PHRASE`. Never LIKE.   The parser keeps stopwords, so pass keywords(text) from toy_embed, not the raw question.   Bind the user's text as a driver parameter.2. BM25 relevance is `score()`. It only works with a MATCH_* predicate in WHERE and   `ORDER BY score() DESC LIMIT n`, selected plain (`score() AS relevance`): no ROUND(),   no window function, no JOIN in that SELECT. Round, rank or join it in an outer query.3. Vector search: `ORDER BY l2_distance_approximate(embedding, <vector>) ASC LIMIT n`.   Pass the vector as a string and cast it: CAST(%s AS ARRAY<FLOAT>) with '[f1, ..., f8]'.   Never pass a list/array as a driver parameter.4. Hybrid = MATCH_* pre-filter + vector ORDER BY; show the distance, do not select score()   in that query. For fusion, rank two lists in subqueries and combine with RRF (k = 60);   a1/rrf.sql in the starter kit shows the pattern.5. Query vectors come from toy_embed(text) in a1/toy_embed.py (toyEmbed in a1/toy_embed.js).   Do not invent embeddings.Build: a CLI (or small web page) with keyword / vector / hybrid modes, a category filter,and a "show SQL" option. Run every SQL statement against the live cluster first.

AI エージェントが陥りやすい Doris の落とし穴

TrapWhat to do instead
次の使用: LIKE '%term%' によるテキスト検索col MATCH_ANY 'a b' (OR)、 MATCH_ALL (AND)、 MATCH_PHRASE 'a b' 。これらは転置インデックスを使います
次の呼び出し: score() を任意のクエリで使用score() には次が必要: MATCH_* の述語を次に指定: WHERE および ORDER BY score() DESC LIMIT n。指定しないと Doris はクエリを拒否します
丸め、結合、順位付けの対象: score() を同じクエリ内で行う次はそのまま使います: score() AS relevance を単一テーブルのクエリ内でそのまま使います。丸めや結合、 ROW_NUMBER() は外側のクエリで行ってください。次の代わりにサブクエリを使います: WITH CTE
BM25 とベクトル距離を 1 つの次の句でソート: ORDER BY、または次を選択: score() をベクトル順位付けクエリで使用サポートされていません。ハイブリッド検索は次の組み合わせです: MATCH_* の事前フィルター + ベクトル ORDER BYで距離を表示するか、2 つのランキングリストを RRF で融合します
クエリベクトルをドライバーのパラメーターとして渡すリストは次に変換されます: (…) (pymysql)または 8 個の個別引数(mysql2)。文字列として次を渡します: '[0.1, …]' と記述します: CAST(%s AS ARRAY<FLOAT>)、またはリテラルを直接埋め込みます
コサイン ANN 指標を探すANN の指標は l2_distance および inner_productです。ベクトルを単位長に正規化すると、L2 距離の昇順とコサイン類似度の降順は同じ順位になります
2

実装する

5 マイルストーン、 M0 → M4。パネルは読んでいるステップに追従します。

  1. マイルストーン 05 分

    データをロード

    スターターキットのフォルダー内で実行:

    M0 · データをロードbash
    docker exec -i doris mysql -uroot -h127.0.0.1 -P9030 < seed/a1_doc_chunks.sql
    確認ポイント

    ロードの最後にカテゴリごとの件数が表示されます: 7 カテゴリ、299 セクション 、合計です。

  2. マイルストーン 115 分

    BM25 によるキーワード検索

    SQL シェルを開きます(./doris.sh sql、または上記の docker exec コマンド)。次を実行します:

    M1 · BM25 によるキーワード検索sql
    USE hackathon;SELECT page_title, section, score() AS relevanceFROM doc_chunksWHERE body MATCH_ANY 'vector index recall'ORDER BY relevance DESCLIMIT 10;

    続いて次を試します: MATCH_ALL (すべての語を含む)と MATCH_PHRASE 'inverted index' (隣接し、指定順)。入力ボックス → SQL → 結果リストの順に接続します。ユーザー入力はドライバーパラメーターで渡し(MATCH_ANY %s)、SQL に直接埋め込まないでください。アポストロフィが 1 つあるだけでクエリが壊れます。 a1/starter.py および a1/starter.js はこの方法を実装しています。

    質問を全文入力しましたか?english パーサーはストップワードを残すため、 'how do I …' はテーブルの約 3 分の 1 に一致します。代わりに次を渡します: keywords(text) (簡易埋め込み関数から取得)。ストップワードに加え、次の語も除外します: apache および doris。これらはほぼすべてのセクションに現れます。

    確認ポイント

    次の入力の上位結果: vector index recall は Vector Index と Hybrid Search のページです。先頭は How does the Apache Doris vector index work?

  3. マイルストーン 210 分

    フィルターを追加

    M2 · フィルターを追加sql
    SELECT page_title, section, category, score() AS relevanceFROM doc_chunksWHERE body MATCH_ANY 'vector index recall'  AND category = 'search'ORDER BY relevance DESCLIMIT 10;

    カテゴリをドロップダウンまたは CLI フラグで選べるようにします(7 つの値は「データ」に掲載)。追加条件は通常の SQL です: category IN (…), page = ….

    確認ポイント

    Vector Index のセクションは表示されなくなります。このページの分類は ai なので、Hybrid Search が先頭に移動します。

  4. マイルストーン 315 分

    ベクトル検索

    コードまたはコマンドラインから簡易埋め込み関数で質問をベクトル化します:

    M3 · 質問をベクトル化python
    from toy_embed import toy_embed, to_sql_arrayvec = to_sql_array(toy_embed("store messy nested records whose schema keeps changing"))# '[0.005740, 0.005740, 0.999885, ...]'  ·  CLI: python a1/toy_embed.py "..."
    M3 · ベクトル検索sql
    SELECT page_title, section,       l2_distance_approximate(embedding,         [0.005740, 0.005740, 0.999885, 0.005740, 0.005740, 0.005740, 0.005740, 0.005740]) AS distFROM doc_chunksORDER BY dist ASCLIMIT 10;

    コードからは SQL を固定し、ベクトル文字列をパラメーターで渡します: l2_distance_approximate(embedding, CAST(%s AS ARRAY<FLOAT>))。Python のリストや JavaScript の配列をパラメーターにすると動作しません。

    確認ポイント

    質問には JSON も VARIANT も含まれていませんが、先頭 6 件はすべて VARIANT のセクションです。同じ語のキーワード検索では Metadata Cache や Binlog のセクションも混ざります。

  5. マイルストーン 420 分

    ハイブリッド

    質問: "stream kafka changes into doris exactly once"。ベクトルはこの質問の簡易埋め込みです。キーワードフィルターは次の語を含むセクションのみを残します: kafka:

    M4 · ハイブリッドsql
    SELECT page_title, section,       l2_distance_approximate(embedding, [0.007020, 0.007020, 0.007020, 0.007020, 0.999827, 0.007020, 0.007020, 0.007020]) AS distFROM doc_chunksWHERE body MATCH_ANY 'kafka'                     -- keyword pre-filter (inverted index)  AND category IN ('ingestion', 'lakehouse')     -- structured filterORDER BY dist ASC                                -- vector ranking (ANN)LIMIT 10;
    • L4WHERE body MATCH_ANY 'kafka'キーワードの事前フィルター(転置インデックス)
    • L5AND category IN ('ingestion', 'lakehouse')構造化フィルター
    • L6ORDER BY dist ASCベクトル順位付け(ANN)

    アプリでは、1 つの入力を両方の処理に渡します: keywords(text) を次の用途に: MATCH_ANY, toy_embed(text) をベクトルに使います。 MATCH_ANY は複数語に対して緩いフィルターです。厳密な条件を求める場合は「必須語」入力欄か次を用意してください: MATCH_ALL 。ここでは次を選択しないでください: score() 。距離を表示してください。

    モード切り替え(キーワード / ベクトル / ハイブリッド)と次を追加します: SQL を表示 の切り替え。同じ質問で 3 つのリストを比較し、どこが異なり、なぜ異なるかを考えてみましょう。

    確認ポイント

    すべての結果に Kafka が含まれ、先頭は How does the Apache Doris Kafka and CDC integration work? ベクトルモードだけの場合、先頭は Pipeline Execution Engine › Overview になります(同じ質問)。

3

完成させる

チェックを済ませ、成果を共有し、バッジを獲得しましょう。

完了条件

チェックリスト

提出してバッジを獲得

参加 → 提出 → バッジを獲得。

  1. 成果物は次の名前のフォルダーにまとめてください: ご自身の GitHub ID。コードと README を含め、README に内容、実行方法、スクリーンショットまたは GIF 1 点、使用した Doris の機能を記載します。

  2. プルリクエストを作成 次の場所に追加してください: morningman/demo-env 配置先: doris-hackathon-glasgow-2026/<your-github-id>/。フォークして push するか、GitHub の次の操作を使ってください: Add file → Upload files。ほかに入力する項目はありません。

  3. 共有する 共有先: #dev 参加先: Apache Doris Slack:プルリクエストまたはリポジトリへのリンクと、スクリーンショットまたは短いデモを共有します。質問、提出、バッジの案内はすべてこちらで行います。

タスクに参加した方全員に次のバッジを贈ります: Apache Doris Contributor バッジ。Doris 本体へのプルリクエストがマージされている必要はありません。締め切りはないので、ご自身のペースで進めてください。チーム参加も可能ですが必須ではありません。全員の GitHub ID を次に記載してください: README.

追加の挑戦

  • Reciprocal Rank Fusion。 BM25 のリストとベクトルのリストを次で融合します: 1/(60 + rank) (1 つの SQL 文内): a1/rrf.sql (スターターキット内)は各リストの順位を次で計算します: ROW_NUMBER() 単純な次のクエリの 1 つ外側で処理します: score() 。その後、次の句で融合します: FULL OUTER JOIN。参考資料: RRF のページ で、4 行の例を使って同じパターンを解説しています。

  • 本格的な埋め込みを次で生成: EMBED() (API キーはご自身で用意):AI リソースを作成し、次を作ります: doc_chunks_v2 、モデルの次の値に合わせます: dim。データを次で埋めます: INSERT INTO doc_chunks_v2 SELECT …, EMBED(body) FROM doc_chunks (ベクトル列は NOT NULLのため、更新ではなく挿入します)。クエリも SQL で埋め込みを生成します。参照: 埋め込み.

  • 入力中に検索 ともに歩む MATCH_PHRASE_PREFIX.

  • 一致した語を強調表示 します(Doris はスコアを返しますが、ハイライトは返しません。アプリ側で描画してください)。

  • レイテンシバッジ:各モードの所要時間をミリ秒で表示します。

トラブルシューティングと参考資料

SymptomFix
docker: command not found (macOS)Docker Desktop を起動してください。起動済みなら CLI へのリンクを作成します: sudo ln -s /Applications/Docker.app/Contents/Resources/bin/docker /usr/local/bin/docker
error getting credentials (イメージ取得時)削除する項目: credsStore のフィールドを次から削除: ~/.docker/config.json (ローカル開発環境のみ)
port is already allocated別のプロセス(多くは別の Doris)が 9030、8030、8040 のいずれかを使っています。実行中のコンテナーを確認してください(docker ps)。または別のホストポートを公開します。例: -p 19030:9030。その場合は 19030 に接続してください。
The container name "/doris" is already in use以前に起動したことがあります: docker start doris。最初からやり直すには、 docker rm -f doris (データが削除されます)
コンテナーが終了する、または状態が変わらない: (healthy)確認: docker logs doris。多くはメモリが原因です。Docker Desktop に 6 GB 以上を割り当ててください(Settings → Resources)。Apple Silicon では次を追加しないでください: --platform linux/amd64
permission denied: ./doris.sh解凍ツールによって実行権限が失われています: chmod +x doris.sh、または次を実行: bash doris.sh start
Current database is not set実行: USE hackathon; を先に実行するか、次で接続します: -Dhackathon
score() function requires WHERE clause with MATCH function, ORDER BY and LIMIT次を追加: MATCH_* の述語、 ORDER BY score() DESC および LIMIT、さらに次は score() を式で包まないでください(上記の落とし穴を参照)
Ann topn query vector cannot be emptyベクトルリテラルが空、またはプレースホルダーのままです。次の出力を貼り付けてください: python a1/toy_embed.py "…"
Can not found function 'l2_distance_approximate' which has 9 arityドライバーが配列パラメーターを 8 個の値に展開しました。次を使ってベクトルを文字列で渡してください: CAST(? AS ARRAY<FLOAT>)
10 分以上解決できない場合投稿先: #dev 参加先: Apache Doris Slack

参考資料

ハイブリッド検索BM25全文検索ベクトルインデックスReciprocal Rank Fusion埋め込み

Apache Doris Hackathon

すべてのタスク

コースに戻る
  1. A160~120 分ハイブリッド検索アプリ現在位置
  2. A245~90 分MCP で Doris に質問課題の概要を開く
  3. A360~120 分ログ検索エクスプローラー課題の概要を開く
  4. A460~120 分エージェントトレースエクスプローラー課題の概要を開く
  5. B30~60 分トラック B · ドキュメントからデモへ課題の概要を開く