2012-03-28 18 views
6

Estamos notando que se están creando muchos registros duplicados en varias tablas de nuestra base de datos, pero no sabemos por qué ocurre esto. Curiosamente, aunque los registros son duplicados (¡hasta los created_at sellos!), En nuestra tabla de usuarios, la contraseña sal y el hash son diferentes en cada registro, lo que me lleva a creer que de alguna manera Rails está ejecutando transacciones/operaciones de alguna manera dos veces. Obviamente, no estamos llamando save o create varias veces en el código de la aplicación.¿Qué podría hacer que Rails cree registros duplicados?

Esta duplicación no parece ocurrir con cada registro guardado en la base de datos, y parece que aún no podemos inferir un patrón. También hay una validación validates_uniqueness_of en el modelo de usuario (aunque todavía no hay una clave única en la tabla, tenemos que limpiar todos los duplicados para poder hacer eso), por lo que Rails debe detenerse si ya existe un registro, pero si las solicitudes se activan simultáneamente, esa es una condición de carrera.

Actualmente estamos ejecutando Rails 3.2.2 detrás de Passenger 3.0.11/nginx en nuestros servidores de aplicaciones (actualmente 2 de ellos), y tenemos un servidor web nginx central que envía solicitudes en sentido ascendente a un servidor de aplicaciones. ¿Podría esta configuración de algún modo causar procesos duplicados o algo así? ¿Sería importante que las solicitudes no estén bloqueadas en un servidor en sentido ascendente (es decir, si un usuario solicita una página que incluye contenido estático como imágenes, se pueden usar uno o ambos servidores de aplicaciones)? (Siento que está agarrando pajitas, pero quiero cubrir todas las posibilidades)

¿Qué otra cosa podría causar que esto suceda?

Actualización: A modo de ejemplo, un usuario fue creado hoy que obtuvo registros duplicados. Ambos tienen el sello created_at de 2012-03-28 16:48:11, y todas las columnas excepto hashed_password y salt son idénticas. Desde el registro de solicitudes, puedo ver lo siguiente:

servidor de aplicaciones 1:

Started POST "/en/apply/create_user" for 1.2.3.4 at 2012-03-28 12:47:19 -0400 
[2012-03-28 12:47:19] INFO : Processing by ApplyController#create_user as HTML 
[2012-03-28 12:47:20] INFO : Rendered apply/new_user.html.erb within layouts/template (192.8ms) 

Started POST "/en/apply/create_user" for 1.2.3.4 at 2012-03-28 12:48:10 -0400 
[2012-03-28 12:48:10] INFO : Processing by ApplyController#create_user as HTML 
[2012-03-28 12:48:11] INFO : Redirected to apply/initialize_job_application/3517 
[2012-03-28 12:48:11] INFO : /app/controllers/apply_controller.rb:263:in `block (2 levels) in create_user' 

servidor de aplicaciones 2:

Started POST "/en/apply/create_user" for 1.2.3.4 at 2012-03-28 12:48:10 -0400 
[2012-03-28 12:48:10] INFO : Processing by ApplyController#create_user as HTML 

Servidor Web:

1.2.3.4 - - [28/Mar/2012:12:48:10 -0400] "POST /en/apply/create_user HTTP/1.1" 499 0 "en/apply/create_user" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)" "-" 
1.2.3.4 - - [28/Mar/2012:12:48:11 -0400] "POST /en/apply/create_user HTTP/1.1" 302 147 "en/apply/create_user" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)" "-" 

Así que el crear la acción fue golpeada tres veces (volviendo al formulario la primera vez debido a un error, probablemente), y al menos una vez en cada servidor. Los dos últimos son registrados por el servidor web como solicitudes separadas, pero el primero obtiene el código de estado 499 Client Closed Request (una extensión nginx según wikipedia), y el segundo obtiene un 302 como se esperaba. ¿Podría el 499 estar causando los problemas aquí?

+0

¿Ha echado un vistazo a los archivos de registro de la aplicación? Cuando existe un usuario duplicado en el archivo db puede encontrar 2 solicitudes en su archivo de registro correspondiente a ese punto o solo uno? –

+0

No tenemos registros de consulta de rieles completos en producción, solo el registro de solicitud. Sin embargo, los insertos están presentes en el bin-log de mysql y la acción de creación aparece en los registros de solicitud más de una vez por lo menos un ejemplo le (aunque eso probablemente no causaría múltiples * duplicados * exactos, ¿no?) –

+0

¿Pero puede ver pares de solicitudes que coincidan con registros duplicados o solo solicitudes únicas? –

Respuesta

5

Dos posibilidades vienen a la mente.

El primero es un comportamiento extraño (y contra RFC) de Nginx cuando se usa como equilibrador de carga. Volverá a intentar las solicitudes fallidas en el siguiente back-end. El RFC permite eso solo para métodos seguros (por ejemplo, GET o HEAD). El resultado de esto es que si su nginx considera que una solicitud falló por algún motivo, es posible que se vuelva a enviar al siguiente servidor.Sin embargo, si ambos servidores completan su transacción, tienes un registro duplicado. A juzgar por el registro de su servidor web (y el código de estado 499 que utiliza Nginx para indicar que un usuario hace clic en abortar en su navegador), esta parece ser la causa más probable.

La segunda posibilidad es que los usuarios hagan doble clic en el botón Enviar. Con el momento adecuado, sus navegadores podrían enviar dos solicitudes completas casi al mismo tiempo.

Para asegurarse de que sus registros de usuario son realmente únicos, debe crear índices únicos en su base de datos. Estos son luego en realidad aseguraron (aunque con un mensaje de error peor en comparación con el cheque ActiveRecord. Debido a eso, siempre se debe definir su restricción de exclusividad tanto en el esquema de base de datos y sus modelos.

También, usted podría mirar en sustitución de su frontend nginx con un balanceador de carga más conforme. Recomendaría haproxy para eso.

+0

¿Es plausible que la solicitud ya pueda pasar al servidor de aplicaciones desde el servidor web antes de que el usuario anule la solicitud y el servidor web nginx marque la solicitud como '499'? En ese caso, ¿la solicitud continuaría en el servidor de aplicaciones aunque el servidor web sepa que ha finalizado? –

+0

Creo que sí. Una vez que la solicitud fue enviada a Rack, ya no aborta. Entonces, aunque su aplicación-servidor-nginx podría detectar la interrupción de la conexión, su pila de rieles no lo sabe y no puede hacer nada al respecto. Completa su transacción mientras nginx descarta la respuesta.Todo mientras su frontend loadbalancer ya ha redistribuido erróneamente la solicitud. –

+0

Véase también http://www.ruby-forum.com/topic/1674379 –

0

Realmente parece una condición de carrera. Asegúrese de bloquear entre las solicitudes. Podría suceder fácilmente que una o dos solicitudes se dupliquen de vez en cuando. Lo mismo puede suceder al intercambiar elementos sin transacciones, así que asegúrese de no tener una carrera entre sus solicitudes.

+0

¿Qué estaría bloqueando? Creo (corríjanme si estoy equivocado) que Rails y/o mysql bloquean las tablas que están en uso en una transacción: nos hemos encontrado con errores de bloqueo de mysql porque una tabla estuvo bloqueada demasiado tiempo. –

+0

Sí, pero no estoy hablando de las transacciones. Estoy hablando de las solicitudes que se envían a los servidores. Si su administrador de solicitudes no asigna correctamente las solicitudes a los servidores externos, es muy probable que obtenga una condición de carrera, y así duplique las entradas. Depende de cómo haya configurado el administrador, pero creo que es una gran posibilidad. – Spyros

Cuestiones relacionadas