Multi-Tenant SaaS'ta Cost Attribution: Aynı API Key ile 1000 Müşterinin Maliyetini Doğru Atfetmek
B2B SaaS'ta tek bir OpenAI API key'i kullanıp 1000 müşterinin maliyetini ayrı ayrı raporlayabilmek gerekiyor. Bu derste tenant_id propagation, metadata injection, ve dashboard segmentation pattern'lerini görüyoruz.
Şükrü Yusuf KAYA
18 dakikalık okuma
Orta🏢 SaaS'ın gizli probleminin görünür hali
Multi-tenant SaaS'ın yıllardır taşıdığı problem: 'tek altyapı, çok müşteri'. AI feature'ları bu probleme yeni bir kat ekledi: token bazlı değişken maliyet. Bu derste o katmanı netleştiriyoruz.
Problem: Hangi Müşteri Ne Harcatıyor?#
B2B SaaS'ın gerçek-hayat senaryosu:
- 500 müşterin var
- Tek bir OpenAI hesabı
- Aylık toplam LLM faturası: $25.000
- "Acme Corp'un AI özelliği ne kadar maliyetli?" sorusunu bilemiyorsun
Bu üç sebepten kritik:
- Pricing — bazı müşterilere "AI feature unlimited" vaat ettin. Gerçekten unlimited olunca, kâr ya da zarar mı?
- Chargeback — kurumsal müşterilere "AI feature usage'ın $X" demek
- Abuse detection — bir müşteri sistemini sömürüyorsa görmek
Çözüm#
LLM çağrılarına tenant_id, user_id, feature etiketleri mutlaka koy. Telemetry'de bu etiketlere göre filtrele.
Attribution Mimarisi — 4 Katman#
[HTTP Request] → [Middleware: tenant context] ↓ [Service Layer] → [LiteLLM call with metadata] ↓ [LiteLLM] → [Provider API + cost calc] ↓ [Callback] → [Langfuse / ClickHouse with full attribution]
Her katmanda context propagation kritik.
Adım 1 — Tenant Context Middleware#
FastAPI / Next.js / Express — fark etmez. Her isteğin başında tenant context kur.
FastAPI örneği#
from fastapi import FastAPI, Depends, Request from contextvars import ContextVar # Per-request context current_tenant: ContextVar[str] = ContextVar("current_tenant", default="") current_user: ContextVar[str] = ContextVar("current_user", default="") app = FastAPI() @app.middleware("http") async def tenant_context_middleware(request: Request, call_next): # JWT'den tenant_id ve user_id çıkar auth = request.headers.get("Authorization", "") payload = decode_jwt(auth.replace("Bearer ", "")) current_tenant.set(payload.get("tenant_id", "anonymous")) current_user.set(payload.get("user_id", "anonymous")) response = await call_next(request) return response
Adım 2 — Servis Layer'da Metadata Injection#
from litellm import completion def call_llm( messages: list, model: str = "claude-sonnet-4-6", feature: str = "default", ): """LLM call wrapper — otomatik attribution ekler.""" return completion( model=model, messages=messages, metadata={ "tenant_id": current_tenant.get(), "user_id": current_user.get(), "feature": feature, "trace_id": str(uuid.uuid4()), "timestamp": datetime.utcnow().isoformat(), # Ekstra business context "tenant_plan": get_tenant_plan(current_tenant.get()), "deploy_env": os.environ.get("ENV", "dev"), }, )
Bu wrapper'ı her LLM call'unda kullan, doğrudan çağırma.
completion(...)Adım 3 — Callback'te Saklama#
ClickHouse veya Langfuse'a yazarken metadata'yı yapısal alan olarak işaretle:
def telemetry_callback(kwargs, response, start_time, end_time): metadata = kwargs.get("metadata", {}) ch_client.insert("llm_telemetry.requests", [[ datetime.utcnow(), metadata.get("trace_id"), metadata.get("tenant_id"), # ← KEY için indexed metadata.get("user_id"), # ← indexed metadata.get("feature"), # ← indexed metadata.get("tenant_plan"), # ← business segmentation response.usage.prompt_tokens, response.usage.completion_tokens, response._hidden_params["response_cost"], ... ]])
ClickHouse'da ve için bloom filter index'i koy (Modül 3.5).
tenant_iduser_idAdım 4 — Dashboard Segmentation#
Grafana / Langfuse'ta tenant'a göre segmentlenmiş raporlar:
"Top 10 maliyet üreten tenant"#
SELECT tenant_id, sum(cost_total_usd) AS monthly_cost, count() AS requests, avg(cost_total_usd) AS avg_per_request FROM llm_telemetry.requests WHERE ts >= toStartOfMonth(now()) GROUP BY tenant_id ORDER BY monthly_cost DESC LIMIT 10
"Plan'a göre ortalama maliyet"#
SELECT tenant_plan, count(DISTINCT tenant_id) AS tenant_count, sum(cost_total_usd) AS total_cost, sum(cost_total_usd) / count(DISTINCT tenant_id) AS avg_per_tenant FROM llm_telemetry.requests WHERE ts >= now() - INTERVAL 30 DAY GROUP BY tenant_plan
Bu sorgular chargeback raporlarının temeli.
Vaka: B2B Asistan SaaS#
Senaryo: Türkçe konuşan B2B müşteri-hizmetleri asistanı satıyorsun. 200 müşteri, aylık $20.000 toplam LLM faturası.
Attribution'sız hayat#
- "Acme Corp şu özelliği çok kullanıyor sanırım" (tahmin)
- "AI feature kâr getiriyor mu?" (bilmiyorum)
- "Pro plan ile Enterprise plan margin'i nasıl?" (bilmiyorum)
Attribution'lı hayat#
Dashboard:
TOP 5 müşteri: Acme Corp : $3.200/ay (16% of total) Beta Industries : $2.100/ay (10.5%) Gamma Holding : $1.700/ay (8.5%) Delta Group : $1.100/ay (5.5%) Epsilon LLC : $890/ay (4.4%) Plan margin: Starter ($199/mo): avg $35 LLM cost → 82% margin ✅ Pro ($499/mo) : avg $120 LLM cost → 76% margin ✅ Enterprise (custom): avg $450 LLM cost → varies — bireysel
Şimdi gerçek pricing kararları alabilirsin: Enterprise plan'a ek 599'a çıkar.
LiteLLM Virtual Keys — Tenant-per-key#
Daha sağlam bir mimari: her tenant'a kendi virtual key'i. LiteLLM bu konseptin ana mimarı.
Mimari#
Master API Key (gerçek OpenAI key) ↓ LiteLLM Proxy (yönetir, attribute eder, limit koyar) ├── Virtual Key 1 (acme-corp-key) ├── Virtual Key 2 (beta-industries-key) ├── Virtual Key 3 (gamma-holding-key) ...
Her virtual key:
- Kendi budget'ı (örn: Acme Corp aylık $5.000 limit)
- Kendi rate limit'i
- Kendi model whitelist'i (örn: Starter plan sadece GPT-5-mini)
- Otomatik attribution
Nasıl çalışır?#
# LiteLLM Proxy admin API ile virtual key yarat import requests resp = requests.post( "http://litellm-proxy/key/generate", headers={"Authorization": f"Bearer {LITELLM_MASTER_KEY}"}, json={ "key_alias": "acme-corp", "models": ["gpt-5-mini", "claude-haiku-4-5"], "max_budget": 5000, # USD "budget_duration": "1mo", "tpm_limit": 100_000, "rpm_limit": 600, "metadata": {"tenant_id": "acme-corp", "plan": "pro"}, }, ) new_key = resp.json()["key"] # sk-...-acme-corp-spesifik
Her tenant'a bu key'i ver. Otomatik attribution, otomatik budget enforcement.
Bunun mimarisini Modül 15'te (production cost engineering) detaylı işliyoruz.
💡 Attribution'ın geri dönüşü
Attribution kuran SaaS'lar, kurmamış olanlara göre %30-50 daha yüksek margin elde ediyor — çünkü gerçek müşteri ekonomisini görüyor ve pricing'i adapt edebiliyor. Mühendislik doğrudan iş çıktısına çevrilir.
▶️ Sıradaki ders
4.2 — Feature-Flag → Cost-Flag. A/B testin gerçek $/user farkını ölçmek. Yeni AI feature'ın gerçekten para kazandırıyor mu yoksa kaybediyor mu — bunu mühendislik düzeyinde doğrulamak.
Sık Sorulan Sorular
Doğru — direkt manuel eklemek hatalıdır. Çözüm: tüm LLM çağrılarını wrap eden bir **service layer** kullan (`call_llm()` örneği gibi). Aynı interface'i kullanmak zorunlu olunca attribution otomatik. Modül 15'te tip-safe pattern göstereceğiz.
Yorumlar & Soru-Cevap
(0)Yorum yazmak için giriş yap.
Yorumlar yükleniyor...
İlgili İçerikler
Modül 0: Neden Maliyet, Neden Şimdi?
AI Maliyet Patlaması: 2022'den 2026'ya Token Fiyatları Neden %96 Düştü Ama Faturalar Neden 40 Kat Arttı?
Öğrenmeye BaşlaModül 0: Neden Maliyet, Neden Şimdi?
Birim Ekonomisi Sözlüğü: COGS, Gross Margin, $/User, Contribution Margin — Mühendisin Bilmesi Gereken 9 Finansal Kavram
Öğrenmeye BaşlaModül 0: Neden Maliyet, Neden Şimdi?