[Eventmachine-talk] Evented database access

25 views
Skip to first unread message

Brian Takita

unread,
Jun 8, 2008, 4:21:31 PM6/8/08
to eventmachine-talk
We have a custom EventMachine server that is used to host a multiplayer game.

In the game server we access the database. This IO blocking makes it
more optimal to use threads instead of one thread.
Of course threads have a cpu performance penalty.

We also are cpu bound in certain sections of the process. This makes
it more performant to place the cpu bound sections in critical
sections to avoid context switching.

It seems like using a lightweight concurrency approach would be more efficient.
Im thinking that we could have the main Eventmachine thread and a game
processing thread. The game processing thread would have a Queue
object that we could push commands to.

So in our IO bound sections (querying the database), we would use a
nonblocking socket that enqueues the rest of the operations once the
database server responds to the query.

This would mean we would need to restructure our program to use more
closure style commands or use contituations.

For example:
http://pastie.org/211184

Does this seem worth it, or are there easier approaches?

Thanks,
Brian
_______________________________________________
Eventmachine-talk mailing list
Eventmac...@rubyforge.org
http://rubyforge.org/mailman/listinfo/eventmachine-talk

Francis Cianfrocca

unread,
Jun 8, 2008, 4:23:45 PM6/8/08
to eventmac...@rubyforge.org
Out of curiosity, what database are you using? For a long, long time I've wanted to see evented versions of the standard DBMS libraries.

Brian Takita

unread,
Jun 8, 2008, 4:34:29 PM6/8/08
to eventmac...@rubyforge.org
On Sun, Jun 8, 2008 at 1:23 PM, Francis Cianfrocca
<garbag...@gmail.com> wrote:
> Out of curiosity, what database are you using? For a long, long time I've
> wanted to see evented versions of the standard DBMS libraries.
Mysql.

Yes that would be nice to have evented versions for the DBMS libraries.

Francis Cianfrocca

unread,
Jun 8, 2008, 5:14:21 PM6/8/08
to eventmac...@rubyforge.org
There are so many moving parts to this problem that it's hard to give a useful answer in the abstract. If you have compute-bound code that is so sensitive that you can actually see a difference by pinning it to a CPU, that's an effect that's orders of magnitude finer than what you might gain by making the database accesses more concurrent.

If you're looking to improve raw per-request speed, then you need to ask whether there are capturable latencies in the architecture, where you can better use the time you're waiting for something else to happen. If you're looking to improve scalability, then reducing threading can be critically important.

If you haven't done so already, you might start by doing a careful profile of your application. The approach of using a Queue to stack up the DBMS accesses is actually very interesting, but my intuition is that, unless you run the DBMS engine on the same server as the application, your losses from the lower I/O concurrency will swamp your gains from the reduced thread switching.

Brian Takita

unread,
Jun 8, 2008, 5:27:28 PM6/8/08
to eventmac...@rubyforge.org
On Sun, Jun 8, 2008 at 2:14 PM, Francis Cianfrocca
<garbag...@gmail.com> wrote:
> There are so many moving parts to this problem that it's hard to give a
> useful answer in the abstract. If you have compute-bound code that is so
> sensitive that you can actually see a difference by pinning it to a CPU,
> that's an effect that's orders of magnitude finer than what you might gain
> by making the database accesses more concurrent.
>
> If you're looking to improve raw per-request speed, then you need to ask
> whether there are capturable latencies in the architecture, where you can
> better use the time you're waiting for something else to happen. If you're
> looking to improve scalability, then reducing threading can be critically
> important.
Yes, the Q will be large with lots of concurrent client requests, so
there is more work that can be done while being I/O blocked.
Threading (especially Ruby's) is less cpu efficient than a more
lightweight approach.

>
> If you haven't done so already, you might start by doing a careful profile
> of your application. The approach of using a Queue to stack up the DBMS
> accesses is actually very interesting, but my intuition is that, unless you
> run the DBMS engine on the same server as the application, your losses from
> the lower I/O concurrency will swamp your gains from the reduced thread
> switching.
Ideally we would still have the same I/O concurrency because we would
use something like IO.select and move on the the next item in the Q.
When there is a response, then the next command would enqueued, which
could possibly be another I/O blocking operation.

Francis Cianfrocca

unread,
Jun 8, 2008, 5:38:34 PM6/8/08
to eventmac...@rubyforge.org

Assuming I understood you correctly, you're thinking about single-threading your blocking DB calls by stacking them up on a queue. That does avoid the high performance impact of  spinning a thread for each request. But you may find that the database server is underutilized, which means you're still not getting optimal performance, so you have to find a balance.

If there were an evented mysql library (which someone really needs to write), the problem would be easy

James Tucker

unread,
Jun 9, 2008, 6:19:14 AM6/9/08
to eventmac...@rubyforge.org
On 8 Jun 2008, at 22:38, Francis Cianfrocca wrote:

On Sun, Jun 8, 2008 at 5:27 PM, Brian Takita <brian....@gmail.com> wrote:
On Sun, Jun 8, 2008 at 2:14 PM, Francis Cianfrocca
<garbag...@gmail.com> wrote:
> There are so many moving parts to this problem that it's hard to give a
> useful answer in the abstract. If you have compute-bound code that is so
> sensitive that you can actually see a difference by pinning it to a CPU,
> that's an effect that's orders of magnitude finer than what you might gain
> by making the database accesses more concurrent.

I totally agree, I have been toying with the idea of testing out an intermediate evented pool to handle a higher latency IO work, that is, leaving the Q deliberately on the DB server, but having an effective proxy which can make gains only really by it's ability to coalesce requests. Clearly this doesn't work for all scenarios, but can dramatically speed up total bandwidth over small requests.

> If you're looking to improve raw per-request speed, then you need to ask
> whether there are capturable latencies in the architecture, where you can
> better use the time you're waiting for something else to happen. If you're
> looking to improve scalability, then reducing threading can be critically
> important.

I'm still sitting on the fence about threads vs. processes for the really big background tasks. Under the ruby paradigm, processes seem to clearly be the way (on MRI at least), but I'm dying to get the JRuby stuff up and running for this reason. They have real threads, and for something like a DB pool, that could really help, while we get our evented drivers sorted.

Yes, the Q will be large with lots of concurrent client requests, so
there is more work that can be done while being I/O blocked.
Threading (especially Ruby's) is less cpu efficient than a more
lightweight approach.

Quite right, and especially when we have to work the IO around it. Have you considered also simply splitting into two processes in order to achieve "a non-blocking thread"? Under MRI, and certain architectures, this again can do it, even with the overhead of local IPC over sockets.


>
> If you haven't done so already, you might start by doing a careful profile
> of your application. The approach of using a Queue to stack up the DBMS
> accesses is actually very interesting, but my intuition is that, unless you
> run the DBMS engine on the same server as the application, your losses from
> the lower I/O concurrency will swamp your gains from the reduced thread
> switching.
Ideally we would still have the same I/O concurrency because we would
use something like IO.select and move on the the next item in the Q.
When there is a response, then the next command would enqueued, which
could possibly be another I/O blocking operation.

The inherent problem with certainly SQL as a DBMS paradigm is that it's a request-response busy waiting loop, by logic. Even with an evented driver, you're really considering using futures and deferrables (or threads) to get around the issue that you really do just have to wait for the response. Now there are some potential solutions, one of which has worked for us in the past using PostgreSQLs NOTIFY clause and heavily abusing stored procedures and queuing tables. All of the DB business logic lives inside the DB, and requests are largely fire and forget (the app treats them as async, effectively).

The problem with this approach is that your devs will eventually learn to hate you after the first few thousand lines of plpgsql. Code generation can help.

Assuming I understood you correctly, you're thinking about single-threading your blocking DB calls by stacking them up on a queue. That does avoid the high performance impact of  spinning a thread for each request. But you may find that the database server is underutilized, which means you're still not getting optimal performance, so you have to find a balance.

And as with the suggestions I've made above, there is a balance, I've mostly suggested methods which add further indirection or overhead, and that is never free. The real impact depends on the rest of your architecture and specific message sizes, latencies and processing requirements.

If there were an evented mysql library (which someone really needs to write), the problem would be easy

I've started a postgres one, but I've put a lot on my plate, so there is no promise of a speedy delivery, if anyone wants to help however, please get in contact. The logic bound issue can still cause problems too, and so there's a lot of experimental work to do to try and really start pushing against the capabilities of the technology, especially if coming from a ruby app. There's also a slightly special case of synchrony problems under certain conditions, which I will want to study, although a plpgsql architecture can overcome that conveniently.

Thomas Ptacek

unread,
Jun 9, 2008, 11:06:32 AM6/9/08
to eventmac...@rubyforge.org
> The inherent problem with certainly SQL as a DBMS paradigm is that it's a
> request-response busy waiting loop, by logic. Even with an evented driver,
> you're really considering using futures and deferrables (or threads) to get

It shouldn't matter that there's a response loop --- most
client-server protocols work this way. As long as the "wait" state
isn't blocking, other state machines can progress, and everything is
happy.

I spent some time reading ruby-mysql after Francis posted yesterday.
This doesn't appear to be a hard project. There, see? I've committed
myself to it. =)

--
---
Thomas H. Ptacek // matasano security
read us on the web: http://www.matasano.com/log

Francis Cianfrocca

unread,
Jun 9, 2008, 11:27:43 AM6/9/08
to eventmac...@rubyforge.org
All right!

By the way, Eventmachine is now on github, so feel free to add a fork for your work on this..

James Tucker

unread,
Jun 9, 2008, 11:59:40 AM6/9/08
to eventmac...@rubyforge.org

On 9 Jun 2008, at 16:06, Thomas Ptacek wrote:

>> The inherent problem with certainly SQL as a DBMS paradigm is that
>> it's a
>> request-response busy waiting loop, by logic. Even with an evented
>> driver,
>> you're really considering using futures and deferrables (or
>> threads) to get
>
> It shouldn't matter that there's a response loop --- most
> client-server protocols work this way. As long as the "wait" state
> isn't blocking, other state machines can progress, and everything is
> happy.

