Delayed Job vs. Resque vs. Sidekiq (y algo de GIL al final) - Consulta con ejemplo práctico

313 views
Skip to first unread message

Juan Roldan

unread,
Mar 7, 2014, 8:32:54 PM3/7/14
to rub...@googlegroups.com
Buenas noches rubystas queridos,

hoy me plantié un dilema que quiero compartir con ustedes, de paso aprovecho para comentarles mis pensamientos y hacer una que otra consulta:

Mi aplicación está corriendo con Rails 4 y Ruby 2.0.0.
En 3 meses aprox. la cantidad de usuarios será de 20.000. La verdad que por ahora escapa a mis conocimientos predecir cuántos de ellos serán concurrentes o no,
pero es mejor un número antes que nada.

Hace horas que estoy leyendo sobre DJ, Resque y Sidekiq, ventajas y desventajas, terminología más simple que otra, etc etc.

Quiero elegir uno de los 3 para controlar las tareas realizadas en background por mi aplicación (sin ningún orden en particular),
que se dividen en 3 categorías (bien podrían ser 3 queues :) )

"Notifications" - Similar al estilo de notificaciones diarias de Facebook que nos avisan sobre comentarios en fotos, etiquetas, relaciones, etc.
                          Valga la aclaración, éstas notificaciones no estarían siendo usadas por mail.
                          Simplemente impactarían en la BD y ésa información se usaría para mostrarse en una sección del usuario como ser "NewsFeed".
"Linkedin"       - Permiten a la aplicación, una vez obtenida la autorización del usuario, importar datos personales de su cuenta en Linkedin: skills, work history, languages, etc.
"Emails"          - El clásico envío de email cuando el usuario se: loguea en el sitio, solicita un desbloqueo del password, se suscribe al sitio, etc. 

Mi razonamiento es el siguiente:
1.- Descarto DJ, por el simple hecho de que requiere usar mi BD para "manejar" todo lo referido a su funcionamiento, es decir, decidir qué Job procesar a continuación,
     almacenar el estado del Job, etc., cuando bien podría NO usar mi BD para almacenar ésa información.
     Con 20.000 usuarios realizando alguna o todas de las 3 actividades descritas arriba, suena lógico no elegir DJ.

2.- Aquí es donde encuentro el conflicto: Usar exclusivamente Resque vs. Usar Sidekig (que se basa en Resque)
     Y la mejor fuente que encontré hasta ahora está aquí: https://github.com/mperham/sidekiq/wiki/FAQ
     Los posts más actuales y "serios" recomiendan Sidekiq debido a que es más rápido que Resque principalmente, dado que Sidekiq procesos "multi-threaded" para manejar los jobs en background
     y Resque usa procesos "single-threaded". De ésta forma, procesar 30 notificaciones en paralelo requeriría 20 procesos (con Resque) y 1 proceso con 20 threads (con Sidekiq).

    Estando a punto de comenzar a implementar Sidekiq, encontré éste bienemérito post:
    http://www.sitepoint.com/comparing-background-processing-libraries-sidekiq/

    Extrapolando la sección del artículo que me genera confusión:
    "Sidekiq is easy enough if you learned Resque, but it has a pretty big “problem” that we haven’t discussed yet: thread safety.
     Since Sidekiq uses threads, you can only uses libraries in Sidekiq if they are thread safe – anything else will likely cause a ton of problems that are difficult to track down.
     This really limits what you can do with Sidekiq."

     Secondly, you must ensure that your code is thread safe (e.g. global variables are a no-go).
     Ruby (rather, the “default” ruby interpreter which is MRI) also has something called an “interpreter lock”, which means that only one thread can run at a time,
     so Sidekiq will work much better with alternative implementations of Ruby, such as Rubinus and JRuby. "

3.- Concluyo que, en Ruby, Sidekiq no presentaría ventaja alguna de performance, dado que el GIL (Global Interpreter Locker) estaría limitando la ventaja fundamental de Sidekiq,
     la cual es trabajar con múltiples threads en paralelo dentro de 1 proceso mejorando la performance y la utilización de recursos (memoria) dentro de la aplicación.
     
     Lo cual me deja a elegir Resque, el único "vencedor" y con una interfaz muy buena para ver los jobs pendientes, procesados y borrados.

Preguntas: (al fin),
a) es la elección correcta? dadas las condiciones que les comenté más arriba.
b) si confundí algún concepto o lo malinterpreté, serían tan amables de poder aclarármelo? Sigo leyendo artículos en internet y obtengo el mismo enfoque.

