İçeriğe atla

2025-12-05

AWS Mesajlaşma Servisleri: SQS vs SNS vs EventBridge - Karar Verme Çerçevesi

Özelliklere göre değil, iletişim modeline göre seçim yap. SQS, SNS ve EventBridge arasında karar vermek için pratik bir rehber; çalışan CDK örnekleri ve maliyet analizi ile.

SQS, SNS ve EventBridge arasında seçim yapmak bir özellik karşılaştırması değil, iletişim modeli kararıdır. SQS bir noktadan noktaya iş kuyruğu, SNS pub/sub fan-out ve EventBridge şema farkında, replay destekli bir olay yönlendiricisidir. Özellik listesine bakarak seçim yapan bir ekip, yanlış servis modelinin doğru servisin doğal olarak sağlayacağı davranışı geri kazanmak için özel bir katman yazmayı zorunlu kıldığını genellikle geç fark eder; AWS mesajlaşma yeniden yazımlarının çoğu buradan çıkar.

Bu yazı, üç servisi iletişim modeli, teslim semantiği, sıralama, maliyet ve operasyonel yüzey açısından karşılaştırır. Seçim karar ağacını, yaygın hibrit desenleri (SNS’den SQS’e fan-out, EventBridge’den SQS’e filtreli abonelik) ve servisler arası migrasyon maliyetlerini ele alır.

İletişim Modellerini Anlamak

AWS, her biri belirli pattern’ler için tasarlanmış üç farklı mesajlaşma yaklaşımı sunar:

SQS (Pull Model): Consumer’lar aktif olarak mesaj polling yapar. Queue, backpressure kontrolü sağlar; consumer’lar kendi hızlarında işlem yapar. Önemli bir teknik detay: boş queue’larda polling yapmak bile ücret oluşturur. 5 Lambda poller’ın her biri dakikada 3 request yaparsa, sıfır mesajla bile ayda 648.000 polling request’i demek.

SNS (Push Model): Publisher gönderir, subscriber hemen alır. Birden fazla endpoint’e real-time delivery iyi çalışır, ama bir trade-off var: mesaj persistence yok. Subscriber down olursa, SNS’i SQS ile birleştirmediğin sürece mesaj kaybolur.

EventBridge (Event-Driven Router): Event’ler rule’lara göre eşleştirilir ve target’lara yönlendirilir. Bu, filtering, transformation ve multi-target delivery’yi birleştirir. Ordering garantisi yok, ama archive ve replay yetenekleri SQS ve SNS’in sunmadığı debugging seçenekleri sağlıyor.

Karar Verme Çerçevesi

Özellik matrislerini karşılaştırmak yerine, ben kararı şöyle veriyorum:

Hayir

Evet

Hayir

Evet

Hayir

Evet

Hayir

Evet

Hayir

Evet

Evet

Hayir

Evet

Hayir

Evet

Hayir

Yeni Messaging Ihtiyaci

Mesaj

persistence

gerekli mi?

SNS Kullan

Push notifications

Birden fazla

consumer?

Rate limiting

gerekli mi?

SQS Kullan

Point-to-point queue

Lambda Kullan

Direct invocation

Kompleks

routing logic?

Mesaj

siralama

gerekli mi?

SNS FIFO + SQS FIFO

Ordered fan-out

SNS + SQS

Standard fan-out

Cross-account

veya SaaS

event'ler?

EventBridge

Advanced routing

Event archive/

replay gerekli?

Content-based

filtering?

EventBridge + SQS

Filtered buffering

Bu decision tree, farklı mimarilerde işe yarayan pattern’leri yansıtıyor. Önemli olan, önce primary requirement’ını belirlemek, sonra secondary consideration’lara geçmek.

Çalışan Örnekler

SQS: Backpressure Kontrolüyle Point-to-Point

Kontrollü throughput ile güvenilir mesaj işleme gerektiğinde, SQS basit bir implementasyon sağlar:

