MySQL i NoSQL: Pomóż mi wybrać właściwy

Istnieje duża baza danych, 1,000,000,000 wierszy, zwana wątkami(te wątki faktycznie istnieją, nie utrudniam tego tylko dlatego, że mi się to podoba). Threads zawiera tylko kilka rzeczy, aby przyspieszyć działanie: (int id, string hash, int replycount, int dateline (timestamp), int forumid, string title)

Zapytanie:

select * from thread where forumid = 100 and replycount > 1 order by dateline desc limit 10000, 100

Ponieważ jest 1G rekordów, to dość powolne zapytanie. Więc pomyślałem, Podzielmy ten 1G rekordów na tyle tabel, ile forum (Kategoria) mam! To jest prawie idealne. Mając wiele tabel mam mniej rekordów do przeszukania i jest to naprawdę szybsze. Zapytanie staje się teraz:

select * from thread_{forum_id} where replycount > 1 order by dateline desc limit 10000, 100

Jest to naprawdę szybsze z 99% forów (kategorii), ponieważ większość z nich ma tylko kilka tematów (100k-1M). Jednak ponieważ istnieją niektóre z około 10m rekordów, niektóre zapytania są nadal powolne (0.1/.2 sekundy, za dużo dla mojej aplikacji!, już korzystam z indeksów!).

Nie wiem jak to poprawić za pomocą MySQL. Jest jakiś sposób?

Do tego projektu użyję 10 serwerów (12GB ram, dysk twardy 4x7200rpm na oprogramowaniu raid 10, Quad core)

Chodziło o to, aby po prostu podzielić bazy danych między serwery, ale z problemem wyjaśnionym powyżej, który nadal nie jest wystarczający.

Jeśli zainstaluję Cassandrę na tych 10 serwerach (zakładając, że znajdę czas, aby to działało tak, jak powinno), czy powinienem mieć zwiększenie wydajności?

co? powinienem? Pracować z MySQL z rozproszoną bazą danych na wielu maszynach lub zbudować klaster cassandra?

Poproszono mnie o zamieszczenie jakie są indeksy, oto one:

mysql> show index in thread;
PRIMARY id
forumid
dateline
replycount

Wybierz wyjaśnij:

mysql> explain SELECT * FROM thread WHERE forumid = 655 AND visible = 1 AND open <> 10 ORDER BY dateline ASC LIMIT 268000, 250;
+----+-------------+--------+------+---------------+---------+---------+-------------+--------+-----------------------------+
| id | select_type | table  | type | possible_keys | key     | key_len | ref         | rows   | Extra                       |
+----+-------------+--------+------+---------------+---------+---------+-------------+--------+-----------------------------+
|  1 | SIMPLE      | thread | ref  | forumid       | forumid | 4       | const,const | 221575 | Using where; Using filesort | 
+----+-------------+--------+------+---------------+---------+---------+-------------+--------+-----------------------------+
Author: Peter O., 2010-12-12

5 answers

Powinieneś przeczytać poniżej i dowiedzieć się trochę o zaletach dobrze zaprojektowanej tabeli innodb i jak najlepiej korzystać z klastrowych indeksów - dostępnych tylko z innodb !

Http://dev.mysql.com/doc/refman/5.0/en/innodb-index-types.html

Http://www.xaprb.com/blog/2006/07/04/how-to-exploit-mysql-index-optimizations/

Następnie Zaprojektuj Swój system według następującego uproszczonego przykładu:

Przykład schemat (uproszczony)

Ważne jest to, że tabele używają silnika innodb, a klucz podstawowy tabeli wątków nie jest już pojedynczym kluczem auto_incrementującym, ale złożonym klastrem opartym na kombinacji forum_id i thread_id. np.

threads - primary key (forum_id, thread_id)

forum_id    thread_id
========    =========
1                   1
1                   2
1                   3
1                 ...
1             2058300  
2                   1
2                   2
2                   3
2                  ...
2              2352141
...

Każdy wiersz forum zawiera licznik o nazwie next_thread_id (unsigned int), który jest utrzymywany przez wyzwalacz i zwiększa się za każdym razem, gdy wątek jest dodawany do danego forum. Oznacza to również, że możemy przechowywać 4 miliard wątków na forum, a nie łącznie 4 miliardy wątków, jeśli używa się pojedynczego klucza głównego auto_increment dla thread_id.

forum_id    title   next_thread_id
========    =====   ==============
1          forum 1        2058300
2          forum 2        2352141
3          forum 3        2482805
4          forum 4        3740957
...
64        forum 64       3243097
65        forum 65      15000000 -- ooh a big one
66        forum 66       5038900
67        forum 67       4449764
...
247      forum 247            0 -- still loading data for half the forums !
248      forum 248            0
249      forum 249            0
250      forum 250            0
Wadą użycia klucza złożonego jest to, że nie można już po prostu wybrać wątku za pomocą jednej wartości klucza w następujący sposób:
select * from threads where thread_id = y;

