Задавайте вопросы, мы ответим
Вы не зашли.
Я разрабатываю веб-сервис занимающийся анализом контента веб-сайтов в сети, для каждого сайта выделяются его ключевые слова (тэги).
В данный момент, не мудрствуя лукаво, keywords сохраняются в той же таблице, что и остальная статистика по сайту:
Таблица `www`:
id int(11) primary key, auto_increment
url varchar(64) utf8_general_ci, INDEX
...
keywords text utf8_general_ci
...
favicon char(23) utf8_general_ci
screenshots char(23) utf8_general_ci
Массив ключевых слов хранится в виде: greeting cards|5,holidays|17,labor|8,greeting|5 , где через символ "|" хранится его вес на странице. Количество ключевых слов для каждого веб-сайта ограничено 15 словами.
И по этому неиндексному полю выполняются запросы вида:
SELECT COUNT(*) AS `count` FROM `www` WHERE `keywords` LIKE '%holidays\\|%' - Запрашиваю кол-во сайтов с нужным ключевым словом и его вхождением.
SELECT `url` FROM `www` WHERE `keywords` LIKE '%holidays\\|%' - Запрашиваю сами сайты с нужным ключевым словом и его вхождением.
Вполне естественно, что в первый же месяц BETA-выхода, сервис стал загибаться. Сейчас в таблице около 20 000 записей, в сутки прибавляется около 1,5-2 тысяч. В планах - продержаться на MySQL до нескольких(5-10?) миллионов записей, ежедневный прирост более, чем на 4 тысячи сайтов планирую не допускать.
Я вижу для себя такие способы перестроения базы:
Вариант 1: Убать упоминание о keywords из основной таблицы `www` и вынести keywords в отдельную таблицу вида:
id bigint(20) primary key, auto_increment
site_id bigint(20) INDEX
keyword varchar(256) INDEX
ves tinyint(2)
Таким образом запросы примут вид:
SELECT `keyword` WHERE `site_id`='3846', т.е. выборка будет производиться по индексному полю.
При таком варианте для каждого сайта из таблицы `www` будет создаваться до 15 записей в таблице ключевых слов. Если хранение 2-х миллионов записей о веб-сайтах я могу себе представить, то 30 миллионов записей в таблице мне откровенно кажется утопией. К тому же смущает дублирование одного и того же `keyword` для нескольких `site_id`.
Вариант 2: Опять же убрать упоминания о ключевых словах из основной таблицы и создать две вспомогательных:
а) Таблица соответствия ключевых слов веб-сайту:
id bigint(20) primary key, auto_increment
site_id bigint(20) INDEX
keyword_id bigint(20) INDEX
ves tinyint(2)
б) Таблица самих ключевых слов.
id bigint(20) primary key, auto_increment
keyword varchar(256) UNIQUE
При такой структуре запросы примут такой вид:
Запрос, количества сайтов с заданным ключевым словом вначале определяет id ключевого слова в таблице "б" и затем запрашивает в таблице "а" количество записей WHERE `keyword_id`= id из таблицы "б"
Запрос выбирающий сайты по ключевому слову также вначале определяет id ключевого слова в таблице "б", затем запрашивает в таблице "а" `site_id` сайтов в которых присутствует ключевое слово и лишь после этого выбирает сайт из основной таблицы `www` по первичному ключу id.
В таком варианте мне не нравится "длинный маршрут" от ключевого слова до сайта, и пожалуй, главное, что количество записей о соответствии ключевых слов также, как и в первом варианте решения растет значительно быстрее количества сайтов.
Плюсом такого варианта можно считать то, что количество самих ключевых слов в таблице "б" удастся "сдерживать" за счет соответствия одного ключевого слова нескольким сайтам.
Посоветуйте пожалуйста верное направление. Я представляю себе, что проблема наверняка достаточно типовая и избитая для DBA больших баз, но на данный момент не нашел такого 100-процентного типового и правильного решения.
Версия сервера: 5.0.51a-24+lenny4
Расположено на VDS, ОС: Debian
Заранее благодарю за внимание)
Неактивен
У вас ключевые слова не очень длинные, поэтому разница между вариантами 1,2 небольшая (она больше, если используется utf8). Второй вариант предпочтителен, так как вы сокращаете длину записи в самой большой таблице, что позитивно влияет на кэширование. Насчет лишнего запроса беспокоиться не стоит, если в таблице 2 сделать индекс на префикс от keyword, поиск в ней будет быстрым. В качестве хранилища думаю подойдет MyISAM.
Таблица в 30 миллионов записей не утопия. См. сообщение http://webew.ru/posts/2699.webew , в котором упомянуто, что MySQL отлично справляется с таблицей в 3 миллиарда записей, каждая 11 байт длиной (в том примере поиск по первичному ключу выполняется несколько секунд).
Структуру таблицы я бы изменил. id в ней не нужен, если используется MyISAM. Также не требуется bigint, достаточно int и mediumint.
Неактивен
rgbeast, Большое спасибо! Полез делать, обязательно отпишусь о результатах.
Неактивен
Поздравляю всех участников с праздниками! Как обещался, пишу о результатах.
Воспользовался советом rgbeast, после работ база выглядит следующим образом:
Таблица keywords: хранит ключевые слова
Структура:
id int(11)
keyword varchar(256)
Индексы:
PRIMARY PRIMARY id
keyword UNIQUE keyword
Используемое пространство:
Данные 5,176 КБ
Индекс 6,790 КБ
Всего 11,966 КБ
Строки 152,301
Длина строки ø 34
Таблица site_key: хранит соответствие сайтов и ключевых слов
site_id mediumint(11)
keyword_id int(11)
ves smallint(5)
Индексы:
site_id INDEX 296070 site_id
keyword_id
keyword_id INDEX 296070 keyword_id
site_id
Используемое пространство:
Данные 2,891 КБ
Индекс 7,772 КБ
Всего 10,663 КБ
Строки 296,070
Длина строки ø 10
Размер строки ø 37 Байт
Таблица сводных данных о проанализированном сайте не хранит упоминания о ключевых словах. Соответствия "подтягиваются" из таблицы site_key.
Все таблицы в базе имеют тип MyISAM.
Время выполнения запросов при такой структуре сократилось более чем разительно. Используемые запросы ниже:
1) Выбор количества сайтов по ключевому слову:
SELECT COUNT(*) AS `count`
FROM `site_key`, `keywords`
WHERE keywords.id=site_key.keyword_id AND keywords.keyword='Videos'
2) Выбор ключевых слов для заданного сайта:
SELECT *
FROM `keywords` , `site_key`
WHERE keywords.id=site_key.keyword_id AND `site_id` = '24459'
3) Выбор списка сайтов (некоторых их параметров) по ключевому слову:
SELECT `url`, `title`, `description`, `favicon` FROM `www`, `site_key` , `keywords` WHERE
keywords.keyword='Videos' AND
keywords.id=site_key.keyword_id AND
site_key.site_id=www.id AND
www.status!='b'
GROUP BY site_key.site_id
LIMIT 0, 30
Размер таблиц сократился многократно, Время запросов, как и писал выше, - "небо и земля", ранее сайт практически "умирал".
Интересна экономия на таблице ключевых слов: 296,070 соответствий при 152,301 ключевых словах. Т.е. метод хранения ключевых слов и их соответствий в разных таблицах значительно экономнее хранения "ключевое слово - id сайта" с дублированием повторяющихся ключевых слов.
Уважаемые гуру, если увидите в структуре или запросах что-то заслуживающее оптимизации - напишите пожалуйста.
Спасибо!)
Отредактированно Михаил) (02.01.2011 16:40:03)
Неактивен
Неужели нет зацепок и не к чему придраться?)
Неактивен
Нет смысла придираться для галочки. Оптимизировать нужно тогда, когда что-то
плохо работает. Когда всё работает хорошо — оптимизировать не нужно
Особенно это актуально с первого по десятое января, правда?
Неактивен
Просто как вариант. Для таблицы ключевых слов можно попробовать определить два уровня. Т.е. добавить колонку parent_id. Будет два типа записей: основная часть слова и окончание. Допустим сейчас может храниться так:
id | keyword
-------------
1 | кресло
2 | кресла
3 | креслу
то есть эти три слова считаются разными, хотя в них различаются только последние буквы. Что предлагаю я:
id | parent_id | keyword
-------------------------
1 | 0 | кресл
2 | 1 | а
3 | 1 | о
4 | 1 | у
очевидно, что размер таблицы уменьшится (хотя количество строк увеличится). Насколько я помню подобные вещи называются "деревья поиска" или что-то вроде того.
Неактивен
Спасибо за внимание к теме!
Такую обработку окончаний однозначно лучше выполнять самим скриптом, а не хранить в базе все возможные варианты слова.
Интересно применить алгоритм, позволяющий идентифицировать основную форму слова (именит. падеж, ед. число, для глаголов - инфинитив). После определения, можно удалить неосновные формы и поменять соответствия сайтов новому слову.
Применив такой алгоритм при определении ключевых слов, можно было бы немало сэкономить на количестве хранимых ключевых слов.
Например: Выбираем по LIKE %..% : доктор, доктору, доктора, докторишка. Сокращаем до "доктор".
Неактивен
Кстати говоря есть ещё нейронные сети. Они неплохо выполняют задачу кластеризации (то есть разбиения на схожие группы)...хотя это врятли подойдёт для высоконагруженной системы на php
Неактивен