---
title: 'Dağıtım Akışı'
source: 'https://academia.sh/tr/kurslar/php-uretim/dagitim-akisi'
course: "PHP'de Kalite, Başarım ve Dağıtım"
language: tr
updated: '2026-10-21T05:03:36+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Sürüm dizinleri ve sembolik bağ ile atomik geçişin kesintisiz dağıtımı nasıl sağladığını açıklayabilmek'
    - 'Üretim profilinde geçişten sonra eski sürümün koşabildiğini ölçerek göstermek'
    - 'realpath(), opcache_reset() ve opcache_invalidate() çağrılarının yeni sürümü getirip getirmediğini karşılaştırmak'
---

# Dağıtım Akışı

Kodu üretime alırken sürüm dizinleri ve sembolik bağ geçişi: atomik geçişten sonra neden eski sürüm koşar, hangi çağrı yeni sürümü getirir.

Önceki beş ders tek tek ayar ve ölçüm yaptı: kod önbelleği, süreç yöneticisi, yapılandırma yönergeleri. Bir dağıtım hepsini aynı anda sınar, çünkü yeni kod yalnız diske yazılmakla üretime çıkmaz. Kesintisiz dağıtım (zero-downtime deployment) denen düzende her sürüm kendi dizinine kurulur ve istekleri karşılayan yol, tek bir adımda yeni dizine çevrilir. Bu ders o adımı kurar ve üretim ayarlarının onu nasıl etkilediğini ölçer.

Ölçü ekseni yine şu soru: bu davranış üretime çıkıyor mu? DevOps ve Platform Mühendisliği müfredatında geliştirme ile üretim arasındaki her sapma gizli kusur sayıldı ve kapatılması hedeflendi. Burada farkın çoğu kasıtlıdır: PHP iki ayar dosyasıyla dağıtılır ve fark o dosyalardan gelir. Bu ders farkı kapatmaz, adlandırır. Aşağıdaki `yan.php` kursun ortak çekirdeğidir; bu derste de yalnız diske yazılır, çıktısı yoktur.

```php
<?php
// yan.php — bu davranış üretime çıkıyor mu?
declare(strict_types=1);

// PHP'nin dağıttığı iki ayar dosyası (php.ini-development, php.ini-production) yedi
// yönergede ayrışır; yedisi buraya birebir alındı. Üretimdeki son iki satır dağıtım
// dosyasında yorum satırıdır: kod önbelleği komut satırında da açık, dosya denetimi kapalı.
const PROFILLER = [
    'geliştirme' => [
        'display_errors' => '1',
        'display_startup_errors' => '1',
        'error_reporting' => '32767',
        'mysqlnd.collect_memory_statistics' => '1',
        'zend.assertions' => '1',
        'zend.exception_ignore_args' => '0',
        'zend.exception_string_param_max_len' => '15',
    ],
    'üretim' => [
        'display_errors' => '0',
        'display_startup_errors' => '0',
        'error_reporting' => '24575',
        'mysqlnd.collect_memory_statistics' => '0',
        'zend.assertions' => '-1',
        'zend.exception_ignore_args' => '1',
        'zend.exception_string_param_max_len' => '0',
        'opcache.enable_cli' => '1',
        'opcache.validate_timestamps' => '0',
    ],
];

/** Betiği ayar dosyası olmadan, yalnız profilin yönergeleriyle ayrı bir süreçte koşturur. */
function calistir(string $betik, array $yonergeler): bool {
    $komut = ['php', '-n'];
    foreach ($yonergeler as $ad => $deger) {
        array_push($komut, '-d', "$ad=$deger");
    }
    $komut[] = $betik;
    $surec = proc_open($komut, [1 => ['pipe', 'w'], 2 => ['file', '/dev/null', 'w']], $borular);
    $cikti = stream_get_contents($borular[1]);
    fclose($borular[1]);
    proc_close($surec);
    $satirlar = array_values(array_filter(array_map('trim', explode("\n", $cikti))));
    return end($satirlar) === 'evet';
}

/** Davranışın iki koşumda görülüp görülmediğinden yanını çıkarır. */
function yan(bool $gelistirmede, bool $uretimde): string {
    return match (true) {
        $gelistirmede && $uretimde => 'iki yanda da',
        $gelistirmede => 'yalnız geliştirmede',
        $uretimde => 'yalnız üretimde',
        default => 'koşuma girmiyor',
    };
}

function yanRaporu(string $ad, string $betik): string {
    $gelistirmede = calistir($betik, PROFILLER['geliştirme']);
    $uretimde = calistir($betik, PROFILLER['üretim']);
    $sonYan = yan($gelistirmede, $uretimde);
    echo "$ad → $sonYan\n    geliştirme ", $gelistirmede ? 'evet' : 'hayır',
        ' · üretim ', $uretimde ? 'evet' : 'hayır', "\n";
    return $sonYan;
}
```

