GİB Mükellef Sorgulama Servisi: ~200 MB RAM ile Milyonlarca Kaydı Yönetin
Açık Kaynak28 Şubat 202618 dk

GİB Mükellef Sorgulama Servisi: ~200 MB RAM ile Milyonlarca Kaydı Yönetin

GİB e-Fatura ve e-İrsaliye mükellef listelerini senkronize eden ve sorgulatan açık kaynak .NET mikroservis. Stream tabanlı XML parsing, PostgreSQL Materialized View ile sıfır kesinti, delta sync, HMAC auth ve webhook desteği.

Mersel Açık Kaynak Ekibi

Yazar

GİB Mükellef Sorgulama Servisi

Türkiye'de e-Fatura ve e-İrsaliye sistemlerinde mükellef sorgulama ihtiyacı hemen her uygulamada tekrar eden bir problemdir. GİB'in yayınladığı PK/GB XML listeleri milyonlarca kaydı barındırır ve bu verileri her uygulamanın kendi bünyesinde yönetmesi ciddi bir teknik yük oluşturur.

gib-user-list bu sorunu bir kez çözüp tüm iç uygulamalarınıza sunma felsefesiyle tasarlanmıştır.

GitHub'da Açık Kaynak

gib-user-list — .NET 9, PostgreSQL, EF Core 9 üzerine inşa edilmiş, MIT lisanslı açık kaynak proje. github.com/mersel-os/gib-user-list

Kurulum, yapılandırma veya production ortamına taşıma konusunda desteğe mi ihtiyacınız var? Ekibimizle iletişime geçin — projenizi birlikte hayata geçirelim. Tüm açık kaynak projelerimiz için açık kaynak sayfamızı ziyaret edin.

Temel Avantajlar

AvantajDetay
~200 MB RAMStream tabanlı XML parsing ile milyonlarca kaydı düşük bellekle işler
Sıfır KesintiPostgreSQL Materialized View ile güncelleme sırasında API kesintiye uğramaz
DB BağımsızUygulamalarınız MSSQL, MongoDB, Oracle kullanıyor olabilir — fark etmez
Ultra Hızlı SorgulamaVKN/TCKN sorguları in-memory cache'ten sub-millisaniye
Her Platformda ÇalışırKubernetes, Linux systemd, Docker Compose veya Windows Servisi
Hazır .NET SDKNuGet paketi ile tek satırda entegre, HMAC otomatik

Tipik Kullanım Senaryosu

Bu servis, doğrudan dış dünyaya açılmak yerine internal mikroservis olarak tasarlanmıştır:

Mimari Akış
Dış Dünya / Kullanıcılar
Sizin e-Dönüşüm Backend Servisiniz
MSSQL, MongoDB, Oracle — fark etmez
internal HTTP / JSON
bu servis
GibUserList API ×N instance
yatay ölçeklendirilebilir
PostgreSQL
Materialized View ile ultra hızlı okuma

Kendi uygulamanızda mükellef tablosu tutmanıza, GİB XML'lerini parse etmenize gerek kalmaz. Backend servisiniz bu internal mikroservise bir HTTP çağrısı yapar, sonucu alır ve kendi iş mantığında kullanır.

Mikroservis mimarisinde API katmanını dilediğiniz kadar yatay ölçeklendirebilirsiniz. Her instance aynı Materialized View'dan okuduğu için performans sabit kalır.

Mimari

Proje Clean Architecture prensiplerine uygun olarak katmanlı bir yapıdadır:

Proje Yapısı
MERSEL.Services.GibUserList
├── src/
├── Domain/Entity, value object, enum (sıfır dış bağımlılık)
├── Application/CQRS, validation, arayüzler
├── Infrastructure/EF Core, caching, GİB servisleri, metrikler
├── Web.Api/Minimal API, auth, Scalar UI
├── Worker.Updater/Bağımsız sync job
└── Client/.NET HTTP istemci kütüphanesi
├── tests/
├── docker/
├── monitoring/Prometheus + Grafana
└── k8s/Kubernetes manifest'leri
BileşenTeknoloji
Runtime.NET 9
VeritabanıPostgreSQL 17
ORMEF Core 9
APIMinimal API
MetriklerOpenTelemetry + Prometheus
TestxUnit + Testcontainers
ContainerDocker
OrkestrasyonKubernetes (CronJob + Deployment)

