Problemas de concurrencia al insertar registro en base de datos postgresql. (rails 1.1.6)

108 views
Skip to first unread message

Diego Bertaso

unread,
Apr 1, 2015, 10:57:59 AM4/1/15
to rub...@googlegroups.com
Estimados Colegas del Grupo.

En un sistema que se encuentra en producción y que se usa a nivel nacional con muchos usuarios conectados y realizando transacciones en linea se me esta presentando al insertar registros en una base de datos postgresql se presentando un problema, a continuación le presentare  los antecendentes y el problema para ver si ustedes me pueden dar un pista de como resolver el problema.

Antecedentes:

La transacción debe grabar en la base de datos un solicitud de crédito y antes de grabar lee una tabla de parámetros la semilla del número de la solicitud, le suma uno y actualiza la tabla de parámetros, este número se lo asigna a la solicitud y lo graba, todo esto ocurre dentro de un "transactio do".

Lo que esta pasando es lo siguiente, ocurre el evento guardar por el cliente en dos sitios diferentes de la red, el primero toma el parámetro y le suma 1 supongamos que el número obtenido es 5 a continuación entra el otro y toma el 6, entonces la transacción que tiene el numero 6 termina primero e inmediatamente después termina la que tomo el número 5. Entonces ocurre que en el parámetro la semilla queda actualizada con el número 5 y la próxima solicitud cuando la graban da el mensaje de error el "número de solicitud ya existe" porque esta tomando como semilla el número 5 y debería ser 7  por que ya están las solicitudes con el número 5 y 6 grabadas.

Se lo que esta ocurriendo pero no se porqué, tengo una idea y quiero someterla a este foro para saber si estoy en lo cierto u ocurre por alguna otra razón.

Dentro del transaction do se actualizan 3 tablas y por lo que he leído el método transaction do se basa en el modulo Transaction::Simple, lo que no he podido conseguir es la explicación de Simple, no se si es que solo aplica para un objeto(Tabla) o puede envolver a varios objetos(Tablas). De ser así una posible solución seria un transaction do para cada objeto(tabla) que se deba actualizar, pero eso rompería con el principio ACID, porqué si ocurriera un error alguno de los objetos(tablas) quedaría sin actualizar y quedaría la base de datos inconsistente lo que rompería con lo que debería asegurar la transacción.

Le anexo un extracto del código donde ocurre la actualización mencionada al principio.

  def save_new
    begin
      transaction do
     
        #ParametroGeneral.connection.execute("LOCK parametro_general in ACCESS EXCLUSIVE MODE;")
        parametro = ParametroGeneral.find(:first)
  
        if parametro.numero_solicitud_inicial_uea.nil?
            parametro.numero_solicitud_inicial_uea = 50000000
        end

        ultima_solicitud = parametro.numero_solicitud_inicial_uea + 1
        parametro.numero_solicitud_inicial_uea = ultima_solicitud
        parametro.save!
  
        self.numero = ultima_solicitud
        self.save!
        self.crear_recaudos()
        return true
      end
      rescue Exception => e
        return false
    end
  end


Agradeciendo de antemano la información que me puedan aportar, se despide


Diego Bertaso.


cesar sulbaran

unread,
Apr 1, 2015, 2:31:16 PM4/1/15
to rub...@googlegroups.com
DIego este tema lo podrias solucionar utilizando la versión 9.4 de postgresql amigo, y con muy pocos cambios tener los resultados que esperas con vistas materializadas, ya que para esta nueva versión esta disponible los commit sin bloqueos de tablas.

Lo otro que puedes realizar son un mecanismo de alta disponibilidad, en varias instancias postgresql  bien sea utilizando pgpool o cualquer otra herramienta para que puedas separar las capas de lectura y escritura... Aunque la verdad me doy mas por el uso de tecnologias como elasticsearch y considero que implementar un eventstore en ese sistema seria perfecto... Pero representaria cambios muy fuertes.

Lo que mas te puedo sugerirr es realizar un particionamiento de tablas.

___________________________________________________
Ing. Cesar A. Sulbaran P.
Junior Web Developer.
(RUBY AND RUBY ON RAILS)
User 100% Open Sources.
Postgresql dba.

Kernel: 2.6.32-5--bigmen
http://www.google.com/profiles/cesulbaran
___________________________________________________

--
Has recibido este mensaje porque estás suscrito al grupo "RubyVE - Grupo de Usuarios Ruby de Venezuela" de Grupos de Google.
Para anular la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a rubyve+un...@googlegroups.com.
Para acceder a más opciones, visita https://groups.google.com/d/optout.

Carlos Forero

unread,
Apr 1, 2015, 2:55:01 PM4/1/15
to rub...@googlegroups.com
Diego.

Simplemente usa el método lock!:


def save_new
    begin
      transaction do
      
        #ParametroGeneral.connection.execute("LOCK parametro_general in ACCESS EXCLUSIVE MODE;")
        parametro = ParametroGeneral.find(:first)
        parametro.lock!
   
        if parametro.numero_solicitud_inicial_uea.nil?
            parametro.numero_solicitud_inicial_uea = 50000000
        end

        ultima_solicitud = parametro.numero_solicitud_inicial_uea + 1
        parametro.numero_solicitud_inicial_uea = ultima_solicitud
        parametro.save!
   
        self.numero = ultima_solicitud
        self.save!
        self.crear_recaudos()
        return true
      end
      rescue Exception => e
        return false
    end
  end