## Sürüm Dizinleri ve Atomik Geçiş

Düzen şöyledir: her sürüm `surum-1`, `surum-2` gibi ayrı bir dizindedir ve isteklerin kullandığı `guncel` adı bir sembolik bağdır (symbolic link); bağ, o anda yayında olan sürüm dizinini gösterir. Yeni sürüm yanda hazırlanır, çalışan sürüme hiç dokunulmaz. Yayına alma, bağın başka bir dizini göstermesinden ibarettir.

Geçişin atomik (atomic) olması gerekir: bir istek bağı okurken ne eski ne yeni dizini gösteren bir ara durum görmemelidir. `symlink()` var olan bir adın üzerine yazmaz; bu yüzden önce `guncel-yeni` adıyla yeni bir bağ kurulur, sonra `rename()` onu `guncel` adının üstüne taşır. Aynı dosya sistemi içinde `rename()` tek adımda biter: `guncel` adı her an ya eski ya yeni dizini gösterir. İki yardımcı fonksiyon bu düzeni kurar. Dosya bu derste yalnız diske yazılır.

```php
<?php
// kur.php — iki sürüm dizini, ilkini gösteren bağ ve atomik geçiş
declare(strict_types=1);

function surumleriKur(): void {
    foreach ([1, 2] as $n) {
        @mkdir(__DIR__ . "/surum-$n");
        $yol = __DIR__ . "/surum-$n/selam.php";
        file_put_contents($yol, "<?php return $n;");
        touch($yol, time() - 60);
    }
    @unlink(__DIR__ . '/guncel');
    symlink('surum-1', __DIR__ . '/guncel');
}

function gecir(): void {
    @unlink(__DIR__ . '/guncel-yeni');
    symlink('surum-2', __DIR__ . '/guncel-yeni');
    rename(__DIR__ . '/guncel-yeni', __DIR__ . '/guncel');
}
```

Her sürümün `selam.php` dosyası yalnız sürüm numarasını döndürür. `touch()` dosyaların zamanını geçmişe alır; önbellek son saniyelerde değişmiş dosyayı almadığı için yoklamalar bu adımı kodun başında yapar. İlk yoklama geçişin diskte işe yaradığını, ikincisi önbellek açıkken ne olduğunu sorar. Her yoklama kendi sürecinde ve `surumleriKur()` ile sıfırdan kurulan dizinlerde koşar.

```php
<?php
// dagitim.php — geçişten sonra hangi sürüm koşuyor?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('gecis.php', <<<'PHP'
<?php
// gecis.php — bağ geçişten sonra yeni dizini gösterir mi?
require __DIR__ . '/kur.php';
surumleriKur();
gecir();
echo is_link(__DIR__ . '/guncel') && readlink(__DIR__ . '/guncel') === 'surum-2' ? "evet\n" : "hayır\n";
PHP);

file_put_contents('eski.php', <<<'PHP'
<?php
// eski.php — geçişten sonra aynı yoldan eski sürüm mü koşar?
require __DIR__ . '/kur.php';
surumleriKur();
$once = include __DIR__ . '/guncel/selam.php';
gecir();
$sonra = include __DIR__ . '/guncel/selam.php';
echo $sonra === $once ? "evet\n" : "hayır\n";
PHP);

yanRaporu('bağ geçişten sonra yeni dizini gösterir', 'gecis.php');
yanRaporu('geçişten sonra aynı yoldan eski sürüm koşar', 'eski.php');
```

