Using concurrent-ruby with Rails

421 views
Skip to first unread message

Arindam Mukherjee

unread,
Jul 26, 2016, 5:03:55 PM7/26/16
to Concurrent Ruby
I am writing some Rails code that uses concurrent-ruby for doing some workflow-type background jobs. These threads can insert / update records in the model.

I use Phusion Passenger with Rails 4.2, with a MySQL backend. The Ruby interpreter used is MRI.

I am noticing that there are intermittent failures while trying to insert or update records in MySQL with no obvious reasons printed in the debug logs. Could this be because I am doing writes from multiple threads? Could changing the interpreter to Rubinius / JRuby help?

Are there any caveats in general with using concurrent-ruby on Rails, and in particular with concurrent database updates?

Arindam

Jerry D'Antonio

unread,
Jul 28, 2016, 9:17:59 AM7/28/16
to Arindam Mukherjee, Concurrent Ruby
Arindam,

Rails is thread safe, as is ActiveRecord. But to use ActiveRecord in a multi-threaded application you must manually check in/out connections from the ConnectionPool. The documentation for ActiveRecord has a good explanation here: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html. There are also a good number of blog posts on the subject, including this: https://bibwild.wordpress.com/2014/07/17/activerecord-concurrency-in-rails4-avoid-leaked-connections/.

What abstractions from Concurrent Ruby are you using? Our abstractions don't write anything to the Rails error log. Logging is an application-level responsibility and Concurrent Ruby is a low-level library. It's also designed for use outside of Rails so we cannot assume that a log file exists. All of your high-level abstractions has error handling/reporting features built-in, however. It should be easy to leverage those features to write to the Rails error log.

I hope this helps.

Best regards,
Jerry


--
You received this message because you are subscribed to the Google Groups "Concurrent Ruby" group.
To unsubscribe from this group and stop receiving emails from it, send an email to concurrent-ru...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Arindam Mukherjee

unread,
Jul 31, 2016, 2:56:10 AM7/31/16
to Jerry D'Antonio, Concurrent Ruby
Thanks Jerry.

I run my tasks wrapped in Concurrent::Futures. A dependency graph of
these futures is implicitly defined using Concurrent.dataflow.

I did manually check connections out of the ActiveRecord connection
pool using the with_connection function. It didn't seem to make any
significant difference.

I then removed some database level foreign key constraints, using
validations and application logic to enforce it. It does seem to have
made a difference. I am guessing that inserts / updates / deletes (DML
statements) result in some kind of multi-table lock with
coarse-grained lock escalation in presence of the foreign key
constraints. Concurrent statements simply experienced lock timeout.

The debug logs I was referring to were in the rails log. What I wanted
to point out was that there was little clue from the cryptic logs
there - no obvious error messages, just failure.

Finally, there was a reason I asked about the interpreters. I have a
feeling that if we were not using MRI but Rubinius or JRuby instead,
concurrent-ruby would have used all available cores. This would mean
that two futures - one holding a lock and another waiting on it, would
be scheduled more favorably across cores. On a single core, one
holding the lock could be scheduled out while it still held the lock,
and the one waiting on it could be run when it has no hope of
acquiring the lock. This would up the odds of a lock time-out.

Regards,
Arindam

Jerry D'Antonio

unread,
Aug 3, 2016, 9:17:09 AM8/3/16
to Arindam Mukherjee, Concurrent Ruby
I am guessing that inserts / updates / deletes (DML statements) result in some kind of multi-table lock

Possibly. Given what you experienced that sounds like a good hypothesis.

I have a feeling that if we were not using MRI but Rubinius or JRuby instead, concurrent-ruby would have used all available cores.

You are correct. MRI uses only one core. Using one of those runtimes is definitely better when your concurrent code does a lot of computation. If your code is I/O heavy then MRI is often better. MRI releases the GIL when performing I/O. By only using one core MRI can context switch between concurrent I/O operations much more efficiently. Sometimes you'll actually get better performance on MRI than JRuby or Rubinius in these cases. The only way to know is to benchmark your code on each runtime and see what it does. (Not coincidentally this is precisely why we work so hard to provide APIs and abstractions that are consistent on all runtimes.)

Best regards,
Jerry

Reply all
Reply to author
Forward
0 new messages