Сравнение производительности нескольких строк - PullRequest
2 голосов
/ 23 сентября 2011

У меня есть таблица исполнителей с более чем 100 000 записей, которые я использую для сравнения с массивом (от 1 до нескольких тысяч) исполнителей, представленных пользователем. Мой текущий запрос выглядит так:

SELECT id from artists WHERE lower(name) IN(downcase_artists)

Это хорошо работает, но мне интересно, можно ли сделать это быстрее. Время запроса варьируется от нескольких сотен мс до иногда целых 10 секунд, когда оно совпадает с тысячами художников. Имя столбца индексируется. (это даже имеет значение для строковых столбцов?)

Я думал, что, возможно, что-то вроде Redis ускорит это? Сохраняя ключ-хранилище имени исполнителя и его соответствующего идентификатора?

Есть ли какая-то другая опция, которая мне не хватает, которая бы ускорила это?

EDIT : как предположил Джеймс, я попытался реализовать какой-то кешированный метод all_artists (используя дополнение memcache на heroku) и использовать его для сопоставления моих строк:

artist_ids = self.all_cached.select{|a| downcase_array.include?(a.name)}.collect(&:id)

Я получил очень небольшое время запроса в БД, но общее время запроса резко увеличилось:

Before: Completed 200 OK in 1853ms (Views: 164.2ms | ActiveRecord: 1476.3ms)  
After: Completed 200 OK in 12262ms (Views: 169.2ms | ActiveRecord: 1200.6ms)

Я получаю похожие результаты, когда запускаю его локально:

Before: Completed 200 OK in 405ms (Views: 75.6ms | ActiveRecord: 135.4ms)
After: Completed 200 OK in 3205ms (Views: 245.1ms | ActiveRecord: 126.5ms)

Если оставить время ActiveRecord в стороне, похоже, что удаление строки, совпадающей с запросом, усугубило мою проблему (а это всего лишь 100 строк). Или я что-то упустил?

Я также посмотрел на полнотекстовые поисковые системы, такие как Sphinx, но они определенно звучат излишне, так как я ищу только один столбец ...

РЕДАКТИРОВАТЬ 2 : мне наконец удалось сократить время запроса до

Before: Completed 200 OK in 1853ms (Views: 164.2ms | ActiveRecord: 1476.3ms)  
Now: Completed 200 OK in 226ms (Views: 127.2ms | ActiveRecord: 48.7ms)

с использованием Redis-хранилища строк JSON ( см. Полный ответ )

Ответы [ 5 ]

2 голосов
/ 23 сентября 2011

Использование IN может быть довольно дорогостоящим, если я правильно помню. Как насчет этого:

caches_action :find_all_artists

