<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
 <title>asceth</title>
 <link href="http://.github.com/website/atom.xml" rel="self"/>
 <link href="http://.github.com/website/"/>
 <updated>2009-03-23T13:04:50-05:00</updated>
 <id>http://.github.com/website/</id>
 <author>
   <name>Dr Nic Williams</name>
   <email>williams@gmail.com</email>
 </author>

 
 <entry>
   <title>Building Panda3d 1.5.4 on Mac OSX</title>
   <link href="http://.github.com/website/2009/03/20/building-panda3d-1.5.4-on-mac-osx.html"/>
   <updated>2009-03-20T00:00:00-05:00</updated>
   <id>http://.github.com/website/2009/03/20/building-panda3d-1.5.4-on-mac-osx</id>
   <content type="html">&lt;p&gt;&lt;a href="http://www.panda3d.org"&gt;Panda3d&lt;/a&gt; is an open source, cross-platform game engine.  It is written in C++ but includes a first-class Python interface.  This means very, very few things actually require you to go down to C++ to use the all of the features of the engine.  Panda3d is distributed as packages for Windows and *nix based distro's along with the source code.  However compiling for Mac OSX isn't as straight forward as it sounds.  These are the steps I took to build Panda3d on my Macbook Pro (OSX 10.5.6).&lt;/p&gt;

&lt;p&gt;Most of this tutorial comes from &lt;a href="http://art.bitbop.net/2008/08/building-panda3d-on-mac-os-x/"&gt;http://art.bitbop.net/2008/08/building-panda3d-on-mac-os-x/&lt;/a&gt;.
&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Update 3/23/09: Added more instructions for sample model building and common shaders.&lt;/p&gt;

&lt;br /&gt;


&lt;h1&gt;Steps&lt;/h1&gt;

&lt;p&gt;1) Download Panda3d 1.5.4 and extract it
&lt;br /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ wget http://panda3d.org/download/panda3d-1.5.4/panda3d-1.5.4.tar.gz
$ tar xvzf panda3d-1.5.4.tar.gz
$ cd panda3d-1.5.4
&lt;/code&gt;&lt;/pre&gt;

&lt;br /&gt;


&lt;p&gt;2) Install dependencies with &lt;a href="http://www.macports.org/"&gt;MacPorts&lt;/a&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ sudo port install jpeg tiff libpng
$ sudo port install libtar ode fftw
$ sudo port install freetype
&lt;/code&gt;&lt;/pre&gt;

&lt;br /&gt;


&lt;p&gt;3) Install &lt;a href="http://developer.nvidia.com/object/cg_toolkit.html"&gt;NVIDIA Cg Tookit&lt;/a&gt;&lt;/p&gt;

&lt;br /&gt;


&lt;p&gt;4) Build ppremake
&lt;br /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cd ppremake
$ aclocal
$ autoheader
$ automake --foreign -a
$ autoconf
$ ./configure
$ make
$ sudo make install
&lt;/code&gt;&lt;/pre&gt;

&lt;br /&gt;


&lt;p&gt;5) Configure your .bash_profile (open ~/.bash_profile in your favorite editor and add these lines)
&lt;br /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;export PATH=/usr/local/panda/bin:$PATH
export PYTHONPATH=/usr/local/panda/lib:/usr/local/panda/lib/direct:$PYTHONPATH
export DYLD_LIBRARY_PATH=/usr/local/panda/lib:$DYLD_LIBRARY_PATH
&lt;/code&gt;&lt;/pre&gt;

&lt;br /&gt;


&lt;p&gt;6) Set ownership of /usr/local/panda to yourself (optional otherwise some commands below need to be prefixed with sudo)
&lt;br /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ sudo chown -R user:group /usr/local/panda
&lt;/code&gt;&lt;/pre&gt;

&lt;br /&gt;


&lt;p&gt;7) Create the Config.pp file in /usr/local/panda
&lt;br /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;#define PYTHON_IPATH /usr/include/python2.5
#define PYTHON_LPATH /usr/lib/python2.5
#define JPEG_IPATH /opt/local/include
#define JPEG_LPATH /opt/local/lib
#define PNG_IPATH /usr/X11/include/libpng
#define PNG_LPATH /usr/X11/lib/
#define TIFF_IPATH /opt/local/include
#define TIFF_LPATH /opt/local/lib
#define TAR_IPATH /opt/local/include
#define TAR_LPATH /opt/local/lib
#define HAVE_FFTW 1
#define FFTW_IPATH /opt/local/include
#define FFTW_LPATH /opt/local/lib
#define FFTW_LIBS dfftw drfftw
#define CG_IPATH /Library/Frameworks/Cg.framework/Headers
#define CG_LPATH /Library/Frameworks/Cg.framework
#define CG_LIBS
#define CG_FRAMEWORK Cg
#define HAVE_CG 1
#define CGGL_LIBS
#define CGGL_FRAMEWORK Cg
#define HAVE_CGGL 1
#define ZLIB_IPATH /opt/local/include
#define ZLIB_LPATH /opt/local/lib
#define ODE_IPATH /opt/local/include
#define ODE_LPATH /opt/local/lib
&lt;/code&gt;&lt;/pre&gt;

&lt;br /&gt;


&lt;p&gt;8) Build dtool
&lt;br /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cd dtool
$ ppremake
$ make
$ make install
&lt;/code&gt;&lt;/pre&gt;

&lt;br /&gt;


&lt;p&gt;9) Build panda
&lt;br /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cd panda
$ ppremake
$ make
$ make install
&lt;/code&gt;&lt;/pre&gt;

&lt;br /&gt;


&lt;p&gt;10) Build direct
&lt;br /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cd direct
$ ppremake
$ make
$ make install
&lt;/code&gt;&lt;/pre&gt;

&lt;br /&gt;


&lt;p&gt;11) Build pandatool
&lt;br /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cd pandatool
$ ppremake
$ make
$ make install
&lt;/code&gt;&lt;/pre&gt;

&lt;br /&gt;


&lt;p&gt;12) Build dmodels
&lt;br /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cd dmodels
$ ppremake
$ make
$ make install
&lt;/code&gt;&lt;/pre&gt;

&lt;br /&gt;