API ve Worker ayrı proseslerdir. API sürekli çalışır ve sorguları yanıtlar; Worker periyodik olarak tetiklenir, GİB verilerini günceller ve kapanır. Bu ayrım sayesinde güncelleme sırasında API performansı etkilenmez ve Worker'ın kullandığı bellek işi bitince tamamen serbest kalır.

Hızlı Başlangıç

Docker Compose ile

cd docker
docker compose up -d
ServisAdres
PostgreSQL 17localhost:5432
APIhttp://localhost:8080
Scalar API Dokümantasyonuhttp://localhost:8080/scalar/v1
Sağlık Kontrolühttp://localhost:8080/health
WorkerÇalışır, verileri senkronize eder, çıkar

Monitoring Stack ile

cd monitoring
export POSTGRES_PASSWORD=gizli-sifre
export GRAFANA_ADMIN_PASSWORD=grafana-sifre
docker compose up -d
ServisAdres
APIhttp://localhost:8080
Prometheushttp://localhost:9090
Grafanahttp://localhost:3000
Metriklerhttp://localhost:8080/metrics

Senkronizasyon Mekanizması

Worker'ın senkronizasyon akışı 12 adımdan oluşur:

  1. GİB'den PK + GB ZIP dosyalarını paralel indir (retry destekli)
  2. ZIP'ten XML dosyalarını çıkar
  3. XmlReader ile akış tabanlı parse — tüm listeyi belleğe yüklemez
  4. PostgreSQL COPY protokolü ile geçici tablolara toplu yaz (25K batch)
  5. Advisory Lock — eş zamanlı sync çalışmasını engeller
  6. Diff Enginecontent_hash ile değişiklik tespiti (added/modified/removed)
  7. Safe removal guard — toplam silme yüzdesi eşik aşarsa silme atlanır
  8. REFRESH MATERIALIZED VIEW CONCURRENTLY (retry destekli, maks 3 deneme)
  9. Belge türüne göre XML.GZ arşiv üret (PK+GB birleşik)
  10. Eski arşiv dosyalarını retention'a göre temizle
  11. sync_metadata güncelle
  12. Webhook bildirimi gönder, çık — ~200 MB RAM tamamen serbest kalır

Neden Stream Tabanlı?

GİB'in mükellef XML listeleri milyonlarca kayıt içerir. Tüm dosyayı belleğe yükleyip XmlDocument ile deserialize etmek gigabyte'larca RAM tüketir. Bu servis XmlReader ile tek kayıt tek kayıt okur ve her 25.000 kayıtta PostgreSQL'e COPY ile yazar. Toplam bellek tüketimi ~200 MB civarında kalır.

Materialized View ile Sıfır Kesinti

REFRESH MATERIALIZED VIEW CONCURRENTLY PostgreSQL'e özgü güçlü bir yetenektir: eski veri okunmaya devam ederken arka planda yeni veri hazırlanır, tamamlandığında atomik olarak değiştirilir.

Tablo / NesneTürAçıklama
e_invoice_gib_usersAna tabloe-Fatura mükellefleri (content_hash ile diff)
e_despatch_gib_usersAna tabloe-İrsaliye mükellefleri
gib_user_changelogChangelogDeğişiklik kayıtları: added, modified, removed
mv_e_invoice_gib_usersMaterialized ViewAPI buradan okur — sıfır kesinti
mv_e_despatch_gib_usersMaterialized ViewAPI buradan okur
sync_metadataMetadataSon sync zamanı, durum, hata özeti, sayılar

MV yenileme başarısız olursa 5 saniye aralıklarla 2 kez daha denenir; tüm denemeler başarısız olursa kritik webhook bildirimi gönderilir. MSSQL Server'da bu davranışın doğrudan karşılığı yoktur.

API Endpoint'leri

Sorgulama

MetodYolAçıklama
GET/api/v1/einvoice/{identifier}VKN/TCKN ile e-Fatura mükellefi sorgula
GET/api/v1/einvoice?search=...&page=1&pageSize=20e-Fatura mükellefi ara
POST/api/v1/einvoice/batchToplu VKN/TCKN sorgula (maks 100)
GET/api/v1/edespatch/{identifier}VKN/TCKN ile e-İrsaliye mükellefi sorgula
GET/api/v1/edespatch?search=...e-İrsaliye mükellefi ara
POST/api/v1/edespatch/batchToplu e-İrsaliye sorgula (maks 100)

