TRAFİK POLİSİ OPERASYON VE TAKİP SİSTEMİ ·GİRİŞ

Sahayı Gören,
Doğru Görevlendiren
Operasyon Merkezi

Trafik polislerinin konumu saniyeler içinde değişirken; olayın nerede olduğunu, en yakın uygun ekibin kim olduğunu ve o ekibin gerçekten müsait olup olmadığını tek ekranda cevaplayan; görevlendirmeyi iki operatör aynı anda denese bile tutarlı tutan sistem.

Java 21 · Spring Boot 3.4 PostgreSQL 16 · Flyway Redis 7 Apache Kafka SSE React 18 · MUI · RTK Query
32 slayt · 4 aşama 10 canlı sahne 7 elle denenebilir demo Ölçülerek doğrulandı
Nasıl okunmalı

Dört aşamada: sahadan ölçeğe

Önce sistemin ne işe yaradığını görürüz. Sonra onu ayakta tutan mimari kararlara ineriz. Ardından "iki operatör aynı anda ne yaparsa ne olur" sorusuyla yüzleşiriz. En sonunda sistemi bugünkünün on katı yüke hazırlayan kararları tartışırız. Soldaki renkli şerit her an hangi aşamada olduğunuzu söyler.

1 · OPERASYON Problem, roller, uçtan uca akış ve ekranlar
2 · MİMARİ Paket düzeni, sözleşme, veri modeli, indeksler
3 · DOĞRULUK Yarış koşulu, kilitleme, sürüm kontrolü, güvenlik
4 · ÖLÇEK SSE, Kafka, virtual thread, idempotency, hız sınırı
Kod tabanı
0
Java dosyası
Arayüz
0
TypeScript dosyası
Test
0
otomatik test (49+8+34)
Veri
0
günlük konum satırı
01
Aşama 1

Operasyon: sistem ne yapıyor?

Kod yazmadan önce cevaplanması gereken soru: bir trafik operasyon merkezinde gün nasıl geçiyor ve hangi an kritik?

Telsizle yürüyen görevlendirmenin ürettiği somut hatalar
Üç rol, üç farklı yetki sınırı
Konum bildiriminden görevlendirmeye uçtan uca akış
Operatörün gerçekten baktığı ekran
Operasyon canlı sahne

Ekip nerede? Müsait mi? — telsizde cevabı yok

Aşağıdaki sahne gerçek bir vardiyayı temsil ediyor: solda olaylar birikiyor, sağda ekipler sahada. Operatör kimin nerede olduğunu bilmediği için görevlendirmeyi tahminle yapıyor.

Vardiya simülasyonu 0 hatalı görevlendirme

Çift görevlendirme

Aynı ekip iki ayrı olaya yönlendirilir. İkinci olay ekip gelmediği için bekler; kimse fark etmez.

Yanlış ekip seçimi

Olaya 12 km uzaktaki ekip gönderilirken 800 m ötedeki müsait ekip beklemede kalır.

Geriye dönük iz yok

"Bu ekip saat 14:20'de neredeydi?" sorusunun cevabı hiçbir yerde tutulmaz.

Problem bir yazılım problemi değil, bir görünürlük problemi. Operatörün elinde konum yok, durum yok, geçmiş yok. Sistemin ilk işi karar vermek değil; karar verebilmek için gereken üç bilgiyi aynı anda göstermek.
Sistemin sözü

Üç soru, tek ekranda, canlı

Sistem yeni bir iş akışı dayatmaz. Operatörün zaten sorduğu üç soruyu, sayfa yenilemeden güncellenen tek bir ekranda cevaplar.

01

Olay nerede?

Olay, öncelik rengiyle haritaya düşer. Konumu, türü ve açılış zamanı kayıt altındadır.

  • Kaza · tıkanıklık · ihlal · yol çalışması
  • Dört öncelik seviyesi
02

En yakın uygun ekip kim?

Sahadaki tüm personel harita üzerinde. İşaretçi rengi görev durumunu gösterir.

  • Konum 3 saniyede bir güncellenir
  • Geçmiş rota sorgulanabilir
03

Gerçekten müsait mi?

Görevlendirme anında kilitle doğrulanır. Meşgul bir ekip ikinci bir olaya atanamaz.

  • Veritabanı seviyesinde garanti
  • Kritik olay öncelik devralabilir
