Qual é a melhor maneira de implementar um fluxo de atividades sociais? [fechadas]


265

Estou interessado em ouvir suas opiniões sobre qual é a melhor maneira de implementar um fluxo de atividades sociais (o Facebook é o exemplo mais famoso). Os problemas / desafios envolvidos são:

  • Diferentes tipos de atividades (postagem, comentário ..)
  • Diferentes tipos de objetos (publicação, comentário, foto ..)
  • 1-n usuários envolvidos em diferentes funções ("O usuário x respondeu ao comentário do usuário y na publicação Z do usuário")
  • Exibições diferentes do mesmo item de atividade ("você comentou .." vs. "seu amigo x comentou" vs. "usuário x comentou .." => 3 representações de uma atividade "comentar")

.. e mais alguns, especialmente se você o elevar a um alto nível de sofisticação, como o Facebook, por exemplo, combinando vários itens de atividade em um ("usuários x, yez comentaram sobre essa foto"

Serão apreciados quaisquer pensamentos ou sugestões sobre padrões, documentos, etc., sobre as abordagens mais flexíveis, eficientes e poderosas para implementar esse sistema, modelo de dados etc.

Embora a maioria dos problemas seja independente de plataforma, é provável que eu acabe implementando esse sistema no Ruby on Rails

Respostas:


143

Eu criei esse sistema e segui essa abordagem:

Tabela de banco de dados com as seguintes colunas: id, userId, tipo, dados, hora.

  • userId é o usuário que gerou a atividade
  • type é o tipo da atividade (por exemplo, escreveu uma postagem no blog, adicionou uma foto e comentou a foto do usuário)
  • data é um objeto serializado com metadados para a atividade em que você pode colocar o que quiser

Isso limita as pesquisas / pesquisas que você pode fazer nos feeds, para usuários, horário e tipos de atividades, mas em um feed de atividades do tipo facebook, isso não é realmente limitante. E com índices corretos na mesa, as pesquisas são rápidas .

Com esse design, você teria que decidir quais metadados cada tipo de evento exigiria. Por exemplo, uma atividade de feed para uma nova foto pode ser algo como isto:

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

Você pode ver que, embora o nome da foto certamente esteja armazenado em alguma outra tabela que contenha as fotos e eu possa recuperar o nome a partir daí, duplicarei o nome no campo de metadados, porque você não deseja fazer isso. qualquer junção em outras tabelas de banco de dados, se você quiser velocidade. E para exibir, digamos 200, eventos diferentes de 50 usuários diferentes, você precisa de velocidade.

Então, eu tenho classes que estendem uma classe básica de FeedActivity para renderizar os diferentes tipos de entradas de atividades. O agrupamento de eventos também seria construído no código de renderização, para manter a complexidade do banco de dados.


3
Sim, está correto. Ultimamente, tenho usado o MongoDB ( mongodb.org ) em alguns projetos, cuja abordagem sem esquema o torna muito adequado para criar um fluxo de atividades sociais com bom desempenho que segue esse design.
Heyman

6
TheApprentice: Sim, você também pode querer inserir um campo de nome de usuário. Em nosso sistema, exibimos apenas eventos gerados pelos amigos de um usuário, e acredito que já tínhamos um mapa do ID do usuário dos amigos->> na memória, portanto, procurar os nomes de usuário não requer um JOIN e foi rápido.
heyman

2
Você precisaria lidar com esse caso manualmente. Provavelmente, é melhor fazê-lo quando a foto for excluída (localize o item do feed no feed do usuário e exclua / atualize).
Heyman

21
Eu não entendo direito o que há de tão bom nessa resposta? Como a criação de uma tabela simples se traduz em um feed de atividade ponderada semelhante ao facebook? Tudo o que ele está fazendo é armazenar toda a atividade. O que ainda deixa a questão de como transformar uma tabela de dados em um feed de atividade ponderada dinâmico?
ChuckKelly

4
@ChuckKelly: Se bem me lembro, em 2008, quando escrevi a resposta, o feed do Facebook não tinha peso algum. Era apenas um feed cronológico com toda a atividade de seus amigos.
precisa saber é

117

Esta é uma apresentação muito boa, descrevendo como o Etsy.com arquitetou seus fluxos de atividades. É o melhor exemplo que encontrei sobre o tópico, embora não seja específico dos trilhos.

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


21
^^ Porque você precisa voltar ao SO depois de visitar o site. lol
Stephen Corwin

1
Ótima apresentação que explica em detalhes como o sistema funciona em um site real de alto tráfego.
ramirami

44

Abrimos nossa abordagem de código aberto: https://github.com/tschellenbach/Stream-Framework Atualmente, é a maior biblioteca de código aberto voltada para a solução desse problema.

A mesma equipe que criou o Stream Framework também oferece uma API hospedada, que lida com a complexidade para você. Dê uma olhada no getstream.io Existem clientes disponíveis para Node, Python, Rails e PHP.

Além disso, dê uma olhada neste post de alta escalabilidade, onde explicamos algumas das decisões de design envolvidas: http://highscalability.com/blog/2013/10/28/design-decisions-for-scaling-your-high-traffic- feeds.html

Este tutorial irá ajudá-lo a configurar um sistema como o feed do Pinterest usando Redis. É muito fácil começar.

Para saber mais sobre o design de feeds, recomendo a leitura de alguns dos artigos nos quais baseamos o Feedly:

Embora o Stream Framework seja baseado em Python, não seria muito difícil de usar em um aplicativo Ruby. Você pode simplesmente executá-lo como um serviço e colocar uma pequena API http na frente dele. Estamos pensando em adicionar uma API para acessar o Feedly de outros idiomas. No momento, você terá que fazer o seu próprio papel.


19

Os maiores problemas com fluxos de eventos são visibilidade e desempenho; você precisa restringir os eventos exibidos para serem apenas os interessantes para esse usuário específico e manter o tempo necessário para classificar e identificar esses eventos gerenciáveis. Eu construí uma rede social pequena; Eu descobri que, em pequenas escalas, manter uma tabela de "eventos" em um banco de dados funciona, mas isso pode ser um problema de desempenho sob carga moderada.

Com um fluxo maior de mensagens e usuários, provavelmente é melhor usar um sistema de mensagens, no qual os eventos são enviados como mensagens para perfis individuais. Isso significa que você não pode se inscrever facilmente nos fluxos de eventos das pessoas e ver os eventos anteriores com muita facilidade, mas você está simplesmente processando um pequeno grupo de mensagens quando precisa processar o fluxo para um usuário específico.

Acredito que essa foi a falha de design original do Twitter - lembro-me de ler que eles estavam acessando o banco de dados para extrair e filtrar seus eventos. Isso tinha tudo a ver com arquitetura e nada com Rails, que (infelizmente) deu origem ao meme "rubi não escala". Recentemente, vi uma apresentação em que o desenvolvedor usou o Simple Queue Service da Amazon como back-end de mensagens para um aplicativo semelhante ao twitter que teria recursos de dimensionamento muito mais altos - pode valer a pena examinar o SQS como parte do seu sistema, se suas cargas forem altas o suficiente .


Tim, por acaso você se lembra do nome da apresentação ou do apresentador?
1111 Danita

foi na apresentação de Ignite Boston, da Oreilly and Associate, número 3 ou 4. Acredito que o apresentador tenha um livro sobre a escala de RoR com Oreilly. Desculpe, não posso ser mais específico!
Tim Howland

Obrigado Tim :) A propósito, o que você quis dizer com "pequena rede social"? Quantos usuários ou usuários ativos em um determinado momento?
269 ​​Danita

