Skip to content

İstek Birleştirme & Debouncing

Mobil uygulamalarda ağ trafiğini optimize etmek ve kullanıcı deneyimini iyileştirmek için kritik teknikler.

Hızlı Karar

DurumYaklaşımDikkat
Kullanıcı hızlı input üretiyorDebounceFazla gecikme hissedilir
Çok küçük istek varBatchHata ayrıştırması gerekir
Aynı veri tekrar isteniyorRequest coalescingCache key doğru olmalı
Kritik anlık aksiyonDebounce etmeKullanıcı güveni bozulur

Üretim Kontrol Listesi

  • Problem: Ağ çağrısı gerçekten fazla mı, yoksa backend yavaş mı?
  • Çözüm: Batch window, max size, cancellation, partial failure ve retry net mi?
  • Trade-off: Daha az istek batarya ve maliyeti azaltır; latency ve hata yönetimi karmaşıklaşır.
  • Hata durumu: Lost request, duplicate send, partial batch failure ve stale result ele alınmalı.
  • Ölçüm: Request count, batch size, debounce delay, cancellation rate ve p95 latency izlenmeli.
  • Güvenlik/maliyet: Farklı kullanıcı/tenant istekleri aynı batch'te karıştırılmamalı.

İstek Birleştirme Stratejileri

Kavramsal Çerçeve

  • Amaç: Birden fazla küçük isteği tek bir ağ çağrısında birleştirme
  • Faydalar:
    • Ağ yükünün azaltılması
    • Batarya optimizasyonu
    • Sunucu yükünün azaltılması
    • Ağ verimliliğinin artırılması
    • Daha iyi kullanıcı deneyimi
  • Ödünleşimler:
    • Gecikme vs verimlilik
    • Karmaşıklık vs basitlik
    • Bellek kullanımı vs performans
    • Gerçek zamanlı gereksinimler vs birleştirme faydaları

Uygulama Teknikleri

Zamana Dayalı Birleştirme

  • Sabit Zaman Pencereleri:
    • Her 5 saniyede birikmiş istekleri gönderme
    • Kullanım durumuna göre yapılandırılabilir pencere boyutları
    • Öncelik tabanlı pencere ayarlaması
  • Uyarlanabilir Zaman Pencereleri:
    • Ağ koşullarına göre pencere boyutu ayarlaması
    • Batarya seviyesi dikkate alma
    • Kullanıcı aktivite kalıpları
  • Uygulama Desenleri:
    • Android:
      • Handler.postDelayed() ile zamanlanmış birleştirme
      • Arka plan birleştirme için WorkManager entegrasyonu
      • Sistem optimizasyonlu zamanlama için JobScheduler
    • iOS:
      • Timer.scheduledTimer() ile periyodik çalıştırma
      • BackgroundTasks framework entegrasyonu
      • Reaktif birleştirme için Combine framework
    • Flutter:
      • Timer.periodic() ile çapraz platform birleştirme
      • İzole tabanlı arka plan işleme
      • Akış tabanlı birleştirme uygulaması
kotlin
// Android Time-Based Batching Implementation
class RequestBatcher<T> {
    private val pendingRequests = mutableListOf<T>()
    private val batchHandler = Handler(Looper.getMainLooper())
    private var batchRunnable: Runnable? = null
    
    private val batchSize = 20
    private val batchTimeoutMs = 5000L
    
    fun addRequest(request: T) {
        synchronized(pendingRequests) {
            pendingRequests.add(request)
            
            // Size-based trigger
            if (pendingRequests.size >= batchSize) {
                processBatch()
                return
            }
            
            // Time-based trigger
            if (batchRunnable == null) {
                batchRunnable = Runnable {
                    processBatch()
                }.also { runnable ->
                    batchHandler.postDelayed(runnable, batchTimeoutMs)
                }
            }
        }
    }
    
    private fun processBatch() {
        synchronized(pendingRequests) {
            if (pendingRequests.isEmpty()) return
            
            val batch = pendingRequests.toList()
            pendingRequests.clear()
            
            batchRunnable?.let { batchHandler.removeCallbacks(it) }
            batchRunnable = null
            
            // Process batch in background
            executeBatch(batch)
        }
    }
    
