Запросы с группировкой, обсчитывающие по несколько млн. записей

Sukebe
Дата: 15.01.2013 16:32:39
Ситуация такая - есть лог freeRADIUS'а (таблица radacct), в который за день падает сотня-другая тысяч записей.
MySQL крутится на виртуалке.

Возникла задача выводить статистику по этим данным с несколькими вариантами группировки.

Подскажите - это реально делать при такой конфигурации в реалтайме, подправив индексы/запросы/конфиг, или нужно придумывать что-то "более другое" (таблицы с агрегированными данными, например)?

Пример запроса
+ SELECT
SELECT 
    `radacct`.`operator_id`, 
    `radacct`.`acctterminatecause`, 
    COUNT(radacctid) AS `count`, 
    AVG(acctsessiontime) AS `avgDuration`, 
    SUM(acctsessiontime) AS `totalDuration` 
FROM `radacct` 
WHERE 
    acctstoptime BETWEEN '2013-01-11 00:00:00' AND acctstoptime <= '2013-01-11 23:59:59'
GROUP BY `operator_id`, `acctterminatecause`;


EXPLAIN
idselect_typetabletypepossible_keyskeykey_lenrefrowsfilteredExtra
1SIMPLEradacctrangeacctstoptimeacctstoptime9NULL402876100.00Using where; Using temporary; Using filesort


Результат
+ Результат выполнения (время - 3 мин 12 с)
operator_idacctterminatecausecountavgDurationtotalDuration
1Admin-Reset1111542483.8333472207807
1Lost-Carrier1109742438.4679470939678
1NAS-Error10955 42275.9085 463132578
1Session Cleanup11002 42228.3819464596658
1Session-Timeout1120442805.1034 479588378
1User-Request1107942460.5601 470420545
2Admin-Reset1133341758.9411473254079
2Lost-Carrier1134842173.0262478579501
2NAS-Error1116941995.9020469052229
2Session Cleanup1127542018.3899473757346
2Session-Timeout1129241718.7529471088158
2User-Request1127042379.4449477616344
3Admin-Reset1118542324.2472473396705
3Lost-Carrier11098 42202.8286 468366992
3NAS-Error1118542042.1582470241539
3Session Cleanup11271 41813.4908471279855
3Session-Timeout1120742286.8521473908752
3User-Request1112442354.9024471155934


