MySQL и NoSQL: помогите выбрать правильный

Существует большая база данных, 1 000 000 000 строк, называемых потоками (эти потоки фактически существуют, я не делаю ничего сложнее только из-за того, что мне это нравится). В потоках есть только несколько вещей, чтобы сделать вещи быстрее: (int id, string hash, int answercount, int dateline (timestamp), int forumid, название строки)

Query:

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

Так как это 1G записей, это довольно медленный запрос. Так что я подумал, позвольте разделить этот 1G записей на столько таблиц, сколько у меня есть форумов (категорий)! Это почти идеально. Имея много таблиц, у меня меньше записей для поиска, и это действительно быстрее. Теперь запрос будет выглядеть следующим образом:

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

Это действительно быстрее с 99% форумов (категория), так как большинство из них имеют только несколько тем (100k-1M). Однако из-за того, что некоторые из них содержат около 10 млн записей, некоторые запросы по-прежнему замедляются (0,1/.2 секунды, для моего приложения!, Я уже использую индексы!).

Я не знаю, как улучшить это с помощью MySQL. Есть ли способ?

Для этого проекта я буду использовать 10 серверов (12 ГБ, жесткий диск 4х7200 об/мин на программном рейде 10, четырехъядерный процессор)

Идея состояла в том, чтобы просто разделить базы данных между серверами, но с описанной выше проблемой это все еще не достаточно.

Если я установлю cassandra на этих 10 серверах (предположим, что я нахожу время, чтобы заставить его работать, как и предполагалось), следует ли предположить, что нужно повысить производительность?

Что мне делать? Продолжайте работать с MySQL с распределенной базой данных на нескольких машинах или создайте кластер cassandra?

Меня попросили указать, что такое индексы, вот они:

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

Выберите объяснение:

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 | 
+----+-------------+--------+------+---------------+---------+---------+-------------+--------+-----------------------------+

Ответ 1

Вы должны прочитать следующее и немного узнать о преимуществах хорошо разработанной таблицы innodb и о том, как лучше всего использовать кластерные индексы - доступно только с 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/

затем создайте свою систему в соответствии со следующим упрощенным примером:

Пример схемы (упрощенной)

Важными особенностями являются то, что в таблицах используется механизм innodb, а первичный ключ для таблицы потоков больше не является единственным ключом auto_incrementing, а составным кластеризованным ключом на основе комбинации forum_id и thread_id. например.

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
...

Каждая строка форума содержит счетчик next_thread_id (unsigned int), который поддерживается триггером и увеличивается каждый раз, когда поток добавляется к данному форуму. Это также означает, что мы можем хранить 4 миллиарда потоков на форум, а не 4 миллиарда потоков, если использовать один первичный ключ auto_increment для 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

Недостатком использования составного ключа является то, что вы уже не можете просто выбрать поток с помощью одного значения ключа следующим образом:

select * from threads where thread_id = y;

вам нужно сделать:

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

Однако ваш код приложения должен знать, какой форум просматривается пользователем, поэтому его не так сложно реализовать - сохранить просматриваемый в данный момент forum_id в переменной сеанса или в скрытом поле формы и т.д.

Здесь упрощенная схема:

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 ;

Возможно, вы заметили, что я включил answer_count как часть первичного ключа, который немного странен, поскольку (forum_id, thread_id) составной является уникальным сам по себе. Это просто оптимизация индекса, которая экономит некоторые операции ввода-вывода, когда выполняются запросы, использующие функцию reply_count. Пожалуйста, обратитесь к 2 ссылкам выше для получения дополнительной информации об этом.

Примеры запросов

Я по-прежнему загружаю данные в свои таблицы примеров, и до сих пор у меня загружено ок. 500 миллионов строк (вдвое больше, чем ваша система). Когда процесс загрузки будет завершен, я должен ожидать, что будет примерно:

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

Я специально сделал некоторые из форумов, содержащих более 5 миллионов потоков, например, форум 65 имеет 15 миллионов потоков:

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

Время выполнения запросов

select sum(next_thread_id) from forums;

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

под innodb суммирование next_thread_ids, чтобы дать общее количество потоков намного быстрее, чем обычно:

select count(*) from threads;

Сколько потоков имеет форум 65:

select next_thread_id from forums where forum_id = 65

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

снова это быстрее, чем обычно:

select count(*) from threads where forum_id = 65

Хорошо, теперь мы знаем, что у нас около 500 миллионов потоков, а форум 65 имеет 15 миллионов потоков - посмотрим, как работает схема:)

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

Выглядит довольно эффектно для меня - так что одна таблица с 500 миллионами строк (и растет) с запросом, который покрывает 15 миллионов строк за 0,02 секунды (при загрузке!)

Дальнейшие оптимизации

К ним относятся:

  • разбиение на диапазон

  • Sharding

  • бросая деньги и оборудование на него

