Ogromne dane i wydajność w SQL Server

20

Napisałem aplikację z zapleczem SQL Server, która gromadzi i przechowuje oraz bardzo dużą liczbę rekordów. Obliczyłem, że u szczytu średnia liczba zapisów wynosi około 3-4 miliardy dziennie (20 godzin pracy).

Moje oryginalne rozwiązanie (zanim wykonałem faktyczne obliczenie danych) polegało na tym, że moja aplikacja wstawiała rekordy do tej samej tabeli, do której pytają moi klienci. Rozbił się i spłonął dość szybko, oczywiście, ponieważ nie można wykonać zapytania do tabeli, w której umieszczono tak wiele rekordów.

Moje drugie rozwiązanie polegało na wykorzystaniu 2 baz danych, jednej dla danych otrzymanych przez aplikację i jednej dla danych gotowych dla klienta.

Moja aplikacja odbierałaby dane, dzieliła je na partie ~ 100 000 rekordów i wstawiała zbiorczo do tabeli pomostowej. Po zarejestrowaniu ~ 100 000 aplikacji aplikacja w locie utworzy kolejną tabelę pomostową o takim samym schemacie jak poprzednio i rozpocznie wstawianie do tej tabeli. Utworzyłby rekord w tabeli zadań o nazwie tabeli, która ma 100 000 rekordów, a procedura przechowywana po stronie programu SQL Server przenosiłaby dane z tabel pomostowych do tabeli produkcyjnej gotowej na klienta, a następnie upuszczałaby tabela tymczasowa utworzona przez moją aplikację.

Obie bazy danych mają ten sam zestaw 5 tabel o tym samym schemacie, z wyjątkiem bazy danych pomostowej zawierającej tabelę zadań. Baza danych pomostowych nie ma ograniczeń integralności, klucza, indeksów itp. W tabeli, w której będzie przechowywana większość rekordów. Pokazana poniżej nazwa tabeli to SignalValues_staging. Celem było, aby moja aplikacja jak najszybciej zatrzasnęła dane w SQL Server. Proces tworzenia tabel w locie, aby można je było łatwo migrować, działa całkiem dobrze.

Oto 5 odpowiednich tabel z mojej bazy danych pomostowych oraz tabela moich zadań:

Tabele pomostowe Procedura składowana, którą napisałem, obsługuje przenoszenie danych ze wszystkich tabel pomostowych i wstawianie ich do produkcji. Poniżej znajduje się część mojej procedury składowanej, która wstawia do produkcji z tabel pomostowych:

-- Signalvalues jobs table.
SELECT *
      ,ROW_NUMBER() OVER (ORDER BY JobId) AS 'RowIndex'
INTO #JobsToProcess
FROM 
(
    SELECT JobId 
           ,ProcessingComplete  
           ,SignalValueStagingTableName AS 'TableName'
           ,(DATEDIFF(SECOND, (SELECT last_user_update
                              FROM sys.dm_db_index_usage_stats
                              WHERE database_id = DB_ID(DB_NAME())
                                AND OBJECT_ID = OBJECT_ID(SignalValueStagingTableName))
                     ,GETUTCDATE())) SecondsSinceLastUpdate
    FROM SignalValueJobs
) cte
WHERE cte.ProcessingComplete = 1
   OR cte.SecondsSinceLastUpdate >= 120

