I find it a bit funny to see concurrency dragged out in a case that's pretty much a perfect example of where Ruby handles concurrency quite decently, both in term of performance, and compact, simple code.
E.g. his URL example:
require 'net/http'
require 'uri'
urls = ["http://www.cnn.com","http://espn.go.com/","http://grantland.com","http://www.newyorker.com/"]
urls.collect do |url|
Thread.new do
response = Net::HTTP.get_response(URI.parse(url))
puts "#{url}: #{response.code} #{response.message}"
end
end.each(&:join)
I'm not so convinced about the performance argument, though. I have a pure-Ruby statsd implementation collecting massive amount of metrics from dozens of servers and calculating averages, feeding time series to Redis, pulling them out and rolling them up back into Redis and to a Couchdb server for longer term storage - it was a "hack" I threw together to replace our (admittedly simplistic) usage of Graphite because Graphite does things to your disks that no hard drive should have to suffer through, and it's not breaking a sweat. I'm sure you can squeeze out a bit more with Go, but for a monitoring setup, if you're not spending most of your time waiting on IO in the kernel, I'm not quite sure what you're doing.
I can see their point on packaging and delivery, though, having had to deal with packaging up three sets of patched gems for three different Ruby versions just in our office environment + servers internally for an internal tool. One of the reasons I'm experimenting with my ahead-of-time/"mostly static" compiler for Ruby is that I'm very much for static binaries.
But we also already do have jruby which makes packaging a lot easier too. Granted I can understand people not wanting to deal with the JVM.
(the short-ish version: make sure you test it with a realistic number of metrics and updates coming in from a realistic number of servers; if it can keep up on hardware you're willing to throw at it, then by all means go for Graphite - it has tons of flexibility that makes it a convenient way to get started. Just expect it to need a fairly hefty amount of disk IO compared to the amount of data you feed it in; the interfaces to Graphite are simple enough and narrow enough you can replace it later if need be, once you know what parts of it you're using)
Graphite essentially assumes that disk is cheap. Even fast disks. And either you're not collecting very big data sets, or you roll up to more rarely updated series very rapidly, or you have a hefty high performance RAID to put it on, or you distribute it over multiple servers.
And for a lot of people it's probably true it'd be cheaper to just buy a suitable set of SSD's and doing RAID10 and/or spreading it out over multiple servers or something than spend time putting together something else.
But if you start seeing yourself needing to scale up substantially, it may be worth thinking through exactly what your needs are and whether you can get away with less by giving up on some of what Graphite offers.
And one outcome of the expectation of fast disk is simply that Whisper - the underlying storage code - makes some assumptions that leads to lots of unnecessary system calls; lots of seeks that turns out to be no-ops, and small reads and writes. All of them are common problems that people tend to be unaware of, but they were painfully obvious after some strace usage. Just fixing that would improve Graphite performance substantially even without ditching functionality, but I really did not feel like digging into unfamiliar Python code to get halfway there when I could just as quickly replace the whole thing.
That assumption of being prepared to use a fast RAID just for some metrics for our dashboards and monitoring didn't sit well with me in general.
And given that we basically just used it to aggregate a set of time series over relatively recent history and don't need most of Graphite's features, and that we don't particularly care if the occasional server crash takes out some of the most recent history with it on one server when we can cheaply run a second collecting most of the same data (and would've ended up with more than that with Graphite), it seemed like worth doing things differently. I basically replaced Graphite (the subset that we use of it) with this, in the course of a few days:
* A tiny statsd implementation in Ruby, that flushes data to a Redis with disk snapshots turned off. It does this every 10 seconds.
* An aggregation script that's a couple of hundred lines that uses key based filtering to aggregate all 10-second data for any five minute interval into a new time series, and again aggregate all five-minute interval data into hourly time series, and so on, and that moves all data older than a few hours to a disk backed CouchDB (could put it into "anything" really)
* A tiny Sinatra app that provides a somewhat Graphite compatible JSON output (but with no attempt to support the Graphite query syntax or server side image rendering.
* A slightly modified Graphene version to build our dashboard on.
It does by no means do everything Graphite does, but what it does, it does without brutally hammering disk. It consume a tiny fraction of the resources Graphite used too.
Note that it is very unfinished, and I'm moving very slowly with it. I'm overdue for posting another blog post or two about it - I have two that just needs editing, and a third halfway done. At current pace I'm realistically hoping to be close to having it self hosted by christmas.
(the big caveat of course being that getting it self-hosted is not the great indication of feature completeness it may sound like, since you get to cop out by not using constructs you don't feel like implementing yet)
To be honest, it kind of veered off course from me just illustrating some low level techniques to quickly bootstrap a very primitive language, to me deciding I wanted to experiment with a Ruby compiler, and so the writing is all over the place.
One of the things that motivates me apart from writing about it, is to try to figure out how dynamic Ruby really need to be, and how much static information you can "recover". On the surface, Ruby is notoriously tricky, because e.g. you can't tell based on local information even what piece of code will be called. E.g:
t1 = Thread.new do
loop do
puts 4 + 5
sleep (1)
end
end
sleep(rand(3))
class Fixnum
def + other
42
end
end
t1.join
It's no fun reasoning about that "puts 4+5" when even integer addition may suddenly stop working after a random interval...
But of course in real life, most of the really hairy nastiness doesn't occur outside of scary examples (I've never seen anyone override stuff in Fixnum to do anything useful, for example), and could easily be documented as unsupported in an AOT compiler, allowing both making the code substantially more static, inferring types in quite large sections of programs eventually, and making it faster (as a side note, there's a Truffle based backend on the way for jRuby that achieves much of this by basically assuming the normal case in lot of situations, but "de-optimizing" when assumptions turn out to be wrong, which will get many of the benefits).
I'm hoping to essentially find space where the compiler can basically declare "mostly safe" default behaviour, such as assuming nobody overrides Fixnum#+, and optimize for that, with switches or pragma's to re-enable "standard" behaviour for those cases where someone is in fact doing something really silly and expecting it to work.
The GIL is there to protect the interpreter's internal data structures, so it's only necessary to hold it while running the interpreter (while executing ruby code) or manipulating interpreter internals.
This is all IO, there's no interpreter execution while you're waiting for an HTTP request to come back so the GIL is released before starting IO, and re-acquired after the request comes back (before resuming execution), same when executing native code which doesn't interact with the interpreter (e.g. image processing implemented in C).
The GIL is an issue when you have multiple interpreted (not native) CPU-bound threads (and depending on the exact strategy it may also be an issue when there's an interpreted CPU-bound thread and multiple IO-bound threads, CPython 3's "new GIL" has/had that problem)
Also MRI has a GIL, GILs are implementation details (with consequences, but still) and other implementations may or may not use a GIL.
MRI has a GIL, but the GIL doesn't block on I/O like HTTP requests (but you'll still be pegged to one core unless you do process forking yourself). JRuby and Rubinius use native threads so you can use all cores with just Thread.new.
Green threads were removed from ruby when 1.9 was released, and we're almost at 2.2 now. Is there some other factor limiting you to one core?
Edit: I just want to say I've done some more reading on the subject and have learned that the GIL does in fact prevent a single process from running on multiple cores. That said, if you wrote a thread-safe ruby application, then just fork it n-1 times (where n = number of cores) and everything should be fine.
What versions of the JVM don't map JVM threads directly to Os threads? I thought green threading was removed a long time ago. I would love more info, thank you.
It definitely is. I've benchmarked similar solutions where the time it took to perform 1000+ POSTs to a REST endpoint was cut in half with every new thread up to about 6 threads. The interpreter performance for any language is going to be a much smaller impediment than the speed of the network.
I think go's approach to readability is comparable to "simple English". Both have a minimal set of words (tokens) and syntax constructs. You lose some witty constructs, and you often find yourself writing in a stupid way.
But, since the code is always plain and stupid, you go through very few mental twists when you are parsing the language. Since code is usually read much more than written, this advantage is a huge.
I agree. I find it far more valuable to have a language with minimal constructs that can be easily read by everyone on the team rather than a language that tries to include every functional bell and whistle. Think I'll start giving Go a serious look.
I'm a huge fan of Go, but moving from Ruby to Go can't be a decision based on performance gains only.
There are a lot of advantages the Ruby language and ecosystem provide. What about programmer happiness and productivity? What about the experienced Ruby devs you surely have? Are they wanting to switch to Go? What about the lack of experience using Go? Are you sure you're going to avoid all the pitfalls of the new and shiny language? Isn't better hardware cheaper than your devs' time?
I'm not saying that your decision is wrong, and probably you have put a lot of thought into it, but most of time, developing software is using tools you know well, that are battle hardened and have been proven to deliver fast.
When the performance you're considering is your own machines, I wholeheartedly agree with you. But when the performance you're considering is that of your customers, the story changes.
The guys who wrote the story are building a monitoring product and their agent code will be installed on all of their customers' machines. Every CPU cycle and byte of RAM should be sacred to them because it takes those resources from their customers' applications.
If a performance gain improves the user experience by either making your product faster or by expanding the ceiling in which you can introduce new features, then yes, the decision can be based on that metric only.
I can appreciate many of the points you made, but programmers are happiest when they can eat, and happy paying customers can put food on the table :)
I shall never understand this "Ruby is slow and difficult to parallelize so let's use Go!" mindset. People knew about the negative aspects of Ruby in advance (i.e. GIL + performance) but still chose to use it even though back then (whenever that was) there were other alternatives - no doubt without the big userbase that Ruby had thanks to Rails.
Now there comes Go which makes parallelism easy(er) but introduces challenges of its own and also has some downsides (i.e. polymorphism) which Ruby solved better - but whatever: "a good developer can always work around such challenges".
So in the end, most people are just following the hype and (IMHO) exchanging one bad apple for another...
Go wasn't an option then. Obviously the languages that WERE options, weren't preferred. The article mentions the authors lack of Java love specifically. There are plenty of other situations where Ruby (or something else) was eventually a bottleneck, and people moved to Java, or wrote their own PHP compiler, or moved to .NET version of ColdFusion... Now that Go's an option, some of the folks looking for speed are going to choose it.
The Scout team developed a tool based on languages they knew, they proved that there was value and demand in the marketplace, and then they decided to implement a performant architecture that can be optimized to handle a larger volume of traffic.
Hopefully this post can help some HN startups from making the mistake of pre-optimization. You're not Google until you are Google.
i've not been paying a lot of attention to golang because it never seemed to solve a need i had, but the recent discussions around the blogosphere about how well it does cross-compilation have got me excited. every time i've tried compiling ocaml code on windows it's been a horrible experience.
I've cross-compiled several programs I've written in Go, and it has been painless.
What is even more exciting is that the binary includes everything your program needs to run, so runtime installations are no longer a barrier of entry.
I've been able to spool up a VM, grab my bins, and do what I need to do in extremely short order. It's great!
"One thing that was getting our nerd juices flowing: Go."
I have a hard time believing that statement. Ignoring whatever "nerd juices" may be, surely something that was entirely designed for dead simple imperative syntax isn't getting them flowing.
You want something different, I get that. What you really want is something statically compiled and cross-platform.
You have a hard time believing that statement, or you'd rather not? 'cause other than calling the author a liar, you're saying it's not possible for someone to be excited about the language. That's awfully presumptuous.
If you're so serious about your web stack and know it is going to need all these fancy and performance features, why build on Ruby in the first place? Why not use something that is performant with a history of successful apps behind it like Java?
It seems that everyone in web is just blindly following whatever flavor of the month tech stack tech bloggers who probably spend more time writing about software than they do writing software are hyping. CRUD apps(IE 90% of web based companies) are probably one of the most solved problems in tech. Why is it that every week there is some post by some inane startup nobody has ever heard of about "why we rewrote our stack from Ruby/Python to X shiny new language that people use buzzwords I probably don't even know the meaning of to describe"? This sort of lines up with that post from yesterday I think about companies just wanting to push out the product as fast as possible rather than caring about the actual quality of it.
Why wait until you need to scale and hire 10 100k/year engineers to rewrite your app into a capable language and migrate it with 0 downtime instead of just doing shit right the first time? Seems like it would be a lot less headache, even if VCs were throwing piles of cash at you. Snapchat, for whatever other things you can fault them technically for like being hacked or whatever, did this right. They built on Java and deployed to App Engine and their application scaled right up.
I can understand extreme cases like Twitter(went from Ruby/Rails to Scala), where it was essentially a trivial CRUD application that eventually got so enormous that language performance features became an issue.
Also I don't understand why web people are so obsessed with "concurrency". How does concurrency help webapps that are just getting a request, doing some stuff with databases(which have their own well implemented concurrency mechanisms) and sending a response back so some tween can look at pics of their friends and send gossipy messages? Unless you are doing something that requires long running processes like video streaming or an online MMO type game, what is the point of it? Oddly the the parent posts' link's application in question is some kind of server monitoring service, so I really don't know why they chose Ruby.
If the only language I know is Visual Basic should I code my web stack in Visual Basic and deploy it to Microsoft Azure?
A surefire way to tell if a developer is lazy(and not in the good way) and takes little pride in their work is if they show an interest in a platform or language solely because its in a language they are familiar with, instead of picking tools that are the best for the job at hand. I mean this reasonably of course, if your app is a reasonably good fit for Ruby and you're familiar with Ruby more power to you. I just don't understand companies developing odd domain specific/specialized software in languages and platforms not meant for it and then complaining that it runs too slow.
Something I'm also unsure of is why almost every company these days seems to be choosing Rails to build on. It doesn't seem to be that they are just picking the language which has the largest available pool of developers available; from what I've read Ruby as a language was completely obscure until Rails came along, and even now isn't used much outside of Rails and the occasional script. Python is really popular and used in many domains ranging from web to scientific computing to interfaces for stuff like Arduinos and has a batteries-included de-facto web framework, Django, so I would imagine there were more Python programmers available, but you see far fewer companies using a Python stack. Java also has lots of battle tested web systems and there is a sea of cheap Java developers(might as well hire a cheap dev if your initial product is going to be a rushed piece of shit), though it is verbose, has a longer dev cycle, and is considered to be for the enterprise type businesses run by Wall Street fat cats everyone is trying to "disrupt".
Is it because Rails is just trendy, or some other reason?
Rails is excellent for building CRUD apps, what it was designed for, and as you say that covers a lot of ground. Plenty of code available for all sorts of common features on the web. I learned Ruby specifically to run Rails, and mostly, Ruby is a lot of fun to write. That it's staggeringly, hideously, Godot-level slow is eventually problematic but by then you've got enough traffic to pay for multiple servers. :)
E.g. his URL example:
I'm not so convinced about the performance argument, though. I have a pure-Ruby statsd implementation collecting massive amount of metrics from dozens of servers and calculating averages, feeding time series to Redis, pulling them out and rolling them up back into Redis and to a Couchdb server for longer term storage - it was a "hack" I threw together to replace our (admittedly simplistic) usage of Graphite because Graphite does things to your disks that no hard drive should have to suffer through, and it's not breaking a sweat. I'm sure you can squeeze out a bit more with Go, but for a monitoring setup, if you're not spending most of your time waiting on IO in the kernel, I'm not quite sure what you're doing.I can see their point on packaging and delivery, though, having had to deal with packaging up three sets of patched gems for three different Ruby versions just in our office environment + servers internally for an internal tool. One of the reasons I'm experimenting with my ahead-of-time/"mostly static" compiler for Ruby is that I'm very much for static binaries.
But we also already do have jruby which makes packaging a lot easier too. Granted I can understand people not wanting to deal with the JVM.