← Vaka Çalışmalarına Dön (Dosya 02)
2024–2025
Sapmapayı: Soğuk Zincir Uyum Takibi ve Sıfır Kopyalama Raporlama
TimescaleDB, Apache Arrow Zero-Copy IPC akışı, X.509 e-İmza ve RFC 3161 Zaman Damgası
Yüz binlerce telemetri satırında sıfır kopyalama aktarımı · 5070 Sayılı Kanun Uyumlu e-İmza
Yığın: TimescaleDB · Apache Arrow (IPC) · C# .NET 10 · X.509 / PKCS#11 · RFC 3161 Timestamp · WebSocket
1. Bağlam
Sapmapayı; aşı, ilaç ve bozulabilir gıda depolarındaki soğuk zincir gereksinimlerini denetleyen kurumsal bir uyum (compliance) platformudur. Sensörlerden gelen sıcaklık ve nem verilerini periyodik olarak analiz eder, kritik eşik sapmalarını tespit eder ve yasal olarak bağlayıcı e-imzalı denetim raporları üretir.
2. Problem
Sistem iki ana tıkanıklıkla karşılaştı:
- Büyük Zaman Serisi Sorgularında Tarayıcı Donması: Bir deponun 30 günlük sıcaklık geçmişi sorgulandığında ~250.000 veri noktası dönüyordu. Bunu geleneksel JSON formatında serileştirip tarayıcıya yollamak ~25MB payload yaratıyor ve JavaScript
JSON.parse()ana iş parçacığını (main thread) 800ms’den fazla kilitliyordu. - Yasal Delil Niteliği: Raporların sonradan değiştirilmediğini kanıtlamak için her raporun resmi X.509 nitelikli e-imza ve RFC 3161 zaman damgası ile damgalanması şarttı.
3. Mimari ve Veri Yolu
[ TimescaleDB Hypertables ]
│ Continuous Aggregate View
▼
┌────────────────────────────────────────────────────────┐
│ .NET Backend: Arrow RecordBatch Streamer │
│ ┌───────────────────┐ ┌───────────────────────┐ │
│ │ Npgsql Binary │───────►│ Arrow RecordBatch │ │
│ │ Raw Buffer Reader │ │ IPC Streamer │ │
│ └───────────────────┘ └──────────┬────────────┘ │
└─────────────────────────────────────────┼──────────────┘
│ Zero-Copy IPC Stream (Binary)
▼
┌────────────────────────────────────────────────────────┐
│ Frontend & Canvas / WebGL │
│ Tarayıcı: Arrow Table.from() -> Float64Array (0ms parse)│
└────────────────────────────────────────────────────────┘
│ Onaylanan Rapor
▼
┌────────────────────────────────────────────────────────┐
│ Rapor Mühürleme Servisi │
│ ┌───────────────────┐ ┌───────────────────────┐ │
│ │ QuestPDF Engine │───────►│ PKCS#11 Worker Pool │ │
│ │ Ham PDF Çıktısı │ │ TÜBİTAK Zaman Damgası │ │
│ └───────────────────┘ └───────────────────────┘ │
└────────────────────────────────────────────────────────┘
4. Kararlar ve Elenen Alternatifler
- Apache Arrow IPC seçildi, JSON REST API elendi: JSON payload’larındaki alan adı tekrarı ve string parse maliyeti yüksek veri noktalarında kabul edilemez gecikme yaratıyordu. Arrow sütunsal (columnar) bellek düzeni sayesinde, veritabanından okunan ikili dizi doğrudan tarayıcının
Float64Arraybelleğine kopyalamasız (zero-copy) bağlandı. - Asenkron HSM/E-İmza havuzu kuruldu, senkron çağrılar elendi: E-imza donanım güvenlik modülleri (HSM) seri çalışmaya meyillidir ve istek başına 300–700ms süre harcar. Senkron API çağrıları web iş parçacıklarını tükettiği için imzalama süreçleri arka planda izole bir kuyruk havuzuna aktarıldı.
- TimescaleDB Continuous Aggregates seçildi, MongoDB elendi: 1 dakikalık sensör verilerinden saatlik min/max/ortalama hesaplamak için her seferinde ham tabloyu taramak yerine, diskte otomatik hesaplanan continuous aggregate görünümleri kullanıldı.
5. Gerçek Kod: Arrow RecordBatch İkili Akışı
Aşağıdaki .NET parçası, zaman serisi verisini JSON serileştirme döngüsüne sokmadan doğrudan Apache Arrow RecordBatch IPC akışına dönüştürür:
using Apache.Arrow;
using Apache.Arrow.Ipc;
using System.IO.Pipelines;
public async Task StreamTelemetryArrowAsync(
long deviceId,
DateTimeOffset start,
DateTimeOffset end,
PipeWriter outputWriter,
CancellationToken ct)
{
var schema = new Schema.Builder()
.Field(new Field("timestamp", new TimestampType(TimeUnit.Millisecond, "UTC"), false))
.Field(new Field("temperature", new DoubleType(), false))
.Field(new Field("humidity", new DoubleType(), false))
.Build();
using var arrowWriter = new ArrowStreamWriter(outputWriter.AsStream(), schema);
await using var conn = await _dataSource.OpenConnectionAsync(ct);
await using var cmd = conn.CreateCommand();
cmd.CommandText = @"
SELECT bucket AS timestamp, avg_temp, avg_hum
FROM chiller_hourly_summary
WHERE device_id = $1 AND bucket BETWEEN $2 AND $3
ORDER BY bucket ASC;";
// Doğrudan bellek bloklarına aktarım - Sıfır JSON tahsisi
var batch = await ReadRecordBatchFromDbAsync(cmd, schema, ct);
await arrowWriter.WriteRecordBatchAsync(batch, ct);
await outputWriter.FlushAsync(ct);
}
6. Sonuç ve Ölçülebilir Çıktılar
- Ağ Yükü: 250.000 satırlık telemetri seti, JSON formatında
24.8MB iken Apache Arrow IPC ile%87 tasarruf).~3.1MBseviyesine indi ( - Tarayıcı CPU Tıkanıklığı:
JSON.parseişleminde görülen 750–900ms’lik UI donması, Arrow typed array doğrudan eşlemesi ile<15msseviyesine çekildi. - Yasal Güvenlik: Üretilen her PDF rapor, 5070 sayılı Elektronik İmza Kanunu’na tam uyumlu biçimde nitelikli elektronik sertifika ve TÜBİTAK Kamu SM RFC 3161 zaman damgasıyla mühürlendi.
- Doğrulanan Ölçek: Günlük 500K+ işlenen telemetri satırı;
<15mstarayıcı IPC ayrıştırma gecikmesi.
7. Neyi Farklı Yapardım?
- Raporlama servisindeki ilk denemede HTML-to-PDF kütüphaneleri (Puppeteer/Chromium headless) kullanılmıştı. Headless Chrome sunucuda konteyner başına 400MB+ RAM harcıyor ve eşzamanlı isteklerde çökmelere neden oluyordu. Sistemi C# yerel PDF çizim motoru (
QuestPDF) ile baştan yazarak bellek ayak izini 15MB’a ve render süresini 10 katına indirdim. Bu kararı en baştan vermeliydim.