¿Debería un solo fallo fallar una operación masiva?

11

En la API en la que estoy trabajando, hay una operación de eliminación masiva que acepta una matriz de ID:

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

Tuve la libertad de implementar la operación de eliminación como lo creía conveniente, así que decidí hacer que todo fuera transaccional: es decir, si un solo ID no es válido, toda la solicitud falla. Llamaré a esto el modo estricto .

try{
savepoint = conn.setSavepoint();

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

conn.commit();
}

La alternativa (implementada en otra parte de nuestro paquete de software) es hacer lo que podamos en el backend e informar fallas en una matriz. Esa parte del software trata con menos solicitudes para que la respuesta no termine siendo una matriz gigantesca ... en teoría.


Un error reciente que ocurrió en un servidor con pocos recursos me hizo volver a mirar el código, y ahora estoy cuestionando mi decisión original, pero esta vez estoy más motivado por las necesidades comerciales que por las mejores prácticas. Si, por ejemplo, fallo la solicitud completa, el usuario tendrá que volver a intentarlo, mientras que si se eliminan varios elementos, el usuario puede finalizar la acción y luego pedirle a un administrador que haga el resto (mientras yo trabajo en solucionar el error !). Este sería el modo permisivo .

Traté de buscar orientación en línea sobre el tema, pero he venido con las manos vacías. Entonces vengo a ustedes: ¿Qué es lo que más se espera de las operaciones masivas de esta naturaleza? ¿Debo seguir estrictamente más, o debería ser más permisivo?

rath
fuente
99
Depende. ¿Cuál es el costo de tener algo que no se elimina cuando debería ser? (El costo se define como datos incorrectos, dolor de cabeza, comportamiento no deseado, el tiempo que le toma a un administrador solucionarlo, etc.) ¿Es eso aceptable? Si puede vivir con las consecuencias de no fallar en todo, hágalo. Si causaría demasiado problema, no lo hagas. Conoces tu software y las consecuencias, por lo que tendrás que hacer un juicio.
Becuzz
1
@Becuzz El costo sería que el usuario notara una o dos sobras y abriera un ticket al respecto; la situación actual es "omg delete is broken". Afortunadamente, el usuario está al final del pasillo, así que esta vez no es un gran problema. El punto es que me gusta hacer lo correcto siempre que sea ​​posible, y con una base de código de más de 10 años, Dios sabe que algunas cosas pueden hacerse correctamente
rath
Creo que esto también depende de si quieres escalabilidad o no. Si no tiene la intención de tener muchas identificaciones, no debería importar demasiado. Si tiene la intención de tener un millón de ID, o mejor aún, no está absolutamente seguro de que no sucederá, entonces podría pasar una hora eliminando las ID solo para que se restablezca por completo debido a 1 ID no válida.
imnota4
1
@ imnota4 Un excelente punto que no había considerado. La interfaz de usuario restringe la solicitud a un máximo de aproximadamente 250, pero el backend no tiene restricción. ¿Puedo pedirle que vuelva a publicar su comentario como respuesta?
rath
1
El modo permisivo también facilita el trabajo de los administradores porque no necesitan reproducir el error con toda la pila de id. También podría ser útil informar en la respuesta la causa de cada error. Mirando la causa, podría ser posible que el usuario final lo resuelva sin tickets de "omg delete is broken".
Laiv

Respuestas:

9

Está bien hacer una versión 'estricta' o 'agradable' de un punto final de eliminación, pero debe decirle claramente al usuario lo que sucedió.

Estamos haciendo una acción de eliminación con este punto final. Probable DELETE /resource/bulk/o algo similar. Yo no soy exigente. Lo que importa aquí es que no importa si decides ser estricto o amable, debes informar exactamente lo que sucedió.

Por ejemplo, una API con la que trabajé tenía un DELETE /v1/student/punto final que aceptaba ID masivas. Regularmente enviamos la solicitud durante las pruebas, recibimos una 200respuesta y asumimos que todo estaba bien, solo para descubrir más tarde que todos los que estaban en la lista todavía estaban EN la base de datos (configurados como inactivos) o no se eliminaron realmente debido a un error que desordenó las llamadas futuras GET /v1/studentporque recuperamos datos que no esperábamos.

La solución a esto vino en una actualización posterior que agregó un cuerpo a la respuesta con los identificadores que no se eliminaron. Esto es, que yo sepa, una especie de mejor práctica.

En pocas palabras, no importa lo que hagas, asegúrate de proporcionar una manera para que el usuario final sepa qué está pasando y posiblemente por qué está sucediendo. Es decir, si elegimos un formato estricto, la respuesta podría ser 400 - DELETE failed on ID 1221 not found. Si elegimos una versión 'agradable', podría ser 207 - {message:"failed, some ids not deleted", failedids:{1221, 23432, 1224}}(disculpe mi pobre formato json).

