Является ли BigTable медленным или я тупой?

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

Каждый пользователь имеет порядка 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}

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

Ответ 1

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

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

Хорошей новостью является то, что в GAE memcaching одна хэш-таблица, содержащая все награды и их значения очков, довольно проста (хорошо, выглядела довольно просто, когда я смотрел на документы 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 не обязательно делает то, что реляционные базы данных предназначены для достижения наилучших результатов. Вместо этого он хорошо распределяет данные по многим узлам. Но почти все веб-сайты работают нормально с узким местом на одном сервере db и, следовательно, не требуют строгого внимания к тому, что делает BigTable.

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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

Ответ 4

Google BigTable запускается в распределенной файловой системе Google.

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

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

Ответ 5

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

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

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

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

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

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

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