Структура таблицы и индексы
+ CREATE TABLE
CREATE TABLE `radacct` (
  `radacctid` bigint(21) NOT NULL AUTO_INCREMENT,
  `acctsessionid` varchar(64) NOT NULL,
  `acctuniqueid` varchar(32) NOT NULL,
  `username` varchar(64) NOT NULL,
  `operator_id` int(11) NOT NULL,
  `order_num` varchar(32) NOT NULL,
  `groupname` varchar(64) NOT NULL,
  `realm` varchar(64) DEFAULT '',
  `nasipaddress` varchar(15) NOT NULL,
  `nasportid` varchar(15) DEFAULT NULL,
  `nasporttype` varchar(32) DEFAULT NULL,
  `acctstarttime` datetime DEFAULT NULL,
  `acctstoptime` datetime DEFAULT NULL,
  `acctsessiontime` int(12) DEFAULT NULL,
  `acctauthentic` varchar(32) DEFAULT NULL,
  `connectinfo_start` varchar(50) DEFAULT NULL,
  `connectinfo_stop` varchar(50) DEFAULT NULL,
  `acctinputoctets` bigint(20) DEFAULT NULL,
  `acctinputpackets` bigint(20) DEFAULT NULL,
  `acctoutputoctets` bigint(20) DEFAULT NULL,
  `acctoutputpackets` bigint(20) DEFAULT NULL,
  `calledstationid` varchar(50) NOT NULL,
  `callingstationid` varchar(50) NOT NULL,
  `acctterminatecause` varchar(32) NOT NULL,
  `servicetype` varchar(32) DEFAULT NULL,
  `framedprotocol` varchar(32) DEFAULT NULL,
  `framedipaddress` varchar(15) NOT NULL,
  `acctstartdelay` int(12) DEFAULT NULL,
  `acctstopdelay` int(12) DEFAULT NULL,
  `xascendsessionsvrkey` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`radacctid`),
  KEY `username` (`username`),
  KEY `framedipaddress` (`framedipaddress`),
  KEY `acctsessionid` (`acctsessionid`),
  KEY `acctsessiontime` (`acctsessiontime`),
  KEY `acctuniqueid` (`acctuniqueid`),
  KEY `acctstarttime` (`acctstarttime`),
  KEY `acctstoptime` (`acctstoptime`),
  KEY `nasipaddress` (`nasipaddress`),
  KEY `conn_id` (`operator_id`),
  KEY `terminate_op` (`acctterminatecause`,`operator_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16532422 DEFAULT CHARSET=utf8


Объем данных
Количество данных - ~100 000 - 200 000 записей в сутки, сейчас в БД 16 млн. записей.
Уникальных значений поля username - 100 000 (данные тестировочные, на продакшене будет в 2-3 раза меньше). На каждый username в сутки приходится как минимум 1 запись.

Немного информации по серверу:
+ mysqltuner.pl
-------- General Statistics --------------------------------------------------
[--] Skipped version check for MySQLTuner script
[OK] Currently running supported MySQL version 5.1.63-0+squeeze1-log
[OK] Operating on 32-bit architecture with less than 2GB RAM

-------- Storage Engine Statistics -------------------------------------------
[--] Status: +Archive -BDB -Federated +InnoDB -ISAM -NDBCluster
[--] Data in MyISAM tables: 1G (Tables: 76)
[--] Data in InnoDB tables: 11G (Tables: 63)
[!!] Total fragmented tables: 64

-------- Performance Metrics -------------------------------------------------
[--] Up for: 119d 19h 10m 28s (35M q [3.444 qps], 69K conn, TX: 2B, RX: 26B)
[--] Reads / Writes: 4% / 96%
[--] Total buffers: 680.0M global + 2.7M per thread (151 max threads)
[OK] Maximum possible memory usage: 1.1G (53% of installed RAM)
[OK] Slow queries: 0% (3K/35M)
[OK] Highest usage of available connections: 11% (17/151)
[OK] Key buffer size / total MyISAM indexes: 16.0M/1.2G
[OK] Key buffer hit rate: 100.0% (21M cached / 97 reads)
[!!] Query cache efficiency: 6.6% (101K cached / 1M selects)
[OK] Query cache prunes per day: 0
[OK] Sorts requiring temporary tables: 0% (38 temp sorts / 1M sorts)
[!!] Joins performed without indexes: 34585
[OK] Temporary tables created on disk: 12% (7K on disk / 56K total)
[OK] Thread cache hit rate: 99% (19 created / 69K connections)
[!!] Table cache hit rate: 1% (63 open / 5K opened)
[OK] Open file limit used: 4% (44/1K)
[OK] Table locks acquired immediately: 99% (34M immediate / 34M locks)
[!!] InnoDB data size / buffer pool: 12.0G/500.0M


Фрагмент my.cnf
+ my.cnf
key_buffer		= 16M
max_allowed_packet	= 16M
thread_stack		= 192K
thread_cache_size       = 8

myisam-recover         = BACKUP

query_cache_limit	= 1M
query_cache_size        = 16M

ignore_builtin_innodb
plugin-load=innodb=ha_innodb_plugin.so;innodb_trx=ha_innodb_plugin.so;innodb_locks=ha_innodb_plugin.so;innodb_lock_waits=ha_innodb_plugin.so;innodb_cmp=ha_innodb_plugin.so;innodb_cmp_reset=ha_innodb_plugin.so;innodb_cmpmem=ha_innodb_plugin.so;innodb_cmpmem_reset=ha_innodb_plugin.so

innodb_flush_method=O_DIRECT

innodb_buffer_pool_size=500M
innodb_additional_mem_pool_size=100M

innodb_log_file_size=12M
innodb_log_buffer_size=32M

[isamchk]
key_buffer		= 16M



Заранее спасибо :)
ScareCrow
Дата: 15.01.2013 16:38:44
сделать индекс
acctstoptime `operator_id`, `acctterminatecause`;
попробовать поиграть с порядком полей в нем.
ScareCrow
Дата: 15.01.2013 16:39:41
а вообще сделать OLAP куб и не парится.
Akina
Дата: 15.01.2013 17:20:03
    COUNT(radacctid) AS `count`, 
    AVG(acctsessiontime) AS `avgDuration`, 
    SUM(acctsessiontime) AS `totalDuration` 

Вот в этом фрагменте - нет ли избыточности подсчёта? попробуйте count и sum посчитать, оформив код как подзапрос, а среднее - посчитать во внешнем запросе... есть определённая вероятность, что время запроса изменится.

Sukebe
Возникла задача выводить статистику по этим данным с несколькими вариантами группировки.

Что разумеется под этими словами? В разных разрезах (по разным полям)? или группировка всегда по (operator_id, acctterminatecause), а меняется в запросе что-то ещё?

WHERE 
    acctstoptime BETWEEN '2013-01-11 00:00:00' AND acctstoptime <= '2013-01-11 23:59:59'

Не обращая внимания на опечатку (кстати, два неравенства возможно разумне between) - отбор всегда посуточный? если так - почему не предрасчитать (и затолкать в индекс) чисто дату?.

В общем, пока не то чтобы очень полные данные по вопросу...
ScareCrow
Дата: 15.01.2013 17:36:33
автор
оформив код как подзапрос, а среднее - посчитать во внешнем запросе... есть определённая вероятность, что время запроса изменится.

в большую сторону?
Akina
Дата: 15.01.2013 18:39:49
ScareCrow
в большую сторону?
Думаю, в меньшую.
Счас проверил у себя на таблице статистики (1.4 млн. записей, по 10 запусков каждого запроса):
select sql_no_cache source, count(sent), sum(sent), avg(sent) from test group by source

5.125-5.271 с
select sql_no_cache x.source, x.cnt, x.sm, x.sm/x.cnt av from (
select source, count(sent) cnt, sum(sent) sm from test group by source
) x

4.303-4.472 с
javajdbc
Дата: 15.01.2013 19:27:50
ScareCrow
а вообще сделать OLAP куб и не парится.


Идея хорошая. Какими средствами и в какой
среде вы советуете игратся в кубики ?

(ПОнятно что вариантив много, мне интересен ваш опыт --
у меня похожие задачи начинают вырисовыватся)
javajdbc
Дата: 15.01.2013 19:46:53
автор
Возникла задача выводить статистику по этим данным с несколькими вариантами группировки.

Подскажите - это реально делать при такой конфигурации в реалтайме, подправив индексы/запросы/конфиг, или нужно придумывать что-то "более другое" (таблицы с агрегированными данными, например)?


вопшемто и вы сами и предыдушие советчики перечислили все
основные способы. Я добавлю, что вы можете взять несколько
направлений работы и в какой-то момент должны решить
что вам лучше получается, важнее, выгоднее, быстрее

-- индексы -- надо делать, выгоды полно.
недостатки -- незначительное замедление вставок
и занимает место на диске (если это проблема)

-- индексы отрабатывайте сначала на простых вариантах , например

+
SELECT
`radacct`.`operator_id`,
`radacct`.`acctterminatecause`,
COUNT(radacctid) AS `count`,
AVG(acctsessiontime) AS `avgDuration`,
SUM(acctsessiontime) AS `totalDuration`
FROM `radacct`
WHERE
acctstoptime BETWEEN '2013-01-11 00:00:00'
AND acctstoptime <= '2013-01-11 23:59:59'
and operator_id = 12345 -- реальный номер
and acctterminatecause = 'abs' -- реальное значение


индексы могут ускорить группировки, однако мы
имеем полный перебор В ЛЮБОМ случае.

-- преагрегация -- это направление очень мощное и
сильно советую.
После одиночного прогона -- создания преагрегатной таблицы
за, скажем 5-10 минут, ваши запросы будут
возврашатся за 1-100 милисекунд
Вы сможете быстро строить графики, всякие репорты по дням/месяцам, и т д

Проблема -- преагрегацию надо поддерживать --
допустим раз в день. или раз в час. Если бизнесс требует
реал-тайм результаты -- придется вернутся к реал-тайм запросам.

Это направление сильное и довольно дешевое в сравнении с

-- ОЛАП -- тут все серьезнее, надо знать средства и методики.
займет много времени с нуля. Советую если есть много времени,
денег и нужно очень много результатов
javajdbc
Дата: 15.01.2013 20:02:27
Sukebe,

насчет конфигурации -- можно уменьшить
количество мах_конекшн, увеличить
ИнноДБ пул... у вас еше гиг в запасе --
если никто на этой машине не бегает, можно
болше отдать базе....

Up for: 119d 19h 10m 28s -- неплохо ! :-)

Учитывая что 4 месяца все стабильно, сильно
не меняйте....
saturnxxi
Дата: 15.01.2013 20:45:59
Sukebe,

как уже сказали выше, если INNODB, то повысить пул. Чем больше, тем лучше. Оперативки конечно маловато, для таких объемов данных. Таблицу секционирвать по времени и создать подсекции по ай ди оператора. Проверить есть ли прунинг. Возрастет как скорость вставки так и выборки.