Wydajność importu InnoDB

10

Zmagam się z hurtowym importowaniem dość dużego stołu InnoDB składającego się z około 10 milionów wierszy (lub 7 GB) (który jest dla mnie największym stołem, z jakim pracowałem do tej pory).

Przeprowadziłem badania, jak poprawić szybkość importowania Inno i na razie moja konfiguracja wygląda następująco:

/etc/mysql/my.cnf/
[...]
innodb_buffer_pool_size = 7446915072 # ~90% of memory
innodb_read_io_threads = 64
innodb_write_io_threads = 64
innodb_io_capacity = 5000
innodb_thread_concurrency=0
innodb_doublewrite = 0
innodb_log_file_size = 1G
log-bin = ""
innodb_autoinc_lock_mode = 2
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit=2
innodb_buffer_pool_instances=8


import is done via bash script, here is the mysql code:
SET GLOBAL sync_binlog = 1;
SET sql_log_bin = 0;
SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET AUTOCOMMIT = 0;
SET SESSION tx_isolation='READ-UNCOMMITTED';
LOAD DATA LOCAL INFILE '$filepath' INTO TABLE monster
COMMIT;

Dane są dostarczane w CSVpliku.
Obecnie testuję moje ustawienia za pomocą mniejszych „zrzutów testowych” z 2 milionami, 3 milionami… rzędów każdy i używam time import_script.shdo porównania wydajności.

Wadą jest to, że otrzymuję tylko ogólny czas działania, więc muszę czekać na zakończenie pełnego importu, aby uzyskać wynik.

Moje dotychczasowe wyniki:

  • 10 000 rzędów: <1 sekunda
  • 100 000 rzędów: 10 sekund
  • 300 000 rzędów: 40 sekund
  • 2 miliony wierszy: 18 minut
  • 3 miliony wierszy: 26 minut
  • 4 miliony wierszy: (anulowane po 2 godzinach)

Wygląda na to, że nie ma rozwiązania „książki kucharskiej” i należy samodzielnie ustalić optymalną kombinację ustawień.
Oprócz sugestii na temat tego, co należy zmienić w mojej konfiguracji, doceniłbym również więcej informacji, w jaki sposób mógłbym lepiej porównać proces importowania / uzyskać lepszy wgląd w to, co się dzieje i gdzie może być wąskie gardło.
Próbowałem przeczytać dokumentację ustawień, które zmieniam, ale z drugiej strony nie jestem świadomy żadnych skutków ubocznych, a jeśli nawet obniżę wydajność przy źle dobranej wartości.

W tej chwili chciałbym wypróbować sugestię z czatu do użycia MyISAMpodczas importowania i później zmienić silnik tabeli.
Chciałbym tego spróbować, ale w tej chwili moje DROP TABLEzapytanie również kończy się kilka godzin. (Co wydaje się kolejnym wskaźnikiem, moje ustawienie jest mniej niż optymalne).

Informacje dodatkowe:
Aktualnie używany komputer ma 8 GB pamięci RAM i hybrydowy dysk twardy Solid State w / 5400 RPM.
Chociaż staramy się również usunąć przestarzałe dane z omawianej tabeli, nadal potrzebuję dość szybkiego importu do
a) testowania automatic data cleanup featurepodczas programowania
ib) w przypadku awarii naszego serwera chcielibyśmy użyć drugiego serwera jako zamiennika (co wymaga -data danych, ostatni import zajął ponad 24 godziny)

mysql> SHOW CREATE TABLE monster\G
*************************** 1. row ***************************
       Table: monster
