Insert/update masivos

912 views
Skip to first unread message

lonami

unread,
Sep 13, 2012, 7:34:51 AM9/13/12
to ror...@googlegroups.com
Hola, he estado buscando información sobre como cargar grandes cantidades de datos, y he visto algunos hilos muy buenos aquí, con bastante información, sobre todo este:
https://groups.google.com/forum/?fromgroups=#!searchin/ror_es/active$20record/ror_es/7KVvHAjWvG0/VnL5dQ0JzhgJ

Mi situación es la siguiente, diariamente exporto de nuestro sistema las facturas (más de 200.000), y las cargo en una base de datos para una aplicación rails. Hasta ahora leo línea a línea de un fichero, y con activerecord compruebo si ya existe en la base de datos y actualizo los cambios que puedan haber (factura pagada, reclamada, etc...), y sino existe la factura en la BD pues la inserto. La factura está enlazada con varias tablas (formas de pago, clientes, etc...) y necesito mejorar mucho mucho el rendimiento de esta carga de datos. Ahora mismo el tiempo de esta carga excede las 6 horas y media... inaceptable lo mire por donde lo mire.

He estado mirando cargar ficheros CSV directamente en la base de datos, con lo que el tiempo bajaría enormemente, pero necesito comprobar si existe primero, y si necesita una actualización después antes de decidir insertarlo....

Se me ocurre que para las facturas que no estén en la base de datos si puedo ir generando un fichero CSV y cargarlas de golpe, pero no veo cómo podría agilizar el tema de las actualizaciones. ¿Alguna idea?

Gracias!

Jaime Iniesta

unread,
Sep 13, 2012, 7:54:38 AM9/13/12
to ror...@googlegroups.com
El 13 de septiembre de 2012 13:34, lonami <imano...@gmail.com> escribió:
Se me ocurre que para las facturas que no estén en la base de datos si puedo ir generando un fichero CSV y cargarlas de golpe, pero no veo cómo podría agilizar el tema de las actualizaciones. ¿Alguna idea?

¿No podrías hacer que el sistema de facturas comunicara los cambios a la aplicación rails en el momento en que ocurran? Puedes montar una API en tu app rails para esto.
 
--
Jaime Iniesta, freelance web developer
http://jaimeiniesta.com

lonami

unread,
Sep 13, 2012, 8:00:02 AM9/13/12
to ror...@googlegroups.com
Estamos trabajando con SAP y de momento tenemos que conformarnos con actualizar los datos al finalizar la jornada de trabajo.
Sería ideal poder actualizarlo todo en tiempo real, y nos habríamos evitado más de un problema con todo esto.

David J Brenes

unread,
Sep 13, 2012, 8:01:44 AM9/13/12
to ror...@googlegroups.com
Yo tuve un problema parecido hace un tiempo y estuve mirando el tema
de inserts y updates masivos.

Puedes hacer inserts desde otras tablas, así que podrías probar:

- Cargar todo el CSV en una tabla temporal con una instrucción LOAD
DATA de MySQL: http://dev.mysql.com/doc/refman/5.1/en/load-data.html

