GAE - Как жить без объединений?
Пример задачи:
Объекты:
- Пользователь содержит имя и список друзей (ссылки пользователей)
- Сообщение в блоге содержит заголовок, содержание, дату и Writer (Пользователь)
Требования:
Мне нужна страница, отображающая заголовок и ссылку на блог последних 10 сообщений пользователем. Мне также хотелось бы сохранить пейджинг обратно через старые записи.
Решение SQL:
Итак, в sql-стране это будет что-то вроде:
select * from blog_post where user_id in (select friend_id from user_friend where user_id = :userId) order by date
Решения GAE, о которых я могу думать, следующие:
- Загрузите пользователя, просмотрите список друзей и загрузите их последние сообщения в блоге. Наконец, объедините все сообщения в блоге, чтобы найти последние 10 записей в блоге.
- В блоге есть список всех пользователей, у которых есть автор в качестве друга. Это будет означать простое чтение, но приведет к перегрузке квоты при добавлении друга, у которого много сообщений в блоге.
Я не считаю, что одно из этих решений будет масштабироваться.
Я уверен, что другие ударили по этой проблеме, но я искал, смотрел видео google io, читал другой код... Что мне не хватает?
Ответы
Ответ 1
Этот раздел посвящен обсуждению в Google io:
http://code.google.com/events/io/sessions/BuildingScalableComplexApps.html
В принципе команда Google предлагает использовать свойства списка и то, что они называют реляционными объектами индекса, пример приложения можно найти здесь: http://pubsub-test.appspot.com/
Ответ 2
Если вы посмотрите, как будет выполняться предоставленное вами SQL-решение, оно будет выглядеть следующим образом:
- Получить список друзей для текущего пользователя
- Для каждого пользователя в списке запустите сканирование индекса по последним сообщениям
- Объединить все проверки с шага 2, останавливаясь, когда вы получили достаточно записей.
Вы можете выполнить точно такую же процедуру самостоятельно в App Engine, используя экземпляры Query в качестве итераторов и объединив их слиянием.
Вы правы, что это не будет хорошо масштабироваться для большого количества друзей, но оно страдает от тех же проблем, которые имеет реализация SQL, но это просто не маскирует их: выборка последних 20 (например) записи стоят примерно O (n log n), где n - количество друзей.
Ответ 3
"Загрузите пользователя, просмотрите список друзей и загрузите их последние сообщения в блоге".
Что все соединения - это вложенные циклы. Некоторые виды объединений - это петли с поиском. Большинство поисков - это всего лишь петли; некоторые из них - хеши.
"Наконец, объедините все сообщения в блоге, чтобы найти последние 10 записей в блоге"
Это ORDER BY с LIMIT. Это то, что делает база данных для вас.
Я не уверен, что не масштабируется по этому поводу; это то, что делает база данных.
Ответ 4
Вот пример в python, блестевший от http://pubsub-test.appspot.com/:
У кого-нибудь есть java? Спасибо.
from google.appengine.ext import webapp
from google.appengine.ext import db
class Message(db.Model):
body = db.TextProperty(required=True)
sender = db.StringProperty(required=True)
receiver_id = db.ListProperty(int)
class SlimMessage(db.Model):
body = db.TextProperty(required=True)
sender = db.StringProperty(required=True)
class MessageIndex(db.Model):
receiver_id = db.ListProperty(int)
class MainHandler(webapp.RequestHandler):
def get(self):
receiver_id = int(self.request.get('receiver_id', '1'))
key_only = self.request.get('key_only').lower() == 'on'
if receiver_id:
if key_only:
keys = db.GqlQuery(
'SELECT __key__ FROM MessageIndex WHERE receiver_id = :1',
receiver_id).fetch(10)
messages.extend(db.get([k.parent() for k in keys]))
else:
messages.extend(Message.gql('WHERE receiver_id = :1',
receiver_id).fetch(10))