# Kimlik Modülüyle Deterministik Zamanlama

> Kimlik değerinin mod 24 dağılımı, kayıtları ek koordinasyon durumu tutmadan günün sabit saatlerine deterministik olarak yerleştirir; aynı kimlik her çalıştırmada aynı saate düşer.

- Author: Muhammet Ali Köker
- Language: tr
- Canonical: https://alikoker.com.tr/kimlik-moduluyle-deterministik-zamanlama
- Translation: https://alikoker.com.tr/en/deterministic-scheduling-with-identity-modulo
- Published: 2026-07-31T12:00:00+03:00
- Modified: 2026-08-30T16:20:00+03:00
- Verified: 2026-08-07T11:00:00+03:00
- Type: article

Periyodik çalışan bir görev, her yürütmede bütün kayıtları taramak yerine her kaydı günün belirli bir saatine deterministik olarak atayabilir:

```text
bucket(id) = id mod 24
```

Görev yalnızca mevcut saate karşılık gelen kayıtları işler:

```text
process(id, hour) ⇔ id mod 24 = hour
```

Bu yöntem, veri kümesinin tamamını her zamanlayıcı tetiklemesinde değerlendirmek yerine günlük iş yükünü 24 zaman dilimine böler. Merkezi kuyruk, [dağıtık kilit](/wiki/distributed-lock), kalıcı imleç veya dinamik zamanlayıcı gerektirmez. Bir kaydın hangi saatte işleneceği yalnızca değişmeyen kimliği ve saat bilgisi üzerinden yeniden hesaplanabilir.

Yaklaşımın sadeliği yanıltıcı olabilir. Mod işlemi yalnızca sayıları sınıflara ayırır; yükün gerçekten dengeli olması, kimliklerin dağılımına ve her kaydın işlem maliyetine bağlıdır. Ayrıca zamanlayıcının saat içinde kaç kez çalıştığı, hata sonrasında yeniden denemenin nasıl gerçekleştiği ve bir kaydın aynı gün içinde birden fazla kez işlenmesinin güvenli olup olmadığı açıkça tanımlanmalıdır.

Doğru kurulduğunda bu model, küçük ve orta ölçekli periyodik işlerde son derece düşük operasyonel maliyetle kararlı yük dağılımı sağlar.

## Mod işlemiyle zaman bölümü oluşturmak

Kayıt kimliklerinin pozitif tamsayı olduğu varsayılsın. Gün 24 saatlik bölümlere ayrılır:

```text
H = {0, 1, 2, ..., 23}
```

Her kayıt için saat bölümü:

```text
hᵢ = idᵢ mod 24
```

olarak belirlenir. "hᵢ", her zaman "0" ile "23" arasındadır.

Saat "15" olduğunda yalnızca:

```text
id mod 24 = 15
```

koşulunu sağlayan kayıtlar seçilir. Aynı kayıt ertesi gün yine saat 15 bölümüne düşer. Atama değişmez; başka bir durum tablosunda saklanmasına gerek yoktur.

C# tarafında temel koşul şu kadar sadedir:

```text
if (userId % 24 == DateTime.Now.Hour)
{
 Process(userId);
}
```

SQL tarafında ön filtreleme yapılacaksa eşdeğer koşul:

```text
WHERE USERID % 24 = @Hour
```

şeklindedir.

Bu yapı bir tür deterministik zamansal parçalamadır. Geleneksel sharding yaklaşımında kayıtlar farklı veritabanı veya düğümlere dağıtılır. Burada aynı ilke fiziksel düğüm yerine zaman dilimine uygulanır:

```text
mekânsal parçalama: kayıt → sunucu
zamansal parçalama: kayıt → saat
```

Amaç paralel işlem hacmi artırmak değil, periyodik iş yükünü zaman ekseninde yaymaktır.

```text
Toplam kayıt sayısı "N" ve bölüm sayısı "B = 24" ise ideal bölüm büyüklüğü:
```

```text
μ = N / B
```

olur.

```text
"N = 28.742" için:
```

```text
μ = 28.742 / 24 ≈ 1.197,58
```

Dolayısıyla her saat yaklaşık 1.198 kaydın işlenmesi beklenir.

Gerçek dağılımın tam olarak eşit olması gerekmez. Kayıt sayısı 24'e tam bölünmeyebilir ve kimlik kümesinde silinmiş, atlanmış veya belirli örüntülerle üretilmiş değerler bulunabilir. Önemli olan farkların operasyonel kapasiteyi bozacak kadar büyümemesidir.