- Insertar desde esa tabla con un INSERT ... SELECT
(http://dev.mysql.com/doc/refman/5.1/en/insert-select.html) e indicar
que en los duplicados, en lugar de insertar actualice, con un INSERT
... ON DUPLICATE KEY UPDATE
(http://dev.mysql.com/doc/refman/5.1/en/insert-on-duplicate.html)



2012/9/13 lonami <imano...@gmail.com>:
> --
> You received this message because you are subscribed to the Google
> Groups "ror-es" group.
> To post to this group, send email to ror...@googlegroups.com
> To unsubscribe from this group, send email to
> ror_es+un...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/ror_es?hl=en
> Rails no escala.



--


David J.Brenes

"Imagination is more important than Knowledge"

Rafael Valenzuela

unread,
Sep 13, 2012, 8:21:01 AM9/13/12
to ror...@googlegroups.com
Buenas te contesto.
puedes cargar datos masivos mediante un bulk como ha dicho antes el compañero esto a groso modo inyecta directamente en la base da datos la información, esto es muy rápido pero no vas a  poder modificar los datos mientras lo insertas.
Yo normalmente cuando me pasa esto, de ser lento en la inserción  quito los indices y pk antes de la inserción y lo pongo al terminar eso ayuda mucho.
Pero lo que necesitas para mi es un proceso ETL y montar una DW (Datawarehouse) o datamart.
Luego no se que base de datos usas Mysql, Oracle ,etc.. .Si es Mysql que tipo de motor (innodb,etc..) o tabla (tablas federadas,temporales,etc..) estas usando.Si me das mas datos te puedo decir, yo ahora estoy con un sistema en tiempo real insercción por segundo aprox.

Espero haberme explicado

lonami

unread,
Sep 13, 2012, 8:30:19 AM9/13/12
to ror...@googlegroups.com
Miraré bien la opción de "Insert on duplcate key update" a ver si podemos echar mano de ella.
Sowe, estamos usando Postgresql (9.1), sin haber tocado nada de su configuración, está de "fábrica" se puede decir. Haré una carga pero de menos elementos, unos miles, sin indices y pk a ver cómo varía el tiempo.

Gracias a todos por las respuestas.

David J Brenes

unread,
Sep 13, 2012, 8:34:55 AM9/13/12
to ror...@googlegroups.com
El "insert on duplicate" es de mysql. Parece que en postgre hay que tirar de funciones para poder emularlo (aunque es más rico, claro): http://stackoverflow.com/questions/1109061/insert-on-duplicate-update-postgresql

2012/9/13 lonami <imano...@gmail.com>

Samuel Lown

unread,
Sep 13, 2012, 11:08:07 AM9/13/12
to ror...@googlegroups.com
Hola Ionami,

Puedes ir por la opción de importar por CSV, pero sospecho que el problema que tienes es con el IO-wait, más que el CPU. Es decir, la app pasa mucho tiempo esperando a que termina de guardar o buscar cosas por red o al disco duro.

Tienes varias opciones para diminuir los efectos de esto. Lo primero es una buena base de datos con bastante memoria (sobre todo para el caché), y unos indices buenos. Esto ayudará muchísimo a buscar los datos.

Segundo; workers, forking, o multi-threading son tus amigos. Crea un proceso de importación principal que carga los datos del fuente, sin procesar, y manda estos a unos workers/fork/thread para procesar en parallelo. Lo más fácil configurar sería Resque [1] con Redis, y tiene una interfaz de gestión muy chulo y útil, especialmente si hay algún fallo.

Ya nos contarás que tal.

Cheers,
sam 

--
You received this message because you are subscribed to the Google
Groups "ror-es" group.
To post to this group, send email to ror...@googlegroups.com
To unsubscribe from this group, send email to
ror_es+un...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/ror_es?hl=en
Rails no escala.

Gaizka Villate

unread,
Sep 13, 2012, 11:39:39 AM9/13/12
to ror...@googlegroups.com
¡Hola, Imanol!

En The Cocktail hemos utilizado, e iba bastante bien, la gema ar-extensions, para varias aplicaciones (ojo, Rails 2.3.x)

https://github.com/zdennis/ar-extensions/tree/master/ar-extensions

Esto te permite hacer inserts múltiples de manera muy sencilla:

Si tienes unos cuantos objetos A::R en memoria sin guardar, y quieres guardarlos con un insert por cada 500 elementos (dependiendo del tamaño de cada registro, puedes querer reducir este número)

my_objects.in_groups_of(500).each do |group|
import group.compact, :validate => false
end

Nosotros siempre lo hemos utilizado para INSERTS. Pero también permite hacer updates, utilizando por debajo el ON DUPLICATE UPDATE de mysql de manera transparente.

Claro que tú utilizas PostgreSQL, así que esto posiblemente no te sirva de mucho :)

En el README de esta gema ahora hablan de https://github.com/zdennis/activerecord-import

Acabo de echar un vistazo rápido al código, y tiene adapters para PostgreSQL, y varios commits de agosto, así que parece un proyecto vivo ;)

Espero que te sirva.

En cuanto a la solución de Sam, no se si distribuir la importación en 200.000 trabajos de resque/similar te iba a solucionar mucho la vida. Al final ibas a acabar haciendo tus 200.000 inserts o updates, no de manera lineal, pero de manera mucho más complicada ;)

-- Gaizka

El día 13/09/12 04:34 -0700, lonami escribió:

lonami

unread,
Sep 14, 2012, 3:33:28 AM9/14/12
to ror...@googlegroups.com
Hola Gaizka, muchísimas gracias por esa gema, no la conocía y me va a venir muy bien. He hecho una prueba rápida y me convence, ahora le toca cumplir ante la carga real, pero con 2.500 elementos se comportó muy bien, menos de 2 minutos.

Ahora mismo he refactorizado el código. Con esto y con activerecord-import he lanzado una prueba de carga real, a ver cómo se comporta, en cuanto termine os cuento los resultados. Si esto no lo rebaja drásticamente, empezaremos a pensar en colas de tareas, etc...