Tasarımın omurgası: "müsait mi?" sorusunun cevabı ekranda gösterilen bir bilgi değil, görevlendirme anında veritabanının zorladığı bir kural. Ekranda gördüğünüz eskimiş olabilir; kural eskiyemez.
Yetkilendirme

Üç rol, keskin sınırlar

Yetki kontrolü uç nokta seviyesinde yapılır ve API sözleşmesinin görünür parçasıdır. Yeni bir uç nokta eklendiğinde yetki kuralı yazılmadıysa bu, sözleşme dosyasında gözle görülür.

RolPersonelOlayGörevlendirmeGörüntüleme
ADMIN
sistem yöneticisi
Oluştur · Güncelle · PasifleştirTam yetki Tam yetkiTümü
OPERATOR
merkez operatörü
YazamazOluştur · Güncelle Ata · Kaldır · Durum değiştirTümü
VIEWER
izleyici
YazamazYazamaz YazamazTümü
PoliceOfficerClient.java sözleşme
/** Yeni personel oluşturur. Sicil numarası benzersizdir. */
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
@Idempotent
ResponseEntity<PoliceOfficerDetail> create(
    @RequestBody @Valid CreatePoliceOfficerRequest request);
Neden anotasyonlar arayüzde? Bir uç noktanın yolu, yetkisi, doğrulaması ve ne yaptığı tek dosyada okunur. Controller sınıfı yalnızca @Override içerir; iş kuralı oraya sızdırmak yapının kendisiyle çelişir.

Spring MVC'nin arayüzdeki anotasyonları işlediği varsayılmadı: her parametre tipi çalışan sisteme karşı ayrı ayrı denendikten sonra sekiz controller'ın tamamına uygulandı.
Operasyon canlı akış

Konum bildiriminden görevlendirmeye

Aynı olay, altı durakta izleniyor. Her durak farklı bir teknolojiye denk geliyor; sunumun geri kalanı bu durakları tek tek açacak.

İstek yolu 1 · Konum bildirimi
Arayüz

Operatörün baktığı ekran

Aşağıdaki pano canlı çalışıyor: işaretçiler hareket ediyor, başka bir operatörün açtığı olay bildirim olarak düşüyor. Ekran tasarımı MUI v6 üzerine kurulu; renk kodlaması tüm ekranlarda aynı.

localhost:3000/dashboard
Yeni olay
Aktif olay
14
Müsait
126
Görevde
78
Kritik
3
Aktif olaylar
Zincirleme kazaEVT-2026-001042 · D-100 KRİTİK
Şerit ihlaliEVT-2026-001041 · E-5 YÜKSEK
Yoğun trafikEVT-2026-001039 · Sahil yolu ORTA
Yol çalışmasıEVT-2026-001036 · Merkez DÜŞÜK

Canlı harita

İşaretçi rengi görev durumu, olay rengi öncelik. Renkler TypeScript sabitlerinde tutulur — canvas bağlamı CSS değişkeni çözemez.

İstatistik kartları

Ağır toplama sorguları Redis üzerinde 30 saniye önbelleklenir.

Personel ekranı

240+ kayıt sayfalı listede. Arama girişi geciktirilir, her tuş vuruşunda istek gitmez.

Canlı bildirim

Başka bir operatörün işlemi bu ekrana da düşer. Sayfa yenilemesi yoktur.

02
Aşama 2

Mimari: sistemi ne taşıyor?

Ekranın arkasında hangi kararlar var? Bu aşamada paket düzeninden indeks seçimine kadar her karar gerekçesiyle birlikte.

Teknoloji yığını ve neden bu bileşenler
İş alanına göre paketleme
Günde 7 milyon satırı taşıyan veri modeli
Doğru indeks olmadan çalışmayan sorgular
Bileşenler

Her bileşen bir soruna karşılık geliyor

Projede "ileride lazım olur" diye eklenip kapalı bırakılmış bileşen yok. Her biri çalışır durumda ve neyi çözdüğü ölçülebilir.

Java 21 · Spring Boot 3.4

Virtual thread, record ve sealed interface desteği. Güvenlik, veri erişimi ve izleme tek yapılandırma altında.

PostgreSQL 16

Tablo bölümleme, pg_trgm ve BRIN indeks çekirdekte. Ek ürün gerekmedi.

Redis 7

Tek thread'li yürütme atomik Lua script'e imkân verir. Dört ayrı iş için kullanılıyor.

Apache Kafka