Musisz zrobić:

select * from threads where forum_id = x and thread_id = y;

Jednak Twój kod aplikacji powinien być świadomy, które forum przegląda użytkownik, więc nie jest to do końca trudne do zaimplementowania-Zapisz aktualnie przeglądany identyfikator forum_id w sesji zmienne lub ukryte pole formularza itp...

Oto schemat uproszczony:

drop table if exists forums;
create table forums
(
forum_id smallint unsigned not null auto_increment primary key,
title varchar(255) unique not null,
next_thread_id int unsigned not null default 0 -- count of threads in each forum
)engine=innodb;


drop table if exists threads;
create table threads
(
forum_id smallint unsigned not null,
thread_id int unsigned not null default 0,
reply_count int unsigned not null default 0,
hash char(32) not null,
created_date datetime not null,
primary key (forum_id, thread_id, reply_count) -- composite clustered index
)engine=innodb;

delimiter #

create trigger threads_before_ins_trig before insert on threads
for each row
begin
declare v_id int unsigned default 0;

  select next_thread_id + 1 into v_id from forums where forum_id = new.forum_id;
  set new.thread_id = v_id;
  update forums set next_thread_id = v_id where forum_id = new.forum_id;
end#

delimiter ;

Być może zauważyłeś, że dodałem reply_count jako część klucza głównego, co jest nieco dziwne, ponieważ kompozyt (forum_id, thread_id) jest unikalny sam w sobie. Jest to tylko optymalizacja indeksu, która zapisuje niektóre operacje wejścia / wyjścia, gdy wykonywane są zapytania używające reply_count. Więcej informacji na ten temat można znaleźć w 2 linkach powyżej.

Przykładowe zapytania

Wciąż Ładuję dane do mojego przykładu stoły i do tej pory mam załadowany ok. 500 milionów wierszy (o połowę mniej niż Twój system). Po zakończeniu procesu ładowania powinienem spodziewać się około:

250 forums * 5 million threads = 1250 000 000 (1.2 billion rows)

Celowo sprawiłem, że niektóre fora zawierają ponad 5 milionów wątków, na przykład forum 65 mA 15 milionów wątków:

forum_id    title   next_thread_id
========    =====   ==============
65        forum 65      15000000 -- ooh a big one

Query runtimes

select sum(next_thread_id) from forums;

sum(next_thread_id)
===================
539,155,433 (500 million threads so far and still growing...)

Pod innodb sumowanie next_thread_ids, aby dać całkowitą liczbę wątków, jest znacznie szybsze niż zwykle:

select count(*) from threads;

Ile wątków forum 65:

select next_thread_id from forums where forum_id = 65

next_thread_id
==============
15,000,000 (15 million)

Znowu jest to szybsze niż zwykle:

select count(*) from threads where forum_id = 65

Ok teraz wiemy, że mamy do tej pory około 500 milionów wątków, a forum 65 mA 15 milionów wątków-zobaczmy jak będzie wyglądał schemat:)

select forum_id, thread_id from threads where forum_id = 65 and reply_count > 64 order by thread_id desc limit 32;

runtime = 0.022 secs

select forum_id, thread_id from threads where forum_id = 65 and reply_count > 1 order by thread_id desc limit 10000, 100;

runtime = 0.027 secs

Wygląda dość wydajny dla mnie-więc jest to pojedyncza tabela z 500 + milionów wierszy (i rośnie) z zapytaniem, które obejmuje 15 milionów wierszy w 0.02 sekundy (podczas gdy pod obciążeniem !)

Dalsze optymalizacje

Te include:

  • Partycjonowanie według zakresu

  • Sharding

  • Rzucanie w niego pieniędzmi i sprzętem

Itd...

Mam nadzieję, że ta odpowiedź okaże się pomocna:)

 75
Author: Jon Black,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-07 05:48:15

EDIT: Twoje jednokolumnowe indeksy nie wystarczą. Musiałbyś przynajmniej pokryć trzy zaangażowane kolumny.

Bardziej zaawansowane rozwiązanie: zamień replycount > 1 na hasreplies = 1, tworząc nowe pole hasreplies, które jest równe 1, Gdy replycount > 1. Gdy to zrobisz, Utwórz indeks na trzech kolumnach w tej kolejności: INDEX(forumid, hasreplies, dateline). Upewnij się, że jest to indeks BTREE obsługujący zamawianie.

Wybierasz na podstawie:

  • A dany forumid
  • A dany hasreplies
  • uporządkowane by dateline