No, indeed it shouldn't, but you have to step away from traditional
branch logic styles in order to deal with that:

def DB.query(str)
DB.send_raw(str)
until buffer.has_response?
rebuffer
end
buffer.pop
end

Now even in an initial evented implementation, all this really changes
is that fetch doesn't really do anything (remove the loop, assuming it
happens automatically on receive_data), potentially return maybe a
future (from buffer.pop), or nil, or just block, in which case the
block happens only marginally later in the stack. You can head for a
callback approach, but that's just what I'm talking about... The api
has to change significantly for that, as you can't "pause" code in ruby.

No matter how deep you get into the application code you end up in a
situation where the application logic itself is your blocker. Under
rails for example, the request response loop for HTTP will hard block
on application logic that's waiting for a database response. This is
just an api problem, but that's also An API Problem, if you catch my
drift. Especially worse when it's a big project like rails, that would
maybe like to support both styles of processing.

The way around this is a deferrable or other lightweight state machine
construct in order to use a callback or a continuation style
enclosure, with which you can still 'return' on the originating
request/method/action whatever (returning on the active receive_data /
timer callback / etc). In order to support this however, there are
greater demands on a change of API, and I am trying to come up with
something that won't kill developer productivity by code overhead.

The same thing is apparent in a Rack & Thin setup, where one can't
effectively service long lived XHR at this time, due to the fact that
the API demands a real http response array at the 'end' of every
request (#call). In a non-threaded server this means waiting for
whatever may block the application logic, not just IO blocking.

The short of what I'm trying to get at is, this isn't going to be a
drop-in solution, sadly, and moreover, I really don't think it even
can be.


> I spent some time reading ruby-mysql after Francis posted yesterday.
> This doesn't appear to be a hard project. There, see? I've committed
> myself to it. =)

Going postgres-pr here, all input more than welcome ;)

Thomas Ptacek

unread,
Jun 9, 2008, 2:12:55 PM6/9/08
to eventmac...@rubyforge.org
> No, indeed it shouldn't, but you have to step away from traditional branch
> logic styles in order to deal with that:

Yes, that is the nature of evented programs.

The problem EM has now is that there's no elegant scalable persistence
layer. From what I can see, the best you can do now is write a
synchronous web app as a sidecar, and hand your database operations
off to it; at least then your EM engine can queue requests in memory
and drain them out over HTTP.

All an evented DB driver does is get rid of that middleman, allowing
you to queue requests directly to the database.

It sounds like you're exploring some kind of
continuation-passing-style database interface. From what I can tell,
attempts to design scalable continuation-driven network apps are like
land wars in Asia. I'd be happy just to be able to have a screaming
fast application that didn't serialize on its SQL connection.

Roger Pack

unread,
Jun 9, 2008, 3:25:18 PM6/9/08
to eventmac...@rubyforge.org
> The short of what I'm trying to get at is, this isn't going to be a
> drop-in solution, sadly, and moreover, I really don't think it even
> can be.

Perhaps some day I might try to work on the following:
ruby 1.9
rails multi-thread safe :) -- one DB connection per thread
hack the mysql adapter so that it sets rb_thread_non_blocking_region
around any sql calls # allow for non blocking threads. Dangerous, but
hey, this is theoretical land, right?
I'd still have to have a thread per process, though. But if it worked
it would be nice, and potentially use less RAM.
But that's still multi-threaded.

I guess for non multi-threaded you'd have to adopt a staged
architecture [pretty popular for Event driven stuff--I noticed it
looking at some SEDA code the other day].
I guess it works with stages, something like

stage 1
do some code
setup a db call which will return its result to stage 2 for you
end

def stage 2
same
end

def stage 3
render a page
end

AND you'd have to rewrite the mysql binary to hook in to EM's threaded
loop for its socket handling.
I guess the revactor package does something along these lines. Maybe
that would work more elegantly.
Take care.
-R

Thomas Ptacek

unread,
Jun 9, 2008, 3:31:02 PM6/9/08
to eventmac...@rubyforge.org
This is pretty confusing, but just to be clear: MySQL is a
client-server protocol, and if you can speak it, you can drive an
arbitrary number of queries to an arbitrary number of databases
through a single thread via the event loop. No binary hacking
required. The MySQL protocol is less complicated than it seems.

--
---
Thomas H. Ptacek // matasano security
read us on the web: http://www.matasano.com/log

James Tucker

unread,
Jun 9, 2008, 5:45:09 PM6/9/08
to eventmac...@rubyforge.org

On 9 Jun 2008, at 20:25, Roger Pack wrote:

>> The short of what I'm trying to get at is, this isn't going to be a
>> drop-in solution, sadly, and moreover, I really don't think it even
>> can be.
>
> Perhaps some day I might try to work on the following:
> ruby 1.9
> rails multi-thread safe :) -- one DB connection per thread
> hack the mysql adapter so that it sets rb_thread_non_blocking_region
> around any sql calls # allow for non blocking threads. Dangerous,
> but hey, this is theoretical land, right?
> I'd still have to have a thread per process, though. But if it
> worked it would be nice, and potentially use less RAM.
> But that's still multi-threaded.

In this regard, I'm getting quite excited to see if a nice stop gap
can be made with the JRuby reactor, given that it supports real
threads, and has mature db interfaces available.

Brian Takita

unread,
Jun 9, 2008, 6:45:26 PM6/9/08
to eventmac...@rubyforge.org
I was thinking of either continuations or a command queue. The
branching logic would be different from your typical app.
I suppose ruby blocks & lambdas could help out with making it more palatable.

