Den 14. oktober 2026 publiserte AWS en teknisk gjennomgang av et mønster mange skyteam har bedt om i årevis: multi-region disaster recovery for Amazon API Gateway WebSocket-APIer. Det er et godt tidspunkt å ta tak i det, for WebSocket-tilkoblinger oppfører seg helt annerledes enn vanlige HTTP-kall når en hel AWS-region faller ut. En vanlig REST-klient prøver bare på nytt. En WebSocket-klient mister derimot en åpen, stateful forbindelse, og den må kobles til på nytt, autentiseres på nytt og gjenopprette hvilken som helst tilstand den hadde før bruddet.

Denne artikkelen er en praktisk, kommandolinje-for-kommandolinje gjennomgang av hvordan du bygger et slikt oppsett selv, fra en frisk SAM-stack i to regioner til testet failover med reconnect-logikk på klienten. Du trenger ingen enterprise-avtale med AWS for å følge med, bare en vanlig konto, to regioner og litt tålmodighet. Vi bruker eu-north-1 (Stockholm) som primærregion og eu-west-1 (Irland) som sekundærregion, siden det er et realistisk valg for nordiske team som vil holde data innenfor EU.

Dette er ikke bare en øvelse for selskaper med strenge beredskapskrav. WebSocket-API-er driver i dag alt fra live-dashbord og chattjenester til handelsplattformer og flerspillerfunksjoner, og felles for dem alle er at brukerne merker nedetid umiddelbart fordi tilkoblingen er åpen hele tiden. Et sted mellom “vi stoler på at AWS aldri feiler” og “vi bygger aktiv-aktiv i tre regioner” ligger det automatiserte aktiv-passiv-mønsteret denne guiden viser deg, og det er et realistisk startpunkt for de fleste produksjonsteam.

Hva er multi-region disaster recovery for WebSocket-APIer?

Multi-region disaster recovery betyr at applikasjonen din kan fortsette å virke selv om hele AWS-regionen den normalt kjører i, blir utilgjengelig. For en vanlig stateless HTTP-tjeneste er dette relativt greit: du ruter trafikken til en annen region, og ferdig. For en WebSocket API i AWS er det mer sammensatt, fordi tilkoblingen i seg selv er tilstanden. Ifølge AWS Architecture Blog kombinerer mønsteret Amazon Route 53 DNS failover, Amazon DynamoDB Global Tables, en canary-helsesjekk og reconnect-logikk på klientsiden for automatisk å koble til klienter på nytt og gjenopprette sesjonstilstand under en regional failover (AWS sine offisielle annonseringer).

Legg merke til at ingen av delene i dette mønsteret er eksotiske. Route 53, DynamoDB og API Gateway er alle tjenester de fleste AWS-team allerede bruker. Det som gjør oppgaven vanskelig, er å sy dem sammen slik at en klient faktisk merker failover som noen sekunders avbrudd i stedet for en full nedetid, og at tilkoblingstilstanden i DynamoDB ikke korrumperes av duplikate eller forsinkede skriv under overgangen.

Det er også verdt å være ærlig om kostnadssiden før du går i gang. En aktiv-passiv-løsning som denne koster i praksis bare litt mer enn én enkelt region, fordi API Gateway og Lambda fakturerer per kall og ikke per oppetid. Den eneste komponenten som påløper en viss fast kostnad uavhengig av trafikk, er den ekstra DynamoDB-replikaen. Det gjør dette mønsteret vesentlig billigere å innføre enn en fullverdig aktiv-aktiv-arkitektur, samtidig som du får det meste av beredskapsgevinsten.

Forutsetninger: dette trenger du før du starter

Sett av rundt 90 minutter til hele gjennomgangen, inkludert ventetid på regional replikering av DynamoDB-tabeller. Mesteparten av tiden går med til venting, ikke skriving: SAM-deploy til to regioner tar typisk 5–10 minutter hver, og Global Tables-replikeringen kan ta ytterligere 5–15 minutter avhengig av hvor mye data som allerede ligger i tabellen fra før. Du trenger følgende installert og klart:

  • En AWS-konto med rettigheter til API Gateway, Lambda, DynamoDB, Route 53 og CloudFormation i to regioner
  • AWS CLI – nyeste v2-versjon (sjekk med aws --version)
  • AWS SAM CLI – nyeste versjon, for å bygge og deploye WebSocket-stacken
  • Node.js – nyeste LTS-utgave (brukt her: 22.x) til Lambda-funksjonene og testklienten
  • wscat – nyeste versjon, installert med npm install -g wscat, til å teste WebSocket-tilkoblinger manuelt
  • Docker – nyeste versjon, kun hvis du vil kjøre den lokale testen med LocalStack
  • Et registrert domene i Route 53 (eller en hosted zone du kontrollerer) til failover-routing

