<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>Je hack donc je suis &#187; rails</title>
	<atom:link href="http://frederic.logier.org/category/rails/feed/" rel="self" type="application/rss+xml" />
	<link>http://frederic.logier.org</link>
	<description>pour une écologie numérique</description>
	<lastBuildDate>Wed, 21 Jul 2010 19:19:13 +0000</lastBuildDate>
	<language>fr</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<cloud domain='frederic.logier.org' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>http://1.gravatar.com/blavatar/1f06d42ecd282c7191503daa4ec60e0c?s=96&#038;d=http://s2.wp.com/i/buttonw-com.png</url>
		<title>Je hack donc je suis &#187; rails</title>
		<link>http://frederic.logier.org</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="http://frederic.logier.org/osd.xml" title="Je hack donc je suis" />
	<atom:link rel='hub' href='http://frederic.logier.org/?pushpress=hub'/>
		<item>
		<title>Nodecast : architecture d&#8217;une application web</title>
		<link>http://frederic.logier.org/2010/07/21/nodecast-architecture-dune-application-web/</link>
		<comments>http://frederic.logier.org/2010/07/21/nodecast-architecture-dune-application-web/#comments</comments>
		<pubDate>Wed, 21 Jul 2010 15:20:07 +0000</pubDate>
		<dc:creator>fredix</dc:creator>
				<category><![CDATA[rails]]></category>
		<category><![CDATA[web]]></category>
		<category><![CDATA[gearman]]></category>
		<category><![CDATA[mongodb]]></category>
		<category><![CDATA[nodecast]]></category>
		<category><![CDATA[sinatra]]></category>

		<guid isPermaLink="false">http://frederic.logier.org/?p=300</guid>
		<description><![CDATA[Certains le savent peut-être, je travaille depuis quelques mois sur mon projet personnel Nodecast. Pour résumer, ce projet a l&#8217;ambition de proposer un outil de monitoring simple à mettre en œuvre mais aussi un outil de recensement façon Linux counter. Il n&#8217;a cependant pas pour objectif de concurrencer un logiciel de type Nagios. Outre le [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=frederic.logier.org&amp;blog=3098224&amp;post=300&amp;subd=fredix&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Certains le savent peut-être, je travaille depuis quelques mois sur mon projet personnel <a href="http://www.nodecast.net" target="_blank">Nodecast</a>. Pour résumer, ce projet a l&#8217;ambition de proposer un outil de monitoring simple à mettre en œuvre mais aussi un outil de recensement façon <a href="http://counter.li.org/" target="_blank">Linux counter</a>. Il n&#8217;a cependant pas pour objectif de concurrencer un logiciel de type Nagios. Outre le challenge du développement de la partie web, il y a également celui du client desktop en Qt, mais qui fera peut-être l&#8217;objet d&#8217;un futur billet.</p>
<p><span id="more-300"></span></p>
<p>Lors d&#8217;une précédente expérience professionnelle en 2007 (<a href="http://www.af83.com/" target="_blank">AF83</a>) j&#8217;avais mis en oeuvre des techniques de <a href="http://dev.af83.com/ruby/de-la-repartition-de-charge-en-ruby-on-rails-12/2008/01/22" target="_blank">répartition de charge </a>via des <a href="http://dev.af83.com/ruby/de-la-repartition-de-charge-en-ruby-on-rails-22/2008/01/22" target="_blank">traitements asynchrones </a>pour le développement d&#8217;un microblog/chat web. Je cherchais depuis à réutiliser ces technologies, ce qui m&#8217;a poussé au développement de Nodecast.</p>
<p>Or si à l&#8217;époque le domaine des serveurs de message queues était encore balbutiant, il a nettement évolué depuis. En effet à l&#8217;époque après en avoir testé quelques uns, j&#8217;avais fini par me résoudre à utiliser le protocole XMPP via un serveur Jabber. Il faut bien avouer que la mise en œuvre a été plutôt ardu. XMPP est un bon protocole mais il est au final peu adapté pour un simple système de file d&#8217;attente, trop verbeux et la librairie Ruby XMPP4R faiblarde, à l&#8217;époque en tout cas.</p>
<p>Depuis les serveurs de file d&#8217;attente ont poussé comme des champignons, et certains sont même dérivés de serveur type clé/valeur.  J&#8217;ai pour ma part choisi <a href="http://gearman.org" target="_blank">Gearman</a> qui me parait une bonne technologie depuis sa réécriture en C, et c&#8217;est un bon compromis fonctionnalités / simplicité / performances. Avant d&#8217;aller plus loin je préfère présenter le schéma de l&#8217;architecture du site, ce qui rendra plus aisé les explications.</p>
<h2>Architecture</h2>
<p><a href="http://fredix.files.wordpress.com/2010/07/architecture.png"><img class="alignnone size-full wp-image-302" title="architecture" src="http://fredix.files.wordpress.com/2010/07/architecture.png?w=600&#038;h=400" alt="" width="600" height="400" /></a></p>
<p>La partie droite en orange représente le site web. Nginx transmet les requêtes HTTP vers un pool de services <a href="http://code.macournoyer.com/thin/" target="_blank">Thin</a> qui est un serveur web applicatif, il est en charge d&#8217;exécuter l&#8217;application en Ruby on Rails. Pour des raisons de performances et efficacité, j&#8217;ai choisi d&#8217;utiliser une base de données NoSQL, <a href="http://www.mongodb.org/" target="_blank">mongoDB</a>.</p>
<p>La partie intéressante est celle à gauche représentée par le nuage gris. En effet il représente les services en charge de l&#8217;API.</p>
<h2>API asynchrone</h2>
<p>L&#8217;API est ici une API REST. Le client en Qt effectue donc simplement des requêtes HTTP afin de communiquer avec le service web. Il fait des POST pour l&#8217;ajout de données, des PUT pour la modification et des GET pour la consultation.</p>
<p>Plus le client envoi des données sur un court délai, plus les statistiques seront détaillées. Le problème est que cela génère une multitude de connexions et donc de traitement. Et plus il y en a, plus la réponse au client sera longue, ce qui va dégrader la qualité de service. Bien entendu la solution est de rendre asynchrone les traitements afin que la réponse au client soit la plus rapide possible.</p>
<p>Cela est représenté par l&#8217;échange 1 et 2 entre le serveur Nginx et le serveur thin.</p>
<h3>Client &lt;-&gt; serveur</h3>
<p>Lors d&#8217;un POST, le client va demander la création d&#8217;une donnée au service. Ici il s&#8217;agit de la création d&#8217;une nouvelle machine (host) à monitorer. Le workflow est le suivant :</p>
<ol>
<li>client Qt POST les datas</li>
<li>serveur sinatra authentifie le client</li>
<li>serveur sinatra génère un identifiant unique (UUID)</li>
<li>serveur sinatra sérialize les datas, et transmet la charge à Gearman dans la file d&#8217;attente du dispatcher</li>
<li>serveur sinatra renvoi au client un XML contenant l&#8217;identifiant unique</li>
<li>le client stocke cet identifiant et l&#8217;utilisera pour tous les prochains échanges</li>
</ol>
<p>Le principe ici est que le serveur sinatra ne fasse que le strict minimum, afin de répondre le plus rapidement et d&#8217;être disponible pour une prochaine requête. Ainsi même sous une charge importante, les requêtes transmises par les clients Qt, seront empilé et mise en attente au chaud dans le serveur de file Gearman.</p>
<p>Pour une requête PUT le workflow est plus simple puisque l&#8217;étape 3 est supprimée. Le serveur sinatra renvoi dans tous les cas un XML contenant le status &laquo;&nbsp;proceed&nbsp;&raquo; afin de signaler au client que sa requête a été prise en compte.</p>
<p>L&#8217;intérêt de Sinatra est qu&#8217;il est très simple à mettre en oeuvre ce qui en fait à mon avis un candidat idéal pour servir une API. Le code du serveur tiens d&#8217;ailleurs dans un seul fichier :</p>
<pre>#!/usr/bin/env ruby
require 'rubygems'

require 'gearman'

require "bundler"
Bundler.setup
Bundler.require(:default)

RAILS_ENV="production"

#Gearman::Util.debug = true
SERVERS = ['localhost:4730']

@@logger = Logger.new('log/server.log', 'daily')
@@logger.debug("Created logger")

File.open(File.join('../config/database.mongo.yml'), 'r') do |f|
 @settings = YAML.load(f)[RAILS_ENV]
end

Mongoid.configure do |config|
 name = @settings["database"]
 host = @settings["host"]
 config.use_object_ids = @settings["use_object_ids"]
 @@logger.info "database : #{name}"
 @@logger.info "host : #{host}"
 config.master = Mongo::Connection.new.db(name)
 # config.slaves = [
 #                Mongo::Connection.new(host, @settings["slave_one"]["port"], :slave_ok =&gt; true).db(name)
 #               ]
end

require 'models_mongoid/user.rb'
require 'models_mongoid/profil.rb'
require 'models_mongoid/host.rb' 

set :logging, true

helpers do
 def protected!
 unless authorized?
 response['WWW-Authenticate'] = %(Basic realm="Nodecast HTTP Auth")
 throw(:halt, [401, "Not authorized\n"])
 end
 end

 def authorized?
 @auth ||=  Rack::Auth::Basic::Request.new(request.env)
 @current_user = User.where(:email =&gt; @auth.credentials.first, :authentication_token =&gt; @auth.credentials.last).first
 @auth.provided? &amp;&amp; @auth.basic? &amp;&amp; @current_user
 end
end

post '/hosts.xml' do
 protected!
 xml = Crack::XML.parse(request.body.read)
 @@logger.info("#{Time.now} : RECEIVE CREATE")

 uuid = UUIDTools::UUID.timestamp_create.to_s

 host = {
 :user =&gt; @current_user.email,
 :uuid =&gt; uuid,
 :timestamp =&gt; Time.now.utc,
 :datas =&gt; xml
 }

 payload('dispatcher_add', host)

 builder do |xml|
 xml.instruct!
 xml.host do
 xml.uuid host[:uuid]
 end
 end
end

put '/host/update/:id' do
 protected!
 xml = Crack::XML.parse(request.body.read)
 @@logger.info("#{Time.now} : RECEIVE UPDATE")

 host = {
 :user =&gt; @current_user.email,
 :uuid =&gt; params[:id],
 :timestamp =&gt; Time.now.utc,
 :datas =&gt; xml
 }

 payload('dispatcher_update', host)

 builder do |xml|
 xml.instruct!
 xml.host do
 xml.status "proceed"
 end
 end
end

private

def payload(worker, data)

 client = Gearman::Client.new(SERVERS)
 taskset = Gearman::TaskSet.new(client)

 #task = Gearman::Task.new('update', Marshal.dump(host), :background =&gt; true, :poll_status_interval =&gt; 1)
 task = Gearman::Task.new(worker, Marshal.dump(data))
 task.on_complete {|d| @@logger.info "complete : #{d}" }  

 task.on_warning {|w| @@logger.info "[client] warn: #{w}" }
 task.on_fail {|f| @@logger.info "[client] calculation failed : #{f}" }
 taskset.add_task(task)  
end</pre>
<p>On voit bien ici que &laquo;&nbsp;post &#8216;/hosts.xml&#8217; do&nbsp;&raquo; et &#8216;put &#8216;/host/update/:id&#8217; do&nbsp;&raquo; permettent de répondre très simplement aux requêtes POST et PUT des clients. Pour le reste on voit bien que le serveur ne fait que préparer un Hash avec l&#8217;XML transmis par le client, un timestamp, l&#8217;uuid du host et l&#8217;id de l&#8217;utilisateur. Ce Hast est ensuite sérialisé puis la charge est envoyée dans la file d&#8217;attente du dispatcher.</p>
<h3>Dispatcher &lt;-&gt; workers</h3>
<p>Comme on le voit dans le code les traitements sont envoyés dans la file d&#8217;attente sur 2 canaux : &laquo;&nbsp;dispatcher_update&nbsp;&raquo; et &laquo;&nbsp;dispatcher_add&nbsp;&raquo;. Il y a donc un processus Ruby qui attend des traitements sur ces canaux afin de les préparer puis les transmettre à chaque worker. J&#8217;avais tout d&#8217;abord un seul worker, mais il est beaucoup plus intéressant de le découper en plusieurs workers spécifiques à une tâche. En effet des utilisateurs peuvent décider de ne pas envoyer les informations relatives au CPU ou bien au réseau. De fait les worker auront la charge qui correspond au contenu des requêtes transmises.</p>
<p>Le dispatcher ne dépend pas des traitements à effectuer, son rôle est de découper le traitement en de multiples sous-traitement qu&#8217;il transmet dans le canal de chaque worker. Si les traitements sont lourds il peut malgré tout continuer à les répartir dans chacun des canaux, quelque soit la charge en cours.</p>
<p>Les traitements se font réellement dans chaque worker. Ils désérialisent les données reçues de leur file d&#8217;attente, puis les stocke dans mongodb. Voici un des workers dédié au traitement des load :</p>
<pre>#!/usr/bin/env ruby
require 'rubygems'

gem 'mongoid', '1.9.0'

require 'mongoid'
gem 'gearman-ruby', '3.0.1'
require 'gearman'
require 'uuidtools'

require 'logger'
require 'yaml'
require 'optparse'
require "pp"

options = {}

optparse = OptionParser.new do |opts|
 opts.on('-w', '--work WORK', 'path to the work directory') do |work|                                        
 options[:work] = work
 end
 opts.on('-m', '--mongo MONGO', 'path to the mongo file') do |mongo|                                        
 options[:mongo] = mongo
 end
 opts.on('-e', '--env ENV', 'rails environment') do |env|                                        
 options[:env] = env
 end
end

begin
 optparse.parse!
 mandatory = [:work, :mongo, :env]
 missing = mandatory.select{ |param| options[param].nil? }
 if not missing.empty?
 puts "Missing options: #{missing.join(', ')}"
 puts optparse
 exit
 end
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
 puts $!.to_s
 puts optparse
 exit
end

puts "Performing task with options: #{options.inspect}"            

#Gearman::Util.debug = true if options[:env] == "development"

servers = ['localhost:4730']
@@worker = Gearman::Worker.new(servers)

logger = Logger.new("#{options[:work]}/log/worker_stats_load.log", 'daily')
logger.debug("Created logger")

File.open(File.join("#{options[:mongo]}/database.mongo.yml"), 'r') do |f|
 @settings = YAML.load(f)[options[:env]]
end

Mongoid.configure do |config|
 name = @settings["database"]
 host = @settings["host"]
 config.use_object_ids = @settings["use_object_ids"]
 logger.info "database : #{name}"
 logger.info "host : #{host}"
 config.master = Mongo::Connection.new.db(name)
 # config.slaves = [
 #                Mongo::Connection.new(host, @settings["slave_one"]["port"], :slave_ok =&gt; true).db(name)
 #               ]
end

require "#{options[:work]}/models_mongoid/user.rb"
require "#{options[:work]}/models_mongoid/profil.rb"
require "#{options[:work]}/models_mongoid/host.rb"
require "#{options[:work]}/models_mongoid/osystem.rb" 

require "#{options[:work]}/models_mongoid/host_ram.rb"
require "#{options[:work]}/models_mongoid/host_cpu.rb"
require "#{options[:work]}/models_mongoid/host_network.rb"
require "#{options[:work]}/models_mongoid/host_last_comment.rb"
require "#{options[:work]}/models_mongoid/host_stats_uptime.rb"
require "#{options[:work]}/models_mongoid/host_stats_load.rb"
require "#{options[:work]}/models_mongoid/host_stats_network.rb"
require "#{options[:work]}/models_mongoid/host_stats_cpu.rb"
require "#{options[:work]}/models_mongoid/host_stats_memory.rb" 

require "#{options[:work]}/models_mongoid/load_statistic.rb" 

########## JOB UPDATE STAT ############
@@worker.add_ability('update_load') do |data,job|

 dump = Marshal.load(data)

 xml = dump[:push]
 host = Host.where(:uuid =&gt; dump[:uuid]).first

 host_load = {
 :created_at =&gt; dump[:timestamp],
 :updated_at =&gt; dump[:timestamp],
 :loadavg0 =&gt; xml[:loadavg0],
 :loadavg1 =&gt; xml[:loadavg1],
 :loadavg2 =&gt; xml[:loadavg2]
 }    

 begin

 ls = host.load_statistics.create(host_load)
 logger.info "LOAD stats created"

 loadavg0 = 0.0
 loadavg1 = 0.0
 loadavg2 = 0.0

 host.load_statistics.each do |stat|
 loadavg0 += stat.loadavg0
 loadavg1 += stat.loadavg1
 loadavg2 += stat.loadavg2
 end

 if !host.stats_load
 host.create_stats_load(
 :created_at =&gt; dump[:timestamp],
 :updated_at =&gt; dump[:timestamp],
 :number =&gt; 1,
 :loadavg0 =&gt; ls.loadavg0,
 :loadavg1 =&gt; ls.loadavg1,
 :loadavg2 =&gt; ls.loadavg2,
 :max0 =&gt; ls.loadavg0,
 :max1 =&gt; ls.loadavg1,
 :max2 =&gt; ls.loadavg2
 )
 else      
 host.stats_load.updated_at = dump[:timestamp]
 host.stats_load.number += 1

 host.stats_load.max0 = ls.loadavg0 if ls.loadavg0 &gt; host.stats_load.max0
 host.stats_load.max1 = ls.loadavg1 if ls.loadavg1 &gt; host.stats_load.max1
 host.stats_load.max2 = ls.loadavg2 if ls.loadavg2 &gt; host.stats_load.max2

 host.stats_load.average0 = loadavg0 / host.stats_load.number
 host.stats_load.average1 = loadavg1 / host.stats_load.number
 host.stats_load.average2 = loadavg2 / host.stats_load.number

 host.stats_load.loadavg0 = ls.loadavg0
 host.stats_load.loadavg1 = ls.loadavg1
 host.stats_load.loadavg2 = ls.loadavg2

 host.save
 end

 logger.info "Embedded host load stats updated"

 rescue =&gt; e
 logger.info "failed on update : #{e}"
 raise Exception.new("failed on update : #{e}")
 end

end

loop do
 @@worker.work
end</pre>
<h2>Avantages</h2>
<p>En cas de soucis sur les workers, ceux-ci pourront être stoppé sans problème même en production. Grâce au découplage mis en place, les traitements sont tous simplement en attente dans leur file d&#8217;attente. Il y a intérêt à avoir un serveur d&#8217;API très basique (ici le code dans Sinatra) afin d&#8217;éviter au maximum les éventuels plantages ou corruption des données, et de plus cela le rend, comme je l&#8217;ai déjà dis, plus rapide.</p>
<h2>Evolution</h2>
<p>Cette architecture est encore basique car ne tourne que sur une seule machine. Cependant les technologies employées permettront de la répartir sur plusieurs machines très simplement. Tout d&#8217;abord au niveau de mongoDB via des slaves. Puis en démarrant plusieurs démons Gearman. Les workers pourront ainsi dépiler leurs jobs sur l&#8217;un des serveurs Gearman du cluster.</p>
<p>Ensuite il pourra être intéressant que chaque worker log dans mongoDB ses tâches et ses temps de traitement afin de détecter les éventuels bottleneck. Gearman possède également une multitude d&#8217;options comme pouvoir rendre prioritaire un job.</p>
<p>Enfin il est bien entendu approprié que le front web puisse utiliser l&#8217;API afin d&#8217;effectuer des traitements lourds demandés par l&#8217;utilisateur.</p>
<br />Filed under: <a href='http://frederic.logier.org/category/rails/'>rails</a>, <a href='http://frederic.logier.org/category/web/'>web</a> Tagged: <a href='http://frederic.logier.org/tag/gearman/'>gearman</a>, <a href='http://frederic.logier.org/tag/mongodb/'>mongodb</a>, <a href='http://frederic.logier.org/tag/nodecast/'>nodecast</a>, <a href='http://frederic.logier.org/tag/sinatra/'>sinatra</a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/fredix.wordpress.com/300/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/fredix.wordpress.com/300/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/fredix.wordpress.com/300/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/fredix.wordpress.com/300/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/fredix.wordpress.com/300/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/fredix.wordpress.com/300/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/fredix.wordpress.com/300/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/fredix.wordpress.com/300/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/fredix.wordpress.com/300/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/fredix.wordpress.com/300/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/fredix.wordpress.com/300/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/fredix.wordpress.com/300/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/fredix.wordpress.com/300/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/fredix.wordpress.com/300/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=frederic.logier.org&amp;blog=3098224&amp;post=300&amp;subd=fredix&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://frederic.logier.org/2010/07/21/nodecast-architecture-dune-application-web/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/0fc8e5d51ed3c0726b2826e8caeb8017?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">fredix</media:title>
		</media:content>

		<media:content url="http://fredix.files.wordpress.com/2010/07/architecture.png" medium="image">
			<media:title type="html">architecture</media:title>
		</media:content>
	</item>
		<item>
		<title>Une API asynchrone avec Gearman, Sinatra et mongoID</title>
		<link>http://frederic.logier.org/2010/06/22/une-api-asynchrone-avec-gearman-sinatra-et-mongoid/</link>
		<comments>http://frederic.logier.org/2010/06/22/une-api-asynchrone-avec-gearman-sinatra-et-mongoid/#comments</comments>
		<pubDate>Tue, 22 Jun 2010 08:17:32 +0000</pubDate>
		<dc:creator>fredix</dc:creator>
				<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[gearman]]></category>
		<category><![CDATA[mongodb]]></category>
		<category><![CDATA[sinatra]]></category>

		<guid isPermaLink="false">http://frederic.logier.org/?p=222</guid>
		<description><![CDATA[Même si Ruby on Rails facilite énormément la création d&#8217;une API REST il y a un grand intérêt à séparer le site frontal de l&#8217;API. Le site peut subir des montées en charge ou une coupure de maintenance qui occasionnerait de fait une interruption de l&#8217;API. Or s&#8217;il peut être gênant que le frontal soit interrompu, ça [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=frederic.logier.org&amp;blog=3098224&amp;post=222&amp;subd=fredix&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Même si Ruby on Rails facilite énormément la création d&#8217;une API REST il y a un grand intérêt à séparer le site frontal de l&#8217;API. Le site peut subir des montées en charge ou une coupure de maintenance qui occasionnerait de fait une interruption de l&#8217;API. Or s&#8217;il peut être gênant que le frontal soit interrompu, ça l&#8217;est encore plus pour l&#8217;API qui permet de mettre à jour des données ou de les fournir à une multitude de clients tiers. Dans ce billet je présente sans aller trop loin dans les détails, la création de cette API.<span id="more-222"></span><a href="http://www.sinatrarb.com" target="_blank"></a></p>
<p><a href="http://www.sinatrarb.com" target="_blank">Sinatra</a> est un framework web léger non MVC qui permet via son DSL de répondre à toutes les méthodes HTTP (DELETE, PUT,POST,GET) simplement et en utilisant un seul fichier Ruby. <a href="http://www.sinatrarb.com/intro" target="_blank">L&#8217;introduction</a> démontre la plupart de ses capacités. C&#8217;est à mon avis idéal pour servir une API plutôt que de sortir l&#8217;artillerie lourde Rails.</p>
<p><a href="http://mongoid.org/" target="_blank">MongoID</a> est un driver Ruby pour la base NoSQL <a href="http://www.mongodb.org/" target="_blank">mongoDB</a>. Il est en concurrence avec <a href="http://mongomapper.com/" target="_blank">MongoMapper</a>.</p>
<p><a href="http://gearman.org/" target="_blank">Gearman</a> est un serveur qui fait parti des innombrables serveur de job. En effet depuis ces dernières années un nouveau serveur sort tous les 6 mois ou presque avec plus ou moins de succès. Parmi ceux-ci on peut citer <a href="http://kr.github.com/beanstalkd/" target="_blank">Beanstalkd</a> ou <a href="http://www.rabbitmq.com/" target="_blank">RabbitMQ</a>, il existe un &laquo;&nbsp;vieux&nbsp;&raquo; (1 an) comparatif non exhaustif de quelques serveurs de job <a href="http://www.darkcoding.net/software/choosing-a-message-queue-for-python-on-ubuntu-on-a-vps/" target="_blank">Choosing a message queue for Python on Ubuntu on a VPS</a>. Pour ajouter au trouble on peut trouver parmi eux des serveurs NoSQL type clé/valeur comme redis avec le plugin <a href="http://github.com/defunkt/resque" target="_blank">Resque</a>.</p>
<p>Je travaille depuis quelques mois sur mon projet <a href="http://www.nodecast.net" target="_blank">Nodecast</a> et je souhaitais rendre l&#8217;API asynchrone. Le principe du projet est qu&#8217;un client desktop extrait régulièrement des informations systèmes et les transmet au site web. Un serveur <a href="http://www.sinatrarb.com/" target="_blank">Sinatra</a> expose l&#8217;API et enregistre les données dans MongoDB. Le problème est que si de plus en plus clients viennent pusher leurs données sur le serveur Sinatra, celui-ci aura forcément de plus en plus de mal à encaisser la charge. Avec une architecture de ce type entre la requête HTTP POST du client et la réponse du serveur, tout fonctionne de manière synchrone, de fait si le serveur Sinatra atteins son pallier il ne sera plus en mesure de répondre à de nouvelles requêtes. Il est nécessaire que l&#8217;échange entre le client et le serveur Sinatra soit le plus rapide possible.</p>
<p>Pour cela il est essentiel de découpler l&#8217;interaction entre le serveur et le travail effectué. Pour comprendre cela imaginons que le client ne transmette pas d&#8217;informations systèmes mais carrément une vidéo que le serveur devra par exemple ré-encoder. Il n&#8217;est pas concevable que lorsque le client (desktop ou browser) a fini de poster la vidéo, l&#8217;utilisateur doive encore attendre la fin du réencodage du film avant d&#8217;avoir la main. Ce travail de ré-encodage doit s&#8217;effectuer dans un 2ème temps.</p>
<p>La première règle à définir est que l&#8217;échange entre le client et le serveur doit s&#8217;effectuer le plus vite possible, c&#8217;est un échange du type &laquo;&nbsp;fire and forget&nbsp;&raquo;. Pour cela lorsque le client desktop poste les informations, le serveur ne doit renvoyer qu&#8217;un acknowledge et aucune autre information permettant au client de savoir si le travail s&#8217;est bien effectué. Car si c&#8217;était le cas on reviendrait à un fonctionnement synchrone &#8230; Cependant rien empêche le serveur de renvoyer au client un identifiant unique afin de lui permettre par la suite de connaitre via une autre requête l&#8217;état de du travail envoyé.</p>
<p>Pour rendre cela possible il est nécessaire de séparer le code qui réceptionne la requête du code qui va traiter la demande. Il manque un tampon entre les deux, car le code qui va traiter la demande, le worker, peut être justement occupé à en traiter une. L&#8217;intérêt d&#8217;un serveur de job (appelé aussi serveur de file de message) est de stocker le job et le transmettre au worker lorsque celui-ci en fait la demande. Dans l&#8217;industrie il existe le protocole <a href="http://www.amqp.org" target="_blank">AMQP</a> que RabbitMQ implémente. Je n&#8217;ai malheureusement pas réussi à faire lancer ce dernier sur mon serveur Ubuntu 10.04. J&#8217;ai découvert Gearman qui est presque aussi simple d&#8217;usage sur Beanstalkd tout en proposant des capacités de montée en charge, un worker pouvant s&#8217;abonner à plusieurs serveurs Gearman. De plus cet outil est développé par <a href="http://www.danga.com/gearman/" target="_blank">Danga</a> pour LiveJournal, voir leur présentation sur le backend de LiveJournal : <a href="http://www.slideshare.net/SergeyChernyshev/behind-the-scenes-at-livejournal-scaling-storytime-158352" target="_blank">Behind the Scenes at LiveJournal: Scaling Storytime</a>.</p>
<p>Sinatra expose l&#8217;API, réceptionne les demandes des clients, les transmet aussitôt à Gearman dans une file nommée en fonction du job (update,insert,&#8230;) puis renvoie aussitôt un ack au client desktop pour lui confirmer que sa requête est prise en compte. Sinatra est donc rapidement disponible à réceptionner une nouvelle requête. Le worker est abonné aux files d&#8217;attente dont il a la charge, il supprime le job du serveur afin de le traiter. Enfin Gearman offre la possibilité au worker de retourner au client l&#8217;état du travail effectué. Le client, ici Sinatra, dispose de ces 3 méthodes :</p>
<div id="_mcePaste">task.on_complete {|d| logger.info &laquo;&nbsp;complete : #{d}&nbsp;&raquo; }</div>
<div id="_mcePaste">task.on_warning {|w| logger.info &laquo;&nbsp;[client] warn: #{w}&nbsp;&raquo; }</div>
<div id="_mcePaste">task.on_fail {|f| logger.info &laquo;&nbsp;[client] failed : #{f}&nbsp;&raquo; }</div>
<p>Il peut ainsi stocker dans un fichier de log le statut du worker.</p>
<p>Voici le code du client Sinatra lancé par Thin :</p>
<pre>#!/usr/bin/env ruby
require 'rubygems'

require 'gearman'

require "bundler"

Bundler.setup

Bundler.require(:default)

RAILS_ENV="development"

Gearman::Util.debug = true

SERVERS = ['localhost:4730']

logger = Logger.new('log/server.log', 'daily')

logger.debug("Created logger")

File.open(File.join('../config/database.mongo.yml'), 'r') do |f|

@settings = YAML.load(f)[RAILS_ENV]

end

Mongoid.configure do |config|

name = @settings["database"]

host = @settings["host"]

config.use_object_ids = @settings["use_object_ids"]

logger.info "database : #{name}"

logger.info "host : #{host}"

config.master = Mongo::Connection.new.db(name)

end

require 'models_mongoid/user.rb'

require 'models_mongoid/profil.rb'

require 'models_mongoid/host.rb'

set :logging, true

helpers do

def protected!

unless authorized?

response['WWW-Authenticate'] = %(Basic realm="Nodecast HTTP Auth")

throw(:halt, [401, "Not authorized\n"])

end

end

def authorized?

@auth ||=  Rack::Auth::Basic::Request.new(request.env)

# logger.info "AUTH = #{@auth.inspect}"

@current_user = User.where(:email =&gt; @auth.credentials.first, :authentication_token =&gt; @auth.credentials.last).first

@auth.provided? &amp;&amp; @auth.basic? &amp;&amp; @current_user

end

end

post '/hosts.xml' do

protected!

xml = Crack::XML.parse(request.body.read)

logger.info("POST")

uuid = UUIDTools::UUID.timestamp_create.to_s

host = {

:user =&gt; @current_user.email,

:uuid =&gt; uuid,

:datas =&gt; xml

}

client = Gearman::Client.new(SERVERS)

taskset = Gearman::Taskset.new

# task = Gearman::Task.new('add', Marshal.dump(xml), :background =&gt; true, :poll_status_interval =&gt; 1)

task = Gearman::Task.new('add', Marshal.dump(host))

task.on_complete {|d| logger.info "complete : #{d}" }

task.on_warning {|w| logger.info "[client] warn: #{w}" }

task.on_fail {|f| logger.info "[client] calculation failed : #{f}" }

taskset &lt;&lt; task

client.run(taskset)

builder do |xml|

xml.instruct!

xml.host do

xml.uuid host[:uuid]

end

end

end

put '/host/update/:id' do

protected!

xml = Crack::XML.parse(request.body.read)

host = {

:user =&gt; @current_user.email,

:uuid =&gt; params[:id],

:datas =&gt; xml

}

logger.info("UPDATE")

client = Gearman::Client.new(SERVERS)

taskset = Gearman::Taskset.new

#task = Gearman::Task.new('update', Marshal.dump(host), :background =&gt; true, :poll_status_interval =&gt; 1)

task = Gearman::Task.new('update', Marshal.dump(host))

task.on_complete {|d| logger.info "complete : #{d}" }

task.on_warning {|w| logger.info "[client] warn: #{w}" }

task.on_fail {|f| logger.info "[client] calculation failed : #{f}" }

taskset &lt;&lt; task

client.run(taskset)

builder do |xml|

xml.instruct!

xml.host do

xml.status "proceed"

end

end

end</pre>
<p>et le code du worker lancé par un script daemons</p>
<pre>#!/usr/bin/env ruby
require 'rubygems'

gem 'mongoid', '1.9.0'
require 'mongoid'
gem 'gearman-ruby', '2.0.0'
require 'gearman'
require 'uuidtools'
require 'logger'
require 'yaml'
require 'optparse'
require "pp"

options = {}

optparse = OptionParser.new do |opts|
  opts.on('-w', '--work WORK', 'path to the work directory') do |work|
    options[:work] = work
  end
  opts.on('-m', '--mongo MONGO', 'path to the mongo file') do |mongo|
    options[:mongo] = mongo
  end
  opts.on('-e', '--env ENV', 'rails environment') do |env|
    options[:env] = env
  end
end

begin
  optparse.parse!
  mandatory = [:work, :mongo, :env]
  missing = mandatory.select{ |param| options[param].nil? }
  if not missing.empty?
    puts "Missing options: #{missing.join(', ')}"
    puts optparse
    exit
  end
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
  puts $!.to_s
  puts optparse
  exit
end

puts "Performing task with options: #{options.inspect}"            

Gearman::Util.debug = true if options[:env] == "development"

servers = ['localhost:4730']
@@worker = Gearman::Worker.new(servers)

logger = Logger.new("#{options[:work]}/log/worker.log", 'daily')
logger.debug("Created logger")

File.open(File.join("#{options[:mongo]}/database.mongo.yml"), 'r') do |f|
  @settings = YAML.load(f)[options[:env]]
end

Mongoid.configure do |config|
  name = @settings["database"]
  host = @settings["host"]
  config.use_object_ids = @settings["use_object_ids"]
  logger.info "database : #{name}"
  logger.info "host : #{host}"
  config.master = Mongo::Connection.new.db(name)
end

require "#{options[:work]}/models_mongoid/user.rb"
require "#{options[:work]}/models_mongoid/profil.rb"
require "#{options[:work]}/models_mongoid/host.rb"
require "#{options[:work]}/models_mongoid/osystem.rb"
require "#{options[:work]}/models_mongoid/load_statistic.rb"
require "#{options[:work]}/models_mongoid/memory_statistic.rb"
require "#{options[:work]}/models_mongoid/uptime_statistic.rb"
require "#{options[:work]}/models_mongoid/network_statistic.rb"
require "#{options[:work]}/models_mongoid/cpu_statistic.rb" 

########## JOB ADD HOST ############
@@worker.add_ability('add') do |data,job|

  dump = Marshal.load(data)
  xml = dump[:datas]

  @current_user = User.where(:email =&gt; dump[:user]).first

  p "user = #{@current_user.inspect}"

  push_host = {
    :uuid =&gt; dump[:uuid],
    :public =&gt; xml['host']['public'],
    <img src='http://s.wordpress.com/wp-includes/images/smilies/icon_surprised.gif' alt=':o' class='wp-smiley' /> s_version =&gt; xml['host']['version'],
    :patch_level =&gt; xml['host']['patch_level'].nil? ? "" : xml['host']['patch_level'].downcase,
    :cpu_vendor =&gt; xml['host']['cpu_vendor'],
    :architecture =&gt; xml['host']['architecture'],
    :cpu_model =&gt; xml['host']['cpu_model'],
    :cpu_mhz =&gt; xml['host']['cpu_mhz'],
    :cpu_cache_size =&gt; xml['host']['cpu_cache_size'],
    :cpu_number =&gt; xml['host']['cpu_number'],
    :cpu_total_cores =&gt; xml['host']['cpu_total_cores'],
    :cpu_total_sockets =&gt; xml['host']['cpu_total_sockets'],
    :cpu_cores_per_socket =&gt; xml['host']['cpu_cores_per_socket'],
    :mem_ram =&gt; xml['host']['mem_ram'],
    :mem_total =&gt; xml['host']['mem_total'],
    :hostname =&gt; xml['host']['hostname'],
    :domain_name =&gt; xml['host']['domain_name'],
    :default_gateway =&gt; xml['host']['default_gateway'],
    :primary_dns =&gt; xml['host']['primary_dns'],
    :secondary_dns =&gt; xml['host']['secondary_dns'],
    :primary_interface =&gt; xml['host']['primary_interface'],
    :primary_addr =&gt; xml['host']['primary_addr'],
    :user_id =&gt; @current_user.id
  }

  host_uptime = {
    :time =&gt; xml['host']['uptime_time'],
    :days =&gt; xml['host']['uptime_days']
  }

  host_load = {
    :loadavg0 =&gt; xml['host']['loadavg0'],
    :loadavg1 =&gt; xml['host']['loadavg1'],
    :loadavg2 =&gt; xml['host']['loadavg2']
  }

  host_mem = {
    :mem_used =&gt; xml['host']['mem_used'],
    :mem_free =&gt; xml['host']['mem_free'],
    :mem_actual_free =&gt; xml['host']['mem_actual_free'],
    :mem_actual_used =&gt; xml['host']['mem_actual_used'],
    :mem_actual_free_percent =&gt; xml['host']['mem_actual_free_percent'],
    :mem_actual_used_percent =&gt; xml['host']['mem_actual_used_percent'],
    :swap_total =&gt; xml['host']['swap_total'],
    :swap_used =&gt; xml['host']['swap_used'],
    :swap_free =&gt; xml['host']['swap_free'],
    :swap_page_in =&gt; xml['host']['swap_page_in'],
    :swap_page_out =&gt; xml['host']['swap_page_out']
    }

  host_network = {
    :rx_rate =&gt; xml['host']['rx_rate'],
    :tx_rate =&gt; xml['host']['tx_rate']
  }

  host_cpu = {
    :user =&gt; xml['host']['cpu_user'],
    :sys =&gt; xml['host']['cpu_sys'],
    :nice =&gt; xml['host']['cpu_nice'],
    :idle =&gt; xml['host']['cpu_idle'],
    :wait =&gt; xml['host']['cpu_wait'],
    :irq =&gt; xml['host']['cpu_irq'],
    :soft_irq =&gt; xml['host']['cpu_soft_irq'],
    :stolen =&gt; xml['host']['cpu_stolen'],
    :combined =&gt; xml['host']['cpu_combined'],
    :total =&gt; xml['host']['cpu_total']
  }

  begin
    profil = @current_user.profils.where(:context =&gt; xml['host']['profil']).first

    if !profil
      profil = @current_user.profils.create(:context  =&gt; xml['host']['profil'])
    end

    @host = profil.hosts.create(push_host)

    osystem = Osystem.where(:vendor =&gt; xml['host']['vendor'].downcase, :vendor_version =&gt; xml['host']['vendor_version'].downcase).first

    if !osystem
      osystem = Osystem.create(
                               :name =&gt; xml['host']['name'].downcase,
                               :vendor =&gt; xml['host']['vendor'].downcase,
                               :vendor_version =&gt; xml['host']['vendor_version'].downcase,
                               :vendor_code_name =&gt; xml['host']['vendor_code_name'].nil? ? "" : xml['host']['vendor_code_name'].downcase,
                               :description =&gt; xml['host']['description'],
                               <img src='http://s.wordpress.com/wp-includes/images/smilies/icon_surprised.gif' alt=':o' class='wp-smiley' /> s_base =&gt; xml['host']['os_base'],
                               <img src='http://s.wordpress.com/wp-includes/images/smilies/icon_surprised.gif' alt=':o' class='wp-smiley' /> s_type =&gt; xml['host']['os_type']
                               )
    end

    @host.osystem_id = osystem.id
    osystem.hosts_number += 1
    osystem.save
    @host.save

    if xml['host']['activated_memory'] == "true"
      @host.memory_statistics.create(host_mem)
    end

    if xml['host']['activated_load'] == "true"
      @host.load_statistics.create(host_load)
    end

    if xml['host']['activated_uptime'] == "true"
      @host.uptime_statistics.create(host_uptime)
    end

    if xml['host']['activated_network'] == "true"
      @host.network_statistics.create(host_network)
    end

    if xml['host']['activated_cpu'] == "true"
      @host.cpu_statistics.create(host_cpu)
    end

  rescue =&gt; e
    logger.info "error on create host : #{e}"
  end

  @current_user.hosts_number += 1
  @current_user.save

logger.info "created"
end

########## JOB UPDATE HOST ############
@@worker.add_ability('update') do |data,job|

  dump = Marshal.load(data)
  xml = dump[:datas]

  @current_user = User.where(:email =&gt; dump[:user]).first
  @host = Host.where(:uuid =&gt; dump[:uuid], :blocked =&gt; false).first

  if !@host
logger.info "host unknown"

  elsif @current_user.hosts.include?(@host)
    # os = Osystem.find(:all, :conditions =&gt; "lower(name) = '#{xml['host'][:os_name].downcase}' AND lower(codename) LIKE '%#{xml['host'][:os_codename].downcase}%' AND version = '#{xml['host'][:os_release]}' AND computer_architecture = '#{xml['host'][:architecture]}'")

    host_uptime = {
      :time =&gt; xml['host']['uptime_time'],
      :days =&gt; xml['host']['uptime_days']
    }

    host_load = {
      :loadavg0 =&gt; xml['host']['loadavg0'],
      :loadavg1 =&gt; xml['host']['loadavg1'],
      :loadavg2 =&gt; xml['host']['loadavg2']
    }

    host_mem = {
      :mem_used =&gt; xml['host']['mem_used'],
      :mem_free =&gt; xml['host']['mem_free'],
      :mem_actual_free =&gt; xml['host']['mem_actual_free'],
      :mem_actual_used =&gt; xml['host']['mem_actual_used'],
      :mem_actual_free_percent =&gt; xml['host']['mem_actual_free_percent'],
      :mem_actual_used_percent =&gt; xml['host']['mem_actual_used_percent'],
      :swap_total =&gt; xml['host']['swap_total'],
      :swap_used =&gt; xml['host']['swap_used'],
      :swap_free =&gt; xml['host']['swap_free'],
      :swap_page_in =&gt; xml['host']['swap_page_in'],
      :swap_page_out =&gt; xml['host']['swap_page_out']
    }

    host_network = {
      :rx_rate =&gt; xml['host']['rx_rate'],
      :tx_rate =&gt; xml['host']['tx_rate']
    }

    host_cpu = {
      :user =&gt; xml['host']['cpu_user'],
      :sys =&gt; xml['host']['cpu_sys'],
      :nice =&gt; xml['host']['cpu_nice'],
      :idle =&gt; xml['host']['cpu_idle'],
      :wait =&gt; xml['host']['cpu_wait'],
      :irq =&gt; xml['host']['cpu_irq'],
      :soft_irq =&gt; xml['host']['cpu_soft_irq'],
      :stolen =&gt; xml['host']['cpu_stolen'],
      :combined =&gt; xml['host']['cpu_combined'],
      :total =&gt; xml['host']['cpu_total']
    }

    begin

      if xml['host']['profil'] != @host.profil.context
        profil = @current_user.profils.where(:context =&gt; xml['host']['profil']).first
        if !profil
          profil = @current_user.profils.create(:context  =&gt; xml['host']['profil'])
        end
        @host.profil = profil
        @host.save
      end

         if xml['host']['activated_memory'] == "true"
           mem = @host.memory_statistics.create(host_mem)
         end

         if xml['host']['activated_load'] == "true"
           @host.load_statistics.create(host_load)
         end

         if xml['host']['activated_uptime'] == "true"
           @host.uptime_statistics.create(host_uptime)
         end

         if xml['host']['activated_network'] == "true"
           @host.network_statistics.create(host_network)
         end

         if xml['host']['activated_cpu'] == "true"
           @host.cpu_statistics.create(host_cpu)
         end

         logger.info "updated"

    rescue =&gt; e
      logger.info "failed on update : #{e}"
      raise Exception.new("failed on update : #{e}")
    end

  else
    logger.info "unauthorized update"
    raise Exception.new("unauthorized update")
  end
end

@@worker.work</pre>
<br />Filed under: <a href='http://frederic.logier.org/category/rails/'>rails</a>, <a href='http://frederic.logier.org/category/ruby/'>ruby</a> Tagged: <a href='http://frederic.logier.org/tag/gearman/'>gearman</a>, <a href='http://frederic.logier.org/tag/mongodb/'>mongodb</a>, <a href='http://frederic.logier.org/tag/sinatra/'>sinatra</a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/fredix.wordpress.com/222/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/fredix.wordpress.com/222/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/fredix.wordpress.com/222/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/fredix.wordpress.com/222/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/fredix.wordpress.com/222/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/fredix.wordpress.com/222/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/fredix.wordpress.com/222/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/fredix.wordpress.com/222/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/fredix.wordpress.com/222/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/fredix.wordpress.com/222/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/fredix.wordpress.com/222/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/fredix.wordpress.com/222/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/fredix.wordpress.com/222/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/fredix.wordpress.com/222/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=frederic.logier.org&amp;blog=3098224&amp;post=222&amp;subd=fredix&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://frederic.logier.org/2010/06/22/une-api-asynchrone-avec-gearman-sinatra-et-mongoid/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/0fc8e5d51ed3c0726b2826e8caeb8017?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">fredix</media:title>
		</media:content>
	</item>
		<item>
		<title>before_filter :rails_party</title>
		<link>http://frederic.logier.org/2008/11/24/before_filter-rails_party/</link>
		<comments>http://frederic.logier.org/2008/11/24/before_filter-rails_party/#comments</comments>
		<pubDate>Mon, 24 Nov 2008 13:56:00 +0000</pubDate>
		<dc:creator>fredix</dc:creator>
				<category><![CDATA[MondeLibre]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://fredix.wordpress.com/2008/11/24/before_filter-rails_party/</guid>
		<description><![CDATA[Si vous suivez l’actualité RubyonRails, vous savez sans doute que l’édition 2008 de Paris on Rails a lieu le 1er décembre à la Cité des sciences. Par contre vous ne savez peut être pas qu’une Rails party est organisé la vieille ce dimanche 30 novembre. Toutes les infos ici : rails party 2008 et inscription [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=frederic.logier.org&amp;blog=3098224&amp;post=86&amp;subd=fredix&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<div class="post-body">
<p>Si vous suivez l’actualité RubyonRails, vous savez sans doute que l’édition 2008 de <a href="http://paris.onrails.info/">Paris on Rails</a> a lieu le 1er décembre à la Cité des sciences. Par contre vous ne savez peut être pas qu’une Rails party est organisé la vieille ce dimanche 30 novembre. Toutes les infos ici : <a href="http://www.rubyfrance.org/evenements/rails-party-2008">rails party 2008</a> et <a href="http://railsparty.ror.bearstech.com/">inscription ici</a>. Ils ont bien de la chance ces parisiens ! Quand est-ce que Lyon sera à la hauteur de sa dimension … ?</p>
<p>Pour enchainer sur le thème Ruby, j’ai réalisé une nouvelle et sans doute dernière version de <a href="http://download.gna.org/geekast/geekast-0.1.3.tgz">Geekast 0.1.3</a> afin de fixer ce <a href="https://bugs.launchpad.net/ubuntu/+source/geekast/+bug/237032">bug</a>. <a href="http://peercast.org/">Peercast</a> n’étant plus maintenu par son développeur la motivation n’y est plus et je suis de plus en apprentissage du C++ sur <a href="http://github.com/fredix/imotion/tree/master">iMotion</a> un autre projet perso. Quoi qu’il en soit je ne compte pas abandonner Ruby qui est un vrai plaisir à utiliser et complémentaire à un langage compilé.</p>
</p></div>
<br />Publié dans MondeLibre, rails, ruby  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/fredix.wordpress.com/86/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/fredix.wordpress.com/86/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/fredix.wordpress.com/86/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/fredix.wordpress.com/86/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/fredix.wordpress.com/86/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/fredix.wordpress.com/86/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/fredix.wordpress.com/86/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/fredix.wordpress.com/86/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/fredix.wordpress.com/86/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/fredix.wordpress.com/86/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/fredix.wordpress.com/86/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/fredix.wordpress.com/86/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/fredix.wordpress.com/86/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/fredix.wordpress.com/86/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=frederic.logier.org&amp;blog=3098224&amp;post=86&amp;subd=fredix&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://frederic.logier.org/2008/11/24/before_filter-rails_party/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/0fc8e5d51ed3c0726b2826e8caeb8017?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">fredix</media:title>
		</media:content>
	</item>
		<item>
		<title>De l&#8217;OS et des serveurs web</title>
		<link>http://frederic.logier.org/2008/10/26/de-los-et-des-serveurs-web/</link>
		<comments>http://frederic.logier.org/2008/10/26/de-los-et-des-serveurs-web/#comments</comments>
		<pubDate>Sun, 26 Oct 2008 14:00:00 +0000</pubDate>
		<dc:creator>fredix</dc:creator>
				<category><![CDATA[MondeLibre]]></category>
		<category><![CDATA[rails]]></category>

		<guid isPermaLink="false">http://fredix.wordpress.com/2008/10/26/de-los-et-des-serveurs-web/</guid>
		<description><![CDATA[Dans la continuité de ma migration vers Fedora sur mes desktops, je viens de migrer mon serveur dedibox sous Centos la version gratuite de Red Hat Enterprise Linux. Même si cet OS est très robuste et maintenu 7 ans, on y trouve moins de paquet qu’ailleurs, aussi j’ai ajouté le dépôt Fedora dédié à Centos, [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=frederic.logier.org&amp;blog=3098224&amp;post=84&amp;subd=fredix&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<div class="post-body">
<p>Dans la continuité de ma migration vers Fedora sur mes desktops, je viens de migrer mon serveur dedibox sous <a href="http://centos.org/">Centos</a> la version gratuite de Red Hat Enterprise Linux. Même si cet OS est très robuste et maintenu 7 ans, on y trouve moins de paquet qu’ailleurs, aussi j’ai ajouté le dépôt Fedora dédié à Centos, <a href="http://fedoraproject.org/wiki/EPEL"><span class="caps">EPEL</span></a>. J’ai pu ainsi installer Nginx et ejabberd 2.0.2. J’ai été surpris de trouver dans les dépôts des gems tel que Rails, mais malheureusement celui-ci ne fonctionne pas car il a été lié à une version gem de rake trop ancienne (0.7). C’est bien gentil de vouloir packager les gems mais encore faut-il le faire correctement <img src='http://s.wordpress.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' />  Donc téléchargement de rubygems 1.3 et installation classique des gems, cela fonctionne très bien, je ne vois pas l’intérêt de faire un double travail côté distribution…</p>
<p>C’est pour moi l’occasion de quitter le bon vieux couple apache/mongrel et tester le fameux <a href="http://nginx.net/">nginx</a> / <a href="http://code.macournoyer.com/thin/">thin</a>. Grâce à cette doc <a href="http://glauche.de/2008/01/12/thin-nginx-with-rails/">thin-nginx-with-rails</a>, j’ai pu rapidement faire tourner mon blog avec thin et nginx. A voir ce que cela donnera sur de futurs projets perso plus gourmant qu’un blog. Pour info, pour pouvoir utiliser le fichier rake par thin il suffit de le copier dans lib/tasks/thin.rake du projet Rails.Et pour la conf nginx dans /etc/nginx/conf.d/upstream-fair.conf</p>
</p></div>
<br />Publié dans MondeLibre, rails  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/fredix.wordpress.com/84/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/fredix.wordpress.com/84/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/fredix.wordpress.com/84/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/fredix.wordpress.com/84/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/fredix.wordpress.com/84/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/fredix.wordpress.com/84/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/fredix.wordpress.com/84/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/fredix.wordpress.com/84/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/fredix.wordpress.com/84/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/fredix.wordpress.com/84/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/fredix.wordpress.com/84/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/fredix.wordpress.com/84/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/fredix.wordpress.com/84/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/fredix.wordpress.com/84/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=frederic.logier.org&amp;blog=3098224&amp;post=84&amp;subd=fredix&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://frederic.logier.org/2008/10/26/de-los-et-des-serveurs-web/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/0fc8e5d51ed3c0726b2826e8caeb8017?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">fredix</media:title>
		</media:content>
	</item>
		<item>
		<title>De la répartition de charge en Ruby on Rails</title>
		<link>http://frederic.logier.org/2008/01/22/de-la-repartition-de-charge-en-ruby-on-rails/</link>
		<comments>http://frederic.logier.org/2008/01/22/de-la-repartition-de-charge-en-ruby-on-rails/#comments</comments>
		<pubDate>Tue, 22 Jan 2008 14:56:00 +0000</pubDate>
		<dc:creator>fredix</dc:creator>
				<category><![CDATA[MondeLibre]]></category>
		<category><![CDATA[XMPP]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://fredix.wordpress.com/2008/01/22/de-la-repartition-de-charge-en-ruby-on-rails/</guid>
		<description><![CDATA[Je viens de publier un article en 2 parties sur le blog d’AF83 : De la répartition de charge en Ruby on Rails 1/2 De la répartition de charge en Ruby on Rails 2/2 C’est plutôt technique, et présente une solution parmi tant d’autres dans ce domaine très particulier.<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=frederic.logier.org&amp;blog=3098224&amp;post=64&amp;subd=fredix&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<div class="post-body">
<p>Je viens de publier un article en 2 parties sur le blog d’AF83 :</p>
<p><a href="http://dev.af83.com/ruby/de-la-repartition-de-charge-en-ruby-on-rails-12/2008/01/22/">De la répartition de charge en Ruby on Rails 1/2</a></p>
<p><a href="http://dev.af83.com/ruby/de-la-repartition-de-charge-en-ruby-on-rails-22/2008/01/22/">De la répartition de charge en Ruby on Rails 2/2</a></p>
<p>C’est plutôt technique, et présente une solution parmi tant d’autres dans ce domaine très particulier.</p>
</div>
<br /><img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/fredix.wordpress.com/64/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/fredix.wordpress.com/64/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/fredix.wordpress.com/64/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/fredix.wordpress.com/64/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/fredix.wordpress.com/64/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/fredix.wordpress.com/64/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/fredix.wordpress.com/64/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/fredix.wordpress.com/64/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/fredix.wordpress.com/64/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/fredix.wordpress.com/64/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/fredix.wordpress.com/64/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/fredix.wordpress.com/64/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/fredix.wordpress.com/64/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/fredix.wordpress.com/64/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/fredix.wordpress.com/64/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/fredix.wordpress.com/64/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=frederic.logier.org&amp;blog=3098224&amp;post=64&amp;subd=fredix&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://frederic.logier.org/2008/01/22/de-la-repartition-de-charge-en-ruby-on-rails/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/0fc8e5d51ed3c0726b2826e8caeb8017?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">fredix</media:title>
		</media:content>
	</item>
		<item>
		<title>De la répartition de charge en Ruby on Rails 2/2</title>
		<link>http://frederic.logier.org/2008/01/22/de-la-repartition-de-charge-en-ruby-on-rails-22/</link>
		<comments>http://frederic.logier.org/2008/01/22/de-la-repartition-de-charge-en-ruby-on-rails-22/#comments</comments>
		<pubDate>Tue, 22 Jan 2008 14:49:00 +0000</pubDate>
		<dc:creator>fredix</dc:creator>
				<category><![CDATA[MondeLibre]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[texte]]></category>
		<category><![CDATA[web]]></category>

		<guid isPermaLink="false">http://fredix.wordpress.com/2008/01/22/de-la-repartition-de-charge-en-ruby-on-rails-22/</guid>
		<description><![CDATA[Article publié initialement sur le site d&#8217;AF83 L&#8217;architecture. Les bots Il ne reste qu&#8217;à exploiter les capacités de Rails et de XMPP afin de développer les bots qui vont effectuer les traitements. Exemple : PLAIN TEXT RUBY: #!/usr/bin/env ruby # # MonBot save message through ActiveRecord require &#8216;rubygems&#8217; require &#8216;xmpp4r-simple&#8217; require &#8216;daemons&#8217; require &#8216;yaml&#8217; require [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=frederic.logier.org&amp;blog=3098224&amp;post=63&amp;subd=fredix&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Article publié initialement sur le <a href="http://dev.af83.com/ruby/de-la-repartition-de-charge-en-ruby-on-rails-22/2008/01/22/">site d&#8217;AF83</a></p>
<p><strong>L&#8217;architecture.</strong></p>
<p><em>Les bots</em></p>
<p>Il ne reste qu&#8217;à  exploiter les capacités de Rails et de XMPP afin de développer les bots qui vont effectuer les traitements.</p>
<p>Exemple :</p>
<div class="igBar"><span><a href="http://dev.af83.com/ruby/de-la-repartition-de-charge-en-ruby-on-rails-22/2008/01/22/#">PLAIN TEXT</a></span></div>
<div class="syntax_hilite"><span class="langName">RUBY:</span>
<div id="ruby-1">
<div class="ruby" style="font-family:monospace;">
<ol>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(0,128,0);font-style:italic;">#!/usr/bin/env ruby</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(0,128,0);font-style:italic;">#</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(0,128,0);font-style:italic;"># MonBot save message through ActiveRecord</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"> </div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(204,0,102);font-weight:bold;">require</span> <span style="color:rgb(153,102,0);">&#8216;rubygems&#8217;</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(204,0,102);font-weight:bold;">require</span> <span style="color:rgb(153,102,0);">&#8216;xmpp4r-simple&#8217;</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(204,0,102);font-weight:bold;">require</span> <span style="color:rgb(153,102,0);">&#8216;daemons&#8217;</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(204,0,102);font-weight:bold;">require</span> <span style="color:rgb(153,102,0);">&#8216;yaml&#8217;</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(204,0,102);font-weight:bold;">require</span> <span style="color:rgb(153,102,0);">&#8216;logger&#8217;</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"> </div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;">RAILS_ENV = ARGV<span style="color:rgb(0,102,0);font-weight:bold;">[</span><span style="color:rgb(128,0,0);">0</span><span style="color:rgb(0,102,0);font-weight:bold;">]</span> || <span style="color:rgb(153,102,0);">&#8216;development&#8217;</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(204,0,102);font-weight:bold;">require</span> <span style="color:rgb(204,0,255);font-weight:bold;">File</span>.<span style="color:rgb(153,0,204);">dirname</span><span style="color:rgb(0,102,0);font-weight:bold;">(</span><span style="color:rgb(0,0,255);font-weight:bold;">__FILE__</span><span style="color:rgb(0,102,0);font-weight:bold;">)</span> + <span style="color:rgb(153,102,0);">&#8216;/../config/environment&#8217;</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"> </div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(204,0,102);font-weight:bold;">require</span> <span style="color:rgb(153,102,0);">&#8216;mysql_retry_lost_connection&#8217;</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"> </div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(153,102,204);font-weight:bold;">class</span> MonBot</div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;">@@bot_jid = <span style="color:rgb(153,102,0);">&laquo;&nbsp;monbot@jabber.toto.com/1&#8243;</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;">@@bot_password = <span style="color:rgb(153,102,0);">&#8217;123&#8242;</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;">@@logger = <span style="color:rgb(204,0,255);font-weight:bold;">Logger</span>.<span style="color:rgb(153,0,204);">new</span><span style="color:rgb(0,102,0);font-weight:bold;">(</span><span style="color:rgb(153,102,0);">&laquo;&nbsp;monbot.log&nbsp;&raquo;</span><span style="color:rgb(0,102,0);font-weight:bold;">)</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"> </div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(153,102,204);font-weight:bold;">def</span> initialize</div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;">@@logger.<span style="color:rgb(153,0,204);">info</span><span style="color:rgb(0,102,0);font-weight:bold;">(</span><span style="color:rgb(153,102,0);">&#8216;initialize&#8217;</span><span style="color:rgb(0,102,0);font-weight:bold;">)</span> <span style="color:rgb(0,102,0);font-weight:bold;">{</span> <span style="color:rgb(153,102,0);">&laquo;&nbsp;Initializing in #{RAILS_ENV} mode &#8230;&nbsp;&raquo;</span> <span style="color:rgb(0,102,0);font-weight:bold;">}</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(0,102,255);font-weight:bold;">@jabber</span> = <span style="color:rgb(102,102,255);font-weight:bold;">Jabber::Simple</span>.<span style="color:rgb(153,0,204);">new</span><span style="color:rgb(0,102,0);font-weight:bold;">(</span>@@bot_jid, @@bot_password<span style="color:rgb(0,102,0);font-weight:bold;">)</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(153,102,204);font-weight:bold;">end</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"> </div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(153,102,204);font-weight:bold;">def</span> receive_msg</div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(204,0,102);font-weight:bold;">loop</span> <span style="color:rgb(153,102,204);font-weight:bold;">do</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(0,102,255);font-weight:bold;">@jabber</span>.<span style="color:rgb(153,0,204);">received_messages</span> <span style="color:rgb(153,102,204);font-weight:bold;">do</span> |message|</div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"> </div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(0,128,0);font-style:italic;"># on dÃ©sÃ©rialise le message s&#8217;il a Ã©tÃ© transmis de la sorte</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;">obj = <span style="color:rgb(204,0,255);font-weight:bold;">YAML</span>.<span style="color:rgb(204,0,102);font-weight:bold;">load</span><span style="color:rgb(0,102,0);font-weight:bold;">(</span>message.<span style="color:rgb(153,0,204);">body</span><span style="color:rgb(0,102,0);font-weight:bold;">)</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"> </div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;">mon_traitement<span style="color:rgb(0,102,0);font-weight:bold;">(</span>obj<span style="color:rgb(0,102,0);font-weight:bold;">)</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"> </div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(153,102,204);font-weight:bold;">end</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(204,0,102);font-weight:bold;">sleep</span> <span style="color:rgb(128,0,0);">0.5</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(153,102,204);font-weight:bold;">end</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(153,102,204);font-weight:bold;">end</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"> </div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;">private</div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"> </div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(153,102,204);font-weight:bold;">def</span> mon_traitement<span style="color:rgb(0,102,0);font-weight:bold;">(</span>obj<span style="color:rgb(0,102,0);font-weight:bold;">)</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;">obj.<span style="color:rgb(153,0,204);">find_by_login</span><span style="color:rgb(0,102,0);font-weight:bold;">(</span><span style="color:rgb(153,102,0);">&laquo;&nbsp;toto&nbsp;&raquo;</span><span style="color:rgb(0,102,0);font-weight:bold;">)</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(153,102,204);font-weight:bold;">end</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"> </div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(153,102,204);font-weight:bold;">end</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"> </div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;">bot = MonBot.<span style="color:rgb(153,0,204);">new</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;">bot.<span style="color:rgb(153,0,204);">receive_msg</span></div>
</li>
</ol></div>
</p></div>
</p></div>
<div class="igBar"><span><a href="http://dev.af83.com/ruby/de-la-repartition-de-charge-en-ruby-on-rails-22/2008/01/22/#">PLAIN TEXT</a></span></div>
<div class="syntax_hilite"><span class="langName">RUBY:</span>
<div id="ruby-2">
<div class="ruby" style="font-family:monospace;">
<ol>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;">RAILS_ENV = ARGV<span style="color:rgb(0,102,0);font-weight:bold;">[</span><span style="color:rgb(128,0,0);">0</span><span style="color:rgb(0,102,0);font-weight:bold;">]</span></div>
</li>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(204,0,102);font-weight:bold;">require</span> <span style="color:rgb(204,0,255);font-weight:bold;">File</span>.<span style="color:rgb(153,0,204);">dirname</span><span style="color:rgb(0,102,0);font-weight:bold;">(</span><span style="color:rgb(0,0,255);font-weight:bold;">__FILE__</span><span style="color:rgb(0,102,0);font-weight:bold;">)</span> + <span style="color:rgb(153,102,0);">&#8216;/../config/environment&#8217;</span></div>
</li>
</ol></div>
</p></div>
</p></div>
<p> Ces lignes permettent à un script Ruby de charger l&#8217;environnement Rails du projet. Le bot est alors capable d&#8217;attaquer notre modèle de données via ActiveRecord.</p>
<div class="igBar"><span><a href="http://dev.af83.com/ruby/de-la-repartition-de-charge-en-ruby-on-rails-22/2008/01/22/#">PLAIN TEXT</a></span></div>
<div class="syntax_hilite"><span class="langName">RUBY:</span>
<div id="ruby-3">
<div class="ruby" style="font-family:monospace;">
<ol>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(204,0,102);font-weight:bold;">require</span> <span style="color:rgb(153,102,0);">&#8216;xmpp4r-simple&#8217;</span></div>
</li>
</ol></div>
</p></div>
</p></div>
<p> Ce gem nous permet de communiquer vers le compte Jabber du bot. Il peut donc dÃ©piler les objets qui lui sont destinÃ© et les dÃ©sÃ©rialiser pour les traiter.</p>
<div class="igBar"><span><a href="http://dev.af83.com/ruby/de-la-repartition-de-charge-en-ruby-on-rails-22/2008/01/22/#">PLAIN TEXT</a></span></div>
<div class="syntax_hilite"><span class="langName">RUBY:</span>
<div id="ruby-4">
<div class="ruby" style="font-family:monospace;">
<ol>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(204,0,102);font-weight:bold;">require</span> <span style="color:rgb(153,102,0);">&#8216;daemons&#8217;</span></div>
</li>
</ol></div>
</p></div>
</p></div>
<p>Ce gem permet de gérer le bot en tant que service (stop/start/restart).</p>
<div class="igBar"><span><a href="http://dev.af83.com/ruby/de-la-repartition-de-charge-en-ruby-on-rails-22/2008/01/22/#">PLAIN TEXT</a></span></div>
<div class="syntax_hilite"><span class="langName">RUBY:</span>
<div id="ruby-5">
<div class="ruby" style="font-family:monospace;">
<ol>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(204,0,102);font-weight:bold;">require</span> <span style="color:rgb(153,102,0);">&#8216;yaml&#8217;</span></div>
</li>
</ol></div>
</p></div>
</p></div>
<p> Pour sérialiser/désérialiser vos objets Ruby / Rails.</p>
<div class="igBar"><span><a href="http://dev.af83.com/ruby/de-la-repartition-de-charge-en-ruby-on-rails-22/2008/01/22/#">PLAIN TEXT</a></span></div>
<div class="syntax_hilite"><span class="langName">RUBY:</span>
<div id="ruby-6">
<div class="ruby" style="font-family:monospace;">
<ol>
<li>
<div style="font-family:'Courier New',Courier,monospace;font-weight:normal;"><span style="color:rgb(204,0,102);font-weight:bold;">require</span> <span style="color:rgb(153,102,0);">&#8216;mysql_retry_lost_connection&#8217;</span></div>
</li>
</ol></div>
</p></div>
</p></div>
<p>Ce gem sert à intercepter une coupure de la connexion vers MySQL. Il surcharge ActiveRecord afin de renégocier une connexion.</p>
<p>Avec ces outils nous avons la capacité de créer des services exploitant une file d&#8217;attente Jabber et fonctionnant en parallèle. Ainsi, si la charge vient à augmenter, les messages en attente de traitement ne seront pas perdus, puisqu&#8217;en attente dans les comptes Jabber stockés par ejabberd. De plus il est possible de multiplier un même bot en exploitant les ressources du protocole Jabber, chaque bot écoutant sur sa propre ressource (bot@monserveurjabber.com/1, bot@monserveurjabber.com/2, &#8230;).</p>
<p><strong>Résumé</strong></p>
<p>Pour Noumba, nous avons développé un projet en Rails, le Hub, qui gère la file d&#8217;attente Jabber, et communique en REST avec le frontal Noumba. Si cette architecture est sur-dimensionnée pour votre projet, nul besoin d&#8217;un backend. Votre site et des bots suffisent amplement. De même quelques comptes Gmail suffisent si vos ne souhaitez pas déployer votre propre serveur Jabber.</p>
<p>Si un backend en Rails s&#8217;avère nécessaire il n&#8217;est pas conseillé de dupliquer les modèles de votre site principal vers celui-ci. Cela fonctionne mais le principe DRY est de fait supprimé. Cependant un outil tel que <a href="http://blog.teksol.info/2007/4/20/sharing-models-between-two-rails-applications-using-piston">Piston</a> permet de temporiser cette affirmation.</p>
<p><strong>Les autres</strong></p>
<p>Après divers essais Twitter a fini par développer son propre serveur de file qui exploite memcached, <a href="http://blog.twitter.com/2008/01/twitters-starling-released-as-open.html">starling</a></p>
<p>Un développeur de <a href="http://seesmic.com/">Seesmic</a> indique utiliser ActiveMQ et RabbitMQ mais semble vouloir migrer vers une solution XMPP : <a href="http://code-bear.com/bearlog/2008/01/18/scaling-questions-and-issues">scaling-questions-and-issues</a></p>
<p>Il existe un grand nombre d&#8217;alternatives, on peut citer :</p>
<ul>
<li><a href="http://xph.us/software/beanstalkd/">Beanstalkd</a> , <a href="http://nubyonrails.com/articles/about-this-blog-beanstalk-messaging-queue">about-this-blog-beanstalk-messaging-queue</a></li>
</ul>
<ul>
<li><a href="http://code.google.com/p/sparrow/">Sparrow</a></li>
</ul>
<ul>
<li><a href="http://ap4r.rubyforge.org/wiki/wiki.pl?HomePage">AP4R</a></li>
</ul>
<p>Les deux premiers utilisent le twisted like <a href="https://rubyforge.org/projects/eventmachine/">eventmachine</a></p>
<p><strong>Bémol</strong></p>
<p>Une telle architecture implique la gestion d&#8217;un serveur Jabber, ce qui peut s&#8217;avérer une tâche plus complexe et lourde qu&#8217;un réel MoM dédié. De plus la stabilité de la bibliothèque xmpp4r ainsi que le plugin ActionMessenger est à surveiller de près. Pour ce dernier, il a été nécessaire de le patcher afin qu&#8217;il puisse supporter plusieurs instances Rails.</p>
<p><strong>Avenir</strong></p>
<p>XMPP est un protocole standard et ouvert très répandu ce qui en fait un candidat idéal si l&#8217;on souhaite une architecture pérenne et évolutive. L&#8217;architecture décentralisée de Jabber et ses capacités à se connecter à des services externes tel que OpenID (<a href="http://xmppid.net/">xmppid.net</a>) ouvre la porte à une multitude de possibilités.</p>
<p>Ses nombreuses fonctionnalités dédiés au chat (room, pub/sub, voIP, &#8230;) sont toutes indiquées pour des sites communautaires et sociaux à tel point que le projet <a href="http://diso-project.org/">DiSo</a> souhaite l&#8217;utiliser en son coeur, mais ceci est un autre sujet.</p>
<br /><img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/fredix.wordpress.com/63/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/fredix.wordpress.com/63/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/fredix.wordpress.com/63/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/fredix.wordpress.com/63/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/fredix.wordpress.com/63/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/fredix.wordpress.com/63/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/fredix.wordpress.com/63/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/fredix.wordpress.com/63/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/fredix.wordpress.com/63/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/fredix.wordpress.com/63/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/fredix.wordpress.com/63/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/fredix.wordpress.com/63/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/fredix.wordpress.com/63/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/fredix.wordpress.com/63/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/fredix.wordpress.com/63/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/fredix.wordpress.com/63/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=frederic.logier.org&amp;blog=3098224&amp;post=63&amp;subd=fredix&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://frederic.logier.org/2008/01/22/de-la-repartition-de-charge-en-ruby-on-rails-22/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/0fc8e5d51ed3c0726b2826e8caeb8017?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">fredix</media:title>
		</media:content>
	</item>
		<item>
		<title>De la répartition de charge en Ruby on Rails 1/2</title>
		<link>http://frederic.logier.org/2008/01/22/de-la-repartition-de-charge-en-ruby-on-rails-12/</link>
		<comments>http://frederic.logier.org/2008/01/22/de-la-repartition-de-charge-en-ruby-on-rails-12/#comments</comments>
		<pubDate>Tue, 22 Jan 2008 14:45:00 +0000</pubDate>
		<dc:creator>fredix</dc:creator>
				<category><![CDATA[MondeLibre]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[texte]]></category>

		<guid isPermaLink="false">http://fredix.wordpress.com/2008/01/22/de-la-repartition-de-charge-en-ruby-on-rails-12/</guid>
		<description><![CDATA[Article publié initialement sur le site d&#8217;AF83 Cet article a pour but de décrire l’architecture employée derrière le site de micro-blogging Noumba. Il n’a pas pour objectif d’être exhaustif ni de prétendre proposer la solution idéale, mais seulement présenter un moyen de résoudre certaines contraintes selon notre contexte. Micro-blogging Le micro-blogging est un concept, lancé [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=frederic.logier.org&amp;blog=3098224&amp;post=62&amp;subd=fredix&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Article publié initialement sur le <a href="http://dev.af83.com/ruby/de-la-repartition-de-charge-en-ruby-on-rails-12/2008/01/22/">site d&#8217;AF83</a></p>
<p>Cet article a pour but de décrire l’architecture employée derrière le site de micro-blogging <a href="http://noumba.net/">Noumba</a>. Il n’a pas pour objectif d’être exhaustif ni de prétendre proposer la solution idéale, mais seulement présenter un moyen de résoudre certaines contraintes selon notre contexte.</p>
<p><strong>Micro-blogging</strong></p>
<p>Le micro-blogging est un concept, lancé par <a href="http://twitter.com/">Twitter</a>, qui permet de s’exprimer en de courtes phrases, tenant en une centaine de caractères de manière à pouvoir être lu depuis un téléphone portable, par SMS ou WAP.<br />En une phrase on ne communique le même type d’information que sur son blog. Cela va donc de son humeur, un lien, ou une rapide information qui ne nécessite pas un long exposé. Ce type d’information sont plus courante que celle que l’on écrirait sur son blog, et à ce titre le micro-blogging est un complément au blog plus qu’une alternative.</p>
<p>Le corollaire est la possibilité de s’exprimer depuis son téléphone portable vers le site.</p>
<p>Au final un site de micro-blogging est souvent utilisé en tant que passerelle entre 2 outils de communication (Phone2Phone, Desktop2API, Phone2Jabber, Web2…).</p>
<p><strong>Contexte</strong></p>
<p>Noumba est un site web de micro-blogging en Ruby on Rails. Il est ouvert à tous, mais son orientation commerciale le prédispose à une certaine tranche d’âge d’utilisateurs, entre 13 et 25 ans. De fait il est pour l’instant plus utilisé pour du chat que du micro-blogging, ce qui génère un fort nombre de messages par utilisateur.</p>
<p>En outre de part le profil des utilisateurs, des filtres sont indispensable afin de garantir une certaine qualité de contenu et de confidentialité.</p>
<p>Pour le reste, les contraintes sont les mêmes que des sites tels que Twitter ou Jaiku, cependant Noumba a ceci de particulier qu’il propose un certain nombre de satellites utilisés par des partenaires, tel que SFR (<a href="http://sfr.noumba.net/">sfr.noumba.net</a>), NRJ, (<a href="http://mikl.noumba.net/">mikl.noumba.net</a>, <a href="http://6-9.noumba.net/">6-9.noumba.net</a>) MyMajorCompany (<a href="http://mymajorcompany.noumba.net/">mymajorcompany.noumba.net</a>), MCM (<a href="http://mcm.noumba.net/">mcm.noumba.net</a>), …</p>
<p><strong>Contraintes</strong></p>
<p><em>Des traitements asynchrones</em></p>
<p>Lorsqu’un message est posté, des traitements sont appliqués (filtrage, routage, emails, logs,…). Ces traitements ne doivent pas augmenter la latence entre l’utilisateur et le site web. Il n’est donc pas possible de les effectuer de manière synchrone, c’est à dire imposer à l’utilisateur d’attendre leur fin avant de lui rendre la main.<br />Si un fonctionnement synchrone est envisageable avec quelques utilisateurs en simultané, il est tout bonnement impossible pour un site à vocation grand public si l’on veut respecter une certaine qualité de service.</p>
<p>La solution est d’effectuer ces traitements de manière asynchrone, c’est à dire en parallèle, sans imposer à l’utilisateur leur fin pour lui rendre la main.</p>
<p><strong>Les solutions</strong></p>
<p><em>background</em></p>
<p>La première solution utilisée sur Noumba fut <a href="http://backgroundrb.rubyforge.org/">backgroundrb</a>. Cet outil fournit un serveur de tâche permettant de les paralléliser dans un processus.<br />Si cette solution est intéressante pour quelques tâches simples, elle n’est plus possible dans le contexte lourd d’un micro-blogging, d’autant plus que backgroundrb utilisait les threads.<br />Les threads en Ruby sont peu efficaces (<a href="http://en.wikipedia.org/wiki/Green_threads">green thread</a>) et une telle architecture consomme trop de ressources et est l’origine de bugs difficilement décelables.<br />Ces threads permettent d’exécuter les traitements, workers, cependant la communication entre eux n’est pas aisée ce qui complique l’enchainement des traitements.</p>
<p>Backgroundrb a depuis été repris et n’utilise plus à priori les threads. Malgré tout, une architecture robuste à base de file d’attente est préférable.</p>
<p><strong>Hey MoM ! ou le monde des messages brokers</strong></p>
<p>Les Message oriented Middleware sont des architectures qui permettent de faire communiquer des applicatifs de manière asynchrone par le biais d’une file d’attente. <a href="http://java.sun.com/products/jms/">JMS</a> est le MoM le plus connu et un certain nombre d’implémentations libres existent (<a href="http://activemq.apache.org/">ActiveMQ</a>, <a href="http://www.rabbitmq.com/">RabbitMQ</a>)</p>
<p>Parmis ceux-ci, certains implémentent le protocole texte stomp, plus simple et adapté à  un site de micro-blogging. <a href="http://stompserver.rubyforge.org/">StompServer</a> en Ruby en fait parti.</p>
<p>Plus de détails sur les <a href="http://pyfourmond.free.fr/mom.htm">MoM</a></p>
<p><a href="http://code.google.com/p/activemessaging/wiki/ActiveMessaging">ActiveMessaging</a>.</p>
<p>Ce plugin Rails permet de communiquer vers un serveur MoM. Il lance un poller qui interroge le serveur MoM afin de lui transmettre ou lui retirer les messages.<br />Ce plugin est MoM agnostique, on peut interroger aussi bien un serveur ActiveMQ, StompServer et même AmazonSQS.</p>
<p>Noumba a utilisé un certain temps un backend StompServer / ActiveMessaging. C’est une architecture intéressante cependant l’évolution et la qualité du code de ActiveMessaging sont à surveiller de prêt.</p>
<p>Noumba en tant que micro-blog génère une forte charge. Or cette charge devient vite un handicap pour le bon fonctionnement du poller.<br />La conclusion qui s’impose est que Noumba se doit d’être le plus léger possible en effectuant le moins d’opération même par le biais d’un plugin.</p>
<p><strong>Que REST il ?</strong></p>
<p>REST est en effet la solution la plus simple, la plus standard (HTTP) et la moins lourde.<br />Dans sa dernière architecture, Noumba communique en REST avec un backend, le Hub, également en Rails. Les messages sous forme de Hash sont sérialisés puis transmis au backend.<br />Cependant il est toujours nécessaire d’implémenter une architecture MoM cà´té Hub afin d’obtenir un fonctionnement asynchrones.</p>
<p><em>XMPP bien sûr !</em></p>
<p>Quel serveur robuste fonctionne de fait sous forme de file d’attente, tout en étant robuste et utilisant un protocole standard, connu et ouvert ? XMPP bien sûr !<br />Ejabberd est un des serveurs Jabber le plus robuste. Etant en erlang, il peut facilement être réparti sous forme de cluster si le besoin se fait sentir.</p>
<p>Entre REST et XMPP il nous manque le lien Ruby on Rails. <a href="http://trypticon.org/software/actionmessenger/">ActionMessenger</a> vient le combler. Ce plugin Rails permet de transmettre des messages XMPP en utilisant un compte Jabber. Il fonctionne simplement à la manière d’ActionMailer.</p>
<p>Le Hub implémente ce plugin. Il reçoit les objets sérialisés et les transmet à divers comptes Jabber puis répond aussità´t à Noumba au travers de son API REST.</p>
<br /><img alt="" border="0" src="http://feeds.wordpress.com/1.0/categories/fredix.wordpress.com/62/" /> <img alt="" border="0" src="http://feeds.wordpress.com/1.0/tags/fredix.wordpress.com/62/" /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/fredix.wordpress.com/62/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/fredix.wordpress.com/62/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/fredix.wordpress.com/62/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/fredix.wordpress.com/62/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/fredix.wordpress.com/62/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/fredix.wordpress.com/62/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/fredix.wordpress.com/62/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/fredix.wordpress.com/62/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/fredix.wordpress.com/62/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/fredix.wordpress.com/62/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/fredix.wordpress.com/62/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/fredix.wordpress.com/62/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/fredix.wordpress.com/62/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/fredix.wordpress.com/62/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=frederic.logier.org&amp;blog=3098224&amp;post=62&amp;subd=fredix&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://frederic.logier.org/2008/01/22/de-la-repartition-de-charge-en-ruby-on-rails-12/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/0fc8e5d51ed3c0726b2826e8caeb8017?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">fredix</media:title>
		</media:content>
	</item>
	</channel>
</rss>