>
> The same thing is apparent in a Rack & Thin setup, where one can't
> effectively service long lived XHR at this time, due to the fact that the
> API demands a real http response array at the 'end' of every request
> (#call). In a non-threaded server this means waiting for whatever may block
> the application logic, not just IO blocking.
>
> The short of what I'm trying to get at is, this isn't going to be a drop-in
> solution, sadly, and moreover, I really don't think it even can be.
I already implemented a solution for Comet style requests.
http://github.com/pivotal/screw-unit-server
http://github.com/pivotal/js-spec-server

I needed to use Selenium to upon a browser which ran the spec suite,
and have the server wait for a response from the client that said what
the response was.

I had to monkey patch Thin's process method to not close the
connection unless the Content-Length header == the body's length.
You can also send a closing empty response by setting the Content-Length to 0.

I wouldn't say its exactly easy, because its an "unusual" concern for
your average web app, but for Comet style servers, it makes sense.

Roger Pack

unread,
Jun 9, 2008, 8:03:24 PM6/9/08
to eventmac...@rubyforge.org
> In this regard, I'm getting quite excited to see if a nice stop gap
> can be made with the JRuby reactor, given that it supports real
> threads, and has mature db interfaces available.

Nice. Now if we could just make rails thread safe... :) [actually I
did see a google summer of code project on it. Maybe once that's out
[if it helps] then this would work]. But perhaps a 'quick' web server
would be merb running in Jruby or something, with a lot of threads.
Dunno.

-R

Thomas Ptacek

unread,
Jun 10, 2008, 2:18:22 AM6/10/08
to eventmac...@rubyforge.org
Sorry, that took me longer to expected. Have you ever read MySQLd? Ew.

I've got a bottom-edge driver for the MySQL client-server protocol,
barely tested, supporting only the COMMAND_QUERY commands (meaning:
almost every command you'd execute from the mysql command line). It
supports only 5.x MySQLd. It solves no real-world problem at this
point, and is sure to be an embarassment to my children's children's
children, but is a starting point.

It clocks in at 600 lines of Ruby --- less, if you get rid of my hexdump code.

EventMachine::run {
c = Asymy::Connection.new(:target => "localhost",
:port => 13306,
:username => "user",
:password => "pass",
:database => "mysql")
c.exec("show databases") do |fields, rows|
pp fields
pp rows
end
}

Since I have no idea how this code would even fit in with the
eventmachine tree, here's a tarball:

http://www.matasano.com/asymy.tgz

Again, I'm literally posting the very first thing I managed to make do
anything, because I thought it might be interesting. I make no claims
that any of this actually works.

Charles Jolley

unread,
Jun 10, 2008, 3:52:20 AM6/10/08
to eventmac...@rubyforge.org
I just posted a rough patch of code that helps to "unwind" event
driven programming using continuations in Ruby. It currently has some
memory leaks which I need to figure out but the code is quite simple
and it would make it possible for you to put a layer on top of an
event-driven/callback-style DB API to make it "normal" again.

http://www.okito.net/post/37659446/pipelined-easy-event-driven-programming-for-ruby

-Charles

On Jun 9, 2008, at 3:45 PM, Brian Takita wrote:

>> You can head for a callback approach, but
>> that's just what I'm talking about... The api has to change
>> significantly
>> for that, as you can't "pause" code in ruby.

_______________________________________________

James Tucker

unread,
Jun 10, 2008, 6:18:03 AM6/10/08
to eventmac...@rubyforge.org

On 10 Jun 2008, at 01:03, Roger Pack wrote:

>> In this regard, I'm getting quite excited to see if a nice stop gap
>> can be made with the JRuby reactor, given that it supports real
>> threads, and has mature db interfaces available.
>
> Nice. Now if we could just make rails thread safe... :) [actually I
> did see a google summer of code project on it. Maybe once that's
> out [if it helps] then this would work]. But perhaps a 'quick' web
> server would be merb running in Jruby or something, with a lot of
> threads. Dunno.

If you keep the separation on the other side of the db interface
boundary, it won't need to be. Run your entire app in one thread, and
only share a single thread safe request queue, you can avoid all of
the rails issues, and the db thread will also remain quiet when it's
not talking to the db. Of course, again, we're coming into the app
logic problem with that, and you somewhat loose the advantage of
threads if you continue to use AR and block on db responses.

It's worth noting also that AR does have some concurrency
capabilities: ActiveRecord::Base.allow_concurrency = true

Of course, the observations that have been made about this is that
thread pooling under MRI makes no gains, with AR, Sequel and
DataMapper. I haven't run those up under JRuby to see if the story is
any different there. For more information, see Kevin Williams article
on the topic.

James Tucker

unread,
Jun 10, 2008, 6:20:53 AM6/10/08
to eventmac...@rubyforge.org
That's extremely interesting, I had been wondering if that could be
done...

well done :)

James Tucker

unread,
Jun 10, 2008, 6:26:09 AM6/10/08
to eventmac...@rubyforge.org

On 10 Jun 2008, at 07:18, Thomas Ptacek wrote:

> Sorry, that took me longer to expected. Have you ever read MySQLd? Ew.

Right...

> I've got a bottom-edge driver for the MySQL client-server protocol,
> barely tested, supporting only the COMMAND_QUERY commands (meaning:
> almost every command you'd execute from the mysql command line). It
> supports only 5.x MySQLd. It solves no real-world problem at this
> point, and is sure to be an embarassment to my children's children's
> children, but is a starting point.
>
> It clocks in at 600 lines of Ruby --- less, if you get rid of my
> hexdump code.
>
> EventMachine::run {
> c = Asymy::Connection.new(:target => "localhost",
> :port => 13306,
> :username => "user",
> :password => "pass",
> :database => "mysql")
> c.exec("show databases") do |fields, rows|
> pp fields
> pp rows
> end
> }

Very cool, well done :)

> Since I have no idea how this code would even fit in with the
> eventmachine tree, here's a tarball:
>
> http://www.matasano.com/asymy.tgz

Under Protocols, I've been thinking that we're going to need to start
modularising that tree however, or at least not pre-loading
everything, otherwise we'll end up like facets < 2.0.

> Again, I'm literally posting the very first thing I managed to make do
> anything, because I thought it might be interesting. I make no claims
> that any of this actually works.

:)

Kirk Haines

unread,
Jun 10, 2008, 8:11:13 AM6/10/08
to eventmac...@rubyforge.org
On Tue, Jun 10, 2008 at 1:52 AM, Charles Jolley <cha...@okito.net> wrote:
> I just posted a rough patch of code that helps to "unwind" event driven
> programming using continuations in Ruby. It currently has some memory leaks
> which I need to figure out but the code is quite simple and it would make it
> possible for you to put a layer on top of an event-driven/callback-style DB
> API to make it "normal" again.
>
> http://www.okito.net/post/37659446/pipelined-easy-event-driven-programming-for-ruby

It has memory leaks because continuations in ruby (MRI ruby, at least)
leak memory. And they are really slow, too. Cool idea, though. :)


Kirk Haines

Charles Jolley

unread,
Jun 10, 2008, 11:36:35 AM6/10/08
to eventmac...@rubyforge.org, eventmac...@rubyforge.org
Well I've experimented a bit and continuations do not leak memory all
of the time...only under certain conditions which I am still trying to
identify. (i suspect it has to do with the interaction between
continuations and closures) I may not be able to make this code work
without leaking memory but we shall see.

If the memory leaks can be eliminated I think this approach could be
useful anyway because the perf gain from writing internally Evented
libraries that still use a non-event driven api far outweighs the
added cost of continuations. That was the point of my post -
continuation based Evented code outran the nonevented version by 20x

-Charles

Roger Pack

