Работа с очень большими таблицами (MySQL)

Denveroid1

Новичок
Работа с очень большими таблицами (MySQL)

Работаю с MySQL 5.0.19. Машина: P4 Xeon 2.4 X 2, 2ГБ RAM - это мин. конфигурация.

Имеется следующая таблица:
Код:
CREATE TABLE PROXY_LOG (
  id bigint(20) unsigned NOT NULL auto_increment,
  support_id bigint(20) unsigned NOT NULL default '0',
  account_id int(10) NOT NULL default '0',
  src_ip int(12) default NULL,
  app_id int(10) NOT NULL default '0',
  log_time int(10) NOT NULL default '0',
  method_code int(10) NOT NULL default '0',
  uri varchar(255) NOT NULL default '',
  obj_crc bigint(20) unsigned NOT NULL default '0',
  core_ref_crc bigint(20) unsigned NOT NULL default '0',
  response_code int(4) NOT NULL default '0',
  ts_cookie_name varchar(255) NOT NULL default '',
  `status` bigint(20) unsigned NOT NULL default '0',
  learning_mask bigint(20) unsigned NOT NULL default '0',
  alert_mask bigint(20) unsigned NOT NULL default '0',
  blocking_mask bigint(20) unsigned NOT NULL default '0',
  flg_sampling int(2) NOT NULL default '0',
  request longblob,
  flg_has_request int(2) NOT NULL default '0',
  flg_req_truncated int(2) NOT NULL default '0',
  flg_req_blocked int(2) NOT NULL default '0',
  actual_request_length int(10) NOT NULL default '0',
  response longblob,
  flg_has_response int(2) NOT NULL default '0',
  flg_resp_truncated int(2) NOT NULL default '0',
  flg_resp_blocked int(2) NOT NULL default '0',
  actual_response_length int(10) NOT NULL default '0',
  violations blob,
  PRIMARY KEY  (id),
  KEY support_id (support_id),
  KEY account_id (account_id),
  KEY src_ip (src_ip),
  KEY log_time (log_time),
  KEY learning_mask (learning_mask),
  KEY alert_mask (alert_mask),
  KEY blocking_mask (blocking_mask),
  KEY flg_has_request (flg_has_request),
  KEY flg_has_response (flg_has_response)
);
По названию таблицы не трудно догадаться, что речь идет о логгировании всего траффика, включая реквесты и респонсы, и всякую доп. информацию. Записью в таблицу занимается модуль, написанный на C. Он выполняет LAZY INSERTS со скоростью до 5000 реквестов в секунду.

В таблице могут находится миллионы записей. Для примера, скажем, до 3 миллионов максимум. Для статистику скажу, что при 2 миллионах записей со стандартными, не огромными реквестам+респонсами, файлы весят: PROXY_LOG.MYD около 2ГБ, а индексный файл PROXY_LOG.MYI около 250МБ.

Пользователю нужно предоставить интерфейс для поиска по этой таблице по куче различных фильтров и показать ему результаты поиска c возможностью сортировки результатов по различным полям (log_time, uri, ip, response_code). По умолчании сортировка идет по ORDER BY log_time DESC - то есть времени.

Также нужен, естесственно, пэйджинг. То есть придется использовать оффсеты: LIMIT <offset>, <perpage>

Основной критерий поиска - это account_id. Пользователя обычно интересует траффик для определенного account_id (или нескольких определенных account_id, так называемая группа аккаунтов). То есть, всегда в селекте будет присутствовать WHERE account_id = <account_id> или WHERE account_id IN (<account_id_list>).

Дополнительные критерии поиска:
  • Violation - поле status, держит в себе статус реквеста в виде bitwised кода. То есть, что найти определенный статус в этом поле нужно сделать WHERE status&<какой_то_код> = <какой_то_код>. Надеюсь понятно объясняю. :rolleyes:
  • IP - поле src_ip. Хранится как ip2long
  • Time Period - поле log_time. UNIX_TIMESTAMP
  • Request Type - поле status. Варианты: Legal Request и Illegal Request. Если status=0, то это Legal Request, иначе Illegal.
  • Response Code - поле response_code. Просто поиск по респонс коду WHERE response_code=<response_code>
  • Blocked Requests - поле flg_req_blocked. Вариант только один: WHERE flg_req_blocked=1
  • Truncated Requests - поле flg_req_truncated. Вариант только один: WHERE flg_req_truncated=1
  • Support ID - поле support_id. Просто поиск по полю WHERE support_id=<support_id>
  • Containing String - поле uri. Поиск стринга содержащегося в поле uri, WHERE uri LIKE '%<что-то>%'.
Все ключи можно менять на ваше усмотрение.

При данной структуре и при 2 лямах записях, простые запросы:

mysql> SELECT id FROM PROXY_LOG WHERE account_id='1' AND status!=0 LIMIT 100000,10;
+--------+
| id |
+--------+
| 930511 |
| 930514 |
| 930529 |
| 930533 |
| 930534 |
| 930537 |
| 930544 |
| 930551 |
| 930566 |
| 930572 |
+--------+
10 rows in set (11.13 sec)

mysql> SELECT id FROM PROXY_LOG WHERE account_id='1' LIMIT 100000,10;
+--------+
| id |
+--------+
| 328696 |
| 328698 |
| 328700 |
| 328701 |
| 328703 |
| 328704 |
| 328708 |
| 328709 |
| 328711 |
| 328712 |
+--------+
10 rows in set (2.41 sec)

