Server-Side HTTP Client'lar: Native Fetch'ten Effect'e, Production Deneyimleri

Node.js HTTP client'larının kapsamlı karşılaştırması: performans testleri, circuit breaker pattern'ler ve gerçek production hikayeleri

Bize $50K'ya Mal Olan HTTP Client Hatası#

Üç yıl önce, microservice mimarimiz gayet güzel çalışıyordu. Yirmi yedi servis, HTTP üzerinden mutlu mesut haberleşiyordu. Sonra Black Friday geldi ve payment servisimiz timeout'a düşmeye başladı. Fail etmiyordu—sadece takılıyordu. 30 saniye boyunca. Her request'te.

Suçlu? Native fetch'i proper timeout handling olmadan kullanıyorduk. O takılan connection'lar tüm Lambda concurrent execution'larımızı tüketti. O ayki AWS faturası: Bütçenin $50K üzerinde. Acıttı.

Bu pahalı ders bana HTTP client seçiminin sadece feature'larla ilgili olmadığını öğretti—sabah 3'te on-call telefonun çaldığında neyin bozulacağını anlamakla ilgili.

Server-Side HTTP Client'lar Neden Düşündüğünüzden Daha Önemli#

Browser'da HTTP client'lar basit. Request yaparsın, response'u handle edersin, bitti. Server-side? İşte orada işler ilginçleşiyor:

  • Connection pooling saniyede binlerce request yaparken kritik hale geliyor
  • Memory leak'ler Node.js process'inizi günler içinde yavaşça öldürebilir
  • Circuit breaker'lar graceful degradation ile cascading failure arasındaki fark
  • Retry stratejileri network probleminin outage'a dönüşüp dönüşmeyeceğini belirler

Gelin her büyük oyuncuya bakalım ve production gerçekliğiyle nasıl başa çıktıklarını görelim.

Native Fetch: Her Zaman Yeterli Olmayan Varsayılan#

Node.js 18'den beri native fetch var. Her yerde kullanmak cazip—zero dependency, standard API, sevmemek için ne var?

TypeScript
// Yeterince basit görünüyor
const response = await fetch('https://api.example.com/data', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ key: 'value' })
});

Native Fetch'in Parladığı Yerler#

  • Zero dependency: Docker image'larınız yalın kalıyor
  • Standard API: Aynı kod browser, Node.js, Deno, Bun'da çalışıyor
  • Modern: Arka planda undici kullanıyor (Node.js 18'den beri)

Yetersiz Kaldığı Yerler#

Production'da bizi yakayan şey:

TypeScript
// Timeout tuzağı - bu düşündüğünüzü yapmıyor
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);

try {
  const response = await fetch('https://slow-api.com', {
    signal: controller.signal
  });
} catch (error) {
  // Bu abort'u yakalar, ama TCP connection hala açık olabilir!
}

AbortController sadece JavaScript tarafını iptal eder. Alttaki TCP connection? O yavaşça connection pool'unuzu yiyerek kalabilir.

Production Kararı#

Native fetch'i şunlar için kullanın:

  • Basit script'ler ve CLI tool'ları
  • Prototype'lar ve POC'ler
  • Hem client hem server'ı kontrol ettiğinizde

Şunlardan kaçının:

  • Retry, circuit breaker veya connection pooling gerektiğinde
  • Saniyede binlerce request yaparken
  • Güvenilmez third-party API'larla entegre olurken

Axios: İsviçre Çakısı#

Axios haftada 45 milyon download ile en popüler seçim olmaya devam ediyor. Her yerde olmasının bir nedeni var.

TypeScript
import axios from 'axios';
import axiosRetry from 'axios-retry';

// Production-ready konfigürasyon
const client = axios.create({
  timeout: 10000,
  maxRedirects: 5,
  validateStatus: (status) => status <500
});

// Retry logic ekle
axiosRetry(client, {
  retries: 3,
  retryDelay: axiosRetry.exponentialDelay,
  retryCondition: (error) => {
    return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
           error.response?.status === 429; // Rate limited
  }
});

// Logging için request/response interceptor'lar
client.interceptors.request.use((config) => {
  config.headers['X-Request-ID'] = generateRequestId();
  logger.info('Giden request', { 
    method: config.method, 
    url: config.url 
  });
  return config;
});

Bulduğumuz Memory Leak#

