AWS RDS RDS Proxy PostgreSQL MySQL baza danych connection pooling

AWS RDS Proxy: konfiguracja, tuning i znane ograniczenia

Poprawna konfiguracja AWS RDS Proxy: MaxConnectionsPercent, zarządzanie idle connections, unikanie pinningu i scenariusze, w których RDS Proxy szkodzi bardziej niż pomaga.

Jerzy Kopaczewski ·
Wdrożyłeś RDS Proxy licząc na rozwiązanie problemów z wyczerpaniem połączeń. Zamiast tego widzisz błędy ConnectionBorrowTimeout, sesje są przypinane (pinned) - co niweluje sens poolingu - albo latencja rośnie w porównaniu z bezpośrednimi połączeniami. Ten runbook opisuje poprawną konfigurację, parametry tuningu i scenariusze, w których RDS Proxy jest złym narzędziem.

Jeśli jeszcze nie trafiłeś na limit połączeń i chcesz zrozumieć podstawowy problem, zacznij od naszego runbooka RDS connection limit exceeded. Ta strona zakłada, że masz już wdrożone lub oceniasz RDS Proxy.

Jeśli potrzebujesz wsparcia architektonicznego w kwestii connection pooling w produkcji, umów się na konsultację.

Kiedy RDS Proxy pomaga

RDS Proxy jest przydatny w konkretnych scenariuszach:

  • Lambda → RDS - Funkcje Lambda otwierają nowe połączenie przy każdym wywołaniu. Bez poolingu burst 500 wywołań Lambda tworzy 500 połączeń do bazy jednocześnie. RDS Proxy multipleksuje je na mniejszą pulę.
  • Wiele mikroserwisów, mało zapytań z każdego - 20 serwisów utrzymujących pule po 10 połączeń = 200 połączeń. RDS Proxy może obsłużyć je wszystkie z 50 faktycznych połączeń do bazy.
  • Szybkość failoveru - RDS Proxy wykrywa failover Multi-AZ szybciej niż propagacja DNS i przekierowuje ruch w sekundy zamiast ~30 sekund.
# Sprawdź czy RDS Proxy pomoże: policz distinct client connections vs aktywne zapytania
psql -h your-rds.cluster-abc.eu-central-1.rds.amazonaws.com -U master -d mydb -c "
SELECT
  count(*) AS total_connections,
  count(*) FILTER (WHERE state = 'active') AS actually_running,
  count(*) FILTER (WHERE state = 'idle') AS idle_wasting_slots
FROM pg_stat_activity
WHERE backend_type = 'client backend';"

Jeśli idle_wasting_slots to >70% total_connections, RDS Proxy pomoże znacząco.

Kluczowe parametry konfiguracji

MaxConnectionsPercent

Maksymalny procent max_connections instancji RDS, który proxy może wykorzystać.

# Domyślnie: 100 (proxy może użyć wszystkich dostępnych połączeń)
# Rekomendowane: 80-90 (zostaw margines na bezpośredni dostęp administracyjny)

aws rds modify-db-proxy-target-group \
  --db-proxy-name my-proxy \
  --target-group-name default \
  --connection-pool-config "MaxConnectionsPercent=85"

Dlaczego nie 100%? Jeśli proxy użyje wszystkich połączeń i musisz połączyć się bezpośrednio do debugowania (przez psql jako superuser), będziesz zablokowany. Zostaw 10-15% jako margines bezpieczeństwa.

MaxIdleConnectionsPercent

Maksymalny procent połączeń z MaxConnectionsPercent, które proxy utrzymuje otwarte w stanie idle.

# Domyślnie: 50
# Dla obciążeń burstowych (Lambda): ustaw wyżej (70-80)
# Dla stabilnych obciążeń: domyślna wartość jest OK

aws rds modify-db-proxy-target-group \
  --db-proxy-name my-proxy \
  --target-group-name default \
  --connection-pool-config "MaxIdleConnectionsPercent=70"

Kompromis: Wyższe wartości = szybsza reakcja na burst (połączenia są pre-otwarte). Niższe wartości = mniej idle connections zużywających pamięć na instancji RDS.