Edgar Gonzalez

unread,
Apr 1, 2015, 3:46:07 PM4/1/15
to Carlos Forero, rub...@googlegroups.com
Desafortunadamente Diego estas usando una version muy vieja de Rails

Lock esta disponible a partir de la 1.2.0

http://apidock.com/rails/ActiveRecord/Locking/Pessimistic/lock%21

--
Edgar Gonzalez
@edgar
http://gonzalez.io

On April 1, 2015 at 2:55:02 PM, Carlos Forero (ecarlo...@gmail.com) wrote:
> Diego.
>
> Simplemente usa el método lock!:
>
> def save_new
> begin
> transaction do
>
> #ParametroGeneral.connection.execute("LOCK parametro_general in
> ACCESS EXCLUSIVE MODE;")
> parametro = ParametroGeneral.find(:first)
> *parametro.lock!*
>
> if parametro.numero_solicitud_inicial_uea.nil?
> parametro.numero_solicitud_inicial_uea = 50000000
> end
>
> ultima_solicitud = parametro.numero_solicitud_inicial_uea + 1
> parametro.numero_solicitud_inicial_uea = ultima_solicitud
> parametro.save!
>
> self.numero = ultima_solicitud
> self.save!
> self.crear_recaudos()
> return true
> end
> rescue Exception => e
> return false
> end
> end
>
> Más información:
> http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html
>
> >> *transactio do".*Lo que esta pasando es lo siguiente, ocurre el evento
> Para obtener más opciones, visita https://groups.google.com/d/optout.
>

Diego Bertaso

unread,
Apr 1, 2015, 5:15:29 PM4/1/15
to rub...@googlegroups.com
Hola Cesar.


La verdad hermano no entendí nada de lo que me dijiste y no vi la relación con lo que pregunte. Que yo sepa rails 1.1.6 no trabaja bien con postgresql 9.4.

Muchas gracias y saludos.

Diego Bertaso

unread,
Apr 1, 2015, 5:16:30 PM4/1/15
to rub...@googlegroups.com, ecarlo...@gmail.com

Saludos Carlos.

Gracias por la respuesta pero el metodo lock! no esta disponible para la version 1.1.6 de rails.

Edgar Gonzalez

unread,
Apr 1, 2015, 5:44:30 PM4/1/15
to Diego Bertaso, rub...@googlegroups.com, ecarlo...@gmail.com
Diego,

Como comenté antes estas usando una version demasiado vieja de Rails, que yo recuerde la version 1.2 es del 2007, es decir que estamos hablando de antes de esa fecha.

Conseguir soporte para esa version esta extremadamente difícil, de hecho en github la version mas vieja que hay es la 1.2-stable

--
Edgar Gonzalez
@edgar
http://gonzalez.io

jose leonel subero gamarra

unread,
Apr 1, 2015, 6:24:19 PM4/1/15
to rub...@googlegroups.com

Que usas como transporte de tu sistema?  Passenger, mongrel, thin,... Que servidor web? Apache, nginx,...  Te pregunto por que yo tuve un problema parecido y lo solucionamos con vatios cambios sin subir nada

Carlos Forero

unread,
Apr 1, 2015, 7:47:38 PM4/1/15
to rub...@googlegroups.com
Diego.

No leí el título de tu correo completo y tampoco me imaginé que estarías usando una versión tan vieja de rails.

Sin embargo, lo único que tienes que hacer es un bloqueo pesimista sobre el registro que usas para guardar la secuencia en postgresql. Eso en postgres se hace usando la sentencia "FOR UPDATE", este bloqueará el registro durante toda la transacción. A continuación te muestro el código: (NOTA: asumí por el nombre del modelo que el nombre de la tabla es parametro_generales)

def save_new
    begin
      transaction do
      
        parametro = ParametroGeneral.find_by_sql(
          "SELECT * FROM parametro_generales LIMIT 1 FOR UPDATE")
   
        if parametro.numero_solicitud_inicial_uea.nil?
            parametro.numero_solicitud_inicial_uea = 50000000
        end

        ultima_solicitud = parametro.numero_solicitud_inicial_uea + 1
        parametro.numero_solicitud_inicial_uea = ultima_solicitud
        parametro.save!
   
        self.numero = ultima_solicitud
        self.save!
        self.crear_recaudos()
        return true
      end
      rescue Exception => e
        return false
    end
  end

Diego Bertaso

unread,
Apr 1, 2015, 10:08:18 PM4/1/15
to rub...@googlegroups.com
Hola José Subero

Están usando passenger sobre apache2.

Saludos.

Gracias

Diego Bertaso

unread,
Apr 1, 2015, 10:09:18 PM4/1/15
to rub...@googlegroups.com
Hola Carlos.

Muchas Gracias por el dato lo probaré

jose leonel subero gamarra

unread,
Apr 1, 2015, 11:24:11 PM4/1/15
to rub...@googlegroups.com

Nosotros eliminamos todas las auditorías innecesarias, sustituimos apache por nginx, mongrel por passenger, y parcheamos el kernel para aumentar las conexiones límites en debían y listo, cada cosa logró mejorar toda la app

Reply all
Reply to author
Forward
0 new messages