Skip to content

İzole Consumer Hesaplarına Event Fan-Out: Sıfır Dokunuşlu Producer, Domain Başına Sahiplik

Çok takımlı AWS organizasyonları için platform mühendisliği varsayılanı: tek event, birçok consumer, her biri kendi hesabında kendi SQS ve DLQ'suyla; fan-out event bus katmanında yaşar.

Bir producer servisi yeni consumer'ları midye gibi üstüne toplar. Event isteyen her yeni takım başka bir if enabled(x) publish(x) dalı ekler ve her yeni consumer için producer'ın deploy edilmesi gerekir. Dallar, uygulama koduymuş gibi görünen bir anahtar kutusudur; herhangi bir hatanın etki alanı ise ödeme işlemleriyle aynı hesabın içindedir.

Bu yazı, çok takımlı bir AWS organizasyonunda event omurgasının sahibi olan platform ve backend mühendisleri içindir. Audit, Customer Center, Marketing ve gelecekteki consumer'lar için varsayılan bir topoloji tanımlar ve farklı bir omurgaya ne zaman geçmek gerektiğini gösterir.

Tez: fan-out event bus katmanında yaşar, uygulama kodunda değil

Tek event, birçok consumer, her biri kendi hesabında kendi SQS ve DLQ'suyla; fan-out event bus katmanında yaşar, uygulama kodunda değil.

Somut olarak bu; özel bir events hesabında merkezi bir EventBridge custom bus, bu bus'tan her consumer hesabındaki alıcı bus'a cross-account kurallar ve her consumer hesabının içinde consumer başına SQS, DLQ, compute ve store anlamına gelir. Producer servisi, event'lerini kimin okuduğunu asla öğrenmez. Yeni bir consumer eklemek, producer deploy'u değil platform tarafında bir pull request'tir.

Kazanım, organizasyon yapısında ortaya çıkar. Her domain takımı bir consumer'ı uçtan uca sahiplenir: bütçe, uyumluluk duruşu, nöbet ve geri alma. Producer takımı entegrasyon toplantıları olmadan özellik çıkarmaya devam eder. Bunun kısa karşılığı "bağımsız yönetilebilir yapı"dır.

Servis seçimi: hangi bus, hangi queue

Varsayılanı kurmadan önce, varsayılanı kıracak kısıtı adlandırın. EventBridge custom bus; schema registry, native cross-account bus-to-bus kuralları, target başına dead-letter queue ve bus'ta filtreleme ile varsayılan olarak kazanır. Bu varsayılanı sarsan dört kısıt vardır ve her biri devreye giren spesifik bir servise karşılık gelir.