```
bağ geçişten sonra yeni dizini gösterir → iki yanda da
    geliştirme evet · üretim evet
geçişten sonra aynı yoldan eski sürüm koşar → yalnız üretimde
    geliştirme hayır · üretim evet
```

Birinci yoklama iki profilde de `evet` verdi: geçiş diskte her yerde aynı biçimde tamamlanır. İkinci yoklama yalnız üretimde `evet` verdi (**BA8.** Ölçüm komut satırı sürecinde, tek süreçle yapıldı; gerçek bir dağıtımda geçiş sürüyor olan işçi süreçlerin yanında olur ve her süreç kendi isteğini yeni yoldan görür, bu derste o düzen kurulmadı). Sembolik bağ yeni dizini gösterdiği hâlde `guncel/selam.php` yolundan gelen ikinci yükleme, birincisiyle aynı değeri döndürdü: önbellek bu yolu zaten tanıyordu ve diske bakmadı. Geliştirmede kod önbelleği kapalı olduğundan her yükleme diski okur ve yeni sürüm hemen gelir.

## Yeni Sürümü Getiren Çağrılar

Sorun, önbelleğin bir yolu anahtar olarak tutması ve bu yolun bağdan geçmesidir. Dağıtım betiğinin elinde üç aday çağrı vardır: dosya sistemi denetim önbelleğini temizleyen `clearstatcache(true)`, tüm kod önbelleğini boşaltan `opcache_reset()` ve tek dosyayı geçersiz kılan `opcache_invalidate()`. Dördüncüsü çağrı değil, yoldur: bağı kodda `realpath()` ile çözmek. Dört yoklama aynı kurulumla koşar; her birinde `evet`, adının söylediği şeyin gerçekleştiği anlamına gelir.

```php
<?php
// cozumler.php — hangi çağrı geçişten sonra yeni sürümü getirir?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('stat.php', <<<'PHP'
<?php
// stat.php — clearstatcache(true) sonrası eski sürüm kalır mı?
require __DIR__ . '/kur.php';
surumleriKur();
include __DIR__ . '/guncel/selam.php';
gecir();
clearstatcache(true);
$sonra = include __DIR__ . '/guncel/selam.php';
echo $sonra === 1 ? "evet\n" : "hayır\n";
PHP);

file_put_contents('sifirla.php', <<<'PHP'
<?php
// sifirla.php — opcache_reset() true döner ama aynı istekte eski sürüm kalır mı?
require __DIR__ . '/kur.php';
surumleriKur();
include __DIR__ . '/guncel/selam.php';
gecir();
$dondu = opcache_reset();
$sonra = include __DIR__ . '/guncel/selam.php';
echo $dondu === true && $sonra === 1 ? "evet\n" : "hayır\n";
PHP);

file_put_contents('baglayol.php', <<<'PHP'
<?php
// baglayol.php — bağ yoluyla geçersiz kılmadan sonra yeni sürüm gelir mi?
require __DIR__ . '/kur.php';
surumleriKur();
$yol = __DIR__ . '/guncel/selam.php';
include $yol;
gecir();
opcache_invalidate($yol, true);
$sonra = include $yol;
echo $sonra === 2 ? "evet\n" : "hayır\n";
PHP);

file_put_contents('eskiyol.php', <<<'PHP'
<?php
// eskiyol.php — eski sürümün çözülmüş yoluyla geçersiz kılma yeni sürümü getirir mi?
require __DIR__ . '/kur.php';
surumleriKur();
$yol = __DIR__ . '/guncel/selam.php';
include $yol;
$eskiYol = realpath($yol);
gecir();
$dondu = opcache_invalidate($eskiYol, true);
$sonra = include $yol;
echo $dondu === true && $sonra === 2 ? "evet\n" : "hayır\n";
PHP);

file_put_contents('cozulmus.php', <<<'PHP'
<?php
// cozulmus.php — bağ include'dan önce realpath() ile çözülünce yeni sürüm gelir mi?
require __DIR__ . '/kur.php';
surumleriKur();
include realpath(__DIR__ . '/guncel') . '/selam.php';
gecir();
$sonra = include realpath(__DIR__ . '/guncel') . '/selam.php';
echo $sonra === 2 ? "evet\n" : "hayır\n";
PHP);

yanRaporu('clearstatcache(true) sonrası eski sürüm kalır', 'stat.php');
yanRaporu('bağ yoluyla geçersiz kılmadan sonra yeni sürüm gelir', 'baglayol.php');
yanRaporu('eski sürümün çözülmüş yoluyla geçersiz kılma yeni sürümü getirir', 'eskiyol.php');
yanRaporu('realpath() ile çözülen yoldan yeni sürüm gelir', 'cozulmus.php');
yanRaporu('opcache_reset() true döner ama aynı istekte eski sürüm kalır', 'sifirla.php');
```