Düzensiz ve yüksek hacimli konum akışını veritabanından ayıran tampon.

Server-Sent Events

Tek yönlü akış için WebSocket'ten az parça: HTTP üzerinde çalışır, yeniden bağlanma protokolde tanımlı.

React 18 · MUI · RTK Query

Sunucu durumu RTK Query önbelleğinde, istemci durumu Redux slice'ta. İki kaynak birbirine kopyalanmaz.

Değerlendirildi, kullanılmadı

  • PostGIS — coğrafi ihtiyaç indeksli enlem/boylam aralık sorgusuyla karşılanıyor. Poligon içi arama gerekirse eklenmeli.
  • WebSocket — akış tek yönlü. Çift yönlü iletişim gerekmediği için ek karmaşıklık.
  • Elasticsearch — arama, ad ve sicil üzerinde alt dize araması. pg_trgm ayrı küme işletmeden karşılıyor.

Redis'in dört işi

  • Önbellek — dashboard istatistikleri, 30 sn
  • Pub/Sub — SSE yayınlarının örnekler arası dağıtımı
  • Hız sınırı — token bucket sayaçları (Lua)
  • Idempotency — tekrarlanan isteklerin tespiti

Dördünü tek bileşenle karşılamak, işletim yükünü dört ayrı ürüne dağıtmaktan düşük tutuyor.

Mimari katmanlara tıklayın

Teknik katmana göre değil, iş alanına göre

Katman bazlı paketlemede tek bir özellik değişikliği beş ayrı klasöre dokunmayı gerektirir. Burada bir özelliğin tüm parçaları tek ağaç altında.

com.trafficops security/ kimlik doğrulama, JWT, yetkilendirme police/ personel yönetimi organization/ birim, takım, referans veri event/ trafik olayları assignment/ olay – personel görevlendirmesi location/ konum bildirimi ve Kafka ingest'i realtime/ SSE yayını ve Redis pub/sub dashboard/ istatistikler common/ hata, idempotency, hız sınırı Her feature paketi aynı iskelete sahiptir: client/ API sözleşmesi (arayüz) controller/ sözleşmenin gerçekleştirimi service/ iş kuralı arayüzü service/impl/ iş kuralı gerçekleştirimi repository/ veri erişimi entity/ JPA varlıkları dto/ istek/yanıt nesneleri (record)
Bir pakete tıklayın. Her paketin ne barındırdığı ve neden ayrı durduğu burada açılır.
Bağımlılık yönü tek taraflı. Controller servise, servis repository'ye bağlıdır; ters yön yoktur. Bunun somut karşılığı konum ingest'i: LocationIngestChannel arayüzünün Kafka ve doğrudan-yazma olmak üzere iki gerçekleştirimi var. Hangisinin çalışacağı tek bir ortam değişkeniyle belirleniyor, çağıran kod hiç değişmiyor.
Veri modeli

Dokuz tablo, tek kritik ilişki

Model küçük. Zorluk tablo sayısında değil, event_assignments üzerindeki tek kuralda: bir personelin aynı anda yalnızca bir aktif görevlendirmesi olabilir.

TabloİçerikİlişkiNot
usersKullanıcı, rol, şifre özetirefresh_tokens 1–nBCrypt cost 12
units / teamsOrganizasyon hiyerarşisibirim → takım → personelReferans verisi migration'da
police_officersSicil, ad, rütbe, görev durumuteams, vehiclesAda göre trigram indeks
vehiclesPlaka, tip, zimmetpolice_officers 1–1
traffic_eventsKod, tür, öncelik, durum, konumevent_assignments 1–nSürüm alanı ile korunur
event_assignmentsRol, durum, zaman damgalarıolay ⟷ personelKısmi UNIQUE indeks burada
police_locationsEnlem, boylam, hız, yön, zamanpolice_officers n–1Aylık bölümlenmiş tablo
Personel
0
demo kurulumunda
Konum aralığı
3 sn
bildirim sıklığı
Günlük büyüme
0
konum satırı
Bu hacim tek parça bir tabloda birkaç ay dayanır. Sonra sorgular değil, önce bakım işlemleri yavaşlar: VACUUM uzar, indeks belleğe sığmaz, eski veriyi silmek saatler sürer. Sonraki slayt bunun çözümü.
Mimari sorguyu çalıştırın

17 bölüm, sorgu yalnızca birine bakıyor