ConnectionBorrowTimeout

Jak długo klient czeka (w sekundach) na połączenie z puli proxy zanim dostanie błąd.

# Domyślnie: 120 sekund
# Dla API z restrykcyjnymi SLA: 5-10 sekund (fail fast, zwróć 503)
# Dla batch jobs: 120+ jest OK

aws rds modify-db-proxy-target-group \
  --db-proxy-name my-proxy \
  --target-group-name default \
  --connection-pool-config "ConnectionBorrowTimeout=10"

Kluczowe: Jeśli widzisz błędy ConnectionBorrowTimeout w CloudWatch, to oznacza, że pula jest wyczerpana. To NIE jest timeout do zwiększenia - to sygnał, że:

  1. MaxConnectionsPercent jest za niski
  2. Twoje obciążenie ma zbyt wiele długo trwających zapytań blokujących połączenia
  3. Sesje są przypięte (pinned) - patrz niżej

InitQuery (inicjalizacja sesji)

SQL wykonywany przy tworzeniu nowego połączenia do bazy:

# PostgreSQL: ustaw application_name do debugowania
aws rds modify-db-proxy-target-group \
  --db-proxy-name my-proxy \
  --target-group-name default \
  --connection-pool-config "InitQuery=SET application_name='rds-proxy-pool'"

# MySQL: ustaw timezone
# InitQuery="SET time_zone='+00:00'"

Trzymaj InitQuery minimalnym. Każde polecenie tutaj wykonuje się przy każdym tworzeniu nowego połączenia.

Connection pinning: cichy zabójca

Pinning oznacza, że sesja klienta zostaje zablokowana na konkretnym połączeniu do bazy i nie może być multipleksowana. Gdy sesja jest przypięta, RDS Proxy nie może ponownie użyć tego połączenia dla innych klientów - co niweluje cały sens poolingu.

Co powoduje pinning

PrzyczynaPostgreSQLMySQLObejście
Polecenia SET (zmienne sesji)✅ Pinuje✅ PinujeUżyj InitQuery lub unikaj per-session SET
Prepared statements✅ Pinuje✅ PinujeUżyj extended query protocol lub reuse nazw prepared statements po stronie serwera
Tabele TEMPORARY✅ Pinuje✅ PinujeUżyj CTE lub tabel stałych z kolumną session_id
Zmienne użytkownika MySQL (@var)N/A✅ PinujePrzekazuj wartości przez logikę aplikacji
LOCK TABLE✅ Pinuje✅ PinujeUżyj blokad na poziomie wiersza (SELECT FOR UPDATE)
Kursory (DECLARE/FETCH)✅ Pinuje✅ PinujeUżyj LIMIT/OFFSET lub keyset pagination
LISTEN/NOTIFY✅ PinujeN/AUżyj SNS/SQS zamiast tego do powiadomień o zdarzeniach
Advisory locks✅ Pinuje✅ PinujeUżyj DynamoDB do distributed locking

Wykrywanie pinningu