unread,
Jun 10, 2008, 11:48:03 AM6/10/08
to eventmac...@rubyforge.org
> It clocks in at 600 lines of Ruby --- less, if you get rid of my
> hexdump code.
>
> EventMachine::run {
> c = Asymy::Connection.new(:target => "localhost",
> :port => 13306,
> :username => "user",
> :password => "pass",
> :database => "mysql")
> c.exec("show databases") do |fields, rows|
> pp fields
> pp rows
> end
> }

Nice! An evented mysql. I wonder how you could combine this with
existing apps to get a single threaded server :)
-R

Tony Arcieri

unread,
Jun 10, 2008, 1:18:46 PM6/10/08
to eventmac...@rubyforge.org
On Tue, Jun 10, 2008 at 1:52 AM, Charles Jolley <cha...@okito.net> wrote:
I just posted a rough patch of code that helps to "unwind" event driven programming using continuations in Ruby.  It currently has some memory leaks which I need to figure out but the code is quite simple and it would make it possible for you to put a layer on top of an event-driven/callback-style DB API to make it "normal" again.

http://www.okito.net/post/37659446/pipelined-easy-event-driven-programming-for-ruby

You might check out Revactor... this is exactly what it does, except using Ruby 1.9 fibers (and thus free of the memory leaks): http://revactor.org

That said I posted something similar to this mailing list a few years ago (using continuations) and as far as I could tell the memory leaks were intractable.

--
Tony Arcieri
medioh.com

Brian Takita

unread,
Jun 10, 2008, 1:43:04 PM6/10/08
to eventmac...@rubyforge.org
Too bad fibers aren't backported to 1.8.
>
> --
> Tony Arcieri
> medioh.com

Brian Takita

unread,
Jun 10, 2008, 1:46:24 PM6/10/08
to eventmac...@rubyforge.org
On Tue, Jun 10, 2008 at 10:43 AM, Brian Takita <brian....@gmail.com> wrote:
> On Tue, Jun 10, 2008 at 10:18 AM, Tony Arcieri <to...@medioh.com> wrote:
>> On Tue, Jun 10, 2008 at 1:52 AM, Charles Jolley <cha...@okito.net> wrote:
>>>
>>> I just posted a rough patch of code that helps to "unwind" event driven
>>> programming using continuations in Ruby. It currently has some memory leaks
>>> which I need to figure out but the code is quite simple and it would make it
>>> possible for you to put a layer on top of an event-driven/callback-style DB
>>> API to make it "normal" again.
>>>
>>>
>>> http://www.okito.net/post/37659446/pipelined-easy-event-driven-programming-for-ruby
>>
>> You might check out Revactor... this is exactly what it does, except using
>> Ruby 1.9 fibers (and thus free of the memory leaks): http://revactor.org
>>
>> That said I posted something similar to this mailing list a few years ago
>> (using continuations) and as far as I could tell the memory leaks were
>> intractable.
> Too bad fibers aren't backported to 1.8.
Tony, do you have a code sample on how this would work using Revactor?

Tony Arcieri

unread,
Jun 10, 2008, 1:50:36 PM6/10/08
to eventmac...@rubyforge.org
On Tue, Jun 10, 2008 at 11:46 AM, Brian Takita <brian....@gmail.com> wrote:
Tony, do you have a code sample on how this would work using Revactor?

For all intents and purposes it'd be the same as the MySQL (or ActiveRecord / DataMapper / DBI / whatever) API, as it facilitates "blocking" calls that wait for the request to complete (but suspend the fiber making the request until it completes)

--
Tony Arcieri
medioh.com

Charles Jolley

unread,
Jun 10, 2008, 3:12:59 PM6/10/08
to eventmac...@rubyforge.org
Revactor looks really interesting Tony.  Does it work in any special way with EM by any chance?  The pipelined code actually pauses execution while waiting on a deferrable.  This way it can interact with any evented-library that uses deferrables to communicate callback state (ala the HTTP library in EM).

-Charles

medioh.com _______________________________________________

Roger Pack

unread,
Jun 10, 2008, 6:16:24 PM6/10/08
to eventmac...@rubyforge.org
is there any benefit to using revactor versus just a normal threaded approach?

> You might check out Revactor... this is exactly what it does, except using

> Ruby 1.9 fibers (and thus free of the memory leaks): http://revactor.org
>
> That said I posted something similar to this mailing list a few years ago
> (using continuations) and as far as I could tell the memory leaks were
> intractable.

as in very hard to track down?

Tony Arcieri

unread,
Jun 10, 2008, 6:28:12 PM6/10/08
to eventmac...@rubyforge.org
On Tue, Jun 10, 2008 at 4:16 PM, Roger Pack <roger...@leadmediapartners.com> wrote:
is there any benefit to using revactor versus just a normal threaded approach?

It depends on what you're writing.  Revactor is fundamentally evented and thus eliminates some of the headaches of threads.

That said Rubinius exposes an Actor API which is mostly compatible with Revactor's, so you can prototype applications on Revactor then move them over to Rubinius as soon as it's ready.

> That said I posted something similar to this mailing list a few years ago
> (using continuations) and as far as I could tell the memory leaks were
> intractable.

as in very hard to track down?

As in I see no solution.  My original approach required continuing within a continuation, and if you do that you just keep piling more and more on the stack without ever reclaiming what's underneath.  I haven't looked specifically at the continuation-based approaches others have posted but if they're encountering endless memory growth it's likely the same problem.

That's why you need a coroutine mechanism like Fibers to make this approach workable...

--
Tony Arcieri
medioh.com

Jeff Fedor

unread,
Jun 10, 2008, 7:27:37 PM6/10/08
to eventmac...@rubyforge.org
I'm about to do a revactor prototype port of my own threaded TCPServer. My stuff is on 1.86 and so it's threads not fibres. I've had some fun with race conditions and having to force context switching so i'm pretty ramped to play with revactor.

J
-----Original Message-----
From: "Tony Arcieri" <to...@medioh.com>

Date: Tue, 10 Jun 2008 16:28:12
To:eventmac...@rubyforge.org
Subject: Re: [Eventmachine-talk] Evented database access

--
Tony Arcieri
medioh.com <http://medioh.com> _______________________________________________

Roger Pack

unread,
Jun 11, 2008, 5:49:47 PM6/11/08
to eventmac...@rubyforge.org
> In this regard, I'm getting quite excited to see if a nice stop gap can be
> made with the JRuby reactor, given that it supports real threads, and has
> mature db interfaces available.

Yeah you'd think that JRuby + a thread safe server [merb/ramaze] + a
thread safe DB [sequel] + multiple threads would be good enough.
Maybe it is :)

Another thought would be [as discussed previously with Tony], using
fibers instead of threads and using an evented DB connector with a DB
connection pool. Then when it does DB requests, it pauses the fiber
until the request returns [without blocking the other fibers].

Tony's done some of the base work for this--revactor comes with a
fibered version of mongrel.

I'm wondering if sequel could be patched to use an evented DB driver.
If so then this + the fibered mongrel might yield similar benefits to
the jruby solution mentioned above. You'd be single threaded without
blocking. Of course, that means you'd be single threaded [so
single-cored, basically]--but hopefully that one thread could do a lot
of background processing while waiting for DB connections to return.
Do you think has potential to yield a nice, single threaded web
server?

Having worked with EM a little in the past, it seems that if you can
move to single threaded, it's a lot faster. Like a lot. And seems to
use less RAM.

I guess a drawback would be that if you have something that's CPU
bound, like...a page that takes FOREVER to render [has that ever
happened to anyone?] then it would block the others, but not for too
long. Evented mongrel I suppose would suffer from the same thing.
Even the threaded solutions would, if you got too many of long
requests piled up against them.


NB that Rails isn't thread safe so this wouldn't help that until they
get their act together. Until there isn't a huge mutex lock over the
entire thing, no continuation or fiber will help them, I'd doubt. But
other frameworks, quite possibly. :)