и т.д...

надеюсь, что вы найдете этот ответ полезным:)

Ответ 2

РЕДАКТИРОВАТЬ: ваших индексов с одним столбцом недостаточно. Вам нужно, по крайней мере, охватить три задействованные столбцы.

Более продвинутое решение: замените replycount > 1 на hasreplies = 1, создав новое поле hasreplies, равное 1, когда replycount > 1. После этого создайте индекс в трех столбцах в следующем порядке: INDEX(forumid, hasreplies, dateline). Убедитесь, что индекс BTREE поддерживает порядок.

Вы выбираете на основе:

  • данный forumid
  • данный hasreplies
  • упорядочен по dateline

Как только вы это сделаете, выполнение запроса будет включать:

  • перемещение по BTREE, чтобы найти поддерево, которое соответствует forumid = X. Это логарифмическая операция (длительность: log (количество форумов)).
  • перемещение вниз по BTREE, чтобы найти поддерево, которое соответствует hasreplies = 1 (при этом все еще соответствует forumid = X). Это операция с постоянным временем, потому что hasreplies - только 0 или 1.
  • перемещаясь по сортированному поддереву dateline, чтобы получить требуемые результаты, без необходимости читать и повторно сортировать весь список элементов на форуме.

Мое предыдущее предложение индексировать на replycount было неправильным, потому что это был запрос диапазона и, таким образом, предотвращал использование dateline для сортировки результатов (так что вы бы очень быстро выбрали потоки с ответами, но полученный список миллионов строк пришлось бы сортировать полностью, прежде чем искать 100 элементов, которые вам нужны).

ВАЖНО: в то время как это повышает производительность во всех случаях, ваше огромное значение OFFSET (10000!) будет снижать производительность, поскольку MySQL, похоже, не может пропустить вперед, несмотря на то, BTREE. Таким образом, чем больше ваш OFFSET, тем медленнее будет запрос.

Я боюсь, что проблема OFFSET не решена автоматически, распространяя вычисления на несколько вычислений (как вы пропустите параллельное смещение?) или переместитесь в NoSQL. Все решения (включая NoSQL) будут сводиться к моделированию OFFSET на основе dateline (в основном говоря dateline > Y LIMIT 100 вместо LIMIT Z, 100, где Y - дата элемента при смещении Z)). Это работает и устраняет любые проблемы с производительностью, связанные со смещением, но не позволяет перейти непосредственно на страницу 100 из 200.

Ответ 3

Существует часть вопросов, связанных с параметром NoSQL или MySQL. На самом деле это одна из основных вещей, скрытых здесь. Язык SQL легко записывается для человека и бит, который трудно читать для компьютера. В базах больших томов я бы рекомендовал избегать бэкэнд SQL, поскольку для этого требуется дополнительный синтаксический анализ шага. Я провел обширный бенчмаркинг, и есть случаи, когда SQL-парсер является самой медленной точкой. Вы ничего не можете с этим поделать. Хорошо, вы можете использовать предварительно обработанные операторы и получать к ним доступ.

Кстати, он не широко известен, но MySQL вырос из базы данных NoSQL. Компания, в которой работали разработчики MySQL Дэвид и Монти, была компанией, занимающейся складированием данных, и им часто приходилось писать собственные решения для необычных задач. Это привело к большому набору внутренних библиотек C, используемых для ручного написания функций базы данных, когда Oracle и другие выполняли плохо. SQL был добавлен в этот почти 20-летний зоопарк в 1996 году для удовольствия. Что произошло после того, как вы знаете.

На самом деле вы можете избежать накладных расходов SQL с MySQL. Но обычно синтаксический анализ SQL - это не самая медленная часть, но просто полезно знать. Чтобы проверить накладные расходы парсера, вы можете просто сделать тест для "SELECT 1", например;).

Ответ 4

Нельзя пытаться подогнать архитектуру базы данных к оборудованию, которое вы планируете покупать, но вместо этого планируете купить оборудование, соответствующее вашей архитектуре базы данных.

Как только у вас будет достаточно ОЗУ для сохранения рабочего набора индексов в памяти, все ваши запросы, которые могут использовать индексы, будут быстрыми. Убедитесь, что ваш буфер ключей установлен достаточно большим, чтобы удерживать индексы.

Итак, если 12 Гб недостаточно, не используйте 10 серверов с 12 ГБ ОЗУ, используйте меньше, чем 32 ГБ или 64 ГБ ОЗУ.

Ответ 5

Индексы обязательны - но не забудьте выбрать правильный тип индекса: BTREE более подходит при использовании запросов с параметром "<" или " > " в ​​предложениях WHERE, тогда как HASH более подходит, когда у вас много разных значений в одном столбце, и вы используете "=" или "< = > " в предложении WHERE.

Дальнейшее чтение http://dev.mysql.com/doc/refman/5.0/en/mysql-indexes.html