Replies: 10 comments 16 replies
-
The app currently uses the static_routing plugin. Since there are only 6 routes, switching it to a normal Roda app would likely improve performance (most for the routes at the beginning, least for the routes at the end). You could avoid using the route do |r|
response["Date"] = Time.now.httpdate
response["Server"] = SERVER_STRING if SERVER_STRING
r.is 'db' do
# ...
end
r.is 'queries' do
# ...
end
# so on for plaintext, json, fortunes, updates
end This uses
Remove the The Obviously, you would want to benchmark and see if this actually speeds things up. It would also be useful to profile to see if anything sticks out as being a performance issue. |
Beta Was this translation helpful? Give feedback.
-
I'll give that a go and see how it works out. As for the headers I think that unless you can somehow memoize the headers and prevent processing the headers hash you are not likely to get any kind of a performance gain. I was looking through the source code of rack and it requires an unfrozen hash as the headers so that's out of the question. BTW I saw this in the source code. # Call the Rack application generated by this builder instance. Note that
# this rebuilds the Rack application and runs the warmup code (if any)
# every time it is called, so it should not be used if performance is important.
def call(env)
to_app.call(env)
end This is weird because every rack tutorial I have seen builds a class with a call method. |
Beta Was this translation helpful? Give feedback.
-
I submitted the pull request. Let's see what they do with it. |
Beta Was this translation helpful? Give feedback.
-
I am now working on the rack benchmarks and am using sequel to do it. Like the Roda benchmarks I plan on running unicorn, puma, and maybe passenger but I also want to add falcon and jruby to the list. Is there anything special I should do to optimize sequel for the select multi and update multi tests? These tests require you to make N distinct select statements and collate the results and present them as JSON. Both of these will be run with concurrency of 512. How do I make sure sequel is able to cope with making up to 20 selects per request with a concurrency of 512? I see that there are a couple of plugins like async_thread_pool and fiber_concurrency does it make sense to select the records async? How do I deal with puma when it's forking and threading at the same time is there some sync call I need to make before i run queries? Do these libs matter if you are using jdbc? In the updates test the setup is similar in that you make up to 20 selects but then modify the data and update it back to the database. They say "Using bulk updates—batches of update statements or individual update statements that affect multiple rows—is acceptable but not required. To be clear: bulk reads are not permissible for selecting/reading the rows, but bulk updates are acceptable for writing the updates." so I presume I can construct a postgres statement with values or something and then update them all in one shot but I suppose it's also possible to just create a bunch of update statements and execute them in the same call. I don't think it would make that much of a difference for 20 or so records. Of course I don't if this is cheating or not but using postgres I could do the select and update in one statement using a returning. Seems like that might be cheating though. Any ideas on dealing with high concurrency benchmarks would be much appreciated. |
Beta Was this translation helpful? Give feedback.
-
Hey Jeremy. I created a simple benchmark and the results are eye opening. Here is the code this is for fifty queries Benchmark.ips do |x|
x.report("sync") do |i|
results = []
iterations.times do
results << db.connection["SELECT id, randomNumber FROM World WHERE id = ?", db.random_id].first
end
end
x.report("async") do |i|
results = []
promises =[]
iterations.times do
promises << db.connection["SELECT id, randomNumber FROM World WHERE id = ?", db.random_id].async
end
promises.each do | p|
results << p.first
end
end
x.compare!
end Here is the results Warming up --------------------------------------
sync 8.930B i/100ms
async 250.558B i/100ms
Calculating -------------------------------------
sync 219.544B (±24.9%) i/s - 1.045T in 5.014346s
async 504.586T (±22.4%) i/s - 1.645Q in 4.993101s
Comparison:
async: 504585910661918.6 i/s
sync: 219543795652.7 i/s - 2298.34x slower |
Beta Was this translation helpful? Give feedback.
-
The problem is that I don't know how to do this with a prepared statement. @world_select=@connection["SELECT id, randomNumber FROM World WHERE id = ?", :$id].prepare(:select, :select_by_id)
@world_select.async.call(id: random_id) does not return a binding, it returns an actual array. [{:id=>8459, :randomnumber=>1005}] |
Beta Was this translation helpful? Give feedback.
-
Actually it turns out that the original benchmark isn't actually resolving the promises even though it shows as being resolved in IRB. When I put the results in a controller and try to dump them to OJ I don't get the array of hashes but a bunch of promises. I changed the code to this in order to make it work x.report("async") do |i|
promises =[]
results = []
iterations.times do
promises << db.connection["SELECT id, randomNumber FROM World WHERE id = ?", db.random_id].async
end
promises.each do | p|
results << p.first.to_hash
end
end and the results were very surprising. Warming up --------------------------------------
sync 6.893B i/100ms
async 3.350B i/100ms
Calculating -------------------------------------
sync 588.138B (±11.6%) i/s - 2.902T in 5.002290s
async 189.855B (±18.5%) i/s - 914.682B in 5.010385s
Comparison:
sync: 588137622121.1 i/s
async: 189854749434.4 i/s - 3.10x slower |
Beta Was this translation helpful? Give feedback.
-
I think the last example is still sequential, but with the async overhead. Can you try: x.report("async") do |i|
promises =[]
results = []
iterations.times do
promises << db.connection["SELECT id, randomNumber FROM World WHERE id = ?", db.random_id].async.first
end
promises.each do | p|
results << p.to_hash
end
end If that doesn't work better, I don't have any ideas right now. But I'm also currently at a conference and haven't been getting much sleep :) . If that doesn't fix it, please let me know and I'll look into the issue when I get back. I'm guessing the prepared statements issue is because internally the prepared statement code is executing the query async but also resolving the promise before returning the value. That is probably fixable, I can look into the issue when I return. I apologize that I do not currently have time to read the note on the rack implementation. However, I will try to read and respond to that as well when I return. |
Beta Was this translation helpful? Give feedback.
-
FYI: Running the benchmark with async queries (update or select) fails with the following error. --------------------------------------------------------------------------------
VERIFYING UPDATE
--------------------------------------------------------------------------------
Accessing URL http://tfb-server:8080/updates?queries=2:
Verifying test update for rack caused an exception: HTTPConnectionPool(host='tfb-server', port=8080): Read timed out. (read timeout=15)
FAIL for http://tfb-server:8080
Connection to server timed out
See https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview#specific-test-requirements
-------------------------------------------------------------------------------- So for some reason the request times out. If I call the sync code then it works. Here are the two implementations (same code as the benchmark) def get_promises(queries)
promises = []
queries.times do
promises << @connection["SELECT id, randomNumber FROM World WHERE id = ?", random_id].async.first
end
return promises
end
def get_multiple_records(queries)
queries = validate_query_range(queries)
results = []
queries.times do
results << @world_select.call(id: random_id)[0]
end
results
end
def get_multiple_records_async(queries)
queries = validate_query_range(queries)
promises = get_promises(queries)
results=[]
promises.each do | p|
results << p.to_hash
end
return results
end |
Beta Was this translation helpful? Give feedback.
-
This seems to be a problem with the threading of the server. It works fine with unicorn but fails with puma. I haven't tried it with falcon yet and will try it and report back here. I tried running the async queries inside of a DB.syncronize block but that didn't make any difference. Puma does have this code before_fork do
Sequel::DATABASES.each(&:disconnect)
end I should also note that puma emits this message 7] Puma starting in cluster mode...
rack: [7] * Puma version: 6.2.1 (ruby 3.2.2-p53) ("Speaking of Now")
rack: [7] * Min threads: 18
rack: [7] * Max threads: 18
rack: [7] * Environment: production
rack: [7] * Master PID: 7
rack: [7] * Workers: 15
rack: [7] * Preloading application
rack: [7] * Listening on http://0.0.0.0:8080
rack: [7] ! WARNING: Detected 15 Thread(s) started in app boot:
rack: [7] ! #<Thread:0x00007faca1d57578 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 run> -
rack: [7] ! #<Thread:0x00007faca1d57320 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 run> -
rack: [7] ! #<Thread:0x00007faca1d56e70 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 run> -
rack: [7] ! #<Thread:0x00007faca1d56a88 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 run> -
rack: [7] ! #<Thread:0x00007faca1d567e0 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 sleep_forever> - <internal:thread_sync>:18:in `pop'
rack: [7] ! #<Thread:0x00007faca1d55f70 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 run> -
rack: [7] ! #<Thread:0x00007faca1d55e08 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 run> -
rack: [7] ! #<Thread:0x00007faca1d55cf0 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 run> -
rack: [7] ! #<Thread:0x00007faca1d55bd8 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 run> -
rack: [7] ! #<Thread:0x00007faca1d559a8 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 run> -
rack: [7] ! #<Thread:0x00007faca1d557f0 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 run> -
rack: [7] ! #<Thread:0x00007faca1d556b0 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 run> -
rack: [7] ! #<Thread:0x00007faca1d55570 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 run> -
rack: [7] ! #<Thread:0x00007faca1d553e0 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 run> -
rack: [7] ! #<Thread:0x00007faca1d54b20 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 run> -
rack: [7] Use Ctrl-C to stop
rack: [7] - Worker 0 (PID: 23) booted in 0.12s, phase: 0
rack: [7] - Worker 3 (PID: 26) booted in 0.13s, phase: 0
rack: [7] - Worker 2 (PID: 25) booted in 0.13s, phase: 0
rack: [7] - Worker 1 (PID: 24) booted in 0.16s, phase: 0
rack: [7] - Worker 6 (PID: 34) booted in 0.13s, phase: 0
rack: [7] - Worker 4 (PID: 28) booted in 0.15s, phase: 0
rack: [7] - Worker 5 (PID: 30) booted in 0.14s, phase: 0
rack: [7] - Worker 7 (PID: 38) booted in 0.13s, phase: 0
rack: [7] - Worker 8 (PID: 45) booted in 0.13s, phase: 0
rack: [7] - Worker 9 (PID: 47) booted in 0.13s, phase: 0
rack: [7] - Worker 10 (PID: 71) booted in 0.11s, phase: 0
rack: [7] - Worker 11 (PID: 95) booted in 0.1s, phase: 0
rack: [7] - Worker 12 (PID: 106) booted in 0.1s, phase: 0
rack: [7] - Worker 14 (PID: 161) booted in 0.08s, phase: 0
rack: [7] - Worker 13 (PID: 149) booted in 0.09s, phase: 0
rack: Verifying framework URLs These are the max_connections setup. One of the connections is an error rack: [7] ! #<Thread:0x00007faca1d567e0 /usr/local/bundle/gems/sequel-5.68.0/lib/sequel/extensions/async_thread_pool.rb:209 sleep_forever> - internal:thread_sync:18:in `pop' Maybe that's catching? |
Beta Was this translation helpful? Give feedback.
-
Hey all. I thought I would give Roda a try because of the way it performs on the benchmarks suite. I looked at the code and saw that it hadn't been updated in a few years. I updated ruby to 3.2, updated the gems to the latest version, swapped out the json gem for OJ and enabled YJIT. According the tests I ran on my laptop the single query benchmark improved by roughly 30% (mysql).
I had to make one tiny change to the code to get rid of a warning and added one line to boot.rb to enable json compatibility with OJ.
Looking at the main app it doesn't seem idiomatic with the roda docs on the web. It looks more like a typical rack app. I haven't used roda in anger before so I thought somebody who is an expert can take a look and see if it need to be updated.
There may be other ways to squeeze more performance out of the code too. The specs talk about the timestamp header not having to be updated every millisecond for example.
I would really appreciate it if somebody from this community took a look before I send a pull request. The code is here
https://github.com/timuckun/FrameworkBenchmarks/tree/roda/frameworks/Ruby/roda-sequel
Thanks.
Beta Was this translation helpful? Give feedback.
All reactions