Kontynuując moją serię artykułów na temat zatrzasków, tym razem omówię zatrzask APPEND_ONLY_STORAGE_INSERT_POINT i pokażę, jak może to być główne wąskie gardło w przypadku dużych obciążeń aktualizacyjnych, w których używana jest dowolna forma izolacji migawki.
Gorąco polecam przeczytanie pierwszego posta z serii przed tym, aby mieć ogólną wiedzę na temat zatrzasków.
Co to jest zatrzask APPEND_ONLY_STORAGE_INSERT_POINT?
Aby wyjaśnić ten zatrzask, muszę trochę wyjaśnić, jak działa izolacja migawek.
Po włączeniu jednej z dwóch form wersjonowania SQL Server używa mechanizmu o nazwie wersjonowanie aby zachować przed zmianą wersje rekordu w sklepie wersji w tempdb. Odbywa się to w następujący sposób:
- Rekord został zidentyfikowany jako mający zostać zmieniony.
- Bieżący rekord jest kopiowany do magazynu wersji.
- Rekord został zmieniony.
- Jeśli rekord nie miał jeszcze 14-bajtowego tagu wersjonowania , jeden jest dodawany na końcu rekordu. Znacznik zawiera znacznik czasu (nie w czasie rzeczywistym) i wskaźnik do poprzedniej wersji rekordu w magazynie wersji.
- Jeśli rekord miał już znacznik wersji, jest aktualizowany o nowy znacznik czasu i wskaźnik magazynu wersji.
Sygnatura czasowa przechowywania wersji dla całego wystąpienia jest zwiększana za każdym razem, gdy rozpoczyna się nowa instrukcja lub partia albo tworzona jest nowa wersja rekordu w dowolnej bazie danych, w której włączono dowolną formę izolacji migawki. Ten znacznik czasu służy do upewnienia się, że zapytanie przetwarza poprawną wersję rekordu.
Na przykład wyobraźmy sobie bazę danych, która odczytała zadeklarowaną migawkę włączoną, więc każda instrukcja ma gwarancję, że zobaczy rekordy w momencie uruchomienia instrukcji. Znacznik czasu wersjonowania jest ustawiany na moment uruchomienia instrukcji, więc każdy napotkany rekord, który ma wyższy znacznik czasu, jest „niewłaściwą” wersją, a zatem „właściwa” wersja ze znacznikiem czasu przed znacznikiem czasu instrukcji musi zostać pobrana z magazyn wersji. Mechanika tego procesu nie ma znaczenia dla celów tego posta.
Jak więc wersje są fizycznie przechowywane w magazynie wersji? Cały rekord sprzed zmiany, w tym kolumny poza wierszem, jest kopiowany do magazynu wersji, podzielony na fragmenty 8000 bajtów, które w razie potrzeby mogą obejmować dwie strony (np. 2000 bajtów na końcu jednej strony i 6000 bajtów na początek następnego). Ta pamięć o specjalnym przeznaczeniu składa się z jednostek alokacji tylko do dołączania i jest używany tylko do operacji przechowywania wersji. Nazywa się to tak, ponieważ nowe dane można dołączyć tylko natychmiast po zakończeniu ostatnio wprowadzonej wersji. Co jakiś czas tworzona jest nowa jednostka alokacji, dzięki czemu regularne czyszczenie magazynu wersji jest bardzo wydajne — ponieważ niepotrzebną jednostkę alokacji można po prostu usunąć. Ponownie, mechanika tego wykracza poza zakres tego posta.
A teraz dochodzimy do definicji zatrzasku:każdy wątek, który musi skopiować rekord sprzed zmiany do magazynu wersji, musi wiedzieć, gdzie znajduje się punkt wstawiania w bieżącej jednostce alokacji tylko do dołączania. Te informacje są chronione przez zatrzask APPEND_ONLY_STORAGE_INSERT_POINT.
W jaki sposób zatrzask staje się wąskim gardłem?
Oto problem:jest tylko jeden akceptowalny tryb, w którym można uzyskać zatrzask APPEND_ONLY_STORAGE_INSERT_POINT:tryb EX (ekskluzywny). A jak wiesz, czytając wpis wprowadzający do serii, tylko jeden wątek na raz może trzymać zatrzask w trybie EX.
Zebranie wszystkich tych informacji:gdy jedna lub więcej baz danych ma włączoną izolację migawek, a jednocześnie występuje wystarczająco duże obciążenie aktualizacją tych baz danych, wiele wersji będzie generowanych przez różne połączenia, a ten zatrzask stanie się trochę wąskiego gardła, przy czym rozmiar wąskiego gardła wzrasta wraz ze wzrostem obciążenia aktualizacji w przypadku wersjonowania.
Pokazuje wąskie gardło
Możesz łatwo odtworzyć wąskie gardło dla siebie. Zrobiłem to w następujący sposób:
- Utworzono tabelę z kilkoma kolumnami liczb całkowitych o nazwie cXXX, gdzie XXX to liczba i indeks klastrowy w kolumnie tożsamości int o nazwie DocID
- Wstawiono 100 000 rekordów z losowymi wartościami dla wszystkich kolumn
- Utworzono skrypt z nieskończoną pętlą, aby wybrać losowy identyfikator DocID z zakresu od 1 do 10 000, wybrać losową nazwę kolumny i zwiększyć wartość kolumny o 1 (stąd tworzenie wersji)
- Utworzono dziewięć identycznych skryptów, z których każdy wybiera inny zakres kluczy klastra o wartości 10 000
- Ustaw DELAYED_DURABILITY na WYMUSZONE, aby skrócić czas oczekiwania na WRITELOG (co prawda rzadko to robisz, ale pomaga to zaostrzyć wąskie gardło w celach demonstracyjnych)
Następnie uruchomiłem wszystkie dziesięć skryptów jednocześnie i zmierzyłem licznik Access Methods:Index Searches/s, aby śledzić liczbę aktualizacji. Nie mogłem użyć Bazy danych:Żądania wsadowe/s, ponieważ każdy skrypt miał tylko jedną partię (nieskończoną pętlę) i nie chciałem używać Transakcji/s, ponieważ mógłby zliczać transakcje wewnętrzne, a także ten, który otacza każdą aktualizację.
Kiedy izolacja migawek nie była włączona, na moim laptopie z systemem Windows 10 i SQL Server 2019 otrzymywałem około 80 000 aktualizacji na sekundę w dziesięciu połączeniach. Następnie, gdy ustawiłem READ_COMMMITED_SNAPSHOT na ON dla bazy danych i ponownie uruchomiłem test, przepustowość obciążenia spadła do około 60 000 aktualizacji na sekundę (spadek przepustowości o 25%). Patrząc na statystyki oczekiwania, 85% wszystkich czekań dotyczyło LATCH_EX, a patrząc na statystyki zatrzasków, 100% dotyczyło APPEND_ONLY_STORAGE_INSERT_POINT.
Pamiętaj, że stworzyłem scenariusz, aby pokazać wąskie gardło w najgorszym. W rzeczywistym środowisku z mieszanym obciążeniem, ogólnie przyjęte wytyczne dotyczące spadku przepustowości podczas korzystania z izolacji migawki to 10-15%.
Podsumowanie
Innym potencjalnym obszarem, na który może mieć wpływ to wąskie gardło, są wtórne odczytywane przez grupę dostępności. Jeśli replika bazy danych jest ustawiona jako czytelna, wszystkie zapytania względem niej automatycznie wykorzystują izolację migawki, a wszystkie powtórzenia rekordów dziennika z bazy podstawowej będą generować wersje. Przy wystarczająco wysokim obciążeniu aktualizacją pochodzącym z podstawowej i wielu baz danych ustawionych jako czytelne, a równoległe ponawianie jest normą dla pomocniczych grup dostępności, zatrzask APPEND_ONLY_STORAGE_INSERT_POINT może stać się wąskim gardłem również na drugorzędnej grupie dostępności, co może prowadzić do wtórne pozostające w tyle za podstawowym. Nie testowałem tego, ale jest to dokładnie ten sam mechanizm, który opisałem powyżej, więc wydaje się to prawdopodobne. W takim przypadku możliwe jest wyłączenie równoległego ponownego wykonywania przy użyciu flagi śledzenia 3459, ale może to prowadzić do gorszej ogólnej przepustowości na serwerze pomocniczym.
Odkładając na bok scenariusz grupy dostępności, niestety, nieużywanie izolacji migawki jest jedynym sposobem na całkowite uniknięcie tego wąskiego gardła, co nie jest realną opcją, jeśli obciążenie opiera się na semantyce zapewnianej przez izolację migawki lub jest ona potrzebna do złagodzenia problemów z blokowaniem (ponieważ izolacja migawki oznacza, że zapytania odczytu nie uzyskują blokad udziałów, które blokują zapytania o zmianę).
Edycja:z poniższych komentarzy *możesz* usunąć wąskie gardło zatrzasku, używając ADR w SQL Server 2019, ale wtedy wydajność jest znacznie gorsza z powodu obciążenia ADR. Scenariusz, w którym zatrzask staje się wąskim gardłem z powodu dużego obciążenia aktualizacją, absolutnie nie jest prawidłowym przypadkiem użycia dla ADR.