¿Cuál es la mejor manera de implementar un flujo de actividad social? [cerrado]

265

Estoy interesado en escuchar sus opiniones sobre cuál es la mejor manera de implementar un flujo de actividad social (Facebook es el ejemplo más famoso). Los problemas / desafíos involucrados son:

  • Diferentes tipos de actividades (publicar, comentar ...)
  • Diferentes tipos de objetos (post, comentario, foto ...)
  • 1-n usuarios involucrados en diferentes roles ("Usuario x respondió al comentario del Usuario y en la publicación Z del Usuario")
  • Diferentes vistas del mismo elemento de actividad ("usted comentó .." vs. "su amigo x comentó" vs. "usuario x comentó .." => 3 representaciones de una actividad de "comentario")

.. y algo más, especialmente si lo lleva a un alto nivel de sofisticación, como lo hace Facebook, por ejemplo, combinando varios elementos de actividad en uno ("los usuarios x, y y z comentaron esa foto"

Cualquier comentario o sugerencia sobre patrones, documentos, etc. sobre los enfoques más flexibles, eficientes y potentes para implementar dicho sistema, modelo de datos, etc. sería apreciado.

Aunque la mayoría de los problemas son independientes de la plataforma, es probable que termine implementando dicho sistema en Ruby on Rails

Jon Seigel
fuente

Respuestas:

143

He creado dicho sistema y adopté este enfoque:

Tabla de base de datos con las siguientes columnas: id, userId, type, data, time.

  • userId es el usuario que generó la actividad
  • type es el tipo de actividad (es decir, escribió una publicación en el blog, agregó una foto, comentó la foto del usuario)
  • Los datos son un objeto serializado con metadatos para la actividad en la que puedes poner lo que quieras

Esto limita las búsquedas / búsquedas, que puede hacer en las fuentes, a los usuarios, los tipos de tiempo y actividad, pero en una fuente de actividad de tipo Facebook, esto no es realmente limitante. Y con los índices correctos en la tabla, las búsquedas son rápidas .

Con este diseño, tendría que decidir qué metadatos debería requerir cada tipo de evento. Por ejemplo, una actividad de feed para una nueva foto podría verse así:

{id:1, userId:1, type:PHOTO, time:2008-10-15 12:00:00, data:{photoId:2089, photoName:A trip to the beach}}

Puede ver que, aunque el nombre de la foto ciertamente está almacenado en otra tabla que contiene las fotos, y podría recuperar el nombre desde allí, duplicaré el nombre en el campo de metadatos, porque no desea hacerlo cualquier se une a otras tablas de la base de datos si desea velocidad. Y para mostrar, digamos 200, diferentes eventos de 50 usuarios diferentes, necesita velocidad.

Luego tengo clases que amplían una clase básica de FeedActivity para representar los diferentes tipos de entradas de actividad. La agrupación de eventos también se construiría en el código de representación, para evitar la complejidad de la base de datos.

Hey hombre
fuente
3
Sí, eso es correcto. Últimamente he estado usando MongoDB ( mongodb.org ) en algunos proyectos, cuyo enfoque sin esquemas lo hace muy adecuado para crear un flujo de actividad social que funcione bien y siga este diseño.
Hey
66
TheApprentice: Sí, es posible que desee agregar también un campo de nombre de usuario. En nuestro sistema, solo mostramos los eventos generados por los amigos de un usuario, y creo que ya teníamos un mapa del nombre de usuario-> nombre de usuario de los amigos en la memoria, por lo que buscar los nombres de usuario no requería UNIRSE y fueron rápidos.
heyman
2
Tendría que manejar ese caso manualmente. Probablemente sea mejor hacerlo cuando se elimine la foto (busque el elemento de fuente en la fuente del usuario y elimínelo / actualícelo).
Hey
21
No entiendo qué hay de bueno en esta respuesta. ¿Cómo se traduce la creación de una tabla simple en una fuente de actividad ponderada similar a Facebook? Todo lo que está haciendo es almacenar toda la actividad. ¿Qué aún deja la pregunta de cómo convertir una tabla de datos en una fuente de actividad dinámica ponderada?
ChuckKelly
44
@ChuckKelly: si recuerdo correctamente, en 2008, cuando escribí la respuesta, el feed de Facebook no estaba ponderado en absoluto. Fue solo una fuente cronológica con toda la actividad de tus amigos.
Hey
117

Esta es una muy buena presentación que describe cómo Etsy.com diseñó sus flujos de actividad. Es el mejor ejemplo que he encontrado sobre el tema, aunque no se trata de rieles específicos.

http://www.slideshare.net/danmckinley/etsy-activity-feeds-architecture

Mark Kennedy
fuente
21
^^ Porque tienes que volver a SO después de visitar el sitio. lol
Stephen Corwin
1
Gran presentación que explica en detalle cómo funciona el sistema en un sitio web real de alto tráfico.
ramirami
44

Hemos abierto nuestro enfoque: https://github.com/tschellenbach/Stream-Framework Actualmente es la biblioteca de código abierto más grande destinada a resolver este problema.

El mismo equipo que creó Stream Framework también ofrece una API alojada, que maneja la complejidad por usted. Eche un vistazo a getstream.io Hay clientes disponibles para Node, Python, Rails y PHP.

Además, eche un vistazo a esta publicación de alta escalabilidad donde explicamos algunas de las decisiones de diseño involucradas: http://highscalability.com/blog/2013/10/28/design-decisions-for-scaling-your-high-traffic- feeds.html

Este tutorial lo ayudará a configurar un sistema como el feed de Pinterest con Redis. Comenzar es bastante fácil.

Para obtener más información sobre el diseño de feed, recomiendo leer algunos de los artículos en los que basamos Feedly:

Aunque Stream Framework está basado en Python, no sería demasiado difícil de usar desde una aplicación Ruby. Simplemente puede ejecutarlo como un servicio y pegar una pequeña API http delante de él. Estamos considerando agregar una API para acceder a Feedly desde otros idiomas. Sin embargo, por el momento tendrás que interpretar el tuyo.

Thierry
fuente
19

Los mayores problemas con las transmisiones de eventos son la visibilidad y el rendimiento; debe restringir los eventos que se muestran para que sean solo los interesantes para ese usuario en particular, y debe mantener la cantidad de tiempo necesario para clasificar e identificar esos eventos manejables. He construido una red social pequeña; Descubrí que a pequeñas escalas, mantener una tabla de "eventos" en una base de datos funciona, pero que puede ser un problema de rendimiento bajo una carga moderada.

Con un flujo mayor de mensajes y usuarios, probablemente sea mejor ir con un sistema de mensajería, donde los eventos se envían como mensajes a perfiles individuales. Esto significa que no puede suscribirse fácilmente a las secuencias de eventos de las personas y ver eventos anteriores con mucha facilidad, pero simplemente está representando un pequeño grupo de mensajes cuando necesita representar la secuencia para un usuario en particular.

Creo que este fue el defecto de diseño original de Twitter. Recuerdo haber leído que estaban golpeando la base de datos para ingresar y filtrar sus eventos. Esto tenía todo que ver con la arquitectura y nada que ver con Rails, que (desafortunadamente) dio a luz al meme "rubí no escala". Hace poco vi una presentación en la que el desarrollador utilizó el servicio Simple Queue de Amazon como su backend de mensajería para una aplicación similar a Twitter que tendría capacidades de escalamiento mucho más altas; puede valer la pena mirar a SQS como parte de su sistema, si sus cargas son lo suficientemente altas .

Tim Howland
fuente
Tim, ¿recuerdas por casualidad el nombre de la presentación o del presentador?
Danita
fue en la presentación Ignite Boston de Oreilly and Associate, ya sea en el número 3 o 4, creo que el presentador tenía un libro sobre escalar RoR con Oreilly. Lo siento, no puedo ser más específico!
Tim Howland
Gracias Tim :) Por cierto, ¿qué quisiste decir con "pequeña red social"? ¿Cuántos usuarios o usuarios activos en un momento determinado?
Danita
3
En caso de que alguien lo necesite, creo que esta es la presentación de la que habla Tim: "Dan Chak - Escalando al tamaño de sus problemas" radar.oreilly.com/2008/09/ignite-boston-4----videos -uplo.html
Danita
Pequeño en este caso es tal que "seleccionar * de los eventos donde event.is es visible para este usuario" devuelve un resultado en menos de una segunda o dos cifras de unos cientos de miles de filas de eventos.
Tim Howland
12