    private fun executeBatch(requests: List<T>) {
        // Network call with batched requests
        GlobalScope.launch(Dispatchers.IO) {
            try {
                val response = apiService.batchRequests(requests)
                handleBatchResponse(response)
            } catch (e: Exception) {
                handleBatchError(e, requests)
            }
        }
    }
}
swift
// iOS Combine-Based Batching
class RequestBatcher<T> {
    private let subject = PassthroughSubject<T, Never>()
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        subject
            .collect(.byTimeOrCount(
                .main, 
                strategy: .byTimeOrCount(5.0, 20)
            ))
            .sink { [weak self] batch in
                self?.processBatch(Array(batch))
            }
            .store(in: &cancellables)
    }
    
    func addRequest(_ request: T) {
        subject.send(request)
    }
    
    private func processBatch(_ requests: [T]) {
        Task {
            do {
                let response = try await apiService.batchRequests(requests)
                await handleBatchResponse(response)
            } catch {
                await handleBatchError(error, requests)
            }
        }
    }
}

Boyut Tabanlı Birleştirme

  • İstek Sayısı Limitleri:
    • Toplu başına maksimum 50 istek
    • Yük boyutuna göre dinamik limit ayarlaması
    • Öncelik tabanlı istek dahil etme
  • Yük Boyutu Limitleri:
    • Toplu istek başına maksimum 1MB
    • Sıkıştırma farkında boyut limitleri
    • Ağ tipi dikkate alma
  • Bellek Hususları:
    • Tampon taşması önleme
    • Bellek basıncı izleme
    • Otomatik toplu bölme

Platform Özel Birleştirme Çözümleri

Firebase Analytics Birleştirme

  • Otomatik Birleştirme:
    • Olaylar kuyruğa alınır ve toplu olarak gönderilir
    • Ağ kullanılabilirliği farkındalığı
    • Batarya optimizasyonu entegrasyonu
  • Özel Olay Birleştirme:
    • Manuel toplu oluşturma için özel uygulama
    • İş mantığı entegrasyonu ile akıllı birleştirme

GraphQL Birleştirme

  • Sorgu Birleştirme: Birden fazla GraphQL sorgusunu tek HTTP isteğinde
  • DataLoader Deseni: N+1 sorgu problemini çözme
  • Uygulama: Apollo Client ile otomatik istek birleştirme
dart
// Flutter GraphQL Batching
class GraphQLBatcher {
  final List<QueryRequest> _pendingQueries = [];
  Timer? _batchTimer;
  final Duration batchWindow = Duration(milliseconds: 10);
  
  Future<QueryResult> executeQuery(String query, Map<String, dynamic> variables) {
    final completer = Completer<QueryResult>();
    final request = QueryRequest(query, variables, completer);
    
    _pendingQueries.add(request);
    
    _batchTimer?.cancel();
    _batchTimer = Timer(batchWindow, () {
      _executeBatch();
    });
    
    return completer.future;
  }
  
  void _executeBatch() {
    if (_pendingQueries.isEmpty) return;
    
    final batch = List<QueryRequest>.from(_pendingQueries);
    _pendingQueries.clear();
    
    final batchedQuery = _createBatchQuery(batch);
    
    _httpClient.post('/graphql', body: batchedQuery).then((response) {
      final results = _parseBatchResponse(response.body);
      for (int i = 0; i < batch.length; i++) {
        batch[i].completer.complete(results[i]);
      }
    }).catchError((error) {
      for (final request in batch) {
        request.completer.completeError(error);
      }
    });
  }
  
  String _createBatchQuery(List<QueryRequest> requests) {
    final queries = requests.asMap().entries.map((entry) {
      final index = entry.key;
      final request = entry.value;
      return 'query$index: ${request.query}';
    }).join('\n');
    
    return '{\n$queries\n}';
  }
}

class QueryRequest {
  final String query;
  final Map<String, dynamic> variables;
  final Completer<QueryResult> completer;
  
  QueryRequest(this.query, this.variables, this.completer);
}

Debouncing Mekanizmaları

Arama Girişi Debouncing

  • Problem: Her tuş vuruşunda API çağrısı maliyetli ve gereksiz
  • Çözüm: Aramayı tetiklemeden önce yazma duraklamasını bekleme
  • Uygulama Örnekleri:
    • Android: RxJava debounce() operatörü ile gözlemlenebilir akışlar
    • iOS: Combine debounce() ile yayıncı dönüşümü
    • Flutter: Zamanlayıcı iptali ile manuel debouncing
    • React Native: Lodash debounce ile yardımcı program tabanlı yaklaşım