import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Duration } from 'aws-cdk-lib';

// Başarısız işlemler için dead letter queue
const dlq = new sqs.Queue(this, 'ProcessingDLQ', {
  retentionPeriod: Duration.days(14),
});

const processingQueue = new sqs.Queue(this, 'ProcessingQueue', {
  visibilityTimeout: Duration.seconds(300),
  receiveMessageWaitTime: Duration.seconds(20), // Long polling
  deadLetterQueue: {
    maxReceiveCount: 3,
    queue: dlq,
  },
});

// Kontrollü concurrency ile Lambda consumer
const processor = new lambda.Function(this, 'QueueProcessor', {
  runtime: lambda.Runtime.NODEJS_20_X,
  handler: 'index.handler',
  code: lambda.Code.fromAsset('lambda'),
  timeout: Duration.seconds(240), // Visibility timeout'tan az
  reservedConcurrentExecutions: 10, // Throughput kontrolü
});

processingQueue.grantConsumeMessages(processor);
new lambda.EventSourceMapping(this, 'QueueTrigger', {
  target: processor,
  eventSourceArn: processingQueue.queueArn,
  batchSize: 10,
  maxBatchingWindow: Duration.seconds(5),
});

Buradaki kritik detay, visibility timeout (300s) ile Lambda timeout (240s) arasındaki ilişki. Duplicate’leri önlemek için mesajlar işlem süresinden daha uzun süre gizli kalmalı. Long polling, empty poll maliyetlerini saatte 180 request’ten yaklaşık 9’a düşürür.

SNS + SQS: Message Filtering ile Fan-out

Birden fazla servise event broadcast etmek için, SNS topic-queue chaining dayanıklı fan-out sağlar:

import * as sns from 'aws-cdk-lib/aws-sns';
import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions';

const orderTopic = new sns.Topic(this, 'OrderTopic', {
  displayName: 'Order Events',
});

const inventoryQueue = new sqs.Queue(this, 'InventoryQueue');
const shippingQueue = new sqs.Queue(this, 'ShippingQueue');
const analyticsQueue = new sqs.Queue(this, 'AnalyticsQueue');

// SNS seviyesinde filtering ile subscribe
orderTopic.addSubscription(
  new subscriptions.SqsSubscription(inventoryQueue, {
    rawMessageDelivery: true,
    filterPolicy: {
      orderType: sns.SubscriptionFilter.stringFilter({
        allowlist: ['STANDARD', 'BULK'],
      }),
    },
  })
);

orderTopic.addSubscription(
  new subscriptions.SqsSubscription(shippingQueue, {
    rawMessageDelivery: true,
    filterPolicy: {
      priority: sns.SubscriptionFilter.stringFilter({
        allowlist: ['HIGH', 'URGENT'],
      }),
    },
  })
);

// Analytics her şeyi alır
orderTopic.addSubscription(
  new subscriptions.SqsSubscription(analyticsQueue, {
    rawMessageDelivery: true,
  })
);

rawMessageDelivery ayarı göründüğünden daha önemli. Onsuz, SNS mesajını bir envelope içine sarar, consumer kodunda JSON.parse(JSON.parse(message)) gerektirir. Aktif olduğunda, SQS mesaj içeriğini doğrudan alır.

EventBridge: Content-Based Routing

Routing mantığı karmaşıklaştığında, EventBridge SNS filtering’in eşleşemeyeceği yetenekler sağlar:

import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';

const eventBus = new events.EventBus(this, 'OrderEventBus', {
  eventBusName: 'order-events',
});

// Debugging ve replay için archive
new events.Archive(this, 'OrderArchive', {
  sourceEventBus: eventBus,
  eventPattern: {
    source: ['orders.service'],
  },
  retention: Duration.days(30),
});