police_locations tablosu recorded_at sütununa göre aylık RANGE bölümlerine ayrılmış. Aşağıda iki sorguyu karşılaştırın.

Bölüm eleme (partition pruning) hazır
Zaman filtreli sorgu, planlama aşamasında ilgisiz bölümleri eler. Gerçek sistemde EXPLAIN çıktısı bunu doğruladı: 17 bölümün 16'sı elendi (Subplans Removed: 16).

Bölüm eleme

"Son 24 saatin rotası" sorgusu yalnızca ilgili ayın bölümünü okur.

Ucuz arşivleme

Eski veriyi silmek milyonlarca satırlık DELETE değil, tek bir DROP TABLE. Saniyeler sürer, tablo şişmez.

Küçük indeks

Her bölüm kendi indeksini taşır; indeksler bellekte tutulabilir boyutta kalır.

Mimari yarışı başlatın

Aramanın çalışması indeks tipine bağlı

Operatör personel adının ortasından arama yapar: LIKE '%yılmaz%'. Standart B-tree indeksi bu deseni kullanamaz. Aşağıda iki plan yan yana koşuyor.

Tam tarama vs trigram GIN hazır
İndeksAlanNeden bu tip
B-tree (bileşik)(police_id, recorded_at DESC) Hem "son konum" hem "rota geçmişi" sorgusunu tek indeksle karşılar.
GIN + pg_trgmad, sicil no Alt dize araması B-tree kullanamaz. Trigram indeksi olmadan her arama tam tablo taraması yapar.
BRINpolice_locations.recorded_at Veri zamana göre sıralı eklendiği için B-tree'nin çok küçük bir kesri boyutunda benzer fayda verir.
Kısmi UNIQUE(police_id) WHERE status='ACTIVE' İş kuralını uygulamaya değil veritabanına yaptırır. Sonraki aşamanın konusu.
03
Aşama 3

Doğruluk: iki operatör aynı anda

Buraya kadar her şey tek kullanıcı varsayımıyla çalışıyordu. Şimdi ikinci operatör geliyor — ve aynı polisi farklı bir olaya atamaya çalışıyor.

Kontrol-sonra-işlem yarışı ve ürettiği bozuk veri
Üç katmanlı savunma ve kilit sırası
Aynı kaydı iki kişi düzenlerse
Kimlik, token rotasyonu ve SSE bileti
Doğruluk kilidi açıp kapatın

Aynı polis, aynı anda, iki olay

İki operatör aynı saniyede "ata" düğmesine basıyor. Kilit kapalıyken iki transaction da "personel müsait" cevabını okuyor ve ikisi de yazıyor.

Eş zamanlı görevlendirme hazır
Kilit kapalıyken deneyin. İki transaction da uygunluk kontrolünden geçer; ikisi de yazar. Sonuç: bir polis iki olaya birden atanmış olur ve sahaya tek ekip gider.
Savunma

Tek çözüm yeterli değil: üç katman

Her katman bir öncekinin atlanabileceği varsayımıyla eklenmiş. Üçüncüsü uygulamaya değil veritabanına dayanan nihai garanti.

01

Satır kilidi

PESSIMISTIC_WRITE ile işlem başlarken olay ve personel satırları kilitlenir. İkinci transaction ilkinin bitmesini bekler ve güncel durumu okur.

02

Sabit kilit sırası

Kilitler daima aynı sırayla alınır: önce olay, sonra personeller kimliğe göre artan sırada. Sıra sabit olmasaydı iki görevlendirme karşılıklı bekleyip kilitlenirdi.

03

Kısmi UNIQUE indeks

Uygulama katmanındaki her kontrol bir hata sonucu atlansa bile veritabanı ikinci aktif görevlendirmeyi reddeder.

V2__indexes_and_partitions.sql nihai garanti
-- Bir personelin ayni anda yalnizca bir AKTIF gorevlendirmesi olabilir.
-- Kismi indeks: kural yalnizca aktif satirlar icin gecerli, kapanmis
-- gorevlendirmeler ayni personel icin sinirsiz sayida birikebilir.
CREATE UNIQUE INDEX ux_assignment_active_officer
    ON event_assignments (police_id)
    WHERE status = 'ACTIVE';
Kilit sırası ve kilitlenme sabit sıra
İyimser kilitleme

Aynı kaydı iki kişi düzenlerse

Görevlendirmede kötümser kilit doğru; ama kayıt düzenlemede her açılan formda satır kilitlemek sistemi kilitler. Burada iyimser kilitleme kullanılıyor.