DECLARE @i INT = (SELECT COUNT(*) FROM #JobsToProcess)

DECLARE @jobParam UNIQUEIDENTIFIER
DECLARE @currentTable NVARCHAR(128) 
DECLARE @processingParam BIT
DECLARE @sqlStatement NVARCHAR(2048)
DECLARE @paramDefinitions NVARCHAR(500) = N'@currentJob UNIQUEIDENTIFIER, @processingComplete BIT'
DECLARE @qualifiedTableName NVARCHAR(128)

WHILE @i > 0
BEGIN

    SELECT @jobParam = JobId, @currentTable = TableName, @processingParam = ProcessingComplete
    FROM #JobsToProcess 
    WHERE RowIndex = @i 

    SET @qualifiedTableName = '[Database_Staging].[dbo].['+@currentTable+']'

    SET @sqlStatement = N'

        --Signal values staging table.
        SELECT svs.* INTO #sValues
        FROM '+ @qualifiedTableName +' svs
        INNER JOIN SignalMetaData smd
            ON smd.SignalId = svs.SignalId  


        INSERT INTO SignalValues SELECT * FROM #sValues

        SELECT DISTINCT SignalId INTO #uniqueIdentifiers FROM #sValues

        DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId

        DROP TABLE #sValues
        DROP TABLE #uniqueIdentifiers

        IF NOT EXISTS (SELECT TOP 1 1 FROM '+ @qualifiedTableName +') --table is empty
        BEGIN
            -- processing is completed so drop the table and remvoe the entry
            IF @processingComplete = 1 
            BEGIN 
                DELETE FROM SignalValueJobs WHERE JobId = @currentJob

                IF '''+@currentTable+''' <> ''SignalValues_staging''
                BEGIN
                    DROP TABLE '+ @qualifiedTableName +'
                END
            END
        END 
    '

    EXEC sp_executesql @sqlStatement, @paramDefinitions, @currentJob = @jobParam, @processingComplete = @processingParam;

    SET @i = @i - 1
END

DROP TABLE #JobsToProcess

Używam, sp_executesqlponieważ nazwy tabel tabel pomostowych pochodzą jako tekst z rekordów w tabeli zadań.

Ta procedura składowana jest uruchamiana co 2 sekundy przy użyciu sztuczki, której nauczyłem się z tego postu dba.stackexchange.com .

Problemem, którego nie mogę rozwiązać przez całe życie, jest szybkość, z jaką wkładki są produkowane. Moja aplikacja tworzy tymczasowe tabele pomostowe i bardzo szybko wypełnia je rekordami. Wkładka do produkcji nie nadąża za ilością stołów i ostatecznie istnieje nadwyżka stołów na tysiące. Jedyny sposób, jaki kiedykolwiek był w stanie nadążyć z danych przychodzących jest usunąć wszystkie klucze, indeksy, ograniczenia itp ... na produkcji SignalValuestabeli. Problem, z którym się wtedy spotykam, polega na tym, że tabela kończy się tak dużą liczbą rekordów, że zapytanie staje się niemożliwe.

Próbowałem podzielić tabelę na partycje za pomocą [Timestamp]jako kolumny partycjonowania bezskutecznie. Każda forma indeksowania spowalnia wstawki tak bardzo, że nie nadążają. Ponadto musiałbym stworzyć tysiące partycji (jedna co minutę? Godzina?) Lata wcześniej. Nie mogłem wymyślić, jak je stworzyć w locie

Próbowałem tworząc podział przez dodanie kolumny do tabeli komputerowej o nazwie TimestampMinute, której wartość była na INSERT, DATEPART(MINUTE, GETUTCDATE()). Wciąż za wolno.

Próbowałem uczynić go tabelą zoptymalizowaną pod kątem pamięci zgodnie z tym artykułem Microsoft . Może nie rozumiem, jak to zrobić, ale MOT jakoś spowolnił wkładki.

Sprawdziłem plan wykonania procedury składowanej i stwierdziłem, że (myślę?) Najbardziej intensywna operacja

SELECT svs.* INTO #sValues
FROM '+ @qualifiedTableName +' svs
INNER JOIN SignalMetaData smd
    ON smd.SignalId = svs.SignalId

Dla mnie to nie ma sensu: dodałem rejestrowanie zegara ściennego do procedury składowanej, która okazała się inna.

Jeśli chodzi o rejestrowanie czasu, ta konkretna instrukcja powyżej wykonuje się w ~ 300 ms na 100 000 rekordów.

Wyrok

INSERT INTO SignalValues SELECT * FROM #sValues

wykonuje się w 2500-3000 ms na 100 000 rekordów. Usuwanie z tabeli dotkniętych rekordów według:

DELETE c FROM '+ @qualifiedTableName +' c INNER JOIN #uniqueIdentifiers u ON c.SignalId = u.SignalId

zajmuje kolejne 300ms.

Jak mogę to zrobić szybciej? Czy SQL Server może poradzić sobie z miliardami rekordów dziennie?

Jeśli jest to istotne, jest to SQL Server 2014 Enterprise x64.

Konfiguracja sprzętu:

Zapomniałem dołączyć sprzęt w pierwszym etapie tego pytania. Mój błąd.

Przedmówię to następującymi stwierdzeniami: Wiem, że tracę wydajność z powodu konfiguracji sprzętowej. Próbowałem wiele razy, ale ze względu na budżet, poziom C, wyrównanie planet itp. Nic nie mogę zrobić, aby uzyskać lepszą konfigurację. Serwer działa na maszynie wirtualnej i nie mogę nawet zwiększyć pamięci, ponieważ po prostu nie mamy już więcej.

Oto informacje o moim systemie:

Informacja o systemie

Pamięć jest podłączona do serwera VM przez interfejs iSCSI do urządzenia NAS (spowoduje to obniżenie wydajności). Serwer NAS ma 4 dyski w konfiguracji RAID 10. Są to dyski wirujące WD WD4000FYYZ o pojemności 4 TB z interfejsem SATA 6 GB / s. Serwer ma skonfigurowany tylko jeden magazyn danych, więc tempdb i moja baza danych znajdują się w tym samym magazynie danych.

Maksymalna wartość DOP wynosi zero. Czy powinienem zmienić to na stałą wartość, czy po prostu pozwolić SQL Serverowi na to? Czytam na RCSI: Czy mam rację zakładając, że jedyną korzyścią z RCSI są aktualizacje wierszy? Nigdy nie będzie żadnych aktualizacji tych konkretnych zapisów, będą one INSERTedytowane i SELECTedytowane. Czy RCSI nadal na mnie skorzysta?

Moja tempdb wynosi 8 MB. Na podstawie poniższej odpowiedzi z jyao zmieniłem #sValues ​​na zwykłą tabelę, aby całkowicie uniknąć tempdb. Wydajność była jednak prawie taka sama. Spróbuję zwiększyć rozmiar i wzrost tempdb, ale biorąc pod uwagę, że rozmiar #sValues ​​będzie mniej więcej zawsze taki sam, nie spodziewam się dużego wzrostu.

Podjąłem plan wykonania, który załączyłem poniżej. Ten plan wykonania jest jedną iteracją tabeli pomostowej - 100 000 rekordów. Wykonanie zapytania było dość szybkie, około 2 sekund, ale należy pamiętać, że jest to bez indeksów w SignalValuestabeli, a SignalValuestabela, będąca celem INSERT, nie ma w nim żadnych rekordów.

Plan wykonania

Brandon
źródło
3
Czy eksperymentowałeś już z opóźnioną wytrzymałością?
Martin Smith,
2
Jakie indeksy obowiązywały w przypadku płytek o powolnej produkcji?
paparazzo
Jak dotąd nie sądzę, aby było tu wystarczająco dużo danych, aby dowiedzieć się, co tak naprawdę zajmuje tak dużo czasu. Czy to procesor? Czy to IO? Ponieważ wydajesz się otrzymywać 30 000 wierszy na sekundę, dla mnie nie wygląda to na IO. Czy rozumiem to prawo, że jesteś bliski osiągnięcia swojego celu? Potrzebujesz 50 000 wierszy na sekundę, więc jedna partia 100 000 co 2 sekundy powinna wystarczyć. Obecnie wydaje się, że jedna partia zajmuje 3 sekundy. Zamieść aktualny plan wykonania jednego reprezentatywnego uruchomienia. Wszelkie sugestie, które nie atakują najbardziej czasochłonnych operacji, są dyskusyjne.
usr
Opublikowałem plan wykonania.
Brandon,

Odpowiedzi:

7

Obliczyłem, że u szczytu średnia liczba zapisów wynosi około 3-4 miliardy dziennie (20 godzin pracy).

Z Twojego zrzutu ekranu masz TYLKO 8 GB całkowitej pamięci RAM i 6 GB przydzielonych do SQL Server. Jest to zbyt niski poziom dla tego, co próbujesz osiągnąć.

Sugeruję, abyś zaktualizował pamięć do wyższej wartości - 256 GB i podniósł również procesory VM.

Na tym etapie musisz zainwestować w sprzęt.

Zapoznaj się także z przewodnikiem wydajności ładowania danych - opisuje inteligentne sposoby efektywnego ładowania danych.

Moja tempdb wynosi 8 MB.

W oparciu o edycję .. powinieneś mieć rozsądną tempdb - najlepiej wiele plików danych tempdb o jednakowych rozmiarach wraz z szerokimi instancjami włączonymi TF 1117 i 1118.

Sugerowałbym, aby uzyskać profesjonalną kontrolę stanu zdrowia i zacząć od tego miejsca.

Wysoce zalecane

  1. Zwiększ specyfikację swojego serwera.

  2. Poproś profesjonalną * osobę o sprawdzenie poprawności instancji serwera bazy danych i postępuj zgodnie z zaleceniami.

  3. Raz a. oraz b. są gotowe, a następnie zanurz się w dostrajaniu zapytań i innych optymalizacjach, takich jak przeglądanie statystyk oczekiwania, planów zapytań itp.

Uwaga: Jestem profesjonalnym ekspertem od serwerów SQL w hackhands.com - firmie pluralsight, ale w żadnym wypadku nie sugeruję, abyś zatrudnił mnie do pomocy. Sugeruję jedynie, abyś wziął profesjonalną pomoc wyłącznie na podstawie swoich zmian.

HTH.

Kin Shah
źródło
Staram się przygotować do tego propozycję (czytaj: błaganie) o więcej sprzętu. Mając to na uwadze i twoją odpowiedź tutaj, z punktu widzenia konfiguracji SQL Server lub optymalizacji zapytań nie ma nic innego, co sugerowałoby, aby przyspieszyć?
Brandon,
1

Ogólne porady dotyczące takich problemów z dużymi danymi, gdy patrzy się na ścianę i nic nie działa:

Jedno jajko zostanie ugotowane około 5 minut. 10 jajek zostanie ugotowanych w tym samym czasie, jeśli wystarczy prądu i wody.

Lub innymi słowy:

Najpierw spójrz na sprzęt; po drugie, oddziel logikę procesu (przebudowę danych) i zrób to równolegle.

Możliwe jest dynamiczne i automatyczne tworzenie niestandardowych partycjonowania pionowego według liczby tabel i rozmiarów tabeli; Jeśli mam Quarter_1_2017, Quarter_2_2017, Quarter_3_2017, Quarter_4_2017, Quarter_1_2018 ... i nie wiem, gdzie są moje rekordy i ile mam partycji, uruchom te same zapytania dla wszystkich niestandardowych partycji w tym samym czasie, osobne sesje i montaż wynik do przetworzenia dla mojej logiki.

Goran Mancevski
źródło
Wydaje się, że problemem PO jest obsługa wstawiania i dostępu do nowo wprowadzonych danych, więcej niż przetwarzanie danych sprzed tygodni lub miesięcy. OP wspomina o dzieleniu danych według minuty na swoim znaczniku czasu (czyli 60 partycji, dzielenie bieżących danych na osobne segmenty); dzielenie na ćwierć raczej nie byłoby zbyt pomocne. Twoje zdanie jest ogólnie dobrze przyjęte, ale jest mało prawdopodobne, aby pomóc komuś w tej konkretnej sytuacji.
RDFozz
-1

Zrobię następujące sprawdzenie / optymalizację:

  1. Upewnij się, że zarówno plik danych, jak i dzienniki produkcyjnej bazy danych nie rosną podczas operacji wstawiania (w razie potrzeby wstępnie ją powiększ)

  2. Nie używaj

    select * into [dest table] from [source table];

    ale zamiast tego wstępnie zdefiniuj [tablicę docelową]. Również zamiast upuszczania [dest tabeli] i odtworzenia go, obetnę tabelę. W ten sposób w razie potrzeby zamiast tabeli tymczasowej użyłbym zwykłej tabeli. (Mogę również utworzyć indeks w [docelowej tabeli], aby ułatwić wykonanie zapytania o przyłączenie)

  3. Zamiast używać dynamicznego sql, wolałbym używać zakodowanych nazw tabel z pewną logiką kodowania, aby wybrać, który stół ma działać.

  4. Będę również monitorować wydajność pamięci / procesora i operacji we / wy dysku, aby sprawdzić, czy podczas dużego obciążenia nie występują niedobory zasobów.

  5. Ponieważ wspomniałeś, że możesz obsłużyć wstawianie, upuszczając indeksy po stronie produkcyjnej, sprawdziłbym, czy występuje wiele podziałów stron, jeśli tak, zmniejszę współczynnik wypełnienia indeksów i odbuduję indeksy, zanim zdecyduję się upuścić indeksy.

Powodzenia i uwielbiam twoje pytanie.

jyao
źródło
Dziękuję za odpowiedź. Ustawiłem rozmiar bazy danych na 1 GB i powiększałem o 1 GB, przewidując, że operacje wzrostu zajmą trochę czasu, co początkowo pomogło w szybkości. Spróbuję dziś wdrożyć wstępny wzrost. Zaimplementowałem tabelę [dest] jako zwykłą tabelę, ale nie zauważyłem większego wzrostu wydajności. Przez ostatnie kilka dni nie miałem zbyt wiele czasu, ale spróbuję dziś dotrzeć do pozostałych.
Brandon