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.
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:
MaxConnectionsPercentjest za niski- Twoje obciążenie ma zbyt wiele długo trwających zapytań blokujących połączenia
- 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
| Przyczyna | PostgreSQL | MySQL | Obejście |
|---|---|---|---|
| Polecenia SET (zmienne sesji) | ✅ Pinuje | ✅ Pinuje | Użyj InitQuery lub unikaj per-session SET |
| Prepared statements | ✅ Pinuje | ✅ Pinuje | Użyj extended query protocol lub reuse nazw prepared statements po stronie serwera |
| Tabele TEMPORARY | ✅ Pinuje | ✅ Pinuje | Użyj CTE lub tabel stałych z kolumną session_id |
| Zmienne użytkownika MySQL (@var) | N/A | ✅ Pinuje | Przekazuj wartości przez logikę aplikacji |
| LOCK TABLE | ✅ Pinuje | ✅ Pinuje | Użyj blokad na poziomie wiersza (SELECT FOR UPDATE) |
| Kursory (DECLARE/FETCH) | ✅ Pinuje | ✅ Pinuje | Użyj LIMIT/OFFSET lub keyset pagination |
| LISTEN/NOTIFY | ✅ Pinuje | N/A | Użyj SNS/SQS zamiast tego do powiadomień o zdarzeniach |
| Advisory locks | ✅ Pinuje | ✅ Pinuje | Uż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
| Scenariusz | Dlaczego RDS Proxy jest złym wyborem | Lepsza alternatywa |
|---|---|---|
| Aplikacja już używa connection pooling (HikariCP, PgBouncer) | Podwójny pooling powoduje nieprzewidywalne zachowanie | Dostosuj istniejącą pulę; proxy jest zbędne |
| Intensywne użycie prepared statements | Sesje pinują się natychmiast, niwelując pooling | PgBouncer w trybie transaction z server-reset |
| LISTEN/NOTIFY do zdarzeń real-time | Sesje pinują się na cały czas subskrypcji | Bezpośrednie połączenie dla subskrybentów; SNS/SQS dla nowych architektur |
| Wymagania latencji sub-milisekundowej | Narzut 1-3ms na hop | Bezpośrednie połączenie z application-level pooling |
| Długo trwające transakcje (>30 sekund) | Trzyma połączenie z puli, głodzi innych | Dedykowana read replica lub oddzielne połączenie dla batch jobs |
| Multi-tenant z >200 bazami | Uderza w limit proxy per konto | Flota 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 RDSDatabaseConnectionsCurrentlySessionPinned- przypięte (bezużyteczne dla poolingu)ClientConnections- ile klientów jest podłączonych do proxyDatabaseConnectionsBorrowLatency- jak długo klienci czekają na połączenie z puliQueryRequests/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:
- Potwierdź, że pinning nie będzie problemem (sprawdź prepared statements, polecenia SET, tabele tymczasowe)
- Ustaw
MaxConnectionsPercentna 85% (zostaw margines administracyjny) - Ustaw
ConnectionBorrowTimeoutnisko dla API (5-10s) - fail fast - Monitoruj
DatabaseConnectionsCurrentlySessionPinnedod pierwszego dnia - Porównaj koszt z samodzielnym PgBouncerem, jeśli masz duże instancje