Sürüm kontrolü olmadan

  • Operatör A olayı açar, açıklamayı düzenler.
  • Operatör B aynı olayı açar, önceliği kritik yapar.
  • A kaydeder. Ardından B kaydeder.
  • B'nin yazdığı, A'nın değişikliğini sessizce siler. Kimse fark etmez.
vs

@Version ile

  • Kayıt okunurken sürüm numarası da alınır.
  • A kaydeder, sürüm 4 → 5 olur.
  • B kaydetmeye çalışır; elindeki sürüm 4.
  • Çakışma hatası döner. Arayüz güncel veriyi yükler, B kararını yeniden verir.
İki kilit tipi, iki farklı soru. Kötümser kilit "bu satıra kimse dokunmasın" der ve çakışmayı önler — kısa, kritik işlemler için. İyimser kilit "dokunulduysa haberim olsun" der ve çakışmayı fark eder — kullanıcının form doldurduğu uzun aralıklar için. Görevlendirmede birincisi, düzenlemede ikincisi kullanılıyor.
Doğruluk parçalara tıklayın

Kısa ömürlü erişim, dönen yenileme

Access token 15 dakika geçerli. Süresi dolduğunda arayüz kullanıcıya hissettirmeden yeniler ve başarısız isteği tekrarlar.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIyIiwicm9sZSI6Ik9QRVJBVE9SIn0.4Xk9_tQm2rLpZ8vN
HEADER
HS256
İmza algoritması. Sunucu bu alana güvenmez; beklediği algoritmayı kendisi dayatır.
PAYLOAD
sub · role · exp
Kullanıcı kimliği, rolü ve geçerlilik sonu. Şifreli değil, yalnızca imzalı — sır taşımaz.
SIGNATURE
HMAC-SHA256
Sunucu sırrıyla üretilir. Payload'daki tek bir karakter değişirse imza tutmaz.

15 dakika

Token çalınsa bile kullanım penceresi dar. Sunucuda oturum tutulmadığı için backend yatay ölçeklenebilir.

Rotasyon

Her yenilemede eski refresh token geçersiz kılınır. Bir token ikinci kez kullanılırsa bu bir sızıntı işaretidir ve zincirin tamamı iptal edilir.

Özetle saklama

Refresh token'lar veritabanında SHA-256 özetiyle durur. Veritabanı okunsa bile doğrudan kullanılamaz.

Küçük ama önemli karar

Tarayıcı başlık gönderemiyorsa token nereye yazılır?

Tarayıcının EventSource API'si özel HTTP başlığı göndermeye izin vermez. Akışı açarken kimlik nasıl taşınacak?

Token'ı URL'e koymak

  • Tarayıcı geçmişine yazılır.
  • Ters proxy ve sunucu erişim kayıtlarına düz metin olarak düşer.
  • Referrer başlığıyla üçüncü taraflara sızabilir.
  • Süresi 15 dakika — bu süre boyunca tüm API için geçerli bir anahtar ortada dolaşır.
seçim

Kısa ömürlü akış bileti

  • POST /api/auth/stream-ticket normal başlıkla çağrılır.
  • Dönen bilet 60 saniye geçerli ve tek kullanımlık.
  • Yalnızca SSE uç noktası için geçerli; diğer API'lere kapı açmaz.
  • Loglara düşse bile kullanılabilir ömrü çoktan bitmiş olur.
Akışın açılışı
# 1) Normal, kimlikli istek — token Authorization basliginda
POST /api/auth/stream-ticket
 { "ticket": "9f2c...a7", "expiresInSeconds": 60 }

# 2) EventSource yalnizca URL alabilir — bilet burada kullanilir
GET /api/realtime/stream?ticket=9f2c...a7
 text/event-stream (acik kalir)
04
Aşama 4

Ölçek: bugünün on katı yük

Sistem çalışıyor. Peki tek sunucu yetmediğinde, saha cihazları toplu veri gönderdiğinde ve aynı istek üç kez ulaştığında ne oluyor?

İkinci sunucu geldiğinde SSE nasıl bozulur
Konum akışını veritabanından ayırmak
Thread başına bağlantı modelinin duvarı
Aynı isteğin ikinci kez gelmesi
Ani yükte kimin geçeceğine karar vermek
Ölçek ikinci sunucuyu açın

İkinci sunucu, sessizce bozulan yayın