Espero sus opiniones,

Un abrazo!

Ing. Juan Roldán


     



Michel Martens

unread,
Mar 7, 2014, 10:49:09 PM3/7/14
to rub...@googlegroups.com
Los tres tienen defectos graves. El principal es que si el worker
se muere, el job se pierde para siempre. Sólo la versión paga de
Sidekiq usa BRPOPLPUSH para crear un backup de los items que toma
de Redis, y no me parece bien pagar por una cuenta de Sidekiq Pro.

Por ese mismo motivo, prefiero correr procesos distintos para cada
worker en vez de threads, así si se muere el worker la pérdida es
menor. Además, al crear muchas conexiones a Redis lo que puede
ocurrir es que la latencia sea muy grande. Redis es single threaded,
entonces si hay un timeout configurado del lado del cliente, hay
que  tener en cuenta los tiempos de ejecución promedio de todos los
clientes para ver si hay riesgo de que una conexión se encuentre
con un timeout. Lo mejor para paralelizar este tipo de trabajos es
usar varias instancias de Redis, así cada una usa un core distinto.

Tampoco me gusta el enfoque de DelayedJob, Resque y Sidekiq, fijate
lo que escribí acá:

https://github.com/soveran/ost#differences-with-delayedjob-and-resque

Fijate también esto: "Remember that Sidekiq will run your jobs AT
LEAST once so they need to be idempotent." Tené en cuenta eso al
momento de implementarlo, para no mandar dos veces el mismo email
y errores similares.

Juan Roldán

unread,
Mar 8, 2014, 2:11:49 PM3/8/14
to rub...@googlegroups.com
Muchas gracias Michel por tu respuesta!

Muy específica y concreta. Ya tengo un branch en mi proyecto para probar ésa gema.

Ahora lo que me queda por averiguar es por qué Sidekiq es uno de los sistemas más usados
a pesar de que hay mucho en juego en caso de que el worker se muera y 
teniendo al Global Interpreter Locker que limite la ventaja fundamental de Sidekiq:
procesar trabajos usando threads.

Seguiré investigando y cualquier conclusión interesante la comparto con el grupo.-

Muchas gracias!

Un abrazo.

Juan.



--
Has recibido este mensaje porque estás suscrito a un tema del grupo "rubysur" de Grupos de Google.
Para anular tu suscripción a este tema, visita https://groups.google.com/d/topic/rubysur/3SEHttTxQjY/unsubscribe.
Para anular tu suscripción a este grupo y a todos sus temas, envía un mensaje a rubysur+u...@googlegroups.com.
Para acceder a más opciones, visita https://groups.google.com/d/optout.



--
Ing. Juan Roldán

“Change before you have to” - Jack Welch

Michel Martens

unread,
Mar 8, 2014, 2:33:28 PM3/8/14
to rub...@googlegroups.com
Sin el global interpreter lock vas a tener mejor performance usando
threads, así que si vas por ese lado tal vez te convenga usar jruby
o rubinius. Como en este caso hay mucho I/O, incluso con MRI
deberías tener buen rendimiento.

No sé por qué razones la mayoría usa Sidekiq, pero una idea puede
ser que en plataformas como Heroku, que cobran por proceso, es más
conveniente desde lo económico ejecutar threads.

De todas formas, es importante distinguir entre popularidad y
calidad. La popularidad no es medida de calidad.

Juan Roldán

unread,
Mar 8, 2014, 6:53:31 PM3/8/14
to rubysur
Muchas gracias por el feedback,
eso se valora mucho en el grupo y fomenta una accionar similar a los demas usuarios.

2014-03-08 16:33 GMT-03:00 Michel Martens <sov...@gmail.com>:
Sin el global interpreter lock vas a tener mejor performance usando
threads, así que si vas por ese lado tal vez te convenga usar jruby
o rubinius.  Como en este caso hay mucho I/O, incluso con MRI
deberías tener buen rendimiento.

No sé por qué razones la mayoría usa Sidekiq, pero una idea puede
ser que en plataformas como Heroku, que cobran por proceso, es más
conveniente desde lo económico ejecutar threads.
Coincido con vos Michel.

De todas formas, es importante distinguir entre popularidad y
calidad. La popularidad no es medida de calidad.
Tal cual.

Muchas gracias por los consejos y la ayuda!

Estare posteando mis resultados y las dificultades que tuve seguramente.

Saludos!

Juan.