Alguien preguntaba por los requerimientos (aunque ahora no veo el mensaje por aquí, sólo en el correo). La base de datos hará más operaciones de escritura que de lectura, ya que la carga diaria de información sobrepasa con creces el número de consultas ejecutadas sobre ella.
La máquina en la que está corriendo es un Xeon, con 8 núcleos (2 procesadores), 4 GB ram y un raid. Corre un windows 2008 server (esto no me han dejado cambiarlo).

Muchas gracias nuevamente por todos los comentarios.

Salduos.

Raúl Alexis Betancor Santana

unread,
Sep 14, 2012, 4:42:27 AM9/14/12
to ror...@googlegroups.com
El 14 de septiembre de 2012 08:33, lonami <imano...@gmail.com> escribió:
Alguien preguntaba por los requerimientos (aunque ahora no veo el mensaje por aquí, sólo en el correo). La base de datos hará más operaciones de escritura que de lectura, ya que la carga diaria de información sobrepasa con creces el número de consultas ejecutadas sobre ella.
La máquina en la que está corriendo es un Xeon, con 8 núcleos (2 procesadores), 4 GB ram y un raid. Corre un windows 2008 server (esto no me han dejado cambiarlo).

En mi opinión ... muy, muy, muy poca memoria y pésimo sistema operativo, para correr un proceso que según cuentas, tiene que manejar más de 200.000 entradas diarias.

Por ponerte un símil ... ese servidor es como un ferrari con el deposito de gasolina de un seat 600 ... nada más le pisas el acelerador te quedas con el tanque seco.

Saludos 

Imanol

unread,
Sep 14, 2012, 4:49:01 AM9/14/12
to ror...@googlegroups.com
Hola Rúl, lo sé. Va muy limitado y el SO es demasiado pesado, pero no tengo otra opción, ni en cuanto a hardware ni en cuanto al SO, esto es impuesto. Es algo desesperante creeme...


--
You received this message because you are subscribed to the Google
Groups "ror-es" group.
To post to this group, send email to ror...@googlegroups.com
To unsubscribe from this group, send email to
ror_es+un...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/ror_es?hl=en
Rails no escala.



--


//  Imanol Cea González



Samuel Lown

unread,
Sep 14, 2012, 6:30:46 AM9/14/12
to ror...@googlegroups.com
En cuanto a la solución de Sam, no se si distribuir la importación en 200.000 trabajos de resque/similar te iba a solucionar mucho la vida. Al final ibas a acabar haciendo tus 200.000 inserts o updates, no de manera lineal, pero de manera mucho más complicada ;)

Jaja que no tengas miedo Gaizka, resque no muerde :-)

Tu solución esta bien, pero estás pensando en el corto plazo; a final es un hack que no va a escalar. Tarde o pronto vas a tener el mismo problema cuando llega a 2,000,000 entradas. Con resque escalas con más workers y listo. Una combinación de ambos podría ser interesante también.

Ahora, la pregunta es si algún día llegará a 2,000,000 entradas...

Cheers,
sam



 

-- Gaizka

El día 13/09/12 04:34 -0700, lonami escribió:
> Hola, he estado buscando información sobre como cargar grandes cantidades
> de datos, y he visto algunos hilos muy buenos aquí, con bastante
> información, sobre todo este:
> https://groups.google.com/forum/?fromgroups=#!searchin/ror_es/active$20record/ror_es/7KVvHAjWvG0/VnL5dQ0JzhgJ
>
> Mi situación es la siguiente, diariamente exporto de nuestro sistema las
> facturas (más de 200.000), y las cargo en una base de datos para una
> aplicación rails. Hasta ahora leo línea a línea de un fichero, y con
> activerecord compruebo si ya existe en la base de datos y actualizo los
> cambios que puedan haber (factura pagada, reclamada, etc...), y sino existe
> la factura en la BD pues la inserto. La factura está enlazada con varias
> tablas (formas de pago, clientes, etc...) y necesito mejorar mucho mucho el
> rendimiento de esta carga de datos. Ahora mismo el tiempo de esta carga
> excede las 6 horas y media... inaceptable lo mire por donde lo mire.
>
> He estado mirando cargar ficheros CSV directamente en la base de datos, con
> lo que el tiempo bajaría enormemente, pero necesito comprobar si existe
> primero, y si necesita una actualización después antes de decidir
> insertarlo....
>
> Se me ocurre que para las facturas que no estén en la base de datos si
> puedo ir generando un fichero CSV y cargarlas de golpe, pero no veo cómo
> podría agilizar el tema de las actualizaciones. ¿Alguna idea?
>
> Gracias!
>

--
You received this message because you are subscribed to the Google
Groups "ror-es" group.
To post to this group, send email to ror...@googlegroups.com
To unsubscribe from this group, send email to
ror_es+un...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/ror_es?hl=en
Rails no escala.