Du trenger ikke Terraform for å følge denne guiden, men hvis teamet ditt allerede har infrastruktur som kode der, er mønsteret like greit å uttrykke i Terraform som i SAM. Vi har tidligere gått gjennom hvordan du bygger AWS-infrastruktur med Terraform hvis du vil ha det som utgangspunkt i stedet.

IAM-rettigheter: minste nødvendige tilgang

Fordi dette oppsettet spenner over to regioner og tre tjenester, er det fristende å gi byggebrukeren brede rettigheter “for å slippe å feilsøke IAM”. Ikke gjør det. En rolle med for mye tilgang i én region blir et like stort problem som selve regionsutfallet du prøver å beskytte deg mot. Hold deg til konkrete handlinger per tjeneste, og skill tydelig mellom rettigheter Lambda-funksjonene trenger i kjøretid, og rettigheter du selv trenger som operatør når du setter opp failover-kjeden.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DynamoDBConnectionsAccess",
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:DeleteItem",
        "dynamodb:UpdateItem"
      ],
      "Resource": "arn:aws:dynamodb:*:ACCOUNT_ID:table/ws-connections"
    },
    {
      "Sid": "ApiGatewayManagementAccess",
      "Effect": "Allow",
      "Action": [
        "execute-api:ManageConnections"
      ],
      "Resource": "arn:aws:execute-api:*:ACCOUNT_ID:*/prod/POST/@connections/*"
    }
  ]
}

Legg denne policyen på utførelsesrollen til hver Lambda-funksjon, ikke på en delt rolle som også brukes til andre formål. Operatørrollen du selv bruker til å kjøre sam deploy og Route 53-kommandoene, bør være separat og kreve MFA, siden den rollen i praksis kan styre hvilken region hele produksjonstrafikken går til.

Mange team gjør feilen å gjenbruke samme IAM-rolle på tvers av begge regionene, i troen på at det forenkler administrasjonen. Problemet er at en IAM-rolle i seg selv er global, mens ressursene den peker til (DynamoDB-tabellen, API Gateway-API-en) er regionale. Hvis policyen bruker en hardkodet ARN med feil region, vil funksjonen feile stille i den andre regionen helt til du faktisk tester failover, altså akkurat når du ikke har tid til å feilsøke IAM. Bruk derfor ${AWS::Region}-variabelen i CloudFormation-malen i stedet for å hardkode regionnavnet i ressurs-ARN-ene.

Det er også verdt å skru på AWS CloudTrail-logging for Route 53-endringer spesifikt. Fordi en enkelt update-health-check– eller change-resource-record-sets-kommando kan flytte all trafikk mellom regioner, bør enhver slik endring være sporbar til en bestemt person eller et bestemt deploy-pipeline-kjøring, ikke bare til en delt servicekonto. De samme prinsippene om minste nødvendige tilgang gjelder forresten også for andre AWS-tjenester i stacken din, blant annet lagring, se gjennomgangen av sikker S3-konfigurasjon hvis du også lagrer failover-logger eller meldingshistorikk der.

Arkitekturen du skal bygge

Før du åpner terminalen, er det verdt å forstå hvilke fire komponenter som faktisk gjør jobben. Hver av dem løser et konkret problem: hvem klienten snakker med, hvor tilstanden lever, hvem som bestemmer at en region er nede, og hvordan klienten oppfører seg når det skjer.

KomponentAWS-tjenesteRolle i arkitekturen
InngangspunktAmazon API Gateway (WebSocket API)Tar imot og holder åpne tilkoblinger i hver region
TilkoblingslogikkAWS Lambda ($connect, $disconnect, $default)Håndterer autentisering, ruting av meldinger og rydding
Delt tilstandAmazon DynamoDB Global TablesReplikerer tilkoblings- og sesjonsdata mellom regioner
TrafikkstyringAmazon Route 53 (helsesjekk + failover-policy)Avgjør hvilken region som er “live” for nye tilkoblinger
KlientlogikkEgen frontend-kode (nettleser/app)Kobler til på nytt med backoff når forbindelsen brytes

Datasenter og forbindelse

API Gateway WebSocket-API-er er regionale ressurser. Det betyr at du må deploye to separate stacker, én per region, med samme route-struktur. Klienten kobler aldri direkte til API Gateway-endepunktet du ser i konsollen. Den kobler til et eget domenenavn (for eksempel ws.dittdomene.no) som Route 53 peker til riktig regional endepunkt basert på helsesjekken.

Dette domenenavnet krever et TLS-sertifikat som dekker begge de regionale custom domain-mappingene, enten via AWS Certificate Manager i hver region eller ett sertifikat importert til begge. Det er lett å glemme dette steget fordi SAM-malen i seg selv ikke krever et custom domain for å fungere, men uten det må klienten din peke direkte mot de regionspesifikke execute-api-adressene, og da er det ingenting Route 53 kan gjøre for deg under en failover.

Tilstand og konsistens

