Vol.042026年5月9日
AI

Embedding コストを 1/70 にした 3 つの工夫

hash で再計算スキップ / dim を 1536 → 256 / バッチング。月 $42 が $0.6 になった実測。

SoSoraEndo2026年5月9日2 min739

なぜコストが膨らむか

OpenAI の text-embedding-3-small は 1M トークンで $0.02。安いように見えるが、検索インデックスを毎晩 全件再計算 する設計だと 無駄な再計算が累積 して気づくと月 $50〜200 になる。

私は手元のサイドプロジェクトでこれをやって痛い目を見た。3 つの工夫 で 1/10 にした実話を書く。

工夫 1: text の hash で再計算をスキップ

embedding する元 text に変化が無ければ、embedding も変化しない。当たり前だが、これを hash で機械的に検出 する仕組みが要る。

class Post < ApplicationRecord
  before_save :update_embedding_hash

  def update_embedding_hash
    text = embedding_source_text  # title + excerpt + body の連結
    self.embedding_source_hash = Digest::SHA256.hexdigest(text)
  end
end

class EmbedJob
  def perform(post_id)
    post = Post.find(post_id)
    return if post.embedding_source_hash == post.last_embedded_hash
    
    embedding = OpenAI::Client.new.embeddings.create(input: post.embedding_source_text).data.first.embedding
    post.update!(embedding: embedding, last_embedded_hash: post.embedding_source_hash)
  end
end

これだけで 更新があった記事だけ embedding が走る。50 記事のサイトで月 1 回更新が 5 件なら、コストは 1/10 になる。

工夫 2: dim を 1536 → 768 / 256 に絞る

text-embedding-3-smalldim を任意に指定 できる(OpenAI の Matryoshka Embeddings 機能)。

client.embeddings.create(
  model: 'text-embedding-3-small',
  input: text,
  dimensions: 768  # ← 1536 → 768 に絞る
)

dim を半分にすると、ストレージも検索コストも半分になる。精度の劣化は 5% 未満(OpenAI の論文値)で、検索体感はほぼ変わらない。

私は最初 1536 で運用していたが、768 に下げて検索品質が変わらなかったので 256 まで下げた。これで 1536 → 256 で 6 分の 1

工夫 3: バッチング

OpenAI の embeddings API は 入力配列で複数同時 に投げられる。

texts = posts.map(&:embedding_source_text)
response = client.embeddings.create(
  model: 'text-embedding-3-small',
  input: texts,         # ← 配列
  dimensions: 256
)

response.data.each_with_index do |item, i|
  posts[i].update!(embedding: item.embedding)
end

50 件を 1 リクエストで処理できる。リクエスト数で課金されるサービス(embeddings は基本トークン課金だが、レート制限はリクエスト数)では、バッチで rate limit を回避 できる。

実測コスト

私の手元のプロジェクト(200 記事 / 月次更新 30 件)で、この 3 つを順次入れた結果:

状態月コスト
全件再計算 + 1536 dim$42
hash チェック追加$8
dim 768 に変更$4
dim 256 に変更 + バッチング$0.60

70 倍の差。dim を絞るだけで効果は劇的。

落とし穴

新規記事を全件 reindex すると、hash check が効かない。これは想定通りで、新規記事 200 本の初回 embedding に $5〜10 程度かかる。これは諦めて払う。

逆に注意すべきは、body のスペースだけ変えた更新で hash がズレること。text.strip.gsub(/\s+/, ' ') のような正規化を hash 計算前に挟むと、空白の差分で再計算が走らない。

まとめ

embedding コストを下げる 3 つの工夫:

  1. hash で更新検出 → 変更あった記事だけ recompute
  2. dim を絞る (1536 → 256) → ストレージ + 計算 6 分の 1、品質 5% 劣化
  3. バッチング → rate limit 回避、API 呼び出し数を圧縮

3 つ全部入れると、コストは 約 1/70 になる。月 $40 が $0.6 になる感覚。

安い API でも、回数を雑に増やせば富裕税になる。最初の設計で hash と dim を考える。

Tags

Reaction

Share

X (Twitter)