Create Table: CREATE TABLE `monster` (
  `monster_id` int(11) NOT NULL AUTO_INCREMENT,
  `ext_monster_id` int(11) NOT NULL DEFAULT '0',
  `some_id` int(11) NOT NULL DEFAULT '0',
  `email` varchar(250) NOT NULL,
  `name` varchar(100) NOT NULL,
  `address` varchar(100) NOT NULL,
  `postcode` varchar(20) NOT NULL,
  `city` varchar(100) NOT NULL,
  `country` int(11) NOT NULL DEFAULT '0',
  `address_hash` varchar(250) NOT NULL,
  `lon` float(10,6) NOT NULL,
  `lat` float(10,6) NOT NULL,
  `ip_address` varchar(40) NOT NULL,
  `cookie` int(11) NOT NULL DEFAULT '0',
  `party_id` int(11) NOT NULL,
  `status` int(11) NOT NULL DEFAULT '2',
  `creation_date` datetime NOT NULL,
  `someflag` tinyint(1) NOT NULL DEFAULT '0',
  `someflag2` tinyint(4) NOT NULL,
  `upload_id` int(11) NOT NULL DEFAULT '0',
  `news1` tinyint(4) NOT NULL DEFAULT '0',
  `news2` tinyint(4) NOT NULL,
  `someother_id` int(11) NOT NULL DEFAULT '0',
  `note` varchar(2500) NOT NULL,
  `referer` text NOT NULL,
  `subscription` int(11) DEFAULT '0',
  `hash` varchar(32) DEFAULT NULL,
  `thumbs1` int(11) NOT NULL DEFAULT '0',
  `thumbs2` int(11) NOT NULL DEFAULT '0',
  `thumbs3` int(11) NOT NULL DEFAULT '0',
  `neighbours` tinyint(4) NOT NULL DEFAULT '0',
  `relevance` int(11) NOT NULL,
  PRIMARY KEY (`monster_id`),
  KEY `party_id` (`party_id`),
  KEY `creation_date` (`creation_date`),
  KEY `email` (`email`(4)),
  KEY `hash` (`hash`(8)),
  KEY `address_hash` (`address_hash`(8)),
  KEY `thumbs3` (`thumbs3`),
  KEY `ext_monster_id` (`ext_monster_id`),
  KEY `status` (`status`),
  KEY `note` (`note`(4)),
  KEY `postcode` (`postcode`),
  KEY `some_id` (`some_id`),
  KEY `cookie` (`cookie`),
  KEY `party_id_2` (`party_id`,`status`)
) ENGINE=InnoDB AUTO_INCREMENT=13763891 DEFAULT CHARSET=utf8
nuala
źródło
2
Czy próbowałeś z mniejszymi importami, takimi jak wiersze 10 000 lub 100 000?
ypercubeᵀᴹ
1
Uruchom, SHOW CREATE TABLE yourtable\Gaby pokazać nam strukturę tabeli tego 10-milionowego wiersza.
RolandoMySQLDBA
@RolandoMySQLDBA, więc zrobiłem (z ukrytymi nazwami pól)
nuala
Wyłączenie podwójnego bufora zapisu ( innodb_doublewrite = 0) powoduje, że instalacja MySQL nie jest bezpieczna w przypadku awarii: jeśli wystąpi awaria zasilania (nie awaria MySQL), dane mogą zostać po cichu uszkodzone.
jfg956

Odpowiedzi:

13

Po pierwsze, musisz wiedzieć, co robisz z InnoDB, gdy zaorasz miliony wierszy w tabeli InnoDB. Rzućmy okiem na architekturę InnoDB.

Architektura InnoDB

W lewym górnym rogu znajduje się ilustracja puli buforów InnoDB. Zauważ, że jest jej część poświęcona buforowi wstawiania. Co to robi Służy do migrowania zmian do indeksów wtórnych z puli buforów do bufora wstawiania w systemowym obszarze tabel (alias ibdata1). Domyślnie innodb_change_buffer_max_size jest ustawiony na 25. Oznacza to, że do 25% puli buforów można wykorzystać do przetwarzania indeksów wtórnych.

W twoim przypadku masz 6,935 GB na pulę buforów InnoDB. Do przetworzenia twoich indeksów dodatkowych zostanie wykorzystane maksymalnie 1,734 GB.

Teraz spójrz na swój stół. Masz 13 indeksów wtórnych. Każdy przetwarzany wiersz musi wygenerować dodatkowy wpis indeksu, połączyć go z kluczem podstawowym wiersza i wysłać jako parę z bufora wstawiania w puli buforów do bufora wstawiania w ibdata1. Zdarza się to 13 razy w każdym rzędzie. Pomnóż to przez 10 milionów, a niemal poczujesz wąskie gardło.

Nie zapominaj, że zaimportowanie 10 milionów wierszy w jednej transakcji zgromadzi wszystko w jednym segmencie wycofywania i wypełni przestrzeń UNDO w ibdata1.

PROPOZYCJE

SUGESTIA # 1

Moją pierwszą sugestią byłoby zaimportowanie tej dość dużej tabeli

  • Usuń wszystkie nieunikalne indeksy
  • Zaimportuj dane
  • Utwórz wszystkie nieunikalne indeksy

SUGESTIA # 2