## Dağılımın değerlendirilmesi

Gerçek veri kümesinin toplam kayıt sayısını yayımlamadan da dağılım kalitesi ölçülebilir. Her bölüm için kayıt sayısı `n_i`, ortalama bölüm büyüklüğü `μ` olmak üzere:

```text
dengesizlik_i = |n_i - μ| / μ
```

oranını kullanıyorum.

Uyguladığım 24 bölümlü dağıtımda gözlenen bölüm sapmaları ortalamanın yaklaşık **+%5,3 / -%4,8** bandında kaldı. En yoğun bölümün kayıt yükü en hafif bölümün yaklaşık **1,11 katıydı**:

```text
max(n_i) / min(n_i) ≈ 1,11
```

Bu sonuç, kimliklerin mod 24 altında kusursuz eşit dağılmadığını; ancak gözlenen veri kümesinde belirgin bir bölüme yığılma da oluşmadığını gösterdi. Mutlak kayıt adetleri dağıtım kalitesinin değerlendirilmesi için gerekli değildir; yüzde sapma ve `max/min` oranı veri hacmi değiştiğinde de karşılaştırılabilir kalır.

### Neden kimlik üzerinden dağıtım çoğu zaman dengelidir?

Ardışık üretilen kimliklerde modulo dağılımı doğal olarak dengelenir. Kimlikler:

1, 2, 3, ..., N

biçiminde kesintisiz ilerliyorsa her 24 ardışık kayıt, 24 bölümün her birine tam olarak bir kayıt verir.

Bu durumda bölüm sayıları arasındaki fark en fazla bir olur:

```text
|max(nₐ - nᵦ)| ≤ 1
```

Gerçek veritabanlarında silinen kayıtlar, kullanılmayan kimlikler, farklı kaynaklardan taşınan veriler veya başlangıç değerleri nedeniyle bu mükemmel denge bozulabilir. Yine de silinmeler belirli modulo sınıflarını sistematik olarak hedeflemiyorsa dağılım genellikle dengeli kalır.

Örneğin yalnızca çift kimliklerin aktif olduğu bir veri kümesinde:

```text
id mod 24
```

yalnızca çift bölümleri doldurur. Tek saatler boş kalır. Benzer biçimde kimlikler 24'ün katlarıyla üretiliyorsa bütün kayıtlar sıfırıncı bölüme düşer.

Dolayısıyla modulo yönteminin güvenilirliği şu varsayıma bağlıdır:

kimliklerin düşük bitleri veya kalan sınıfları,
iş yüküyle sistematik korelasyon taşımamalıdır

Kimlik üretim düzeni bu koşulu karşılamıyorsa doğrudan kimlik yerine sabit bir hash kullanılabilir:

```text
bucket(id) = floorMod(hash(id), 24)
```

Hash, ardışık veya örüntülü kimlikleri daha homojen kalan sınıflarına dağıtabilir. Ancak ek karmaşıklık getirir ve kullanılan hash fonksiyonunun bütün sürümlerde değişmeden korunması gerekir. Hash değişirse kayıtların çalışma saatleri de topluca değişir.

Mevcut dağılım zaten yüzde 5 civarında sapma gösteriyorsa hash kullanmak zorunlu değildir. Ölçülmüş ve yeterli bulunan basit modulo dağılımı, teorik olarak daha düzgün fakat daha karmaşık bir fonksiyondan genellikle daha değerlidir.

## Saat içinde iki kez çalıştırmanın etkisi

Scheduler'ın her saat başında ve buçukta çalıştığı bir yapı düşünelim. Başlangıç gecikmesi nedeniyle gerçek yürütmeler yaklaşık olarak:

15:01
15:31

zamanlarında gerçekleşebilir. Her iki çalıştırmada da:

```text
DateTime.Now.Hour = 15
```

olduğu için aynı bölüm seçilir.

Bu davranış, aynı saat bölümü için doğal bir ikinci deneme fırsatı oluşturur. İlk çalıştırmada geçici veritabanı, ağ veya kaynak hatası yaşayan kayıtlar ikinci çalıştırmada yeniden ele alınabilir.

Ancak bu avantaj yalnızca işlem idempotent ise güvenlidir. Aynı kaydın aynı gün içinde iki kez işlenmesi:

- aynı verinin iki kez eklenmesine,
- aynı bildirimin iki kez gönderilmesine,
- aynı dosyanın iki farklı içerikle üzerine yazılmasına,
- sayaçların iki kez artırılmasına