3
Caso alguém precise, acho que esta é a apresentação sobre a qual Tim está falando: "Dan Chak - Dimensionando para o tamanho dos seus problemas" radar.oreilly.com/2008/09/ignite-boston-4----videos -uplo.html
Danita

O pequeno neste caso é tal que "selecionar * de eventos onde event.is visível para esse usuário" retorna um resultado em menos de um segundo ou dois dígitos, algumas centenas de milhares de linhas de eventos.
Tim Howland

12

Se você estiver disposto a usar um software separado, sugiro o servidor Graphity, que resolve exatamente o problema dos fluxos de atividades (construindo sobre o banco de dados de gráficos neo4j).

Os algoritmos foram implementados como um servidor REST independente, para que você possa hospedar seu próprio servidor para fornecer fluxos de atividades: http://www.rene-pickhardt.de/graphity-server-for-social-activity-streams-released-gplv3 /

No artigo e no benchmark, mostrei que a recuperação de fluxos de notícias depende apenas linearmente da quantidade de itens que você deseja recuperar sem nenhuma redundância que você obteria ao desnormalizar os dados:

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

No link acima, você encontra screencasts e uma referência dessa abordagem (mostrando que a graphity é capaz de recuperar mais de 10 mil fluxos por segundo).


10

Comecei a implementar um sistema como este ontem, aqui é onde eu tenho que ...