Pozbądź się duplikatów indeksów. W twoim przypadku masz

KEY `party_id` (`party_id`),
KEY `party_id_2` (`party_id`,`status`)

Oba indeksy zaczynają się od party_id, możesz zwiększyć przetwarzanie indeksu dodatkowego o co najmniej 7,6%, pozbywając się jednego indeksu z 13. Musisz w końcu uruchomić

ALTER TABLE monster DROP INDEX party_id;

SUGESTIA # 3

Pozbądź się indeksów, których nie używasz. Przejrzyj kod aplikacji i sprawdź, czy zapytania korzystają ze wszystkich indeksów. Możesz przyjrzeć się wykorzystaniu pt-index-use, aby zasugerowało, które indeksy nie są używane.

SUGESTIA # 4

Powinieneś zwiększyć rozmiar innodb_log_buffer_size do 64M, ponieważ domyślnie jest to 8M. Większy bufor dziennika może zwiększyć wydajność operacji We / Wy zapisu InnoDB.

EPILOG

Wstawiając pierwsze dwie sugestie, wykonaj następujące czynności:

  • Upuść 13 nieunikalnych indeksów
  • Zaimportuj dane
  • Utwórz wszystkie nieunikalne indeksy oprócz party_idindeksu

Być może poniższe mogą pomóc

CREATE TABLE monster_new LIKE monster;
ALTER TABLE monster_new
  DROP INDEX `party_id`,
  DROP INDEX `creation_date`,
  DROP INDEX `email`,
  DROP INDEX `hash`,
  DROP INDEX `address_hash`,
  DROP INDEX `thumbs3`,
  DROP INDEX `ext_monster_id`,
  DROP INDEX `status`,
  DROP INDEX `note`,
  DROP INDEX `postcode`,
  DROP INDEX `some_id`,
  DROP INDEX `cookie`,
  DROP INDEX `party_id_2`;
ALTER TABLE monster RENAME monster_old;
ALTER TABLE monster_new RENAME monster;

Zaimportuj dane do monster. Następnie uruchom to

ALTER TABLE monster
  ADD INDEX `creation_date`,
  ADD INDEX `email` (`email`(4)),
  ADD INDEX `hash` (`hash`(8)),
  ADD INDEX `address_hash` (`address_hash`(8)),
  ADD INDEX `thumbs3` (`thumbs3`),
  ADD INDEX `ext_monster_id` (`ext_monster_id`),
  ADD INDEX `status` (`status`),
  ADD INDEX `note` (`note`(4)),
  ADD INDEX `postcode` (`postcode`),
  ADD INDEX `some_id` (`some_id`),
  ADD INDEX `cookie` (`cookie`),
  ADD INDEX `party_id_2` (`party_id`,`status`);

SPRÓBUJ !!!

ALTERNATYWNY

Możesz utworzyć tabelę o nazwie monster_csvjako tabela MyISAM bez indeksów i wykonaj następujące czynności:

CREATE TABLE monster_csv ENGINE=MyISAM AS SELECT * FROM monster WHERE 1=2;
ALTER TABLE monster RENAME monster_old;
CREATE TABLE monster LIKE monster_old;
ALTER TABLE monster DROP INDEX `party_id`;

Zaimportuj swoje dane do monster_csv. Następnie użyj mysqldump, aby utworzyć kolejny import

mysqldump -t -uroot -p mydb monster_csv | sed 's/monster_csv/monster/g' > data.sql

Plik mysqldump data.sqlrozszerzy polecenia INSERT, importując jednocześnie 10 000-20 000 wierszy.

Teraz wystarczy załadować mysqldump

mysql -uroot -p mydb < data.sql

Na koniec pozbądź się tabeli MyISAM

DROP TABLE monster_csv;
RolandoMySQLDBA
źródło
Nie byłem nawet świadomy tych wszystkich kluczy (to nie jest mój projekt), ale twoje wyjaśnienie wydaje się bardzo przekonujące. Na dziś jest za późno, aby rozpocząć kolejną próbę, ale widzę kilka świetnych porad, co wypróbować jutro. Powiadomimy Cię! <3
nuala
1
Udało mi się zaimportować pełną bazę danych (nie tylko monstertabelę) w mniej niż 20 minut, gdy nie mam kluczy w tabelach InnoDB. Dodanie kluczy zajęło ok. kolejne 20 min. Powiedziałbym, że to prawie rozwiązuje mój problem w tym przypadku. Dziękuję Ci bardzo!
nuala
8