neden olabiliyorsa yarım saatlik ikinci tetikleme hata üretir.

Güvenli işlem modeli şu özelliklerden en az birine sahip olmalıdır:

aynı hedef anahtara idempotent upsert
mevcut çıktı varsa atlama
işlem tarihi + kayıt kimliği için unique kısıt
deterministik dosya yolu
başarı durumunun kalıcı kaydı

Örneğin her kullanıcı için günlük çıktı üretiliyorsa hedef:

/yyyy/MM/dd/{userId}.json

biçiminde deterministik olabilir. İlk çalıştırmada dosya atomik olarak yayımlanmışsa ikinci çalıştırma dosyanın varlığını görerek atlar. İlk çalıştırma yarıda kesilmişse hedef dosya oluşmadığından yeniden üretim yapılır.

Veritabanı yazımında:

(user_id, process_date)

üzerinde benzersiz anahtar tanımlanması, ikinci denemenin mükerrer kayıt oluşturmasını önleyebilir. Ancak "INSERT" hatasını yakalayıp yok saymak yerine işlemin semantiğine uygun "MERGE", kontrollü "UPDATE" veya açık varlık kontrolü kullanılmalıdır.

İki tetikleme şu şekilde yorumlanmalıdır:

```text
ilk çalışma → birincil deneme
ikinci çalışma → aynı bölüm için telafi denemesi
```

İkinci çalışmanın zorunlu olarak bütün başarılı kayıtları tekrar işlemesi gerekmez. Kalıcı başarı bilgisi bulunan kayıtlar atlanabilir; yalnızca eksik veya başarısız olanlar yeniden denenebilir.

## Saat sınırındaki yarış koşulu

Saat bilgisinin işlem boyunca tekrar tekrar okunması, saat geçişinde farklı kayıtların farklı bölümlere göre değerlendirilmesine yol açabilir:

```text
foreach (final User user in users)
{
 if (user.Id % 24 == DateTime.Now.Hour)
 {
 Process(user);
 }
}
```

Görev "14:59:59" anında başlar ve döngü sırasında saat "15:00" olursa listenin ilk kısmı 14, kalan kısmı 15 bölümüne göre değerlendirilebilir. Bu davranış aynı yürütmenin deterministik bölüm sözleşmesini bozar.

Saat başlangıçta tek kez sabitlenmelidir:

```text
final int hour = DateTime.Now.Hour;

foreach (final User user in users)
{
 if (user.Id % 24 == hour)
 {
 Process(user);
 }
}
```

C# dilinde yerel değişken için "final" bulunmadığından gerçek kodda "int hour" kullanılır; temel ilke değerin döngü boyunca yeniden okunmamasıdır.

Daha güvenli yaklaşım zamanlayıcının planladığı çalışma zamanını kullanmaktır. Görev 15'inci saat bölümü için planlandıysa gerçek başlangıç gecikerek "16:00" sonrasına taşsa bile hangi bölümün hedeflendiği açıkça bilinmelidir.

Yalnızca duvar saati okunursa uzun gecikmelerde yanlış bölüm işlenebilir:

planlanan bölüm: 15
gerçek başlangıç: 16:02
DateTime.Now.Hour: 16

Bu risk kısa ve düzenli çalışan yerel zamanlayıcı görevlerinde düşük olabilir. Fakat sistem yoğunluk nedeniyle bir saatten fazla gecikebiliyorsa çalışma bölümü tetikleyici tarafından parametre olarak iletilmelidir:

ProcessBucket(15)

Böylece iş mantığı işletim sistemi saatinden ayrılır.

Saat dilimi de açıkça tanımlanmalıdır. Yerel saat kullanan sistemlerde yaz saati değişiklikleri bazı bölgelerde bir saatin iki kez yaşanmasına veya hiç yaşanmamasına neden olabilir. Türkiye'de sabit UTC+03:00 kullanılması bu özel dalgalanmayı ortadan kaldırır; yine de uygulama düzeyinde açık bir "TimeZoneInfo" veya zaman politikası tanımlamak daha güvenlidir.

## Günlük garanti ve kaçırılan saatler

Modulo zamanlama, görev gün boyunca düzenli çalıştığı sürece her kaydı bir kez hedefler. Ancak uygulama belirli bir saat boyunca kapalı kalırsa o bölümdeki kayıtlar o gün hiç işlenmeyebilir:

uygulama kapalı: 03:00-04:00
kaçırılan bölüm: id mod 24 = 3

Ertesi gün saat 03:00'te kayıtlar yeniden seçilir; fakat işlem günlük zorunluluk taşıyorsa bir günlük gecikme oluşur.

Bu nedenle modulo ataması tek başına kesin teslim garantisi değildir. Yalnızca planlı yürütme zamanını belirler. Kaçırılan işlerin tamamlanması için kalıcı durum gerekir:

```text
due(i, date) =
bucket(i) = hour
AND completed(i, date) = false
```

Daha dayanıklı modelde zamanlayıcı mevcut saat bölümünü işlerken önceki eksikleri de sınırlı biçimde tarayabilir:

önce mevcut bölüm
sonra son kapanmış güne ait eksikler

Alternatif olarak kayıt için son başarılı çalışma zamanı tutulabilir:

lastSuccess(userId)

ve seçim koşulu şu şekilde genişletilebilir:

id mod 24 = currentHour
OR lastSuccess < requiredDate

Bu yaklaşım mevcut saatin güncel yükünü korurken kaçırılan kayıtların zaman içinde geri kazanılmasını sağlar.

Ancak bütün gecikmiş kayıtların tek yürütmede yüklenmesi, modulo ile sağlanan dengeyi bozabilir. Backlog için üst sınır uygulanmalıdır:

currentBucketLimit
backfillLimit
totalExecutionDeadline

Örneğin görev önce mevcut bölümdeki yaklaşık 1.200 kaydı, ardından en fazla 100 gecikmiş kaydı işleyebilir. Böylece geçmiş borç kontrollü biçimde azaltılırken zamanlayıcı periyodu aşılmaz.

### Bölüm sayısının 24 olmak zorunda olmaması

Saatlik zamanlama için 24 bölüm doğal görünür, fakat bölüm sayısı doğrudan zamanlayıcı periyodundan türetilebilir.

Görev her yarım saatte farklı bir bölüm işleyecekse:

```text
B = 48
```

ve bölüm numarası:

```text
slot = hour × 2 + half
```

olarak tanımlanabilir.

Burada:

```text
half = 0, dakika < 30
half = 1, dakika ≥ 30
```

olur. Kayıt seçimi:

```text
id mod 48 = slot
```

şeklinde yapılır.

Bu model her yarım saatlik çalışmada kayıtların yaklaşık kırk sekizde birini işler. Toplam günlük iş yükü değişmez, fakat tek yürütmedeki yoğunluk yarıya iner.

Buna karşılık aynı saat bölümünü iki kez çalıştırmak hata telafisi sağlar. İki yaklaşımın amacı farklıdır:

24 bölüm + saatte iki çalışma:
aynı iş için ikinci deneme

48 bölüm + yarım saatte bir çalışma:
iş yükünü daha küçük parçalara ayırma

Hangisinin doğru olduğu sistem gereksinimine bağlıdır. Geçici hataların ikinci denemede düzelme olasılığı yüksekse 24 bölüm avantajlıdır. Tek yürütme süresi zamanlayıcı aralığına yaklaşıyorsa 48 bölüm daha güvenli olabilir.

Bölüm sayısı değiştirilirse bütün kayıtların zaman ataması değişir:

```text
id mod 24 ≠ id mod 48
```

Bu nedenle bölüm sayısı sıradan bir konfigürasyon değeri değil, zamanlama şemasının parçasıdır. Değişiklik sırasında eski ve yeni düzenin aynı gün içinde mükerrer veya eksik işlem üretmemesi için geçiş politikası gerekir.

## Dinamik kuyruk yerine deterministik parçalama

Merkezi iş kuyruğu daha esnek yük dengelemesi sağlar. Boş çalışan yeni işi alabilir, uzun süren kayıtlar doğal olarak farklı zamanlara yayılır ve öncelik yönetimi yapılabilir. Buna karşılık kuyruk şu ek bileşenleri gerektirir:

- iş kaydı,
- teslim durumu,
- görünürlük süresi,
- yeniden deneme sayacı,
- dead-letter yönetimi,
- eşzamanlı tüketici koordinasyonu,
- bakım ve gözlemleme altyapısı.

Tek process ve sınırlı kayıt hacmi bulunan bir görev için bu maliyet gereksiz olabilir.

Modulo parçalama, iş dağıtım durumunu saklamaz:

```text
assignment = pureFunction(id, partitionCount)
```