Criei uma classe StreamEvent com as propriedades Id , ActorId , TypeId , Date , ObjectId e uma hashtable de pares chave / valor adicionais de detalhes . Isto é representado no banco de dados por um StreamEvent mesa ( Id , ActorID , TypeId , Data , ObjectId ) e um StreamEventDetails mesa ( StreamEventId , DetailKey , DetailValue ).

O ActorId , TypeId e ObjectId permitem que um evento Subject-Verb-Object seja capturado (e consultado posteriormente). Cada ação pode resultar na criação de várias instâncias StreamEvent.

Criei uma subclasse para StreamEvent para cada tipo de evento, por exemplo , LoginEvent , PictureCommentEvent . Cada uma dessas subclasses possui mais propriedades específicas de contexto, como PictureId , ThumbNail , CommenText , etc (o que for necessário para o evento) que são realmente armazenadas como pares de chave / valor na tabela hashtable / StreamEventDetail.

Ao recuperar esses eventos do banco de dados, uso um método de fábrica (baseado no TypeId ) para criar a classe StreamEvent correta.

Cada subclasse de StreamEvent possui um método Render ( context As StreamContext ) que gera o evento para a tela com base na classe StreamContext passada . A classe StreamContext permite que as opções sejam definidas com base no contexto da exibição. Se você olhar para o Facebook, por exemplo, seu feed de notícias na página inicial lista os nomes completos (e os links para o perfil) de todos os envolvidos em cada ação, enquanto, ao procurar o feed de um amigo, você vê apenas o primeiro nome (mas o nome completo de outros atores) .

Ainda não implementei um feed agregado (página inicial do Facebook), mas imagino que vou criar uma tabela AggregateFeed com os campos UserId , StreamEventId preenchidos com base em algum tipo de algoritmo 'Hmmm, você pode achar interessante'.

Quaisquer comentários serão muito apreciados.


Estou trabalhando em um sistema como este, estou muito interessado em algum conhecimento sobre isso, você já terminou o seu?
JasonDavis

Ótima resposta! Excelente separação de preocupações, limpa e elegante!
Mosh

Este é um bom começo! É muito parecido com o modo como comecei a implementar meu primeiro fluxo. Quando você chega ao feed agregado, no entanto, as coisas começam a ficar complicadas rapidamente. Você está certo que precisa de um algoritmo robusto. Minha pesquisa me levou ao algoritmo de Rene Pickhardt (ele fala sobre isso em sua resposta aqui), que eu implementei em meu próprio serviço, que agora é comercial (consulte collabinate.com e minha resposta sobre esta questão para saber mais).
Mafuba

10
// uma entrada por evento real
eventos {
  ID, registro de data e hora, tipo, dados
}

// uma entrada por evento, por feed que contém esse evento
events_feeds {
  event_id, feed_id
}

