Browse Source

sinatra-sequel on JRuby

* Make sinatra-sequel framework compatible with JRuby 9K
* Add runner, language setup, and benchmark config for
  sinatra-sequel-puma-jruby
* Bug Fix: sinatra-sequel framework was applying updates on /updates,
  but returning the original records (instead of the updated records)
* Incorporated findings from StackProf analysis and clearer reading of
  Sequel documentation
Mike Pastore 9 years ago
parent
commit
3dc9d5da48

+ 7 - 13
frameworks/Ruby/sinatra-sequel/Gemfile

@@ -1,15 +1,9 @@
 source 'https://rubygems.org'
 
-platforms :ruby do
-  gem 'mysql2', '0.4.0'
-  gem 'json', '1.8.1', :require => 'json/ext'
-end
-
-gem 'puma', '2.15.3'
-
-gem 'sequel', '4.28.0'
-
-gem 'sinatra', '1.4.6', :require => 'sinatra/base'
-gem 'sinatra-contrib', '1.4.6', :require => 'sinatra/json'
-
-gem 'slim', '3.0.6'
+gem 'jdbc-mysql', '~> 5.1.37', :platform => 'jruby', :require => 'jdbc/mysql'
+gem 'json', '~> 1.8.3', :require => 'json/ext'
+gem 'mysql2', '~> 0.4.0', :platform => 'ruby'
+gem 'puma', '~> 2.15.3'
+gem 'sequel', '~> 4.28.0'
+gem 'sinatra', '~> 1.4.6', :require => 'sinatra/base'
+gem 'slim', '~> 3.0.6'

+ 35 - 0
frameworks/Ruby/sinatra-sequel/Gemfile.lock

@@ -0,0 +1,35 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    jdbc-mysql (5.1.37)
+    json (1.8.3)
+    json (1.8.3-java)
+    mysql2 (0.4.2)
+    puma (2.15.3)
+    puma (2.15.3-java)
+    rack (1.6.4)
+    rack-protection (1.5.3)
+      rack
+    sequel (4.28.0)
+    sinatra (1.4.6)
+      rack (~> 1.4)
+      rack-protection (~> 1.4)
+      tilt (>= 1.3, < 3)
+    slim (3.0.6)
+      temple (~> 0.7.3)
+      tilt (>= 1.3.3, < 2.1)
+    temple (0.7.6)
+    tilt (2.0.1)
+
+PLATFORMS
+  java
+  ruby
+
+DEPENDENCIES
+  jdbc-mysql (~> 5.1.37)
+  json (~> 1.8.3)
+  mysql2 (~> 0.4.0)
+  puma (~> 2.15.3)
+  sequel (~> 4.28.0)
+  sinatra (~> 1.4.6)
+  slim (~> 3.0.6)

+ 23 - 0
frameworks/Ruby/sinatra-sequel/benchmark_config.json

@@ -23,6 +23,29 @@
       "display_name": "sinatra-sequel-puma-mri",
       "notes": "",
       "versus": "rack-puma-mri"
+    },
+    "puma-jruby": {
+      "setup_file": "run_jruby_puma",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "MySQL",
+      "framework": "sinatra-sequel",
+      "language": "Ruby",
+      "orm": "Full",
+      "platform": "JRuby",
+      "webserver": "Puma",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "sinatra-sequel-puma-jruby",
+      "notes": "",
+      "versus": "rack-puma-jruby"
     }
   }]
 }

+ 42 - 33
frameworks/Ruby/sinatra-sequel/hello_world.rb

@@ -3,22 +3,19 @@ SEQUEL_NO_ASSOCIATIONS = true
 
 Bundler.require :default
 
-# Configure Slim templating engine
-Slim::Engine.set_options \
-  :format => :html,
-  :sort_attrs => false
-
-# Configure Sequel ORM
-DB = Sequel.connect \
-  :adapter => RUBY_PLATFORM == 'java' ? 'jdbc:mysql' : 'mysql2',
-  :host => ENV['DB_HOST'],
-  :database => 'hello_world',
-  :user => 'benchmarkdbuser',
-  :password => 'benchmarkdbpass',
-  :max_connections => 32, # == max worker threads per process
+# Configure Sequel ORM (Sequel::DATABASES)
+Sequel.connect \
+  '%<adapter>s://%<host>s/%<database>s?user=%<user>s&password=%<password>s' % {
+    :adapter => RUBY_PLATFORM == 'java' ? 'jdbc:mysql' : 'mysql2',
+    :host => ENV['DBHOST'],
+    :database => 'hello_world',
+    :user => 'benchmarkdbuser',
+    :password => 'benchmarkdbpass'
+  },
+  :max_connections => (ENV['MAX_THREADS'] || 4).to_i,
   :pool_timeout => 5
 