DynamoDB Global Tables bruker versjon 2019.11.21 i dag, og den replikerer skriv mellom regioner i nær sanntid (typisk under ett sekund under normal drift). Replikering er imidlertid “eventually consistent” på tvers av regioner, så du må designe rutene dine for å tåle at samme tilkoblings-ID dukker opp to ganger, eller at en skriveoperasjon ankommer i feil rekkefølge rett etter en failover.

I praksis betyr dette at konflikthåndteringen i Global Tables følger en “last writer wins”-modell basert på tidsstempel. Hvis en klient kobler til i eu-north-1 og skriver en statusoppdatering i det sekundet regionen faller ut, og den samme klienten kobler til på nytt i eu-west-1 noen sekunder senere, er det ikke garantert at den første skriveoperasjonen rekker å replikere før den andre skjer. For de fleste tilkoblingsmetadata (hvem er koblet til, hvilken region de sist traff) er dette helt greit. For forretningskritisk tilstand (for eksempel saldoer eller ordrebøker) bør du vurdere om WebSocket-laget i det hele tatt skal eie den tilstanden, eller om det bare skal formidle hendelser fra en annen, sterkere konsistent datakilde.

Steg 1–3: Sett opp WebSocket-API-en i primærregionen

Start med en frisk SAM-mal i eu-north-1. Dette er den vanligste måten å definere en WebSocket API med tilhørende Lambda-ruter på, og den samme malen gjenbruker du for sekundærregionen i neste steg.

# Steg 1: opprett prosjektmappen
mkdir websocket-dr && cd websocket-dr
sam init --runtime nodejs22.x --name ws-dr-primary --app-template hello-world

# Steg 2: bytt ut template.yaml med WebSocket-oppsettet
cat > template.yaml << 'EOF'
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
  ConnectionsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: ws-connections
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: connectionId
          AttributeType: S
      KeySchema:
        - AttributeName: connectionId
          KeyType: HASH

  WebSocketApi:
    Type: AWS::ApiGatewayV2::Api
    Properties:
      Name: ws-dr-api
      ProtocolType: WEBSOCKET
      RouteSelectionExpression: "$request.body.action"

  ConnectRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref WebSocketApi
      RouteKey: $connect
      Target: !Join [ '/', [ 'integrations', !Ref ConnectIntegration ] ]

  ConnectIntegration:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
      ApiId: !Ref WebSocketApi
      IntegrationType: AWS_PROXY
      IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ConnectFunction.Arn}/invocations

  ConnectFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/connect/
      Handler: index.handler
      Runtime: nodejs22.x
      Environment:
        Variables:
          TABLE_NAME: !Ref ConnectionsTable
EOF

# Steg 3: bygg og deploy til primærregionen
sam build
sam deploy --guided --region eu-north-1 --stack-name ws-dr-primary

Når deployen er ferdig, får du et WebSocket-endepunkt av typen wss://abc123.execute-api.eu-north-1.amazonaws.com/prod. Noter deg API-ID-en, du trenger den i Route 53-steget senere. Hvis du kjenner mønsteret fra EKS med OIDC-leverandører, er logikken bak IAM-rollene her ganske lik: minste nødvendige rettigheter, ingen delte nøkler mellom regioner.

Selve $connect-handleren bør være så enkel og rask som mulig, siden den kjører på hver eneste nye tilkobling i begge regioner. Her er en minimal, men idempotent variant som skriver til DynamoDB uten å feile hvis samme tilkobling av en eller annen grunn registreres to ganger:

// functions/connect/index.js
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
const { DynamoDBDocumentClient, PutCommand } = require("@aws-sdk/lib-dynamodb");

const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));

exports.handler = async (event) => {
  const connectionId = event.requestContext.connectionId;
  const region = process.env.AWS_REGION;

  await client.send(new PutCommand({
    TableName: process.env.TABLE_NAME,
    Item: {
      connectionId,
      region,
      connectedAt: Date.now(),
    },
    // Idempotent: samme connectionId overskriver trygt i stedet for å feile
    ConditionExpression: "attribute_not_exists(connectionId) OR connectionId = :cid",
    ExpressionAttributeValues: { ":cid": connectionId },
  }));

  return { statusCode: 200, body: "Tilkoblet" };
};

Steg 4–6: Legg til sekundærregionen og DynamoDB Global Tables

Nå dupliserer du stacken til eu-west-1, men uten å opprette en helt ny DynamoDB-tabell. I stedet gjør du den eksisterende tabellen til en global tabell, slik at begge regionene deler samme tilkoblingsdata.

# Steg 4: deploy samme stack til sekundærregionen
sam deploy --guided --region eu-west-1 --stack-name ws-dr-secondary

# Steg 5: gjør tabellen om til en global tabell (kjøres fra primærregionen)
aws dynamodb update-table \
  --region eu-north-1 \
  --table-name ws-connections \
  --billing-mode PAY_PER_REQUEST \
  --replica-updates '[{"Create":{"RegionName":"eu-west-1"}}]'