Chciałem napisać komentarz (ponieważ nie jest to ostateczna odpowiedź), ale stał się on zbyt długi:

Dam ci kilka obszernych porad i możemy przejść do szczegółów każdego z nich, jeśli chcesz:

  • Zmniejsz wytrzymałość (już to zrobiłeś). Najnowsze wersje pozwalają nawet robić więcej. Możesz sięgnąć aż do wyłączenia podwójnego bufora zapisu, ponieważ uszkodzenie nie stanowi problemu przy importowaniu.
  • Zwiększ buforowanie przez: Zwiększ rozmiar dziennika transakcji i zwiększ rozmiar dostępnej puli buforów. Monitoruj użycie pliku dziennika transakcji i punktów kontrolnych. Nie bój się dużych dzienników na import.
  • Unikaj dużych transakcji - wycofywanie danych będzie pełne niepotrzebnych danych. To prawdopodobnie twój największy problem.
  • SQL będzie wąskim gardłem, uniknie narzutu SQL (handlersocket, memcached) i / lub załaduje go jednocześnie z kilkoma wątkami. Współbieżność musi osiągnąć dobre miejsce, nie za dużo, nie za mało.
  • Ładowanie danych w fragmentacji kolejności kluczy podstawowych może być isse
  • Przetestuj kompresję InnoDB, jeśli IO jest twoim wąskim gardłem, a procesor i pamięć nie powodują spowolnienia
  • Spróbuj później utworzyć klucze pomocnicze (w niektórych przypadkach szybsze), nie ładuj indeksowanych danych - WYŁĄCZ klucze nie wpływają na InnoDB . Jeśli nie, monitoruj bufor wstawiania (być może wyprzedzając połowę puli buforów).
  • Zmień lub wyłącz algorytm sumy kontrolnej - nie jest to prawdopodobnie twój problem, ale staje się wąskim gardłem w zaawansowanych kartach flash.
  • Ostateczność: Monitoruj swój serwer, aby znaleźć obecne wąskie gardło i spróbuj go złagodzić (InnoDB jest bardzo elastyczny).

Pamiętaj, że niektóre z nich nie są bezpieczne ani wskazane w przypadku braku importu (normalne działanie).

jynus
źródło
Dziękuję Ci bardzo! Najpierw lubię wypróbować pomysł Rolando dotyczący indeksów, ale wydaje mi się, że to „wycofywanie transakcji” nadal będzie problemem. Czy mógłbyś to rozwinąć? Myślę, że chcę wyłączyć jak najwięcej z tej funkcjonalności podczas importu i po prostu włączyć ją ponownie, wchodząc do produkcji ~ Myślę, że ...
nuala
1
Sugestia Rolando jest moim punktem # 7. Unikanie narzutu związanego z wycofywaniem jest tak proste, jak połączenie SET SESSION tx_isolation='READ-UNCOMMITTED';(przydatne tylko w przypadku importowania z kilkoma wątkami równolegle) i komentarza @ypercube na temat wstawiania w partie. Masz tutaj pełny przykład: mysqlperformanceblog.com/2008/07/03/... Upewnij się, że korzystasz ze wszystkich funkcji w najnowszych wersjach InnoDB: mysqlperformanceblog.com/2011/01/07/…
jynus
1
Miałem ogólne wrażenie, że można uniknąć importowania mniejszych uchwytów, ale raczej wybrać operację „all inclusive”, ale widzę, że wielowątkowość może otworzyć pewne możliwości. Zgadnij, to bardzo specyficzne dla konkretnego przypadku. Jednak zaakceptowałem odpowiedź Rolando, ponieważ sama ta poprawka (twoje nr 7) pomogła mi uzyskać pełny import w ciągu <1 godziny, ale twoja lista jest zdecydowanie daleka od bezwartościowości i myślę, że wykorzystam ją jako odniesienie, gdy tylko tempo naszego DB będzie rosło straszy mnie :)
nuala
Zgadzam się z @yoshi. Twoja odpowiedź jest bardziej wyczerpująca pod względem rozwiązywania problemów i poprawy wydajności. +1
RolandoMySQLDBA
3

Jak dotąd podano większość dobrych wskazówek, ale bez wielu wyjaśnień dotyczących najlepszych. Dam więcej szczegółów.

Po pierwsze, opóźnianie tworzenia indeksu jest dobre, z wystarczającą ilością szczegółów w innych odpowiedziach. Nie wrócę na to.