EventBridge custom bus yönlendiricidir. Bus üzerinde filtre kuralları, producer-consumer kontratı olarak schema registry, sender hesabındaki bir IAM role üzerinden cross-account bus-to-bus teslimat (Mart 2023'ten beri zorunlu) ve target başına bir DLQ. Yukarıdaki dört kısıt yumuşak kaldığı sürece varsayılan olarak kalır.

Kinesis Data Streams stream'dir. Partition key bazlı shard içi sıralama, sequence number ile 24 saatten 365 güne kadar replay ve düşük gecikmeli tüketiciler için enhanced fan-out. Maliyet modeli event başına değil shard başına olduğu için istikrarlı yüksek throughput'u ödüllendirir, dalgalı düşük hacmi cezalandırır. Cross-account tüketim mümkündür ama EventBridge bus-to-bus'a göre daha fazla kurulum gerektirir.

Managed Streaming for Kafka (MSK), organizasyon zaten Kafka ekosistemini kullanıyorsa Kinesis'in eşidir. Debezium sink'leri native çalışır, Schema Registry Kafka'ya özel, mevcut producer ve consumer'lar kaynak-uyumlu kalır. Operasyonel yüzey alanı Kinesis'ten büyüktür; özellik paritesi için değil ekosistem uyumu için seçin.

SNS artı SQS fan-out eski pub/sub desenidir ve hedefler SMS, e-posta veya ham HTTPS webhook'ları olduğunda hâlâ doğru cevaptır. EventBridge bir telefon numarasına teslim etmez. SNS artı SQS'i yalnızca bu dilim için tutun; EventBridge masadayken AWS native consumer'lar için omurga olarak kullanmayın.

DynamoDB Streams bir kaynaktır, bus değil. EventBridge Pipes (aşağıdaki varsayılan örnekte olduğu gibi), Lambda veya Kinesis ile eşleşir. Producer'ın veritabanı DynamoDB ise sıfır dokunuşlu capture bu şekilde elde edilir.

Kinesis Data Firehose bir delivery sink'tir, bus değil. Audit consumer'ının substrate'i olarak parlar: event'leri S3 Parquet'e biriktirir, sıkıştırır, event zamanına göre partition'lar ve Glue'ya devreder. Önünde yine bir bus durur.

Step Functions orkestrasyondur, bus değil. Consumer'ın kendisi çok adımlı bir iş akışı olduğunda kullanın; örneğin Customer Center'ın cold-restore akışı S3 Restore tetikler, nesne ılınana kadar poll eder ve sonra ajana bildirim yollar. Bus tetiği teslim eder; durum makinesi Step Functions'ın sorumluluğundadır.

Karar dogmatik değildir. EventBridge'in zayıflıkları Kinesis ile MSK'nın güçlü yanlarıdır; soru, ölçek planında bir yıl sonra değil bugün üretimde dört kısıtın kaçının gerçek olduğudur.

Varsayılan: producer tarafında DynamoDB Streams ve EventBridge Pipes

Producer kodunda sıfır değişiklik gerektiren mekanizmadan başlayın. Producer'ın veritabanı DynamoDB ise, DDB stream ile EventBridge Pipes kombinasyonu, veritabanı yazmasından custom bus'a giden en kısa yoldur.

typescript
// aws-cdk-lib v2import { Stack, StackProps, RemovalPolicy } from "aws-cdk-lib";import { Construct } from "constructs";import * as dynamodb from "aws-cdk-lib/aws-dynamodb";import * as events from "aws-cdk-lib/aws-events";import * as pipes from "aws-cdk-lib/aws-pipes";import * as iam from "aws-cdk-lib/aws-iam";
export class ProducerSideStack extends Stack {  constructor(scope: Construct, id: string, props?: StackProps) {    super(scope, id, props);
    const orders = new dynamodb.Table(this, "Orders", {      partitionKey: { name: "pk", type: dynamodb.AttributeType.STRING },      stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES,      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,      removalPolicy: RemovalPolicy.RETAIN,    });
    const bus = new events.EventBus(this, "CentralBus", {      eventBusName: "platform-events",    });
    const pipeRole = new iam.Role(this, "PipeRole", {      assumedBy: new iam.ServicePrincipal("pipes.amazonaws.com"),    });    orders.grantStreamRead(pipeRole);    bus.grantPutEventsTo(pipeRole);
    new pipes.CfnPipe(this, "OrdersPipe", {      roleArn: pipeRole.roleArn,      source: orders.tableStreamArn!,      target: bus.eventBusArn,      sourceParameters: {        dynamoDbStreamParameters: {          startingPosition: "LATEST",          batchSize: 10,        },        filterCriteria: {          filters: [            { pattern: JSON.stringify({ eventName: ["INSERT", "MODIFY"] }) },          ],        },      },      targetParameters: {        eventBridgeEventBusParameters: {          source: "com.example.orders",          detailType: "order.changed",        },      },    });  }}

Bu şekil sıfır dokunuşludur: producer'ın uygulama kodu DynamoDB'ye zaten yazdığı gibi yazar ve pipe her değişikliği bir bus event'ine çevirir. Sıralama, pipe boyunca partition key başına korunur. Bus fan-out yaptığında downstream'de kaybolur; bu, event id ile dedup yapan domainlerin çoğu için sorun değildir. Producer Postgres veya MySQL üzerindeyse, outbox artı CDC şekli bir ekstra hareketli parça karşılığında daha güçlü transaction garantisi verir. Seçenekler arasında WAL'ı takip eden Debezium veya Kinesis'e serverless DMS ile PutEvents çağıran küçük bir Lambda rölesi vardır. Her veritabanı için tek bir varsayılan seçin ve bunu taş yol haline getirin.

Fan-out mekaniği: bir producer bus, birçok consumer bus

EventBridge'te cross-account teslimat bus'tan bus'a yapılır. Gönderici hesabın her consumer için bir kuralı vardır; hedef, consumer hesabının event bus ARN'ıdır. Mart 2023'ten itibaren her cross-account event-bus hedefi, hedef üzerinde events:PutEvents izni olan, gönderici hesaptaki bir IAM rolü ile çağrılmalıdır. Alıcı taraf ise kendi bus'ına, gönderici bus ARN'ından veya tüm AWS Organization'dan PutEvents'e izin veren bir resource policy ekler.

Cross-account hedefli sender tarafı kuralının CDK kodu şöyle görünür.

typescript
import { Stack, StackProps } from "aws-cdk-lib";import { Construct } from "constructs";import * as events from "aws-cdk-lib/aws-events";import * as targets from "aws-cdk-lib/aws-events-targets";import * as iam from "aws-cdk-lib/aws-iam";import * as sqs from "aws-cdk-lib/aws-sqs";
interface Props extends StackProps {  centralBusArn: string;  auditBusArn: string; // arn:aws:events:eu-central-1:<audit-acct>:event-bus/audit-in}
export class SenderRulesStack extends Stack {  constructor(scope: Construct, id: string, props: Props) {    super(scope, id, props);
    const bus = events.EventBus.fromEventBusArn(this, "Bus", props.centralBusArn);
    const crossAccountRole = new iam.Role(this, "AuditTargetRole", {      assumedBy: new iam.ServicePrincipal("events.amazonaws.com"),    });    crossAccountRole.addToPolicy(new iam.PolicyStatement({      actions: ["events:PutEvents"],      resources: [props.auditBusArn],    }));
    const ruleDlq = new sqs.Queue(this, "AuditRuleDlq", {      retentionPeriod: require("aws-cdk-lib").Duration.days(14),    });
    new events.Rule(this, "AuditRule", {      eventBus: bus,      eventPattern: {        source: ["com.example.orders", "com.example.payments"],        detailType: ["order.changed", "payment.settled"],      },      targets: [        new targets.EventBus(          events.EventBus.fromEventBusArn(this, "AuditBus", props.auditBusArn),          {            role: crossAccountRole,            deadLetterQueue: ruleDlq,          },        ),      ],    });  }}

Atlandığında canını yakan iki detay vardır. İlki, kuralın kendisi için bir dead-letter queue gerekmesidir; yalnızca consumer hesabındaki downstream SQS yetmez. Alıcı bus'ın resource policy'si yanlışsa sender kural başarı raporlar ama teslimat düşer; bunu sadece kural seviyesindeki DLQ yakalar. İkincisi, alıcı SQS customer-managed bir KMS anahtarı kullanıyorsa bu anahtarın policy'si, gönderici hesabın EventBridge servisini kms:GenerateDataKey ve kms:Decrypt için principal olarak adlandırmalıdır. AWS-managed anahtar cross-account yazımları sessizce reddeder.

Alıcı tarafta her consumer hesabı aynı dört katmanlı taş yola sahiptir: alıcı bus, consumer'a özel kural, SQS artı DLQ, compute artı store. Kural yalnızca o consumer'ın umursadığı event tiplerini filtreler; geri kalanı sıfır maliyetle yok sayılır.

typescript
import { Stack, StackProps, Duration } from "aws-cdk-lib";import { Construct } from "constructs";import * as events from "aws-cdk-lib/aws-events";import * as targets from "aws-cdk-lib/aws-events-targets";import * as sqs from "aws-cdk-lib/aws-sqs";import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";
interface ReceiverProps extends StackProps {  senderAccountId: string;}
export class AuditReceiverStack extends Stack {  constructor(scope: Construct, id: string, props: ReceiverProps) {    super(scope, id, props);
    const bus = new events.EventBus(this, "AuditBus", {      eventBusName: "audit-in",    });    bus.grantPutEventsTo(      new (require("aws-cdk-lib/aws-iam")).AccountPrincipal(props.senderAccountId),    );
    const dlq = new sqs.Queue(this, "AuditDlq", {      retentionPeriod: Duration.days(14),    });    const queue = new sqs.Queue(this, "AuditQueue", {      visibilityTimeout: Duration.seconds(60),      deadLetterQueue: { queue: dlq, maxReceiveCount: 5 },    });
    new events.Rule(this, "AuditIngest", {      eventBus: bus,      eventPattern: { source: ["com.example.orders", "com.example.payments"] },      targets: [new targets.SqsQueue(queue)],    });
    new cloudwatch.Alarm(this, "DlqAlarm", {      metric: dlq.metricApproximateNumberOfMessagesVisible(),      threshold: 1,      evaluationPeriods: 1,      alarmDescription: "Audit DLQ has messages, investigate",    });  }}

Yeni bir consumer'ı eklemek artık platform repo'sunda üç dosyalık bir değişikliktir: bir sender kural, bir IAM rolü, bir alıcı stack. Producer deploy olmaz.

Üç consumer, üç terminal store

Her consumer hesabı aynı dört katmanlı şekli korur. Farklılaşan şey store, retention ve erişim SLO'sudur. Domain'e özel endişeler producer'da değil, consumer sınırında yaşar.

Audit: S3 artı Object Lock artı Glacier lifecycle

Audit consumer'ı her event'i S3'e Object Lock Compliance modu ve versioning açık şekilde yazar. Bir lifecycle kuralı, nesneleri 90 gün sonra Glacier Deep Archive'a geçirir. Legal Hold, nesne seviyesinde uygulanan ayrı bir bayraktır; süresi dolmaz ve retention penceresinden sağ çıkar, dava gereksinimlerini tatmin eden budur. Erişim SLO'su dakika ile saat arasındadır: Deep Archive'dan Standard Retrieval yaklaşık 12 saat, Bulk 48 saate kadar sürer ve erişimler açıkça talep edilmelidir.

Audit bucket'ını şifreleyen KMS anahtarı audit hesabında yaşar. Producer takımı isteseler bile audit kayıtlarını çözemez; erişim sınırı güven üzerinden değil, anahtar policy'si ile zorlanır. Sorgulama için S3 prefix üzerinde Athena çoğu olay müdahale akışı için yeterlidir; soğuk veri biri sorana kadar soğuk kalır.

Customer Center: DynamoDB sıcak, S3 artı Athena ılık, talep üzerine restore soğuk

Customer Center consumer'ı event'leri destek ajanlarının gerçek zamanlı kullandığı bir şekle dönüştürür. Son 90 gün, customer id ile anahtarlanmış DynamoDB'de saniye altı lookup'lar için durur; 90 gün ile 2 yıl arası S3'te tarihe göre partitionlanmış olarak durur ve Athena ile saniyeler içinde sorgulanır; daha eski veri Glacier'dadır ve dakika-saat SLO'su ile açıkça restore edilmelidir. Ajan arama UX'i sıcak katman üzerinde tam metin arama istediğinde DynamoDB'nin yanına opsiyonel bir OpenSearch mirror'ı eklenir.

Consumer Lambda'sı projeksiyon mantığını barındırır; bu mantık domain'e özeldir ve producer'ın veri modelinden daha hızlı iterasyona uğrar. Bir ajan 2 yıldan eski bir müşteri kaydını açtığında UI "restore devam ediyor" durumunu render eder ve restore tamamlandığında tekrar sorgular. Katmanlama bu hesabın uygulama detayıdır, producer için görünmez ve koordinasyonsuz değiştirilebilir.

Marketing: warehouse veya CDP, consumer sınırında PII filtresi

Marketing, event'leri Firehose aracılığıyla Snowflake veya Redshift'e, ya da Segment gibi bir CDP'ye yazar. Buradaki kritik kural, PII filtresinin producer'da değil bu consumer sınırında yaşamasıdır. Producer hangi consumer'ın e-postaya, hangisinin anonimleştirilmiş kimliğe, hangisinin hiçbirine ihtiyacı olduğunu bilmez. Filtreyi upstream'e koymak, Marketing'in her yeni alan isteğinde producer takımına ticket açması anlamına gelir.

Retention kısa tutulur; tipik olarak ham event verisinde 90 gün artı GDPR Madde 17 silme hakkı taleplerini destekleyen bir opt-out izlem tablosu. Milyon event başına maliyet Audit ve Customer Center'dan düşüktür çünkü warehouse paylaşılan bir kaynaktır ve retention penceresi dardır. Segment oluşturma toplu veya gerçek zamana yakın çalışır, saniye altı değil.

Ne zaman sapmalı

Bus seçimi yukarıda ele alındı; bu bölüm EventBridge'te kalıp çok hesaplı veya sıfır dokunuşlu varsayılanları esneten sapmalar içindir.

Birinci sapma durumu, tek bir AWS hesabı ve iki veya üç in-process consumer olan küçük takımdır. Çok hesaplı şekil en az üç ayrı uyumluluk duruşuna sahip domain olduğunda ek yükünü hak eder; bunun altında, tek hesap içinde SNS'den SQS'e fan-out, cross-account araç maliyetine katlanmadan aynı örüntüyü korur. Üçüncü consumer ilk iki ile uyuşmayan bir retention veya PII politikasıyla göründüğü anda çok hesaplı şekle terfi edin.

İkinci sapma, DMS veya Debezium faturasının onaylanmadığı brownfield Postgres servisidir. Küçük bir Lambda tarafından okunan ve PutEvents çağıran logical replication slot'u, düşük ile orta hacimlerde operasyonel olarak daha ucuzdur; karşılığında slot muhasebesini siz sahiplenirsiniz. Bu bir varsayılan değil, bir geri düşüştür; çünkü slot yönetim hataları sessizdir ve alarm vermeden event akışını bozabilir.

Yaygın tuzaklar

Sender tarafındaki cross-account IAM rolü en sık kaçırılan detaydır. Kural oluşturma o olmadan da başarılı olur ve teslimat sessizce başarısız olur; Mart 2023 değişikliği hala eski eğitimlerde atlanır. O tarihten önce yazılmış her cross-account bus-to-bus snippet'i bayattır.

Producer sınırında PII temizliği ikincisidir. Temizliğin sahibi producer olduğunda, Marketing'in her alan ekleme veya çıkarma isteği bir producer ticket'ına döner ve Audit ile Customer Center takımları gerçekten ihtiyaç duydukları ham alanlar için yeniden pazarlık etmek zorunda kalır. Filtreyi domain bilgisinin yaşadığı consumer hesabına itin.

Idempotency üçüncüsüdür. EventBridge teslimatı en-az-bir-kere'dir ve Pipes üzerinden DynamoDB Streams Lambda hatalarında replay yapabilir. Her consumer event id ile dedup yapmalıdır; genellikle unique constraint'li bir event_id kolonu ile veya ingestion'da crypto.randomUUID() kullanıp terminal yazmadan önce kısa TTL'li bir cache'e bakarak. Warehouse'a iki kez ulaşan bir order.changed bir kampanya raporunda sessiz iki kat sayımdır; Audit'e ulaşan bir duplike ise silinemeyen, WORM korumalı bir duplikadır.

"Her ihtimale karşı" FIFO SQS dördüncüsüdür. EventBridge fan-out bus boyunca sıralamayı zaten korumadığı için FIFO downstream'de neredeyse hiçbir şey satın almaz ve partition başına throughput'u yarıya düşürür. FIFO'yu yalnızca partition başına sıralamanın iş açısından kritik olduğu ve consumer'ın gerçekten tek bir partition'dan okuduğu durumlar için, örneğin hesap durum makineleri için, saklayın.

Kapanış

Önerilen varsayılan; en az üç domain takımı, consumer başına farklı uyumluluk veya retention duruşu ve halihazırda yerinde bir AWS Organization sınırı olduğunda geçerlidir. Sıralama, replay veya throughput yukarıdaki eşikleri aştığında Kinesis veya MSK'ya sapın; consumer sayısı küçük ve retention politikaları örtüşüyorsa tek hesapta kalın. Çoğu takım için sonraki adım, bir veritabanı yolunu (DynamoDB Streams artı Pipes veya outbox artı CDC) seçip taş yol haline getirmek, ardından ikinci consumer'ı producer'a dokunmadan dahil etmektir.

Kaynaklar

İlgili Yazılar