Rails on the Run

Rails experiments by Matt Aimonetti

Browsing Posts tagged merb

Wow, it’s been a while since I blogged. With all the cool kids saying that spending time reading RSS feeds is overrated (see Defunkt’s keynote for instance) I even wonder if people will ever read this post!

Anyways, I have been quite busy preparing courses for classes I gave to a bunch a great Engineers at one of the Fortune 100 companies based in San Diego. I was also planning my big vacation trip to Europe and wrapping up few projects.

However, during my exile overseas, I came to the conclusion that Rubyists don’t scale. Since Twitter became stable again, we don’t hear many people ranting about Rails not scaling anymore. With one of my clients’ app handling around 7 million requests/day I can tell you Ruby/Merb do scale quite well! But ruby developers don’t seem to scale for some reason.

Maybe saying that we(Rubyists) don’t scale isn’t technically correct but that’s basically what one of my client told me.

Let’s go back in time a little bit and follow my client who we will call clientX.

  • ClientX has a great concept and wants to conquer the internet.
  • ClientX hears that Rails is the way to go.
  • ClientX hires a contractor/mercenary/freelancer/guns for hire/consultant (aka Me)
  • Me builds a killer app using Merb (killing framework)

  • ClientX raises loads of $$$

  • ClientX wants to hire a team because Me doesn’t want to become a FTE

  • ClientX and Me look for Rubyists wanting to relocate and get a decent salary
  • ClientX *can’t find someone they consider good enough and who would accept their package

  • Many JAVA guys are available on location and accept lower packages

  • Ruby app gets ported over to JAVA
  • Me sad :(

So is it really the Rubyists’ fault if we don’t want to relocate and only accept higher packages? Should I blame Obie for telling people to charge more and teaching how to hustle? Or should we just tell clients that it’s time to get used to working remotely?

Honestly, I don’t think any of the above explanations are valid. Ruby is the new/hot technology and very few people have the skills and experience to lead major projects. These people make a good living and enjoy their “freedom” and dream of building their own products. Most of them/us value their work environment, family and are reluctant to move.

scale

At the same time, companies do need people locally(at least a core team) and can’t always afford the cool kids.

ClientX, quite frustrated by the whole hiring process told me once: “you Ruby folks are too unavailable and difficult to work with! We need a committed team that actually cares about the company/product.”

That hurts when you worked hard on a project and just can’t satisfy the client by finding guys willing to relocate and work for them. It gets even more painful when your code gets entirely ported over to JAVA!

But at the same time I understand ClientX’s motivation, PHP guys are cheaper, JAVA guys are more available, why in the word did we go with Ruby and are now struggling finding people?

Once again, there is positive and negative side in everything, by choosing Ruby and a “great contractor” ClientX was able to catch up with the competition and even pass them in no time. They quickly raised good money and got everything they needed to become #1. I don’t believe it would have been possible to do the same thing so quickly with JAVA for instance. However choosing a cutting edge technology means you need to look harder for talented people.

It’s too bad the code gets rewritten in a different language but at the same time, I do my best to facilitate the process and to keep a good relation with my client. There was nothing personal in the decision, it’s just too bad we were not able to keep on using the latest/coolest/awesomess technology available :)

To finish on a positive note, here is the solution to scale your Ruby task force provided to you by the #caboose wisdom:

Based on my conversations with other #caboosers who hire other devs, the word in the street is that you just need to get one or two great ruby guys (who will probably cost you a lot) and find a bunch of smart people to train. You’ll end up with an awesome team of scalable rubyists ;)

I realized I haven’t updated this blog in a while. Here is a quick update on what’s happened and on things to come:

  • RailsConf 08. Great conference, probably my last Rails Conf though. I’ll be in Orlando for Ruby Conf 08 and I’ll focus on 1 or 2 local conferences (probably mountain west and another one).

  • MerbCamp 08 in San Diego this Fall organized by SD Ruby. Details are not finalized yet but Yehuda Katz announced it during his Merb talk at RailsConf.

  • Moved this blog to a new Joyent accelerator with git support and finally have the possibility to use Ambition! (planning on moving from Mephisto to Feather)

  • Launched a client’s Merb app and getting around 3 million hits/day. Merb is just awesome. (more info when the client’s app gets out of beta)

  • I’ll join Gregg Pollack from http://railsenvy.com/ during Qcon and take part in the Ruby for the Enterprise track. My talk will focus on Merb usage in real life.

  • Renamed my github username, new repo url: http://github.com/mattetti (sorry about that)

Ruby is sexy, Ruby is cool and its metaprogramming potential offers some really cook features. However you might not realize that your cleverness is slowing down your code.

Today I was working on cleaning up merb_helper a Merb plugin that brings a lot of the stuff Rails developers are used to. In Merb we aim for speed and try to avoid magic.

merb_plugin didn’t receive a lot of love from the main contributors but few features were added by different contributors and the code became hard to maintain.

Looking at the code I quickly found this bad boy:

(Old Merb Time DSL using metaprogramming)

module MetaTimeDSL

    {:second => 1,
     :minute => 60,
     :hour => 3600,
     :day => [24,:hours],
     :week => [7,:days],
     :month => [30,:days],
     :year => [364.25, :days]}.each do |meth, amount|
      define_method "m_#{meth}" do
        amount = amount.is_a?(Array) ? amount[0].send(amount[1]) : amount
        self * amount
      end
      alias_method "m_#{meth}s".intern, "m_#{meth}"
    end

  end
  Numeric.send :include, MetaTimeDSL