def gather_artist_ids
  @all_artists = Artist.all(:select => "id,name)
end

тогда, где вы хотите запрос:

@downcase_artists = "Joe Schmo, Sally Sue, ..."
@requested_artists = @all_artists.select{|a| @downcase_artists.include?(a)}.collect(&:id)

Вы можете выполнить cache_action для: collect_artist_ids, и ваш уборщик может запускаться только after_create, after_update и after_destroy.

MongoDB: Я использую MongoDB через Mongoid и в нем 1,51 миллиона записей, а поиск по регулярному выражению / usersinput / i занимает менее 100 мс с индексом, где это необходимо. Это исключительно быстро.

1 голос
/ 23 сентября 2011

Поскольку вы храните имена исполнителей в нижнем регистре и ищете полное имя исполнителя, это должно работать для вас.Я изложу команды Redis, вы должны легко найти соответствующий вызов API в вашем клиенте (сначала используйте redis-cli, это прояснит ситуацию для вас).

Я предполагаю, что ваша таблица Artists имеет 3записи: «Правление Киндо», «Театр снов» и «ACT», соответствующие идентификаторы 1, 2, 3.

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


Фаза загрузки, заполнение отсортированного набора всеми исполнителями (обратите внимание настрочные буквы):

ZADD artists 1 "the reign of kindo"
ZADD artists 2 "dream theater"
ZADD artists 3 "a.c.t"

Теперь я буду запрашивать Redis для первых двух полос.Идея состоит в том, чтобы создать на этот раз временный отсортированный набор (query:10), который будет пересекаться с отсортированным набором artists.

Почему бы просто не использовать query какключ?Я присваиваю каждому запросу (произвольно) id, чтобы не возникало коллизий между одновременными поисками пользователей!Кроме того, мы можем обратиться к id позже при кэшировании набора результатов за период (подробнее об этом ниже).

Рекомендуется использовать : в качестве разделителясоглашение (смотрите здесь ).


Фаза запроса, заполняющая отсортированный запрос.

ZADD query:10 0 "the reign of kindo"
ZADD query:10 0 "dream theater"
ZINTERSTORE result_query:10 2 artists query:10 WEIGHTS 1 0
EXPIRE result_query:10 600

Оценка для отсортированного запросане имеет значения, поэтому может быть 0.

С ZINTERSTORE мы храним в result_query:10 пересечение 2 ключей, artists и query:10.Но есть подвох!Как оценки из обоих ключей объединяются в окончательный отсортированный набор?

Ответ: Redis по умолчанию суммы их.

Теперь мы можем использовать атрибут WEIGHTS для ноль баллы, которые мы не хотим.Так что WEIGHTS 1 0 говорит, что будет суммироваться только счет за artists.

Теперь у нас есть подходящие исполнители в result_query:10, что EXPIRE делает его длиться 10 минут.Вы можете найти умный способ использовать этот кэш =)


Получение набора результатов

Таким образом, выполнив все вышеперечисленное, вы можете получить желаемый результат с помощью ZRANGE:

redis> zrange result_query:10 0 -1 withscores
1) "the reign of kindo"
2) "1"
3) "dream theater"
4) "2"

Интервал 0 -1 означает получить всех членов , а атрибут withscores дает ZRANGE возвращает идентификаторы (оценки) каждогочлен вместе со своими строками.

Надеюсь, что все имеет смысл.Это только верхушка айсберга для Редиса.Хороший бенчмаркинг и до встречи!

0 голосов
/ 24 сентября 2011

Я использовал Redis для хранения не только идентификаторов артистов и имен, но и всего ответа json, который я возвращаю пользователю. Мой хэш Redis выглядит так:

{"all_artists" => ["artistname1" => "json_response1", "artistname2" => "json_response2"...]}

Я выполняю сопоставление, используя следующее ( redis-rb gem ):

REDIS.hmget("all_artists", *downcase_array)

Возвращает все строки json (включая идентификатор исполнителя, имя и предстоящие концерты) для соответствующих исполнителей, даже не нажимая на db. Очевидно, я обновляю хэш Redis каждый раз, когда обновляются артисты или концерты.

И полученная разница во времени (для 100 художников):

Before: Completed 200 OK in 1853ms (Views: 164.2ms | ActiveRecord: 1476.3ms)  
Now: Completed 200 OK in 226ms (Views: 127.2ms | ActiveRecord: 48.7ms)

Еще предстоит выполнить некоторую оптимизацию, но теперь сопоставление строк определенно не в порядке.

0 голосов
/ 23 сентября 2011

Удалить функцию «нижний (..)» из запроса.

0 голосов
/ 23 сентября 2011

Я бы рассмотрел систему полнотекстового поиска (Sphinx, Ferret, Lucene и т. Д.), Некоторые из которых в конечном итоге дают вам более интересные возможности поиска. Если вы не будете всегда просто захотите выполнить поиск по имени исполнителя и т. Д.

Тогда я бы подумал о том, чтобы просто оставить кусочек памяти доступным для кеширования perma-имен и использовать его вместо БД.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...