Como implementar o stream de atividades em uma rede social

Estou desenvolvendo minha própria rede social e não encontrei na web exemplos de implementação do stream de ações dos usuários … Por exemplo, como filtrar ações para cada usuário? Como armazenar os events de ação? Que modelo de dados e modelo de object posso usar para o stream de ações e para as ações em si?

Resumo : Para cerca de 1 milhão de usuários ativos e 150 milhões de atividades armazenadas, eu mantenho simples:

  • Use um database relacional para armazenamento de atividades exclusivas (1 registro por atividade / “coisa que aconteceu”) Torne os registros tão compactos quanto possível. Estrutura para que você possa rapidamente pegar um lote de atividades por ID de atividade ou usar um conjunto de IDs de amigos com restrições de tempo.
  • Publique os IDs de atividade no Redis sempre que um registro de atividade for criado, adicionando o ID a uma lista de “stream de atividades” para cada usuário que seja um amigo / assinante que deve ver a atividade.

Consulta Redis para obter o stream de atividades para qualquer usuário e, em seguida, obter os dados relacionados do database conforme necessário. Volte a consultar o database pelo tempo, se o usuário precisar navegar muito para trás no tempo (se você oferecer isso)


Eu uso uma tabela antiga e simples do MySQL para lidar com cerca de 15 milhões de atividades.

Parece algo como isto:

id user_id (int) activity_type (tinyint) source_id (int) parent_id (int) parent_type (tinyint) time (datetime but a smaller type like int would be better) 

activity_type informa o tipo de atividade, source_id informa o registro ao qual a atividade está relacionada. Então, se o tipo de atividade significa “adicionado favorito”, então eu sei que o source_id se refere ao ID de um registro favorito.

O parent_id / parent_type é útil para meu aplicativo. Eles me dizem com o que a atividade está relacionada. Se um livro fosse favorito, o parent_id / parent_type me diria que a atividade está relacionada a um livro (type) com uma determinada chave primária (id)

Eu (user_id, time) em (user_id, time) e consulta por atividades que são user_id IN (...friends...) AND time > some-cutoff-point . Ignorar o id e escolher um índice clusterizado diferente pode ser uma boa ideia – eu não experimentei isso.

Coisas bem básicas, mas funciona, é simples e é fácil de trabalhar conforme suas necessidades mudam. Além disso, se você não estiver usando o MySQL, talvez consiga fazer melhor índice.


Para um access mais rápido às atividades mais recentes, tenho experimentado com o Redis . O Redis armazena todos os seus dados na memory, então você não pode colocar todas as suas atividades lá, mas você pode armazenar o suficiente para a maioria das canvass comuns em seu site. Os 100 mais recentes para cada usuário ou algo parecido. Com Redis na mistura, pode funcionar assim:

  • Crie seu registro de atividade do MySQL
  • Para cada amigo do usuário que criou a atividade, envie o ID para sua lista de atividades no Redis.
  • Apare cada lista para os últimos X itens

O Redis é rápido e oferece uma maneira de canalizar comandos através de uma conexão – então, empurrar uma atividade para 1000 amigos leva milésimos de segundo.

Para uma explicação mais detalhada sobre o que estou falando, veja o exemplo do Redis no Twitter: http://redis.io/topics/twitter-clone

Atualização de fevereiro de 2011 Eu tenho 50 milhões de atividades ativas no momento e eu não mudei nada. Uma coisa legal em fazer algo semelhante a isso é que ele usa linhas pequenas e compactas. Estou planejando fazer algumas mudanças que envolvam muito mais atividades e mais consultas sobre essas atividades, e com certeza vou usar o Redis para manter as coisas mais velozes. Eu estou usando Redis em outras áreas e realmente funciona bem para certos tipos de problemas.

Atualização de julho de 2014 Estamos com cerca de 700 mil usuários ativos mensais. Nos últimos dois anos, tenho usado o Redis (conforme descrito na lista com marcadores) para armazenar os últimos 1000 IDs de atividade para cada usuário. Normalmente existem cerca de 100 milhões de registros de atividades no sistema e eles ainda são armazenados no MySQL e ainda são o mesmo layout. Esses registros nos permitem obter menos memory Redis, eles servem como o registro de dados de atividades e os usamos se os usuários precisarem voltar mais atrás no tempo para encontrar algo.

Esta não foi uma solução inteligente ou especialmente interessante, mas me serviu bem.

Esta é minha implementação de um stream de atividades, usando o mysql. Existem três classs: Activity, ActivityFeed, Subscriber.

Atividade representa uma input de atividade e sua tabela é semelhante a esta:

 id subject_id object_id type verb data time 