--
www.samlown.com
www.autofiscal.com
www.planetaki.com/sam

javier ramirez

unread,
Sep 14, 2012, 7:39:39 AM9/14/12
to ror...@googlegroups.com

On 09/13/2012 12:34 PM, lonami wrote:
Hola, he estado buscando información sobre como cargar grandes cantidades de datos, y he visto algunos hilos muy buenos aquí, con bastante información, sobre todo este:
https://groups.google.com/forum/?fromgroups=#!searchin/ror_es/active$20record/ror_es/7KVvHAjWvG0/VnL5dQ0JzhgJ

Hola,

Una idea. En lugar de centrarte en la mejor manera de consultar tanto dato, igual puedes ir por la opción de reducir el número de consultas que tienes que hacer. Para esto lo que haría sería intentar evitar acceder a la base de datos excepto para lo estrictamente necesario, y usar un almacén de datos alternativo que sea muy rápido para saber qué facturas necesitas cambiar, cuáles necesitas insertar, y cuáles no necesitas tocar para nada. Estoy pensando en Redis.

Para cada factura, me inventaría un algoritmo de checksum que básicamente combine todos los campos susceptibles de cambios (estado de la factura, y no sé si tenéis más campos que puedan cambiar). En tu aplicación rails, tendrías un callback que después de cada actualización de factura, te recalcula el checksum y lo guarda en redis.

Cuando descargas el fichero diario, deberías parsear el fichero, calcular el checksum de cada fichero y pedirle a redis los checksum actuales. Redis es megarápido y te da ese tipo de consultas en menos de lo que dices constantinopla. Gestionar 200.000 lecturas e incluso escrituras en redis es muy rápido. Podrías llegar a hacer cosas como almacenar en diferentes listas de redis el checksum de la db y el checksum del fichero y pedirle directamente la diferencia entre esas listas. Y lo tendrías en el orden de segundos para esa cantidad de datos.

Además, si en redis no aparece el checksum para ese número de factura, significa que la factura no existe y te toca hacer un insert completo.

Entonces, simplemente preguntando a redis, tienes el listado de facturas que son nuevas y puedes cargar en modo bulk de manera eficiente, y el listado de facturas que están modificadas y que tienes que actualizar una por una. Todo esto lo has adivinado sin haber accedido ni una sola vez a tu base de datos. A partir de ese punto, el proceso de update es el que es y te va a llevar el tiempo que sea necesario, pero seguramente tendrás un número de facturas manejable como para que no sea un problema de rendimiento.

Una vez tengas esto, podías llegar a diferentes optimizaciones como guardar directamente en redis una copia de todos los campos que pueden cambiar en una factura, y preguntar directamente por cada valor individual para luego poder hacer updates a saco del estilo "actualízame las facturas con id x,y,z y pon el estado a pagado". Esto te evitaría tener que hacer lecturas de tu db y sería mucho más rápido.

Redis necesita guardar la información en memoria. Normalmente no necesita demasiada memoria, pero todo depende de tu dataset. Te tocaría echar un cálculo de qué estructuras necesitas y si tu instalación actual lo soporta con todo lo que ya tengas corriendo en esa máquina.

Saludos,

j

lonami

unread,
Sep 18, 2012, 7:31:24 AM9/18/12
to ror...@googlegroups.com
Hola Javier, muchas gracias por tu aportación también, muy interesante todo lo que comentas.

No dudo que más adelante tendré que dar una vuelta de tuerca y agilizar más este asunto, pero de momento, usando activerecord-import y minimizando los acceso a la base de datos, he pasado de casi 7 horas a apenas 15 minutos. Aún es muy optimizable, pero lo que nos urge es tenerlo operativo ya, así que las optimizaciones tendrán que esperar un poco.

En un futuro a corto plazo creo que optaré por activerecord-import más algún sistema de colas, cuando tenga resultados los pondré aquí por si a alguien le sirve.

Saludos.

Rafael Valenzuela

unread,
Sep 20, 2012, 3:30:08 PM9/20/12
to ror...@googlegroups.com
Hola,
La verdad es que me ha gustado la respuesta de Javier, muy interesante, pero de todas maneras yo creo que puedes exprimir mucho mas postgre que al final sera tu cuello de botella, ¿no?.
Saludos

--
You received this message because you are subscribed to the Google
Groups "ror-es" group.
To post to this group, send email to ror...@googlegroups.com
To unsubscribe from this group, send email to
ror_es+un...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/ror_es?hl=en
Rails no escala.



--
Reply all
Reply to author
Forward
0 new messages