Em que condições (se houver) é uma boa prática consultar dois servidores e consumir apenas a resposta mais rápida?


12

Perguntei o que agora é uma pergunta excluída por comunidade no SO sobre por que alguém usaria javascript Promise.racee um usuário de alta reputação comentou isso:

Se você tiver dois serviços que calculam algum valor, poderá consultá-los em paralelo e usar o valor que for retornado primeiro, em vez de consultar um, aguardar uma falha e, em seguida, consultar o segundo.

Pesquisei no Google sobre redundância e esse caso de uso em geral, mas não consegui encontrar nada e, do meu POV, nunca é uma boa idéia adicionar carga de trabalho a um servidor / serviço se você não usar a resposta.


Exemplo de brinquedo: em vez de sempre usar o quicksort, você copia os dados, os envia para um quicksort e um mergesort e um heapsort, etc. etc. Você não precisa inspecionar a entrada para ver se é um caso patológico. para qualquer um deles, porque não será um caso
patológico

O artigo de Dean e Barroso, The Tail at Scale, chama uma variação dessa abordagem de "solicitações cobertas". Ele também discute os prós e contras de várias abordagens relacionadas ao controle da variabilidade de cauda longa nas taxas de erro e latência.
Daniel Pryden

O segundo "pedido do servidor" pode ser falso. Pode demorar apenas 5 segundos e, em seguida, retornar uma resposta de espaço reservado. Isso dá um tempo limite para a solicitação real.
User253751 18/0118

Encomende um Lyft e depois um Uber. Tome o que ocorrer primeiro.
user2023861

@ user2023861 nessa analogia, enquanto um motorista foi inutilmente dirigindo para a sua localização, ele / ela poderia ter tomado em outro pedido em vez
Adelin

Respostas:


11

Eu argumentaria que essa é mais uma questão econômica. No entanto, essa é uma decisão que os engenheiros devem poder fazer. Por isso, estou respondendo.

Estou dividindo minha resposta em quatro partes:

  • Gerenciamento de riscos
  • Estratégias
  • Custos
  • Intuição

Gerenciamento de riscos

Portanto, algumas vezes seu cliente falha em obter uma resposta do servidor. Assumirei que isso não se deve a um erro de programação (caso contrário, a solução é corrigi-lo, então faça isso). Em vez disso, deve ser por causa de uma situação fortuita fora do seu controle ...

Mas não além do seu conhecimento. Você deve saber:

  • Com que frequência isso acontece.
  • Que impacto isso tem?

Por exemplo, se a falha e a nova tentativa ocorrerem apenas cerca de 2% das vezes, provavelmente não vale a pena resolvê-lo. Se isso acontece cerca de 80% das vezes, bem ... depende ...

Quanto tempo o cliente precisa esperar? E como isso se traduz em custos ... veja bem, você tem um pequeno atraso em um aplicativo regular, provavelmente não é grande coisa. Se for significativo, e você tiver um aplicativo em tempo real ou um videogame on-line, isso afastará os usuários e provavelmente você investirá em mais ou melhores servidores. Caso contrário, você provavelmente poderá colocar uma mensagem "carregando" ou "aguardando servidor". A menos que o atraso seja realmente grande (na ordem de dezenas de segundos), pode ser demais, mesmo para a aplicação regular.


Estratégias

Como eu disse acima, há mais de uma maneira de resolver esse problema. Suponho que você já tenha implementado o loop try-fail-retry. Então, vamos ver ...

  • Coloque uma mensagem de carregamento. É barato, ajuda na retenção do usuário.
  • Consulta em paralelo. Pode ser mais rápido, ainda pode falhar. Exigirá um servidor redundante (pode ser caro), desperdiçará tempo do servidor e tráfego de rede.
  • Consulte em paralelo para estabelecer o servidor mais rápido e use-o a partir daí. Pode ser mais rápido, ainda pode falhar. Exigirá servidor redundante (pode ser caro), não desperdiçará tanto tempo do servidor e tráfego de rede.

Agora, observe que eu digo que eles ainda podem falhar. Se assumirmos que uma consulta a um servidor tem 80% de chance de falha, uma consulta paralela a dois servidores tem 64% de chance de falha. Portanto, você ainda pode precisar tentar novamente.

Uma vantagem adicional de escolher o servidor mais rápido e continuar a usá-lo é que o servidor mais rápido também tem menos chances de falhar devido a problemas de rede.

