💡

「このDB、もっと知られてもいいのでは?」からOSSコントリビュートに至った話

に公開

はじめに

この記事は、Qiita に掲載した「DifyでInterSystems IRISをベクトルDBとして使ってみる【環境構築編】」の開発裏話になります。

まずは本編はこちらからどうぞ:
https://qiita.com/TomoOkuyama/items/8d926c2e31b1d77700a4

「このDB、もっと知られてもいいのでは?」と思ったところから、どうやってOSSコントリビュートに至ったのかを、技術要素はほどほどにしつつ振り返ってみます。

きっかけ

「InterSystems IRISってベクトル検索できるのに、AI界隈であまり知られてないな...」

ある日ふとそう思いました。

私はInterSystemsでセールスエンジニアをしています。
IRISは医療や金融などミッションクリティカルな現場で長年使われてきたDBで、
最近ベクトル検索にも対応しました。

でも、RAGやLLMアプリの文脈で名前が挙がることはほとんどない。
QdrantやWeaviate、Milvusといった専用DBの陰に隠れている印象でした。

「だったら、人気のあるOSSに統合してもらえばいいのでは?」

そう思い立ち、GitHub Stars 10万超のLLMアプリ開発プラットフォーム「Dify」に
IRISをベクトルDBとして使える機能をコントリビュートすることにしました。

この記事では、その経緯と実装、PRがマージされるまでの話を書きます。

登場人物の紹介

Difyとは

Difyは、RAGやAIエージェントをノーコード/ローコードで構築できるOSSプラットフォームです。

  • GitHub Stars:10万以上
  • 対応ベクトルDB:Weaviate、Qdrant、Milvus、PgVector など多数
  • 特徴:docker-composeで簡単に起動、GUIでRAGパイプラインを構築可能

https://github.com/langgenius/dify

LangChainやLlamaIndexをコードで書くのが面倒な人にとって、
Difyは「とりあえず動くRAGアプリを作りたい」ときの強い味方です。

InterSystems IRISとは

IRISは、1つのエンジンで複数のデータモデルを扱えるマルチモデルDBです。

  • リレーショナル(SQL)
  • ドキュメント(JSON)
  • オブジェクト
  • ベクトル検索 ← New!

Web系やAI界隈ではマイナーですが、
医療業界では電子カルテの裏側で動いていたりと、実は広く使われています。
世界で10億件以上の医療記録がInterSystemsの技術で管理されているとか。

2024年にベクトル検索機能が追加され、RAG用途でも使えるようになりました。
DBにも関わらずPythonも実行できるので、AIアプリの開発もシンプルに実装できます。

なぜDifyを選んだのか

IRISの認知度を上げる方法はいくつか考えられました。

方法 メリット デメリット
ブログ記事を書く 手軽 リーチが限定的
LangChain統合 ユーザー多い すでに多くのDB対応済み、埋もれそう
Dify統合 GUIで試せる、導入障壁が低い コードリーディングが必要

Difyを選んだ理由は、「試すハードルの低さ」 でした。

Difyなら、コードを書かなくてもGUIでベクトルDBを切り替えて試せます。
「ちょっと触ってみるか」のハードルが圧倒的に低い。

また、docker-compose.ymlに設定が集約されているので、
IRISを追加しても「.envを1行変えるだけ」で使える形にできそうでした。

最初にやったこと

まずはDifyのコードを読んで、ベクトルDBがどう統合されているか調べました。

dify/
├── api/
│   └── core/
│       └── rag/
│           └── datasource/
│               └── vdb/
│                   ├── weaviate/
│                   ├── qdrant/
│                   ├── milvus/
│                   └── ... ← ここにirisを追加する

既存のベクトルDB実装を参考にすれば、パターンが見えてきそうです。
Qdrantの実装が一番シンプルだったので、これをベースにすることにしました

コントリビューションガイドを読む

実装に入る前に、DifyのCONTRIBUTING.mdを読みました。

https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md

ここで重要なことに気づきます。

Don't forget to link an existing issue or open a new issue in the PR's description.

PRを出す前に、まずIssueを作成する必要がある、ということです。

いきなりPRを出しても「これ何?」となってしまうし、
もしかしたら既に誰かが同じことをやっているかもしれない。
Issueで「こういう機能を追加したい」と宣言し、メンテナーからOKをもらってから
実装に入るのが正しい流れでした。

Issueを作成する

既存のIssueを検索しましたが、IRISに関するものは見つかりませんでした。
なので、Feature Requestとして新しいIssueを作成しました。

https://github.com/langgenius/dify/issues/28778

タイトルは 「Feature Request: Add support for using InterSystems IRIS as a vector database」 としました。

Issueには以下のポイントを書きました:

1. なぜIRISなのか(背景)

Many hospitals and healthcare organizations use InterSystems IRIS as their primary operational and analytics database.

