Como evitar asignar un mismo registro a dos usuarios diferentes, cuando existen llamadas concurrentes.

Hola amigos, necesito de su ayuda para solventar un problema, el cual no se presenta a diario pero de igual forma debo solventarlo.
He desarrollado un Sistema de Atención a Clientes por Turnos, se le entrega un ticket al usuario, este pasa a sala de espera y mediante audio y video es llamado para que se acerque a la ventanilla donde será atendido, el número de ventanillas disponibles por cada tipo de servicio es de aproximadamente 6, por lo cual la concurrencia o el número de peticiones que pueden ser enviadas para atender a 1 cliente en espera también es de 6, por lo que en ocasiones el sistema ha asignado un mismo cliente a 2 ventanillas diferentes, obligando a una de ellas finalizar el proceso.

La lógica que actualmente le he dado al sistema para asignar un ticket a la ventanilla que lo solicita es muy sencilla.

1. Verifica que existan tickets disponibles para el tipo de servicio que atiende la ventanilla
2. En caso de existir, retorna el ID del primero en turno y lo asigna a la ventanilla que lo solicitó.

Les comento no realizo bloqueos a registros en ningún momento de la lectura, el metodo que retorna la existencia o no de un ticket disponible lo ejecuta cada usuario a demanda, razón por la cual en ocasiones el sistema asigna el mismo registro a 2 usuarios diferentes.

He tenido ideas como tener una especie de DAEMON que se ejecute de forma separada y que su función únicamente sea la de estar verificando una ytabla en la cual se inserten las peticiones y este procedimiento realice los pasos descritos anteriormente, pero no se si eso funcione.

Por lo que acudo a sus conocimiento.

Gracias.

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de ezamudio

synchronized

La solución rápida y puerca es marcar el método del ticket disponible como `synchronized`, para que sólo un hilo lo ejecute a la vez. A menos que cada ventanilla sea un proceso separado y por eso mencionas lo del daemon...

Otra solución es que simplemente hagas un bloqueo en la base de datos (estoy suponiendo que estás usando una base de datos relacional como mysql o postgres):

SELECT * FROM ticket WHERE tipo=el_tipo_que_me_interesa LIMIT 1 FOR UPDATE;
UPDATE ticket SET STATUS=asignado,ventanilla=ventanilla_a_donde_lo_mandas WHERE clave=clave_del_ticket_devuelto;
Imagen de hrmena

synchronized - ezamundio

Agradezco mucho tu respuesta.

El escenario es el siguiente:

Servidor de aplicaciones es un Glassfish 3.1.2 con una sola instancia
Servidor de base de datos Oracle 11g.

Efectivamente, todas las oficinas que lo utilizan se conectan al mismo sitio y cada ventanilla es un proceso separado, por lo que la tabla donde se están depositando los tickets disponibles es utilizado desde diferentes ubicaciones geográficas, tanto para insertar los nuevos tickets como para consultar los que están en espera, estas consultas son hechas por los módulos de cada ventanilla y por el administrador de la sucursal.

Mi temor es que el bloquear la tabla, a pesar de ser milésimas de segundos, pudiese causar algún problema con la carga de trabajo en producción.

Cómo ves la idea de un proceso que se ejecute por separado, el cual únicamente esté consultando una tabla que simula una cola, donde se inserten las solicitudes de asignación de tickets y que el mismo proceso ubique el primer Ticket disponible, según sea el proceso de la ventanilla, y así continué con los siguientes...

Todo comentario es bienvenido.

Imagen de ezamudio

por qué tabla?

Si vas a hacer un proceso separado, algo centralizado al cual todas las ventanillas se conecten, no necesitas ni siquiera una tabla. Simplemente encolas en memoria todas las peticiones para irles dando un ID y ya.

Puedes usar CTM para intentar obtener un ticket, y así no bloqueas la tabla:

1. Tu tabla tiene una columna ATIENDE donde le pones el número de ventanilla que atiende el ticket en ese momento. Los que no han sido atendidos lo tienen en null.
2. SELECT * FROM ticket WHERE ATIENDE IS NULL ORDER BY fecha_de_llegada LIMIT 1
3. UPDATE ticket SET ATIENDE=mi_clave_de_ventanilla WHERE clave=clave_leida_en_paso_dos AND ATIENDE IS NULL
4. Si el UPDATE te dice que sí se hizo, entonces atiendes ese ticket. Si te dice que NO se hizo, es que ese ticket lo apañó alguien más; en ese caso regresas al paso 2, puedes hacer un loop donde intentes esto varias veces (CTM por default lo hace 100mil veces).

Imagen de hrmena

Muy buena idea

Nuevamente agradezco tus comentarios, trabajaré lo que me has sugerido, en caso de funcionar o bien encontrar una solución adicional la compartiré.

Imagen de Nopalin

Con tickets disponibles a que

Con tickets disponibles a que te refieres? hay una tabla donde se insertan registros para indicar que son todos los que se pueden atender durante un turno? o tienes una configuración de un valor tope que son los tickets que se pueden crear mientras se van solicitando?

Les busques por donde le busques, se hará un bloqueo, ya sea a nivel de programación con synchronized o a nivel de base de datos con lock, asi que te recomiendo el camino que se te haga mas simple.

Yo en lo particular crearia un indice unico con algunas columnas, en el metodo cacho la exception y se repite hasta que se logre.

Saludos

Imagen de hrmena

Con tickets disponibles a que... - Nopalin

Buenas amigo, al decir Tickets disponibles, me refiero a todos aquellos tickets que están en espera de ser atendidos, ya sea por que recién llegan a la institución o porque han sido derivados de un proceso a otro y efectivamente tanto los que están siendo atendidos como los que están en espera se encuentran en una misma tabla pero hay un estado que separa cada uno de ellos, en cuanto a limites o valores topes te comento que hay límites.

Como le comenté al amigo ezamudio, y en la cual estoy trabajando un poco sin dejar de lado las ideas que me ha compartido, me estoy inclinando en tener una tabla en la cual se insertan las peticiones de asignación de turno (atender al siguiente en cola), que los usuarios de ventanilla realizan, y tener un daemon el cual está verificando, en la tabla antes mencionada, si existen registros que procesar. Como el daemon es un proceso único que se ejecuta por separado éste atenderá cada petición de forma individual y en caso de no existir turnos o tickets pendientes de ser atendidos retornará NULL, caso contrario retornará el id único del tickets que asignó a la ventanilla.

Los mantendré al tanto de las pruebas que pueda realizar este fin de semana.

Gracias.

Imagen de Nopalin

En realidad lo que vas a

En realidad lo que vas a implementar es una estructura tipo cola, donde el thread de cada petición de las ventanilla llamará al método pop para obtener el siguiente ticket disponible o nulo si no existe. La estructura obviamente será thread safe por lo que utilizará en uno o varios puntos el keyword synchronized, lo cual me parece bien, para eso fué creado.

Saludos me parece bastante buena tu solución.

Imagen de adrianaaae

Mejor la opcion sugerida por @Ezamudio

La mejor opción sugerida en mi humilde sugerencia es el argumentado por @Ezamudio, donde puedes utilizar banderas para marcar
los tickets asignados a alguna ventanilla.
Solo seria añadir a tu tabla una columna mas la cual seria noVentanilla y que esta pueda aceptar valores nulos; y cuando los asignes se le añada
el numero de ventanilla(el numero de ventanilla te podria servir para fines historicos en caso de tener la necesidad de saber en que ventanilla fue atendido el cliente) y cuando vayas por tus tickets pendientes a atender tendrias que consultar los que tiene la columna noVentanilla = null( where noVentanilla is null) y listo.