2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DBの隣にIDEを置く:生パスワードを配らず、ローカルLLMでデータも外に出さない設計

2
Posted at

「このUPDATE、誰が流したの?」。監査でそう聞かれて、答えられない。各自のPCにDBクライアントを配って本番を触らせる運用は、たいていどこかでこの一点に詰まる。

先に断っておくと、筆者はLibreDB Studio(MITライセンスのOSS)の開発チームの一員だ。自分たちのツールの話が入るので宣伝に見える部分はあると思うが、ここで書きたいのは機能の一覧ではなく、「DBを触るツールをどこに置くか」という設計上の判断と、その理由だ。

手元にDBクライアントを配る運用は弱い

チームで本番DBを触るとき、各自のPCにGUIのDBクライアントを入れ、接続情報を配って回る運用は珍しくない。これが地味に効いてくる。

  • 本番DBの生パスワードが人数分、各PCのローカルに散らばる
  • 誰がいつどのクエリを流したか、後から追えない
  • 退職・異動のたびに、配った認証情報をどう回収するかが曖昧になる

接続情報を配った時点で、管理はかなり難しくなる。冒頭の「このUPDATEを流したのは誰か」に答えられない、という状況はできれば避けたい。配った時点で統制が難しくなるなら、いっそ配らない方向で考えればいい。出発点はそのくらい単純な話だった。

IDEはDBの隣に置く

LibreDB Studioの発想はシンプルで、IDEをDBの隣(同じネットワーク内)にコンテナやHelm、npmで立て、利用者はブラウザからアクセスするだけにする。

docker run -p 3000:3000 libredb/libredb-studio

こうするとDBへの接続情報はサーバー側に一元化され、各PCに生パスワードを配る必要がなくなる。あとは利用者がブラウザからアクセスするだけでいい。

その上で、MITの範囲内で次のものが使える。

  • SSO(OIDC)による認証
  • RBACによる権限分離
  • 全クエリの監査ログ

監査ログだけあっても、SSOで実行者が特定できなければ「誰が」が埋まらない。RBACがなければ全員が同じ権限で入ることになる。この三つはどれか一つでは統制として成立しないので、まとめて同じ層に用意している。

「全部同じUIに載る」は、すぐ破綻する

対応エンジンは16種類ある。ただ、この記事で名前を出したいのは、共通UIに載せようとすると素直に載らないものだけだ。実装していて一番の発見は、そこで正直に線を引いたところだった。網羅的な一覧はリポジトリにある。

Druid・Elasticsearch・OpenSearchは分析・検索が主用途で、このIDEの対話的なSQLではUPDATECREATE TABLEといった書き込み系を対象にしていない(Druidのバッチ取り込みやESのインデックスAPIといった専用の書き込み経路は別の話だ)。ここで無理に共通UIへ寄せると、編集ボタンは並んでいるのに実行すると謎のエラーが返る、という最悪の使い勝手になる。対応していないものは「サポートしていません」と明示する方がいい。

Cassandraはもっと厄介だ。フッターの件数欄が実データと食い違う。500行きっかりのテーブルで件数を取ると、143のような数字が出る。

-- 実際には500行あるテーブル
SELECT count(*) FROM events;  -- 完走すれば実数(500)を返すが、全走査するため大きなテーブルではread timeoutで落ちやすい

count(*)自体は全パーティションをスキャンして実数を返すもので、フッターの件数欄が使っているのはそれではない。Cassandraは各ノードがトークンレンジ単位で保持するsystem.size_estimates(推定パーティション数)を集計して概算を出しており、UIの件数欄はそちらを表示している。この推定値はcompaction前後やtombstoneの影響で実数から大きくずれるため、500行のテーブルで143のような数字になる。分散ストレージの構造上、ずれること自体は異常ではない。

だが、フッターに「143件」と平然と出せば、ユーザーはそれを事実だと信じてしまう。間違った数字を出すくらいなら件数欄は空にする。Cassandraでは行数を表示しないことにした。UIの一貫性より、表示する数字が嘘でないことのほうが大事だ。

Trinoを繋ぐと、今度はテーブル一覧の横に主キーやインデックスを出そうとして詰まる。Trinoはクエリエンジンであってデータベースではなく、背後のカタログを束ねて実行するだけなので、そもそもインデックスという概念を持たない。メタデータ取得を全エンジン共通のインターフェースにするのは諦め、エンジンごとに「何に対応していて何に対応していないか」を宣言させる形に変えた。

ワイヤ互換のエンジンも同じだ。プロトコルがワイヤ互換なので、MariaDBやTiDB、ValkeyはMySQL/Redisのクライアントとして接続はできる。ただし、接続できることと全パネルが動くことは別の話だ。互換性はプロトコルの話であって、EXPLAINの出力形式やシステムテーブルの中身までは一致しない。そのため、ER図は出るか、スキーマ差分は取れるか、EXPLAINは可視化できるかを、互換エンジンごとにパネル単位で実際に叩いて確認している。地味な作業だが、ここを実測しないまま「対応」と書くわけにはいかない、と思っている。

ローカルLLMなら、データは外に出ない

AIの接続先は差し替え式にした。狙いはモデルを自分で選べるようにすることで、GeminiやOpenAIのほか、ローカルのOllamaを指定できる。自然言語からSQLを生成し、生成したSQLは人が確認してから流す前提で、エージェント自体はリードオンリーで動く。

個人的にはここが本題だ。Ollamaのようなローカルモデルを指定した場合、プロンプトもスキーマもデータも外部APIへ出ていかない。GeminiやOpenAIを指定すれば当然データはネットワークの外に出るが、ローカルモデルならその通信自体が発生しない。これで、機密データや規制対象のデータを扱う環境でもAI支援を切らずに済む。

まとめ

要点は二つ。DBの隣にIDEを置いて生パスワードを配らず、SSO・RBAC・監査ログで統制する。AIはローカルモデルを選べばデータを外に出さない。加えて、マルチエンジン対応で学んだのは、共通化できないものを共通化したふりをしないことだった。表示できない数字は出さない、できない操作は明示する。派手ではないが、運用と監査で効く部分だと思っている。

ここで書いた挙動は実装に依存する部分が多い。細かいところはリポジトリ(github.com/libredb/libredb-studio)を見てもらうのが早いと思う。手元で動かして確かめたければ、デモ環境(app.libredb.org)も置いてある。

2
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?