Concurrent requests to ruby REST API not possible - mysql

I am trying to create some small REST API using ruby with Sinatra gem running on thin server. The point is to get an idea about how easy/hard is to build such REST API consisting of micro web services and to compare this with other programming languages / technologies available at Amazon's AWS. I have created one quite easily, here's the code (just minimal working project, not yet considering any kind of optimization):
require 'sinatra'
require 'mysql'
require 'json'
set :environment, :development
db_host = 'HOST_URL'
db_user = 'USER'
db_pass = 'PASSWORD'
db_name = 'DB_NAME'
db_enc = 'utf8'
select = 'SELECT * FROM table1 LIMIT 30'
db = Mysql.init
db.options Mysql::SET_CHARSET_NAME, db_enc
db = db.real_connect db_host, db_user, db_pass, db_name
get '/brands' do
rs = db.query select
#db.close
result = []
rs.each_hash do |row|
result.push row
end
result.to_json
end
Running this with ruby my_ws.rb starts Sinatra running on thin, no problem.
Using curl from my terminal like curl --get localhost:4567/brands is also not a problem returning the desired JSON response.
The real problem I am tackling now for few hours already (and searching on Google of course, reading lot of resources also here on SO) is when I try to benchmark the micro WS using Siege with more concurrent users:
sudo siege -b http://localhost:4567/brands -c2 -r2
This should run in benchnark mode issuing 2 concurrent request (-c2 switch) 2 times (-r2 switch). In this case I always get an error in the console stating Mysql::ProtocolError - invalid packet: sequence number mismatch(102 != 2(expected)): while the number 102 is always different on each run. If I run the benchmark only for one user (one concurrent request, i.e. no concurrency at all) I can run it even 1000 times with no errors (sudo siege -b http://localhost:4567/brands -c1 -r1000).
I tried adding manual threading into my code like:
get '/brands' do
th = Thread.new do
rs = db.query select
#db.close
result = []
rs.each_hash do |row|
result.push row
end
result.to_json
end
th.join
th.value
end
but with no help.
From what I have found:
Sinatra is multithreaded by default
thin is also multithreaded if run from Sinatra if run by ruby script.rb
multithreading seems to have no effect on DB queries - here it looks like no concurrency is possible
I'm using ruby-mysql gem as I have found out it's newer then (just) mysql gem but in the end have no idea which one to use (found old articles to use mysql and some other to use ruby-mysql instead).
Any idea on how to run concurrent requests to my REST API? I need to benchmark and compare it with other languages (PHP, Python, Scala, ...).

The problem is solved with two fixes.
The first one is by replacing the mysql adapter with mysql2.
The second one was the real cause of the problem: the MySQL connection was created once for runtime before the I could even dive into the thread (i.e. before the code for route was executed) causing (logically) connection locks.
Now with mysql2 and connection to DB moved under the route execution it is all working perfectly fine even for 250 concurrent requests! The final code:
require 'sinatra'
require 'mysql2'
require 'json'
set :environment, :development
db_host = 'HOST_URL'
db_user = 'USER'
db_pass = 'PASSWORD'
db_name = 'DB_NAME'
db_enc = 'utf8'
select = 'SELECT * FROM table1 LIMIT 30'
get '/brands' do
result = []
Mysql2::Client.new(:host => db_host, :username => db_user, :password => db_pass, :database => db_name, :encoding => db_enc).query(select).each do |row|
result.push row
end
result.to_json
end
Running sudo siege -b http://localhost:4567/brands -c250 -r4 gives me now:
Transactions: 1000 hits
Availability: 100.00 %
Elapsed time: 1.54 secs
Data transferred: 2.40 MB
Response time: 0.27 secs
Transaction rate: 649.35 trans/sec
Throughput: 1.56 MB/sec
Concurrency: 175.15
Successful transactions: 1000
Failed transactions: 0
Longest transaction: 1.23
Shortest transaction: 0.03

Related

MySQL login-path issues with clustercheck script used in xinetd

default: on
# description: mysqlchk
service mysqlchk
{
# this is a config for xinetd, place it in /etc/xinetd.d/
disable = no
flags = REUSE
socket_type = stream
type = UNLISTED
port = 9200
wait = no
user = root
server = /usr/bin/mysqlclustercheck
log_on_failure += USERID
only_from = 0.0.0.0/0
#
# Passing arguments to clustercheck
# <user> <pass> <available_when_donor=0|1> <log_file> <available_when_readonly=0|1> <defaults_extra_file>"
# Recommended: server_args = user pass 1 /var/log/log-file 0 /etc/my.cnf.local"
# Compatibility: server_args = user pass 1 /var/log/log-file 1 /etc/my.cnf.local"
# 55-to-56 upgrade: server_args = user pass 1 /var/log/log-file 0 /etc/my.cnf.extra"
#
# recommended to put the IPs that need
# to connect exclusively (security purposes)
per_source = UNLIMITED
}
/etc/xinetd.d #
It is kind of strange that script works fine when run manually when it runs using /etc/xinetd.d/ , it is not working as expected.
In mysqlclustercheck script, instead of using --user= and passord= syntax, I am using --login-path= syntax
script runs fine when I run using command line but status for xinetd was showing signal 13. After debugging, I have found that even simple command like this is not working
mysql_config_editor print --all >>/tmp/test.txt
We don't see any output generated when it is run using xinetd ( mysqlclustercheck)
Have you tried the following instead of /usr/bin/mysqlclustercheck?
server = /usr/bin/clustercheck
I am wondering if you could test your binary location with the linux which command.
A long time ago since this question was asked, but it just came to my attention.
First of all as mentioned, Percona Cluster Control script is called clustercheck, so make sure you are using the correct name and correct path.
Secondly, since the server script runs fine from command line, it seems to me that the path of mysql client command is not known by the xinetd when it runs the Cluster Control script.
Since the mysqlclustercheck script as it is offered from Percona, it uses only the binary name mysql without specifying the absolute path I suggest you do the following:
Find where mysql client command is located on your system:
ccloud#gal1:~> sudo -i
gal1:~ # which mysql
/usr/local/mysql/bin/mysql
gal1:~ #
then edit script /usr/bin/mysqlclustercheck and in the following line:
MYSQL_CMDLINE="mysql --defaults-extra-file=$DEFAULTS_EXTRA_FILE -nNE --connect-timeout=$TIMEOUT \
place the exact path of mysql client command you found in the previous step.
I also see that you are not using MySQL connection credentials for connecting to MySQL server. mysqlclustercheck script as it is offered from Percona, it uses User/Password in order to connect to MySQL server.
So normally, you should execute the script in the command line like:
gal1:~ # /usr/sbin/clustercheck haproxy haproxyMySQLpass
HTTP/1.1 200 OK
Content-Type: text/plain
Where haproxy/haproxyMySQLpass is the MySQL connection user/pass for HAProxy monitoring user.
Additionally, you should specify them to your script's xinetd settings like:
server = /usr/bin/mysqlclustercheck
server_args = haproxy haproxyMySQLpass
Last but not least, the signal 13 you are getting is because you try to write something in a script run by xinetd. If for example in your mysqlclustercheck you try to add a statement like
echo "debug message"
you probably going to see the broken pipe signal (13 in POSIX).
Finally, I had issues with this script using SLES 12.3 and I finally manage to run it not as 'nobody' but as 'root'.
Hope it helps

Server hangs all requests after a while

Our Rails 4.0 application (Ruby 2.1.2) is running on Nginx with Puma 2.9.0.
I recently noticed that all requests to our application hang after a while (usually 1 or 2 days).
When checking the log, which is set to debug mode, I noticed the following log stack up:
[2014-10-11T00:02:31.727382 #23458] INFO -- : Started GET "/" for ...
It does mean that requests actually hit the Rails app but somehow it isn't proceeded, while normally it would be:
I, [2014-10-11T00:02:31.727382 #23458] INFO -- : Started GET "/" for ....
I, [2014-10-11T00:02:31.729393 #23458] INFO -- : Processing by HomeController#index as HTML
My puma config is the following:
threads 16,32
workers 4
Our application is only for internal usage as now, so the RPM is very low, and none of the requests are take longer than 2s.
What is/are the reasons that could lead to this problem? (puma config, database connection, etc.)
Thank you in advance.
Update:
After installing the gem rack_timer to log the time spent on each middleware, I realized that our requests has been stuck at the ActiveRecord::QueryCache when the hang occurred, with huge amount of time on it:
Rack Timer (incoming) -- ActiveRecord::QueryCache: 925626.7731189728 ms
I removed this middleware for now and it seems to be back to normal. However, I understand the purpose of this middleware is to increase the performance, so removing it is just a temporary solution. Please help me find out the possible cause of this issue.
FYI, we're using mysql (5.1.67) with adapter mysql2 (0.3.13)
It could be a symptom of RAM starvation due to the query cache getting too big. We saw this in one of our apps running on Heroku. The default query cache is set to 1000. Lowering the limit eased the RAM usage for us with no noticeable performance degradation:
database.yml:
default: &default
adapter: postgresql
pool: <%= ENV["DB_POOL"] || ENV['MAX_THREADS'] || 5 %>
timeout: 5000
port: 5432
host: localhost
statement_limit: <%= ENV["DB_STATEMENT_LIMIT"] || 200 %>
However searching for "activerecord querycache slow" returns other causes, such as perhaps outdated versions of Ruby or Puma or rack-timeout: https://stackoverflow.com/a/44158724/126636
Or maybe a too large value for read_timeout: https://stackoverflow.com/a/30526430/126636

MySQL-proxy and basic failover (detect state)

I have just installed mysql proxy 0.8.2, and started playing with it. I am using it together with two MySQL 5.5 servers, listening on 3306, the proxy is running on 4040. Oh, and OS is Win 7 32-bit.
My problem is that that the mysql proxy checking the state of the servers doesn't seem like it should.
I start up the script, and it runs as it should. But when I shutdown the primary server, the script doesn't seem to recognize that - it still tries to connect to it...
Version information
mysql-proxy 0.8.2
chassis: mysql-proxy 0.8.2
glib2: 2.16.6
libevent: 1.4.12-stable
LUA: Lua 5.1.2
package.path: C:\ProgramX86\dev\mysql-proxy\lib\mysql-proxy\lua\?.lua
package.cpath: C:\ProgramX86\dev\mysql-proxy\bin\lua-?.dll
-- modules
proxy: 0.8.2*
My config
[mysql-proxy]
proxy-address = :4040
proxy-backend-addresses = 10.3.0.9:3306,192.168.4.100:3306
proxy-lua-script = C:/ProgramX86/dev/mysql-proxy/failover3.lua
daemon = true
Failover lua script
function connect_server()
if proxy.global.backends[1].state == proxy.BACKEND_STATE_DOWN then
proxy.connection.backend_ndx = 2
else
proxy.connection.backend_ndx = 1
end
print ("s Connecting: " .. proxy.global.backends[proxy.connection.backend_ndx].dst.name)
end
function read_query(packet)
if proxy.global.backends[1].state == proxy.BACKEND_STATE_DOWN then
proxy.connection.backend_ndx = 2
else
proxy.connection.backend_ndx = 1
end
print ("q Connecting: " .. proxy.global.backends[proxy.connection.backend_ndx].dst.name)
end
It's because proxy.global.backends[1].state is still proxy.BACKEND_STATE_UP when the primary server is shut down.
Someone said it will take 3 minutes to wait the back end response, instead of watching Mysql service all the time.
I am trying to find a better way to solve the problem.

asynchronous queries with MySQL/Perl DBI

It is possible that my question title is misleading, but here goes --
I am trying out a prototype app which involves three MySQL/Perl Dancer powered web apps.
The user goes to app A which serves up a Google maps base layer. On document ready, app A makes three jQuery ajax calls -- two to app B like so
http://app_B/points.json
http://app_B/polys.json
and one to app C
http://app_C/polys.json
Apps B and C query the MySQL database via DBI, and serve up json packets of points and polys that are rendered in the user's browser.
All three apps are proxied through Apache to Perl Starman running via plackup started like so
$ plackup -E production -s Starman -w 10 -p 5000 path/to/app_A/app.pl
$ plackup -E production -s Starman -w 10 -p 5001 path/to/app_B/app.pl
$ plackup -E production -s Starman -w 10 -p 5002 path/to/app_C/app.pl
From time to time, I start getting errors back from the apps called via Ajax. The initial symptoms were
{"error":"Warning caught during route
execution: DBD::mysql::st fetchall_arrayref
failed: fetch() without execute() at
<path/to/app_B/app.pm> line 79.\n"}
The offending lines are
71> my $sql = qq{
72> ..
73>
74>
75> };
76>
77> my $sth = $dbh->prepare($sql);
78> $sth->execute();
79> my $res = $sth->fetchall_arrayref({});
This is bizarre... how can execute() not take place above? Perl doesn't have a habit of jumping over lines, does it? So, I turned on DBI_TRACE
$DBI_TRACE=2=logs/dbi.log plackup -E production -p 5001 -s Starman -w
10 -a bin/app.pl
And, following is what stood out to me as the potential culprit in the log file
> Handle is not in asynchronous mode error 2000 recorded: Handle is
> not in asynchronous mode
> !! ERROR: 2000 CLEARED by call to fetch method
What is going on? Basically, as is, app A is non-functional because the other apps don't return data "reliably" -- I put that in quotes because they do work correctly occasionally, so I know I don't have any logic or syntax errors in my code. I have some kind of intrinsic plumbing errors.
I did find the following on DBD::mysql about ASYNCHRONOUS_QUERIES and am wondering if this is the cause and the solution of my problem. Essentially, if I want async queries, I have to add {async => 1} to my $dbh-prepare(). Except, I am not sure if I want async true or false. I tried it, it and it doesn't seem to help.
I would love to learn what is going on here, and what is the right way to solve this.
How are you managing your database handles? If you are opening a connection before starman forks your code then multiple children may be trying to share one database handle and are confusing MySQL. You can solve this problem by always running a DBI->connect in your methods that talk to the database, but that can be inefficient. Many people switch over to some sort of connection pool, but I have no direct experience with any of them.

Connecting to MySQL DB in Ruby Rspec

[n00b alert] I'm probably doing this all wrong... RSpec outputs this failure:
1)... #skipped irrelevant info
Failure/Error: graph.read_db('example1')
Not connected to any DB. #error msg
#./prim.rb:135:in 'read_db'
#./prim_spec.rb:171:in 'block (2 levels) in <top (required)>'
I have set up a MySQL database on the same machine. The program provides an algorithm for computing a graph's minimum spanning tree. Has methods for file I/O, database I/O using ActiveRecord, etc. All WORKS WELL except RSpec tests.
Code (irrelevant parts left out):
prim_spec.rb
describe PGraph, "online" do
before (:all) do
ActiveRecord::Base.establish_connection(
:adapter => "mysql2",
:host => "localhost",
:username => "root",
:password => "xxxxx",
:database => "rubytasks" )
#the exact same statement works perfectly when running the program itself, but fails in RSpec
end
before (:each) do
#graph = PGraph.new
end
it "should correctly retrieve data from database" do
#graph.read_db('example1') #line 171
#business part goes here
end
end
prim.rb
class PGraph
def read_db(graphID)
#the error which is raised (line 135):
raise "Not connected to any DB." unless ActiveRecord::Base.connected?
#reading goes here
end
end
Connection and PGraph manipulation is performed in ui.rb.
So, ummm, what's the correct way to access a real DB (I'm lazy) for testing (or is the problem elsewhere?)? Preferably something simple, since this is just a school assignment. And without messing with Rails or other gems.
PS: I'm using the most recent versions of all gems and server. On Windows 7 x86. Ruby 1.9.2. Thanks.
I'm guessing not everything is loaded properly when you run your rspec tests. Are all classes that setup your database connection loaded properly and with the right parameters when running rspec?