BigTable медленный или я тупой? - PullRequest
26 голосов
/ 05 июня 2009

У меня есть классическая модель "многие ко многим". Пользователь, награда и таблица «многие ко многим» между пользователями и наградами.

Каждый пользователь имеет порядка 400 наград, и каждая награда присуждается примерно 1/2 пользователям.

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

В движке приложения я вижу около 10 секунд, чтобы сделать сумму. Большую часть времени проводят в хранилище данных Google. Вот первые несколько строк cProfile

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      462    6.291    0.014    6.868    0.015 {google3.apphosting.runtime._apphosting_runtime___python__apiproxy.Wait}
      913    0.148    0.000    1.437    0.002 datastore.py:524(_FromPb)
     8212    0.130    0.000    0.502    0.000 datastore_types.py:1345(FromPropertyPb)
      462    0.120    0.000    0.458    0.001 {google3.net.proto._net_proto___parse__python.MergeFromString}

Моя модель данных неверна? Я делаю поиск неправильно? Является ли это недостатком, с которым мне приходится иметь дело с кэшированием и массовым обновлением (это было бы королевской болью в заднице).

Ответы [ 5 ]

20 голосов
/ 05 июня 2009

Может быть и то и другое; -)

Если вы выполняете 400 запросов к таблице наград, по одному на каждый результат, возвращаемый для запроса в таблице сопоставления, то я ожидаю, что это будет болезненно. Ограничение на 1000 результатов для запросов существует, поскольку BigTable считает, что возвращение 1000 результатов находится на пределе его способности работать в разумные сроки. Исходя из архитектуры, я ожидаю, что 400 запросов будут работать намного медленнее, чем один запрос, возвращающий 400 результатов (400 log N против (log M) + 400).

Хорошей новостью является то, что в GAE Memcache одной хеш-таблицы, содержащей все награды и значения их очков, довольно прост (ну, выглядел довольно просто, когда я некоторое время назад просматривал документы memcache. Мне не нужно) сделать это еще).

Кроме того, если вы еще не знали, for result in query.fetch(1000) намного быстрее, чем for result in query, и вы в любом случае ограничены 1000 результатами. Преимущества последнего состоят в том, что (1) это может быть быстрее, если вы покинете досрочно, и (2) если Google когда-либо увеличит лимит свыше 1000, он получит преимущество без изменения кода.

У вас также могут возникнуть проблемы при удалении пользователя (или вознаграждения). Я обнаружил, что в одном тесте я могу удалить 300 объектов за установленный срок. Эти объекты были более сложными, чем ваши объекты отображения, и имели 3 свойства и 5 индексов (включая неявные), в то время как ваша таблица отображения, вероятно, имеет только 2 свойства и 2 (неявных) индекса. [Edit: только что понял, что я сделал этот тест, прежде чем я знал, что db.delete () может взять список, который, вероятно, намного быстрее].

BigTable не обязательно делает то, для чего хорошо подходят реляционные базы данных. Вместо этого он хорошо распределяет данные по многим узлам. Но почти все веб-сайты работают нормально с узким местом на одном сервере БД, и, следовательно, строго не нуждаются в том, что делает BigTable.

Еще одна вещь: если вы выполняете 400 запросов к хранилищу данных по одному HTTP-запросу, то вы обнаружите, что достигли фиксированной квоты хранилища данных задолго до того, как достигли фиксированной квоты запроса. Конечно, если вы находитесь в рамках квот, или если вы сначала нажали на что-то другое, это может быть неактуально для вашего приложения. Но соотношение между этими двумя квотами составляет примерно 8: 1, и я воспринимаю это как намек на то, как Google ожидает, что моя модель данных будет выглядеть.

19 голосов
/ 05 июня 2009

Моя модель данных неверна? Я делаю поиск неправильный?

Да и да, я боюсь.

Что касается вашей модели данных, то лучший способ справиться с этим - сохранить сумму в соответствии с записью пользователя и обновить ее, когда пользователь получает / теряет вознаграждение. Нет смысла считать их счет каждый раз, когда в подавляющем большинстве случаев он не изменится. Если вы сделаете объект «UserAward» типом дочернего объекта «User», вы можете обновить счет и вставить или удалить запись UserAward в одной атомарной транзакции, гарантируя, что ваш счет всегда точен.

один указывает на то, что вы можете запоминать таблицу наград. Это хорошая идея, но, учитывая ограниченный объем данных, еще лучше хранить их в локальной памяти. Глобальные члены сохраняются между HTTP-запросами, и, поскольку я предполагаю, что вы не часто обновляете таблицу наград, вам не нужно сильно беспокоиться о недействительности кэша. Просто загрузите его по первому запросу (или даже запишите в исходный код). Если вы измените список наград, развертывание нового незначительного обновления приведет к сбросу всех экземпляров, что приведет к их перезагрузке.

При поиске помните, что существенными затратами на выполнение операций с хранилищем данных является время приема-передачи. Операция get (), которая ищет 1 или более записей по идентификатору (вы можете пакетно!), Занимает около 20-40 мс. Однако запрос занимает около 160-200 мс. Следовательно, сила денормализации.

1 голос
/ 13 октября 2010

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

Эта идея хорошо продемонстрирована здесь: Создание масштабируемых сложных приложений

0 голосов
/ 01 марта 2017

Даже если вы упомянули BigTable, я думаю, что вы реализуете реляционную базу данных на облачном SQL.

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

Вы создали индексов для быстрого объединения таблиц. Это довольно просто. Вам могут понадобиться индексы BTree для всех полей, которые включают объединение таблиц. Не нужно индексировать агрегирующее поле (из которого вы берете сумму). В основном оба внешних ключа таблицы N: N должны быть проиндексированы. Если эти внешние ключи ссылаются на первичный ключ двух других таблиц, этого достаточно.

Выше порядка 100 строк простой индекс BTree для внешних ключей может иметь приличное и заметное увеличение пропускной способности.

Я использую базу данных на CloudSQL, в которой некоторые граничные таблицы содержат более 2 миллионов записей. Только после 2,5 миллионов записей я рассматриваю некоторую ненормализацию, и это также некоторые дополнительные индексы, которые все еще агрегируются для SUM. В противном случае я буду делать ненужные обновления в поле СУММА всякий раз, когда добавляются новые записи.

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

Если вы используете Django, будьте осторожны при реализации LIMIT в соответствии с их документацией; потому что это очень вводит в заблуждение. Когда вы [: 100] (склеиваете) набор записей, это не то, что вы ожидаете от SQL, который фактически отправляется на сервер SQL. Мне было очень трудно понять это. Django не очень хороший вариант, когда вы планируете сделать что-то, что появилось бы в очень большом масштабе. Но при заказе 1000 записей это было бы хорошо.

0 голосов
/ 05 июня 2009

Google BigTable работает в распределенной файловой системе Google.

Данные распространяются. Может быть, 400 строк MySQL по-прежнему лучше, но для больших данных Google BigTable может быстрее.

Я думаю, именно поэтому они побуждают нас использовать memcache, чтобы сделать его быстрее.

...