2009-10-27 2 views
27

Me pregunto cuál sería la mejor manera de diseñar una aplicación social donde los miembros realicen actividades y sigan las actividades de otros miembros usando Google AppEngine.¿Cómo diseñarías un almacén de datos de AppEngine para un sitio social como Twitter?

Para ser más específicos Asumamos que tenemos estas entidades:

  • Usuarios que tienen amigos
  • Actividades que representan las acciones realizadas por los usuarios (digamos que cada uno tiene un mensaje de cadena y una ReferenceProperty a su usuario propietario, o puede usar la asociación de padres a través de la clave de appengine)

Lo difícil es seguir las actividades de su amigo, lo que significa agregar las últimas actividades de todos tus amigos. Normalmente, eso sería una unión entre la tabla de Actividades y su lista de amigos pero no es un diseño viable en appengine ya que no hay unión simulando que requerirá activar N consultas (donde N es el número de amigos) y luego fusionarse en la memoria - muy caro y probablemente excederá el plazo de solicitud ...)

Actualmente estoy pensando en implementar esto usando colas de la bandeja de entrada donde la creación de una nueva actividad iniciará un proceso en segundo plano que colocará la clave de la nueva actividad en la bandeja de entrada "de cada usuario siguientes:

  • conseguir 'Todos los usuarios que siguen X' es una posible consulta appengine
  • No es una entrada por lotes muy costosa en una nueva entidad "Bandeja de entrada" que básicamente almacena tuplas (usuario, clave de actividad).

voy a estar feliz de pensamiento oído sobre este diseño o sugerencias alternativas, etc.

+1

Estaba viendo el mismo problema y encontré esta excelente (!) Presentación de la App Engine, que dieron en Google I/O: http://www.scribd.com/doc/16952419/Building-scalable-complex -apps-on-App-Engine Espero que también lo encuentres útil. –

Respuesta

24

Tome un vistazo a Building Scalable, Complex Apps on App Engine (pdf), una charla fascinante dada en Google I/O por Brett Slatkin. Él aborda el problema de construir un servicio de mensajería escalable como Twitter.

Aquí es su solución usando una propiedad de lista:

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) 

Esto sólo consulta llave encuentra los índices de mensajes con un receptor igual a la especificada y sin deserializar y la serialización de la lista de receptores. Luego, usa estos índices solo para captar los mensajes que desea.

Aquí es el camino equivocado hacerlo:

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

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

Esto es ineficiente porque las consultas tienen que desempaquetar todos los resultados devueltos por la consulta. Entonces, si devolviera 100 mensajes con 1,000 usuarios en cada lista de receptores, tendría que deserializar valores de propiedad de lista de 100,000 (100 x 1000). Demasiado caro en la latencia del almacén de datos y la CPU.

Estaba muy confundido por todo esto al principio, así que escribí un short tutorial about using the list property.Enjoy :)

+0

Exactamente mi diseño inicial. Pero lo que entendí de esa charla y de la documentación de App Engine es que las listas son bastante inútiles cuando se trata de consultas IN. La consulta que mencionó generará varias consultas en el sistema de google, cada uno de los cuales se filtrará por uno de los valores en las propiedades de la lista y luego combinará el resultado. Google cierra este tipo de consulta a 30 consultas simultáneas, lo que significa que solo se puede usar para la lista que contendrá un número relativamente pequeño de claves (<30). Cuando se trata de amigos, esta lista podría contener decenas si no cientos (¿o miles?) De claves para las personas que estás siguiendo. –

+0

por cierto, le hice la misma pregunta con respecto a las listas en otra pregunta de StackOverflow que publicó :) –

+0

No creo que sea correcto. Brett dice que está limitado a 5000 propiedades indexadas por entidad cuando habla sobre el rendimiento de la propiedad de la lista (ver 14:15 en el video). Creo que debería poder tener miles de usuarios en un receptor StringListProperty, al tiempo que se puede realizar una consulta eficiente. No estoy seguro de qué significa la frase "Una sola consulta que contiene! = O operadores IN está limitada a 30 subconsultas", pero estoy seguro de que no afecta lo que quiere hacer aquí. – wings

7

No sé si es el mejor diseño para una aplicación social, pero era jaikuported to App Engine por su creador original cuando la compañía fue adquirida por Google, por lo que debe ser razonable.

Ver la sección Actors and Tigers and Bears, Oh My! en design_funument.txt. Las entidades se definen en common/models.py y las consultas están en common/api.py.

+0

¡Muchas gracias! ese código es una gran referencia ... –

0

Creo que esto ahora se puede resolver con las nuevas Consultas de proyección en 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]) 

Ahora usted no tiene que lidiar con el alto costo de deserializar la propiedad lista.

0

Robert, sobre su propuesta de solución:

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

creo que el "cuerpo" ndb.TextProperty no se puede utilizar con las proyecciones porque no está indexado. Las proyecciones solo admiten propiedades indexadas. La solución válida sería mantener las 2 tablas: Message y MessageIndex.

Cuestiones relacionadas