Bir IoT platformu müşterimiz Türkiye + Almanya + UAE + Singapur’da 1.2 milyon sensörden saniyede 18.000 telemetry message alıyor. Container’da 4 milyar document, aktif aylık 180 GB write. Cosmos DB faturası başlangıçta $34.000/ay’a çıkmıştı. 3 ay süren optimizasyon sonunda $11.200/ay. Bu yazı o projeden notlar.
API Seçimi: NoSQL, MongoDB, Cassandra, Gremlin, Table
| API | Tipik kullanım | Notlar |
|---|---|---|
| NoSQL (Core SQL) | Yeni proje, full Cosmos DB feature | En geniş feature set |
| MongoDB | Mevcut Mongo uygulaması migrate | 4.0/4.2/5.0/6.0 wire protocol |
| Cassandra | Mevcut Cassandra workload | CQL desteği |
| Gremlin | Graph database (knowledge graph, fraud) | Apache TinkerPop |
| Table | Azure Table Storage migrate, basit key-value | Cheap option |
IoT müşterimiz mevcut Mongo uygulamasından geliyordu, MongoDB API ile başladı. Wire protocol uyumlu, kod değişikliği minimum.
Partition Key Tasarımı: En Kritik Karar
Yanlış partition key = pahalı + yavaş + scale etmeyen. Doğru partition key = ucuz + hızlı + sınırsız scale.
Kötü örnek: partitionKey: deviceId. 1.2 milyon farklı device → 1.2 milyon partition. Aggregation query’ler tüm partition’lara fan-out, RU patlıyor.
İyi örnek: partitionKey: deviceCustomerId_yyyymm (müşteri × ay bazlı bucket). 600 müşteri × 12 ay = 7.200 partition. Bir müşterinin bir aylık tüm data’sı tek partition’da, query verimli.
Gerçek partition key seçimi:
{
"deviceId": "iot-tr-bursa-otomotiv-001-sensor-temp",
"partitionKey": "tr-bursa-otomotiv-001:202604", // tenant + ay
"ts": "2026-04-24T18:30:00Z",
"value": 23.4,
"unit": "C",
"metadata": {...}
}
Sorgu pattern: “Bu müşterinin nisan ayındaki tüm sensör verisi” → tek partition tarama, 5 RU. Yanlış partition’da aynı query 4.500 RU’ya çıkıyordu.
RU/s Capacity Mode
| Mode | Avantaj | Dezavantaj |
|---|---|---|
| Provisioned (manual) | Predictable cost | Atıl kapasite veya throttle |
| Autoscale | Talep’e göre 10x scale | Min %10, max RU/s × $1/100 RU/s/ay |
| Serverless | Pay-per-request | Max 5.000 RU/s, geo-replication yok |
IoT müşterimiz başta Provisioned 100.000 RU/s set etmişti, gece düşük load’da %85 atıl. Autoscale 10.000-100.000 RU/s’e geçince fatura %42 düştü.
Multi-Region Write: Active-Active Topology
Müşteri 4 lokasyonda yazıyor. Single-write region (West Europe) tek noktada bottleneck + cross-continent latency. Multi-region write açıldı:
az cosmosdb update
--resource-group rg-iot
--name cosmos-iot-prod
--enable-multiple-write-locations true
--locations regionName=westeurope failoverPriority=0
--locations regionName=germanywestcentral failoverPriority=1
--locations regionName=uaenorth failoverPriority=2
--locations regionName=southeastasia failoverPriority=3
Her bölgedeki client kendi en yakın region’ına yazıyor. Latency 180 ms → 12 ms.
Conflict resolution: timestamp-based (last writer wins) tercih edildi. IoT telemetry data’da conflict çok nadir, son yazılan değer doğru.
Consistency Level: Performance vs Consistency Trade-off
| Level | Consistency | Latency | RU cost |
|---|---|---|---|
| Strong | Anlık tüm region’lar tutarlı | Yüksek | 2x |
| Bounded staleness | K versiyon veya T zaman geride | Orta | 2x |
| Session | Kullanıcı kendi yazdığını okuyor | Düşük | 1x (default) |
| Consistent prefix | Sıralı ama gecikmeli | Düşük | 1x |
| Eventual | En yüksek perf, en zayıf consistency | En düşük | 1x |
IoT müşterimizde Eventual consistency yeterli — telemetry data, bir saniyelik gecikme business etki yaratmaz. Default Session’dan Eventual’a geçiş %18 RU tasarrufu.
TTL: Otomatik Veri Silme ile Storage Maliyetini Düşürme
Detail telemetry 90 gün sonra silinir, sadece aggregated data kalır. Container TTL = 7.776.000 saniye (90 gün), document’larda _ts field’ı bazlı otomatik temizlik.
az cosmosdb mongodb collection update
--account-name cosmos-iot-prod
--resource-group rg-iot
--database-name iot
--name telemetry
--idx '[{"key":{"keys":["_ts"]},"options":{"expireAfterSeconds":7776000}}]'
Storage 480 TB’dan 180 TB’a düştü, aylık $7.200 storage tasarrufu.
Index Optimization
Default’ta tüm property’ler index’e dahil. Çoğu IoT field’ı asla query’de WHERE/ORDER BY’a girmiyor. Selective indexing:
{
"indexingMode": "consistent",
"includedPaths": [
{"path": "/partitionKey/?"},
{"path": "/ts/?"},
{"path": "/deviceId/?"},
{"path": "/value/?"}
],
"excludedPaths": [
{"path": "/metadata/*"}, // metadata içi tüm field'lar excluded
{"path": "/raw/*"}
]
}
Index storage %78 azaldı, write RU %35 azaldı.
Change Feed ile Real-time Stream Processing
Cosmos DB Change Feed ile her yeni document Azure Functions / Stream Analytics’e push. Müşteri kullanım: anomaly detection (sensör değeri normal range dışı → alert).
[FunctionName("AnomalyDetector")]
public static async Task Run(
[CosmosDBTrigger(
databaseName: "iot",
containerName: "telemetry",
Connection = "CosmosDBConnection",
LeaseContainerName = "leases",
CreateLeaseContainerIfNotExists = true)]
IReadOnlyList<TelemetryDoc> input,
ILogger log)
{
foreach (var doc in input)
{
if (doc.Value > doc.Threshold)
await SendAlert(doc.DeviceId, doc.Value);
}
}
Sahada Düşülen Üç Tuzak
- Partition key’i değiştirmeye çalışmak: Mevcut container’da partition key değiştirilemiyor. Yeni container oluşturup migrate gerekiyor. İlk seferde doğru tasarla.
- Strong consistency’i default tutmak: 2x RU. Real ihtiyaç incelenmeden Strong seçilmiş, bütçe heba.
- Cross-partition query yapmak: SELECT * FROM c WHERE c.deviceId = X (deviceId partition key değilse) tüm partition’ları tarar. Partition key WHERE clause’a koy.
Sonuçlar
| Metrik | Önceki | Sonraki |
|---|---|---|
| Aylık fatura | $34.000 | $11.200 |
| P99 read latency | 180 ms | 12 ms |
| P99 write latency | 240 ms | 18 ms |
| Storage | 480 TB | 180 TB |
| Throttle (429) rate | %2.3 | %0.04 |
CloudSpark olarak Cosmos DB partition key tasarımı, multi-region topology, RU/s optimization, MongoDB migration ve performance tuning projeleri için danışmanlık veriyoruz.