Take care!

-R

Thomas Ptacek

unread,
Jun 11, 2008, 7:15:57 PM6/11/08
to eventmac...@rubyforge.org
> single-cored, basically]--but hopefully that one thread could do a lot
> of background processing while waiting for DB connections to return.
> Do you think has potential to yield a nice, single threaded web
> server?
>
> Having worked with EM a little in the past, it seems that if you can
> move to single threaded, it's a lot faster. Like a lot. And seems to
> use less RAM.

Evented MySQL was really easy; clearly, a single-threaded end-to-end
web stack is doable.

I'm not even sure continuations are that much of a win. The web stack
can abstract away the HTTP callbacks, so that's going to look just
like Rails controllers. Saved block arguments give you blocks instead
of callbacks. If you render Javascript and mostly field JSON or XML
from the database to build the page, which is what plenty of Rails
apps like like today, there's not much pain to using straight EM.

--
---
Thomas H. Ptacek // matasano security
read us on the web: http://www.matasano.com/log

Roger Pack

unread,
Jun 11, 2008, 7:20:56 PM6/11/08
to eventmac...@rubyforge.org
I wonder if it would be possible to re-tool the pure ruby mysql, as
another option:
http://www.tmtm.org/en/ruby/mysql/

Then again, maybe your version is faster, as it is :) Quite possible.
I have heard, however, that the pure ruby mysql is way slower than
the normal.

Speaking of which, I wonder if one could just use revactor on it
straight, as it is now :)
Take care, and thanks for your work on this.
-R

On Tue, Jun 10, 2008 at 12:18 AM, Thomas Ptacek <tq...@matasano.com> wrote:
> Sorry, that took me longer to expected. Have you ever read MySQLd? Ew.
>
> I've got a bottom-edge driver for the MySQL client-server protocol,
> barely tested, supporting only the COMMAND_QUERY commands (meaning:
> almost every command you'd execute from the mysql command line). It
> supports only 5.x MySQLd. It solves no real-world problem at this
> point, and is sure to be an embarassment to my children's children's
> children, but is a starting point.

Tony Arcieri

unread,
Jun 11, 2008, 7:26:00 PM6/11/08
to eventmac...@rubyforge.org
On Wed, Jun 11, 2008 at 5:20 PM, Roger Pack <roger...@leadmediapartners.com> wrote:
Speaking of which, I wonder if one could just use revactor on it
straight, as it is now :)

It should be possible to use the pure Ruby MySQL in an evented manner by (monkey)patching it to use Revactor's sockets rather than Ruby TCP sockets.

--
Tony Arcieri
medioh.com

Charles Jolley

unread,
Jun 11, 2008, 7:28:03 PM6/11/08
to eventmac...@rubyforge.org, eventmac...@rubyforge.org
The benefit of using continuations or fibers in 1.9 is that it allows
you to "unwind" event driven programming so that it looks like a
normal stack based programming model.

You wouldn't want to do this in a low level library like a db adaptor
but rather for high level code. This way you can have a few event-
driven nuts write you low-level code that usually blocks - such as db
adaptors and rest-client code - and then "mere mortals" can write
their application logic like normal.

You don't gain the full benefits of using 100% evdriven code but you
get 80% of the win and the code can be usedby everyone.

Incidentially you do not need to use multiple threads at all of your
db lib is evented. Much faster.

-Charles

Thomas Ptacek

unread,
Jun 11, 2008, 7:37:31 PM6/11/08
to eventmac...@rubyforge.org
The pure ruby MySQL does basically nothing but feed text SQL to the
server and read the responses, and it doesn't support the modern MySQL
login protocol (then again, I don't support the archaic login).
Neither my code nor ruby-mysql supports the binary prepared statement
protocol --- which, if you look at it on the wire, might not be much
of a win anyways.

I don't think extending ruby-mysql is the way to go.

--
---
Thomas H. Ptacek // matasano security
read us on the web: http://www.matasano.com/log

Roger Pack

unread,
Jun 11, 2008, 7:50:11 PM6/11/08
to eventmac...@rubyforge.org
On Wed, Jun 11, 2008 at 5:28 PM, Charles Jolley <cha...@okito.net> wrote:
> The benefit of using continuations or fibers in 1.9 is that it allows you to
> "unwind" event driven programming so that it looks like a normal stack based
> programming model.

I see. So you're saying that using fibers or continuations is a 'half
way point' between being pure event driven and using pure threads.

Wait come to think of it, pure threads aren't even a concurrency
option in 1.9, since it only runs one thread at a time. That makes
fibers even more tempting. Apparently they're only like a 10K
overhead per fiber, which isn't too much.
-R

> You wouldn't want to do this in a low level library like a db adaptor but

> rather for high level code. This way you can have a few event-driven nuts


> write you low-level code that usually blocks - such as db adaptors and
> rest-client code - and then "mere mortals" can write their application logic
> like normal.
>
> You don't gain the full benefits of using 100% evdriven code but you get 80%
> of the win and the code can be usedby everyone.
>
> Incidentially you do not need to use multiple threads at all of your db lib
> is evented. Much faster.

Roger Pack

unread,
Jun 11, 2008, 8:17:47 PM6/11/08
to eventmac...@rubyforge.org
> The pure ruby MySQL does basically nothing but feed text SQL to the
> server and read the responses, and it doesn't support the modern MySQL
> login protocol (then again, I don't support the archaic login).
> Neither my code nor ruby-mysql supports the binary prepared statement
> protocol --- which, if you look at it on the wire, might not be much
> of a win anyways.
>
> I don't think extending ruby-mysql is the way to go.

So it seems that the test we 'wish' to optimize would be:
trying to do 10 'long running' and some number 'very short' mysql
queries immediately after them [while they're still running].
the very short mysql queries should return very quickly.
Which is not the case with today's Ruby :)

So one way to do a proof of concept with this would be using fibers
and revactor and ruby-mysql. Maybe :) Or using the evented mysql
posted here + fibers + EM.

Should it prove successfull, then it is possible that
fibered mongrel + something that ends up using the evented mysql would
perform better than it currently does.
One can hope :)

-R

Thomas Ptacek

unread,
Jun 11, 2008, 8:28:04 PM6/11/08
to eventmac...@rubyforge.org
conns = 0.upto(20).map {|i| Asymy::Connection.new(opts)}

0.upto(10) do |i|
conns[i].exec("select * from huge_table") {|cols, rows| pp [i, rows.size]}
end

0.upto(10) do |i|
conns[10+i].exec("select COUNT(*) from tiny_table") {|cols, rows|
pp [i, rows.size]}
end

I'm not sure I'm seeing why you need fibers for this. The short
queries should return first. The queries should all run concurrently.
The snozzberries should, in fact, taste like snozzberries.

There's no situation in which fibers are going to make the "fastest"
queries on a single connection return first, because the MySQL
protocol itself is synchronous.

On 6/11/08, Roger Pack <roger...@leadmediapartners.com> wrote:

--
---
Thomas H. Ptacek // matasano security
read us on the web: http://www.matasano.com/log

Roger Pack

unread,
Jun 12, 2008, 1:09:26 AM6/12/08
to eventmac...@rubyforge.org
On Wed, Jun 11, 2008 at 6:28 PM, Thomas Ptacek <tq...@matasano.com> wrote:
> conns = 0.upto(20).map {|i| Asymy::Connection.new(opts)}
>
> 0.upto(10) do |i|
> conns[i].exec("select * from huge_table") {|cols, rows| pp [i, rows.size]}
> end
>
> 0.upto(10) do |i|
> conns[10+i].exec("select COUNT(*) from tiny_table") {|cols, rows|
> pp [i, rows.size]}
> end
>
> I'm not sure I'm seeing why you need fibers for this. The short
> queries should return first. The queries should all run concurrently.
> The snozzberries should, in fact, taste like snozzberries.
>
> There's no situation in which fibers are going to make the "fastest"
> queries on a single connection return first, because the MySQL
> protocol itself is synchronous.

Wow. After having tried this out it TOTALLY ROCKS!
Especially compared to 1.8.6 and its threading model, which basically
reverts to 100% blocking IO.
I took the liberty of posting an EM version based on the previous email

http://code.google.com/p/ruby-roger-useful-functions/source/browse/trunk/asymy/big_em.rb
[also has some added code for a connection pool--kind of a hack, but works]

and its competitor--a 'normal' threaded version:
http://code.google.com/p/ruby-roger-useful-functions/source/browse/trunk/asymy/big_thread.rb

And, as you said, it works and tastes like shnozzleberries.
Some observations:

you can run 1000's of shorter queries while the longer ones are running.

MySql itself uses 160% CPU when run against EM and 130% when run
against threaded. This means that using the EM version we are able to
more easily take advantage of DB CPU cycles. When it was running at
160% that was 40% idle, meaning that overall the EM one was able to
complete faster than its 70% idle threaded neighbor. Well, it's my
hypothesis that 'more CPU for MySql means faster overall queries'
It's impossible to actually compare the running times of it, because
the Ruby thread version takes SO MUCH LONGER since it's single
threaded.

Also, if you do a lot of short SQL queries before starting some longer
ones, with threads, the short ones tend to get done rather quickly.
Not sure why that is. I suspect that ruby doesn't swap out threads
very quickly, so the first created threads tend to run to completion
their first time [which is before the longer queries' threads run even
once]. But it does work quickly in that instance, with threads.
Quicker, anyway.

~/dev/ruby-roger-useful-functions/asymy time ruby big_em.rb
real 0m4.731s
user 0m0.121s
sys 0m0.037s

~/dev/ruby-roger-useful-functions/asymy time ruby big_thread.rb
real 0m14.033s
user 0m0.089s
sys 0m0.039s


About fibers--you were correct--they aren't absolutely necessary.
This code is all 1.8.6 code, as an example.
For practical purposes, they'll be nice to use because [as noted by
authors previously] you can use them to generate 'non threaded, but
interleaved code' [i.e. normal looking code] instead of using staged
and/or partitioned code, as EM typically requires you to use. Take a
look at revactor for an example. It basically sleeps threads until
their blocking IO calls stops blocking. They can then continue from
there. So it just uses sleep instead of continuations, but that's
another story.