# Steg 6: verifiser replikeringsstatus
aws dynamodb describe-table --region eu-north-1 --table-name ws-connections \
  --query "Table.Replicas"

Forventet output fra steg 6 ser slik ut når replikeringen er fullført:

[
    {
        "RegionName": "eu-north-1",
        "ReplicaStatus": "ACTIVE"
    },
    {
        "RegionName": "eu-west-1",
        "ReplicaStatus": "ACTIVE"
    }
]

Legg merke til at statusen først vil vise CREATING i noen minutter. Ikke gå videre til neste steg før begge regionene viser ACTIVE, ellers risikerer du skriv som aldri blir replikert.

Hvis du allerede har en tabell med mye data i produksjon, bør du gjøre denne konverteringen i et vindu med lavere trafikk. Selve konverteringen til en global tabell krever at DynamoDB leser gjennom hele den eksisterende tabellen for å bygge opp replikeringsgrunnlaget i den nye regionen, og det kan ta vesentlig lengre tid enn på en tom testabell som i denne guiden.

Steg 7–9: Route 53-helsesjekker og failover-routing

Dette er kjernen i mønsteret. AWS sin egen dokumentasjon er direkte på dette punktet: Du kan bruke Route 53-helsesjekker til å styre DNS-failover fra en API Gateway-API i en primærregion til en API Gateway-API i en sekundærregion (Amazon Web Services). Du trenger en helsesjekk per region, pekende mot et lett "canary"-endepunkt, ikke mot selve WebSocket-routen (helsesjekker kan ikke åpne WebSocket-tilkoblinger).

# Steg 7: opprett helsesjekk mot et HTTP-canary-endepunkt i primærregionen
aws route53 create-health-check \
  --caller-reference "ws-dr-primary-$(date +%s)" \
  --health-check-config '{
    "IPAddress": null,
    "FullyQualifiedDomainName": "abc123.execute-api.eu-north-1.amazonaws.com",
    "Port": 443,
    "Type": "HTTPS",
    "ResourcePath": "/prod/health",
    "RequestInterval": 10,
    "FailureThreshold": 3
  }'

# Steg 8: gjenta for sekundærregionen (eu-west-1-endepunktet)

# Steg 9: opprett failover-recordsett i den hostede sonen din
aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://failover-record.json

failover-record.json inneholder to CNAME-oppføringer for ws.dittdomene.no: én merket PRIMARY som peker til eu-north-1-endepunktet med helsesjekk-ID-en fra steg 7, og én merket SECONDARY som peker til eu-west-1-endepunktet. FailureThreshold på 3 og RequestInterval på 10 sekunder er standardverdiene AWS anbefaler for de fleste produksjonsoppsett, men du kan sette intervallet til 30 sekunder hvis du vil spare på antall helsesjekk-forespørsler.

Route 53 støtter flere rutingpolicyer utover ren failover, og det er verdt å vite hvorfor failover-policyen er riktig valg her. En vektet policy (weighted routing) fordeler trafikk prosentvis mellom regioner og passer bedre til gradvis utrulling enn til beredskap. En latensbasert policy velger region etter hvor raskt klienten når den, noe som er nyttig for ren ytelsesoptimalisering, men som ikke i seg selv reagerer på at en region er nede. Failover-policyen er den eneste av de tre som er eksplisitt bygget rundt helsesjekk-tilstand, og det er derfor den er riktig verktøy for akkurat dette problemet.

Steg 10–11: Reconnect-logikk på klientsiden

Dette er steget de fleste team hopper over, og det er nettopp derfor WebSocket-failover ofte føles verre enn den faktisk er. Route 53 kan bytte DNS-oppføring på sekunder, men en allerede åpen TCP/WebSocket-tilkobling vet ingenting om det. Klienten din må selv oppdage at forbindelsen er brutt og koble til på nytt, med eksponentiell backoff og jitter for å unngå at tusenvis av klienter prøver på nytt samtidig.

// Steg 10-11: reconnect med eksponentiell backoff og jitter
class ResilientSocket {
  constructor(url) {
    this.url = url;
    this.attempt = 0;
    this.connect();
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log("Tilkoblet");
      this.attempt = 0;
    };

    this.ws.onclose = (event) => {
      console.warn("Tilkobling brutt, forsøker på nytt...", event.code);
      this.scheduleReconnect();
    };

    this.ws.onerror = () => this.ws.close();
  }

  scheduleReconnect() {
    this.attempt += 1;
    const base = Math.min(30000, 1000 * 2 ** this.attempt);
    const jitter = Math.random() * 1000;
    setTimeout(() => this.connect(), base + jitter);
  }
}

const socket = new ResilientSocket("wss://ws.dittdomene.no/prod");

Legg merke til at reconnect-forsøket går mot ws.dittdomene.no, ikke mot et hardkodet regionalt endepunkt. Da er det Route 53 som avgjør hvilken region den nye tilkoblingen lander i, ikke klienten selv. Dette er også grunnen til at ruter som $connect og $default bør være idempotente: en klient som kobler til på nytt tre ganger i løpet av et failover-vindu, skal ikke opprette tre motstridende rader i DynamoDB.