// Yüksek değerli siparişler için birden fazla target ile rule
new events.Rule(this, 'HighValueOrderRule', {
  eventBus: eventBus,
  eventPattern: {
    source: ['orders.service'],
    detailType: ['OrderCreated'],
    detail: {
      totalAmount: [{ numeric: ['>', 1000] }],
      region: ['US', 'EU'],
    },
  },
  targets: [
    new targets.LambdaFunction(fraudCheckFunction),
    new targets.SqsQueue(priorityQueue),
    new targets.SnsTopic(alertTopic),
  ],
});

// Nested field matching kullanarak inventory update'leri için rule
new events.Rule(this, 'InventoryUpdateRule', {
  eventBus: eventBus,
  eventPattern: {
    source: ['orders.service'],
    detailType: ['OrderCreated', 'OrderCancelled'],
    detail: {
      items: {
        sku: [{ exists: true }],
      },
    },
  },
  targets: [new targets.SqsQueue(inventoryQueue)],
});

EventBridge filtering, nested field’lar dahil tüm event payload’ında çalışır. SNS message filtering sadece message attribute’larıyla çalışır. Bu fark, event yapısı evrildiğinde önemli hale gelir.

FIFO Ordering Pattern’leri

Ordering garantileri dikkatli servis seçimi gerektirir. Standard SQS ve SNS ordering sağlamaz. EventBridge ordering sağlamaz. Ordered processing için FIFO varyantlarına ihtiyacın var:

// Ordered event'ler için FIFO topic
const orderEventsTopic = new sns.Topic(this, 'OrderEventsFIFO', {
  fifo: true,
  contentBasedDeduplication: true,
  topicName: 'order-events.fifo',
});

// Per-message-group deduplication ile FIFO queue
const processingQueue = new sqs.Queue(this, 'ProcessingQueueFIFO', {
  fifo: true,
  contentBasedDeduplication: true,
  queueName: 'order-processing.fifo',
  visibilityTimeout: Duration.seconds(300),
  deduplicationScope: sqs.DeduplicationScope.MESSAGE_GROUP,
  fifoThroughputLimit: sqs.FifoThroughputLimit.PER_MESSAGE_GROUP_ID,
});

orderEventsTopic.addSubscription(
  new subscriptions.SqsSubscription(processingQueue, {
    rawMessageDelivery: true,
  })
);

Throughput özellikleri önemli: standard FIFO saniyede 300 transaction sağlar, high throughput FIFO 3.000 TPS sağlar (request başına 10 mesajlık batching ile). Message group partitioning ile (MessageGroupId müşteri veya entity başına), bağımsız FIFO sequence’lar oluşturarak 30.000+ TPS’e ulaşabilirsin.

Hibrit Pattern: EventBridge + SQS Buffering

En etkili mimarilerden bazıları servisleri birleştirir. EventBridge routing’i halleder, SQS buffering sağlar:

const eventBus = new events.EventBus(this, 'CentralBus');

// DLQ'larla birlikte servise özgü queue'lar
const createServiceQueue = (serviceName: string) => {
  const dlq = new sqs.Queue(this, `${serviceName}DLQ`, {
    retentionPeriod: Duration.days(14),
  });

  return new sqs.Queue(this, `${serviceName}Queue`, {
    visibilityTimeout: Duration.seconds(300),
    receiveMessageWaitTime: Duration.seconds(20),
    deadLetterQueue: {
      maxReceiveCount: 3,
      queue: dlq,
    },
  });
};

const inventoryQueue = createServiceQueue('Inventory');
const notificationQueue = createServiceQueue('Notification');

// EventBridge filter yapar, SQS buffer'lar
new events.Rule(this, 'InventoryRule', {
  eventBus: eventBus,
  eventPattern: {
    source: ['orders.service', 'returns.service'],
    detail: { requiresInventoryUpdate: [true] },
  },
  targets: [new targets.SqsQueue(inventoryQueue)],
});