```
clearstatcache(true) sonrası eski sürüm kalır → yalnız üretimde
    geliştirme hayır · üretim evet
bağ yoluyla geçersiz kılmadan sonra yeni sürüm gelir → yalnız geliştirmede
    geliştirme evet · üretim hayır
eski sürümün çözülmüş yoluyla geçersiz kılma yeni sürümü getirir → yalnız üretimde
    geliştirme hayır · üretim evet
realpath() ile çözülen yoldan yeni sürüm gelir → iki yanda da
    geliştirme evet · üretim evet
opcache_reset() true döner ama aynı istekte eski sürüm kalır → yalnız üretimde
    geliştirme hayır · üretim evet
```

Üç sonuç ayrılıyor. Birincisi, `clearstatcache(true)` işe yaramadı: üretimde ikinci yükleme yine eski sürümü verdi. Bu çağrı dosya sistemi sorgularının önbelleğini temizler, kod önbelleğine dokunmaz. İkincisi, `opcache_reset()` üretimde `true` döndü ama aynı istekte eski sürüm kaldı; çağrının başarılı görünmesi, o istekte yeni sürümün geleceği anlamına gelmiyor. Bir sonraki isteği bu ölçüm göstermedi. Üçüncüsü, geçersiz kılma çağrısının hangi yolu aldığı önemlidir: bağ yoluyla (`guncel/selam.php`) yapılan çağrıdan sonra üretimde eski sürüm kaldı; yeni sürümü yalnız önbelleği kapalı olan geliştirme gördü. Eski sürümün çözülmüş yoluyla (`realpath()` sonucu) yapılan çağrı ise üretimde yeni sürümü getirdi. Aynı biçimde, include etmeden önce `realpath()` ile çözülen yoldan yeni sürüm iki profilde de geldi. Bu üç sonuç, önbelleğin dosyayı bağın adıyla değil çözülmüş yolla tuttuğu varsayımıyla tutarlıdır. Geliştirmede `opcache_reset()` ve `opcache_invalidate()` önbellek kapalı olduğundan `false` döner, bu yüzden dağıtım betiğinin çağrıları geliştirmede sınanamaz.

## Sınırlayıcı Ölçüm

`realpath()` çözümü, yolun ne zaman çözüldüğüne bağlıdır. Yoklama yolu geçişten önce bir kez çözer ve saklar; geçişten sonra saklanan yoldan yükler. Uzun ömürlü bir süreç yolu başlangıçta çözüp bellekte tutsaydı bu olurdu.