The above code looks awful to me and I decided to rewrite it a way I thought would be more efficient:

 module TimeDSL

    def second
      self * 1
    end
    alias_method :seconds, :second

    def minute
      self * 60
    end
    alias_method :minutes, :minute

    def hour
      self * 3600
    end
    alias_method :hours, :hour

    def day
      self * 86400
    end
    alias_method :days, :day

    def week
      self * 604800
    end
    alias_method :weeks, :week

    def month
      self * 2592000
    end
    alias_method :months, :month

    def year
      self * 31471200
    end
    alias_method :years, :year

  end
  Numeric.send :include, TimeDSL

To make sure I was right, I run the following benchmarks:

require 'benchmark'
TIMES = (ARGV[0] || 100_000).to_i

Benchmark.bmbm do |x|

  x.report("metaprogramming 360.seconds") do
    TIMES.times do
      360.m_seconds
    end
  end

  x.report("no metaprogramming 360.hours") do
    TIMES.times do
      360.seconds
    end
  end

  x.report("metaprogramming 360.minutes") do
    TIMES.times do
      360.m_minutes
    end
  end

  x.report("no metaprogramming 360.minutes") do
    TIMES.times do
      360.minutes
    end
  end

  x.report("metaprogramming 360.hours") do
    TIMES.times do
      360.m_hours
    end
  end

  x.report("no metaprogramming 360.hours") do
    TIMES.times do
      360.hours
    end
  end

  x.report("metaprogramming 360.days") do
    TIMES.times do
      360.m_days
    end
  end

  x.report("no metaprogramming 360.days") do
    TIMES.times do
      360.days
    end
  end

  x.report("metaprogramming 360.weeks") do
    TIMES.times do
      360.m_weeks
    end
  end

  x.report("no metaprogramming 360.weeks") do
    TIMES.times do
      360.weeks
    end
  end

  x.report("metaprogramming 18.months") do
    TIMES.times do
      18.m_months
    end
  end

  x.report("no metaprogramming 18.months") do
    TIMES.times do
      18.months
    end
  end

  x.report("metaprogramming 7.years") do
    TIMES.times do
      7.m_years
    end
  end

  x.report("no metaprogramming 7.years") do
    TIMES.times do
      7.years
    end
  end

end

 Rehearsal ------------------------------------------------------------------
metaprogramming 360.seconds      0.130000   0.000000   0.130000 (  0.133164)
no metaprogramming 360.hours     0.050000   0.000000   0.050000 (  0.042655)
metaprogramming 360.minutes      0.130000   0.000000   0.130000 (  0.133327)
no metaprogramming 360.minutes   0.040000   0.000000   0.040000 (  0.042401)
metaprogramming 360.hours        0.140000   0.000000   0.140000 (  0.134312)
no metaprogramming 360.hours     0.040000   0.000000   0.040000 (  0.043125)
metaprogramming 360.days         0.130000   0.000000   0.130000 (  0.134949)
no metaprogramming 360.days      0.050000   0.000000   0.050000 (  0.043745)
metaprogramming 360.weeks        0.130000   0.000000   0.130000 (  0.135581)
no metaprogramming 360.weeks     0.050000   0.000000   0.050000 (  0.043544)
metaprogramming 18.months        0.130000   0.000000   0.130000 (  0.135234)
no metaprogramming 18.months     0.050000   0.000000   0.050000 (  0.044354)
metaprogramming 7.years          0.140000   0.000000   0.140000 (  0.144062)
no metaprogramming 7.years       0.050000   0.000000   0.050000 (  0.044392)
--------------------------------------------------------- total: 1.260000sec

                                     user     system      total        real
metaprogramming 360.seconds      0.130000   0.000000   0.130000 (  0.132567)
no metaprogramming 360.hours     0.040000   0.000000   0.040000 (  0.042777)
metaprogramming 360.minutes      0.140000   0.000000   0.140000 (  0.132554)
no metaprogramming 360.minutes   0.040000   0.000000   0.040000 (  0.043193)
metaprogramming 360.hours        0.130000   0.000000   0.130000 (  0.133027)
no metaprogramming 360.hours     0.050000   0.000000   0.050000 (  0.042613)
metaprogramming 360.days         0.130000   0.000000   0.130000 (  0.138637)
no metaprogramming 360.days      0.050000   0.000000   0.050000 (  0.043213)
metaprogramming 360.weeks        0.130000   0.000000   0.130000 (  0.134049)
no metaprogramming 360.weeks     0.040000   0.000000   0.040000 (  0.043713)
metaprogramming 18.months        0.140000   0.000000   0.140000 (  0.134941)
no metaprogramming 18.months     0.040000   0.000000   0.040000 (  0.043980)
metaprogramming 7.years          0.150000   0.000000   0.150000 (  0.143389)
no metaprogramming 7.years       0.040000   0.000000   0.040000 (  0.044585)
 0.136591)

The metaprogramming version of the same implementation is almost 3 times slower!

Moral of the story: be careful when using metaprogramming, you might end up slowing down your code considerably.