¡Buena suerte!

Adam Wells
fuente
66
207 Multi-Statuspodría ser apropiado para esa respuesta parcial de falla
Richard Tingle
1
¡AQUÍ VAMOS! ¡Realmente no podía recordarlo! Voy a seguir adelante y actualizar la respuesta con eso, ya que eso está realmente a la altura.
Adam Wells el
2

Uno debe ser estricto y permisivo.

Por lo general, las cargas a granel se dividen en 2 fases:

  • Validación
  • Cargando

Durante la fase de validación, cada registro se examina estrictamente para asegurarse de que cumple con los requisitos de las especificaciones de datos. Uno puede inspeccionar fácilmente 10s de 1000s de registros en solo unos segundos. Los registros válidos se colocan en un nuevo archivo para cargar, los inválidos se marcan y eliminan y generalmente se colocan en un archivo separado (archivo de omisión). Luego se envía una notificación en los registros que fallaron la validación para que puedan ser inspeccionados y diagnosticados con el fin de solucionar problemas.

Una vez que los datos han sido validados, se cargan. Por lo general, se carga en lotes si es lo suficientemente grande como para evitar transacciones de ejecución prolongada o si hay una falla, será más fácil de recuperar. El tamaño del lote depende de qué tan grande sea el conjunto de datos. Si uno solo tiene unos 1000 registros, un lote estaría bien. Aquí puede ser algo permisivo con las fallas, pero es posible que desee establecer un umbral de lote fallido para detener toda la operación. Tal vez si los lotes [N] fallan, uno detendría toda la operación (si el servidor estaba inactivo o algo similar). Por lo general, no hay fallas en este punto porque los datos ya se han validado, pero si se debió a problemas del entorno u otros, simplemente vuelva a cargar los lotes que fallaron. Esto hace que la recuperación sea un poco más fácil.

Jon Raynor
fuente
No valido las ID contra los valores de la base de datos, solo trato de eliminarlos y ver cómo funciona, o me llevaría una eternidad. Abortar después de N fallas parece una sugerencia muy razonable, +1
rath
2

¿Debería un solo fallo fallar una operación masiva?

No hay una respuesta canónica a esto. Es necesario examinar las necesidades y las consecuencias para el usuario, y evaluar las compensaciones. El OP proporcionó parte de la información requerida, pero así es como procedería:

Pregunta 1 : "¿Cuál es la consecuencia para el usuario si falla una eliminación individual?"

La respuesta debería impulsar el resto del diseño / comportamiento implementado.

Si, como se indicó en el OP, es simplemente que el usuario nota la excepción y abre un ticket de problema, pero no se ve afectado (los elementos no eliminados no afectan las tareas posteriores), entonces iría con permisivo con una notificación automática para ti.

Si las eliminaciones fallidas deben resolverse antes de que el usuario pueda continuar, entonces estrictamente es claramente preferible.

Darle al usuario la opción (por ejemplo, esencialmente un indicador de ignorar fallas con el estricto o permisivo como predeterminado) puede ser el enfoque más fácil de usar.

Pregunta 2 : '¿Habría algún problema de coherencia / coherencia de datos si las tareas posteriores se realizan con elementos no eliminados todavía en el almacén de datos?'

Nuevamente, la respuesta conduciría al mejor diseño / comportamiento. Sí -> Estricto, No -> Permisivo, Quizás -> Estricto o Seleccionado por el usuario (particularmente si se puede confiar en el usuario para determinar con precisión las consecuencias).

Kristian H
fuente
0

Creo que esto depende de si quieres escalabilidad o no. Si no tiene la intención de tener muchas identificaciones, no debería importar demasiado. Si tiene la intención de tener un millón de ID, o mejor aún, no está absolutamente seguro de que no sucederá, entonces podría pasar una hora eliminando las ID solo para que se restablezca por completo debido a 1 ID no válida.

imnota4
fuente
-1

Diría que un punto importante aquí es lo que significa que se elimine una gran cantidad de cosas.

¿Estas identificaciones están relacionadas lógicamente de alguna manera, o es solo una conveniencia / rendimiento: agrupación por lotes de estas?

En caso de que de alguna manera, incluso sin conexión, esté conectado, iría por strict. Si es solo un modo por lotes (por ejemplo, el usuario hace clic en "guardar" para sus últimos minutos de trabajo, y solo entonces se transmite el lote), entonces elegiría la permissiveversión.

Como dice la otra respuesta: En cualquier caso, dígale al "usuario" exactamente lo que sucedió.

Martin Ba
fuente