Si está dispuesto a usar un software separado, le sugiero el servidor Graphity que resuelve exactamente el problema de los flujos de actividad (construyendo sobre la base de datos del gráfico neo4j).

Los algoritmos se han implementado como un servidor REST independiente para que pueda alojar su propio servidor para entregar flujos de actividad: http://www.rene-pickhardt.de/graphity-server-for-social-activity-streams-released-gplv3 / /

En el documento y en el punto de referencia, mostré que la recuperación de flujos de noticias depende solo linealmente de la cantidad de elementos que desea recuperar sin ninguna redundancia que obtendría al desnormalizar los datos:

http://www.rene-pickhardt.de/graphity-an-efficient-graph-model-for-retrieving-the-top-k-news-feeds-for-users-in-social-networks/

En el enlace de arriba encontrará capturas de pantalla y un punto de referencia de este enfoque (que muestra que Graphity puede recuperar más de 10k transmisiones por segundo).

Rene Pickhardt
fuente
10

Empecé a implementar un sistema como este ayer, aquí es donde tengo que ...

Creé una clase StreamEvent con las propiedades Id , ActorId , TypeId , Date , ObjectId y una tabla hash de pares de clave / valor Detalles adicionales . Esto se representa en la base de datos por un StreamEvent mesa ( Id , actorId , TypeId , Fecha , OBJECTID ) y un StreamEventDetails mesa ( StreamEventId , DetailKey , DetailValue ).