医療機関ではIRISがすでに広く使われています。
DifyがIRISに対応すれば、既存のデータ基盤を活かしてAIアプリを構築できる。
データを別のベクトルDBにコピーする必要がなくなり、
コンプライアンスやガバナンスの観点でもメリットがある、という点を強調しました。

2. 実装の準備状況

I have already implemented this feature and completed most of the verification in our environment (including connection, vector search operations, and basic performance checks).

「もう実装できてます、動作確認も済んでます」と書くことで、
メンテナーに「すぐPRが出てくるな」と期待してもらえます。
口だけのFeature Requestではなく、実行力を示すのが大事だと思いました。

3. 方向性の確認

I expect that I can open a pull request for review in a short time once we agree on the direction and scope.

「方向性が合意できればすぐPR出します」という姿勢を見せました。
勝手に実装を進めて「実は要らなかった」となるリスクを避けるためです。

Issueを書くときのコツ

振り返ると、以下のポイントが良かったと思います:

ポイント 理由
具体的なユースケースを示す 「医療機関で使われている」という背景が説得力を増す
実装済みであることを明記 「言うだけ」ではなく実行力を示せる
方向性の確認を求める メンテナーとのコミュニケーションの余地を残す
英語で書く DifyのLanguage Policyに従う(日本語だとクローズされる)

メンテナーからの反応

数日後、Issueに 👻 feat:rag ラベルが付けられました。
これは「RAG/Embedding関連のfeature request」という意味で、
メンテナーが内容を確認して分類してくれた証拠です。

これでGOサインをもらえたと判断し、本格的にPRの準備を開始しました。

学び:Issueファーストの重要性

最初は「コード書いてPR出せばいいでしょ」と思っていましたが、
Issueを先に作ることで以下のメリットがありました。

  • 重複を避けられる:同じ機能を誰かが作っていないか確認できる
  • 方向性を確認できる:「そもそもその機能いらない」と言われるリスクを減らせる
  • コミュニケーションの場ができる:実装中の質問もIssueに書ける
  • PRの説明が楽になるCloses #28778 と書くだけでIssueとリンクできる

大きめの機能追加をする場合は、Issue → 承認 → 実装 → PR の流れを守るのが大事ですね。

実装の流れ

Step 1:最小構成で動かす

最初から完璧を目指さず、まずは最小構成で動くものを作りました。

  1. IRISへの接続
  2. ベクトルの保存(INSERT)
  3. ベクトル検索(VECTOR_COSINE)

この3つが動けば、Difyのナレッジベース機能は使えるはずです。

Step 2:Difyの既存パターンに合わせる

DifyにはBaseVectorという抽象クラスがあり、
各ベクトルDBはこれを継承して実装しています。

class IRISVector(BaseVector):
    def create(self, texts: list[Document]) -> list[str]:
        # ベクトルを保存
        ...
    
    def search(self, query_vector: list[float], top_k: int) -> list[Document]:
        # 類似度検索
        ...
    
    def delete(self) -> None:
        # 削除
        ...

既存実装(Qdrant、Weaviate)を横目で見ながら、同じインターフェースで実装しました。

Step 3:全文検索・ハイブリッド検索の追加

Difyはベクトル検索だけでなく、キーワード検索やハイブリッド検索にも対応しています。

IRISには%Text.Textという全文検索機能があり、BM25スコアリングも可能です。
せっかくなので、これらも実装しました。

検索モード IRIS側の実装
ベクトル検索 VECTOR_COSINE()
全文検索 %Text.Text
ハイブリッド 両者のスコアを組み合わせ

設計で迷ったところ

テーブル設計

Difyでは、ナレッジベースごとにコレクション(テーブル)が作られます。
IRISでどう表現するか迷いました。

案1:1テーブルに全部入れて、collection_idカラムで区別

  • メリット:シンプル
  • デメリット:データ量が増えるとパフォーマンスに影響

案2:コレクションごとにテーブルを動的に作成

  • メリット:分離が明確、削除が楽
  • デメリット:DDLを動的に実行する必要がある

案2を採用しました。他のベクトルDB(Qdrant等)も同様のアプローチだったのと、
 コレクション削除時にDROP TABLEするだけで済むのが決め手でした。

接続プールの実装:Community Editionの制限との戦い

実装を進める中で、最も頭を悩ませたのが同時接続数の問題でした。

問題の発見

Difyでナレッジベースを検索すると、裏側ではベクトル検索や全文検索が並列で実行されます。
コードを追ってみると、以下のような流れになっていました。

ThreadPoolExecutor(最大スレッド数 = CPUコア数)
    ├── スレッド1: embedding_search()
    │       └── IrisVector.search_by_vector()
    │               └── _get_cursor() → IRIS接続を取得
    ├── スレッド2: full_text_index_search()
    │       └── IrisVector.search_by_full_text()
    │               └── _get_cursor() → IRIS接続を取得
    └── ...