--
Has recibido este mensaje porque estás suscrito a un tema del grupo "rubysur" de Grupos de Google.
Para anular la suscripción a este tema, visita https://groups.google.com/d/topic/rubysur/3SEHttTxQjY/unsubscribe. Para anular la suscripción a este grupo y todos sus temas, envía un correo electrónico a rubysur+u...@googlegroups.com.
Para obtener más opciones, visita https://groups.google.com/d/optout.

Leo Soto M.

unread,
Mar 8, 2014, 8:00:10 PM3/8/14
to rub...@googlegroups.com
Hola!

Yo diría que hay dos temas a revisar:

1) Muchas cosas en el mundo Ruby *si* son thread-safe. Si no estás usando una librería que sea conocida por tener problemas de thread-safety, no debieras llegar y asumir que es un problema. 

2) El GIL evita que en MRI se esté ejecutando código *ruby* al mismo tiempo. Pero si tus jobs hacen I/O (que seguro lo hacen!) eso permite que mientras un thread está en espera de I/O se ejecuten los otros threads. Si bien nunca vas a exprimir toda la CPU, al menos vas a usarla *mucho* más que en un modelo donde ejecutas secuencialmente cada job y se queda ocioso cuando hay I/O. En resque uno puede lanzar varios workers en una misma máquina, pero el consumo de memoria será considerablemente más que el equivalente con sidekiq.

En la práctica todo depende de qué hacen tus jobs. Si involucran una cantidad no trivial de I/O (¡que es lo típico!) entonces incluso en MRI Sidekiq te va a permitir ejecutar más cosas con los mismos recursos. Y luego cambiarse a JRuby (por ejemplo) no es difícil si realmente se necesita exprimir todos los cores/CPUS. Esto es lo que hicimos en el proyecto en que actualmente estoy trabajando y todo ha funcionado bastante bien. 

Por otro lado, si el throughput que consigues sacar con resque es suficiente para tu problema, pues entonces tal vez sea la mejor opción. Pero es importante entender que el GIL no neutraliza completamente la concurrencia de los threads.


--
Has recibido este mensaje porque estás suscrito al grupo "rubysur" de Grupos de Google.
Para anular tu suscripción a este grupo y dejar de recibir sus mensajes, envía un mensaje a rubysur+u...@googlegroups.com.
Para acceder a más opciones, visita https://groups.google.com/d/optout.



--
Leo Soto M.
http://blog.leosoto.com

Juan Roldán

unread,
Mar 9, 2014, 2:54:15 PM3/9/14
to rubysur
Hola Leo!

muchas gracias por tu aporte :)


2014-03-08 22:00 GMT-03:00 Leo Soto M. <leo....@gmail.com>:
Hola!

Yo diría que hay dos temas a revisar:

1) Muchas cosas en el mundo Ruby *si* son thread-safe. Si no estás usando una librería que sea conocida por tener problemas de thread-safety, no debieras llegar y asumir que es un problema.
Como les comenté en mi primer post, una de las tareas a realizar en background sería importar información del usuario de su cuenta de Linkedin y almacenarla en mi BD.
Todavía no encontré a nadie en internet que haya tenido problemas del tipo 'thread-safe' con la gema 'linkedin' para hacer ésto.

2) El GIL evita que en MRI se esté ejecutando código *ruby* al mismo tiempo. Pero si tus jobs hacen I/O (que seguro lo hacen!) eso permite que mientras un thread está en espera de I/O se ejecuten los otros threads. Si bien nunca vas a exprimir toda la CPU, al menos vas a usarla *mucho* más que en un modelo donde ejecutas secuencialmente cada job y se queda ocioso cuando hay I/O. En resque uno puede lanzar varios workers en una misma máquina, pero el consumo de memoria será considerablemente más que el equivalente con sidekiq.
Totalmente de acuerdo. Me lleva a pensar que el punto débil de Rescue es ése casualmente, el consumo de memoria por la cantidad de workers implementados.

En la práctica todo depende de qué hacen tus jobs. Si involucran una cantidad no trivial de I/O (¡que es lo típico!) entonces incluso en MRI Sidekiq te va a permitir ejecutar más cosas con los mismos recursos. Y luego cambiarse a JRuby (por ejemplo) no es difícil si realmente se necesita exprimir todos los cores/CPUS. Esto es lo que hicimos en el proyecto en que actualmente estoy trabajando y todo ha funcionado bastante bien.
Buen punto. Dado que los datos a importar por usuario son bastantes: perfil_basico, lenguajes, historial de trabajo y skills, ésto daría tiempo a los otros threads a ejecutarse.