Steg 12–13: Test failover med kontrollert feilsimulering

Du bør aldri stole på et disaster recovery-oppsett du ikke selv har testet. AWS anbefaler å øve på reelle failover-scenarioer i stedet for bare å stole på arkitekturdiagrammer, og det gjelder like mye her. Den enkleste måten å simulere en regional hendelse på, uten å faktisk ta ned noe i produksjon, er å deaktivere helsesjekken midlertidig.

# Steg 12: simuler at primærregionen er nede
aws route53 update-health-check \
  --health-check-id YOUR_PRIMARY_HEALTH_CHECK_ID \
  --disabled

# Steg 13: koble til med wscat og se hvilken region du havner i
wscat -c wss://ws.dittdomene.no/prod

# ... vent til DNS-TTL og helsesjekk-terskel har gått (noen titalls sekunder) ...
# koble til på nytt og bekreft at du nå treffer eu-west-1

# husk å slå helsesjekken på igjen etterpå
aws route53 update-health-check \
  --health-check-id YOUR_PRIMARY_HEALTH_CHECK_ID \
  --no-disabled

Et vellykket testforsøk ser slik ut i terminalen etter failover: wscat rapporterer en ny tilkobling, og om du spør API Gateway om hvilken region integrasjonen svarer fra (for eksempel via en enkel loggmelding i Lambda), skal den nå peke til eu-west-1 i stedet for eu-north-1.

Å deaktivere helsesjekken er den enkleste testmetoden, men ikke den eneste. For et mer realistisk kaosforsøk kan du i stedet sette en midlertidig Network ACL-regel som blokkerer innkommende trafikk til primærregionens API Gateway-endepunkt, eller sette FailureThreshold til 1 i en kort periode for å se hvordan systemet oppfører seg ved en mer aggressiv terskel. Uansett metode bør testen inkludere minst én faktisk klient (ikke bare wscat) som kjører den fulle reconnect-logikken fra steg 10–11, siden det er klientoppførselen som til syvende og sist avgjør brukeropplevelsen under failover.

Steg 14: Mål RTO og RPO, og dokumenter øvelsen

Når testen er gjennomført, noterer du to tall for teamet ditt: RTO (recovery time objective), altså hvor lang tid det tok fra du deaktiverte helsesjekken til klienten var tilkoblet igjen i sekundærregionen, og RPO (recovery point objective), altså om noen tilkoblingsrader i DynamoDB manglet eller var utdaterte rett etter bytte. Disse tallene vil variere med din egen RequestInterval, FailureThreshold og DNS-TTL, så det finnes ingen fasit du kan kopiere fra en artikkel. Kjør testen, skriv ned de faktiske tallene fra ditt eget oppsett, og gjenta øvelsen hver gang du endrer noe i failover-kjeden.

En enkel måte å regne på RTO er å summere de tre leddene som faktisk bidrar til ventetiden: tiden til helsesjekken oppdager feilen (RequestInterval multiplisert med FailureThreshold), DNS-TTL-en på recordsettet, og klientens egen reconnect-forsinkelse fra backoff-logikken. Med verdiene fra denne guiden (10 sekunders intervall, terskel på 3, TTL på 30–60 sekunder) vil de to første leddene alene typisk ligge et sted mellom 30 og 90 sekunder, før klientens egen backoff legges til. RPO handler mer om hvor mye tilstand du er villig til å miste enn om tid, og her er svaret for dette mønsteret som regel "praktisk talt null" for ren tilkoblingsmetadata, men det forutsetter at du faktisk har verifisert at Global Tables-replikeringen var ACTIVE i begge regioner før hendelsen.

Komplett eksempelprosjekt: slik ser hele oppsettet ut samlet

Når alle 14 stegene er gjennomført, bør prosjektmappen din se omtrent slik ut:

websocket-dr/
├── template.yaml              # SAM-mal, deployes til begge regioner
├── functions/
│   ├── connect/index.js       # $connect-ruten, skriver til DynamoDB
│   ├── disconnect/index.js    # $disconnect-ruten, rydder opp
│   └── default/index.js       # $default-ruten, idempotent meldingslogikk
├── failover-record.json       # Route 53 change-batch for failover-policy
├── client/resilient-socket.js # Reconnect-logikk med backoff og jitter
└── test/simulate-failover.sh  # Skript som kjører steg 12-13 automatisk

Dette er et komplett, fungerende utgangspunkt: to regionale SAM-stacker, én global DynamoDB-tabell, én Route 53-failover-policy og én klient som vet hvordan den skal oppføre seg når alt dette settes på prøve. Alt du trenger å tilpasse for eget bruk, er domenenavnet, regionvalgene og selve meldingslogikken i default/index.js.