-# Allow #to_json on models and arrays of models
+# Allow #to_json on models and datasets
 Sequel::Model.plugin :json_serializer
 
 class World < Sequel::Model(:World); end
@@ -28,17 +25,23 @@ class Fortune < Sequel::Model(:Fortune)
   unrestrict_primary_key
 end
 
+# Configure Slim templating engine
+Slim::Engine.set_options \
+  :format => :html,
+  :sort_attrs => false
+
 # Our Rack application to be executed by rackup
 class HelloWorld < Sinatra::Base
   configure do
+    # Static file serving is ostensibly disabled in modular mode but Sinatra
+    # still calls an expensive Proc on every request...
+    disable :static
+
+    # XSS, CSRF, IP spoofing, etc. protection are not explicitly required
     disable :protection
 
     # Don't add ;charset= to any content types per the benchmark requirements
     set :add_charset, []
-
-    # Specify the encoder - otherwise, sinatra/json inefficiently
-    # attempts to load one of several on each request
-    set :json_encoder, :to_json
   end
 
   helpers do
@@ -55,29 +58,30 @@ class HelloWorld < Sinatra::Base
 
   after do
     # Add mandatory HTTP headers to every response
-    response['Server'] = 'Puma'
-    response['Date'] = Time.now.to_s
+    response['Server'] ||= 'Puma'
+    response['Date'] ||= Time.now.to_s
   end
 
-  get '/json' do
-    json :message => 'Hello, World!'
+  get '/json', :provides => :json do
+    JSON.fast_generate :message => 'Hello, World!'
   end
 
-  get '/plaintext' do
-    content_type 'text/plain'
+  get '/plaintext', :provides => :text do
     'Hello, World!'
   end
 
-  get '/db' do
-    json World[rand1]
+  get '/db', :provides => :json do
+    World[rand1].to_json
   end
 
-  get '/queries' do
+  get '/queries', :provides => :json do
     queries = (params[:queries] || 1).to_i
     queries = 1 if queries < 1
     queries = 500 if queries > 500
 
-    json World.where(:id => randn(queries))
+    World
+      .where(:id => randn(queries))
+      .to_json
   end
 
   get '/fortunes' do
@@ -91,26 +95,31 @@ class HelloWorld < Sinatra::Base
     slim :fortunes
   end
 
-  get '/updates' do
+  get '/updates', :provides => :json do
     queries = (params[:queries] || 1).to_i
     queries = 1 if queries < 1
     queries = 500 if queries > 500
 
     # Prepare our updates in advance so transaction retries are idempotent
-    updates = randn(queries).sort!.map! { |id| [id, rand1] }
+    updates = randn(queries).map! { |id| [id, rand1] }.to_h
 
     worlds = nil
 
     World.db.transaction do
       worlds = World
-        .where(:id => updates.transpose.first)
+        .where(:id => updates.keys.sort!)
         .for_update
+        .all
+
+      worlds
+        .each { |w| w.randomNumber = updates[w.id] }
 
       World.dataset
         .on_duplicate_key_update(:randomNumber)
-        .import([:id, :randomNumber], updates)
+        .import([:id, :randomNumber], worlds.map { |w| [w.id, w.randomNumber] })
     end
 
-    json worlds
+    # The models are dirty but OK to return if the batch update was successful
+    World.to_json :array => worlds
   end
 end

+ 12 - 0
frameworks/Ruby/sinatra-sequel/run_jruby_puma.sh

@@ -0,0 +1,12 @@
+#!/bin/bash
+
+fw_depends rvm jruby-9k
+
+rvm jruby-$JRUBY_VERSION do \
+  bundle install --jobs=4 --gemfile=$TROOT/Gemfile --path=vendor/bundle
+
+MAX_THREADS=256 ; export MAX_THREADS
+MIN_THREADS=$(( MAX_THREADS / 8 * 2 ))
+
+rvm jruby-$JRUBY_VERSION do \
+  bundle exec puma -t $MIN_THREADS:$MAX_THREADS -b tcp://0.0.0.0:8080 -e production &

+ 8 - 5
frameworks/Ruby/sinatra-sequel/run_mri_puma.sh

@@ -1,10 +1,13 @@
 #!/bin/bash
 
-MRI_VERSION=ruby-2.2.1
+fw_depends rvm ruby-2.2
 