Değişiklik Takibi (Delta Sync)

MetodYolAçıklama
GET/api/v1/einvoice/changes?since=...&until=...e-Fatura değişiklikleri
GET/api/v1/edespatch/changes?since=...&until=...e-İrsaliye değişiklikleri

Her kayıt changeType alanı içerir: added, modified veya removed. Retention süresi dışındaki istekler 410 Gone döner — bu durumda tüketici tam listeyi yeniden indirmelidir.

Arşiv (Tam Liste)

MetodYolAçıklama
GET/api/v1/einvoice/archives/latestEn son e-Fatura tam listesi (XML.GZ)
GET/api/v1/edespatch/archives/latestEn son e-İrsaliye tam listesi (XML.GZ)

Durum ve Sağlık

MetodYolAçıklama
GET/api/v1/statusSenkronizasyon durumu, hata özeti, mükellef sayıları
GET/healthSağlık kontrolü (PostgreSQL, sync freshness, archive storage)
GET/metricsPrometheus metrikleri

Kullanım Örnekleri

# VKN ile e-Fatura mükellefi sorgula
curl http://localhost:8080/api/v1/einvoice/1234567890

# Ünvana göre arama
curl "http://localhost:8080/api/v1/einvoice?search=MERSEL&page=1&pageSize=20"

# Delta sync — son 1 saatteki değişiklikler
curl "http://localhost:8080/api/v1/einvoice/changes?since=2026-02-27T00:00:00Z"

# Senkronizasyon durumunu kontrol et
curl http://localhost:8080/api/v1/status

Tüm sorgu ve arşiv endpoint'leri yanıtta X-Last-Sync-At başlığını döner:

X-Last-Sync-At: 2026-02-19T03:05:12.0000000+03:00

Tüketici Senkronizasyon Protokolü

Tüketicilerin mükellef listesini verimli şekilde güncel tutması için bir protokol sunulur:

Tüketici Senkronizasyon Protokolü
İlk Başlatma
/archives/latest
Tam XML.GZ
Tam listeyi işle
since = şimdi
/changes?since=...
Delta JSON
added / modified / removed
Lokal veriyi güncelle
periyodik · her 30dk
410 Gone? → bootstrap
  1. Bootstrap: /api/v1/einvoice/archives/latest ile tam listeyi XML.GZ olarak indir
  2. Delta Takibi: Periyodik olarak /changes?since=... ile değişiklikleri sorgula
  3. Re-bootstrap: 410 Gone dönerse retention süresi dışına düşülmüş demektir — tam listeyi yeniden indir

HMAC-SHA256 Kimlik Doğrulama

İki mod desteklenir:

Devre dışı (varsayılan): Tüm istekler anonim geçer.

Authentication__Enabled=false

HMAC aktif: Her istemci AccessKey + SecretKey çifti ile tanımlanır.

Authentication__Enabled=true
Authentication__TimestampToleranceSeconds=300
Authentication__Clients__0__AccessKey=belgehub-prod
Authentication__Clients__0__SecretKey=gizli-anahtar-buraya
Authentication__Clients__0__Name=BelgeHub
BaşlıkAçıklama
X-Access-Keyİstemcinin public erişim anahtarı
X-TimestampUnix epoch (saniye)
X-SignatureHMAC-SHA256(SecretKey, "{METHOD}\n{PathAndQuery}\n{Timestamp}")

Sunucu tarafında timestamp toleransı (varsayılan 5 dk) ve sabit-zaman imza karşılaştırması uygulanır.

Webhook Bildirimleri

Kritik olaylar için Slack ve/veya HTTP webhook desteği:

Webhooks__Enabled=true
Webhooks__Slack__Enabled=true
Webhooks__Slack__WebhookUrl=https://hooks.slack.com/services/T.../B.../xxx
Webhooks__Slack__Channel=#gib-sync-alerts
OlayÖnemAçıklama
SyncCompletedBilgiSenkronizasyon başarıyla tamamlandı
SyncFailedKritikSenkronizasyon başarısız oldu
SyncPartialUyarıAdvisory lock çakışması veya arşiv uyarıları
RemovalGuardTriggeredUyarıGüvenli silme koruyucusu toplu silmeyi engelledi
MvRefreshFailedKritikMaterialized view yenileme başarısız
HealthStatusChangedDeğişkenSağlık kontrolü durumu değişti