Por otro lado, si el throughput que consigues sacar con resque es suficiente para tu problema, pues entonces tal vez sea la mejor opción. Pero es importante entender que el GIL no neutraliza completamente la concurrencia de los threads.
Dato interesante. Ésa era la última duda que tenía, qué tanto impedía/disminuía Ruby la performance cuando usamos threads.

Muchas gracias por tus palabras y opiniones Leo! Se valora mucho.

Creo que ya tengo información suficiente para analizar la implementación. Apenas tenga algo estable funcionando volveré con mis conclusiones :)

Un abrazo!

Juan.

--
Has recibido este mensaje porque estás suscrito a un tema del grupo "rubysur" de Grupos de Google.
Para anular tu suscripción a este tema, visita https://groups.google.com/d/topic/rubysur/3SEHttTxQjY/unsubscribe.
Para anular tu suscripción a este grupo y a todos sus temas, envía un mensaje a rubysur+u...@googlegroups.com.

Para acceder a más opciones, visita https://groups.google.com/d/optout.



--

Leo Soto M.

unread,
Mar 9, 2014, 2:58:35 PM3/9/14
to rub...@googlegroups.com
2014-03-09 15:54 GMT-03:00 Juan Roldán <malw...@gmail.com>:

[...]

Todavía no encontré a nadie en internet que haya tenido problemas del tipo 'thread-safe' con la gema 'linkedin' para hacer ésto.

Esto es off-topic, pero te aprovecho de pasar el dato de que la gema linkedin no soporta OAuth2 (me acaba de tocar lidiar con esto): <https://github.com/hexgnu/linkedin/issues/178>. Dependiendo de tu caso de uso esto puede ser un problema o no. Todo depende como estés haciendo la autorización y autenticación.

Juan Roldán

unread,
Mar 9, 2014, 3:23:14 PM3/9/14
to rubysur
Interesante,


Entiendo. No me percaté de ése problema hasta ahora que lo mencionás, dado que no tuve problemas entre Linkedin y OAuth2 por el momento, 
Listando las gemas instaladas (development y production) veo:

oauth (0.4.7)
oauth2 (0.8.1) 

Supongo que se está usando la primera con Linkedin, porque la segunda la estoy usando para manejar el Login con Google+.

Igualmente, la implementación que estoy pensando para hacer dentro del Job encargado de importar los datos del usuario necesitaría hacer la llamada:

client = LinkedIn::Client.new(access_token, secret_token)
client.authorize_from_access(user_access_token, user_access_secret)

Básciamente, de aquí surgió mi duda sobre si la librería era thread-safe o no.

Juan.

--
Has recibido este mensaje porque estás suscrito a un tema del grupo "rubysur" de Grupos de Google.
Para anular tu suscripción a este tema, visita https://groups.google.com/d/topic/rubysur/3SEHttTxQjY/unsubscribe.
Para anular tu suscripción a este grupo y a todos sus temas, envía un mensaje a rubysur+u...@googlegroups.com.
Para acceder a más opciones, visita https://groups.google.com/d/optout.

Leo Soto M.

unread,
Mar 9, 2014, 3:25:21 PM3/9/14
to rub...@googlegroups.com
El punto allí es que client.authorize_from_access recibe token/secret de OAuth1. Por lo que ojo con como obtienes el token del usuario cuando se autentica y te autoriza (Si lo estás obteniendo con OAuth2, entonces la gema linkedin no te sirve).

Juan Roldán

unread,
Mar 9, 2014, 3:33:05 PM3/9/14
to rubysur
Hace un par de meses que la autenticación funciona bastante bien.
Estoy almacenando el par token/secret de acuerdo al servicio del cual los obtengo. Y es ése par el que uso si quiero autenticarme con Linkedin, Google, etc.

Muchas gracias por la aclaración Leo!

Un abrazo.


--
Has recibido este mensaje porque estás suscrito a un tema del grupo "rubysur" de Grupos de Google.
Para anular tu suscripción a este tema, visita https://groups.google.com/d/topic/rubysur/3SEHttTxQjY/unsubscribe.
Para anular tu suscripción a este grupo y a todos sus temas, envía un mensaje a rubysur+u...@googlegroups.com.
Para acceder a más opciones, visita https://groups.google.com/d/optout.
Reply all
Reply to author
Forward
0 new messages