Budowanie tabeli MySQL o wielkości 1000 mln wierszy

18

To pytanie zostało przełożone z Przepełnienia stosu na podstawie sugestii w komentarzach, przeprosin za powielanie.

pytania

Pytanie 1: w miarę powiększania się tabeli bazy danych, jak mogę dostroić MySQL, aby zwiększyć szybkość wywołania LOAD DATA INFILE?

Pytanie 2: czy użycie klastra komputerów do załadowania różnych plików csv, poprawienie wydajności lub zabicie go? (to moje zadanie na jutro przy użyciu danych obciążenia i wkładek masowych)

Cel

Wypróbowujemy różne kombinacje detektorów funkcji i parametrów klastrowania do wyszukiwania obrazów, w wyniku czego musimy być w stanie budować duże bazy danych w odpowiednim czasie.

Informacje o maszynie

Maszyna ma 256 gigawatów pamięci RAM, a dostępne są kolejne 2 maszyny z taką samą ilością pamięci RAM, jeśli istnieje sposób na poprawę czasu tworzenia poprzez dystrybucję bazy danych?

Schemat tabeli

wygląda schemat tabeli

+---------------+------------------+------+-----+---------+----------------+
| Field         | Type             | Null | Key | Default | Extra          |
+---------------+------------------+------+-----+---------+----------------+
| match_index   | int(10) unsigned | NO   | PRI | NULL    |                |
| cluster_index | int(10) unsigned | NO   | PRI | NULL    |                |
| id            | int(11)          | NO   | PRI | NULL    | auto_increment |
| tfidf         | float            | NO   |     | 0       |                |
+---------------+------------------+------+-----+---------+----------------+

utworzony z

CREATE TABLE test 
(
  match_index INT UNSIGNED NOT NULL,
  cluster_index INT UNSIGNED NOT NULL, 
  id INT NOT NULL AUTO_INCREMENT,
  tfidf FLOAT NOT NULL DEFAULT 0,
  UNIQUE KEY (id),
  PRIMARY KEY(cluster_index,match_index,id)
)engine=innodb;

Jak dotąd porównywanie

Pierwszym krokiem było porównanie wkładek masowych z ładowaniem z pliku binarnego do pustej tabeli.

It took:  0:09:12.394571  to do  4,000  inserts with 5,000 rows per insert
It took: 0:03:11.368320 seconds to load 20,000,000 rows from a csv file

Biorąc pod uwagę różnicę w wydajności, którą poszedłem przy ładowaniu danych z binarnego pliku csv, najpierw załadowałem pliki binarne zawierające wiersze 100K, 1M, 20M, 200M, korzystając z poniższego wywołania.

LOAD DATA INFILE '/mnt/tests/data.csv' INTO TABLE test;

Zabiłem ładowanie pliku binarnego o wielkości 200M wierszy (~ 3 GB pliku csv) po 2 godzinach.

Uruchomiłem więc skrypt, aby utworzyć tabelę i wstawiłem różne liczby wierszy z pliku binarnego, a następnie upuściłem tabelę, patrz wykres poniżej.

wprowadź opis zdjęcia tutaj

Wstawienie 1M wierszy z pliku binarnego zajęło około 7 sekund. Następnie zdecydowałem się na wykonanie testu porównawczego wstawiając 1M wierszy jednocześnie, aby sprawdzić, czy wystąpi wąskie gardło przy danym rozmiarze bazy danych. Po trafieniu bazy danych do około 59 milionów wierszy średni czas wstawiania spadł do około 5000 na sekundę

wprowadź opis zdjęcia tutaj

Ustawienie globalnego key_buffer_size = 4294967296 nieznacznie poprawiło prędkości wstawiania mniejszych plików binarnych. Poniższy wykres pokazuje prędkości dla różnych liczb rzędów

wprowadź opis zdjęcia tutaj

Jednak wstawienie 1M wierszy nie poprawiło wydajności.

wiersze: 1 000 000 czas: 0: 04: 13,761428 wstawek / s: 3,940

vs dla pustej bazy danych

wiersze: 1 000 000 czas: 0: 00: 6,339295 wstawek / s: 315,492

Aktualizacja

Wykonanie ładowania danych przy użyciu następującej sekwencji vs po prostu użycie polecenia ładowania danych

