/** * WPML compatibility functions * * @global array $duplicated_posts Array to store the posts being duplicated. * * @package Yoast\WP\Duplicate_Post * @since 3.2 */ add_action( 'admin_init', 'duplicate_post_wpml_init' ); /** * Add handlers for WPML compatibility. */ function duplicate_post_wpml_init() { if ( defined( 'ICL_SITEPRESS_VERSION' ) ) { add_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10, 3 ); add_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10, 3 ); add_action( 'shutdown', 'duplicate_wpml_string_packages', 11 ); } } global $duplicated_posts; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: Renaming a global variable is a BC break. $duplicated_posts = []; /** * Copy post translations. * * @global SitePress $sitepress Instance of the Main WPML class. * @global array $duplicated_posts Array of duplicated posts. * * @param int $post_id ID of the copy. * @param WP_Post $post Original post object. * @param string $status Status of the new post. */ function duplicate_post_wpml_copy_translations( $post_id, $post, $status = '' ) { global $sitepress; global $duplicated_posts; remove_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10 ); remove_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10 ); $current_language = $sitepress->get_current_language(); $trid = $sitepress->get_element_trid( $post->ID ); if ( ! empty( $trid ) ) { $translations = $sitepress->get_element_translations( $trid ); $new_trid = $sitepress->get_element_trid( $post_id ); foreach ( $translations as $code => $details ) { if ( $code !== $current_language ) { if ( $details->element_id ) { $translation = get_post( $details->element_id ); if ( ! $translation ) { continue; } $new_post_id = duplicate_post_create_duplicate( $translation, $status ); if ( ! is_wp_error( $new_post_id ) ) { $sitepress->set_element_language_details( $new_post_id, 'post_' . $translation->post_type, $new_trid, $code, $current_language ); } } } } // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: see above. $duplicated_posts[ $post->ID ] = $post_id; } } /** * Duplicate string packages. * * @global array() $duplicated_posts Array of duplicated posts. */ function duplicate_wpml_string_packages() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: renaming the function would be a BC-break. global $duplicated_posts; foreach ( $duplicated_posts as $original_post_id => $duplicate_post_id ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. $original_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $original_post_id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. $new_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $duplicate_post_id ); if ( is_array( $original_string_packages ) ) { foreach ( $original_string_packages as $original_string_package ) { $translated_original_strings = $original_string_package->get_translated_strings( [] ); foreach ( $new_string_packages as $new_string_package ) { $cache = new WPML_WP_Cache( 'WPML_Package' ); $cache->flush_group_cache(); $new_strings = $new_string_package->get_package_strings(); foreach ( $new_strings as $new_string ) { if ( isset( $translated_original_strings[ $new_string->name ] ) ) { foreach ( $translated_original_strings[ $new_string->name ] as $language => $translated_string ) { do_action( // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals -- Reason: using WPML native filter. 'wpml_add_string_translation', $new_string->id, $language, $translated_string['value'], $translated_string['status'] ); } } } } } } } } chromadb運用法 – Raqqa

chromadb運用法

。ただし 「勝手に“同期”される仕組み」ではなく、運用として “同じChromaを共有する” か “エクスポート/インポート(もしくはスナップショット/復元)で複製する” のどちらかに寄せるのが現実的です。


まず押さえる前提

Chroma は(少なくとも一般的な使い方では)Postgresみたいな 組み込みレプリケーションで dev⇄prod を自動同期する思想ではありません。
なので **「同期方法」=「どうやって同じデータ状態を作るか(=プロモーション)」**を決める話になります。


一番おすすめ:Chromaは “1つを共有” して、devが書き込み/prodは読み取りだけ

あなたの方針(prodは search-only)と最も整合します。

  • dev環境/extract → /translate → /package → /chroma/upsert を実行(書き込みあり)
  • prod環境/chroma/search だけ使う(書き込み機能はデプロイしない or 無効化)

これだと「同期」自体が不要で、devで取り込んだカスタム情報がそのままprodで使えます

安全にする鍵

  • ネットワーク的に prodからChromaへの書き込み経路を作らない(prodのAPIはsearchだけ)
  • コレクション名を固定(例 portal_field_ja, portal_view_common_ja)し、doc_id(あなたの sha256)で**上書き可能(idempotent)**にする
  • もし「devの試験データをprodに混ぜたくない」なら、コレクションを分ける(例:*_staging*_prod

Chromaをdev/prodで分けたい場合:エクスポート→インポートで“プロモーション”

Chroma Cookbook には Chroma Data Pipes (cdp) を使った API経由のexport/import が載っています。 Chroma Cookbook

例(概念)

  • dev から export(collectionごと)
    • cdp export "http://dev-chroma:8000/portal_field_ja" > portal_field_ja.jsonl
  • prod へ import
    • cdp import "http://prod-chroma:8000/portal_field_ja" < portal_field_ja.jsonl

この方式の良い点

  • “同期”というより リリース成果物(index)をprodへ昇格できる
  • dev/prodでChromaを分離できる(安全)

注意点(事故ポイント)

  • 埋め込みモデル/次元は一致必須(一致しないと投入時に次元エラーになりがち)
  • 取り込み物は「正」ではなくインデックス(=壊れても再生成できる)なので、
    • 正のデータは devportal DB の portal_chroma_doc(あなたの設計)
    • Chroma は “再構築可能な派生物” として扱うのが堅い

“バックアップ/復元”としての同期:スナップショット or 永続領域コピー

Chroma Cookbook には **collection snapshot(バックアップ/復元用途)**の考え方があり、スナップショットは self-contained で backup/restore 向け、と説明されています。 Chroma Cookbook
また、運用上は persistent directory(永続化ディレクトリ)をバックアップして復元する発想も一般に語られています(ただし停止/整合性・バージョン差に注意)。 Chroma Cookbook

実務的には

  • 「日次バックアップ」= snapshot/永続領域バックアップ
  • 「dev→prod昇格」= export/import(cdp) or “prodに対して同じ upsert を実行”
    が混ざることが多いです。

あなたのユースケースに対する“推奨結論”

あなたの前提(Devでカスタム取り込み→prodでSQL生成に必須、かつ prodはsearch-only)なら、最も安全で運用が簡単なのは:

✅ 1) Chromaは共通(1つ)にして、書き込みはdevだけ

同期不要。安全策は「prodからは書けない」をネットワーク/デプロイで担保。

✅ 2) どうしても分離するなら、indexを成果物として export/import で昇格