Bu fonksiyonun sonucu yeniden hesaplanabilir. Merkezi koordinasyon gerektirmez ve aynı veri kümesinde her çalıştırmada aynı sonucu üretir.

Karşılığında dinamik dengeleme yeteneği sınırlıdır. Uzun süren kayıtlar kendiliğinden başka bölüme taşınmaz. Öncelikli iş, ek bir kural olmadan öne alınamaz. Bölüm kaçırıldığında teslim garantisi ayrıca kurulmalıdır.

Bu nedenle modulo tabanlı zamanlama özellikle şu koşullarda uygundur:

- Kayıt kimlikleri kararlıysa,
- İş yükü kayıtlar arasında yaklaşık benzerse,
- Toplam hacim tek process tarafından günlük olarak işlenebiliyorsa,
- Karmaşık kuyruk altyapısı istenmiyorsa,
- Aynı kaydın yeniden işlenmesi güvenliyse,
- Saatlik bölüm kaçırmaları telafi edilebiliyorsa.

Kayıt maliyetleri aşırı değişken, teslim süreleri katı veya yatay çalışan ölçeklemesi zorunluysa kalıcı kuyruk daha uygun olabilir.

Modulo işleminin değeri, en gelişmiş yük dengeleme algoritması olmasından değil, problem yeterince düzenliyken daha karmaşık altyapıya ihtiyaç bırakmamasından gelir.

## Basit formülün arkasındaki sistem sözleşmesi

Kimlik modülüne göre saat seçmek hesaplama açısından sabit maliyetlidir:

```text
O(1)
```

Bütün kayıtlar uygulama tarafında taranıyorsa toplam seçim maliyeti:

```text
O(N)
```

olur. Filtre veritabanına aktarılır ve uygun erişim planı sağlanırsa uygulamaya yalnızca hedef bölüm taşınır. Bununla birlikte "USERID % 24" ifadesi normal indeksin doğrudan kullanımını sınırlayabilir. Büyük tablolarda sanal sütun veya function-based index değerlendirilebilir:

BUCKET AS (MOD(USERID, 24))

ve bu sütun üzerinde indeks oluşturulabilir. Orta ölçekli veri kümelerinde tam tarama maliyeti düşük kalabilir; şema karmaşıklığı eklemeden önce gerçek [yürütme planı](/wiki/execution-plan) ölçülmelidir.

Asıl doğruluk formülden değil, çevresindeki sözleşmelerden gelir:

kimlik kararlı mı?
bölüm sayısı sabit mi?
saat tek kez mi okunuyor?
işlem idempotent mi?
ikinci tetikleme ne anlama geliyor?
kaçırılan bölüm nasıl tamamlanıyor?
kayıt sayısı ile gerçek maliyet korelasyonlu mu?

Bu sorular cevaplanmadan yalnızca "% 24" koşulu kullanmak, yükü dağıtsa bile güvenilir bir zamanlayıcı tasarımı oluşturmaz.

Buna karşılık koşullar açıkça tanımlandığında yöntem güçlü bir özellik kazanır: sistemin hangi kaydı ne zaman işleyeceği dış bir durumdan değil, saf ve doğrulanabilir bir fonksiyondan türetilir.

```text
zaman bölümü = kimlik mod bölüm sayısı
```

Bu deterministik ilişki; yeniden başlatmayı, test üretmeyi, yük tahminini ve hata analizini kolaylaştırır. Karmaşık sistemlerde her sorunun dinamik bir koordinasyon katmanıyla çözülmesi gerekmez. Bazen en güvenilir dağıtım mekanizması, değişmeyen bir kimliği sabit sayı ile bölmek ve kalan değerin ifade ettiği sözleşmeyi eksiksiz uygulamaktır.

## Kaynakça

- Oracle. (2014). Java Platform, Standard Edition 8 API - java.lang.Math. Oracle. [URL](https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html)

## Bu Çalışmaya Atıf

Köker, M. A. (2026). Kimlik Modülüyle Deterministik Zamanlama. alikoker.com.tr. https://alikoker.com.tr/kimlik-moduluyle-deterministik-zamanlama

- BibTeX: https://alikoker.com.tr/kimlik-moduluyle-deterministik-zamanlama.bib
- RIS: https://alikoker.com.tr/kimlik-moduluyle-deterministik-zamanlama.ris
- CSL-JSON: https://alikoker.com.tr/kimlik-moduluyle-deterministik-zamanlama.csl.json