&lt;p&gt;13) Copy sample models and filters (change /usr/local/panda to where you installed panda3d)
&lt;br /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cd models
$ cp -r ./* /usr/local/panda/models/
$ cd ../direct/src/filter
$ cp -r ./*sha /usr/local/panda/lib/direct/filter/
&lt;/code&gt;&lt;/pre&gt;

&lt;br /&gt;


&lt;p&gt;14) Run genPyCode
&lt;br /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ genPyCode
&lt;/code&gt;&lt;/pre&gt;

&lt;br /&gt;


&lt;p&gt;15) Create the Config.prc file in /usr/local/panda/etc (More details &lt;a href="http://panda3d.org/wiki/index.php/Configuring_Panda"&gt;here&lt;/a&gt;)
&lt;br /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;plugin-path /usr/local/panda/lib
default-model-extension .egg.pz
model-path .
model-path /usr/local/panda/models
&lt;/code&gt;&lt;/pre&gt;

&lt;br /&gt;


&lt;p&gt;15a) If you set your model extension to .egg.pz above then run these commands for panda3d's built in models&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cd /usr/local/panda
$ find . -type f -name *.egg | xargs pzip
$ cd /usr/local/panda/models/gui/
$ bam2egg dialog_box_gui.bam dialog_box_gui.egg
$ bam2egg radio_button_gui.bam radio_button_gui.egg
$ pzip *egg
$ cd ../misc
$ # run bam2egg filename outfilename.egg on all .bam files
$ pzip *egg
&lt;/code&gt;&lt;/pre&gt;

&lt;br /&gt;


&lt;p&gt;16) Done.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Ruby, AMQP and RabbitMQ for Data Processing Win.</title>
   <link href="http://.github.com/website/2009/02/13/ruby-amqp-rabbitmq-for-data-processing-win.html"/>
   <updated>2009-02-13T00:00:00-06:00</updated>
   <id>http://.github.com/website/2009/02/13/ruby-amqp-rabbitmq-for-data-processing-win</id>
   <content type="html">&lt;h3&gt;Overview&lt;/h3&gt;

&lt;p&gt;At work we needed to be able to process a huge amount of data.  13 individual areas and each area took about 2 days with only 18 months backlog.. and we were getting the data nightly.  Once we process that backlog the amount of data is drastically reduced but the new system is constantly changing which could mean having to re-process that backlog to incorporate new changes or fixes.  We needed something faster but our current process was sequential (process users, process clients (500 each time until done), etc for a couple more sections).&lt;/p&gt;

&lt;p&gt;The order of processing the sections cannot be changed but we could process individual records in each section in parallel.  We looked at a couple of options including Haskell and Erlang but didn't have the time to spend on those rewrites.  I decided to try a map/reduce approach that could still use our existing Rails models reducing the time spent on trying something new.  I settled on RabbitMQ to be a broker where I could publish messages and have a pool of workers process those messages (yes I'm partial to Erlang).&lt;/p&gt;

&lt;p&gt;We now have a publisher class, a processer class and a reporter class.  The publisher takes each section and publishes the legacy record to a message queue (RabbitMQ) and a pool of processers will listen for messages, run the conversion then save the result to a local database.  The reporter simply lets me see how many pending messages there are and how many consumers (workers) are listening to the queue.&lt;/p&gt;

&lt;p&gt;As a non-formal benchmark, we are now able to run through all 13 areas with a 12 month backlog in under 24 hours which would have taken a little over 2 weeks with the old process.&lt;/p&gt;

&lt;p&gt;The code that follows is the 'framework' I used for the new conversion process.&lt;/p&gt;

&lt;br /&gt;


&lt;h4&gt;Contents&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="#setting_up"&gt;Setting Up&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="#rabbitmq"&gt;RabbitMQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#ruby_amqp"&gt;Ruby AMQP&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#helper_class"&gt;Helper Class&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#publisher_class"&gt;Publisher Class&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#processer_class"&gt;Processer Class&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#reporter_class"&gt;Reporter Class&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#resources"&gt;Resources&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;br /&gt;


&lt;h3&gt;Setting Up&lt;/h3&gt;

&lt;p&gt;The first thing to do is setup RabbitMQ and install Erlang if needed.  For Erlang its a simple configure, make and make install steps to get it on your system (Mac or Linux) or the Windows binary.  &lt;a href="http://erlang.org/download.html"&gt;http://erlang.org/download.html&lt;/a&gt;  (Recommend the R12B-5 version)&lt;/p&gt;

&lt;h4&gt;RabbitMQ&lt;/h4&gt;

&lt;p&gt;From source:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cd your_preferred_work_directory
$ hg clone http://hg.rabbitmq.com/rabbitmq-codegen
$ hg clone http://hg.rabbitmq.com/rabbitmq-server
$ cd rabbitmq-server
$ make run
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This gives you a nice quick rabbitmq server up and running.  If you wish to go the more formal route you can get a tarball/package and follow the &lt;a href="http://www.rabbitmq.com/install.html"&gt;appropriate instructions&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;Ruby AMQP&lt;/h4&gt;

&lt;p&gt;You'll need the ruby amqp library.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ sudo gem sources -a http://gems.github.com
$ sudo gem install tmm1-amqp
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;With RabbitMQ up and running you can do some of the examples from the tmm1-amqp gem to make sure everything is operating smoothly.&lt;/p&gt;

&lt;br /&gt;


&lt;h3&gt;Helper Class&lt;/h3&gt;

&lt;p&gt;Everyone loves helpers.&lt;/p&gt;

&lt;div class="syntax"&gt;
&lt;pre&gt;
require &lt;span style="color: #60A63B;"&gt;'rubygems'&lt;/span&gt;
require &lt;span style="color: #60A63B;"&gt;'mq'&lt;/span&gt;
require &lt;span style="color: #60A63B;"&gt;'benchmark'&lt;/span&gt;

&lt;span style="color: #E07C18; font-weight: bold;"&gt;class&lt;/span&gt; &lt;span style="color: #D23838;"&gt;Integer&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;paged_collect&lt;/span&gt;(mod)
    acc = &lt;span style="color: #D23838;"&gt;Hash&lt;/span&gt;.new
    &lt;span style="color: #CD5C5C;"&gt;self&lt;/span&gt;.times &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt; |num|
      index = num % mod
      acc[index] = &lt;span style="color: #D23838;"&gt;Array&lt;/span&gt;.new &lt;span style="color: #E07C18; font-weight: bold;"&gt;unless&lt;/span&gt; acc[index]
      acc[index] = acc[index] &amp;lt;&amp;lt; num
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
    acc
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

&lt;span style="color: #E07C18; font-weight: bold;"&gt;module&lt;/span&gt; &lt;span style="color: #D23838;"&gt;MQHelper&lt;/span&gt;

  &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;log&lt;/span&gt; message
    puts &lt;span style="color: #60A63B;"&gt;"&lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{MQ.id}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt;: &lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{message}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt;"&lt;/span&gt;
    &lt;span style="color: #CD5C5C;"&gt;$stdout&lt;/span&gt;.flush
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

  &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;logp&lt;/span&gt; *args
    print args
    &lt;span style="color: #CD5C5C;"&gt;$stdout&lt;/span&gt;.flush
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

  &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;graceful_death&lt;/span&gt;
    &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.stop{ &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.stop }
    exit(0)
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

  protected

  &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;load_rails_environment&lt;/span&gt;
    mark = &lt;span style="color: #D23838;"&gt;Benchmark&lt;/span&gt;.realtime &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;
      require &lt;span style="color: #60A63B;"&gt;'config/environment'&lt;/span&gt;
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
    log &lt;span style="color: #60A63B;"&gt;"loaded rails environment... &lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{mark}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt; seconds"&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

  &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;infrastructure&lt;/span&gt;(&amp;amp;block)
    mark = &lt;span style="color: #D23838;"&gt;Benchmark&lt;/span&gt;.realtime &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;
      block.call
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
    log &lt;span style="color: #60A63B;"&gt;"loading required infrastructure... &lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{mark}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt; seconds"&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

  &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;calc_pages&lt;/span&gt;(total, offset)
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;if&lt;/span&gt; total &amp;lt; offset
      1
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;else&lt;/span&gt;
      (total / offset).to_i
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

  &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;calc_modulus&lt;/span&gt;(total_pages)
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;if&lt;/span&gt; total_pages &amp;lt; 100
      3 + (total_pages/10).to_i
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;else&lt;/span&gt;
      13 + (total_pages/10).to_i
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

  &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;serialize&lt;/span&gt; data
    &lt;span style="color: #D23838;"&gt;Marshal&lt;/span&gt;.dump(data)
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

  &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;unserialize&lt;/span&gt; data
    autoload_missing_constants &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;
      &lt;span style="color: #D23838;"&gt;Marshal&lt;/span&gt;.load data
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

  &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;autoload_missing_constants&lt;/span&gt;
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;yield&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;rescue&lt;/span&gt; &lt;span style="color: #D23838;"&gt;ArgumentError&lt;/span&gt; =&amp;gt; error
    lazy_load ||= &lt;span style="color: #D23838;"&gt;Hash&lt;/span&gt;.new {|hash, hash_key| hash[hash_key] = &lt;span style="color: #CD5C5C;"&gt;true&lt;/span&gt;; &lt;span style="color: #CD5C5C;"&gt;false&lt;/span&gt;}
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;if&lt;/span&gt; error.to_s[/undefined &lt;span style="color: #E07C18; font-weight: bold;"&gt;class&lt;/span&gt;|referred/] &amp;amp;&amp;amp; !lazy_load[error.to_s.split.last.constantize]
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;retry&lt;/span&gt;
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;else&lt;/span&gt;
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;raise&lt;/span&gt; error
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

&lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Integer#paged_collect&lt;/strong&gt; takes an integer(x) and loops from 0 to x (y) collecting sets of pages based on the modulus of y with the parameter.  The purpose here was to give you an array of arrays.  It's use will be made more clear in the Publisher class.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;#load_rails_environment&lt;/strong&gt; and &lt;strong&gt;#infrastructure&lt;/strong&gt; load required files.  Infrastructure is a helper to benchmark how long it took to load those needed files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;#calc_pages&lt;/strong&gt; takes a total and an offset and calculates how many pages are needed to cover the total amount.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;#calc_modulus&lt;/strong&gt; is a helper method for #paged_collect to give a decent sized integer for the modulus operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;#serialize&lt;/strong&gt; and &lt;strong&gt;#unserialize&lt;/strong&gt; are helper methods to load and dump data.  &lt;strong&gt;#autoload_missing_constants&lt;/strong&gt; allows a Marshal.load to find the constants it needs and not fail.&lt;/p&gt;

&lt;br /&gt;


&lt;h3&gt;Publisher Class&lt;/h3&gt;

&lt;p&gt;A few things before the code..&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.start &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;
&lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;sets up the amqp gem and eventmachine for publishing and consuming messages.  Your code will be wrapped inside this and the block will not exit until you call &lt;em&gt;EM.stop_event_loop&lt;/em&gt;  or:&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.stop &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;
  &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.stop
&lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;The above is a graceful way of telling the amqp gem and eventmachine to finish any io work left (publishing etc) and then stop.  We'll use the two above code blocks a good bit to do 'burst' publishing.  More details after the code:&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color: #BB53C5;"&gt;#&lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;!/usr/bin/ruby
&lt;/span&gt;
&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;Core
&lt;/span&gt;require &lt;span style="color: #60A63B;"&gt;'date'&lt;/span&gt;

&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;Gems
&lt;/span&gt;require &lt;span style="color: #60A63B;"&gt;'rubygems'&lt;/span&gt;
require &lt;span style="color: #60A63B;"&gt;'mq'&lt;/span&gt;

&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;Helper
&lt;/span&gt;require &lt;span style="color: #60A63B;"&gt;'import.mq.helper.rb'&lt;/span&gt;

&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;Shutdown AMQP gracefully so messages are published
&lt;/span&gt;&lt;span style="color: #D23838;"&gt;Signal&lt;/span&gt;.trap(&lt;span style="color: #60A63B;"&gt;'INT'&lt;/span&gt;) { &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.stop{ &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.stop } }
&lt;span style="color: #D23838;"&gt;Signal&lt;/span&gt;.trap(&lt;span style="color: #60A63B;"&gt;'TERM'&lt;/span&gt;){ &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.stop{ &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.stop } }

&lt;span style="color: #E07C18; font-weight: bold;"&gt;class&lt;/span&gt; &lt;span style="color: #D23838;"&gt;Publisher&lt;/span&gt;

  attr_reader &lt;span style="color: #D23838; font-weight: bold;"&gt;:options&lt;/span&gt;

  include &lt;span style="color: #D23838;"&gt;MQHelper&lt;/span&gt;

  &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;Need rails for our models
&lt;/span&gt;  &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;initialize&lt;/span&gt; env
    load_rails_environment
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

  &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;entry point - ` publish.rb "users" ` will publish the users
&lt;/span&gt;  &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;publish&lt;/span&gt;
    section = &lt;span style="color: #D23838;"&gt;ARGV&lt;/span&gt;[0]

    &lt;span style="color: #E07C18; font-weight: bold;"&gt;if&lt;/span&gt; section.nil?
      log &lt;span style="color: #60A63B;"&gt;"You must supply a section to publish (users, clients, etc..)."&lt;/span&gt;
      graceful_death
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;else&lt;/span&gt;
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;case&lt;/span&gt; section
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;when&lt;/span&gt; &lt;span style="color: #60A63B;"&gt;"users"&lt;/span&gt;
        publish_users
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;when&lt;/span&gt; &lt;span style="color: #60A63B;"&gt;"clients"&lt;/span&gt;
        publish_clients
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

  protected

  &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;publish_users&lt;/span&gt;
    total = &lt;span style="color: #D23838;"&gt;Legacy&lt;/span&gt;::&lt;span style="color: #D23838;"&gt;User&lt;/span&gt;.count
    log &lt;span style="color: #60A63B;"&gt;"publishing &lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{total}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt; users..."&lt;/span&gt;

    &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;We know the user count is small so we don't do
&lt;/span&gt;    &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;any pagination and instead load it all at once
&lt;/span&gt;    &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;and publish
&lt;/span&gt;    lusers = &lt;span style="color: #D23838;"&gt;Legacy&lt;/span&gt;::&lt;span style="color: #D23838;"&gt;User&lt;/span&gt;.all

    &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;start amqp
&lt;/span&gt;    &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.start &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;
      &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;create/grab our exchange 'conversion' which is a topic exchange
&lt;/span&gt;      topic = &lt;span style="color: #D23838;"&gt;MQ&lt;/span&gt;.new.topic(&lt;span style="color: #60A63B;"&gt;'conversion'&lt;/span&gt;)

      &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;for each legacy user, publish its dump with the routing key 'import.users'
&lt;/span&gt;      lusers.each &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt; |user|
        logp &lt;span style="color: #60A63B;"&gt;"publishing &lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{user.login}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt;..."&lt;/span&gt;
        topic.publish(serialize(user), &lt;span style="color: #D23838; font-weight: bold;"&gt;:key&lt;/span&gt; =&amp;gt; &lt;span style="color: #60A63B;"&gt;'import.users'&lt;/span&gt;)
        log &lt;span style="color: #60A63B;"&gt;"OK!"&lt;/span&gt;
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

      &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;We're done so tell amqp and eventmachine to finish publishing and then stop
&lt;/span&gt;      &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.stop &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;
        &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.stop
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end publish users
&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;publish_clients&lt;/span&gt;
    &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;calculate the number of times (pages) we need to loop with a 100 row offset/limit
&lt;/span&gt;    total_count = &lt;span style="color: #D23838;"&gt;Legacy&lt;/span&gt;::&lt;span style="color: #D23838;"&gt;Client&lt;/span&gt;.count
    number_of_pages = calc_pages(total_count, 100)
    log &lt;span style="color: #60A63B;"&gt;"publishing &lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{total_count}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt; clients in &lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{number_of_pages}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt; pages..."&lt;/span&gt;

    &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;Publishes a set of pages, allows eventmachine/amqp to send them
&lt;/span&gt;    &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;then restarts the process with another set of pages.
&lt;/span&gt;    modulus = calc_modulus(number_of_pages)
    number_of_pages.paged_collect(modulus).each_value &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt; |pages|
      &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.start &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;
        pages.each &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt; |page|
          lclients = &lt;span style="color: #D23838;"&gt;Legacy&lt;/span&gt;::&lt;span style="color: #D23838;"&gt;Client&lt;/span&gt;.all(&lt;span style="color: #D23838; font-weight: bold;"&gt;:limit&lt;/span&gt; =&amp;gt; 100, &lt;span style="color: #D23838; font-weight: bold;"&gt;:offset&lt;/span&gt; =&amp;gt; page*100)

          topic = &lt;span style="color: #D23838;"&gt;MQ&lt;/span&gt;.new.topic(&lt;span style="color: #60A63B;"&gt;'conversion'&lt;/span&gt;)
          lclients.map &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt; |lclient|
            topic.publish(serialize(lclient), &lt;span style="color: #D23838; font-weight: bold;"&gt;:key&lt;/span&gt; =&amp;gt; &lt;span style="color: #60A63B;"&gt;'import.clients'&lt;/span&gt;)
          &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
          log &lt;span style="color: #60A63B;"&gt;"published page &lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{page}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt; of &lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{number_of_pages}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt; for clients..."&lt;/span&gt;
        &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
        &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.stop &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;
          &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.stop
        &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end publish clients
&lt;/span&gt;
&lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

&lt;span style="color: #D23838;"&gt;Publisher&lt;/span&gt;.new(&lt;span style="color: #D23838;"&gt;ENV&lt;/span&gt;).publish
&lt;/pre&gt;


&lt;p&gt;You might want to read through the code comments for the &lt;strong&gt;#publish_users&lt;/strong&gt; to get a feel for how a simple legacy publish works.  &lt;strong&gt;#publish_clients&lt;/strong&gt; is a bit more complex in that we do 'burst' publishing.  Burst publishing in this context is publishing a set amount of messages, waiting for that to finish, then publishing the next set.  The reason why this is important is that if you are in a tight loop going through 100,000 records publishing them all then your workers won't see a single message until you're done with the 100,000 record loop.  By doing the 'burst' publishing we can make sure 500 messages are sent, then do another 500, wait for those to be sent then continue on so we know our workers have work to do while we churn through 100,000 records.&lt;/p&gt;

&lt;p&gt;So let's take a look at &lt;strong&gt;#publish_clients&lt;/strong&gt; specifically the 'burst' part.&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;Publishes a set of pages, allows eventmachine/amqp to send them
&lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;then restarts the process with another set of pages.
&lt;/span&gt;modulus = calc_modulus(number_of_pages)
number_of_pages.paged_collect(modulus).each_value &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt; |pages|
  &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.start &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;
    pages.each &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt; |page|
      lclients = &lt;span style="color: #D23838;"&gt;Legacy&lt;/span&gt;::&lt;span style="color: #D23838;"&gt;Client&lt;/span&gt;.all(&lt;span style="color: #D23838; font-weight: bold;"&gt;:limit&lt;/span&gt; =&amp;gt; 100, &lt;span style="color: #D23838; font-weight: bold;"&gt;:offset&lt;/span&gt; =&amp;gt; page*100)

      topic = &lt;span style="color: #D23838;"&gt;MQ&lt;/span&gt;.new.topic(&lt;span style="color: #60A63B;"&gt;'conversion'&lt;/span&gt;)
      lclients.map &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt; |lclient|
        topic.publish(serialize(lclient), &lt;span style="color: #D23838; font-weight: bold;"&gt;:key&lt;/span&gt; =&amp;gt; &lt;span style="color: #60A63B;"&gt;'import.clients'&lt;/span&gt;)
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
      log &lt;span style="color: #60A63B;"&gt;"published page &lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{page}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt; of &lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{number_of_pages}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt; for clients..."&lt;/span&gt;
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
    &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.stop &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;
      &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.stop
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

&lt;/pre&gt;


&lt;p&gt;The first two lines are constructing our sets of pages.  If we had 10 pages our sets of pages would look like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{0 =&amp;gt; [0, 4, 8], 1 =&amp;gt; [1, 5, 9], 2 =&amp;gt; [2, 6], 3 =&amp;gt; [3, 7]}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This means the first burst will publish pages 0, 4 and 8 and the next burst will publish 1, 5, and 9 and so on.  Now we get into our 'AMQP.start' block.  For each of the pages we grab the legacy records and then publish each one to our topic exchange 'conversion' with a routing key of 'import.clients'.  The routing key is important so your workers know what type of data is coming to them.  It also lets you have two sets of workers listening on different routing keys but on the same exchange.&lt;/p&gt;

&lt;p&gt;After publishing each page we gracefully stop AMQP and eventmachine which allows our messages to be published so our workers can start processing.  Then we move on to the next set of pages.&lt;/p&gt;

&lt;br /&gt;


&lt;h3&gt;Processer Class&lt;/h3&gt;

&lt;p&gt;The Processer class is a bit more complex in that we use one script invocation to start a pool of workers.  I'll list the entire code then break it down piece by piece.&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color: #BB53C5;"&gt;#&lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;!/usr/bin/ruby
&lt;/span&gt;
&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;Core
&lt;/span&gt;require &lt;span style="color: #60A63B;"&gt;'date'&lt;/span&gt;

&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;Gems
&lt;/span&gt;require &lt;span style="color: #60A63B;"&gt;'rubygems'&lt;/span&gt;
require &lt;span style="color: #60A63B;"&gt;'mq'&lt;/span&gt;

&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;Helper
&lt;/span&gt;require &lt;span style="color: #60A63B;"&gt;'import.mq.helper.rb'&lt;/span&gt;

&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;For ack to work appropriatly you must shutdown AMQP gracefully,
&lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;otherwise all items in your queue will be returned
&lt;/span&gt;&lt;span style="color: #D23838;"&gt;Signal&lt;/span&gt;.trap(&lt;span style="color: #60A63B;"&gt;'INT'&lt;/span&gt;) {
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;unless&lt;/span&gt; &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.forks.empty?
    &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.forks.each &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt; |pid|
      &lt;span style="color: #D23838;"&gt;Process&lt;/span&gt;.kill(&lt;span style="color: #60A63B;"&gt;'KILL'&lt;/span&gt;, pid)
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
  &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.stop{ &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.stop }
  exit(0)
}
&lt;span style="color: #D23838;"&gt;Signal&lt;/span&gt;.trap(&lt;span style="color: #60A63B;"&gt;'TERM'&lt;/span&gt;) {
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;unless&lt;/span&gt; &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.forks.empty?
    &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.forks.each &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt; |pid|
      &lt;span style="color: #D23838;"&gt;Process&lt;/span&gt;.kill(&lt;span style="color: #60A63B;"&gt;'KILL'&lt;/span&gt;, pid)
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
  &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.stop{ &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.stop }
  exit(0)
}

&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;spawn workers
&lt;/span&gt;workers = &lt;span style="color: #D23838;"&gt;ARGV&lt;/span&gt;[1] ? (&lt;span style="color: #D23838;"&gt;Integer&lt;/span&gt;(&lt;span style="color: #D23838;"&gt;ARGV&lt;/span&gt;[1]) &lt;span style="color: #E07C18; font-weight: bold;"&gt;rescue&lt;/span&gt; 1) : 1
puts &lt;span style="color: #60A63B;"&gt;"workers: &lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{workers}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt;"&lt;/span&gt;
&lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.fork(workers) &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;
  &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.start &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;

    &lt;span style="color: #E07C18; font-weight: bold;"&gt;class&lt;/span&gt; &lt;span style="color: #D23838;"&gt;Processer&lt;/span&gt;

      attr_reader &lt;span style="color: #D23838; font-weight: bold;"&gt;:options&lt;/span&gt;

      include &lt;span style="color: #D23838;"&gt;MQHelper&lt;/span&gt;

      &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;initialize&lt;/span&gt; env
        &lt;span style="color: #CD5C5C;"&gt;@options&lt;/span&gt; = env

        load_rails_environment
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

      &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;entry point - `process.rb "users"`
&lt;/span&gt;      &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;process&lt;/span&gt;
        section = &lt;span style="color: #D23838;"&gt;ARGV&lt;/span&gt;[0]

        &lt;span style="color: #E07C18; font-weight: bold;"&gt;if&lt;/span&gt; section.nil?
          log &lt;span style="color: #60A63B;"&gt;"You must supply a section to process (users, clients, etc..)"&lt;/span&gt;
          graceful_death
        &lt;span style="color: #E07C18; font-weight: bold;"&gt;else&lt;/span&gt;
          &lt;span style="color: #E07C18; font-weight: bold;"&gt;case&lt;/span&gt; section
          &lt;span style="color: #E07C18; font-weight: bold;"&gt;when&lt;/span&gt; &lt;span style="color: #60A63B;"&gt;"users"&lt;/span&gt;
            process_user
          &lt;span style="color: #E07C18; font-weight: bold;"&gt;when&lt;/span&gt; &lt;span style="color: #60A63B;"&gt;"clients"&lt;/span&gt;
            infrastructure &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;
              require &lt;span style="color: #60A63B;"&gt;'client_loader'&lt;/span&gt;
            &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
            process_client
          &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
        &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end process
&lt;/span&gt;
      protected

      &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;update legacy users to new users model
&lt;/span&gt;      &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;process_user&lt;/span&gt;
        mq = &lt;span style="color: #D23838;"&gt;MQ&lt;/span&gt;.new
        &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;open a queue (name can be anything)
&lt;/span&gt;        &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;and then bind it to the topic exchange 'conversion'
&lt;/span&gt;        &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;and routing key 'import.users'.
&lt;/span&gt;        queue = mq.queue(&lt;span style="color: #60A63B;"&gt;'cltc.import.users'&lt;/span&gt;).bind(mq.topic(&lt;span style="color: #60A63B;"&gt;'conversion'&lt;/span&gt;), &lt;span style="color: #D23838; font-weight: bold;"&gt;:key&lt;/span&gt; =&amp;gt; &lt;span style="color: #60A63B;"&gt;'import.users'&lt;/span&gt;)

        run_process(queue) &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt; |user|
          new_user = &lt;span style="color: #D23838;"&gt;User&lt;/span&gt;.find_by_login(user.login) || &lt;span style="color: #D23838;"&gt;User&lt;/span&gt;.new
          new_user.login = user.login
          new_user.password = new_user.password_confirmation = user.password

          new_user.save &lt;span style="color: #CD5C5C;"&gt;false&lt;/span&gt;
        &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end process user
&lt;/span&gt;
      &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;update legacy clients to new clients model
&lt;/span&gt;      &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;process_client&lt;/span&gt;
        mq = &lt;span style="color: #D23838;"&gt;MQ&lt;/span&gt;.new
        queue = mq.queue(&lt;span style="color: #60A63B;"&gt;'cltc.import.clients'&lt;/span&gt;).bind(mq.topic(&lt;span style="color: #60A63B;"&gt;'cltc'&lt;/span&gt;), &lt;span style="color: #D23838; font-weight: bold;"&gt;:key&lt;/span&gt; =&amp;gt; &lt;span style="color: #60A63B;"&gt;'import.clients'&lt;/span&gt;)

        run_process(queue) &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt; |client|
          &lt;span style="color: #D23838;"&gt;ClientLoader&lt;/span&gt;.new client
        &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end process client
&lt;/span&gt;
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;run_process&lt;/span&gt;(queue, &amp;amp;block)
        queue.subscribe(&lt;span style="color: #D23838; font-weight: bold;"&gt;:ack&lt;/span&gt; =&amp;gt; &lt;span style="color: #CD5C5C;"&gt;true&lt;/span&gt;) { |headers, payload|
          data = unserialize(payload)
          block.call(data)
          headers.ack
        }
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end run process
&lt;/span&gt;
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end Processer
&lt;/span&gt;
    &lt;span style="color: #D23838;"&gt;Processer&lt;/span&gt;.new(&lt;span style="color: #D23838;"&gt;ENV&lt;/span&gt;).process
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end AMQP.start
&lt;/span&gt;&lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end EM.fork
&lt;/span&gt;
&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;wait on forks
&lt;/span&gt;&lt;span style="color: #E07C18; font-weight: bold;"&gt;while&lt;/span&gt; !&lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.forks.empty?
  sleep(5)
&lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Let's first look at our Signal traps for &lt;strong&gt;INT&lt;/strong&gt; and &lt;strong&gt;TERM&lt;/strong&gt;.  I'll only list one here as they have the same code for each trap.&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color: #D23838;"&gt;Signal&lt;/span&gt;.trap(&lt;span style="color: #60A63B;"&gt;'INT'&lt;/span&gt;) {
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;unless&lt;/span&gt; &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.forks.empty?
    &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.forks.each &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt; |pid|
      &lt;span style="color: #D23838;"&gt;Process&lt;/span&gt;.kill(&lt;span style="color: #60A63B;"&gt;'KILL'&lt;/span&gt;, pid)
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
  &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.stop{ &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.stop }
  exit(0)
}
&lt;/pre&gt;


&lt;p&gt;Later on you'll see that we use eventmachine's fork method to create our pool of workers.  When we send the 'master' process an &lt;strong&gt;INT&lt;/strong&gt; or &lt;strong&gt;TERM&lt;/strong&gt; we need to kill all of our forks so we don't leave any orphans.  Again we see the &lt;strong&gt;AMQP#stop&lt;/strong&gt; being called to let things finish.  Our fork code is pretty simple, since we wrap all of our main code inside &lt;strong&gt;EM#fork&lt;/strong&gt;.&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;spawn workers
&lt;/span&gt;workers = &lt;span style="color: #D23838;"&gt;ARGV&lt;/span&gt;[1] ? (&lt;span style="color: #D23838;"&gt;Integer&lt;/span&gt;(&lt;span style="color: #D23838;"&gt;ARGV&lt;/span&gt;[1]) &lt;span style="color: #E07C18; font-weight: bold;"&gt;rescue&lt;/span&gt; 1) : 1
puts &lt;span style="color: #60A63B;"&gt;"workers: &lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{workers}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt;"&lt;/span&gt;
&lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.fork(workers) &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;
  &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.start &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;
    &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;main code here
&lt;/span&gt;  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Here we grab how many workers from the command line so &lt;em&gt;process.rb "users" 16&lt;/em&gt; would start 16 workers on the users section.  We then tell &lt;strong&gt;EM#fork&lt;/strong&gt; how many workers we want and then have &lt;strong&gt;AMQP#start&lt;/strong&gt; and our main code after that.  It should be noted that &lt;strong&gt;EM#fork&lt;/strong&gt; is an extension provided by the tmm1-amqp library.&lt;/p&gt;

&lt;p&gt;The next bit of code is pretty straight-forward so I won't re-paste it here.  In short declare our Processer class, load rails for our models and define our &lt;strong&gt;#process&lt;/strong&gt; method to take the command line argument and process the appropriate section.  Next up is our &lt;strong&gt;#process_user&lt;/strong&gt; method.&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;update legacy users to new users model
&lt;/span&gt;&lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;process_user&lt;/span&gt;
  mq = &lt;span style="color: #D23838;"&gt;MQ&lt;/span&gt;.new
  &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;open a queue (name can be anything)
&lt;/span&gt;  &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;and then bind it to the topic exchange 'conversion'
&lt;/span&gt;  &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;and routing key 'import.users'.
&lt;/span&gt;  queue = mq.queue(&lt;span style="color: #60A63B;"&gt;'cltc.import.users'&lt;/span&gt;).bind(mq.topic(&lt;span style="color: #60A63B;"&gt;'conversion'&lt;/span&gt;), &lt;span style="color: #D23838; font-weight: bold;"&gt;:key&lt;/span&gt; =&amp;gt; &lt;span style="color: #60A63B;"&gt;'import.users'&lt;/span&gt;)

  run_process(queue) &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt; |user|
    new_user = &lt;span style="color: #D23838;"&gt;User&lt;/span&gt;.find_by_login(user.login) || &lt;span style="color: #D23838;"&gt;User&lt;/span&gt;.new
    new_user.login = user.login
    new_user.password = new_user.password_confirmation = user.password

    new_user.save &lt;span style="color: #CD5C5C;"&gt;false&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end process user&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;A consumer/processer/listener of messages uses a queue on the message queue server to tell the server it wants to receive messages.  A queue alone isn't enough however so we must bind our queue to the topic exchange we were publishing to earlier which in this case was 'conversion'.  We also pass a routing key because topic exchanges send messages to queues(listeners) based on that key.  Other exchange types are direct (one to one) and fanout (unconditional one to many).&lt;/p&gt;

&lt;p&gt;After we bind to our queue we need to send a message to the server telling it we want to start consuming/processing messages.  The &lt;strong&gt;#run_process&lt;/strong&gt; method does just this and accepts a queue to use and a block which will be called for each message received.&lt;/p&gt;

&lt;p&gt;Let's jump to the &lt;strong&gt;#run_process&lt;/strong&gt; method.&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;run_process&lt;/span&gt;(queue, &amp;amp;block)
  queue.subscribe(&lt;span style="color: #D23838; font-weight: bold;"&gt;:ack&lt;/span&gt; =&amp;gt; &lt;span style="color: #CD5C5C;"&gt;true&lt;/span&gt;) { |headers, payload|
    data = unserialize(payload)
    block.call(data)
    headers.ack
  }
&lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end run process
&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;This method takes a queue and a block to call for processing each message received.  We subscribe to the queue which is asynchronous.  This means whenever the server has a message it will let us know and the block given to subscribe will be called.  The ack option we passed to subscribe is telling the server that we will let it know that we processed the message.  If the server doesn't receive an ack from us it should put the message back into the queue.  This is useful when a worker dies and you want that message to be 'recovered'.&lt;/p&gt;

&lt;p&gt;The payload is the message body.  Here we unserialize it then call our block with it for processing then use &lt;em&gt;headers.ack&lt;/em&gt; to tell the server we finished processing.  Next up is &lt;strong&gt;#process_clients&lt;/strong&gt;.&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;update legacy clients to new clients model
&lt;/span&gt;&lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;process_client&lt;/span&gt;
  mq = &lt;span style="color: #D23838;"&gt;MQ&lt;/span&gt;.new
  queue = mq.queue(&lt;span style="color: #60A63B;"&gt;'cltc.import.clients'&lt;/span&gt;).bind(mq.topic(&lt;span style="color: #60A63B;"&gt;'cltc'&lt;/span&gt;), &lt;span style="color: #D23838; font-weight: bold;"&gt;:key&lt;/span&gt; =&amp;gt; &lt;span style="color: #60A63B;"&gt;'import.clients'&lt;/span&gt;)

  run_process(queue) &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt; |client|
    &lt;span style="color: #D23838;"&gt;ClientLoader&lt;/span&gt;.new client
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
&lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end process client
&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Again we create our queue and then bind to our topic exchange with the appropriate routing key that matches how the messages were published in our Publisher class.  Then we again call &lt;strong&gt;#run_process&lt;/strong&gt; grab our legacy client in the block and use another class to do our conversion.  The block you pass to &lt;strong&gt;#run_process&lt;/strong&gt; can do whatever it needs to do to process/convert the data.  As long as it returns the message will be acked.&lt;/p&gt;

&lt;p&gt;If you look at the bottom of the code you see we let our master process live by doing a sleep loop as long as there are forks active.  This allows us to use our master process as a control process.  We can send an &lt;strong&gt;INT&lt;/strong&gt; or &lt;strong&gt;TERM&lt;/strong&gt; signal and have our master process kill the forks.&lt;/p&gt;

&lt;br /&gt;


&lt;h3&gt;Reporter Class&lt;/h3&gt;

&lt;p&gt;All this processing needs some sort of reporting.  You could put logging statements in the processing code but logging takes time which decreases performance and you want all the performance you can get if you are doing a lot of data processing.  Instead we use a separate class to report on how many messages are left to be processed or how many consumers/workers are waiting for work.&lt;/p&gt;

&lt;pre&gt;
&lt;span style="color: #BB53C5;"&gt;#&lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;!/usr/bin/ruby
&lt;/span&gt;
&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;Core
&lt;/span&gt;require &lt;span style="color: #60A63B;"&gt;'date'&lt;/span&gt;
require &lt;span style="color: #60A63B;"&gt;'benchmark'&lt;/span&gt;

&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;Gems
&lt;/span&gt;require &lt;span style="color: #60A63B;"&gt;'rubygems'&lt;/span&gt;
require &lt;span style="color: #60A63B;"&gt;'mq'&lt;/span&gt;

&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;Helper
&lt;/span&gt;require &lt;span style="color: #60A63B;"&gt;'import.mq.helper.rb'&lt;/span&gt;

&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;For ack to work appropriatly you must shutdown AMQP gracefully,
&lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;otherwise all items in your queue will be returned
&lt;/span&gt;&lt;span style="color: #D23838;"&gt;Signal&lt;/span&gt;.trap(&lt;span style="color: #60A63B;"&gt;'INT'&lt;/span&gt;) {
  &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.stop{ &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.stop }
  exit(0)
}
&lt;span style="color: #D23838;"&gt;Signal&lt;/span&gt;.trap(&lt;span style="color: #60A63B;"&gt;'TERM'&lt;/span&gt;) {
  &lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.stop{ &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.stop }
  exit(0)
}

&lt;span style="color: #D23838;"&gt;AMQP&lt;/span&gt;.start &lt;span style="color: #E07C18; font-weight: bold;"&gt;do&lt;/span&gt;

  &lt;span style="color: #E07C18; font-weight: bold;"&gt;class&lt;/span&gt; &lt;span style="color: #D23838;"&gt;Reporter&lt;/span&gt;

    attr_reader &lt;span style="color: #D23838; font-weight: bold;"&gt;:options&lt;/span&gt;
    &lt;span style="color: #CD5C5C;"&gt;@timer&lt;/span&gt; = &lt;span style="color: #CD5C5C;"&gt;nil&lt;/span&gt;

    include &lt;span style="color: #D23838;"&gt;MQHelper&lt;/span&gt;

    &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;initialize&lt;/span&gt; env
      &lt;span style="color: #CD5C5C;"&gt;@options&lt;/span&gt; = env

      load_rails_environment
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

    &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;report&lt;/span&gt;
      section = &lt;span style="color: #D23838;"&gt;ARGV&lt;/span&gt;[0]

      &lt;span style="color: #E07C18; font-weight: bold;"&gt;if&lt;/span&gt; section.nil?
        log &lt;span style="color: #60A63B;"&gt;"You must supply a section to report (users, clients, etc..)"&lt;/span&gt;
        graceful_death
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;else&lt;/span&gt;
        &lt;span style="color: #E07C18; font-weight: bold;"&gt;case&lt;/span&gt; section
        &lt;span style="color: #E07C18; font-weight: bold;"&gt;when&lt;/span&gt; &lt;span style="color: #60A63B;"&gt;"users"&lt;/span&gt;
          report_user
        &lt;span style="color: #E07C18; font-weight: bold;"&gt;when&lt;/span&gt; &lt;span style="color: #60A63B;"&gt;"clients"&lt;/span&gt;
          report_client
        &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
      &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end report
&lt;/span&gt;
    protected

    &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;report_user&lt;/span&gt;
      mq = &lt;span style="color: #D23838;"&gt;MQ&lt;/span&gt;.new
      queue = mq.queue(&lt;span style="color: #60A63B;"&gt;'cltc.import.users'&lt;/span&gt;).bind(mq.topic(&lt;span style="color: #60A63B;"&gt;'cltc'&lt;/span&gt;), &lt;span style="color: #D23838; font-weight: bold;"&gt;:key&lt;/span&gt; =&amp;gt; &lt;span style="color: #60A63B;"&gt;'import.users'&lt;/span&gt;)

      run_report(queue)
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end report user
&lt;/span&gt;
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;report_client&lt;/span&gt;
      mq = &lt;span style="color: #D23838;"&gt;MQ&lt;/span&gt;.new
      queue = mq.queue(&lt;span style="color: #60A63B;"&gt;'cltc.import.clients'&lt;/span&gt;).bind(mq.topic(&lt;span style="color: #60A63B;"&gt;'cltc'&lt;/span&gt;), &lt;span style="color: #D23838; font-weight: bold;"&gt;:key&lt;/span&gt; =&amp;gt; &lt;span style="color: #60A63B;"&gt;'import.clients'&lt;/span&gt;)

      run_report(queue)
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt;

    &lt;span style="color: #E07C18; font-weight: bold;"&gt;def&lt;/span&gt; &lt;span style="color: #E07C18;"&gt;run_report&lt;/span&gt;(queue)
      &lt;span style="color: #CD5C5C;"&gt;@timer&lt;/span&gt; = &lt;span style="color: #D23838;"&gt;EM&lt;/span&gt;.add_periodic_timer(5) {
        queue.status {|num_messages, num_consumers|
          log &lt;span style="color: #60A63B;"&gt;"[&lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{Time::now.strftime('%m/%d - %H:%M:%S')}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt;]  &lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{num_consumers}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt; consumers have &lt;/span&gt;&lt;span style="color: #CD5C5C;"&gt;#{num_messages}&lt;/span&gt;&lt;span style="color: #60A63B;"&gt; messages left to process"&lt;/span&gt;
        }
      }
    &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end run report
&lt;/span&gt;
  &lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end Reporter
&lt;/span&gt;
  &lt;span style="color: #D23838;"&gt;Reporter&lt;/span&gt;.new(&lt;span style="color: #D23838;"&gt;ENV&lt;/span&gt;).report
&lt;span style="color: #E07C18; font-weight: bold;"&gt;end&lt;/span&gt; &lt;span style="color: #BB53C5;"&gt;# &lt;/span&gt;&lt;span style="color: #BB53C5;"&gt;end AMQP.start
&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;This should look familiar as most of it is the same concept as the Publisher class.  The new code to look at is in the &lt;strong&gt;#run_report&lt;/strong&gt; method.  Here we add a periodic timer that will run the given block every 5 seconds.  We use eventmachine's handiness to do this.  The timed block will ask the status of the given queue and call the given block with the number of consumers (listeners) and the number of messages waiting to be processed.  Because each queue is bound by a routing key you will only see that routing key's consumers and pending messages.  Alternatively if you ran a report with a routing key of 'import.*' you would see the consumers and pending messages on 'import.users' and 'import.clients' (* matches one level).&lt;/p&gt;

&lt;p&gt;With our status we simply log how many consumers and messages we have.&lt;/p&gt;

&lt;h4&gt;How to tell you've finished publishing and processing&lt;/h4&gt;

&lt;p&gt;You know you've finished publishing when your publisher process exits.  But how do you know when you've finished processing all the way?  The key is to know how many consumers were listening on a routing key.  So let's say we had 16 consumers on 'import.clients'.  When our publisher(s) finish for that key and our reporter states we have 0 messages left with 16 consumers listening we know we're done.  When consumers are busy processing they will drop from the status report.  So if all 16 consumers were processing and we have 1,000 pending messages our reporter would tell us we had 0 consumers and 1,000 messages.&lt;/p&gt;

&lt;p&gt;There may be an easier to way to tell when we're done processing but this method definitely works and can be used to switch your pool or workers to a new section and begin work there.  Switching them over is an exercise for the reader.&lt;/p&gt;

&lt;br /&gt;


&lt;h3&gt;Resources&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://amqp.rubyforge.org"&gt;Ruby AQMP Gem doc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://eventmachine.rubyforge.org/EventMachine.html"&gt;EventMachine doc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.rabbitmq.com"&gt;RabbitMQ Site&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Feel free to leave comments/questions especially if I left something out or didn't explain a section very well.&lt;/p&gt;
</content>
 </entry>
 
 
</feed>