As a note the asym library works really well in 1.8 but doesn't work
[as yet] in 1.9, despite a feeble attempt to get it to work.
It also doesn't work with [AFAIK] blank passwords, but it does work
with non-blank passwords.
I did also note that the ruby mysql adapter hasn't been updated since
2005, so might be a little outdated :)

Thanks for your work.

Thomas Ptacek

unread,
Jun 12, 2008, 1:32:37 AM6/12/08
to eventmac...@rubyforge.org
Whoah, you benchmarked it! I'll be honest and say I've been afraid
even to touch it. Thank you!

At Aman Gupta's suggestion, I copied this up to GitHub:

http://www.github.com/tqbf/asymy

I'm an svn person, but you git people are apparently meant to know
what to do with it there.

My totally unscientific guess is that the nature of the MySQL protocol
and server architecture is going to dominate performance. You could
play games with query scheduling on a single connection, for instance
by having Connection#exec store queries on a heap instead of a queue.

I'm debating taking another few hours to do the prepared statement
binary protocol.

--
---
Thomas H. Ptacek // matasano security
read us on the web: http://www.matasano.com/log

Thomas Ptacek

unread,
Jun 12, 2008, 1:49:13 AM6/12/08
to eventmac...@rubyforge.org
> As a note the asym library works really well in 1.8 but doesn't work
> [as yet] in 1.9, despite a feeble attempt to get it to work.
> It also doesn't work with [AFAIK] blank passwords, but it does work
> with non-blank passwords.
> I did also note that the ruby mysql adapter hasn't been updated since
> 2005, so might be a little outdated :)

It supports blank passwords now. Thanks for noticing.

--
---
Thomas H. Ptacek // matasano security
read us on the web: http://www.matasano.com/log

Roger Pack

unread,
Jun 12, 2008, 10:16:14 AM6/12/08
to eventmac...@rubyforge.org
> At Aman Gupta's suggestion, I copied this up to GitHub:
>
> http://www.github.com/tqbf/asymy

How do we contribute back to this?

>
>
> I'm an svn person, but you git people are apparently meant to know
> what to do with it there.
>
> My totally unscientific guess is that the nature of the MySQL protocol
> and server architecture is going to dominate performance. You could
> play games with query scheduling on a single connection, for instance
> by having Connection#exec store queries on a heap instead of a queue.

Yeah some type of 'more fair scheduling than a queue' might be nice.
Either that or maybe a really large pool :)

Kirk Haines

unread,
Jun 12, 2008, 12:30:14 PM6/12/08
to eventmac...@rubyforge.org
On Thu, Jun 12, 2008 at 8:16 AM, Roger Pack <rogerp...@gmail.com> wrote:

>> My totally unscientific guess is that the nature of the MySQL protocol
>> and server architecture is going to dominate performance. You could
>> play games with query scheduling on a single connection, for instance
>> by having Connection#exec store queries on a heap instead of a queue.
>
> Yeah some type of 'more fair scheduling than a queue' might be nice. Either
> that or maybe a really large pool :)

This is damn interesting to me. One of the reasons I have never added
the ability for Analogger to log directly to a database is because the
synchronous nature of that would kill performance. An asynchronous,
event based sytem, though, would be perfect. (*goes to hack on the
code*)


Kirk Haines

Roger Pack

unread,
Jun 12, 2008, 1:32:45 PM6/12/08
to eventmac...@rubyforge.org
> the ability for Analogger to log directly to a database is because the
> synchronous nature of that would kill performance. An asynchronous,
> event based sytem, though, would be perfect. (*goes to hack on the
> code*)

One thing I want to hack together once I get the chance is something
up your alley--
hack evented mongrel to spawn fibers instead of Threads.
Then when you do async DB calls you just do something like.

var = nil
main_fiber = Fiber.current
conn.exec("select * from table") { |i,c| var = i,c
main_fiber.resume
}
Fiber.yield # yields it back to the original caller
return var

Well I haven't quite figured it out yet. anyway with such as this
then all of your DB calls are simple calls to yield.

Oh wait, for your logger you probably wanted to do it asynchronously.
Yeah you wouldn't need this then :)
-R

Kirk Haines

unread,
Jun 12, 2008, 2:34:54 PM6/12/08
to eventmac...@rubyforge.org
On Thu, Jun 12, 2008 at 11:32 AM, Roger Pack <rogerp...@gmail.com> wrote:

> One thing I want to hack together once I get the chance is something up your
> alley--
> hack evented mongrel to spawn fibers instead of Threads.
> Then when you do async DB calls you just do something like.

evented_mongrel doesn't use threads. But yeah, the idea is a sound one.


Kirk Haines

Tony Arcieri

unread,
Jun 12, 2008, 5:45:57 PM6/12/08
to eventmac...@rubyforge.org
And Mongrel on Revactor already provides a way to spawn fibers instead of threads with very minor modifications to Mongrel
--
Tony Arcieri
medioh.com

hemant

unread,
Jun 13, 2008, 10:56:06 AM6/13/08
to eventmac...@rubyforge.org

Well, I have been following this discussion somewhat closely, but I
wonder, how is it possible with whatever sockets you use, especially
with mysql. With mysql, especially threre is no way to make async
query because protocol doesn't support multiplexing. There is no way
to identify, who made the request and for whom this response has come.
twisted simulates this behaviour with thread_pool, but that sort of
defeats the purpose, doesn't it?