SET autocommit=0;
SET foreign_key_checks=0;
SET unique_checks=0;
LOAD DATA INFILE '/mnt/imagesearch/tests/eggs.csv' INTO TABLE test_ClusterMatches;
SET foreign_key_checks=1;
SET unique_checks=1;
COMMIT;
wprowadź opis zdjęcia tutaj

Wygląda to więc dość obiecująco pod względem wielkości generowanej bazy danych, ale inne ustawienia nie wydają się wpływać na wydajność wywołania infiltracji danych ładowania.

Próbowałem następnie załadować wiele plików z różnych komputerów, ale polecenie ładowania danych infiltruje tabelę, ze względu na duży rozmiar plików powodujący przekroczenie limitu czasu przez inne maszyny

ERROR 1205 (HY000) at line 1: Lock wait timeout exceeded; try restarting transaction

Zwiększenie liczby wierszy w pliku binarnym

rows:  10,000,000  seconds rows:  0:01:36.545094  inserts/sec:  103578.541236
rows:  20,000,000  seconds rows:  0:03:14.230782  inserts/sec:  102970.29026
rows:  30,000,000  seconds rows:  0:05:07.792266  inserts/sec:  97468.3359978
rows:  40,000,000  seconds rows:  0:06:53.465898  inserts/sec:  96743.1659866
rows:  50,000,000  seconds rows:  0:08:48.721011  inserts/sec:  94567.8324859
rows:  60,000,000  seconds rows:  0:10:32.888930  inserts/sec:  94803.3646283

Rozwiązanie: Wstępnie obliczaj identyfikator poza MySQL zamiast korzystać z automatycznego przyrostu

Budowanie stołu za pomocą

CREATE TABLE test (
  match_index INT UNSIGNED NOT NULL,
  cluster_index INT UNSIGNED NOT NULL, 
  id INT NOT NULL ,
  tfidf FLOAT NOT NULL DEFAULT 0,
  PRIMARY KEY(cluster_index,match_index,id)
)engine=innodb;

z SQL

LOAD DATA INFILE '/mnt/tests/data.csv' INTO TABLE test FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n';"

wprowadź opis zdjęcia tutaj

Wydaje się, że pobranie skryptu w celu wstępnego obliczenia indeksów spowodowało zmniejszenie wydajności w miarę powiększania się bazy danych.

Aktualizacja 2 - za pomocą tabel pamięci

Około 3 razy szybciej, bez uwzględnienia kosztów przeniesienia tabeli w pamięci do tabeli opartej na dysku.

rows:  0  seconds rows:  0:00:26.661321  inserts/sec:  375075.18851
rows:  10000000  time:  0:00:32.765095  inserts/sec:  305202.83857
rows:  20000000  time:  0:00:38.937946  inserts/sec:  256818.888187
rows:  30000000  time:  0:00:35.170084  inserts/sec:  284332.559456
rows:  40000000  time:  0:00:33.371274  inserts/sec:  299658.922222
rows:  50000000  time:  0:00:39.396904  inserts/sec:  253827.051994
rows:  60000000  time:  0:00:37.719409  inserts/sec:  265115.500617
rows:  70000000  time:  0:00:32.993904  inserts/sec:  303086.291334
rows:  80000000  time:  0:00:33.818471  inserts/sec:  295696.396209
rows:  90000000  time:  0:00:33.534934  inserts/sec:  298196.501594

przez załadowanie danych do tabeli opartej na pamięci, a następnie skopiowanie jej do tabeli opartej na dysku w porcjach miało narzut 10 min 59,71 s, aby skopiować 107 356 741 wierszy za pomocą zapytania

insert into test Select * from test2;

co sprawia, że ​​ładowanie 100 milionów wierszy zajmuje około 15 minut, co jest w przybliżeniu tym samym, co bezpośrednie wstawienie go do tabeli opartej na dysku.

