Uma falha única deve falhar em uma operação em massa?

11

Na API em que estou trabalhando, há uma operação de exclusão em massa que aceita uma matriz de IDs:

["1000", ..., "2000"]

Eu estava livre para implementar a operação de exclusão como quisesse, então decidi tornar tudo transacional: ou seja, se um único ID for inválido, toda a solicitação falhará. Vou chamar isso de modo estrito .

try{
savepoint = conn.setSavepoint();

for(id : IDs)
    if( !deleteItem(id) ){
        conn.rollback(savepoint);
        sendHttp400AndBeDoneWithIt();
        return;
    }

conn.commit();
}

A alternativa (implementada em outro lugar em nosso pacote de software) é fazer o que podemos no back-end e relatar falhas em uma matriz. Essa parte do software lida com menos solicitações para que a resposta não acabe sendo uma matriz gigantesca ... em teoria.


Um bug recente ocorrendo em um servidor com poucos recursos me fez olhar para o código novamente e agora estou questionando minha decisão original - mas desta vez sou mais motivado pelas necessidades de negócios do que pelas melhores práticas. Se, por exemplo, falhar em toda a solicitação, o usuário terá que tentar novamente; se vários itens forem excluídos, o usuário poderá concluir a ação e solicitar ao administrador que faça o resto (enquanto eu trabalho na correção do bug) !). Este seria o modo permissivo .

Tentei procurar on-line por alguma orientação sobre o assunto, mas vim de mãos vazias. Então, venho até você: O que é mais esperado por operações em massa dessa natureza? Devo seguir rigorosamente mais ou devo ser mais permissivo?

rath
fonte
9
Depende. Qual é o custo de ter algo que não foi excluído quando deveria? (O custo é definido como dados incorretos, dor de cabeça, comportamento indesejado, o tempo que um administrador leva para corrigi-lo etc.) Isso é aceitável? Se você pode viver com as consequências de não falhar em tudo, vá em frente. Se isso causasse muitos problemas, não. Você conhece o seu software e as consequências, portanto terá que fazer uma decisão.
Becuzz
1
@Becuzz O custo seria o usuário perceber uma ou duas sobras e abrir um ticket sobre isso; a situação atual é "omg delete is broken". Felizmente, o usuário está no final do corredor, por isso não é um problema demais dessa vez. O ponto é, eu gosto de fazer a coisa certa , sempre que possível, e com uma base de código 10+ anos de idade, Deus sabe algumas coisas podem estar a ser feito corretamente
Rath
Eu acho que isso também depende se você quer escalabilidade ou não. Se você não pretende ter muitos IDs, isso não deve importar muito. Se você pretende ter um milhão de IDs, ou melhor ainda, não tem certeza absoluta de que isso não aconteça, você pode gastar uma hora excluindo IDs apenas para redefinir completamente devido a 1 ID inválido.
imnota4
1
@ imnota4 Um ponto excelente que eu não tinha considerado. A interface do usuário restringe a solicitação a um máximo de cerca de 250, mas o back-end não possui restrições. Posso pedir que você reposte seu comentário como resposta?
Rath 12/16
1
O modo permissivo também facilita o trabalho dos administradores, porque eles não precisam reproduzir a falha com toda a pilha de IDs. Também pode ser útil informar na resposta a causa de cada erro. Olhando a causa, pode ser possível para o usuário final resolvê-lo sem tickets "omg delete is broken".
LAIV

Respostas:

9

Não há problema em fazer uma versão 'estrita' ou 'agradável' de um ponto de extremidade de exclusão, mas você precisa informar claramente ao usuário o que aconteceu.

Estamos fazendo uma ação de exclusão com este ponto de extremidade. Provável DELETE /resource/bulk/ou algo semelhante. Eu não sou exigente. O que importa aqui é que, independentemente de você decidir ser rigoroso ou agradável, você precisa informar exatamente o que aconteceu.

Por exemplo, uma API com a qual trabalhei tinha um DELETE /v1/student/terminal que aceitava IDs em massa. Nós enviamos regularmente a solicitação durante o teste, obtíamos uma 200resposta e assumíamos que tudo estava bem, apenas para descobrir mais tarde que todos na lista estavam IN no banco de dados ainda (definido como inativo) ou na verdade não foram excluídos devido a um erro que atrapalhou chamadas futuras GET /v1/studentporque recuperamos dados que não estávamos esperando.

A solução para isso veio em uma atualização posterior que adicionou um corpo à resposta com os IDs que não foram excluídos. Esta é - que eu saiba - um tipo de prática recomendada.