// Yüksek öncelikli notification'lar queue'yu bypass eder
new events.Rule(this, 'UrgentNotificationRule', {
  eventBus: eventBus,
  eventPattern: {
    source: ['orders.service'],
    detail: {
      priority: ['URGENT'],
      notificationType: ['SMS', 'PUSH'],
    },
  },
  targets: [new targets.LambdaFunction(urgentNotificationHandler)],
});

Bu pattern ikisinin de en iyisini sağlar: EventBridge’in gelişmiş filtering’i gereksiz SQS write’larını azaltır, SQS ise Lambda reserved concurrency aracılığıyla backpressure koruması ve rate limiting sağlar.

Maliyet Analizi

Pattern’ler arasındaki maliyet farkları önemli. Ayda 10 milyon event ve 5 servise fan-out için:

SNS + SQS Pattern:

  • SNS publish’ler: 10M × 0.50/M=0.50/M = 5.00
  • SNS → SQS delivery’ler: ÜCRETSIZ (aynı region)
  • SQS write’lar: 50M × 0.40/M=0.40/M = 20.00
  • SQS read’ler (batched): 5M × 0.40/M=0.40/M = 2.00
  • Toplam: $27.00/ay

EventBridge + SQS Pattern:

  • EventBridge ingestion: 10M × 1.00/M=1.00/M = 10.00
  • SQS write’lar: 50M × 0.40/M=0.40/M = 20.00
  • SQS read’ler: 5M × 0.40/M=0.40/M = 2.00
  • Toplam: $32.00/ay (+%19)

EventBridge + Doğrudan Lambda:

  • EventBridge ingestion: 10M × 1.00/M=1.00/M = 10.00
  • Lambda invocation’lar: 50M × 0.20/M=0.20/M = 10.00
  • Lambda compute (128MB, 100ms): ~$10.42
  • Toplam: $30.42/ay (+%13)

EventBridge+SQS, SNS+SQS’e göre %19 daha pahalı, EventBridge doğrudan Lambda invocation ise %13 daha pahalı; her ikisi de sana gelişmiş filtering, schema registry, archive/replay ve cross-account routing getiriyor. Bunun maliyete değip değmeyeceği, bu özellikleri kullanıp kullanmadığına bağlı.

Yaygın Implementasyon Sorunları

Bu servislerle çalışırken, bazı sorunlar sürekli tekrar ediyor:

Visibility Timeout Çok Kısa: Lambda timeout 60 saniye ama visibility timeout 30 saniye olduğunda, mesajlar hala işlenirken tekrar görünür hale gelir. Kural: visibilityTimeout >= lambdaTimeout + buffer. Pratikte, 240 saniyelik Lambda timeout için 300 saniyelik visibility timeout kullan.

rawMessageDelivery Eksikliği: SNS mesajları bir envelope içine sarar. rawMessageDelivery: true olmadan, kodun JSON-stringified JSON’ı handle eder. Consumer logic kırılgan hale gelir.

DLQ Monitoring Yok: CloudWatch alarm’ı olmayan dead letter queue’lar sessiz başarısızlıklar anlamına gelir. Mesajlar fark edilmeden birikir.

import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as cloudwatchActions from 'aws-cdk-lib/aws-cloudwatch-actions';

const dlqAlarm = new cloudwatch.Alarm(this, 'DLQAlarm', {
  metric: dlq.metricApproximateNumberOfMessagesVisible(),
  threshold: 1,
  evaluationPeriods: 1,
  comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
});

dlqAlarm.addAlarmAction(new cloudwatchActions.SnsAction(alertTopic));

EventBridge Event Size Chunking: Event’ler 64KB chunk’lar halinde faturalandırılır. 256KB’lık bir event, 64KB’lık bir event’in 4 katı maliyete sahip. Büyük payload’lar için, claim check pattern veriyi S3’te saklar ve bir referans gönderir:

// Payload'ı S3'te sakla
const s3Key = await s3.putObject({
  Bucket: 'event-payloads',
  Key: `events/${eventId}.json`,
  Body: JSON.stringify(largePayload),
});