-fw_depends rvm $MRI_VERSION
+rvm ruby-$MRI_VERSION do \
+  bundle install --jobs=4 --gemfile=$TROOT/Gemfile --path=vendor/bundle
 
-rvm $MRI_VERSION do bundle install --gemfile=$TROOT/Gemfile --path vendor/bundle
+WORKERS=8 # enable Puma's clustered mode
+MAX_THREADS=32 ; export MAX_THREADS
+MIN_THREADS=$(( MAX_THREADS / WORKERS * 2 ))
 
-DB_HOST=${DBHOST} \
-rvm $MRI_VERSION do bundle exec puma -w 8 -t 8:32 -b tcp://0.0.0.0:8080 -e production &
+rvm ruby-$MRI_VERSION do \
+  bundle exec puma -w $WORKERS -t $MIN_THREADS:$MAX_THREADS -b tcp://0.0.0.0:8080 -e production &

+ 31 - 0
toolset/setup/linux/languages/jruby-9k.sh

@@ -0,0 +1,31 @@
+#!/bin/bash
+
+fw_depends rvm java8
+
+# rvm stable [typically] only provides one version of jruby-9.0
+# update this when it changes
+JRUBY_VERSION="9.0.0.0.pre1"
+
+RETCODE=$(fw_exists ${IROOT}/jruby-${JRUBY_VERSION}.installed)
+[ ! "$RETCODE" == 0 ] || { \
+  # Load environment variables
+  source $IROOT/jruby-$JRUBY_VERSION.installed
+  return 0; }
+
+# We assume single-user installation as
+# done in our rvm.sh script and
+# in Travis-CI
+if [ "$TRAVIS" = "true" ]
+then
+  rvmsudo rvm install jruby-$JRUBY_VERSION
+  # Bundler is SOMETIMES missing... not sure why.
+  rvmsudo rvm jruby-$JRUBY_VERSION do gem install bundler
+else
+  rvm install jruby-$JRUBY_VERSION
+  # Bundler is SOMETIMES missing... not sure why.
+  rvm jruby-$JRUBY_VERSION do gem install bundler
+fi
+
+echo "" > $IROOT/jruby-$JRUBY_VERSION.installed
+
+source $IROOT/jruby-$JRUBY_VERSION.installed

+ 0 - 28
toolset/setup/linux/languages/ruby-2.2.1.sh

@@ -1,28 +0,0 @@
-#!/bin/bash
-
-fw_depends rvm
-
-VERSION=2.2.1
-RETCODE=$(fw_exists ${IROOT}/ruby-${VERSION}.installed)
-[ ! "$RETCODE" == 0 ] || { \
-  # Load environment variables
-  source $IROOT/ruby-$VERSION.installed
-  return 0; }
-
-# We assume single-user installation as 
-# done in our rvm.sh script and 
-# in Travis-CI
-if [ "$TRAVIS" = "true" ]
-then
-  rvmsudo rvm install $VERSION
-  # Bundler is SOMETIMES missing... not sure why.
-  rvmsudo rvm $VERSION do gem install bundler
-else
-  rvm install $VERSION
-  # Bundler is SOMETIMES missing... not sure why.
-  rvm $VERSION do gem install bundler
-fi
-
-echo "" > $IROOT/ruby-$VERSION.installed
-
-source $IROOT/ruby-$VERSION.installed

+ 31 - 0
toolset/setup/linux/languages/ruby-2.2.sh

@@ -0,0 +1,31 @@
+#!/bin/bash
+
+fw_depends rvm
+
+# rvm stable [typically] only provides one version of ruby-2.2
+# update this when it changes
+MRI_VERSION=2.2.1
+
+RETCODE=$(fw_exists ${IROOT}/ruby-${MRI_VERSION}.installed)
+[ ! "$RETCODE" == 0 ] || { \
+  # Load environment variables
+  source $IROOT/ruby-$MRI_VERSION.installed
+  return 0; }
+
+# We assume single-user installation as
+# done in our rvm.sh script and
+# in Travis-CI
+if [ "$TRAVIS" = "true" ]
+then
+  rvmsudo rvm install $MRI_VERSION
+  # Bundler is SOMETIMES missing... not sure why.
+  rvmsudo rvm $MRI_VERSION do gem install bundler
+else
+  rvm install $MRI_VERSION
+  # Bundler is SOMETIMES missing... not sure why.
+  rvm $MRI_VERSION do gem install bundler
+fi
+
+echo "" > $IROOT/ruby-$MRI_VERSION.installed
+
+source $IROOT/ruby-$MRI_VERSION.installed