```php
<?php
// sinir.php — geçişten önce çözülen yol geçişten sonra da eski sürümü verir mi?
declare(strict_types=1);
require __DIR__ . '/yan.php';

file_put_contents('saklanan.php', <<<'PHP'
<?php
// saklanan.php — çözülmüş yol geçişten önce saklanır, geçişten sonra kullanılır
require __DIR__ . '/kur.php';
surumleriKur();
$saklanan = realpath(__DIR__ . '/guncel');
$once = include $saklanan . '/selam.php';
gecir();
$sonra = include $saklanan . '/selam.php';
echo $sonra === $once ? "evet\n" : "hayır\n";
PHP);

yanRaporu('geçişten önce çözülen yol geçişten sonra da eski sürümü verir', 'saklanan.php');
```

```
geçişten önce çözülen yol geçişten sonra da eski sürümü verir → iki yanda da
    geliştirme evet · üretim evet
```

Yoklama iki profilde de `evet` verdi: saklanan yol eski dizini gösterdiği için geçişten sonra da eski sürüm geldi, kod önbelleği açık olsun olmasın. Demek ki `realpath()` tek başına bir çözüm değil, bir zamanlama kuralıdır: çözümleme her istekte, include'dan hemen önce yapılmalıdır. Ölçümün sınırı da burada: yoklama bir süreç ve iki sürümle sınırlıdır, saklanan yolun gerçek bir işçi süreçte ne kadar yaşadığı ölçülmedi.

## Özet

- Kesintisiz dağıtım, her sürümü ayrı dizine koyar ve `symlink()` ile kurulan yeni bağı `rename()` ile `guncel` adının üstüne taşır; geçiş (atomik geçiş) diskte iki profilde de tamamlanır.
- Bu dersin ölçü sınıfı **yalnız üretimde**dir: `opcache.validate_timestamps=0` iken geçişten sonra aynı bağ yolundan eski sürüm koşar; geliştirmede kod önbelleği kapalıdır ve yeni sürüm gelir.
- `clearstatcache(true)` bunu çözmez; `opcache_reset()` `true` döner ama aynı istekte eski sürüm kalır; geliştirmede iki `opcache_*` çağrısı `false` döner.
- Yeni sürümü getiren iki yol ölçüldü: include'dan önce `realpath()` ile çözülmüş yol ve eski sürümün çözülmüş yoluyla `opcache_invalidate($yol, true)`. Bağ yoluyla geçersiz kılmadan sonra üretimde eski sürüm kaldı.
- Sınır: `realpath()` sonucu geçişten önce saklanırsa eski sürüm iki yanda da gelir; çözümleme her istekte yapılmalıdır.

## Kurs Kapanışı

Kurs, kodu üretime taşıyan yedi yönergenin ve dört davranış sınıfının ölçülmesiyle kapanıyor. Her dersin ölçülen davranışı, yanı ve sınırlayıcı ölçümü aşağıdadır.