// EventBridge üzerinden küçük referans gönder
await eventBridge.putEvents({
  Entries: [{
    Source: 'orders.service',
    DetailType: 'OrderCreated',
    Detail: JSON.stringify({
      eventId,
      s3Bucket: 'event-payloads',
      s3Key: `events/${eventId}.json`,
    }),
  }],
});

EventBridge Pipes: Lambda Glue Code’u Azaltma

EventBridge Pipes, sadece transform ve forward yapan Lambda function’larının yerini alır. DynamoDB Streams’ten EventBridge’e entegrasyon için:

import * as pipes from 'aws-cdk-lib/aws-pipes';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';

const ordersTable = new dynamodb.Table(this, 'OrdersTable', {
  partitionKey: { name: 'orderId', type: dynamodb.AttributeType.STRING },
  stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES,
});

const pipe = new pipes.CfnPipe(this, 'OrderPipe', {
  source: ordersTable.tableStreamArn!,
  target: eventBus.eventBusArn,
  roleArn: pipeRole.roleArn,

  // Pipe seviyesinde filter
  sourceParameters: {
    dynamoDbStreamParameters: {
      startingPosition: 'LATEST',
      batchSize: 10,
    },
    filterCriteria: {
      filters: [
        {
          pattern: JSON.stringify({
            eventName: ['INSERT', 'MODIFY'],
            dynamodb: {
              NewImage: {
                status: { S: ['PENDING', 'PAID'] },
              },
            },
          }),
        },
      ],
    },
  },

  // Inline transformation
  targetParameters: {
    eventBridgeEventBusParameters: {
      detailType: 'OrderChange',
      source: 'orders.dynamodb',
    },
  },
});

Bu, bir Lambda function’ı ortadan kaldırır ve basit filtering ve transformation senaryoları için maliyetleri %98’e kadar azaltır. Pipe, Lambda invocation artı compute maliyetine karşı milyon başına $0.40’a mal olur.

Migration Stratejileri

Servisler arasında geçiş yaparken, dual publishing sıfır downtime migration sağlar:

// Faz 1: Hem SNS hem EventBridge'e publish et
async function publishEvent(event: OrderEvent) {
  // Mevcut SNS akışı
  await sns.publish({
    TopicArn: existingTopic.topicArn,
    Message: JSON.stringify(event),
  });

  // Yeni EventBridge akışı
  await eventBridge.putEvents({
    Entries: [{
      Source: 'orders.service',
      DetailType: 'OrderCreated',
      Detail: JSON.stringify(event),
      EventBusName: eventBus.eventBusName,
    }],
  });
}

Ardından paralel consumer’lar oluştur, sonuçları karşılaştır, feature flag’ler kullanarak trafiği kademeli olarak kaydır ve sonunda eski yolu kaldır. Bu yaklaşım işe yarar ama geçici olarak mesajlaşma maliyetlerini ikiye katlar; migration timeline’ını buna göre planla.

Ne Zaman Hangi Servisi Kullanmalı

SQS kullan:

  • Tek consumer backpressure kontrolüne ihtiyaç duyuyorsa
  • İşlem hızı sınırlanmalıysa
  • Batch processing verimliliği artırıyorsa
  • FIFO ile exactly-once processing gerekiyorsa
  • Bütçe sıkı ve trafik yüksekse

SNS kullan:

  • Birden fazla subscriber’ın hemen bildirim alması gerekiyorsa
  • Mobile push, SMS veya e-posta delivery gerekiyorsa
  • Attribute’lara göre basit message filtering yeterliyse
  • Real-time delivery, dayanıklılıktan daha önemliyse
  • Dayanıklı delivery ile fan-out pattern (SNS+SQS)

EventBridge kullan:

  • Nested event field’larında content-based routing
  • Cross-account veya cross-region event delivery
  • Üçüncü taraf SaaS entegrasyonu gerekiyorsa
  • Event archive ve replay yetenekleri gerekiyorsa
  • Event contract’ları için schema registry önemliyse