O que me lembra, se você puder descobrir por que a solicitação falhou, faça-o. Isso pode ajudá-lo a gerenciar melhor a situação, mesmo que não consiga evitar as falhas. Por exemplo, você precisa de mais velocidade de transferência no lado do servidor?

Um pouco mais:

  • Implante vários servidores em todo o mundo e escolha o servidor por geolocalização.
  • Faça o balanceamento de carga no lado do servidor (uma máquina dedicada atenderá a todas as solicitações e as transmitirá aos seus servidores, você poderá ter seu paralelismo ali ou uma melhor estratégia de balanceamento).

E quem disse que você precisa fazer apenas um deles? Você pode colocar uma mensagem de carregamento, consultar vários servidores espalhados pelo processo para escolher o mais rápido e usá-lo apenas a partir de então, na tentativa de falha em um loop e fazer com que cada um desses servidores seja um cluster de máquinas com balanceamento de carga . Por que não? Bem, custa ...


Custos

Existem quatro custos:

  • O custo do desenvolvimento (geralmente muito barato)
  • O custo da implantação (geralmente alto)
  • O tempo de execução de custo (depende do tipo de aplicativo e do modelo de negócios)
  • O custo do fracasso (provavelmente baixo, mas não necessariamente)

Você tem que equilibrá-los.

Por exemplo, digamos que você ganha cerca de um dólar por usuário satisfeito. Que você tenha 3000 usuários por dia. Que os pedidos falham cerca de 50% do tempo. E que 2% dos usuários saem sem pagar quando a solicitação falha. Isso significa que você está perdendo (3000 * 50% * 2%) 30 dólares por dia. Agora, digamos que o desenvolvimento do novo recurso custará 100 dólares e a implantação dos servidores custará 800 dólares - e ignorando os custos de tempo de execução - isso significa que você teria um retorno do investimento em ((100 + 800) / 30 ) 30 dias. Agora, você pode verificar seu orçamento e decidir.

Não considere esses valores representativos da realidade, eu os escolhi por conveniência matemática.

Adendos:

  • Lembre-se de que também estou ignorando os detalhes. Por exemplo, você pode ter pouco custo de implantação, mas está pagando pelo tempo da CPU e precisa considerar isso.
  • Alguns clientes podem apreciar se você não desperdiçar o pacote de dados em solicitações redundantes.
  • Melhorar o seu produto pode ajudar a trazer propaganda natural.
  • Não se esqueça dos custos de oportunidade. Você deveria estar desenvolvendo algo mais?

O fato é que, se você considerar o problema em termos de compensação de custos, poderá fazer uma estimativa do custo para as estratégias que considerar e usar essa análise para decidir.


Intuição

Intuição se promovido pela experiência. Não estou sugerindo fazer esse tipo de análise todas as vezes. Algumas pessoas fazem, e tudo bem. Estou sugerindo que você entenda isso e desenvolva uma intuição para isso.

Além disso, na engenharia, além do conhecimento que obtemos da ciência real, também aprendemos na prática e compilamos diretrizes sobre o que funciona e o que não funciona. Portanto, geralmente é aconselhável ver qual é o estado da arte ... embora, às vezes, você precise ver fora de sua área.

Nesse caso, eu examinaria os videogames online. Eles têm telas de carregamento, vários servidores, escolhem um servidor com base na latência e podem até permitir que o usuário troque de servidor. Sabemos que funciona.

Sugiro fazer isso em vez de desperdiçar o tráfego da rede e o tempo do servidor em todas as solicitações; também esteja ciente de que mesmo com o servidor redundante, pode ocorrer falha.


2
Acho que não preciso dizer, mas essa é uma ótima resposta :) Eu sabia que aceitaria nas 10 primeiras linhas, mas dei a você a oportunidade de continuar a falhar e a ler até o final. Você não fez
Adelin

9

Isso é aceitável se o tempo do cliente for mais valioso que o tempo no servidor.

Se o cliente precisar ser rápido e preciso. Você pode justificar a consulta de vários servidores. E é bom cancelar a solicitação se uma resposta válida for recebida.

E é claro que é sempre aconselhável consultar os proprietários / gerentes dos servidores.


Por que você precisa cancelar a solicitação? Certamente isso é subjetivo.
J 17Mᴇᴇ

@ JᴀʏMᴇᴇ, isso é construir na paranóia. Certa vez, trabalhei com um sistema que não limpava sua fila e travava quando a fila estava cheia (sim, era um software profissional).
Toon Krijthe