On the other hand, post gres, fully supports async queries and hence
prolly we should have a pgsql driver first.

Thomas Ptacek

unread,
Jun 13, 2008, 11:34:45 AM6/13/08
to eventmac...@rubyforge.org
> Well, I have been following this discussion somewhat closely, but I
> wonder, how is it possible with whatever sockets you use, especially
> with mysql. With mysql, especially threre is no way to make async
> query because protocol doesn't support multiplexing. There is no way
> to identify, who made the request and for whom this response has come.

Just because you can't drive more than one query on the connection at
a time doesn't mean you can't make the API asynchronous. All you need
to accomplish is not blocking the whole program while a query runs.

> On the other hand, post gres, fully supports async queries and hence
> prolly we should have a pgsql driver first.

Have at it.

--
---
Thomas H. Ptacek // matasano security
read us on the web: http://www.matasano.com/log

Roger Pack

unread,
Jun 13, 2008, 8:12:43 PM6/13/08
to eventmac...@rubyforge.org
In latest news, it turned out to be nicely easy to add fibers to DB
access and to mongrel.

An example of something doint this [a mongrel handler that makes some
async DB calls and returns 'hello'] is

http://code.google.com/p/ruby-roger-useful-functions/source/browse/trunk/asymy/fibers/mongrel_test/test_mongrel_with_db.rb

Basically the only added things were
http://code.google.com/p/ruby-roger-useful-functions/source/browse/trunk/asymy/fibers/mongrel_test/fibered_mongrel.rb
# builds off evented mongrel and just wraps its requests in Fibers.

and an added function in
http://code.google.com/p/ruby-roger-useful-functions/source/browse/trunk/asymy/source/asymy/connection.rb
# added 'exec_and_fiber_yield' [and a small 1.9 patch and 'em_connection']

Also note that in ...handlers.rb of mongrel you have to replace a few
:'s with 'then's [it yields a syntax error when you use it--replace
them there] for 1.9

And, snozzleberries it is. I'm able to pound it with lots of requests
and those that do "very little" IO wait time finish [as hoped for]
very quickly [like 20ms], and those with "lots" of IO finish...after
their IO finishes, Much later.

I am able to hammer it with no ill results. Wow.
The good news for this is that it basically means that with small
tweaks to mongrel and to the DB layer, any [multi-thread safe] 1.9 app
can use this as a drop in replacement and immediately reap the
benefits. I'd imagine this yields some good results, though I haven't
benchmarked it versus threaded or "evented only" yet, and, of course,
my benchmarks are all synthetic.
This has some good potential. Especially with the use of epoll for EM.
Thanks for doing that stuff!
-R

Roger Pack

unread,
Jun 16, 2008, 4:44:42 PM6/16/08
to eventmac...@rubyforge.org
Nice. Were you going to maintain this or just kind of put it out
there for others to use?
[ex: was thinking of changing it slightly to be
@conn.exec("sql") {|columns, rows, error_message_if_it_fails_half_way|
}
Or something or other to denote failure.
Thanks!
-R


On Wed, Jun 11, 2008 at 11:32 PM, Thomas Ptacek <tq...@matasano.com> wrote:
> Whoah, you benchmarked it! I'll be honest and say I've been afraid
> even to touch it. Thank you!
>
> At Aman Gupta's suggestion, I copied this up to GitHub:
>
> http://www.github.com/tqbf/asymy
>

Thomas Ptacek

unread,
Jun 16, 2008, 5:02:40 PM6/16/08
to eventmac...@rubyforge.org
The "columns" and "rows" should probably be merged into an array of
hashes, with the keys merged from the "columns" def and a :value key
from the row data.

As for how to maintain this, I thought this was the problem that
GitHub and DVCS magically solved. Isn't it supposed to read our minds
and stuff and automatically merge everything together? Maybe someone
who has used github for awhile can tell me how this particular piece
of pixie dust is supposed to work. =)

--
---
Thomas H. Ptacek // matasano security
read us on the web: http://www.matasano.com/log

Mark V

unread,
Jun 16, 2008, 8:20:22 PM6/16/08
to eventmac...@rubyforge.org
On Tue, Jun 17, 2008 at 7:02 AM, Thomas Ptacek <tq...@matasano.com> wrote:
> The "columns" and "rows" should probably be merged into an array of
> hashes, with the keys merged from the "columns" def and a :value key
> from the row data.
>
> As for how to maintain this, I thought this was the problem that
> GitHub and DVCS magically solved. Isn't it supposed to read our minds
> and stuff and automatically merge everything together? Maybe someone
> who has used github for awhile can tell me how this particular piece
> of pixie dust is supposed to work. =)
>

Two descriptions I found useful:

http://b.lesseverything.com/2008/3/25/got-git-howto-git-and-github
http://b.lesseverything.com/2008/4/4/git-howto-start-a-new-project-based-on-an-other-project

HTH

Roger Pack

unread,
Jun 17, 2008, 3:19:08 AM6/17/08
to eventmac...@rubyforge.org, m.fel...@gmail.com, ez...@engineyard.com
I'd say try to pass them back in whatever is the fastest to create,
and then let them worry about how to make it work :)
Oh that's me, though.

In further new, got mongrel with a pooled db to work:
non-pooled:
~ time ab -n 50 -c 50 http://127.0.0.1:3000/
real 0m0.116s

pooled:
real 0m0.076s