Hibrit pattern’ler kullan:

  • Kompleks filtered fan-out için EventBridge routing + SQS buffering
  • Standard broadcast pattern’leri için SNS fan-out + SQS durability
  • Lambda olmadan stream processing için EventBridge Pipes

Önemli Çıkarımlar

SQS, SNS ve EventBridge arasındaki seçim, özellik karşılaştırmalarıyla değil, iletişim modelinle başlar. Pull modeller (SQS) backpressure kontrolü sağlar. Push modeller (SNS) real-time fan-out sağlar. Event-driven routing (EventBridge) gelişmiş filtering ve entegrasyon sağlar.

FIFO ordering, SQS ve SNS’in FIFO varyantlarını gerektirir. EventBridge ordering garantisi vermez. Ordered processing için, SNS FIFO + SQS FIFO, daha yüksek throughput için message group partitioning ile end-to-end ordering sağlar.

Maliyet farkları anlamlı: Basit fan-out için SNS+SQS, EventBridge+SQS’ten %19 daha ucuz, ama EventBridge SNS’in sağlamadığı yetenekler sunuyor. EventBridge’den doğrudan Lambda invocation, SNS+SQS ile SQS buffering kullanırken yaklaşık aynı maliyette ama queue yönetim yükünü ortadan kaldırıyor.

Dead letter queue’lar zorunlu. Bunları CloudWatch alarm’larıyla izlemek kritik. Visibility timeout Lambda timeout’ını aşmalı. Long polling, düşük trafikli queue’lar için maliyetleri azaltır. SNS veya EventBridge seviyesinde message filtering, gereksiz downstream processing’i azaltır.

Hibrit pattern’ler genellikle tek servis yaklaşımlarından daha iyi performans gösterir. EventBridge routing ile SQS buffering kombinasyonu hem gelişmiş filtering hem backpressure kontrolü sağlar. İşe yarayan mimari, industry best practice’lere değil, senin spesifik gereksinimlerine bağlı.

İlgili yazılar

SNS/SQS Cross-Account Fan-Out: AWS'de Multi-Account Event Dağıtımı

Amazon SNS ve SQS kullanarak güvenli cross-account event dağıtımı nasıl yapılır öğrenin. IAM policy'leri, KMS şifreleme, AWS CDK implementasyonu ve production'da karşılaşılan yaygın sorunları kapsıyor.

awsaws-snsaws-sqs+6
wasmCloud + NATS: Kilitlenme Aslında Event Bus Topolojisinde Yaşar

Bir keşif tezi: event-driven sistemlerde vendor lock-in runtime katmanında değil, bus topolojisinde yaşar; wasmCloud ve NATS ise bus'ı taşınabilir bir primitif haline getiriyor.

wasmcloudnatsevent-driven+4
AWS ile Edge Computing: CloudFront Functions vs Lambda@Edge

Global uygulamalar için AWS edge computing çözümlerini seçme ve uygulama üzerine pratik örnekler ve maliyet optimizasyonu stratejileri içeren kapsamlı teknik rehber.

awscloudfrontlambda+6
Amazon Cognito Derinlemesine: Temel Authentication'ın Ötesinde

Amazon Cognito'nun gelişmiş özellikleri üzerine kapsamlı teknik kılavuz: özel authentication akışları, federation pattern'leri, multi-tenancy mimarileri, migration stratejileri ve production-grade güvenlik implementasyonu.

awscognitoauthentication+7
AWS AppSync & GraphQL: Production-Ready Real-time API'ler Geliştirmek

AWS AppSync ile ölçeklenebilir real-time API'ler geliştirmek için kapsamlı bir rehber: JavaScript resolver'lar, subscription filtering, caching stratejileri ve infrastructure as code pattern'leri.

awsappsyncgraphql+5