4

Essa técnica pode reduzir a latência. O tempo de resposta do servidor não é determinístico. Em escala, é provável que haja pelo menos um servidor mostrando tempos de resposta ruins. Qualquer coisa que use esse servidor também terá tempos de resposta ruins. Ao enviar para vários servidores, reduz-se o risco de conversar com um servidor com desempenho insatisfatório.

Os custos incluem tráfego de rede adicional, processamento desperdiçado do servidor e complexidade de aplicativos (embora isso possa estar oculto em uma biblioteca). Esses custos podem ser reduzidos cancelando solicitações não utilizadas ou aguardando brevemente antes de enviar uma segunda solicitação.

Aqui está um papel , e outro . Lembro-me de ler um artigo do Google sobre sua implementação também.


2

Concordo principalmente com as outras respostas, mas acho que isso deve ser extremamente raro na prática. Eu queria compartilhar um exemplo muito mais comum e razoável de quando você usaria Promise.race(), algo que eu usei por algumas semanas atrás (bem, o equivalente do python).

Digamos que você tenha uma longa lista de tarefas, algumas que podem ser executadas em paralelo e outras que devem ser executadas antes de outras. Você pode iniciar todas as tarefas sem dependências e aguardar nessa lista com Promise.race(). Assim que a primeira tarefa for concluída, você poderá iniciar qualquer tarefa que dependesse dessa primeira tarefa e, Promise.race()novamente, a nova lista combinada com tarefas inacabadas da lista original. Continue repetindo até que todas as tarefas sejam concluídas.

Nota A API do Javascript não é idealmente projetada para isso. É praticamente o mínimo necessário que funciona, e você precisa adicionar um pouco de código de cola. No entanto, meu argumento é que funções como race()raramente são usadas para redundância. Eles estão lá principalmente para quando você realmente deseja os resultados de todas as promessas, mas não deseja esperar que todas elas sejam concluídas antes de executar as ações subseqüentes.


O problema é que, pelo menos com o Promise.race do Javascript, você realmente inicia a tarefa toda vez que executa o método de corrida. Não estará na tarefa inacabada, seria um novo conjunto de tarefas, sem considerar o que foi executado antes (a menos que você implemente essa lógica no nível da tarefa). A lista original é esquecida de outra forma, e apenas o valor de retorno da primeira tarefa permanece
Adelin

1
As promessas em Javascript são iniciadas com entusiasmo, quando new Promiseé chamado, e não são reiniciadas quando Promise.race()é chamado. Algumas implementações promissoras são preguiçosas, mas ansiosas são muito mais comuns. Você pode testar criando uma promessa no console que efetua logon no console. Você verá os logs imediatamente. Então passe essa promessa para Promise.race(). Você verá que não registra novamente.
Karl Bielefeldt

Ah, isso é verdade. Mas afaik o valor de retorno do resto das promessas, exceto a primeira é esquecido, com promise.race
Adelin

Por isso eu disse que a API não é idealmente projetada. Você precisa armazenar o conjunto original de tarefas em uma variável em algum lugar.
Karl Bielefeldt

1

Além das considerações técnicas, você pode usar essa abordagem quando faz parte do seu modelo de negócios real.

Variações nessa abordagem são relativamente comuns nos lances em tempo real de anúncios. Nesse modelo, um editor (provedor de espaço para anúncios) solicitará que os anunciantes (provedores de anúncios) façam lances para uma impressão de um usuário específico. Portanto, para cada impressão, você consultaria cada um dos anunciantes inscritos, enviando uma consulta com os detalhes da impressão para um endpoint fornecido por cada anunciante (ou, alternativamente, um script fornecido pelo anunciante sendo executado como um endpoint em seus próprios servidores), correndo todos esses pedidos até um tempo limite (por exemplo, 100 ms) e, em seguida, aceitam o lance mais alto, ignorando os outros.

Uma variação específica disso que ajuda a reduzir o tempo de espera do cliente é permitir que o editor permita um valor mínimo de meta para o lance, de modo que o primeiro lance do anunciante que ultrapasse esse valor seja aceito imediatamente (ou, se nenhum dos lances ultrapassar o valor, o máximo será obtido). Portanto, nessa variação, a primeira consulta de chegada pode vencer e a outra descartada, mesmo que sejam tão boas ou até melhores.

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.