De to resterende handlerne er like enkle som connect-funksjonen. disconnect-ruten rydder opp i DynamoDB, mens default-ruten er stedet du legger selve forretningslogikken, og den bør alltid sjekke om en melding allerede er behandlet før den utfører noe med varige effekter:

// functions/disconnect/index.js
const { DynamoDBClient } = require("@aws-sdk/client-dynamodb");
const { DynamoDBDocumentClient, DeleteCommand } = require("@aws-sdk/lib-dynamodb");

const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));

exports.handler = async (event) => {
  await client.send(new DeleteCommand({
    TableName: process.env.TABLE_NAME,
    Key: { connectionId: event.requestContext.connectionId },
  }));
  return { statusCode: 200, body: "Frakoblet" };
};

// functions/default/index.js
exports.handler = async (event) => {
  const body = JSON.parse(event.body || "{}");
  const messageId = body.messageId;

  // Idempotenssjekk: hopp over meldinger vi allerede har sett,
  // f.eks. duplikater fra en reconnect rett etter failover
  const alreadyProcessed = await wasAlreadyProcessed(messageId);
  if (alreadyProcessed) {
    return { statusCode: 200, body: "Allerede behandlet" };
  }

  await processMessage(body);
  await markAsProcessed(messageId);

  return { statusCode: 200, body: "OK" };
};

Vanlige fallgruver når du bygger dette selv

De fleste problemene med denne typen oppsett oppstår ikke fordi AWS-tjenestene feiler, men fordi antagelser fra en enkeltregion-arkitektur sniker seg inn uendret. Her er feilene vi ser oftest når team bygger dette for første gang.

  • Helsesjekk mot feil endepunkt. Route 53 kan ikke helsesjekke en WebSocket-rute direkte. Bruk alltid et separat, lett HTTP(S)-endepunkt for canary-sjekken.
  • Stole på kontrollplanet under en hendelse. Et kjent AWS Well-Architected-prinsipp er å stole på dataplanet og ikke kontrollplanet under gjenoppretting. Failover-kontroller bør fungere uten avhengighet til den primære regionen i det hele tatt.
  • Ingen idempotens i meldingsrutene. Reconnects og retries kan produsere duplikate forespørsler. Hvis $default-ruten ikke tåler å motta samme melding to ganger, får du dupliserte hendelser hos sluttbrukeren.
  • Hardkodet regionalt endepunkt i klienten. Hvis frontend-koden peker direkte mot *.execute-api.eu-north-1.amazonaws.com i stedet for det Route 53-styrte domenet, hjelper ikke failover-oppsettet ditt i det hele tatt.
  • Ingen reconnect-logikk, bare en feilmelding. Uten eksponentiell backoff og jitter kan tusenvis av klienter som prøver på nytt samtidig, selv skape en ny overbelastning i sekundærregionen rett etter failover.
  • Glemmer å skru helsesjekken tilbake på etter testing. Dette er en vanlig, kjedelig feil: teamet tester failover på fredag, glemmer å reaktivere den primære helsesjekken, og hele helgens trafikk går unødvendig til sekundærregionen.
  • Antar at CloudFormation-stacken er identisk fordi malen er den samme. Kontoomfattende tjenestekvoter (service quotas) kan være ulike i de to regionene, spesielt hvis sekundærregionen sjelden brukes. Sjekk alltid kvotene for samtidige Lambda-kjøringer og API Gateway-tilkoblinger i begge regioner før du stoler på dem.

Feilsøking: 8 vanlige problemer og hvordan du løser dem

Når noe ikke virker som forventet i et oppsett med to regioner, er det fort gjort å lete i feil ende. Sjekk alltid Route 53 og DynamoDB-replikeringen først, siden de fleste problemer i praksis stammer derfra og ikke fra selve API Gateway- eller Lambda-koden.

SymptomSannsynlig årsakLøsning
Failover skjer aldri, selv med helsesjekken deaktivertFeil helsesjekk-ID koblet til failover-recordsettetBekreft ID-en med aws route53 get-health-check og match mot recordsettet
Klienten henger i "connecting" i flere minutterDNS-TTL er satt for høytSett TTL til 30–60 sekunder på failover-recordsettene
Duplikate rader i DynamoDB etter failover$connect-ruten er ikke idempotentBruk connectionId som partisjonsnøkkel og condition-skriv
Global Tables viser status CREATING i lang tidStor eksisterende tabell som må replikeres fullt ut førstVent til ReplicaStatus viser ACTIVE før du går videre
Helsesjekk rapporterer feil selv om API-en svarerCanary-endepunktet krever autentisering helsesjekken ikke harLag et eget, åpent helse-endepunkt uten autorisasjon
Klienten kobler til, men mister meldinger rett etter failoverManglende buffring eller re-synk av tilstand fra DynamoDBHent siste kjente tilstand fra tabellen ved hvert nytt $connect-kall
Reconnect-stormen overbelaster sekundærregionenIngen jitter i backoff-logikkenLegg til tilfeldig jitter, ikke kun eksponentiell backoff
Testen virker lokalt, men ikke i produksjonIAM-roller eller ressursnavn er ikke identiske mellom regionerDeploy samme SAM-mal uendret til begge regioner, kun med ulik --region-flagg