Geçen yıl, Axios'un 502 error'ları handle ederken memory leak yaptığını keşfettik. Sorun follow-redirects dependency'sindeydi. Nasıl bulduğumuza bakalım:

TypeScript
// Memory leak reprodüksiyon
async function leakTest() {
  const promises = [];
  for (let i = 0; i <10000; i++) {
    promises.push(
      axios.get('https://api.returns-502.com')
        .catch(() => {}) // Error object'ler memory'de kalıyordu!
    );
  }
  await Promise.all(promises);
  // Heap snapshot'ı burada kontrol et - HTML error response'ları hala memory'de
}

Connection Pooling Düzeltmesi#

Düz Axios her request için yeni connection açar. Scale'de bu server'ınızı öldürür:

TypeScript
import Agent from 'agentkeepalive';

const keepAliveAgent = new Agent({
  maxSockets: 100,
  maxFreeSockets: 10,
  timeout: 60000,
  freeSocketTimeout: 30000
});

const client = axios.create({
  httpAgent: keepAliveAgent,
  httpsAgent: new Agent.HttpsAgent(keepAliveAgent.options)
});

Production Kararı#

Axios hala şunlar için sağlam:

  • Kompleks request/response transformation'lar
  • Kapsamlı middleware ihtiyacı olduğunda
  • Zaten aşina olan takımlar

Ama şunlara dikkat:

  • Bundle size (1.84MB unzipped)
  • Error response'larla memory leak'ler
  • Connection pooling ekstra setup gerektiriyor

Undici: Performans Şampiyonu#

Undici, Node.js fetch'i içerde güçlendiren şey. Ama direkt kullanmak size süper güçler veriyor.

TypeScript
import { request, Agent } from 'undici';

const agent = new Agent({
  connections: 100,
  pipelining: 10, // HTTP/1.1 pipelining
  keepAliveTimeout: 60 * 1000,
  keepAliveMaxTimeout: 600 * 1000
});

// High-throughput senaryolar için axios'tan 3x daha hızlı
const { statusCode, body } = await request('https://api.example.com', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ data: 'value' }),
  dispatcher: agent
});

Performans Rakamları#

Payment servisimizde benchmark yaptık (1000 concurrent request):

Text
Library      | Avg Latency | P99 Latency | Throughput | Memory
-------------|-------------|-------------|------------|--------
Undici       | 23ms        | 89ms        | 4,235 rps  | 124MB
Native Fetch | 31ms        | 156ms       | 3,122 rps  | 156MB
Axios        | 42ms        | 234ms       | 2,234 rps  | 289MB
Got          | 38ms        | 189ms       | 2,567 rps  | 234MB

HTTP/2 Desteği#

Undici HTTP/2 destekliyor, ama açıkça aktive edilmesi gerekiyor:

TypeScript
import { Agent, request } from 'undici';

// HTTP/2 aktif agent oluştur
const h2Agent = new Agent({
  allowH2: true,  // HTTP/2'yi aktive et
  connections: 50,
  pipelining: 0   // HTTP/2 için pipelining'i deaktive et
});

// Belirli HTTP/2 endpoint'leriyle kullan
const response = await request('https://http2.example.com/api', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ data: 'value' }),
  dispatcher: h2Agent
});

// Veya global dispatcher ile
import { setGlobalDispatcher } from 'undici';
setGlobalDispatcher(h2Agent);

// Artık tüm fetch çağrıları mevcut olduğunda HTTP/2 kullanıyor
const h2Response = await fetch('https://http2.example.com/data');

HTTP/2 birden çok paralel request için önemli performance faydası sağlıyor:

TypeScript
// Benchmark: 50 concurrent request ile HTTP/1.1 vs HTTP/2
const h1Agent = new Agent({ allowH2: false });
const h2Agent = new Agent({ allowH2: true });

// HTTP/1.1: ~200ms ortalama (connection overhead)
// HTTP/2: ~80ms ortalama (multiplexing avantajı)

Production Kararı#

Undici şunlarda mükemmel:

  • High-throughput microservice'ler
  • Her milisaniye önemli olduğunda
  • Memory kısıtlı ortamlar

Şunlarda pas geçin:

  • Native HTTP/2 gerekiyorsa
  • Takımınız higher-level abstraction'ları tercih ediyorsa
  • Axios'tan migrate ediyorsanız (çok farklı)

Effect: Fonksiyonel Güç Merkezi#

