Azure SQL i wyczerpanie connection pool size

„Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.”

Dla bardziej doświadczonych ode mnie programistów pewnie nie ma to żadnej tajemnicy. Wiedzą co może powodować takie zachowanie, potrafią napisać kod, który będzie bezpieczny od takiego „efektu”. Z kolei administratorzy DB wiedzą bez wątpienia jak namierzyć i monitorować takie niebezpieczne sytuacje.

Ja nie jestem ani ekspertem w zakresie .NET (chociaż się staram w miarę możliwości) ani administrowania serwerami DB (j.w.) i spotkawszy się z takim uprzejmym wpisem w logu musiałem zebrać troszkę informacji żeby zaradzić coś na problem.

Jaki jest podstawowy kłopot z tym wyjątkiem? Że leci tam, gdzie kod nie umie się dostać do DB, niekoniecznie jest to miejsce, które powoduje wyczerpanie puli zasobów. Niekoniecznie + prawo Murphy’ego = to z pewnością nie jest to miejsce.
Dodajmy do tego, że do bazy dostają się różne usługi, w różnych miejscach, deployowane na różne sposoby, pisane przez różne osoby i mamy wesołą zabawę w kotka i myszkę.

Wiemy, że to wina kodu, a nie obciążenia, bo pojawiło się w mało obciążonym (jeszcze) systemie. Co może powodować taki problem? Zapewne to nie wszystkie możliwości, ale powiedzmy, że takich wytypowaliśmy podejrzanych:

  • nieobsłużony wyjątek między otwieraniem, a zamykaniem połączenia (ew. obsłużony, ale nie sprzątający po połączeniu)
  • wykonanie Response.Redirect przed zamknięciem połączenia

Nie wnikając w to jak powinien wyglądać porządny kod i czy mamy do czynienia z re-usem połączeń itp mechanizmami – najprościej jest tutaj wykorzystać mechanizm using() {}, który powinien elegancko posprzątać po połączeniu.

Można zacząć przeglądać kod, ale jak napisałem wyżej – mamy wiele usług i wiele projektów, być może sporych. Przeglądać wszystkie? Wiadomo, że należy zerknąć do tych, które łączą się z bazą. Albo ktoś będzie wiedział które to są, albo trzeba przepytać kolegów programistów. Niestety nie zawsze odpowiedź „nie” oznacza faktyczny brak łączenia się z naszą bazą. Dobrze jest się upewnić, czy przypadkiem nie jest gdzieś wykorzystywany elastic database query i zdefiniowane na Azure SQL external table. Trick polega na tym, że połączenie następuje wtedy nie na poziomie APP-DB, ale DB-DB. No i konia z rzędem temu, kto od razu zgadnie kiedy to połączenie jest nawiązywane i kiedy rozłączane, szczególnie jak wszystko jest przykryte widokami i nawet nie wiadomo kiedy sięga się po dane z innej bazy. Ja sprawdziłem i z grubsza jest tak, że nawiązanie następuje w momencie odpytywania external table o dane, a rozłączenie w momencie rozłączania połączenia między aplikacją, a bazą „pytającą”. Czyli jak ktoś niedopatrzy czegoś w swoim kodzie i będzie zostawiał po sobie wiszące połączenia do własnej bazy to rykoszetem mogą wisieć połączenia DB-DB na naszej bazie.
Do rzeczy jednak – najlepiej jest zapytać rządzącego bazą. Jest taka procedura, która na ten problem czyni magię:

sp_who2

Żeby nie powtarzać żadnych dokumentacji, to krótko – zwraca to tabelę, w której są aktualne połączenia. Oprócz statusów (naszą uwagę powinna przykuć przede wszystkim niepokojąco duża liczba wiszących statusów sleeping) są tam też dane pozwalające w miarę sprawnie namierzyć psuję.

  • login – login wykorzystany do połączenia, tu może być różnie, jak mówi niezbyt elegancka praktyka „a tu mamy takiego usera, przez którego wszyscy łączą się z bazą”, ale może dać trop albo wręcz wprost winnego
  • hostname – jeśli połączyć się przez SSMS, czy tam inny tool do zarządzania, pojawi się tam po prostu nazwa lokalna naszego komputera i to spoko, łatwo zgadnąć, że to nie to. Aplikacje uruchomione w Azure będą tam za to miały ID instancji (typu RDhexcyferki). Tadam! Bierzemy portal, lecimy po aplikacjach i z Kudu, np. z Support/Analyse/Metrics odczytujemy sobie identyfikator i porównujemy z hostname naszego winowajcy. Wada jest taka, że jeśli mamy wiele aplikacji na tym samym App Service Planie albo sporo deployment slotów (co się poniekąd sprowadza do tego samego) to nie zgadniemy, która powoduje problem. Jednak lepsze to, niż nic.Dodatkowo, ten identyfikator mamy kiedy to aplikacja się łączy. Jeśli mamy do czynienia z elastic database query, to przy zapytaniach DB-DB pojawi się tam wpis typu „DBxxx” z jakimś numerem. To oznacza połączenie z innej bazy i wtedy warto popytać tych, którym się wydaje, że się wcale nie łączą tam gdzie mamy problem.

Oczywiście znalezienie winnej usługi jest fajne, wiadomo na kogo wylać pomyje ;), ale to niestety nie załatwia problemu, nadal trzeba znaleźć, który kawałek kodu robi nam niedobrze. Tyle dobrego, że to już do pewnego stopnia nie nasz problem (no chyba, że akurat tak :P)

Czego bym się chętnie dowiedział, to czy można precyzyjniej namierzyć winowajcę takich wybryków. Tzn. konkretnie wskazać palcem np. Web App czy wręcz jakiś jego slot. Jeszcze mniej wiem o tych połączeniach cross-database. Nie wiem na chwilę obecną czy można jakoś sprawdzić ten identyfikator hostname na swoich serwerach.

Anyone?

Dodaj komentarz