Optymalizacja odpowiedzi backend: kolejki, async, backpressure
1) Dlaczego: Cele i SLO
Celem jest stabilna szybka reakcja nawet pod wybuchami. Firma wyraża to SLO:- API (CRUD/directories): p95 ≤ 250-400 ms, wskaźnik błędu <1%.
- Płatność/rozliczenie (asynchroniczne): wewnętrzny SLA do potwierdzenia ≤ 2-5 minut, a klient - natychmiastowy 202/Accepted + status poller/webhook.
- WS/w czasie rzeczywistym: RTT p95 ≤ 120 си, odłączyć ≤ 0. 5%.
Klucz: rozwiązywanie „wolnych” kroków (dostawców, baz danych, zewnętrznych interfejsów API) od odpowiedzi użytkownika poprzez kolejki i kompetentne ograniczenie obciążenia.
2) Podstawowe zdjęcie: gdzie jest wykonywane opóźnienie
Wąskie gardła: baza danych (puli/indeksy), dostawcy zewnętrzni (PSP/gra), blokowanie I/O, GC/stop world, serializacja JSON, „ciężkie” agregacje.
Objawy: wzrost p99, kolejka połączeń DB, pęknięcia retray, burza powtórna.
Antidotum: rurociągi asynchroniczne + ciśnienie wsteczne + timeouts/retreats + idempotencja.
3) Asynchroniczne wzory: SEDA i CQRS
SEDA (architektura nastawiona na zdarzenia): podzielić przetwarzanie na etapy (ingress → validation → write → integration → notification). Każdy z nich ma swoje własne granice skrętu i współistnienia.
CQRS: Osobno czyta i pisze. Pisanie - do dziennika/bazy danych, czytanie - z rzutów/buforów.
Outbox: wydarzenie jest publikowane atomowo wraz z rekordem (unikać „utraconych” wiadomości).
Saga: długie procesy biznesowe z kompensacją transakcji zamiast globalnych.
4) Kolejki i strumienie: wybór i dostrajanie
RabbitMQ/NATS JetStream - polecenia zadań (kolejki robocze), Kafka - wydarzenia/strumienie z powtórką.
Ustawienia wpływające na odpowiedź:- Prefetch/max in-flight: ograniczyć liczbę jednocześnie przetwarzanych wiadomości na pracownika (na przykład 16-64), aby nie „zatkać” bazy danych/zewnętrznego interfejsu API.
- Akier/powtórzenia: 'ack' po nagraniu idempotentnym; wykładnicze opóźnienie i jitter powtarza.
- DLQ/parking: nie ma niekończących się rekolekcji - po próbach N trafia do kolejki Dead Letter.
- Partycjonowanie (Kafka): klucz esencjonalny (Id/txnId) do zamawiania; paralelizm przez liczbę partii.
5) Ciśnienie wsteczne - jak nie utopić
Pomysł: Weź tylko tyle, ile można przetworzyć w ramach opóźnienia SLO.
Technicy:- Kontrola wstęp: ograniczyć konkurencję (semahore/worker-pool) dla każdej zewnętrznej zależności: baza danych, PSP, dostawca gier.
- Kształtowanie ruchu: token-wiadro/wyciek-wiadro przy wejściu serwisowym i na trasach krytycznych.
- Kolejki z górną granicą: po pełnym, odciąć ogon (429/503 + Retry-After) lub przenieść do asap-batch.
- Współistnienie adaptacyjne (AIMD): zwiększenie paralelizmu w odniesieniu do sukcesu, zmniejszenie czasu.
- Wyłącznik: 'closed → open → half-open' przez błędy/timeouts zewnętrznego interfejsu API; po otwarciu - degradacja (cache/stub).
go sem: = make (chan struct {}, 64 )//limit konkurencji do DB/PSP
uchwyt func (req) {
wybierz {
przypadek sem <- struct {} {}:
defer func () {<-sem} ()
ctx, anuluj: = kontekst. WithTimeout (req. ctx, 300time. Milisekunda)
defer anuluj ()
res, err: = db. Do (ctx, req)
jeśli błąd = = kontekst. przekroczył {metryki. Timeouts. Inc (); powrót Slow ()}
zwrot Ok (res)
domyślnie:
metryki. Ciśnienie wsteczne. Inc ()
powrót Busy (429 ", Retry-After: 0. 2")
}
}
6) Timeouts, rekolekcje i jitter: „trzy wieloryby przetrwania”
Timeouts krótsze niż SLO: jeśli SLO 400 ms, czas do DB/dostawca 250-300 ms; całkowity termin składania wniosków <400-600 ms.
Retrai ograniczone i inteligentne: 1-2 max próby, tylko dla bezpiecznych operacji (idempotent), z wykładnikiem i jitter.
Koalesing: Zagregowane powtórzenia dla jednego klucza.
Pseudokoda (wykładnik + jitter):python do próby w zakresie (0, 2):
spróbuj:
połączenie zwrotne (dep, timeout = 0. 3)
z wyjątkiem Timeout:
backoff = (0. 05 (2attempt)) + losowo. jednolity (0, 0. 05)
sen (backoff)
podnieść w górę StreamNiedostępne
7) Tożsamość i deduplikowanie
Idempotence-Key na HTTP (depozyty, płatności), 'operation _ id' w bazie danych (niepowtarzalny indeks).
Skrzynka odbiorcza/skrzynka odbiorcza: przychodzące haki internetowe - zawsze za pomocą niezmiennej skrzynki odbiorczej z dedupe przez 'event _ id'; outbound - z pola wyboru według transakcji.
Dokładnie raz „w znaczeniu”: pozwalamy na wielokrotną dostawę/wykonanie, ale istnieje tylko jeden efekt.
8) Szybkie API dla wolnych operacji
Odpowiedź synchroniczna: 201/202 + URL stanu ('/status/{ id} '), ETA i wskazówki retro.
Webhooks/Server-Sent Events/WS - push state when ready.
Dyscyplina klienta: „Retry-After”, idempotencja, limit sondażowy.
Przykład odpowiedzi:json
HTTP/1. 1 202 Przyjęte
Lokalizacja :/ v1/withdrawals/req_9f2/status
Retry-After: 2
{
„request_id": „req_9f2,” „stan”: „przetwarzanie”, „next_check_sec": 2
}
9) Zminimalizuj gorącą pracę
Umieść ciężkie rzeczy w tle: transformacje, agregacje, powiadomienia, pisanie do DWH.
Pamięć podręczna i projekcje: powszechnie czytane - cache-off z krótkim TTL i niepełnosprawność imprez.
Wzory partii: zewnętrzne połączenia grupowe (np. limit dostawcy żądania raz w N ms).
Serializacja: szybkie kodeki (protobuf/msgpack) do komunikacji serwisowej; JSON tylko na krawędzi.
10) DB pod kontrolą
Puli połączeń: górne granice (na podstawie rdzeni/IO), kolejki do puli włączone.
Indeksy i plan: p95 wyjaśnić + regresji autotest planów.
Terminy żądania: krótkie, 'statement _ timeout' (Postgres).
Gorące rzędy/zamki: klucze, blokady optymistyczne (wersja bilansu), saga zamiast „monolitycznej” transakcji.
11) WebSocket/w czasie rzeczywistym
Ogranicznik newslettera: transmisja zbiorcza, max msgs/sec na połączenie.
Wewnętrzne obciążenie zwrotne: kolejka komunikatów wychodzących z nasadką; w sprawie przelewu - spadek o niskim priorytecie.
Sticky-routing i PDB podczas zwolnień - tak, aby nie produkować ponownego połączenia burzy.
12) Obserwowalność, aby nie odgadnąć
Mierniki (RED/USE + backpressure):- 'quest _ rate', 'error _ ratio', 'latency _ p95/p99-' na trasach.
- 'queue _ depth', 'lag _ seconds', 'consumer _ inflight', 'retries _ total', 'dlq _ rate'.
- 'backpressure _ drops',' admission _ rejects ',' circuit _ open '.
- МлКА: 'connections _ in _ use/max', 'locks', 'slow _ queries'.
- Traces: spans 'queue → worker → db/psp' with tags 'operation _ id',' partition ',' retry '.
- Kłody: strukturalne, z "trace _ id', bez PII; indywidualne wydarzenia „otwarte/zamknięte”.
13) Badanie obciążenia
Model otwarty (przyloty/sek) do wybuchów; Model zamknięty (VU) na sesje.
Profile: krótkie pęknięcie 60-120 s i moczenie 1-4 h.
Awarie wtrysku: spowolnić zewnętrzny API o + 200-500 ms, spójrz na p99/retrai/kolejki.
Kryteria strefy zielonej: brak wzrostu „queue _ lag”, stabilny p95, „dlq _ rate”.
14) Bezpieczeństwo i niezawodność
Kolejki TLS/mTLS, podpisywanie wiadomości, monitorowanie schematu (rejestr Avro/Protobuf + Schema).
Idempotent producent (Kafka), dokładnie raz tx, gdzie uzasadnione.
Tryb chaosu: okresowo „rzucić” uzależnienie i spojrzeć na degradację (obwód, upadek).
15) Przykłady „elementów” konfiguracji
Kształtowanie wejściowe Nginx/Envoy:nginx limit_req_zone $ binary _ remote _ addr zone = api: 10m rate = 20r/s;
serwer {
lokalizacja/api/{
strefa limit_req = pęknięcie api = 40 węzłów;
proxy_read_timeout 0. 6s; # jest krótszy niż SLO proxy_connect_timeout 0. 2s;
}
}
RabbitMQ (prefetch):
podstawowe. qos (prefetch_count = 32) # saldo procesora/IO
konsument Kafka (fragment Java):
rekwizyty java. put (McConfig. MAX_POLL_RECORDS_CONFIG, 200);
rekwizyty. put (McConfig. FETCH_MAX_BYTES_CONFIG, 5_000_000);
rekwizyty. put (McConfig. MAX_POLL_INTERVAL_MS_CONFIG, 60_000);
16) Lista kontrolna wdrażania (prod-ready)
- Ścieżki krytyczne dzielą się na reakcje synchroniczne i asynchroniczne (SEDA).
- Kontrola wjazdu i ograniczenia konkurencji w odniesieniu do zależności zewnętrznych.
- Terminy są krótsze niż SLO; retrai ≤ 2, z wykładnikiem i jitterem; koalicja.
- Wyłącznik + degradacja (cache/stub), polityka w połowie otwarta.
- Kolejki/strumienie: prefetch/in-flight, DLQ, partie kluczy.
- Idempotency (operation_id/Idempotency-Key), Outbox/Inbox, deduplication.
- Cache: cache-off, krótki TTL + niepełnosprawność imprez.
- DB: limity puli, statement_timeout, indeksy, strategie przeciwdziałania blokadzie.
- WS: limity wiadomości, butching, sticky-routing, PDB.
- Obserwowalność: podciśnienie/kolejki/mierniki wsteczne, szlaki końcowe, deski rozdzielcze.
- Badania obciążenia i awarii (otwarte + zamknięte, rozerwanie + moczenie), kryteria zielonej strefy.
Wznów streszczenie
Szybkie oparcie to nie „zrób kolejny cache”, ale kontrolowany strumień: wejście jest ograniczone, ciężkie - w tle, każdy etap z kolejką i limitami, retrasy są rzadkie i inteligentne, a łańcuchy są chronione przez wyłącznik i idempotencję. Dodaj dyscyplinę czasową, obserwowalność i regularne testy warunków skrajnych - a Twój p95/p99 pozostanie zielony nawet pod wybuchami i kaprysami zewnętrznych dostawców.