Как бы вы разработали хранилище данных AppEngine для такого социального сайта, как Twitter? - PullRequest
28 голосов
/ 27 октября 2009

Мне интересно, как лучше всего создать социальное приложение, в котором участники будут выполнять действия и следить за действиями других участников с помощью Google AppEngine.

Чтобы быть более конкретным, давайте предположим, что у нас есть следующие объекты:

  • Пользователи , у кого есть друзья
  • Действия , которые представляют действия, совершенные пользователями (допустим, у каждого есть строковое сообщение и ReferenceProperty для его владельца-пользователя, или он может использовать родительскую ассоциацию через ключ appengine)

Сложнее всего следить за действиями вашего друга, что означает объединение последних действий всех ваших друзей. Обычно это будет соединение между таблицей «Действия» и списком ваших друзей, но это не жизнеспособный дизайн в appengine, поскольку нет объединения, имитирующего его, что потребует запуска N запросов (где N - количество друзей), а затем слияния в памяти - очень дорого и, вероятно, превысит срок запроса ...)

В настоящее время я думаю о реализации этого с использованием очередей входящих сообщений, в которых при создании нового действия будет запущен фоновый процесс, который поместит ключ нового действия в «входящие» каждого следующего пользователя:

  • Получение запроса «Все пользователи, которые следуют за Х» - это возможный запрос приложения
  • Не очень дорогой пакетный ввод в новую сущность «Входящие», в которой в основном хранятся кортежи (пользователь, ключ активности).

Я буду рад услышать мысли об этом дизайне или альтернативные предложения и т. Д.

Ответы [ 4 ]

25 голосов
/ 27 октября 2009

Взгляните на Создание масштабируемых, сложных приложений на App Engine ( pdf ), захватывающий доклад, сделанный в Google I / O Бреттом Слаткиным Он решает проблему создания масштабируемой службы обмена сообщениями, такой как Twitter.

Вот его решение с использованием свойства списка:

class Message(db.Model):
    sender = db.StringProperty()
    body = db.TextProperty()

class MessageIndex(db.Model):
    #parent = a message
    receivers = db.StringListProperty()

indexes = MessageIndex.all(keys_only = True).filter('receivers = ', user_id)
keys = [k.parent() for k in indexes)
messages = db.get(keys)

Этот запрос только ключа находит индексы сообщений с получателем, равным указанному вами, без десериализации и сериализации списка получателей. Затем вы используете эти индексы, чтобы захватывать только те сообщения, которые вам нужны.

Вот неправильный способ сделать это:

class Message(db.Model):
    sender = db.StringProperty()
    receivers = db.StringListProperty()
    body = db.TextProperty()

messages = Message.all().filter('receivers =', user_id)

Это неэффективно, поскольку запросы должны распаковывать все результаты, возвращаемые вашим запросом. Таким образом, если вы вернули 100 сообщений с 1000 пользователей в каждом списке получателей, вам придется десериализовать 100 000 (100 x 1000) значений свойств списка. Слишком дорого в задержке хранилища данных и процессоре.

Поначалу я был довольно смущен всем этим, поэтому я написал краткое руководство по использованию свойства list . Наслаждайтесь:)

7 голосов
/ 27 октября 2009

Я не знаю, является ли это лучшим дизайном для социального приложения, но jaiku был перенесен в App Engine его оригинальным создателем Компания была приобретена Google, поэтому она должна быть разумной.

См. Раздел Актеры, тигры и медведи, о боже! in design_funument.txt . Объекты определены в common / models.py , а запросы - в common / api.py .

1 голос
/ 23 апреля 2014

Роберт, о вашем предложенном решении:

messages = Message.query(Message.receivers == user_id).fetch(projection=[Message.body])

Я думаю, что "тело" ndb.TextProperty нельзя использовать с проекциями, потому что оно не проиндексировано. Проекции поддерживают только индексированные свойства. Правильным решением было бы сохранить две таблицы: Message и MessageIndex.

0 голосов
/ 17 февраля 2014

Я думаю, что теперь это можно решить с помощью новых проекционных запросов в NDB.

class Message(ndb.Model):
    sender = ndb.StringProperty()
    receivers = ndb.StringProperty(repeated=True)
    body = ndb.TextProperty()

messages = Message.query(Message.receivers == user_id).fetch(projection=[Message.body])

Теперь вам не нужно иметь дело с дорогими затратами на десериализацию свойства списка.

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