Quando o evento for criado, decida em quais feeds ele aparecerá e adicione-o a events_feeds. Para obter um feed, selecione entre events_feeds, participe de eventos, ordene pelo carimbo de data / hora. A filtragem e a agregação podem ser feitas nos resultados dessa consulta. Com este modelo, você pode alterar as propriedades do evento após a criação, sem trabalho extra.


1
Suponha que alguém seja adicionado como amigo após a adição do evento, que precisa ver esse evento em seu feed? em seguida, isso não iria funcionar
Joshua Kissoon


6

Eu tive uma abordagem semelhante à do heyman - uma tabela desnormalizada contendo todos os dados que seriam exibidos em um determinado fluxo de atividades. Funciona bem para um site pequeno com atividade limitada.

Como mencionado acima, é provável que haja problemas de escalabilidade à medida que o site cresce. Pessoalmente, não estou preocupado com os problemas de dimensionamento no momento. Vou me preocupar com isso mais tarde.

Obviamente, o Facebook fez um ótimo trabalho de dimensionamento, então eu recomendo que você leia o blog de engenharia, pois ele possui uma grande quantidade de conteúdo -> http://www.facebook.com/notes.php?id=9445547199

Tenho procurado soluções melhores do que a tabela desnormalizada que mencionei acima. Outra maneira que encontrei para realizar isso é condensar todo o conteúdo que estaria em um determinado fluxo de atividades em uma única linha. Ele pode ser armazenado em XML, JSON ou em algum formato serializado que possa ser lido pelo seu aplicativo. O processo de atualização também seria simples. Após a atividade, coloque a nova atividade em uma fila (talvez usando o Amazon SQS ou outra coisa) e, em seguida, pesquise continuamente a fila para o próximo item. Pegue esse item, analise-o e coloque seu conteúdo no objeto de feed apropriado armazenado no banco de dados.

A coisa boa desse método é que você só precisa ler uma única tabela de banco de dados sempre que esse feed específico for solicitado, em vez de pegar uma série de tabelas. Além disso, ele permite que você mantenha uma lista finita de atividades, pois pode sair do item de atividade mais antigo sempre que atualizar a lista.

Espero que isto ajude! :)


Exatamente meus pensamentos, eu só precisava de uma validação dos meus pensamentos que provavelmente já tenho agora, vivas!
Sohail


3

Eu acho que o Plurk abordagem é interessante: eles fornecem toda a linha do tempo em um formato que se parece muito com os gráficos de ações do Google Finance.

Pode valer a pena olhar para Ning para ver como funciona uma rede de rede social. As páginas do desenvolvedor parecem especialmente úteis.


2

Resolvi isso alguns meses atrás, mas acho que minha implementação é muito básica.
Criei os seguintes 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

Exemplo

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

2

Após implementar fluxos de atividades para habilitar feeds sociais, microblogs e recursos de colaboração em vários aplicativos, percebi que a funcionalidade básica é bastante comum e poderia ser transformada em um serviço externo que você utiliza por meio de uma API. Se você estiver criando o fluxo em um aplicativo de produção e não tiver necessidades únicas ou profundamente complexas, utilizar um serviço comprovado pode ser o melhor caminho a percorrer. Definitivamente, eu recomendaria isso para aplicativos de produção, rolando sua própria solução simples sobre um banco de dados relacional.

Minha empresa Collabinate ( http://www.collabinate.com ) cresceu com essa percepção e implementamos um mecanismo de fluxo de atividades escalável e de alto desempenho no topo de um banco de dados gráfico para alcançá-lo. Na verdade, utilizamos uma variante do algoritmo Graphity (adaptado do trabalho inicial de @RenePickhardt, que também forneceu uma resposta aqui) para construir o mecanismo.

Se você deseja hospedar o mecanismo você mesmo ou precisar de uma funcionalidade especializada, o código principal é realmente de código aberto para fins não comerciais, portanto, você pode dar uma olhada.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.