El ActorId , TypeId y ObjectId permiten capturar un evento sujeto-verbo-objeto (y luego consultarlo). Cada acción puede dar lugar a la creación de varias instancias de StreamEvent.

Luego he creado una subclase para StreamEvent para cada tipo de evento, por ejemplo , LoginEvent , PictureCommentEvent . Cada una de estas subclases tiene propiedades más específicas de contexto, como PictureId , ThumbNail , CommenText , etc. (lo que sea necesario para el evento) que en realidad se almacenan como pares clave / valor en la tabla hashtable / StreamEventDetail.

Al retirar estos eventos de la base de datos, uso un método de fábrica (basado en el TypeId ) para crear la clase StreamEvent correcta.

Cada subclase de StreamEvent tiene un método Render ( contexto como StreamContext ) que genera el evento en la pantalla en función de la clase StreamContext aprobada . La clase StreamContext permite establecer opciones en función del contexto de la vista. Si mira Facebook, por ejemplo, su feed de noticias en la página de inicio enumera los nombres completos (y los enlaces a su perfil) de todos los involucrados en cada acción, mientras que al mirar el feed de un amigo solo ve su nombre (pero los nombres completos de otros actores) .

Todavía no he implementado un feed agregado (página de inicio de Facebook), pero imagino que crearé una tabla AggregateFeed que tiene los campos UserId , StreamEventId que se basa en algún tipo de algoritmo 'Hmmm, puede que le resulte interesante'.

Cualquier comentario sería enormemente apreciado.

jammus
fuente
Estoy trabajando en un sistema como este. Estoy muy interesado en conocerlo. ¿Alguna vez terminaste el tuyo?
JasonDavis
¡Gran respuesta! Excelente separación de preocupaciones, limpio y elegante!
Mosh
¡Este es un buen comienzo! Es muy similar a cómo comencé a implementar mi primera transmisión. Sin embargo, una vez que llega al feed agregado, las cosas comienzan a complicarse rápidamente. Tienes razón en que necesitas un algoritmo robusto. Mi búsqueda me llevó al algoritmo de Rene Pickhardt (habla de ello en su respuesta aquí), que luego implementé en mi propio servicio, que ahora es comercial (consulte collabinate.com y mi respuesta sobre esta pregunta para obtener más información).
Mafuba
10
// una entrada por evento real
eventos {
  id, marca de tiempo, tipo, datos
}

// una entrada por evento, por feed que contiene ese evento
eventos_feeds {
  event_id, feed_id
}

Cuando se crea el evento, decida en qué fuentes aparece y agréguelos a events_feeds. Para obtener un feed, seleccione de events_feeds, únase a eventos, ordene por marca de tiempo. El filtrado y la agregación se pueden hacer en los resultados de esa consulta. Con este modelo, puede cambiar las propiedades del evento después de la creación sin trabajo adicional.

jedediah
fuente
1
Supongamos que alguien más se agrega como amigo después de que se agrega el evento, que necesita ver este evento en su feed. entonces esto no funcionaría
Joshua Kissoon
8