Olay 1 numaralı örnekte oluşuyor. Ama operatörün SSE bağlantısı 2 numaralı örnekte — ve haberi olmuyor. Yük dengeleyici eklendiği gün ortaya çıkan, test ortamında görünmeyen hata.

Yayının örnekler arası dağıtımı tek örnek
Tek sunucuda her şey çalışıyor gibi görünür. İkinci sunucuyu ekleyin, sonra Redis kapalıyken olay oluşturun.
Yayını yapan örnek kendi mesajını da kanaldan geri alır — bu kasıtlı. Böylece "yerel yayın" ve "uzak yayın" diye iki ayrı kod yolu oluşmaz; tüm örnekler aynı yolu çalıştırır. Yerel geri dönüşün gecikmesi milisaniyenin altında. Davranış, ikinci bir container ayağa kaldırılarak doğrulandı.
Ölçek yükü artırın

Saha cihazı 40 dakikalık veriyi bir anda gönderirse

Konum bildirimi en yüksek hacimli uç nokta ve akışı düzensiz. Bağlantısı kopan bir cihaz yeniden bağlandığında biriken kayıtları toplu gönderir.

Doğrudan yazma vs Kafka tamponu normal yük
doğrudan · yanıt (model)
kafka · yanıt (model)
meşgul db bağlantısı
tüketici gecikmesi
Sayılar 20 bağlantılık havuz ve ~18 ms'lik tek yazım varsayımıyla kurulmuş bir modeldir; ölçüm değildir.
Yapılandırma

Dört karar, dört gerekçe

Kafka'yı eklemek kolay; doğru yapılandırmak asıl iş. Aşağıdaki dört değer rastgele seçilmedi.

ParametreDeğerGerekçe
Bölüm anahtarıpoliceId Kafka sıralamayı yalnızca bölüm içinde garanti eder. Aynı personelin kayıtları aynı bölüme düşmezse rota sırası karışır ve harita geriye zıplar.
Bölüm sayısı6 Tüketici sayısının (3) üzerinde tutuldu; yatay büyüme için topic'i yeniden oluşturmak gerekmez.
Batch boyutu500 Tek tek yazım yerine toplu yazım. Veritabanına giden gidiş-dönüş sayısı iki mertebe azalır.
Onay moduAckMode.BATCH Grup veritabanına yazılmadan offset ilerlemez. Tüketici çökerse grup yeniden okunur, veri kaybolmaz.
Ölçülerek doğrulandı: 6 bölümlü topic, 3 tüketici, gecikme 0. 8 saniyede 96 yeni satır; elle gönderilen tek bir POST 202 dönüp verilen koordinatlarla veritabanında satır oluşturdu. İzleme için Kafka UI kurulu.
Ve bir itiraf. Kafka yapılandırması uzun süre çalışmıyordu: güvenilen paket listesi bir paket taşımasından sonra eski adı gösteriyordu. Hata sessizdi — mesaj yazılıyor ama hiç işlenmiyordu. Bu, "hazır ama kapalı" bileşenlerin neden tehlikeli olduğunun somut örneği.
Ölçek operatör sayısını artırın

Açık kalan her bağlantı bir thread tutuyor

SSE bağlantıları saatlerce açık kalır. Klasik platform thread havuzunda her açık bağlantı bir thread'i meşgul eder — ve o thread'in tek yaptığı beklemektir.

Platform thread havuzu vs virtual thread platform
Sınırlar

Nerede bilinçli olarak kullanılmadı

Virtual thread her yere serpiştirilecek bir hızlandırıcı değil. Dört yerde bilerek kullanılmadı; gerekçeleri kodda yorum olarak da yazılı.

JPA transaction içinde paralellik

EntityManager thread'e bağlıdır. Transaction içindeki sorguları farklı thread'lere dağıtmak sessiz veri bozulması riski doğurur. Dashboard'un üç sorgusu tek transaction'da sırayla kalır.

CPU yoğun işler

Virtual thread yalnızca bloke eden G/Ç sırasında taşıyıcı thread'i serbest bırakır. Hesaplama süresini kısaltmaz; çekirdek sayısı neyse odur.

Konum simülatörü

60 eş zamanlı yazma çağrısı bağlantı havuzunu doldurup gerçek operatör isteklerini geciktirirdi. Simülatör bilerek sıralı bırakıldı.

synchronized blokları