mysql> SELECT id FROM PROXY_LOG WHERE account_id='1' AND 8589934592&status=8589934592 LIMIT 0,10;
+---------+
| id |
+---------+
| 2073158 |
| 2073159 |
| 2073160 |
| 2073161 |
| 2073162 |
| 2073163 |
| 2073164 |
| 2073165 |
| 2073166 |
| 2073167 |
+---------+
10 rows in set (32.58 sec)

mysql> SELECT id FROM PROXY_LOG WHERE account_id='1' AND log_time>'1144548120' LIMIT 100000,10;
+---------+
| id |
+---------+
| 1299558 |
| 1299560 |
| 1299561 |
| 1299562 |
| 1299564 |
| 1299565 |
| 1299566 |
| 1299567 |
| 1299568 |
| 1299569 |
+---------+
10 rows in set (15.87 sec)

mysql> SELECT id FROM PROXY_LOG WHERE account_id='1' AND flg_req_blocked='1' AND flg_req_truncated='1' AND src_ip='-1062703303' AND response_code=200 AND uri LIKE '%php%' AND log_time>'1144288920' AND log_time<'1144720980' AND 8589934592&status=8589934592 LIMIT 0,10;
Empty set (0.04 sec)

Задача: Этот самолет должен быстро летать. До 5 секунд считается быстро ;)

У меня есть 2 решения с ограничением по кол-ву записей, в которых нужно искать. Например, я могу дать пользователю опцию искать в последних 1000, 5000, 10000 или 50000. Больше начинает тормозить.

Есть мнения? ;)
 

zerkms

TDD infected
Команда форума
Denveroid1
а ты для интереса хоть раз делал EXPLAIN любого из представленных тобой запросов (в особенности 1, 3 и 4) ? если да - то почему у тебя не возник резонный вопрос. какой? догадайся сам ;)

ps: может всё таки при работе с integer кавычки не нужны? ;)
 

Andreika

"PHP for nubies" reader
Denveroid1
просто интересно - в этот момент (когда ты тестишь и вообще при использовании) модуль все еще собирает инфу? а базу мучает INSERTами или она в состоянии покоя находицца?
 

Denveroid1

Новичок
zerkms
То, что индескы там расставлены бестолково это я и сам знаю.
Я же сказал, что у меня есть 2 решения этой проблемы. Для этого нужен multiple index (`account_id`, `log_time`). Остальные можно выкинуть.

Andreika
Нет, я в статике это тестировал. Но модуль выполняет INSERT DELAYED...
 

zerkms

TDD infected
Команда форума
Denveroid1
ну так что тебе мешает наконец всё таки использовать этот метод решения и потом, довольный результатом, пойти пить пиво, либо недовольный - показать что сделал и что получилось
 

voituk

прозревший
А агрегироватьпользователькую статитику через какой-то период никак нельзя?
 

Denveroid1

Новичок
zerkms
Я в принципе понимаю, что я сделал все, что было в моих силах. Но я не DBA, возможно каких-то тонкостей не знаю.

voituk
Это еще один механизм, который будет работать над этой таблицей. Этого не хочется. Производительность очень важна. Там и так бежит модуль (раз в 10 минут), который чистит старые записи до определенного лимита. Сейчас это 3 миллиона записей.
 

voituk

прозревший
Denveroid1
3 миллиона записей - это совсем не много.
У меня около 200 миллионов и проблеммы такие не стоят.
Правда и статистику агрегируем мы не раз в 10 минут.

Продумай вариант вносить данные пакетами уже в анализированном по всем срезам виде.
 

MadMike

Новичок
1. Грамотные ключи, оптимизированные под каждый конкретный запрос - ни одного запроса, не юзающего ключи быть не должно, все (ну почти все) поля в where должны входить в индекс.
2. Вынеси blob'ы в отдельную таблицу.
3. Обязательно агрегируй данные.

А какой движок-то? MyISAM или InnoDB?
 

Denveroid1

Новичок
MadMike
Грамотные ключи, оптимизированные под каждый конкретный запрос - ни одного запроса, не юзающего ключи быть не должно, все (ну почти все) поля в where должны входить в индекс.
А как мне это поможет в этом случае:
WHERE status&<какой_то_код> = <какой_то_код> ?
Вынеси blob'ы в отдельную таблицу
Не могу. Скорость инсертов очень важна. Писать в разные таблицы - расточительно.
Обязательно агрегируй данные
Об этом можно подумать.
А какой движок-то? MyISAM или InnoDB?
MyISAM
 

MadMike

Новичок
Тогда делай так: берешь маленькую таблицу. Называешь ее LogDaemon. В нее льешь всю стату от демона.
Пишешь перекладывалку из маленькой таблицы в две больших - вторая с блобами.
Можно вторую таблицу держать на другом mysql-сервере, можно даже с типом Heap.

По оптимизации MyISAM кури до просветления http://dev.mysql.com/doc/refman/4.1/en/optimization.html
Там есть куча настроек кеша ключей.
 

vovanium

Новичок
ИМХО, неплохо бы также оптимизировать типы данных в таблице. Странная любовь к BIGINT, прикинь когда тебе реально понадобятся числа больше 4 млрд., т.е. если у тебя 1 млн. хитов в день, то типа INT, тебе хватит на 10 лет... И кроме того что INT занимает в 2 раза меньше места в базе, чем BIGINT, он и быстрее обрабатывается, а также уменьшится размер индексов...

Убрать изврат типа INT(2), заменить на TINYINT, а INT(4) на SMALLINT. Ну и конечно выкинуть блобы в отдельную таблицы.
 
Сверху