PloneへのLanceDB組み込みで見つけたLanceの検索バグを修正した話
現在、Ploneの検索にLanceDBを組み込む作業を進めています。その中で、メタデータによる絞り込みに一致しないはずのデータが、検索結果に現れる不具合を見つけました。原因はLanceDBの基盤であるLanceのインデックス更新処理にあり、修正を提案したPull Request #9352がマージされ、Lance v13.0.0としてリリースされました。
Ploneへの組み込み作業で気付いたこと
LanceDBは、Lanceを基盤とするデータベースです。私たちは独自の研究でもLanceフォーマットを使っていますが、今回の発見のきっかけはPloneへのLanceDB組み込み作業でした。組み込みの構成や検索機能については、別の記事で紹介する予定です。
検索では、ベクトルの近さだけでなく、メタデータの条件で結果を絞り込むことも重要です。たとえば、閲覧できる人を表すラベルが Anonymous のデータだけを検索する場面を考えます。ある行のラベルを Anonymous から Manager に変更したら、その行は Anonymous の検索結果から消えるはずです。
実際、50行の検証データでは、更新直後の検索結果は期待どおり50件から49件になりました。ところが、インデックスの最適化後に同じ条件で検索すると、結果が50件に戻りました。更新した行に保存されている値は Manager なのに、古い Anonymous の条件でヒットしていたのです。update と merge_insert の両方で起き、ベクトル検索にフィルタを組み合わせた場合にも再現しました。現象と再現条件はPR本文に記録しています。
メタデータフィルタリングが外れてしまうように見える、致命的なバグだと感じました。もしアクセス権の判定をこの検索条件だけに任せていれば、権限を外したデータが返る可能性があります。ただし、今回の再現は検証用データによるもので、本番環境での情報漏えいやLanceDBの認証機構の突破を確認したわけではありません。
二つの行まで絞って原因を調べた
調査を進めると、ベクトル検索も大量のデータも必要ありませんでした。リスト型の列を持つ2行に LABEL_LIST インデックスを作り、一方の値を old から new に更新して、インデックスを最適化するだけで再現します。インデックスを使わない検索では old は0件なのに、インデックスを使うと更新済みの行が1件返りました。
原因は、LabelListIndex::update が、更新前の行をインデックスから除くための old_data_filter を受け取っていながら、使っていなかったことです。修正では、古いラベルに対応する行の情報と、NULLのリストに関する情報から更新前の行を取り除いてから、新しい値を加えるようにしました。PythonとRustの回帰テストも追加しています。コードの変更
この不具合は、安定した行IDを有効にした検証データで確認しました。PRに記載した既存の本番テーブルでは、安定した行IDを有効にしていません。また、修正版を使っても、すでに古い情報を含んでいる LABEL_LIST インデックスは自動では直りません。該当するインデックスがある場合は再作成が必要です。
マージされ、Lance v13.0.0でリリース
PRは2026年9月17日に提出し、9月30日にレビューを経てマージされました。その後、修正はLance v13.0.0のリリースノートに掲載され、10月7日に正式版として公開されました。
Ploneへの組み込み中に見つけた問題を、再現条件と原因まで絞り込んでLance本体へ修正を届けられたこと、そしてマージだけでなくリリースまで進んだことが、とても嬉しいです。PloneでLanceDBを使って何を実現しようとしているのかは、今後の記事で紹介します。
以前のPloneサイト実データでのベクトル検索の検証からも、検索の取り組みは続いています。今回の経験は、検索結果の順位や速度だけでなく、絞り込み条件が更新後も正しく働くことを確かめる大切さを改めて教えてくれました。