HTTP webhook'ta Secret tanımlıysa gövdenin HMAC-SHA256 imzası X-Webhook-Signature: sha256=... başlığında gönderilir. Her kanal hangi olayları alacağını NotifyOn ile bağımsız olarak seçebilir.

Webhook gönderim hataları ana iş akışını asla engellemez — yalnızca loglanır. Slack bildirimleri önem derecesine göre renklendirilmiş Block Kit mesajları olarak gönderilir.

Güvenli Silme Koruyucusu

Senkronizasyon sırasında silinecek mükellef oranı MaxAllowedRemovalPercent eşiğini aşarsa (örn. GİB'den hatalı/boş liste gelmesi durumunda) silme işlemi otomatik olarak atlanır ve webhook bildirimi gönderilir.

GibEndpoints__MaxAllowedRemovalPercent=10    # Güvenli silme eşiği (%)

Bu, veri kaybını önleyen kritik bir koruma mekanizmasıdır. GİB'den hatalı veya boş bir liste gelmesi durumunda milyonlarca mükellef kaydının silinmesi engellenir.

Önbellekleme

Caching__Provider=Memory       # "Memory" (varsayılan) veya "Redis"
Caching__DefaultTtlMinutes=60
Caching__RedisConnectionString=localhost:6379

Yalnızca VKN/TCKN ile yapılan doğrudan sorgular (/api/v1/einvoice/{identifier}) önbelleğe alınır. Arama sorguları her zaman veritabanından gelir.

Arşiv Depolama

İki depolama seçeneği:

ProviderYapılandırma
FileSystem (varsayılan)ArchiveStorage__Provider=FileSystem + BasePath=/data/gib-archives
S3 / MinIOArchiveStorage__Provider=S3 + BucketName, Region, ServiceUrl
Proje Yapısı
archives
├── einvoice/
├── einvoice_users_2026-02-17_030000.xml.gz
└── einvoice_users_2026-02-16_030000.xml.gz
└── edespatch/
├── edespatch_users_2026-02-17_030000.xml.gz
└── edespatch_users_2026-02-16_030000.xml.gz

.NET İstemci SDK

Kurulum

dotnet add package MERSEL.Services.GibUserList.Client

DI Kaydı

// Auth devre dışı
builder.Services.AddGibUserListClient(options =>
{
    options.BaseUrl = "https://gib-userlist.example.com";
});

// HMAC kimlik doğrulamalı
builder.Services.AddGibUserListClient(options =>
{
    options.BaseUrl = "https://gib-userlist.example.com";
    options.AccessKey = "belgehub-prod";
    options.SecretKey = "gizli-anahtar-buraya";
});

AccessKey ve SecretKey verildiğinde tüm çıkan istekler otomatik olarak HMAC-SHA256 ile imzalanır.

Kullanım

public class MyService(IGibUserListClient gibuserClient)
{
    public async Task Example(CancellationToken ct)
    {
        // VKN/TCKN ile sorgula
        var user = await gibuserClient
            .GetEInvoiceGibUserAsync("1234567890", ct: ct);

        // Ünvana göre ara
        var results = await gibuserClient
            .SearchEInvoiceGibUsersAsync("MERSEL", ct: ct);

        // Toplu sorgulama (maks 100)
        var batch = await gibuserClient
            .BatchGetEInvoiceGibUsersAsync(
                ["1234567890", "9876543210"], ct);

        // e-İrsaliye mükellefi sorgula
        var despatch = await gibuserClient
            .GetEDespatchGibUserAsync("1234567890", ct: ct);

        // Delta sync
        var changes = await gibuserClient
            .GetEInvoiceChangesAsync(
                since: DateTime.Now.AddHours(-1), ct: ct);

        // Tam mükellef listesi arşivi (bootstrap)
        var archive = await gibuserClient
            .GetLatestEInvoiceArchiveAsync(ct);

        // Senkronizasyon durumu
        var status = await gibuserClient
            .GetSyncStatusAsync(ct);
    }
}

Tüketici Sync Örneği

public async Task SyncLocalDatabase(
    IGibUserListClient client, CancellationToken ct)
{
    try
    {
        var changes = await client.GetEInvoiceChangesAsync(
            since: _lastSyncTime, ct: ct);

        foreach (var change in changes.Data.Changes)
        {
            switch (change.ChangeType)
            {
                case "added":
                case "modified":
                    await UpsertLocalUser(change);
                    break;
                case "removed":
                    await DeleteLocalUser(change.Identifier);
                    break;
            }
        }
        _lastSyncTime = changes.LastSyncAt ?? DateTime.Now;
    }
    catch (GibUserListSyncExpiredException)
    {
        // 410 Gone — tam listeyi tekrar indir
        var archive = await client
            .GetLatestEInvoiceArchiveAsync(ct);
        if (archive.Data is not null)
            await RebuildLocalDatabase(archive.Data);
    }
}

Dağıtım

Kubernetes

kubectl apply -f k8s/api-deployment.yaml
kubectl apply -f k8s/updater-cronjob.yaml
  • API: Deployment + HPA ile yatay ölçeklendirme, sürekli çalışır
  • Worker: CronJob, günde 4 kez çalışır (03:00, 09:00, 15:00, 21:00), bitince kapanır. concurrencyPolicy: Forbid ile aynı anda birden fazla çalışması engellenir.

Linux (systemd + cron)

# API: systemd servisi
sudo systemctl enable --now gibuserlist-api

# Worker: crontab ile periyodik tetikleme
echo "0 3 * * * /opt/gib-userlist/Worker.Updater" | sudo crontab -

Docker Compose

cd docker
docker compose up -d

PostgreSQL, API ve Worker tek komutla ayağa kalkar.

Observability

Prometheus Metrikleri (/metrics)

MetrikTipAçıklama
gibuserlist_queries_totalCounterToplam sorgu sayısı
gibuserlist_queries_duration_millisecondsHistogramSorgu süresi (ms)
gibuserlist_sync_totalCounterSenkronizasyon sayısı
gibuserlist_sync_duration_secondsHistogramSenkronizasyon süresi
gibuserlist_sync_users_processed_totalCounterİşlenen kullanıcı sayısı
gibuserlist_cache_hits_totalCounterÖnbellek isabet sayısı
gibuserlist_cache_misses_totalCounterÖnbellek ıskalama sayısı
gibuserlist_auth_requests_totalCounterAuth isteği sayısı
gibuserlist_sync_changes_totalCounterTespit edilen değişiklik sayısı
gibuserlist_sync_removal_skipped_totalCounterGüvenli silme koruyucusu tetiklenme
gibuserlist_mv_refresh_duration_secondsHistogramMV yenileme süresi
gibuserlist_users_einvoice_countGaugeAktif e-Fatura mükellef sayısı
gibuserlist_users_edespatch_countGaugeAktif e-İrsaliye mükellef sayısı

Grafana Dashboard

monitoring/ klasöründe hazır dashboard, 7 bölüm, 25+ panel:

  • Genel Bakış — Toplam sorgu, hata oranı, ortalama süre, cache hit oranı
  • Sorgu Performansı — Rate, p50/p95/p99, belge türü dağılımı
  • Sync Job — İşlem sayısı, süre, kullanıcı sayısı
  • Cache — Hit/miss rate, hit oranı
  • Kimlik Doğrulama — İstemci bazında sorgu rate, başarısızlık nedenleri
  • HTTP İstekleri — Status code dağılımı, response time
  • .NET Runtime — GC heap, thread pool, bellek

Sonuç

gib-user-list, e-Dönüşüm uygulamalarının en yaygın ihtiyaçlarından biri olan mükellef sorgulama problemini bir kez çözüp tüm altyapınıza sunar. Stream tabanlı parsing ile düşük bellek kullanımı, Materialized View ile sıfır kesinti güncelleme, delta sync ile verimli tüketici protokolü ve kapsamlı observability ile production-grade bir çözümdür.

Kendi uygulamanızda mükellef tablosu tutmanıza, GİB XML'lerini parse etmenize, güncelleme job'ları yazmanıza gerek kalmaz.

Kurulum ve Entegrasyon Desteği

GİB mükellef sorgulama servisini kurmak, Kubernetes'e deploy etmek veya mevcut e-Dönüşüm altyapınıza entegre etmek için uzman ekibimizden destek alın.

Bize Ulaşın
Bu yazıyı paylaşın

Mersel'i Deneyin

12+ yıllık deneyimimizle e-Dönüşüm altyapınızı hazırlayalım.

İletişime Geçin