Subject_id é o id do object que está executando a ação, object_id o id do object que recebe a ação. type e verb descrevem a ação em si (por exemplo, se um usuário adicionar um comentário a um artigo eles seriam “comment” e “created” respectivamente), os dados contêm dados adicionais para evitar junções (por exemplo, ele pode conter o nome do assunto e sobrenome, o título do artigo e url, o corpo do comentário etc.).

Cada Activity pertence a um ou mais ActivityFeeds, e eles são relacionados por uma tabela que se parece com isso:

 feed_name activity_id 

No meu aplicativo eu tenho um feed para cada usuário e um feed para cada item (geralmente artigos de blog), mas eles podem ser o que você quiser.

Um Assinante geralmente é um usuário do seu site, mas também pode ser qualquer object em seu modelo de object (por exemplo, um artigo pode ser inscrito no feed_action de seu criador).

Cada Assinante pertence a um ou mais ActivityFeeds e, como acima, eles são relacionados por uma tabela de links desse tipo:

 feed_name subscriber_id reason 

O campo da reason explica por que o assinante assinou o feed. Por exemplo, se um usuário marcar uma postagem de blog, o motivo é ‘favorito’. Isso me ajuda mais tarde na filtragem de ações para notifications aos usuários.

Para recuperar a atividade de um assinante, faço uma junit simples das três tabelas. A junit é rápida porque eu seleciono algumas atividades graças a uma condição WHERE que parece agora – time > some hours . Evito outras junções graças ao campo de dados na tabela Activity.

Mais explicações sobre o campo da reason . Se, por exemplo, eu quiser filtrar ações para notifications de email para o usuário, e o usuário marcar uma postagem no blog (e assim ele assinar o feed de postagem com o motivo ‘bookmark’), não quero que o usuário receba Notificações por e-mail sobre ações nesse item, enquanto se ele comenta a postagem (e, portanto, assina o feed da postagem com motivo ‘comentário’), eu quero que ele seja notificado quando outros usuários adicionarem comentários à mesma postagem. O motivo do campo me ajuda nessa discriminação (eu implementei através de uma class ActivityFilter), junto com as preferências de notifications do usuário.

Existe um formato atual para stream de atividades que está sendo desenvolvido por um grupo de pessoas bem conhecidas.

http://activitystrea.ms/ .

Basicamente, toda atividade tem um ator (que executa a atividade), um verbo (a ação da atividade), um object (no qual o ator atua) e um alvo.

Por exemplo: Max postou um link para o mural de Adam.

A especificação do seu JSON atingiu a versão 1.0 no momento da escrita, que mostra o padrão da atividade que você pode aplicar.

Seu formato já foi adotado pela BBC, Gnip, Google Buzz Gowalla, IBM, MySpace, Opera, Socialcast, Superfeedr, TypePad, Windows Live, YIID e muitos outros.

Eu acho que uma explicação sobre como funciona o sistema de notifications em grandes sites pode ser encontrada na pergunta sobre estouro de pilha. Como os sites de redes sociais calculam as atualizações de amigos? , na resposta do Jeremy Wall . Ele sugere o uso do Message Qeue e ele indica dois softwares de código aberto que o implementam:

  1. RabbitMQ
  2. Apache QPid

Veja também a pergunta Qual é a melhor maneira de implementar um stream de atividade social?

Você precisa absolutamente de uma fila de mensagens distribuídas e de alto desempenho. Mas não termina aí, você terá que tomar decisões sobre o que armazenar como dados persistentes e como transitórios e etc.

De qualquer forma, é realmente uma tarefa difícil, meu amigo, se você está atrás de um sistema escalável e de alto desempenho. Mas, é claro, alguns engenheiros generosos compartilharam sua experiência sobre isso. LinkedIn recentemente fez seu sistema de fila de mensagens Kafka open source. Antes disso, o Facebook já havia fornecido o Scribe para a comunidade de código aberto. Kafka é escrito em Scala e, a princípio, leva algum tempo para executá-lo, mas eu testei com alguns servidores virtuais. É muito rápido.

http://blog.linkedin.com/2011/01/11/open-source-linkedin-kafka/

http://incubator.apache.org/kafka/index.html

Em vez de criar sua própria conta, você pode procurar um serviço de terceiros usado por meio de uma API. Eu comecei um chamado Collabinate ( http://www.collabinate.com ) que tem um backend gráfico de database e alguns algoritmos bastante sofisticados para lidar com grandes quantidades de dados de uma maneira altamente concorrente e de alto desempenho. Embora não tenha a amplitude de funcionalidade que o Facebook ou o Twitter fazem, é mais do que suficiente para a maioria dos casos de uso em que você precisa criar streams de atividade, feeds sociais ou funcionalidade de microblog em um aplicativo.