kotlin
// Android RxJava Debouncing
class SearchDebouncer {
    private val searchSubject = PublishSubject.create<String>()
    private val compositeDisposable = CompositeDisposable()
    
    init {
        searchSubject
            .debounce(300, TimeUnit.MILLISECONDS)
            .distinctUntilChanged()
            .switchMap { query ->
                if (query.isBlank()) {
                    Observable.just(emptyList<SearchResult>())
                } else {
                    searchService.search(query)
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .onErrorReturn { emptyList() }
                }
            }
            .subscribe { results ->
                updateSearchResults(results)
            }
            .let { compositeDisposable.add(it) }
    }
    
    fun onSearchTextChanged(query: String) {
        searchSubject.onNext(query)
    }
    
    fun dispose() {
        compositeDisposable.dispose()
    }
}
swift
// iOS Combine Debouncing
class SearchDebouncer: ObservableObject {
    @Published var searchText = ""
    @Published var searchResults: [SearchResult] = []
    
    private var cancellables = Set<AnyCancellable>()
    private let searchService: SearchService
    
    init(searchService: SearchService) {
        self.searchService = searchService
        
        $searchText
            .debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
            .removeDuplicates()
            .sink { [weak self] query in
                self?.performSearch(query: query)
            }
            .store(in: &cancellables)
    }
    
    private func performSearch(query: String) {
        guard !query.isEmpty else {
            searchResults = []
            return
        }
        
        searchService.search(query: query)
            .receive(on: DispatchQueue.main)
            .sink(
                receiveCompletion: { _ in },
                receiveValue: { [weak self] results in
                    self?.searchResults = results
                }
            )
            .store(in: &cancellables)
    }
}

Gelişmiş Debouncing Desenleri

  • Ön Uç Debouncing: İlk çağrı anında, sonraki çağrılar debounced
  • Arka Uç Debouncing: Sessizlik periyodundan sonra sadece son çağrı
  • Kısıtlama vs Debouncing: Hız sınırlama vs gecikme tabanlı filtreleme

Kullanıcı Etkileşimi Debouncing

  • Düğme Basma Koruması: Çift tıklama ile yinelenen eylemleri önleme
  • Form Gönderimi: Çoklu form gönderimlerini önleme
  • Gezinme Eylemleri: Hızlı gezinme geçişlerini önleme
dart
// Flutter Custom Debouncer
class Debouncer {
  final Duration delay;
  Timer? _timer;
  
  Debouncer({required this.delay});
  
  void call(VoidCallback action) {
    _timer?.cancel();
    _timer = Timer(delay, action);
  }
  
  void dispose() {
    _timer?.cancel();
  }
}

// Usage in Widget
class SearchWidget extends StatefulWidget {
  @override
  _SearchWidgetState createState() => _SearchWidgetState();
}

class _SearchWidgetState extends State<SearchWidget> {
  final _debouncer = Debouncer(delay: Duration(milliseconds: 300));
  final _controller = TextEditingController();
  
  @override
  void dispose() {
    _debouncer.dispose();
    _controller.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
      onChanged: (value) {
        _debouncer.call(() {
          _performSearch(value);
        });
      },
    );
  }
  
  void _performSearch(String query) {
    // Search implementation
  }
}

Akıllı İstek Optimizasyonu

İstek Tekrarı Önleme

  • Devam Eden İstek Takibi: Aynı API çağrılarını tekrarlamama
  • Önbellek Öncelikli Stratejiler: Ağ isteklerinden önce önbelleği kontrol etme
  • İstek Birleştirme: Benzer istekleri tek bir isteğe birleştirme

Öncelik Tabanlı Kuyruk

  • Yüksek Öncelik: Kullanıcı başlatılan eylemler
  • Orta Öncelik: Arka plan veri yenileme
  • Düşük Öncelik: Analitik, metrikler, kritik olmayan güncellemeler
  • Uygulama: İstek önceliklendirme ile özel kuyruk yönetimi

Performans İzleme

  • İstek Birleştirme Metrikleri: Toplu boyut dağılımı, başarı oranları
  • Debouncing Etkinliği: Azaltılmış istek sayısı, kullanıcı memnuniyeti
  • Ağ Verimliliği: Bant genişliği tasarrufu, yanıt süresi iyileştirmeleri

Eren Demir tarafından oluşturulmuştur.