Większy plik dziennika InnoDB bardzo ci pomoże (jeśli używasz MySQL 5.6, ponieważ nie można go zwiększyć w MySQL 5.5). Wstawiasz 7 GB danych, zaleciłbym całkowity rozmiar dziennika wynoszący co najmniej 8 GB (zachowaj innodb_log_files_in_groupdomyślną wartość (2) i wybijaj innodb_log_file_size4 GB). To 8 GB nie jest dokładne: powinien mieć co najmniej rozmiar importu w dzienniku REDO i prawdopodobnie dwukrotnie lub czterokrotnie ten rozmiar. Rozumowanie za wielkością dziennika InnoDB zwiększa to, że gdy dziennik będzie prawie pełny, InnoDB zacznie agresywnie opróżniać swoją pulę buforów na dysk, aby uniknąć zapełniania się dziennika (gdy dziennik jest pełny, InnoDB nie może wykonać zapisu w bazie danych, dopóki niektóre strony puli buforów są zapisywane na dysku).

Pomoże Ci większy plik dziennika InnoDB, ale powinieneś również wstawić w kolejności klucza podstawowego (posortuj plik przed wstawieniem). Jeśli wstawisz w kolejności klucza podstawowego, InnoDB zapełni jedną stronę, a następnie następną i tak dalej. Jeśli nie wstawisz w kolejności klucza podstawowego, następna wstawka może skończyć się na stronie, która jest pełna i spowoduje „podział strony”. Podział strony będzie kosztowny dla InnoDB i spowolni import.

Masz już pulę buforów tak dużą, jak pozwala na to pamięć RAM, a jeśli twój stół nie mieści się w niej, niewiele możesz zrobić, oprócz kupowania większej ilości pamięci RAM. Ale jeśli tabela mieści się w puli buforów, ale jest większa niż 75% puli buforów, możesz spróbować zwiększyć innodb_max_dirty_pages_pctdo 85 lub 95 podczas importu (wartość domyślna to 75). Ten parametr konfiguracyjny mówi InnoDB, aby zaczął agresywnie opróżniać pulę buforów, gdy procent brudnych stron osiągnie ten limit. Podbijając ten parametr (i jeśli masz szczęście, jeśli chodzi o rozmiar danych), możesz uniknąć agresywnego We / Wy podczas importu i opóźnić te We / Wy później.

Może (i to jest przypuszczenie) importowanie danych w wielu małych transakcjach pomoże ci. Nie wiem dokładnie, jak budowany jest dziennik REDO, ale jeśli jest on buforowany w pamięci RAM (i na dysku, gdy potrzebna będzie zbyt duża ilość pamięci RAM), gdy transakcja postępuje, może dojść do niepotrzebnych operacji we / wy. Możesz spróbować: po posortowaniu pliku podziel go na wiele części (spróbuj z 16 MB i innymi rozmiarami) i zaimportuj je jeden po drugim. Pozwoliłoby to również kontrolować postęp importu. Jeśli nie chcesz, aby Twoje dane były częściowo widoczne dla innego czytnika podczas importowania, możesz zaimportować przy użyciu innej nazwy tabeli, później utworzyć indeksy, a następnie zmienić nazwę tabeli.

O twoim hybrydowym dysku SSD / 5400RPM nie wiem o nich i jak to zoptymalizować. 5400 RPM wygląda wolno dla bazy danych, ale być może SSD tego unika. Być może wypełniasz część dysku SSD dyskiem sekwencyjnymi zapisami w dzienniku REDO, a dysk SSD szkodzi wydajności. Nie wiem.

Złe wskazówki, których nie powinieneś wypróbowywać (bądź ostrożny) to: nie używaj wielowątkowości: bardzo trudno będzie zoptymalizować, aby uniknąć podziału strony w InnoDB. Jeśli chcesz korzystać z wielu wątków, wstaw w różnych tabelach (lub w różnych partycjach tej samej tabeli).

Jeśli zastanawiasz się nad wieloma wątkami, być może masz komputer z wieloma gniazdami (NUMA). W takim przypadku unikaj problemu szaleństwa wymiany MySQL .

Jeśli używasz MySQL 5.5, uaktualnij do MySQL 5.6: ma opcję zwiększenia rozmiaru dziennika REDO i ma lepsze algorytmy opróżniania puli buforów.

Powodzenia w imporcie.

jfg956
źródło