Cloud Streaming Kayıt Dosyalarını Neden Siliyor ve Kalıcı Depolama Nasıl Kurulur
Özet olarak
Öğrenin: Cloud streaming'de kayıt dosyalarının silinmesini önlemek için S3 tabanlı kalıcı depolama mimarisi, senkronizasyon script'leri ve maliyetler
Oyuncunuz üç saat boyunca grind yapar, ilerlemesini kaydeder, akışı kapatır, ertesi gün geri gelir — ve kaydı yok olmuştur. Bu, oyun kodunuzdaki bir hata değil. Kullanıcı hatası da değil. Kayıt dosyasını tutan streaming instance'ı sonlandırılmış ve yerine yeni bir tane getirilmiştir; tüm yerel veriyi de beraberinde götürmüştür.
Bu, cloud game streaming'in temel mimari tuzağıdır. Amazon GameLift Streams gibi platformlar, ephemeral compute kullanarak maliyeti optimize eder: her oturum yeni bir instance ayağa kaldırır, oyun binary'nizi çalıştırır ve oturum bittiğinde onu yok eder. Kaynak kullanımı için harika. Diske dosya yazan herhangi bir oyun için berbat. Kayıt sisteminiz kusursuz çalışıyor — oyun %APPDATA%/YourGame/save.dat dosyasını tam tasarlandığı gibi yazıyor — ama dosya sisteminin kendisi geçici.
Bu runbook'ta tam olarak neyin bozulduğunu, nasıl tespit edileceğini, cloud object storage kullanarak kalıcı bir kayıt katmanının nasıl kurulacağını ve çalıştırmanın gerçek maliyetini ele alacağım. Aşağıdaki her script production'da test edilmiştir ve projenize doğrudan eklemeye hazırdır.
Ne Bozulur: Ephemeral Instance Yaşam Döngüsü
Tipik bir cloud streaming oturumunun yaşam döngüsü ve nerede başarısız olduğu şöyle:
- Oturum başlar — Platform bir instance sağlar, oyun binary'nizi yükler
- Oyuncu bağlanır — Oyun başlatılır, oyuncu oynamaya başlar
- Oyun kayıtları yazar — Kayıt dosyaları yerel diske yazılır (oyun bu diskin geçici olduğunu bilmez)
- Oturum biter — Oyuncu bağlantıyı keser, instance sonlandırılır veya geri dönüştürülür
- Dosyalar yok edilir — Tüm yerel dosyalar silinir; sonraki oturum sıfırdan başlar
Kritik başarısızlık noktası 4→5 adımlarıdır. Oyununuzun kayıt sistemi tam olarak olması gerekeni yapıyor. Sorun şu ki alttaki platform dosya sistemini tek kullanımlık olarak ele alıyor. Bu, oynanış hatası kılığına girmiş bir altyapı sorunudur ve bunun gibi sunucu yaşam döngüsü sorunları, oyuncuların cloud streaming oyunları terk etmesinin en yaygın nedenlerinden biridir.
Bu Sorun Nasıl Tespit Edilir
Belirtiler spesifik ve tekrarlanabilirdir:
- Oyuncu raporu: "Kaydım sürekli kayboluyor" — ama yalnızca cloud/streaming kullanıcılarında, yerel kurulumlarda asla
- Crash log yok: Oyun kusursuz çalışıyor; kayıtlar yalnızca bir sonraki açılışta orada değil
- Oturum kapsamlı kalıcılık: Veri tek bir oturum içinde hayatta kalır ama oturumlar arasında yok olur
- Platforma özgü: Yalnızca streaming instance'larını etkiler, yerel veya dedicated server yapılarını değil
Hata raporlarınız bu kalıba uyuyorsa, ephemeral instance sorunuyla karşı karşıyasınız demektir. Oyun tarafında ne kadar hata ayıklaması yaparsanız yapın çözülmez — çözüm altyapı katmanındadır.
Mimari: Kalıcı Katman Olarak Object Storage
Çözüm kavramsal olarak basit: uzun vadeli depolama için instance'ın yerel dosya sistemine güvenmeyi bırakın. Dayanıklı bir object storage servisini (Amazon S3 gibi) yetkili kayıt konumu olarak kullanın. Bir launcher script senkronizasyonu şeffaf şekilde yönetir — oyun kodunuz değişmeden kalır.
Akış şöyle çalışır:
- Oyun başlatılmadan önce: Mevcut kaydı S3'ten indir → yerel dosya sistemi
- Oyun sırasında: Yerel kayıt dosyasını değişiklikler için izle → güncellemeleri S3'e senkronize et
- Oturum sonunda: Son senkronizasyon, instance kapatılmadan önce tüm verilerin kalıcı hale getirilmesini sağlar
Oyununuz hâlâ normal şekilde yerel diske yazar. Launcher'ın bu yazmaları cloud storage'a aynaladığından haberi yoktur. Bu sıfır-değişiklik yaklaşımı, mevcut herhangi bir oyuna tek satır oyun kodu değiştirmeden kalıcı kayıt ekleyebileceğiniz anlamına gelir.
Adım 1: IAM Rolünü Yapılandırma
Streaming oturumlarına S3 bucket'ınıza kapsamlı erişim sağlayan bir IAM rolüne ihtiyacınız var. Rol, streaming servisine güvenmeli ve least-privilege ilkelerini izlemelidir.
Bu trust policy ile bir rol oluşturun ([ACCOUNT_ID] yerine AWS hesap ID'nizi yazın):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "gameliftstreams.amazonaws.com"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "[ACCOUNT_ID]"
},
"ArnLike": {
"aws:SourceArn": "arn:aws:gameliftstreams:*:[ACCOUNT_ID]:streamsession/*"
}
}
}
]
}
Ardından yalnızca ihtiyacınız olan S3 operasyonlarına izin veren bir inline policy ekleyin:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::your-save-bucket/saves/*"
}
]
}
Kritik: Resource alanını /saves/* gibi belirli bir prefix ile sınırlayın — asla tüm bucket'ı vermeyin. Bu, rol ele geçirilirse hasar yarıçapını sınırlar ve güvenlik en iyi uygulamalarına uyar. Platform gereksinimlerine göre rol adı GameLiftStreams- ile başlamalıdır.
Adım 2: Launcher Script (Windows)
Launcher script orkestratördür. Mevcut kayıtları indirir, oyunu başlatır ve değişiklikleri senkronize etmek için arka planda bir file watcher çalıştırır. Windows batch sürümü şöyle:
@echo off
setlocal
set SCRIPT_DIR=%~dp0
if not defined LOCAL_SAVE_FILE_PATH (
echo WARNING: LOCAL_SAVE_FILE_PATH not set, skipping save sync
goto :start_app
)
if not defined S3_SAVE_PATH (
echo WARNING: S3_SAVE_PATH not set, skipping save sync
goto :start_app
)
set LOG_FILE=%SCRIPT_DIR%save_sync.log
set REGION_ARG=
if defined AWS_REGION set REGION_ARG=-Region "%AWS_REGION%"
REM Download existing save from S3
powershell -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%download-save.ps1" ^
-S3Path "%S3_SAVE_PATH%" -LocalPath "%LOCAL_SAVE_FILE_PATH%" ^
-LogFile "%LOG_FILE%" %REGION_ARG%
REM Start background file watcher
start "" /b powershell -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass ^
-File "%SCRIPT_DIR%watch-save.ps1" -WatchPath "%LOCAL_SAVE_FILE_PATH%" ^
-S3Path "%S3_SAVE_PATH%" -LogFile "%LOG_FILE%" %REGION_ARG%
:start_app
REM Replace with your actual game executable:
YourGame.exe -f
İki ortam değişkeni (S3_SAVE_PATH ve LOCAL_SAVE_FILE_PATH), backend servisiniz StartStreamSession çağrısı yaptığında iletilir. S3 yolu oyuncu başına benzersiz olmalıdır — bunu s3://your-bucket/saves/{player-id}/save.dat gibi kimliği doğrulanmış bir oyuncu ID'sinden oluşturun. Asla session ID kullanmayın; bunlar oturumlar arasında değişir ve kayıt verisini sahipsiz bırakır.
Adım 3: İndirme Script'i
Bu PowerShell script'i S3'te mevcut bir kayıt dosyası olup olmadığını kontrol eder ve bulursa indirir:
param(
[Parameter(Mandatory=$true)][string]$S3Path,
[Parameter(Mandatory=$true)][string]$LocalPath,
[Parameter(Mandatory=$true)][string]$LogFile,
[Parameter(Mandatory=$false)][string]$Region
)
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
Add-Content -Path $LogFile -Value "$timestamp - Checking for save at: $S3Path"
$regionArgs = @()
if (-not [string]::IsNullOrWhiteSpace($Region)) {
$regionArgs = @('--region', $Region)
}
# Ensure local directory exists
$localDir = Split-Path $LocalPath -Parent
if (-not (Test-Path $localDir)) {
New-Item -ItemType Directory -Path $localDir -Force | Out-Null
}
try {
$result = & aws s3 cp $S3Path $LocalPath @regionArgs 2>&1
if ($LASTEXITCODE -eq 0) {
Add-Content -Path $LogFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - Downloaded save from S3"
} else {
Add-Content -Path $LogFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - No existing save found (first session?)"
}
} catch {
Add-Content -Path $LogFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - Download error: $_"
}
Daha önce kaydı olmayan ilk kez oynayan oyuncular için S3 kopyalama işlemi başarısız olur — bu beklenen bir durumdur. Oyun yeni bir kayıt dosyası oluşturur ve watcher onu ilk yazmada algılar.
Adım 4: File Watcher
Bu bileşen, oyun sırasında kayıtların senkronize kalmasını sağlar:
param(
[Parameter(Mandatory=$true)][string]$WatchPath,
[Parameter(Mandatory=$true)][string]$S3Path,
[Parameter(Mandatory=$true)][string]$LogFile,
[Parameter(Mandatory=$false)][string]$Region
)
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
Add-Content -Path $LogFile -Value "$timestamp - Watching for changes: $WatchPath"
$regionArgs = @()
if (-not [string]::IsNullOrWhiteSpace($Region)) {
$regionArgs = @('--region', $Region)
}
$folder = Split-Path $WatchPath -Parent
$fileName = Split-Path $WatchPath -Leaf
if (-not (Test-Path $folder)) {
New-Item -ItemType Directory -Path $folder -Force | Out-Null
}
try {
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $folder
$watcher.Filter = $fileName
$watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite -bor `
[System.IO.NotifyFilters]::Size
$watcher.EnableRaisingEvents = $true
while ($true) {
$change = $watcher.WaitForChanged(
[System.IO.WatcherChangeTypes]::All, 1000
)
if (-not $change.TimedOut) {
$ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
Add-Content -Path $LogFile -Value "$ts - Change detected: $($change.ChangeType)"
try {
$s3Result = & aws s3 cp $WatchPath $S3Path @regionArgs 2>&1
if ($LASTEXITCODE -eq 0) {
Add-Content -Path $LogFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - Synced to S3"
} else {
Add-Content -Path $LogFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - Sync failed: $s3Result"
}
} catch {
Add-Content -Path $LogFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - Sync error: $_"
}
}
}
} catch {
Add-Content -Path $LogFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - Watcher crashed: $_"
}
Adım 5: Linux/Proton Varyantı
Linux tabanlı streaming instance'ları için PowerShell watcher yerine inotifywait kullanın:
#!/bin/bash
# watch-save.sh — Linux equivalent using inotify-tools
WATCH_PATH="$1"
S3_PATH="$2"
LOG_FILE="$3"
echo "$(date -Iseconds) - Watching: $WATCH_PATH" >> "$LOG_FILE"
while true; do
inotifywait -e modify -e create "$WATCH_PATH" 2>/dev/null
sleep 2 # Brief debounce
aws s3 cp "$WATCH_PATH" "$S3_PATH" 2>/dev/null
if [ $? -eq 0 ]; then
echo "$(date -Iseconds) - Synced to S3" >> "$LOG_FILE"
else
echo "$(date -Iseconds) - Sync failed" >> "$LOG_FILE"
fi
done
Maliyet Dökümü: Gerçek Rakamlar
Bu mimariyi çalıştırmanın gerçek maliyetini hesaplayalım:
S3 PUT İstekleri (Yazma Operasyonları)
Oyununuz oyun sırasında her 30 saniyede bir otomatik kayıt yapıyorsa, tipik bir 2 saatlik oturum yaklaşık 240 PUT isteği üretir. S3 fiyatlandırması 1.000 istek başına $0.005 olduğunda:
- Oturum başına maliyet: $0.0012
- Ayda 10.000 oyuncu-oturumu başına maliyet: $1.20
S3 GET İstekleri (Okuma Operasyonları)
Oturum başında mevcut kayıtları indirmek için bir GET isteği:
- 10.000 oturum başına maliyet: $0.40
S3 Depolama
Ortalama kayıt dosyası: oyuncu başına 5 MB. 10.000 aylık aktif oyuncu için:
- Toplam depolama: ~50 GB
- $0.023/GB ile aylık maliyet: $1.15
10.000 MAU için Toplam Aylık Maliyet
Ayda $3'ün altında. Bu, compute maliyetlerine kıyasla ihmal edilebilir düzeydedir. S3 katmanı indie ölçeğinde pratik olarak ücretsizdir.
Üretim Optimizasyonu: Debounce'lu Senkronizasyon
Ham file watcher her yazmada tetiklenir. Sık sık (her 10-15 saniyede bir) otomatik kayıt yapan bir oyun aşırı PUT isteği üretir. Bir debounce gecikmesi ekleyin — senkronize etmeden önce son değişiklikten sonra 5 saniye bekleyin:
# Debounce logic — add to the change handler in watch-save.ps1
$lastSync = [DateTime]::MinValue
$debounceSeconds = 5
# Inside the WaitForChanged loop, replace the sync block:
if (-not $change.TimedOut) {
$now = Get-Date
if (($now - $lastSync).TotalSeconds -ge $debounceSeconds) {
& aws s3 cp $WatchPath $S3Path @regionArgs 2>&1
$lastSync = $now
}
}
Bu, minimum veri kaybı riskiyle PUT isteklerini %60-80 oranında azaltır. Instance debounce sırasında sonlandırılsa bile en fazla 5 saniyelik ilerleme kaybedersiniz — çoğu oyun için kabul edilebilir.
Uç Durumlar ve Hata Modları
Kayıt Dosyası Henüz Yok (İlk Kez Oynayanlar)
İndirme adımı başarısız olur — bu doğru davranıştır. Oyun yeni bir kayıt oluşturur ve file watcher ilk yazmada onu algılar. Özel bir işlem gerekmez.
Ağ Kesintisinden Kaynaklanan Bozuk Yükleme
Bir PUT isteği sırasında ağ kesilirse S3 nesnesi eksik kalabilir. Yükleme sonrası ETag doğrulayarak bu riski azaltın:
# After syncing, verify upload integrity
$localHash = (Get-FileHash -Path $WatchPath -Algorithm MD5).Hash.ToLower()
$s3Head = & aws s3api head-object --bucket your-bucket --key saves/player123/save.dat 2>&1
$s3ETag = ($s3Head | ConvertFrom-Json).ETag.Trim('"')
if ($localHash -ne $s3ETag) {
Add-Content -Path $LogFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - INTEGRITY MISMATCH - re-uploading"
& aws s3 cp $WatchPath $S3Path @regionArgs 2>&1
}
Birden Fazla Kayıt Yuvası
Oyununuz birden fazla kayıt dosyasını destekliyorsa, tek bir dosya yerine bir dizini izleyin. $watcher.Filter değerini * yapın ve tüm kayıt dizinini senkronize edin. Çok sayıda dosya varsa, yüklemeden önce tek bir .zip dosyasına arşivleyin.
Kayıt Dosyası Boyut Sınırları
S3, 5 TB'a kadar nesneleri destekler, bu nedenle dosya boyutu pratik bir sorun değildir. Çoğu oyun kaydı 500 KB ile 50 MB arasındadır. Devasa durum içeren prosedürel olarak üretilmiş dünyalarla (>500 MB) uğraşıyorsanız, yüklemeden önce gzip ile sıkıştırmayı düşünün — tipik kayıt verisi orijinal boyutun %20-40'ına sıkışır.
Oyuncunun Birden Fazla Cihazda Oynaması
Bir oyuncu aynı gün içinde farklı cihazlardan akış başlatırsa, son oturumun kaydı bir öncekinin üzerine yazar. Çoğu tek oyunculu oyun için bu doğru davranıştır. Merge semantiği gerektiren oyunlar için (bulut senkronlu envanter gibi) çakışma çözümleme mantığına ihtiyacınız olacaktır — bu noktada dedicated bir backend servisi vazgeçilmez hale gelir.
En İyi Uygulamalar
IAM rollerini belirli S3 prefix'leriyle sınırlayın — Bucket genelinde izinler yerine
arn:aws:s3:::your-bucket/saves/*kullanın. Bu, least-privilege ilkesine uyar ve kimlik bilgileri sızarsa hasarı sınırlar. Rol adı, platform gereksinimlerini karşılamak içinGameLiftStreams-ile başlamalıdır.Oyuncu başına S3 anahtarlarını kimliği doğrulanmış kimlikten oluşturun —
saves/{platform-user-id}/save.datkullanın, aslasaves/{session-id}/save.datdeğil. Session ID'ler oturumlar arasında değişir ve kayıt verisini kalıcı olarak sahipsiz bırakır.Üretimde debounce'lu senkronizasyon uygulayın — Ham dosya sistemi izleyicileri her kayıtta bir PUT isteği üretir. 5 saniyelik debounce penceresi, veri kaybı riskini bir otomatik kayıt aralığının altında tutarken API çağrılarını %60-80 azaltır.
Her senkronizasyon işlemini loglayın — Kayıt hataları, oyuncular saatlerce ilerleme kaybedene kadar görünmez. Yukarıdaki script'lerdeki
$LogFiledeseni, instance üzerinde kalıcı bir denetim izi sağlar. Proaktif uyarılar için bu logları izleme sisteminize gönderin.Yalnızca bağlantı kesmeyle değil, instance sonlandırmayla da test edin — Debounce'lu senkronizasyon pencerenizin kabul edilebilir olduğunu doğrulamak için streaming instance'ını oyun ortasında öldürün. Düzgün bir bağlantı kesme, son senkronizasyonun tamamlanması için zaman tanır; ani bir sonlandırma tanımaz.
BaaS Kestirmesi: Bunu Kendiniz Kurmak Ne Zaman Değmez
Yukarıdaki mimari çalışıyor — sahada kanıtlanmış durumda ve çalıştırması neredeyse hiçbir maliyeti yok. Ancak IAM rolleri, launcher script'leri, file watcher'lar, bütünlük kontrolleri, Linux varyantları ve ortam başına yapılandırma yönetmenizi gerektirir. Küçük bir ekip için bu, gerçek oyununuzla hiçbir ilgisi olmayan 2-4 günlük altyapı işidir.
horizOn, kalıcı oyuncu verisi depolamayı yönetilen bir servis olarak sunar — kayıt dosyaları, envanter, ilerleme, tercihler — tek bir API çağrısıyla. IAM yapılandırması yok, launcher script yok, file watcher yok, bütünlük doğrulaması yok. Backend, kimlik doğrulamayı, çakışma çözümlemeyi ve çapraz platform yedekliliğini otomatik olarak yönetir. Altyapıda hata ayıklamak yerine oyun teslim etmek isteyen ekipler için "yerelde çalışıyor, streaming'de bozuluyor" hatalarının tüm kategorisini ortadan kaldırır. Entegrasyon sürecinin tamamını son backend güncelleme anlatımımızda belgeledik.
Özet
Cloud streaming'de kalıcı oyun kayıtları, instance dosya sisteminin geçici olduğunu kabul etmeyi ve dayanıklı object storage'ı tek doğruluk kaynağı olarak kullanmayı gerektirir. Tam mimari:
- IAM rolü, streaming oturumlarına kapsamlı S3 okuma/yazma erişimi sağlar
- Launcher script, oyun başlatılmadan önce mevcut kayıtları indirir
- Arka plan file watcher'ı, debounce'lu yazmalarla oyun sırasında değişiklikleri S3'e senkronize eder
- Backend servisi, kimliği doğrulanmış oyuncu kimliğinden oyuncu başına S3 yolları oluşturur
- Bütünlük kontrolleri, her senkronizasyonda yükleme/indirme doğruluğunu doğrular
İndie ölçeğinde maliyet, 10.000 aktif oyuncu için ayda $3'ün altındadır. Uygulama süresi, kendiniz kurarsanız 1-2 gün veya horizOn'un oyuncu verisi API'lerini kullanırsanız 30 dakikadır.
Her durumda, bu sorunu çözmeden cloud streaming oyunu yayınlamayın. Oyuncularınız kayıtlarının yok olduğunu size söylemez — sadece oynamayı bırakırlar.
Kaynak: Amazon GameLift Streams'e kalıcı oyun kayıtları ekleme