cdp export/import(collection単位)+ “埋め込み次元一致” を運用ルール化。 Chroma Cookbook


すぐに決めるべき2点(これだけ決めれば設計が固まります)

  1. Chromaは共通にする?分離する?
  2. 分離する場合:昇格単位は「collection」固定で良い?portal_field_ja, portal_view_common_ja をそのままprodへ)

この2つが決まれば、あなたの既存パイプライン(portal_chroma_doc → upsert idempotent)に沿って、**“devで生成 → prodで必ず同じ検索結果”**を再現できます。

一番おすすめ:Chromaは “1つを共有” して、devが書き込み/prodは読み取りだけ あなたの方針(prodは search-only)と最も整合します。 dev環境:/extract → /translate → /package → /chroma/upsert を実行(書き込みあり) prod環境:/chroma/search だけ使う(書き込み機能はデプロイしない or 無効化) これだと「同期」自体が不要で、devで取り込んだカスタム情報がそのままprodで使えます。 これで行きます。dev環境は今はローカルですが、これもクラウドに上げないといけないですよね、最終的には。思考時間: 1m 3s

方針「Chromaは1つ共有/devが書き込み/prodは読み取りだけ」で行くなら、結論はこうです:

  • “開発環境そのもの”を全部クラウドに上げる必要はありません(あなたのローカルMinikubeは残せる)
  • ただし最終的には、Chroma(共有先)と、そこへ定期的に書き込む“管理用(writer)実行基盤”はクラウド側に置くのが現実的です
    (理由:ネットワーク到達性・安定稼働・秘密情報管理・ジョブ運用)

おすすめ最終形(あなたの方針に一番フィット)

1) クラウドに “共有Chroma” を1つ置く

  • EKS上に StatefuSet で自前運用でもいいし、Chroma Cloud を使う選択肢もあります(CloudはAPI key/tenant/dbの概念あり)。Chroma Docs+2Chroma Docs+2
  • collections は起動時に get_or_create(portal_field_ja / portal_view_common_ja 等)で「存在しない」事故を潰す

2) クラウドに “Writer(管理用)” を置く(dev相当)

ここがポイントで、あなたの /extract → /translate → /package → /chroma/upsert を回す場所をクラウドに置きます。

  • 形式:CronJob + 手動実行API(またはJob)
  • 役割:Odoo(JSON-RPC)から ir_* を同期 → portal_chroma_doc 生成 → Chroma upsert
  • prodには upsert系をデプロイしない/envで完全無効化(search-onlyを堅持)

3) prod は “search-only” で同じChromaを見る

  • prod側は /chroma/search(+ analytics系が参照するretrieval)だけ
  • ネットワーク/権限でも「prodからChromaへ書けない」ようにする(後述)

「ローカルdevはどうなる?」

ローカルはこう使えます:

  • 普段の開発・デバッグはローカルでOK
  • ただし “本当にChromaへ書き込む操作” は、最終的にはクラウドのWriterで回すのが安全
    (ローカルからクラウドChromaへ直接書くのは、初期はアリでも運用にすると事故りがち:VPN/ポート公開/秘密管理/誰がいつ書いたか等)

現実的には、

  • ローカル:IR_SRC_SYNC_ENABLED=false(読み取り/確認中心)
  • クラウドWriter:IR_SRC_SYNC_ENABLED=true + CronJob稼働
    が落としどころです。

セキュリティと事故防止(最小セット)

「共有Chroma」を安全にするには **“アクセス経路の分離”**が肝です。

  • ChromaはVPC内のprivate endpoint(基本は外に出さない)
  • WriterだけがChromaへ書ける(NetworkPolicy / SG / ルーティングで制限)
  • prodは「検索だけ」できればよい(APIレベルでもupsert機能はビルド/ルートごと外す)

※Chroma Cloudを使うなら、API key + tenant/db で環境分離もしやすいです。Chroma Docs+1


バックアップ/災害対策も一緒に決める(重要)

共有Chromaは「同期」しない代わりに、バックアップが必要です。

一番ラクなのは Chroma Data Pipes(cdp)でexport を定期実行する形です(collection単位でjsonlへ)。Chroma Cookbook+2ChromaDB Data Pipes+2
(=復元はimport。あなたのdoc_id設計があるので、再投入も比較的安全)


実装ロードマップ(あなたの現状から最短)

  1. クラウドに shared Chroma を立てる(まず collections 常在まで)
  2. クラウドに “Writer” を立てる(devportal DB + CronJob)
  3. prod は search-only のまま shared Chroma を参照
  4. ローカルは “同じAPIを叩ける” 開発環境として残す(必要ならトンネルで参照)

この方針で進めるなら、次の作業は「クラウドWriterの置き場所」を決めることになります。
あなたの既存方針(EKS上にprodがある、prodはsearch-only)からすると、同じEKS内に portal-admin みたいな namespace を作ってWriterを置くのが一番シンプルです(ネットワーク制御もしやすい)。


Comments

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です