Java 21'de synchronized içindeki virtual thread taşıyıcıya sabitlenir (pinning) ve kazanç kaybolur. Bu yüzden eş zamanlı koleksiyonlar tercih edildi.

Ve sert bir sınır: veritabanı bağlantı havuzu üst sınır olmaya devam eder. Binlerce virtual thread bağlantı isterse aynı anda yalnızca havuz boyutu kadarı gerçek bağlantı alır. Virtual thread veritabanı işlem kapasitesini artırmaz; yalnızca bekleyen isteğin maliyetini düşürür.
Ölçek düğmeye hızlıca iki kez basın

Operatör çift tıklarsa sahaya iki ekip gider

HTTP'de POST doğası gereği tekrarlanabilir değildir. Ağ zaman aşımı sonrası istemci tekrarı da aynı sonucu doğurur: iki olay kaydı, iki görevlendirme.

Olay oluşturma denemesi Idempotency-Key: KAPALI
Anahtar kapalıyken düğmeye üst üste basın. Her basış yeni bir olay kaydı üretir.
DurumDavranış
İlk istekAnahtar rezerve edilir, istek işlenir, yanıt saklanır.
Aynı anahtar, tamamlanmışSaklanan yanıt döner + Idempotent-Replay: true
Aynı anahtar, işleniyor409 — istemci kısa süre sonra tekrar dener.
Aynı anahtar, farklı gövde409 — gövdenin SHA-256 özeti tutmuyor.
Redis erişilemiyorİstek işlenir (fail-open).

Gövdenin özeti saklanır

Aynı anahtar farklı gövdeyle gelirse sessizce yanlış yanıt dönmek yerine hata verilir. Yanlış yanıt, yanıt vermemekten tehlikelidir.

İki ayrı TTL

Rezervasyon kilidi kısa, saklanan yanıt uzun ömürlü. Sunucu işin ortasında durursa anahtar 24 saat asılı kalmaz.

Anahtar arayüzde sabit

Anahtar form açıldığında üretilir ve başarıya kadar değişmez. Her tıklamada yenilenseydi çift tıklama iki farklı anahtar üretir, koruma hiç çalışmazdı. Bir testle korunuyor.

Ölçek ani yük gönderin

Kova dolu başlar, saniyede damlar

Her istek kovadan bir token alır. Kova boşsa istek 429 ile reddedilir. Kova sabit hızda yeniden dolar.

Token bucket · kural: login hazır
Atomiklik

Sayaç neden Lua script'i içinde?

Token bucket "oku → hesapla → yaz" adımlarından oluşur. Bu üç adım ayrı Redis komutlarıyla yapılırsa iki eş zamanlı istek aynı eski değeri okur ve ikisi de geçer.

rate-limit-token-bucket.lua atomik
-- Redis komutlari tek thread'de calisir: bu script basladiginda
-- araya baska hicbir komut giremez. Dagitik kilide gerek yoktur.
local now = redis.call('TIME')   -- sunucu saati: ornekler
                                 -- arasi saat sapmasi etkilemesin
local tokens = tonumber(bucket[1]) or capacity
local delta  = (now - last) * refill_per_second
tokens = math.min(capacity, tokens + delta)

if tokens >= requested then
  tokens = tokens - requested
  redis.call('HSET', KEYS[1], 't', tokens, 'ts', now)
  return { 1, tokens, capacity, 0 }   -- gecti
else
  return { 0, tokens, capacity, retry_after }
end

Neden token bucket, kayan pencere değil?

Bağlantısı kopan saha cihazı biriken konumları toplu gönderir. Kayan pencere bu meşru ani yükü reddederdi. Token bucket kapasite kadar burst'ü kabul edip yalnızca sürdürülebilir ortalamayı sınırlar.

Redis düşerse ne olur?

İstek geçirilir (fail-open). Hız sınırı uygulamanın kendisi değil bir koruma katmanıdır; acil durum merkezinde tüm trafiği reddetmek korumasız çalışmaktan ağır bir sonuçtur.

Sıfır dolum hızı yazılırsa

Uygulama açılışta hata verir. Aksi hâlde sıfıra bölme sonsuz değer üretir ve sistem sessizce limitsiz çalışır — en tehlikeli hata tipi budur.

İşletim

Limitler koddan değil, ortam dosyasından

Üç kuralın tamamı yeniden derleme gerektirmeden değiştirilebilir. Aynı değişkenler Docker kurulumunda da geçerli.