各スレッドが独立してIRISへの接続を確立するため、
CPUコアが多いマシンでは同時に多数のセッションが張られます。

ここで問題になるのが、IRIS Community Editionの同時接続数制限(最大8) です。

8コア以上のマシンでDifyを動かすと、あっという間に制限に引っかかってしまいます。
テスト中に何度も接続エラーが発生し、原因を突き止めるまでかなり時間がかかりました。

解決策:IrisConnectionPoolの実装

この問題を解決するため、接続プールを実装しました。

class IrisConnectionPool:
    def __init__(self):
        self._min_size = config.IRIS_MIN_CONNECTION  # 最小接続数
        self._max_size = config.IRIS_MAX_CONNECTION  # 最大接続数
        self._pool: queue.Queue = queue.Queue()
        self._lock = threading.Lock()
        # ...

接続プールの役割は以下の通りです:

機能 説明
接続の再利用 使い終わった接続をプールに戻し、次のリクエストで再利用
最大接続数の制限 IRIS_MAX_CONNECTION を超える接続を作らない
待機処理 プールが空の場合、接続が返却されるまで待つ

これにより、ThreadPoolExecutorが何スレッド作ろうとも、
IRISへの同時接続数は設定値以内に収まるようになりました。

設定値のデフォルト

Community Editionの制限(最大8接続)を考慮し、以下のデフォルト値を設定しました。

IRIS_MIN_CONNECTION = 1  # 最小接続数
IRIS_MAX_CONNECTION = 3  # 最大接続数(余裕を持たせて3)

最大を3にしたのは、Dify以外のプロセス(管理ポータルその他)が
接続を使う可能性を考慮したためです。

学び:制限を制限のままにしない

Community Editionには制限があります。でも、それを「仕方ない」で終わらせず、
ソフトウェア側で制御する仕組みを入れることで、ユーザー体験を守ることができます。

この接続プールがあることで、ユーザーは同時接続数を意識せずに
Dify + IRISを使えるようになりました。

PRを出す

実装がある程度できたところでDraft PRを作成しました。

PRで気をつけたこと

  1. Issueをリンクする

    • 説明欄に Closes #28778 と書いてIssueと紐づけ
    • これでPRがマージされると自動的にIssueもクローズされる
  2. タイトルを規約に合わせる

    • feat: Add InterSystems IRIS vector database support
    • Difyは Conventional Commits 形式を採用している
  3. スクリーンショットを添付

    • 「動いている証拠」があるとレビュアーも安心
  4. CIを通す

    • Linter、テスト、型チェックをローカルで確認してからPush
    • Python Style、Web Style、Docker Compose Template、SuperLinter...
    • 全部グリーンになるまで修正

マージ!

Dify 1.11.2でマージされました🎉

https://github.com/langgenius/dify/releases/tag/1.11.2

リリースノートに自分の名前が載っているのを見ると、やっぱり嬉しいですね。

やってみて分かったこと

技術面

  • 既存実装を読むのが最短ルート:ドキュメントより、動いているコードを読む方が早い
  • 最小構成から始める:完璧を目指すと永遠にPRを出せない
  • CIを信頼する:ローカルで動いても、CIで落ちることは普通にある
  • 制限は設計で乗り越える:Community Editionの同時接続制限は、接続プールで解決できた

コミュニティ面

  • 英語は完璧じゃなくていい:伝わればOK、翻訳ツールも活用
  • Draft PRは便利:「まだWIPです」と示せるので、早めに出してフィードバックをもらえる
  • メンテナーは忙しい:返信が遅くても焦らない

認知度向上という観点

OSSに統合されると、自分で宣伝しなくても「選択肢として存在する」状態になります。

Difyのドキュメントやdocker-compose.ymlにIRISの名前が載る。
それだけで、「IRISってベクトル検索できるんだ」と知ってもらえる機会が生まれます。

これは記事を書くだけでは得られない効果だと感じました。

おわりに

「このDB、もっと知られてもいいのでは?」という素朴な疑問から始まり、
OSSコントリビュートという形で行動に移してみました。

結果として、

  • Issue #28778 で提案 → PR #29480 でマージ
  • Dify 1.11.2 でIRISがベクトルDBの選択肢に追加
  • リリースノートに名前が載った 🎉

という成果が得られました。

「うちのプロダクト/技術、もっと知られてもいいのに」と思っている方、
OSSへの統合は一つの手段としてアリだと思います。

最後に、環境構築の手順はQiitaにまとめているので、
興味のある方はぜひ試してみてください。

https://qiita.com/TomoOkuyama/items/8d926c2e31b1d77700a4


※この記事は個人の見解であり、所属組織の公式見解ではありません。

Discussion