And also it seems that if your time is dominated by very long running
mySQL queries that it doesn't matter very much if you use pooled or
not, though I could be wrong [it showed like a .5s difference, like
10.3 to 9.8, but it wasn't very strong].
Not sure what/if an ideal setup would be.

Rock on. Here's the pooled fiber :)
So I'd say it works just fine. Only thing left is to get Sequel or AR
to use it as a back end and, coupled with EM you've got a swell,
fibered server [well, if you use ramaze or merb which are thread
safe].

http://code.google.com/p/ruby-roger-useful-functions/source/browse/trunk/asymy/fibers/mongrel_test/connection_pool_fibered.rb

Also as a warning, if you run it with many open connection at the same
time and use ulimit -n [to increase number of file descriptors] and
don't use kqueue/epoll, then some of your connections just hang and
never come back.

Also, for some reason with kqueue I was getting these:
~/19/asymy/fibers/mongrel_test ruby19 test_mongrel_with_db.rb
Assertion failed: (nbytes > 0), function _WriteOutboundData, file
ed.cpp, line 607.
Abort trap

If anybody has seen them before. It appears to have subsequently
stopped doing it, but it's still odd.
Thanks!
-R

Thomas Ptacek

unread,
Jun 17, 2008, 12:09:35 PM6/17/08
to eventmac...@rubyforge.org, m.fel...@gmail.com, ez...@engineyard.com
The error handling in this code is really appalling, so I may work on
that before I do anything else. On most Unix systems you can bump up
the ulimit programatically if you have the privs to run "ulimit".

Roger Pack

unread,
Jun 17, 2008, 1:34:48 PM6/17/08
to eventmac...@rubyforge.org
> On most Unix systems you can bump up
> the ulimit programatically if you have the privs to run "ulimit".

Yeah EM itself has a mechanism for conveniently increasing the file
descriptors available:
EventMachine.set_descriptor_table_size number
and then set_effective_user username

so that you can more conveniently run the program as sudo, set the
descriptors, then change it so you're no longer sudo.[1]

I was noting that if you ever do that, and you don't use epoll or
kqueue, you quite possibly run a danger, as select only handles up to
64 connection [windows] and only file descriptors up to the number
1024 [linux], so need to be careful. Fixing that bug would be nice,
though, too, though unfortunately I can't reliably recreate it. It
does occur, however, with the latest gem release version.

Thanks!
-R

1: http://wilkboardonline.com/roger/rdoc2/classes/EventMachine.html#M000058

Roger Pack

unread,
Jun 19, 2008, 12:54:38 AM6/19/08
to eventmac...@rubyforge.org
> Also, for some reason with kqueue I was getting these:
> ~/19/asymy/fibers/mongrel_test ruby19 test_mongrel_with_db.rb
> Assertion failed: (nbytes > 0), function _WriteOutboundData, file
> ed.cpp, line 607.
> Abort trap

One possible explanation for this is that, using kqueue, all the
sockets are set thus on creation:
em.cpp: EV_SET (&k, ed->GetSocket(), EVFILT_WRITE, EV_ADD |
EV_ONESHOT, 0, 0, ed);

which [I think] means that they will be selected as writable [either
immediately, or when the socket connects--I'd imagine the latter].
This is unanticipated by EM [which thinks it means we had marked it to
write something], so it raises the error since it didn't write
anything.

Now my latest weirdness is trying to figure out why, when I add 1000
ports or so 'in one run', it corrupts the Descriptors table:

(gdb) p Descriptors
Cannot access memory at address 0x3
$8 = {
<std::_Vector_base<EventableDescriptor*,std::allocator<EventableDescriptor*>
>> = {
_M_impl = {
<std::allocator<EventableDescriptor*>> = {
<__gnu_cxx::new_allocator<EventableDescriptor*>> = {<No data
fields>}, <No data fields>},
members of
std::_Vector_base<EventableDescriptor*,std::allocator<EventableDescriptor*>
>::_Vector_impl:
_M_start = 0x58001fc0,
_M_finish = 0x1feb,
_M_end_of_storage = 0x2000
}
}, <No data fields>}


you'll notice that _M_finish looks very small--it should typically be
greater than _M_start

Any ideas?
And, of course, yet another wish--that if you called kqueue or epoll
within an EM::run it would raise, since it might mess up existing
descriptors, and it also won't have been set for the current run block
you're in, so it confuses you as to whether it's in use or not.
Thanks!
-R

James Tucker

unread,
Jun 19, 2008, 5:27:26 AM6/19/08
to eventmac...@rubyforge.org

On 19 Jun 2008, at 05:54, Roger Pack wrote:

>> Also, for some reason with kqueue I was getting these:
>> ~/19/asymy/fibers/mongrel_test ruby19 test_mongrel_with_db.rb
>> Assertion failed: (nbytes > 0), function _WriteOutboundData, file
>> ed.cpp, line 607.
>> Abort trap
>
> One possible explanation for this is that, using kqueue, all the
> sockets are set thus on creation:
> em.cpp: EV_SET (&k, ed->GetSocket(), EVFILT_WRITE, EV_ADD |
> EV_ONESHOT, 0, 0, ed);

Mmm, this is nasty, and I was lead to believe kqueue turns itself on
under EM. Anywhoo, after talking to Kirk last night, we decided it
would be a good idea to add a compile time configuration for the
results of three new functions:

EM::kqueue?
EM::epoll?
EM::ssl?

Which respectively tell you if this part of the extension was
successfully compiled. This should aid in clear intent for apps that
want to be in a generic configuration, and should also solve problems
on some systems where calling epoll or kqueue can cause other problems.

> And, of course, yet another wish--that if you called kqueue or epoll
> within an EM::run it would raise, since it might mess up existing
> descriptors, and it also won't have been set for the current run block
> you're in, so it confuses you as to whether it's in use or not.

Some kind of EM::mode tuple to report this would be useful?

Roger Pack

unread,
Jun 19, 2008, 1:33:49 PM6/19/08
to eventmac...@rubyforge.org
> EM::kqueue?
> EM::epoll?
> EM::ssl?

Or having EM::kqueue return a useful value would do about the same,
though not as flexible [since...once you turn it on, you can never
turn it off--though I can't imagine anybody ever wanting to turn it
off, anyway]. I think Kirk suggested that like a year ago or so :)

>> And, of course, yet another wish--that if you called kqueue or epoll
>> within an EM::run it would raise, since it might mess up existing
>> descriptors, and it also won't have been set for the current run block
>> you're in, so it confuses you as to whether it's in use or not.
>
> Some kind of EM::mode tuple to report this would be useful?

The functions mentioned above would probably be good enough to be able
to know which mode you're running in [i.e. don't set it to kqueue
unless kqueue is available, save that fact away somewhere].
I was just pointing out that if you do
EM::run {
EM::kqueue
# do some stuff
}

That kqueue doesn't actually get run within that block. So it confuses
developers. It has [now that I think about it] the potential of some
nasty side effects, since I think kqueue is indeed run, but it
"misses" the original kqueue initialization stuff, so it might cause
bugs if you allow this to happen. So to avoid confusion it might be
nice to raise on these functions if they're within a running block. I
could add a patch if you'd like.

With regard to the weird nasty error messages, I believe those occur
when using kqueue.
With select [on mac], I get something a little different.

Program received signal EXC_BAD_ACCESS, Could not access memory.
Reason: KERN_PROTECTION_FAILURE at address: 0x0000005f

0x001c678f in EventMachine_t::_RunSelectOnce (this=0xffffffff) at em.cpp:741

741 SelectData.tv = Quantum;

(gdb) p selectData


Cannot access memory at address 0x3

(gdb) p Quantum


Cannot access memory at address 0x3

Odd that things that should be on the stack [SelectData] or in global
space [Quantum] are declared suddenly as 0x3. I wonder if this is
some type of memory corruption. Hopefully sometime soon I can run
this on linux and see if the problem also exists there [and maybe
valgrind will help].

Roger Pack

unread,
Jun 19, 2008, 4:19:09 PM6/19/08
to eventmac...@rubyforge.org, m.fel...@gmail.com, ez...@engineyard.com
K I created a small write up on
"how to make a single threaded, fast ruby web server that uses
asynchronous IO" at
http://betterlogic.com/roger/?p=339
if anyone's interested :)
Seems to have potential.
Also noted that the asymy lib now accomodates for 1.9. Thanks!
-R

Brian Takita

unread,
Aug 6, 2008, 10:04:30 PM8/6/08
to eventmac...@rubyforge.org

Tony Arcieri

unread,
Aug 6, 2008, 10:46:14 PM8/6/08
to eventmac...@rubyforge.org
Yeah, it's been a major problem, especially considering the MySQL gem doesn't seem to use TRAP_BEG / TRAP_END properly...
--
Tony Arcieri
medioh.com

Roger Pack

unread,
Aug 7, 2008, 1:27:57 AM8/7/08
to eventmac...@rubyforge.org
>> ~/dev/ruby-roger-useful-functions/asymy time ruby big_em.rb
>> real 0m4.731s
>> user 0m0.121s
>> sys 0m0.037s
>>
>> ~/dev/ruby-roger-useful-functions/asymy time ruby big_thread.rb
>> real 0m14.033s
>> user 0m0.089s
>> sys 0m0.039s
>
> It appears that the mysql gem causes an interpreter lock.
> http://blog.hungrymachine.com/2008/8/6/ruby-and-multi-threaded-mysql-mri-vs-jruby-jdbc-vs-dataobjects-mysql

Yeah the mysql gem does. You'd have to use an asynchronous mysql
adapter to overcome that [asymy -- or jruby], as discussed a little
above.
Good luck!

Roger Pack

unread,
Sep 2, 2008, 3:19:19 PM9/2/08
to eventmac...@rubyforge.org
>> Speaking of which, I wonder if one could just use revactor on it
>> straight, as it is now :)
>
> It should be possible to use the pure Ruby MySQL in an evented manner by
> (monkey)patching it to use Revactor's sockets rather than Ruby TCP sockets.

Fascinatingly, the pure ruby mysql driver is actually ruby thread
friendly [albeit very slow]. Just learned that--thought I'd mention
it. Guess you could drive it evented-ly that being the case. LOL.

-=R

Reply all
Reply to author
Forward
0 new messages