Effect tamamen farklı bir yaklaşım benimsiyor. Promise'ler yerine, built-in error handling'li composable effect'ler alıyorsunuz.

TypeScript
import { Effect, Schedule, Duration } from 'effect';
import { HttpClient, HttpClientError } from '@effect/platform';

// Otomatik retry'lı API client tanımla
const apiClient = HttpClient.HttpClient.pipe(
  HttpClient.retry(
    Schedule.exponential(Duration.seconds(1), 2).pipe(
      Schedule.jittered,
      Schedule.either(Schedule.recurs(3))
    )
  ),
  HttpClient.filterStatusOk
);

// Type-safe error handling
const fetchUser = (id: string) =>
  Effect.gen(function* (_) {
    const response = yield* _(
      apiClient.get(`/users/${id}`),
      Effect.catchTag('HttpClientError', (error) => {
        if (error.response?.status === 404) {
          return Effect.succeed({ found: false });
        }
        return Effect.fail(error);
      })
    );
    
    return yield* _(response.json);
  });

Öğrenme Eğrisi Hikayesi#

Effect'i bir takıma tanıttık. 1. Hafta: kafa karışıklığı. 2. Hafta: frustrasyon. 4. Hafta: "Asla geri dönmeyeceğiz." Type-safe error handling bütün bir bug sınıfını ortadan kaldırdı.

TypeScript
// Effect öncesi: Runtime sürprizleri
async function riskyOperation() {
  try {
    const user = await fetchUser();
    const orders = await fetchOrders(user.id); // Fail edebilir
    return processOrders(orders); // Bu da fail edebilir
  } catch (error) {
    // Network mi? Auth mi? Business logic mi? Kim bilir!
    logger.error('Bir şeyler fail etti', error);
  }
}

// Effect ile: Error'lar type'ın parçası
const safeOperation = Effect.gen(function* (_) {
  const user = yield* _(fetchUser);
  const orders = yield* _(fetchOrders(user.id));
  return yield* _(processOrders(orders));
}).pipe(
  Effect.catchTags({
    NetworkError: (e) => logAndRetry(e),
    AuthError: (e) => refreshTokenAndRetry(e),
    ValidationError: (e) => Effect.fail(new BadRequest(e))
  })
);

Production Kararı#

Effect şunlar için mükemmel:

  • Birden çok failure mode'u olan kompleks business logic
  • Functional programming'e rahat takımlar
  • Type safety kritik olduğunda

İki kere düşünün eğer:

  • Takımınız FP konseptlerine yeni
  • Junior'ları hızlıca onboard etmeniz gerekiyor
  • Basit bir CRUD servisi

Diğerleri: Hızlı Turlar#

Got: Node.js Uzmanı#

TypeScript
import got from 'got';

const client = got.extend({
  timeout: { request: 10000 },
  retry: {
    limit: 3,
    methods: ['GET', 'PUT', 'DELETE'],
    statusCodes: [408, 429, 500, 502, 503, 504],
    errorCodes: ['ETIMEDOUT', 'ECONNRESET'],
    calculateDelay: ({ attemptCount }) => attemptCount * 1000
  },
  hooks: {
    beforeRetry: [(error, retryCount) => {
      logger.warn(`Retry denemesi ${retryCount}`, error.message);
    }]
  }
});

Sadece Node.js projeleri için harika. Built-in pagination desteği güzel.

Ky: Hafif Fetch Wrapper#

TypeScript
import ky from 'ky';

const api = ky.create({
  prefixUrl: 'https://api.example.com',
  timeout: 10000,
  retry: {
    limit: 2,
    methods: ['get', 'put', 'delete'],
    statusCodes: [408, 429, 500, 502, 503, 504]
  }
});

Minimal overhead ile pilli fetch istediğinizde mükemmel.

SuperAgent: Hala Hayatta#

TypeScript
import superagent from 'superagent';

superagent
  .post('/api/users')
  .send({ name: 'John' })
  .retry(3, (err, res) => {
    if (err) return true;
    return res.status >= 500;
  })
  .end((err, res) => {
    // Callback style hala çalışıyor
  });

Plugin sistemi güçlü, ama Axios popülerlik yarışını kazandı.

Circuit Breaker'lar: Production Kurtarıcınız#

Hangi HTTP client'ı seçerseniz seçin, circuit breaker ekleyin. İşte Cockatiel ile production setup'ımız:

TypeScript
import { circuitBreaker, retry, wrap, ExponentialBackoff } from 'cockatiel';

// 5 ardışık failure'dan sonra açılan circuit breaker
const breaker = circuitBreaker({
  halfOpenAfter: 10000,
  breaker: new ConsecutiveBreaker(5)
});

// Exponential backoff'lu retry policy
const retryPolicy = retry({
  maxAttempts: 3,
  backoff: new ExponentialBackoff()
});

// Birleştir
const resilientFetch = wrap(
  retryPolicy,
  breaker,
  async (url: string) => {
    const response = await undici.request(url);
    if (response.statusCode >= 500) {
      throw new Error(`Server error: ${response.statusCode}`);
    }
    return response;
  }
);

// Kullanım
try {
  const data = await resilientFetch('https://flaky-api.com/data');
} catch (error) {
  if (breaker.state === 'open') {
    // Circuit açık, fallback kullan
    return getCachedData();
  }
  throw error;
}

Circuit Breaker Black Friday'imizi Kurtardı#

Gerçek hikaye: Payment provider'da aralıklı 30 saniyelik timeout'lar vardı. Circuit breaker olmadan: tüm checkout flow'u bloklandı. Circuit breaker ile: 5 failure'dan sonra anında backup provider'a failover yaptı. Kurtarılan gelir: $2M.

Production Monitoring Setup#

Hangi client'ı seçerseniz seçin, instrument edin:

TypeScript
import { metrics } from '@opentelemetry/api-metrics';

const meter = metrics.getMeter('http-client');
const requestDuration = meter.createHistogram('http.request.duration');
const requestCount = meter.createCounter('http.request.count');

// HTTP client'ınızı wrap edin
async function instrumentedRequest(url: string, options: any) {
  const start = Date.now();
  const labels = { method: options.method, url: new URL(url).hostname };
  
  try {
    const response = await yourHttpClient(url, options);
    labels.status = response.status;
    labels.success = 'true';
    return response;
  } catch (error) {
    labels.status = error.response?.status || 0;
    labels.success = 'false';
    throw error;
  } finally {
    requestDuration.record(Date.now() - start, labels);
    requestCount.add(1, labels);
  }
}

Karar Matrisi#

Yıllarca production deneyiminden sonra, tavsiye matrisim:

Use Caseİlk Tercihİkinci TercihKaçının
High-throughput microservice'lerUndiciGotNative Fetch
Kompleks enterprise API'larAxiosEffectKy
Functional programming takımıEffect-SuperAgent
Basit script'ler/CLI'larNative FetchKyEffect
Browser + Node.jsAxiosKyUndici
Edge computing (Cloudflare)Native FetchHonoNode-specific
Legacy sistem entegrasyonuAxiosSuperAgentEffect

Zor Yoldan Öğrenilen Dersler#

  1. Connection pooling opsiyonel değil - Production'da file descriptor'ları yaktık. Her zaman connection limit'leri konfigüre edin.

  2. Memory leak'ler gerçek - O Axios 502 bug'ı bize haftalarca debugging'e mal oldu. Her zaman error senaryolarıyla load test yapın.

  3. Circuit breaker'lar geliri kurtarır - Her external API fail edecek. Bunun için plan yapın.

  4. Timeout'ların katmanları olmalı - Connection timeout, request timeout, total timeout. Hepsini ayarlayın.

  5. Log'lar yeterli değil - Metrik'lere ihtiyacınız var. Ortalamalar değil, response time percentile'ları.

Sırada Ne Var?#

HTTP client manzarası evrim geçirmeye devam ediyor. Native fetch iyileşiyor, undici HTTP/2 ekliyor ve Effect popülerlik kazanıyor. Tavsiyem? Hype'a değil, takımınıza ve use case'inize göre seçin.

Basit başlayın (native fetch), her şeyi ölçün ve gerçek sınırlamalara çarptığınızda upgrade edin. Ve ne seçerseniz seçin, ihtiyacınız olmadan önce circuit breaker ekleyin. Bu konuda bana güvenin.

Mutlu fetch'lemeler ve API'larınız her zaman 200 OK dönsün! 🚀

Loading...

Yorumlar (0)

Sohbete katıl

Düşüncelerini paylaşmak ve toplulukla etkileşim kurmak için giriş yap

Henüz yorum yok

Bu yazı hakkında ilk düşüncelerini paylaşan sen ol!

Related Posts