Rust ile Yüksek Performanslı Web Uygulaması Geliştirme
Performans sorunları her sysadmin’in kabusu haline gelir. Kullanıcılar şikayet eder, metrikler kötüleşir, gecenin üçünde alarm çalar. Rust ve WebAssembly ikilisi tam da bu noktada devreye giriyor. Bu yazıda gerçek dünya senaryoları üzerinden, yüksek performanslı web uygulamalarını nasıl geliştireceğimizi ve bunu edge computing ortamlarına nasıl taşıyacağımızı ele alacağız.
Neden Rust ve WebAssembly?
Klasik bir senaryo: E-ticaret platformunuzun anlık fiyat hesaplama servisi, yüksek trafikte çöküyor. Node.js tabanlı servis garbage collector baskısı altında kalıyor, yanıt süreleri 2 saniyeyi geçiyor. İşte tam bu noktada Rust devreye giriyor.
Rust’ın sysadmin perspektifinden öne çıkan özellikleri şunlar:
- Bellek güvenliği: Garbage collector yok, runtime overhead yok
- Zero-cost abstractions: Yüksek seviyeli kod yazıyorsunuz ama C performansı elde ediyorsunuz
- WebAssembly desteği: Aynı kodu hem sunucuda hem tarayıcıda hem de edge node’larda çalıştırabiliyorsunuz
- Concurrency: Data race’ler derleme zamanında yakalanıyor
WebAssembly ise Rust kodunu tarayıcıdan Cloudflare Workers’a, Fastly Compute@Edge’den kendi edge node’larınıza kadar her yerde çalıştırmanıza olanak tanıyor.
Geliştirme Ortamını Kurmak
Önce temelleri atalım. Rust toolchain kurulumu standart bir işlem ama WebAssembly hedefi için ekstra adımlar gerekiyor.
# Rust toolchain kurulumu
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
# Rust sürümünü kontrol et
rustc --version
cargo --version
# WebAssembly hedefini ekle
rustup target add wasm32-unknown-unknown
rustup target add wasm32-wasi
# wasm-pack kurulumu (WebAssembly paketleme aracı)
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
# wasm-bindgen-cli kurulumu
cargo install wasm-bindgen-cli
# Geliştirme için araçlar
cargo install cargo-watch
cargo install cargo-flamegraph
WASI hedefi sunucu taraflı WebAssembly için kritik. wasm32-unknown-unknown tarayıcı ortamları için kullanılırken, wasm32-wasi sistem çağrılarına ihtiyaç duyan edge runtime’lar için kullanılıyor.
İlk Yüksek Performanslı Servis
Gerçek dünya senaryomuza geçelim: Anlık kur hesaplama servisi. Bu servis saniyede binlerce istek alacak, her milisaniye önemli.
Önce proje yapısını kuralım:
# Yeni proje oluştur
cargo new --lib exchange-rate-wasm
cd exchange-rate-wasm
# Proje yapısını kontrol et
ls -la src/
# Gerekli bağımlılıkları ekle
cat >> Cargo.toml << 'EOF'
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.6"
js-sys = "0.3"
[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
strip = true
EOF
opt-level = "z" boyut optimizasyonu için kritik. WebAssembly modülünüzü edge node’lara dağıtırken dosya boyutu doğrudan cold start süresini etkiliyor.
Şimdi asıl servisi yazalım:
cat > src/lib.rs << 'EOF'
use wasm_bindgen::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct ExchangeRequest {
pub amount: f64,
pub from_currency: String,
pub to_currency: String,
}
#[derive(Serialize, Deserialize)]
pub struct ExchangeResponse {
pub converted_amount: f64,
pub rate: f64,
pub timestamp: u64,
pub fee: f64,
}
// Sabit kur tablosu (gerçek uygulamada harici kaynaktan gelir)
static RATES: &[(&str, &str, f64)] = &[
("USD", "TRY", 32.45),
("EUR", "TRY", 35.12),
("USD", "EUR", 0.92),
("GBP", "TRY", 41.23),
("TRY", "USD", 0.0308),
];
#[wasm_bindgen]
pub fn calculate_exchange(request_json: &str) -> String {
let request: ExchangeRequest = match serde_json::from_str(request_json) {
Ok(r) => r,
Err(e) => return format!("{{"error": "{}"}}", e),
};
let rate = find_rate(&request.from_currency, &request.to_currency)
.unwrap_or(0.0);
let fee = request.amount * 0.001; // %0.1 işlem ücreti
let converted = (request.amount - fee) * rate;
let response = ExchangeResponse {
converted_amount: (converted * 100.0).round() / 100.0,
rate,
timestamp: js_sys::Date::now() as u64,
fee: (fee * 100.0).round() / 100.0,
};
serde_json::to_string(&response).unwrap_or_default()
}
fn find_rate(from: &str, to: &str) -> Option<f64> {
RATES.iter()
.find(|(f, t, _)| *f == from && *t == to)
.map(|(_, _, rate)| *rate)
}
#[wasm_bindgen]
pub fn validate_amount(amount: f64, currency: &str) -> bool {
match currency {
"USD" | "EUR" | "GBP" => amount >= 1.0 && amount <= 100_000.0,
"TRY" => amount >= 10.0 && amount <= 3_000_000.0,
_ => false,
}
}
EOF
Derleme ve Optimizasyon Süreci
Kodu yazdık, şimdi derleyip optimize edelim:
# WebAssembly olarak derle
wasm-pack build --target web --release
# Çıktı dosyalarını incele
ls -lah pkg/
# wasm dosyasının boyutunu kontrol et
wc -c pkg/exchange_rate_wasm_bg.wasm
# Boyutu daha da küçültmek için wasm-opt kullan
# binaryen paketinden geliyor
sudo apt-get install binaryen -y
wasm-opt -Oz
pkg/exchange_rate_wasm_bg.wasm
-o pkg/exchange_rate_wasm_bg.wasm
# Optimizasyon sonrası boyut
wc -c pkg/exchange_rate_wasm_bg.wasm
# WASI hedefi için ayrı derleme (edge runtime)
cargo build --target wasm32-wasi --release
ls -lah target/wasm32-wasi/release/*.wasm
wasm-opt -Oz bazen dosya boyutunu %30-40 küçültüyor. Production ortamında bu adımı CI/CD pipeline’ınıza mutlaka ekleyin.
Actix-web ile Yüksek Performanslı HTTP Sunucusu
WebAssembly modülümüzü servis eden backend’i Rust’ta yazalım. Actix-web, Rust ekosisteminin en hızlı web framework’lerinden biri:
# Backend projesi
cargo new exchange-api
cd exchange-api
cat >> Cargo.toml << 'EOF'
[dependencies]
actix-web = "4"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
wasmtime = "15"
anyhow = "1"
tracing = "0.1"
tracing-subscriber = "0.3"
prometheus = "0.13"
lazy_static = "1"
EOF
cat > src/main.rs << 'EOF'
use actix_web::{web, App, HttpServer, HttpResponse, middleware};
use prometheus::{Counter, Histogram, register_counter, register_histogram};
use lazy_static::lazy_static;
use std::sync::Arc;
use wasmtime::*;
lazy_static! {
static ref REQUEST_COUNTER: Counter = register_counter!(
"exchange_requests_total",
"Toplam işlem sayısı"
).unwrap();
static ref REQUEST_DURATION: Histogram = register_histogram!(
"exchange_request_duration_seconds",
"İstek işlem süresi"
).unwrap();
}
struct AppState {
wasm_engine: Arc<Engine>,
wasm_module: Arc<Module>,
}
async fn calculate(
state: web::Data<AppState>,
body: web::Json<serde_json::Value>,
) -> HttpResponse {
let timer = REQUEST_DURATION.start_timer();
REQUEST_COUNTER.inc();
// WASM modülünü çalıştır
let mut store = Store::new(&state.wasm_engine, ());
let instance = Instance::new(
&mut store,
&state.wasm_module,
&[]
).unwrap();
// Fonksiyonu çağır
let func = instance
.get_typed_func::<(), i32>(&mut store, "calculate_exchange")
.unwrap();
let result = func.call(&mut store, ());
timer.observe_duration();
HttpResponse::Ok().json(result.unwrap_or(0))
}
async fn metrics() -> HttpResponse {
use prometheus::Encoder;
let encoder = prometheus::TextEncoder::new();
let mut buffer = Vec::new();
encoder.encode(&prometheus::gather(), &mut buffer).unwrap();
HttpResponse::Ok()
.content_type("text/plain")
.body(buffer)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
tracing_subscriber::init();
let engine = Engine::default();
let module = Module::from_file(
&engine,
"exchange_rate_wasm_bg.wasm"
).expect("WASM modülü yüklenemedi");
let state = web::Data::new(AppState {
wasm_engine: Arc::new(engine),
wasm_module: Arc::new(module),
});
println!("Sunucu başlıyor: 0.0.0.0:8080");
HttpServer::new(move || {
App::new()
.app_data(state.clone())
.app_data(web::JsonConfig::default().limit(1024))
.route("/calculate", web::post().to(calculate))
.route("/metrics", web::get().to(metrics))
.wrap(middleware::Logger::default())
.wrap(middleware::Compress::default())
})
.workers(num_cpus::get())
.bind("0.0.0.0:8080")?
.run()
.await
}
EOF
Cloudflare Workers’a Deploy Etmek
Edge computing’in gücünden yararlanmak için Cloudflare Workers’a deploy edelim. Bu sayede kodumuz kullanıcıya en yakın edge node’da çalışacak:
# Wrangler CLI kurulumu
npm install -g wrangler
# Cloudflare hesabına giriş
wrangler login
# Workers projesi oluştur
mkdir exchange-worker && cd exchange-worker
# wrangler.toml yapılandırması
cat > wrangler.toml << 'EOF'
name = "exchange-rate-worker"
main = "src/index.js"
compatibility_date = "2024-01-15"
[build]
command = "wasm-pack build --target bundler"
[[rules]]
type = "CompiledWasm"
globs = ["**/*.wasm"]
fallthrough = true
[vars]
ENVIRONMENT = "production"
[limits]
cpu_ms = 50
EOF
# Worker JavaScript entry point
cat > src/index.js << 'EOF'
import init, { calculate_exchange, validate_amount } from '../pkg/exchange_rate_wasm.js';
import wasm from '../pkg/exchange_rate_wasm_bg.wasm';
let initialized = false;
async function ensureInit() {
if (!initialized) {
await init(wasm);
initialized = true;
}
}
export default {
async fetch(request, env) {
await ensureInit();
const url = new URL(request.url);
if (url.pathname === '/exchange' && request.method === 'POST') {
const body = await request.json();
if (!validate_amount(body.amount, body.from_currency)) {
return new Response(
JSON.stringify({ error: 'Geçersiz tutar' }),
{
status: 400,
headers: { 'Content-Type': 'application/json' }
}
);
}
const result = calculate_exchange(JSON.stringify(body));
return new Response(result, {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-store',
'X-Edge-Location': request.cf?.colo || 'unknown'
}
});
}
return new Response('Not Found', { status: 404 });
}
};
EOF
# Deploy et
wrangler deploy
# Log'ları izle
wrangler tail
Performans Testi ve Karşılaştırma
Deploy ettik, şimdi gerçek performansı ölçelim:
# wrk kurulumu (HTTP benchmark aracı)
sudo apt-get install wrk -y
# Önce Node.js versiyonu ile kıyaslama
# Node.js servisini başlat (farklı port)
node exchange-node.js &
NODE_PID=$!
# Rust/WASM servisi
cargo run --release &
RUST_PID=$!
sleep 2
# Test isteği dosyası
cat > test_payload.json << 'EOF'
{"amount": 1000, "from_currency": "USD", "to_currency": "TRY"}
EOF
# Node.js performans testi
echo "=== Node.js Performans Testi ==="
wrk -t4 -c100 -d30s
-s post.lua
--latency
http://localhost:3000/exchange
# Rust/WASM performans testi
echo "=== Rust/WASM Performans Testi ==="
wrk -t4 -c100 -d30s
-s post.lua
--latency
http://localhost:8080/calculate
# Bellek kullanımı karşılaştırması
echo "=== Bellek Kullanımı ==="
ps aux | grep -E "(node|exchange-api)" | awk '{print $1, $6"KB", $11}'
# CPU profiling
sudo perf record -g ./target/release/exchange-api &
sleep 10
sudo perf report
# Temizlik
kill $NODE_PID $RUST_PID
Gerçek senaryoda Rust/WASM kombinasyonu Node.js’e göre genellikle 3-5x daha yüksek throughput ve %70-80 daha düşük bellek kullanımı sağlıyor. p99 gecikme süreleri ise dramatik şekilde düşüyor.
Üretim Ortamı için Systemd ve İzleme
# Systemd servis dosyası oluştur
sudo tee /etc/systemd/system/exchange-api.service << 'EOF'
[Unit]
Description=Exchange Rate API (Rust/WASM)
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=exchange
Group=exchange
WorkingDirectory=/opt/exchange-api
ExecStart=/opt/exchange-api/exchange-api
Restart=on-failure
RestartSec=5
# Güvenlik kısıtlamaları
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/exchange-api
PrivateTmp=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
# Kaynak limitleri
LimitNOFILE=65536
LimitNPROC=512
# Ortam değişkenleri
Environment=RUST_LOG=info
Environment=RUST_BACKTRACE=0
Environment=PORT=8080
Environment=WORKERS=0
[Install]
WantedBy=multi-user.target
EOF
# Kullanıcı oluştur
sudo useradd --system --no-create-home --shell /bin/false exchange
# Binary'yi kopyala
sudo cp target/release/exchange-api /opt/exchange-api/
sudo cp pkg/exchange_rate_wasm_bg.wasm /opt/exchange-api/
sudo chown -R exchange:exchange /opt/exchange-api
# Servisi başlat
sudo systemctl daemon-reload
sudo systemctl enable exchange-api
sudo systemctl start exchange-api
# Durumu kontrol et
sudo systemctl status exchange-api
# Prometheus metrikleri için Nginx reverse proxy
sudo tee /etc/nginx/sites-available/exchange-api << 'EOF'
upstream exchange_backend {
server 127.0.0.1:8080;
keepalive 64;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/ssl/certs/api.example.com.crt;
ssl_certificate_key /etc/ssl/private/api.example.com.key;
location / {
proxy_pass http://exchange_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 10s;
proxy_connect_timeout 5s;
}
location /metrics {
deny all;
}
}
EOF
sudo ln -s /etc/nginx/sites-available/exchange-api /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Sonuç
Rust ve WebAssembly kombinasyonu, yüksek performanslı web uygulamaları için artık olgunlaşmış bir ekosistem sunuyor. Bu yazıda geliştirdiğimiz pipeline şu avantajları getiriyor: Aynı Rust kodu hem Cloudflare Workers gibi edge platformlarda hem de kendi Actix-web sunucunuzda çalışıyor. WASM modülleri sandbox ortamında çalıştığı için güvenlik riski minimuma iniyor. wasm-opt ve release profil ayarlarıyla küçük binary boyutları elde ediyorsunuz, bu da cold start sürelerini doğrudan etkiliyor. Garbage collector yokluğu sayesinde gecikme süreleri tahmin edilebilir kalıyor ve p99 değerleri dramatik şekilde düşüyor.
Tabii ki her şey mükemmel değil. Rust’ın öğrenme eğrisi ciddi, özellikle ownership sistemi ilk başta kafa karıştırıcı olabiliyor. WASM ekosistemi hâlâ gelişmeye devam ediyor, bazı sistem çağrıları WASI üzerinden kısıtlı. Ancak kritik performans gereksinimleri olan servisler için bu yatırım kesinlikle değiyor.
Başlangıç için tavsiyem şu: Mevcut uygulamanızın en yoğun hesaplama yapan kısımlarını tespit edin ve sadece o bölümleri Rust/WASM’a taşıyın. Tam yeniden yazım yerine hibrit yaklaşım çok daha pratik ve risksiz bir geçiş sağlıyor.