Gdy to zrobisz, wykonanie zapytania będzie obejmować:

  • przesunięcie w dół drzewa BTREE, aby znaleźć podtree, które pasuje forumid = X. Jest to operacja logarytmiczna (czas trwania: log (liczba forów)).
  • poruszanie się dalej w dół drzewa BTREE, aby znaleźć podtree, które pasuje hasreplies = 1 (podczas gdy nadal pasuje forumid = X). Jest to operacja w czasie stałym, ponieważ hasreplies jest tylko 0 LUB 1.
  • przechodzenie przez podtree dateline-posortowane w celu uzyskania wymaganego wyniki, bez konieczności czytania i ponownego sortowania całej listy przedmiotów na forum.

Moja wcześniejsza sugestia indeksowania na replycount była błędna, ponieważ byłoby to zapytanie zakresowe, a tym samym uniemożliwiło użycie dateline do sortowania wyników (więc wybrałbyś wątki z odpowiedziami bardzo szybko, ale wynikowa lista milionów linii musiałaby być całkowicie posortowana przed szukaniem 100 potrzebnych elementów).

Ważne : podczas gdy to poprawia wydajność we wszystkich przypadkach, twoja ogromna wartość offsetu (10000!) zmniejszy wydajność, ponieważ MySQL nie wydaje się być w stanie pominąć do przodu pomimo czytania prosto przez BTREE. Im większe jest twoje przesunięcie, tym wolniejsze będzie żądanie.

Obawiam się, że problem offsetu nie jest automagicznie rozwiązany przez rozłożenie obliczeń na kilka obliczeń (jak w ogóle pominąć offset równolegle?) lub przejście do NoSQL. Wszystkie rozwiązania (w tym NoSQL) sprowadza się do symulacji offsetu na podstawie dateline (zasadniczo mówiąc dateline > Y LIMIT 100 zamiast LIMIT Z, 100, gdzie Y jest datą pozycji w offsecie Z). Działa to i eliminuje wszelkie problemy z wydajnością związane z przesunięciem, ale zapobiega przechodzeniu bezpośrednio do strony 100 z 200.

 24
Author: Victor Nicollet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-12 00:00:46

Jest część pytania, które związane z opcją NoSQL lub MySQL. Właściwie to jest jedna fundamentalna rzecz ukryta tutaj. Język SQL jest łatwy do napisania dla człowieka i nieco trudny do odczytania dla komputera. W dużych bazach danych zalecałbym unikanie backendów SQL, ponieważ wymaga to dodatkowego parsowania poleceń krokowych. Zrobiłem obszerne benchmarking i są przypadki, gdy parser SQL jest najwolniejszy punkt. Nic na to nie poradzisz. Ok, można użyć wstępnie parsowanych instrukcji i dostęp do nich.

BTW, nie jest szeroko znany, ale MySQL wyrosło z bazy NoSQL. Firma, w której pracowali autorzy MySQL David i Monty, była hurtownią danych i często musieli pisać niestandardowe rozwiązania dla nietypowych zadań. Doprowadziło to do dużego stosu bibliotek homebrew C używanych do ręcznego pisania funkcji bazodanowych, gdy Oracle i inne działały słabo. SQL został dodany do tego prawie 20-letniego zoo w 1996 roku dla Zabawy. To, co przyszło po tym, jak się dowiedziałeś.

Faktycznie można unikaj SQL overhead z MySQL. Ale zwykle parsowanie SQL nie jest najwolniejszą częścią, ale po prostu dobrze wiedzieć. Aby przetestować Parser można po prostu zrobić benchmark dla "SELECT 1" na przykład ;).

 3
Author: Tõnu Samuel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-25 05:58:55

Nie powinieneś próbować dopasować architektury bazy danych do sprzętu, który planujesz kupić, ale zamiast tego planuj zakup sprzętu, który będzie pasował do Twojej architektury bazy danych.

Gdy masz wystarczająco dużo pamięci RAM, aby utrzymać działający zestaw indeksów w pamięci, wszystkie zapytania, które mogą korzystać z indeksów, będą szybkie. Upewnij się, że bufor klawiszy jest wystarczająco duży, aby pomieścić indeksy.

Więc jeśli 12GB to za mało, nie używaj 10 serwerów z 12GB pamięci RAM, używaj mniej z 32GB lub 64GB pamięci RAM.

 2
Author: Dan Grossman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-11 23:38:53

Indeksy są koniecznością - ale pamiętaj, aby wybrać odpowiedni typ indeksu: BTREE jest bardziej odpowiedni, gdy używasz zapytań z " "w klauzuli WHERE, podczas gdy HASH jest bardziej odpowiedni, gdy masz wiele różnych wartości w jednej kolumnie i używasz" = " lub " " w klauzuli WHERE.

Czytaj dalej http://dev.mysql.com/doc/refman/5.0/en/mysql-indexes.html

 0
Author: descent89,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-11 23:30:42