Avanserte tips for produksjonsmiljøer

Når grunnoppsettet fungerer, er det noen grep som skiller et testprosjekt fra noe du faktisk kan stole på i produksjon.

Idempotens og kostnadskontroll

Bruk betingede skriv (ConditionExpression) i DynamoDB for alle ruter som kan bli kalt flere ganger under en reconnect-storm. Hold også et øye med kostnaden: PAY_PER_REQUEST-modus på en global tabell faktureres per region, så en failover-test som varer en hel arbeidsdag, koster mer enn ett som varer ti minutter.

Sett opp egne CloudWatch-alarmer for helsesjekk-statusen i begge regioner, ikke bare for feilraten på selve API-en. En helsesjekk som flipper mellom frisk og usunn gjentatte ganger (flapping) er ofte et tegn på et underliggende kapasitetsproblem, ikke en reell regional hendelse, og bør varsles separat fra en faktisk failover. Det er også verdt å slå på detaljert API Gateway-logging i begge regioner fra dag én, siden det er mye vanskeligere å feilsøke en hendelse som skjedde i sekundærregionen hvis du aldri har sett et eneste reelt logglinje derfra før.

Når du ruller ut endringer i Lambda-koden, bør du unngå å oppdatere begge regionene samtidig. Deploy først til sekundærregionen, kjør en rask smoke-test der (selv om den ikke mottar produksjonstrafikk), og deploy deretter til primærregionen. Da unngår du scenarioet der en feil i en ny kodeversjon rammer begge regionene samtidig rett før du faktisk trenger failover-kjeden til å virke.

Hvis teamet ditt i stedet kjører WebSocket-arbeidslaster på Kubernetes fremfor API Gateway, gjelder mange av de samme prinsippene, bare med andre verktøy for helsesjekk og trafikkstyring. Kubernetes-bloggen har løpende dekning av hvordan slike mønstre utvikler seg i klyngedrift, se Kubernetes Blog for oppdatert dekning av dette.

For team som vurderer en bredere edge-basert tilnærming i tillegg til regional DNS-failover, kan det være verdt å sammenligne med hvordan Cloudflare beskriver å beskytte offentlige nettverk på sin globale edge, se deres referansearkitektur for å beskytte offentlige nettverk. Det er ikke en erstatning for regional failover i AWS, men et komplementært lag hvis du uansett terminerer trafikk gjennom en CDN foran API Gateway.

Lokal testing med LocalStack før du deployer

Du trenger ikke betale for to regioner i AWS bare for å teste at SAM-malen bygger riktig. LocalStack lar deg kjøre API Gateway, Lambda og DynamoDB lokalt i Docker, slik at du kan iterere raskere før første reelle deploy.

# docker-compose.yml for lokal testing
version: "3.8"
services:
  localstack:
    image: localstack/localstack:latest
    ports:
      - "4566:4566"
    environment:
      - SERVICES=apigateway,lambda,dynamodb,route53
      - DEBUG=1

# kjør: docker compose up -d
# deploy mot LocalStack:
sam build
sam deploy --guided --endpoint-url http://localhost:4566

LocalStack-teamet publiserer løpende egne gjennomganger av akkurat denne typen lokal AWS-testing, blant annet artikler fra utvikler Harsh Mishra, se LocalStack-bloggen for flere eksempler på hvordan du kan teste serverless-arbeidslaster uten en reell AWS-regning mens du utvikler.

Vær realistisk om hva lokal testing faktisk dekker: LocalStack er utmerket til å verifisere at SAM-malen bygger riktig, at Lambda-koden din kjører uten syntaksfeil, og at IAM-policyene faktisk gir funksjonene den tilgangen de trenger. Den emulerer derimot ikke ekte regional failover eller ekte Route 53-helsesjekk-oppførsel mot internett. Bruk LocalStack til å gjøre den tidlige utviklingen raskere, og reserver den ekte to-regions-testen fra steg 12–13 til det du faktisk vil stole på i produksjon.

Som en siste påminnelse: uansett hvilken sky eller hvilket verktøy du bruker, er det god praksis å følge med på offisielle driftsnotater før du oppgraderer noe i denne kjeden. IBM Cloud er ett eksempel på en leverandør som publiserer detaljerte driftsnotater for containertjenester, og samme vane er verdt å ha for AWS-tjenestene i dette oppsettet, spesielt Node.js-runtimen Lambda-funksjonene dine kjører på.

Sammenligning: tre strategier for regional failover