backend/dev.env
# capacity          -> tolere edilen ANI YUK (burst)
# refill_per_second -> SURDURULEBILIR ortalama hiz
RATE_LIMIT_ENABLED=true

# Genel kural: tum /api/** istekleri
RATE_LIMIT_DEFAULT_CAPACITY=300
RATE_LIMIT_DEFAULT_REFILL_PER_SECOND=100

# Giris — kaba kuvvet korumasi, IP bazli ve bilincli olarak siki
RATE_LIMIT_LOGIN_CAPACITY=10
RATE_LIMIT_LOGIN_REFILL_PER_SECOND=0.1667   # ~10/dk

# Konum bildirimi — en yuksek hacimli uc nokta
RATE_LIMIT_LOCATION_CAPACITY=600
RATE_LIMIT_LOCATION_REFILL_PER_SECOND=50

IDEMPOTENCY_ENABLED=true
IDEMPOTENCY_TTL=24h
SCHEDULER_CONCURRENCY_LIMIT=-1       # -1 = sinirsiz
# capacity=10 iken 14 giris denemesi $ for i in $(seq 14); do curl -s -o /dev/null \ -w "%{http_code} " -X POST .../api/auth/login; done 401 401 401 401 401 401 401 401 401 401 429 429 429 429 # 11. istegin yaniti X-RateLimit-Limit: 10 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1786393693 Retry-After: 4 { "status": 429, "code": "RATE_LIMIT_EXCEEDED", "message": "Istek hizi siniri asildi..." }
Arayüz bu yanıtı tanır ve kullanıcıya teknik kod yerine anlaşılır bir mesaj gösterir. İlk 10 isteğin 401 dönmesi beklenen davranış — kasıtlı olarak yanlış şifre gönderildi.
Dürüstlük bölümü

Yapılmayanlar ve nedenleri

Bir mimari sunumunun en değerli kısmı, kapsam dışında bırakılanları da söylemesidir. Aşağıdakiler eksik değil, bilinçli sınır.

KonuMevcut durumÖnerilen adım
Yük testi Darboğazlar analizle bulunup giderildi, çözümlerin çalıştığı ölçüldü. Hedef ölçekte yük testi koşulmadı. Üretim öncesi konum uç noktası için yük testi
Kafka topolojisiKRaft modunda tek broker. En az üç broker, replikasyon faktörü 3
Hatalı mesajlarSürekli başarısız mesaj yeniden deneme sonrası atlanır. Ayrı hata topic'i (dead letter) ve inceleme akışı
Hız sınırı kurallarıÜç kural koda tanımlı; değerleri ortam değişkeninden. Kural tanımının veritabanından yönetilmesi, rol bazlı kota
Bölüm yönetimiAylık bölümler migration ile oluşturulur. Zamanlanmış görev veya pg_partman
İzlemeActuator metrikleri açık. Merkezî metrik toplama ve alarm
Performans iddiası yok. Bu sunumda "şu kadar hızlandı" biçiminde bir cümle geçmiyor. Söylenen şu: darboğazlar doğru tespit edildi, çözümler uygulandı ve çalıştıkları ölçülerek gösterildi. Sayısal iyileşme iddiası için yük testi gerekir; o yapılmadı.
Özet

Dört aşamada ne kurduk?

1 · Operasyon

Görünürlük problemi. Üç soru tek ekranda, canlı olarak cevaplanıyor.

2 · Mimari

İş alanına göre paketleme, sözleşme arayüzleri, bölümlenmiş konum tablosu, doğru indeks tipi.

3 · Doğruluk

Yarış koşuluna karşı üç katman, sabit kilit sırası, iyimser kilitleme, kısa ömürlü kimlik.

4 · Ölçek

Redis pub/sub, Kafka tamponu, virtual thread, idempotency ve Lua ile atomik hız sınırı.

Tek cümlede: Sahayı canlı gösteren, görevlendirmeyi veritabanı seviyesinde tutarlı tutan ve yükü uygulamanın kritik yolundan çıkaran bir operasyon merkezi kurmayı; her bileşeni çalıştığını ölçerek eklemeyi ve yapılmayanları da açıkça yazmayı.
91 otomatik test 17 bölüm · 4 indeks tipi 3 katmanlı eşzamanlılık savunması Redis'in 4 işi 6 bölümlü topic · 3 tüketici
1 / 32
gezin · O tüm slaytlar · Home başa · F tam ekran · R tekrar