GAE - Как жить без присоединений?
-
22-07-2019 - |
Вопрос
Пример Проблемы:
Сущности:
- Пользователь содержит имя и список друзей (ссылки на пользователя)
- Запись в блоге содержит заголовок, содержание, дату и Автора (Пользователя)
Требование:
Мне нужна страница, на которой отображается заголовок и ссылка на блог с последними 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, читал чужой код...Что я упускаю из виду?
Решение 2
Эта тема рассматривается в докладе по Google io:http://code.google.com/events/io/sessions/BuildingScalableComplexApps.html
В основном команда Google предлагает использовать свойства списка и то, что они называют объектами реляционного индекса, пример приложения можно найти здесь: http://pubsub-test.appspot.com/
Другие советы
Если вы посмотрите на то, как будет выполняться предоставленное вами SQL-решение, оно будет выглядеть в основном следующим образом:
- Извлеките список друзей текущего пользователя
- Для каждого пользователя в списке запустите индексную проверку последних сообщений
- Объединить - объедините все проверки, начиная с шага 2, остановившись, когда вы извлекли достаточное количество записей.
Вы можете самостоятельно выполнить точно такую же процедуру в App Engine, используя экземпляры запроса в качестве итераторов и выполняя над ними объединение слиянием.
Вы правы, что это не будет хорошо масштабироваться для большого количества друзей, но оно страдает от точно таких же проблем, что и реализация SQL, просто оно также не маскирует их:Выборка последних 20 (например) записей стоит примерно O (n log n) работы, где n - количество друзей.
"Загрузите пользователя, просмотрите список друзей и загрузите их последние записи в блоге".
Это все, что представляет собой соединение - вложенные циклы.Некоторые виды соединений представляют собой циклы с поисковыми запросами.Большинство поисковых запросов - это просто циклы;некоторые из них являются хэшами.
"Наконец, объедините все записи в блоге, чтобы найти последние 10 записей в блоге"
Это ЗАКАЗ с ОГРАНИЧЕНИЕМ.Это то, что база данных делает для вас.
Я не уверен, что в этом не масштабируемого;в любом случае, это то, что делает база данных.
Вот пример на 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))