StrategiHvordan den virkerKompleksitetNår den passer
Manuell failoverEn operatør oppdager hendelsen og bytter DNS manueltLavInterne verktøy med lav trafikk og høy feiltoleranse
Automatisert aktiv-passiv (denne guiden)Route 53-helsesjekk bytter DNS automatisk til en passiv sekundærregionMiddelsKundevendte WebSocket-tjenester med krav til rask gjenoppretting
Aktiv-aktiv i begge regionerBegge regioner tar trafikk samtidig, med global lastbalanseringHøyTjenester der selv sekunders avbrudd er uakseptabelt

De fleste team bør starte med automatisert aktiv-passiv, slik vi har bygget i denne guiden, og først vurdere aktiv-aktiv når de faktiske RTO/RPO-tallene fra egne tester viser at det er nødvendig. Det er samme tankegang som gjelder når man vurderer kaldstart-optimalisering i Lambda: mål først, optimaliser deretter.

Prisen på kompleksitet stiger ikke lineært mellom disse tre strategiene. Steget fra manuell til automatisert aktiv-passiv er relativt lite: du legger til en helsesjekk, en failover-policy og noen linjer reconnect-logikk på klienten. Steget fra aktiv-passiv til ekte aktiv-aktiv er langt større, fordi du da må løse skrivekonflikter i sanntid i stedet for bare ved en sjelden failover-hendelse, og du må teste begge regionene under reell produksjonslast kontinuerlig, ikke bare i en planlagt øvelse. De fleste WebSocket-arbeidslaster trenger aldri å ta det siste steget.

Husk også at regional nedetid i seg selv ikke er noe eksotisk scenario. Vi har tidligere dekket hvordan en driftsstans i Azures Sweden Central-region rammet nordiske kunder i nesten seks timer, og det samme kan skje i enhver sky dersom du ikke har et testet failover-oppsett klart på forhånd.

Ofte stilte spørsmål

Må begge regionene kjøre aktivt hele tiden, eller kan sekundærregionen stå i ro?
I oppsettet i denne guiden kjører begge regionene aktivt, men kun primærregionen mottar trafikk under normal drift. Det er fortsatt billigere enn en aktiv-aktiv-løsning, siden API Gateway og Lambda er betal-per-bruk, og den eneste faste kostnaden er DynamoDB Global Tables-replikeringen.

Hvor fort bytter Route 53 faktisk til sekundærregionen?
Det avhenger av RequestInterval og FailureThreshold du setter i helsesjekken, pluss DNS-TTL-en på recordsettet. Med standardverdiene fra steg 7–9 skjer byttet vanligvis i løpet av under ett minutt, men du bør alltid måle dette selv med testen i steg 12–13 i stedet for å anta et tall. Husk også at enkelte DNS-resolvere og nettlesere cacher oppføringer lenger enn TTL-en formelt tilsier, så den reelle oppfattede failover-tiden for en liten andel klienter kan bli noe lengre enn tallet du måler fra din egen testmaskin.

Kan jeg bruke dette mønsteret med REST-API-er i stedet for WebSocket?
Ja, Route 53-delen av mønsteret er identisk. Det du slipper med REST, er reconnect-logikken på klienten, siden HTTP i utgangspunktet ikke har en vedvarende tilkobling å miste.

Hva skjer med tilkoblinger som var aktive i primærregionen idet den faller ut?
De mistes. Det finnes ingen måte å "flytte" en etablert WebSocket-tilkobling til en annen region i AWS i dag. Derfor er reconnect-logikken i steg 10–11 ikke valgfri, den er selve mekanismen som gjør failover merkbar som et kort avbrudd i stedet for full nedetid for brukeren.

Trenger jeg DynamoDB Global Tables, eller holder det med én tabell i primærregionen?
Hvis sekundærregionen skal kunne svare på $connect-forespørsler uten å vente på at primærregionen kommer tilbake, må tilkoblingsdataene finnes der også. Global Tables er den enkleste måten å få det til uten å bygge egen replikeringslogikk.

Hvor ofte bør jeg teste failover-oppsettet?
Minst én gang etter enhver endring i Route 53-konfigurasjonen, IAM-rollene eller SAM-malen, og gjerne som en fast, planlagt øvelse hvert kvartal selv om ingenting er endret. Infrastruktur som aldri har blitt testet, er i praksis ikke testet infrastruktur.

Kan jeg bruke en tredje region for ekstra sikkerhet?
Ja, DynamoDB Global Tables støtter flere regioner samtidig, og Route 53 kan konfigureres med flere failover-nivåer. Kompleksiteten øker imidlertid raskt, så de fleste team bør få to-regions-oppsettet solid på plass og testet først.

Fungerer dette oppsettet med en WebSocket-autorisator (Lambda authorizer)?
Ja, men husk at autorisatoren også må deployes og testes i begge regioner, med identisk logikk. Hvis autorisatoren validerer tokens mot en tjeneste som kun finnes i primærregionen, vil klienter ikke klare å koble til i sekundærregionen under en failover, selv om selve WebSocket-API-en er helt frisk der. Test derfor alltid autorisatoren som en del av failover-øvelsen, ikke bare selve tilkoblingen.