Si decide que va a implementar en Rails, quizás encuentre útil el siguiente complemento:

ActivityStreams: http://github.com/face/activity_streams/tree/master

Por lo menos, podrá ver una implementación, tanto en términos del modelo de datos, como de la API proporcionada para las actividades de empujar y tirar.

Alderete
fuente
6

Tuve un enfoque similar al de heyman: una tabla desnormalizada que contiene todos los datos que se mostrarían en un flujo de actividad determinado. Funciona bien para un sitio pequeño con actividad limitada.

Como se mencionó anteriormente, es probable que enfrente problemas de escalabilidad a medida que el sitio crece. Personalmente, no estoy preocupado por los problemas de escala en este momento. Me preocuparé por eso en otro momento.

Obviamente, Facebook ha hecho un gran trabajo de escala, por lo que le recomendaría que lea su blog de ingeniería, ya que tiene un montón de excelente contenido -> http://www.facebook.com/notes.php?id=9445547199

He estado buscando mejores soluciones que la tabla desnormalizada que mencioné anteriormente. Otra forma que he encontrado de lograr esto es condensar todo el contenido que estaría en un flujo de actividad dado en una sola fila. Podría almacenarse en XML, JSON o algún formato serializado que su aplicación pueda leer. El proceso de actualización también sería simple. Luego de la actividad, coloque la nueva actividad en una cola (tal vez usando Amazon SQS u otra cosa) y luego sondee continuamente la cola para el siguiente elemento. Tome ese elemento, analícelo y coloque su contenido en el objeto de fuente apropiado almacenado en la base de datos.

Lo bueno de este método es que solo necesita leer una sola tabla de base de datos cada vez que se solicita ese feed en particular, en lugar de tomar una serie de tablas. Además, le permite mantener una lista finita de actividades, ya que puede mostrar el elemento de actividad más antiguo cada vez que actualice la lista.

¡Espero que esto ayude! :)


fuente
Exactamente mis pensamientos, solo necesitaba una validación de mis pensamientos que probablemente ahora tengo, ¡salud!
Sohail
3

Creo que el enfoque de Plurk es interesante: proporcionan toda su línea de tiempo en un formato que se parece mucho a los gráficos de acciones de Google Finance.

Puede valer la pena mirar a Ning para ver cómo funciona una red de redes sociales. Las páginas del desarrollador parecen especialmente útiles.

madriguera
fuente
2

Resolví esto hace unos meses, pero creo que mi implementación es demasiado básica.
Creé los siguientes modelos:

HISTORY_TYPE

ID           - The id of the history type
NAME         - The name (type of the history)
DESCRIPTION  - A description

HISTORY_MESSAGES

ID
HISTORY_TYPE - A message of history belongs to a history type
MESSAGE      - The message to print, I put variables to be replaced by the actual values

HISTORY_ACTIVITY

ID
MESSAGE_ID    - The message ID to use
VALUES        - The data to use

Ejemplo

MESSAGE_ID_1 => "User %{user} created a new entry"
ACTIVITY_ID_1 => MESSAGE_ID = 1, VALUES = {user: "Rodrigo"}
Rodrigo
fuente
2

Después de implementar flujos de actividad para habilitar las funciones de alimentación social, microblogging y colaboración en varias aplicaciones, me di cuenta de que la funcionalidad básica es bastante común y podría convertirse en un servicio externo que puede utilizar a través de una API. Si está integrando el flujo en una aplicación de producción y no tiene necesidades únicas o profundamente complejas, utilizar un servicio comprobado puede ser la mejor opción. Definitivamente recomendaría esto para aplicaciones de producción en lugar de rodar su propia solución simple sobre una base de datos relacional.

Mi empresa Collabinate ( http://www.collabinate.com ) surgió de esta realización, y hemos implementado un motor de flujo de actividad escalable y de alto rendimiento en la parte superior de una base de datos gráfica para lograrlo. De hecho, utilizamos una variante del algoritmo Graphity (adaptado del trabajo inicial de @RenePickhardt, que también proporcionó una respuesta aquí) para construir el motor.

Si desea alojar el motor usted mismo o necesita una funcionalidad especializada, el código central es en realidad de código abierto para fines no comerciales, por lo que puede echarle un vistazo.

Mafuba
fuente