Resumindo, não importa o que você faça, certifique-se de fornecer uma maneira de informar ao usuário final o que está acontecendo e, possivelmente, por que está acontecendo. IE, se escolhermos um formato estrito, a resposta pode ser 400 - DELETE failed on ID 1221 not found. Se escolhermos uma versão 'agradável', pode ser 207 - {message:"failed, some ids not deleted", failedids:{1221, 23432, 1224}}(desculpe minha má formatação json).

Boa sorte!

Adam Wells
fonte
6
207 Multi-Statuspode ser apropriado para que a resposta fracasso parcial
Richard Tingle
1
AQUI VAMOS NÓS! Eu realmente não conseguia me lembrar disso! Vou seguir em frente e atualizar a resposta com isso, já que isso é realmente o padrão.
21816 Adam Wells #
2

Deve-se ser rigoroso e permissivo.

Geralmente, as cargas a granel são divididas em duas fases:

  • Validação
  • Carregando

Durante a fase de validação, cada registro é analisado estritamente para garantir que atenda aos requisitos das especificações de dados. Pode-se inspecionar facilmente dezenas de milhares de registros em apenas alguns segundos. Os registros válidos são colocados em um novo arquivo a ser carregado, o (s) inválido (s) marcado e removido e geralmente colocado em um arquivo separado (pular arquivo). A notificação é então enviada nos registros que falharam na validação, para que possam ser inspecionados e diagnosticados para fins de solução de problemas.

Depois de validados, os dados são carregados. Geralmente, ele é carregado em lotes, se for grande o suficiente para evitar transações de execução longa ou se houver uma falha, será mais fácil recuperar. O tamanho do lote depende do tamanho do conjunto de dados. Se houver apenas alguns mil registros, um lote estará OK. Aqui você pode ser um pouco permissivo com falhas, mas pode-se definir um limite de lote com falha para interromper toda a operação. Talvez se [N] os lotes falharem, interrompa toda a operação (se o servidor estiver inativo ou algo semelhante). Normalmente, não há falhas neste momento porque os dados já foram validados, mas se houve devido a problemas no ambiente ou outros, basta recarregar os lotes que falharam. Isso facilita um pouco a recuperação.

Jon Raynor
fonte
Não valido os IDs com relação aos valores do banco de dados, apenas tento excluí-los e ver como isso acontece, ou isso levaria uma eternidade. Interromper após falhas de N parece uma sugestão muito razoável, +1
rath
2

Uma falha única deve falhar em uma operação em massa?

Não há uma resposta canônica para isso. As necessidades e conseqüências para o usuário precisam ser examinadas e as compensações avaliadas. O OP forneceu algumas das informações necessárias, mas eis como devo proceder:

Pergunta 1 : 'Qual é a consequência para o usuário se uma exclusão individual falhar?'

A resposta deve orientar o restante do comportamento do design / implementado.

Se, como indicado pelo OP, simplesmente o usuário percebe a exceção e abre um registro de problema, mas não é afetado (os itens não excluídos não afetam as tarefas subseqüentes), então eu aceitaria uma notificação automática para você.

Se as exclusões com falha precisarem ser resolvidas antes que o usuário possa continuar, então é estritamente preferível.

Oferecer ao usuário a opção (por exemplo, essencialmente um sinalizador de ignorar falhas com o rigoroso ou o permissivo como padrão) pode ser a abordagem mais amigável.

Pergunta 2 : 'Haveria algum problema de coerência / consistência de dados se tarefas subsequentes forem executadas com itens não excluídos ainda no repositório de dados?'

Novamente, a resposta direcionaria o melhor design / comportamento. Sim -> Rigoroso, Não -> Permissivo, Talvez -> Rigoroso ou Selecionado pelo Usuário (particularmente se for possível confiar no usuário para determinar com precisão as conseqüências).

Kristian H
fonte
0

Eu acho que isso depende se você quer escalabilidade ou não. Se você não pretende ter muitos IDs, isso não deve importar muito. Se você pretende ter um milhão de IDs, ou melhor ainda, não tem certeza absoluta de que isso não aconteça, você pode gastar uma hora excluindo IDs apenas para redefinir completamente devido a 1 ID inválido.

imnota4
fonte
-1

Eu diria que um ponto importante aqui é o que significa que uma grande quantidade de coisas será excluída.

Esses IDs têm alguma relação lógica ou são apenas um agrupamento em lotes de conveniência / desempenho?

No caso de alguma maneira, mesmo que vagamente, conectada, eu aceitaria strict. Se for apenas um modo de lote (por exemplo, o usuário clica em "salvar" para seus últimos minutos de trabalho e somente então o lote é transmitido), eu usaria a permissiveversão.

Como a outra resposta afirma: De qualquer forma, conte ao "usuário" exatamente o que aconteceu.

Martin Ba
fonte