We continue a series of articles in which we acquaint readers with various web frameworks. And today let me introduce Goliath (Goliath,
http://postrank-labs.github.com/goliath/ ) - an asynchronous web framework for Ruby, created by PostRank (
http://postrank.com/ ), now purchased by Google.
The main feature of Goliath is the use of an event model for I / O, via the EventMachine library, as well as the fibers mechanism (fibers), which appeared in Ruby 1.9. It can be considered an analogue of Node.js, so fashionable today, only in Ruby.
In the article we will consider the following questions:
- fibers and events;
- install goliath;
- writing a simple chat using the mechanism of long-polling;
In conclusion, you will find the traditional performance tests.
')
Fibers and events
Fiber is a kind of execution context, logically similar to a stream, but it is not a stream. Threads are used to parallelize tasks, while fibers are more suitable for asynchronous I / O operations. In fact, fiber is such an advanced goto, wrapped in an abstraction that looks like a stream, only the fibers are actually executed in the same physical stream. If, in a real thread, execution is interrupted by itself and in an arbitrary place of code at the will of the operating system, then in the case of fibers, the developer himself decides when and where to transfer control to another part of the program code.
Ruby fibers are implemented through the Fiber class and its new, yield, and resume methods. A fiber is created as a block of code that can be executed, similar to a thread, but does not start right away. Then calling resume for some fiber will transfer control inside the block. The code block in the fiber will be executed until it ends or the call to yield is met. Calling a yield means stopping the execution of the code in this place, remembering the state and moving on to the execution of the main code that caused the resume. Usually there is a common event loop for all fibers, where control is transferred each time one of the fibers has finished or suspended its work - the Event loop. In the loop, the program waits for the occurrence of any events and calls resume, respectively, for those fibers that were expected by these events.
This approach has several advantages over conventional flows:
- The overhead of creating fiber is minimal - the system planner is not involved here.
- There is no need to take care of synchronization. all fibers are executed in turn in one thread and the developer himself decides when it is possible to transfer control.
- Performance on one core is potentially higher, because switching between execution contexts can be arranged in the most convenient places.
However, it is worth remembering the limitations:
- Fibers will not help involve multiple processor cores.
- If in the code of one fiber there is a hangup or an eternal loop, all fibers in this stream will be blocked.
Here is a small example demonstrating how fibers work:
require 'fiber'
Fibers are supported in Ruby since version 1.9. You can read more about the fibers and the EventMachine library in the
blog of Ilya Grigorik - the author of Goliath and other interesting libraries.
Setting the working environment
I will not re-describe how to install Helicon Zoo, it is described in sufficient detail on the product home page
http://www.helicontech.com/zoo/To install Goliath, launch the Web Platform Installer, select Zoo -> Engines -> Goliath. When installing Goliath, Ruby 1.9.3 will automatically be installed if it is not already in the system. At the moment this is the most suitable version. The goliath supports JRuby, but since the fibers there are implemented through streams, the speed is much lower than in the case of Ruby 1.9. Note that the JRuby team plans to improve fiber support in the near future.
In the
previous article, I showed how to create a new application using WebMatrix and IIS Express. This time, I'll show you how to do the same thing directly from IIS Manager without installing WebMatrix and IIS Express into the system. Follow
this link and download the zip file for the Goliath project. Now launch the IIS Manager, create a new web site and select Depoly -> Import Application on the tab. Then find the downloaded file and follow the wizard’s instructions.

Well, so that what is happening does not seem magic, it is worth adding that the project on Goliath is very simple. There are no folders and rights, no deploy-scripts. Just in the application folder, two files are created - app.rb and web.config. Here are the contents of web.config with comments. You can simply create such a file in any IIS application and get a working Goliath application there.
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <heliconZoo> <application name="goliath.project" > <environmentVariables> <add name="APP_WORKER" value="app.rb" /> <add name="DEPLOY_FILE" value="deploy.rb" /> <add name="DEPLOY_LOG" value="log\zoo-deploy.log" /> <add name="RACK_ENV" value="development" /> </environmentVariables> </application> </heliconZoo> <handlers> <add name="goliath.project#x86" scriptProcessor="goliath.http" path="*" verb="*" modules="HeliconZoo_x86" preCondition="bitness32" resourceType="Unspecified" requireAccess="Script" /> <add name="goliath.project#x64" scriptProcessor="goliath.http" path="*" verb="*" modules="HeliconZoo_x64" preCondition="bitness64" resourceType="Unspecified" requireAccess="Script" /> </handlers> <rewrite> <rules> <rule name="Avoid Static Files" stopProcessing="true"> <match url="^(?!public)(.*)$" ignoreCase="false" /> <conditions logicalGrouping="MatchAll" trackAllCaptures="true"> <add input="{APPL_PHYSICAL_PATH}" pattern="(.*)" ignoreCase="false" /> <add input="{C:1}public\{R:1}" matchType="IsFile" /> </conditions> <action type="Rewrite" url="public/{R:1}" /> </rule> </rules> </rewrite> </system.webServer> </configuration>
We write the first application
To demonstrate the possibility of implementing long-polling using Goliath and IIS, let's write a simple chat. It will consist of two parts: server (Ruby, Goliath) and client (JavaScript). To edit the code you need an editor or development environment. We used Aptana (
http://aptana.org ):

The server part is the app.rb file:
require 'rubygems' require 'goliath' require 'cgi' class Chat < Goliath::API use Goliath::Rack::Params
Client part, index.html:
<!DOCTYPE html> <html> <head> <title>Goliath + Helicon Zoo chat</title> <style type="text/css"> body { font-family: Sans-Serif; font-size: 13pt; padding: 0 6px; } h1 { font-family: "Trebuchet MS", Sans-Serif; font-size: 1.5em; color: #FF9933; } #messages { list-style: none; margin-top: 20px; } </style> </head> <body> <h1>Goliath + Helicon Zoo chat</h1> <form action="/send" method="post" id="send"> <label for="nickname">Nickname:</label> <input name="nickname" size="10" id="nickname" /> <label for="text">Message:</label> <input name="text" size="40" id="text" /> <input type="submit" value="Send" /> </form> <li id="messages"></li> </body> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6/jquery.min.js" type="text/javascript"></script> <script type="text/javascript"> // Submit function on_send( evt ) { evt.preventDefault(); var arr = $(this).serializeArray(); var message = { nickname : arr[ 0 ].value, text : arr[ 1 ].value, color: window.ClientColor }; $.post( '/send', message, function( data ) { $('#text').val( '' ).focus(); }, 'json' ); } // function long_polling( message ) { if ( message ) { var $li = $( '<li><b style="color: ' + message.color + ';">' + message.nickname + ':</b> <span>' + message.text + '</span>' ); $li.hide().appendTo('#messages').slideDown(); } // , $.ajax({ cache: false, type: 'GET', url: '/recv', success: long_polling }); } // $(document).ready(function(){ window.ClientColor = '#' + Math.floor( Math.random() * 16777215 ).toString( 16 ); $('form#send').submit( on_send ); long_polling(); $('#nickname').focus(); }); </script> </html>
Pay attention to the on_recv method. We get the current fiber and add it to the array of pending handlers. More precisely, we put the Ruby procedure there, in which the call method is called, passing control to the fiber. The variable req_fiber, although local, seems to be “locked” in the context of the procedure. Then we immediately stop the fiber. When a message arrives, all procedures will be sequentially called and deleted from the array.
Let's try to run what happened:

Performance tests
Test machine as a server - Core 2 Quad 2.4 Ghz, 8 Gb RAM, gigabit network. To generate the load, a more powerful computer and Apache Benchmark were used with the command “ab.exe -n 100000 -c 100 –k”. Operating Systems - Ubuntu 11.04 Server x64 and Windows Server 2008 R2. Any virtualok - honest iron.
Three tests were performed. In the first Goliath, the application should have simply displayed the current time in high resolution on the page. Time is needed to ensure that the answers do not come from the cache. In the second test, read from the MySQL database, in the third record in the database.
Ruby 1.9.3, Goliath 0.9.4 and MySQL 5.1.54 were used for the tests. In all configurations of IIS, Apache and Nginx HTTP proxying was used, because Goliath itself is an HTTP server.
Here are the results (the value on the graphs - requests per second):



And more detailed graphs of ab according to the first test:



findings
Goliath is an easy, simple and convenient framework. It is especially good at writing various APIs and asynchronous code. The solution has been tested many times in an industrial environment and shows good speed. And most importantly - it allows you to use the vast Ruby ecosystem when developing applications.