| Ders | Ölçülen davranış | Yan | Sınırlayıcı ölçüm |
|---|---|---|---|
| Statik Çözümleme | çağrılmayan koldaki tanımsız fonksiyon koşmaz, söz dizimi denetimi yalnız söz dizimine bakar, belirteç denetleyicisi `ozetle` çağrısını bulur | koşuma girmiyor | adı değişkenden çağrılırsa denetleyici 0 buldu, doğrudan koşum `Error` attı |
| Kod Stili Denetimi | biçimsiz kod iki profilde aynı çalışır, belirteç tabanlı denetleyici satır sonu, çift boşluk ve girintiyi sayar | koşuma girmiyor | nowdoc içindeki satır sonu boşluğunu belirteçsiz düzeltme dizeyi kısaltır, yalnız `T_WHITESPACE` yeniden yazan biçimlendirici uzunluğu 13 bırakır |
| Test Çerçeveleri | `assert()` tabanlı sınama geliştirmede yakalar, üretimde (`zend.assertions=-1`) derlenmez | yalnız geliştirmede | `zend.assertions=0` da yakalamaz ve çalışırken `1`'e çevrilebilir, `-1`'de yol kapalıdır |
| Hata Ayıklayıcı | `kart-0042` istisna izinde geliştirmede görünür, üretimde gizlidir | yalnız geliştirmede | değer iletiye yazılırsa `getMessage()` iki profilde de `kart-0042` taşır |
| Kod Önbellekleme | `validate_timestamps=0` ile yeniden yazılan dosya eski sürümle koşar, `opcache_invalidate($yol, true)` yeni sürümü getirir | yalnız üretimde | `validate_timestamps=1` ve `revalidate_freq=0` eski sürüm sorununu giderir |
| Profil Çıkarma | 500 çağrı, toplam 249500 ve eşit bellek farkı; üç fonksiyonlu zincirde `oku` 400, `donustur` 200, `ozetle` 5 | iki yanda da | `assert()` içindeki sayaç geliştirmede 500, üretimde 0 |
| Bellek ve Bağlantı Yönetimi | `memory_limit` aşılınca istek durur, yıkıcı istek sonunda koşar | iki yanda da | döngüsel referansta `unset()` yıkıcıyı koşturmaz, `gc_collect_cycles()` koşturur |
| Süreç Yöneticisi ve Web Sunucusu | iki işçi süreçte bellekteki sayaç her istekte `1` kalır, uyarılı sayfa iki profilde `200` döner | iki yanda da | sayaç dosyada tutulursa `1 2 3`, bellekte `1 1 1` |
| Yapılandırma Ayarı | yedi yönergelik fark; `ini_set('zend.assertions','1')` geliştirmede kabul, üretimde `false` | yalnız üretimde | `display_errors` üretimde `ini_set()` ile geri açılabildi |
| Dağıtım Akışı | sembolik bağ geçişinden sonra aynı yoldan eski sürüm koşar, `realpath()` ile çözülen yoldan yeni sürüm gelir | yalnız üretimde | geçişten önce çözülüp saklanan yoldan iki profilde de eski sürüm gelir |

Derslerin dağılımı: **3 iki yanda da · 2 yalnız geliştirmede · 3 yalnız üretimde · 2 koşuma girmiyor**. Çekirdek (`davranislar.php`, Statik Çözümleme dersinde koşturuldu): **4 · 4 · 2 · 2**. İki sayı ayrı şeyleri sayar: ilki on dersin ana ölçümünün yanını, ikincisi on iki ayrı davranışın yanını sayar; bir ders birden çok davranış ölçtüğü ve bir davranış birden çok dersi ilgilendirdiği için iki dağılımın aynı çıkması beklenmez.

M12 burada kapanır; beş kurs PHP'yi bir dilden bir üretim sistemine kadar yürüttü. PHP Temelleri kursu, bir sorunun hangi yoldan duyulduğunu ölçtü (*bu sorun hangi kanaldan duyuluyor*: sessiz · dönüş değeri · uyarı · durdurur, 3/4/4/3). Nesneye Dayalı PHP ve Standartlar kursu, bir değişikliğin nereye ulaştığını ölçtü (*bu değişiklik nereye ulaşıyor*: yerinde · bağlantıda · kıran · örtük, 3/3/3/3). Web ile PHP: Oturum, Veri ve Güvenlik kursu, sonucu kimin değiştirebildiğini ölçtü (*bu sonucu kim değiştirebiliyor*: sunucu · ortam · istemci · üçüncü taraf, 5/4/4/2). Web Uygulama Çerçevesi kursu, bir parçanın o istekte koşup koşmadığını ölçtü (*bu parça bu istekte koştu mu*: koştu · ertelendi · atlandı · bağlanmadı, 6/5/5/5). PHP'de Kalite, Başarım ve Dağıtım kursu, bir davranışın üretime çıkıp çıkmadığını ölçtü (*bu davranış üretime çıkıyor mu*: iki yanda da · yalnız geliştirmede · yalnız üretimde · koşuma girmiyor, 3/2/3/2). Sıradaki müfredat Ruby'dir.