Ben
źródło
1
Myślę, że zmiana klucza podstawowego na po prostu idpowinna być szybsza. (Chociaż myślę, że tego nie szukasz)
DavidEG
Cześć David, dziękuję za komentarz, niestety bez klucza zapytania, które musimy wykonać, nie są wystarczająco szybkie (logika wyboru klucza podstawowego jest opisana w tym poście stackoverflow.com/questions/4282526/mysql-group-by- optymalizacja )
Ben
1
Czy to tylko do testowania? Może warto przyjrzeć się silnikowi MySQL MEMORY: dev.mysql.com/doc/refman/5.0/en/memory-storage-engine.html Jeśli planujesz wdrożyć to jako architekturę, jestem ciekawy, jak planujesz odzyskać po awarii, wydaje się, że lepiej poradziłby sobie MapReduce / Hadoop.
wielomian
Cześć wielomianu, dziękuję za podpowiedź, w tej chwili testujemy tylko różne detektory funkcji w różnych skalach, po wygenerowaniu bazy danych to się nie zmieni (w obecnej specyfikacji)
Ben

Odpowiedzi:

4

Dobre pytanie - dobrze wyjaśnione.

Jak dostroić MySQL, aby zwiększyć szybkość wywołania LOAD DATA INFILE?

Masz już wysokie (ish) ustawienie dla bufora kluczy - ale czy to wystarczy? Zakładam, że jest to instalacja 64-bitowa (jeśli nie, to pierwszą rzeczą, którą musisz zrobić, to aktualizacja) i nie działa na MSNT. Po uruchomieniu kilku testów zapoznaj się z wynikami mysqltuner.pl.

Aby najlepiej wykorzystać pamięć podręczną, możesz znaleźć korzyści z grupowania / wstępnego sortowania danych wejściowych (najnowsze wersje polecenia „sort” mają wiele funkcji do sortowania dużych zestawów danych). Również jeśli wygenerujesz numery identyfikacyjne poza MySQL, może to być bardziej wydajne.

używałby klastra komputerów do załadowania różnych plików csv

Zakładając (ponownie), że chcesz, aby zestaw danych wyjściowych zachowywał się jak pojedyncza tabela, jedyne korzyści, jakie otrzymasz, to dystrybucja pracy sortowania i generowania identyfikatorów - do których nie potrzebujesz więcej baz danych. OTOH używając klastra bazy danych, będziesz mieć problemy z rywalizacją (których nie powinieneś postrzegać inaczej niż jako problemy z wydajnością).

Jeśli możesz podzielić dane i obsługiwać wynikowe zestawy danych niezależnie, to tak, uzyskasz korzyści w zakresie wydajności - ale to nie neguje potrzeby dostrojenia każdego węzła.

Sprawdź, czy masz co najmniej 4 Gb dla sort_buffer_size.

Poza tym czynnikiem ograniczającym wydajność jest przede wszystkim dyskowe operacje we / wy. Istnieje wiele sposobów rozwiązania tego problemu - ale dla optymalnej wydajności powinieneś prawdopodobnie rozważyć zestaw lustrzanych zestawów paskowych zestawów danych na dyskach SSD.

symcbean
źródło
1
  • Rozważ swój czynnik ograniczający. To prawie na pewno jedno-wątkowe przetwarzanie procesora.
  • Już ustaliłeś, że load data...jest szybszy niż wstawianie, więc użyj tego.
  • Już ustaliłeś, że naprawdę duże pliki (według numeru wiersza) bardzo spowalniają; chcesz je rozbić na kawałki.
  • Używając nie nakładających się kluczy głównych, ustaw w kolejce co najmniej N * zestawy procesorów, używając nie więcej niż miliona wierszy ... prawdopodobnie mniej (test porównawczy).
  • Użyj sekwencyjnych bloków kluczy głównych w każdym pliku.

Jeśli chcesz być naprawdę sprytny, możesz utworzyć wielowątkowy program, który będzie podawał pojedynczy plik do zbioru nazwanych potoków i zarządzał instancjami wstawiania.

Podsumowując, nie dostrajasz do tego MySQL, tylko dostrajasz swoje obciążenie do MySQL.

Jeff Ferland
źródło
-1

Nie pamiętam dokładnie syntacx, ale jeśli nie jest to db, możesz wyłączyć sprawdzanie klucza obcego.

Możesz także utworzyć indeks po imporcie, może to być naprawdę wzrost wydajności.

Julien Duponchelle
źródło
Odroczenie przebudowy indeksu poprawia wydajność tylko wtedy, gdy liczba wierszy znajdujących się już w tabeli jest znacznie mniejsza niż liczba dodawanych wierszy.
symcbean