# Metryka CloudWatch: DatabaseConnectionsCurrentlySessionPinned
aws cloudwatch get-metric-statistics \
  --namespace AWS/RDS \
  --metric-name DatabaseConnectionsCurrentlySessionPinned \
  --dimensions Name=ProxyName,Value=my-proxy \
  --start-time $(date -u -v-1H +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 300 \
  --statistics Average Maximum

# Jeśli Maximum jest bliskie wartości MaxConnectionsPercent * max_connections,
# pinning zużywa całą pulę

Reguła: Jeśli >30% połączeń jest przypięte w dowolnym momencie, RDS Proxy nie dostarcza znaczącej wartości dla tego obciążenia.

Znane ograniczenia

1. Brak wsparcia dla IAM database authentication ze wszystkich klientów

RDS Proxy obsługuje IAM auth, ale token musi być wygenerowany dla endpointu proxy, nie instancji RDS. Niektóre biblioteki ORM nie obsługują tego poprawnie.

2. Narzut latencji

RDS Proxy dodaje 1-3ms latencji na zapytanie. Dla zapytań o wysokiej częstotliwości i niskiej latencji (warstwy cachowania, real-time scoring) ten narzut się kumuluje:

Bezpośrednio do RDS:  query_time + 0.3ms sieć
Przez RDS Proxy:      query_time + 1.5ms proxy + 0.3ms sieć

Przy 100 zapytaniach na request to dodatkowe 120-300ms. Nie do zaakceptowania dla workloadów wrażliwych na latencję.

3. Proxy działa w wielu AZ, ale to nie zapewnia HA bazy

RDS Proxy jest wdrażane w wielu strefach dostępności, ale jeśli instancja RDS jest single-AZ i ulegnie awarii, proxy nadal musi czekać na odzyskanie. Proxy NIE zapewnia HA, jeśli baza pod spodem jest single-AZ.

4. Brak connection pool per użytkownik bazy

Pula proxy jest współdzielona między wszystkich uwierzytelnionych użytkowników. Jeśli zapytania jednego użytkownika są wolne i trzymają połączenia, głodzą wszystkich innych. Nie ma per-user quota ani fair scheduling.

5. Maksymalnie 200 proxy per konto per region

Twardy limit. Dla architektur multi-tenant z bazą-per-tenant szybko go osiągniesz.

6. Brak wsparcia dla RDS Custom ani self-managed baz danych

RDS Proxy działa tylko z RDS i Aurora. Nie z RDS Custom, nie z PostgreSQL na EC2, nie z samodzielnie zarządzanymi bazami.

7. Koszty się kumulują

RDS Proxy nalicza opłaty za godzinę vCPU powiązanej instancji RDS:

Cennik (eu-central-1): $0.015 za vCPU za godzinę
db.r6g.large (2 vCPU):  $0.015 × 2 × 730 = ~$22/miesiąc
db.r6g.4xlarge (16 vCPU): $0.015 × 16 × 730 = ~$175/miesiąc

Dla dużych instancji koszt proxy jest znaczący. Porównaj z PgBouncerem na t3.small (~$15/miesiąc) z większą kontrolą.

Kiedy NIE używać RDS Proxy

ScenariuszDlaczego RDS Proxy jest złym wyboremLepsza alternatywa
Aplikacja już używa connection pooling (HikariCP, PgBouncer)Podwójny pooling powoduje nieprzewidywalne zachowanieDostosuj istniejącą pulę; proxy jest zbędne
Intensywne użycie prepared statementsSesje pinują się natychmiast, niwelując poolingPgBouncer w trybie transaction z server-reset
LISTEN/NOTIFY do zdarzeń real-timeSesje pinują się na cały czas subskrypcjiBezpośrednie połączenie dla subskrybentów; SNS/SQS dla nowych architektur
Wymagania latencji sub-milisekundowejNarzut 1-3ms na hopBezpośrednie połączenie z application-level pooling
Długo trwające transakcje (>30 sekund)Trzyma połączenie z puli, głodzi innychDedykowana read replica lub oddzielne połączenie dla batch jobs
Multi-tenant z >200 bazamiUderza w limit proxy per kontoFlota PgBouncer lub routing na poziomie aplikacji

Konfiguracja Terraform

resource "aws_db_proxy" "app" {
  name                   = "app-proxy"
  debug_logging          = false
  engine_family          = "POSTGRESQL"
  idle_client_timeout    = 1800
  require_tls            = true
  role_arn               = aws_iam_role.proxy.arn
  vpc_security_group_ids = [aws_security_group.proxy.id]
  vpc_subnet_ids         = aws_subnet.private[*].id

  auth {
    auth_scheme = "SECRETS"
    description = "App database credentials"
    iam_auth    = "DISABLED"
    secret_arn  = aws_secretsmanager_secret.db_credentials.arn
  }
}

resource "aws_db_proxy_default_target_group" "app" {
  db_proxy_name = aws_db_proxy.app.name

  connection_pool_config {
    max_connections_percent      = 85
    max_idle_connections_percent = 70
    connection_borrow_timeout    = 10
    init_query                   = "SET application_name='proxy-pool'"
  }
}

resource "aws_db_proxy_target" "app" {
  db_proxy_name          = aws_db_proxy.app.name
  target_group_name      = aws_db_proxy_default_target_group.app.name
  db_instance_identifier = aws_db_instance.primary.identifier
}

Checklist monitoringu

Ustaw alarmy CloudWatch na tych metrykach:

# Krytyczny: wyczerpanie puli
aws cloudwatch put-metric-alarm \
  --alarm-name "RDSProxy-PoolExhausted" \
  --metric-name DatabaseConnectionsBorrowLatency \
  --namespace AWS/RDS \
  --dimensions Name=ProxyName,Value=my-proxy \
  --threshold 5000 \
  --comparison-operator GreaterThanThreshold \
  --evaluation-periods 3 \
  --period 60 \
  --statistic Average \
  --alarm-actions arn:aws:sns:eu-central-1:ACCOUNT:ops-alerts

# Ostrzeżenie: rosnący współczynnik pinningu
# Użyj custom metric z Lambdy obliczającej:
# DatabaseConnectionsCurrentlySessionPinned / DatabaseConnections * 100

Kluczowe metryki do obserwacji:

  • DatabaseConnections - łączna liczba połączeń proxy do RDS
  • DatabaseConnectionsCurrentlySessionPinned - przypięte (bezużyteczne dla poolingu)
  • ClientConnections - ile klientów jest podłączonych do proxy
  • DatabaseConnectionsBorrowLatency - jak długo klienci czekają na połączenie z puli
  • QueryRequests / QueryDatabaseResponseLatency - throughput i latencja

Rozwiązywanie ConnectionBorrowTimeout

Jeśli dostajesz błędy ConnectionBorrowTimeout:

# Krok 1: Sprawdź czy pinning jest przyczyną
# Jeśli pinned connections >50% total, napraw pinning najpierw (patrz tabela powyżej)

# Krok 2: Sprawdź czy max_connections na RDS jest wąskim gardłem
psql -h your-rds-instance -U master -c "SHOW max_connections;"
# Porównaj z: MaxConnectionsPercent / 100 * max_connections = faktyczny rozmiar puli proxy

# Krok 3: Sprawdź długo trwające zapytania trzymające połączenia
psql -h your-rds-instance -U master -c "
SELECT pid, now() - pg_stat_activity.query_start AS duration, query, state
FROM pg_stat_activity
WHERE (now() - pg_stat_activity.query_start) > interval '30 seconds'
AND state != 'idle'
ORDER BY duration DESC;"

# Krok 4: Jeśli żadne z powyższych - skaluj instancję RDS w górę (więcej max_connections)
# lub dodaj read repliki i kieruj ruch odczytu tam

Potrzebujesz pomocy z architekturą połączeń do bazy?

Strategia connection pooling zależy od wzorców aplikacji, zachowania transakcji i skali. 30-minutowa konsultacja może zaoszczędzić tygodni prób i błędów z konfiguracją proxy. Umów rozmowę lub Wyślij wiadomość.

Podsumowanie

RDS Proxy to dobre rozwiązanie dla wzorców Lambda-do-RDS i aplikacji z wieloma idle connections. To złe rozwiązanie gdy sesje używają funkcji powodujących pinning, gdy liczy się latencja lub gdy aplikacje już implementują connection pooling.

Przed wdrożeniem RDS Proxy:

  1. Potwierdź, że pinning nie będzie problemem (sprawdź prepared statements, polecenia SET, tabele tymczasowe)
  2. Ustaw MaxConnectionsPercent na 85% (zostaw margines administracyjny)
  3. Ustaw ConnectionBorrowTimeout nisko dla API (5-10s) - fail fast
  4. Monitoruj DatabaseConnectionsCurrentlySessionPinned od pierwszego dnia
  5. Porównaj koszt z samodzielnym PgBouncerem, jeśli masz duże instancje
Loading...