16 ms·
Faster Ruby: Thoughts from the outside
- chrisseaton 4y ago> If 90% of the time in an application is spent in database calls This is what a lot of people say, but I'm not sure that this is the case. I think apps spend about 50-70% of their time in compute in the interpreter, at the low end. People don't often make metrics from their apps public, but here's one example from a web site that renders text - so it's reading from the database and not doing some unusually computational task with the result https://genius.com/James-somers-herokus-ugly-secret-annotated https://genius.com/James-somers-herokus-ugly-secret-annotate....
- zasdffaa 4y ago"if" It doesn't say they do.
- suction 4y agoAs for anecdotal evidence seen in a consultancy function: You are wrong in assuming that companies always hire database experts or data scientists to write their db queries who know how to not make the db the bottleneck. In reality you'll find developers who claim "we can't use Ruby here because it's too slow!" while they're unaware that the reason their page needs seconds to load is because of hilariously inefficient queries to the db.
- jiggawatts 4y agoIn my consulting experience, I'm shocked if I find that inefficient queries aren't the root cause of poor performance. In fact, thinking about it, I don't think it has ever happened. I always look at the DB layer first because it's virtually guaranteed that someone wrote a "SELECT * FROM MassiveTable" and added it to the common header code used by every page.
- vidarh 4y agoMatches my experience too, both consulting and in-house as the person who'd be the first to even consider looking at the database query logs or running "explain" on queries. A reason for ORMs is that a lot of developers fear the database (EDIT: not the only reason, to be clear; I love and use ORMs). A result of ORMs is that a lot of developers think they can avoid understanding the database.
- rco8786 4y ago+1 to this. I've lead performance optimization on enough real-world problems to be conditioned to just go straight to the database access patterns from the start...it's always there. Modern languages, including Ruby, are all plenty fast enough computationally for the vast majority of business workloads that aren't Google scale. When things slow down...it's the database or something similar like N+1s calling external APIs.
- brightball 4y agoYep. Agree here entirely. You can even be tricked into looking at heavy write load due to temporary tables being created for huge reads. The more people seem to focus on mastering their language or framework, the more they seem to forget that there’s nothing more important than SQL.
- jasonwatkinspdx 4y agoI'm pretty sure the highest value per character code I've written so far was a 10 line monkey punch back in the rails 3 era that would crash the app if you tried to use an active record query without a limit or with too high a limit.
- the_sleaze9 4y agoHehehe, that reminds me of a similar active record monkey patch at an old job, the name of which was used as a swear word everywhere except where that particular engineer was present.
- skunkworker 4y agoI agree, optimizing db calls in my experience has been low hanging fruit. But (in rails) going through and making your own poro + active model objects can give significant speed increases, especially in cases where the Rails overhead is too high. Object instantiation and database serialization/deserialization seem to be a pain point that gets overlooked more than people realize. But in this case I’m preaching to the choir on ruby app optimization. Do you forsee projects like Rails needing to be rewritten in order to be more favorably JIT and interpreted by truffleruby & ruby jit?
- faebi 4y agoMore than once I run into db serialization performance issues. Postgres reports all nice and fast, yet requests are slow. It took me a while to figure out that activerecord was busy converting my data into one large query string, and then busy sending that string over the wire. The issue was a field using postgres jsonb and I was filling in a large array of text lines in some cases. Workarounds are easy but I never pinned down why it would take multiple 100‘s of milliseconds to serialize into sql.
- jillesvangurp 4y agoI haven't seen too much ruby in the wild lately. Most of my customers use other stacks (python, go, php, jvm, etc.). I default to using Kotlin these days which gives me a nice compromise of an expressive language, nice frameworks, and excellent scaling abilities and performance. But I deal with whatever comes on my path. Mostly, throwing money at the problem and scaling horizontally is a perfectly acceptable solution and when it isn't, using ruby is probably not the way forward either and you might want to consider more performant stacks. That being said, unless you are doing very strange things, you should be able to make ruby perform acceptably. Something, I've observed in other people's ruby code is that it sucks you into doing sub optimal things. Rails and orm can lead people into doing things that just result in a lot of needless database traffic. That's not a ruby problem but just poor design. I've seen people get into trouble with java/hibernate as well. Solution: optimize your queries and database schema and think about what kind of indices you need. Another common issue is people doing silly things with parsing and processing the same things over and over again. Caching helps, good algorithms help, etc. This too is not really a language problem but a design issue. In terms of percentages, most ruby apps are single threaded and use blocking IO; which is why you fire up many ruby processes on a server. So, whenever it is dealing with IO, which should be most of the time, it shouldn't be using much CPU. That's why you can have many more ruby processes than cpu cores, most of those processes would be idling or blocked on IO most of the time (orders of magnitude slower than any computation). This is of course not a great use of memory and you are likely to exhaust that sooner than the cpu. If you can load test, you should be able to find the limit.
- ryanbrunner 4y agoI've worked with Ruby for 12 years and I've never once had Ruby itself be the cause of poor performance. It's almost without exception the database. It might be fair to say a well-optimized route spends 50-70% of it's time in the interpreter, but for non-optimized routes that's far too high.
- boesboes 4y ago>What kind of speedup is available for big Rails applications? If 90% of the time in an application is spent in database calls, then there’s little opportunity for improvement via JIT technologies. This has always been the premise for enabling multi-threading on RoR applications despite the GIL. The convertional wisdom is you spend 80-90% of time waiting for the DB and other IO. After 15 years of hosting rails applications, I'm pretty sure that is not generally true for real world applications. It depends on the application obviously how this works out, but for a larger, complex saas app I work on it works out to 64% ruby time for web requests. For another application, that does a lot more external calls, it's 52%. So raw performance of ruby can have a pretty significant impact on real performance for RoR applications. To the point where a faster CPU (new gen on AWS) improved response times by 15-20% for us.
- faebi 4y agoIt's one reason why ViewComponents exist. It's pretty easy to render a small partial too often and generating a lot of overhead by doing that. It's not really obvious that partial rendering is a magnitude slower.
- byroot 4y agoTo be precise, it's not so much the partial rendering that's slow, but the partial lookup. When you do `<%= render "something" %>` Action View has to do a stupid amount of work to figure out which file it has to render. However, for having looked at it recently, I doubt we can improve that much without breaking backward compatibility.
- pantulis 4y agoI would have thought that the file rendering solution for partials would be cached in production environment.
- byroot 4y agoIt is to some extent, the problem is in the semantic <%= render "foo" %> may end up rendering different partials based the context it's invoked from, e.g. current locale, etc. So the cache can't just be a simple hash. Additionally, once you have identified the template to render, if some locals are passed, you then need to lookup the compiled template for that specific combination of parameters, because `render "foo"` and `render "foo", name: "George"` end up compiling two versions of the same template. For that later issue we just introduced a way to declare what locals a template accept which should allow us to improve that part: https://github.com/rails/rails/pull/45602 https://github.com/rails/rails/pull/45602
- thdespou 4y agoDon't underestimate the cost of ORMs. The serialisation/parsing to models can make a significant performance impact.
- aardvark179 4y agoDon’t rely on estimates and guesses, profile it and check.
- mekster 4y agoIf you care for performance, start graduating ORM and learn to write performant SQL yourself.
- higeorge13 4y agoIn 2 past jobs as a non ruby person in ruby shops i was trying to convince them to write raw sql queries instead of relying on activerecord, but with no luck. I won’t argue about performance but most of the times didn’t even know what and how many queries were being generated..
- cutler 4y agoBut ... Rails console.
- weatherlite 4y agoThere has to be a really good reason for that. It's just like joining a Spring shop and telling everyone to not use JDBC (or whatever the name of the ORM in Java land)...that's not gonna fly well usually. For a good reason imo.
- higeorge13 4y agoThe good reason was that queries were not optimal and when we came up with optimal ones which were not following the access pattern or syntax that activerecord introduces, then the excuse was “…but activerecord…”. If i was a dev i would appreciate some data guy’s opinion in queries, indices, etc while my db crashes every now and then, but it’s either a power play/politics thing to ignore the new guy or the ‘i know activerecord/hibernate/kafka/whatever’ thing and i won’t bother sith anything else that will solve the problem.
- aardvark179 4y agoSince several people are chiming in with suggestions for what might make rails slow I will throw in my two pence. Profile things, and make sure you understand the results of the profile (because profilers can lie to you in a whole variety of ways). When I last checked on TruffleRuby some time ago the slowest thing on a small rails app was all the layers of framework and all the points at which it might log things, while the actual database access and the use of that data was pretty quick. This might not continue to be true for larger applications, but it probably means there is a high minimum amount of work that must be done per request, and it’s so deep on the stack that it’s unlikely to be optimised away. The situation may have improved, we’ve done a lot of work on TruffleRuby since then and completely changed the inlining strategy at least once, and the method lookup and dispatch. But the profiler has also changed significantly so the way it lies will also be different now.
- qikInNdOutReply 4y agoProfiler will not show the load time till all the framework and the includes are loaded though. Which is were alot of time is spend. Just having that whole dependency tree parsed in and hashed up. Wish there were more way to reduce libraries to subsets and not loose that on update.
- aardvark179 4y agoSure it will. It didn't dominate the profiles I was looking at because I was measuring peak performance so had waited until after everything had been loaded and had time to be JIT compiled etc. but it's quite common to see an extremely significant chunk of time used by initial requires and configuration.
- byroot 4y ago> initial requires and configuration That happens at boot time, unless somehow you disabled eager loading or are not running in production mode.
- izietto 4y agoI think that one underrated point about web apps performance is JSON serialization for non-JS languages. JavaScript is obviously fast with that, but non-JS languages exposing API, like Rails in a separated FE/BE architecture, spend much time transforming data into JSON, and it doesn't seem stressed enough to me.
- chrisseaton 4y agoWhy is JS faster than Ruby at JSON serialisation/deserialisation? The algorithm and data structures seem exactly the same in the two languages to me. In fact Ruby should be faster as it has simpler arrays without things like holes.
- izietto 4y agoI guess that, since JSON is Javascript Object Notation, it should be faster for Javascript to serialize/deserialize it, but it might be just a psychological bug of mine. Anyway, my main point wants to be the fact that JSON serialization/deserialization seems an area of improvement on API web apps. As a Rails developer it requires a lot of effort to deal with that part of the application performance. Just providing a list of 50 db records with 10 attributes each, with some transformations (let's say that e.g. 4 attributes are virtual and some keys are renamed), add an important overhead to the response.
- onlyrealcuzzo 4y agoMost interpreted languages have a C library that handles JSON serialization and deserialization.
- izietto 4y agoYes, but are they as fastest as possible? Take Ruby. One of the most common improvements you do as Rails developer to a Rails web app is adding `oj` gem, which replaces the default JSON ser/deser with a more performant one. But it doesn't come by default with Rails. Can you guess how many Rails web apps performing JSON ser/deser don't use it? I'd be curious about that. Moreover, it's really hard to find a presenter library oriented to performance. There are wonderful DSLs, but none of them seems optimized to speed, IMHO.
- girishso 4y agoAfter almost 4 years, I recently started working on a small Rails app. Typical Rails responses used to take hundreds of milliseconds before, now it takes tens of milliseconds (with latest versions). I was not expecting that at all. Together with turbo and stimulus, responses seem almost instant. Kudos to the Ruby and Rails teams. I am definitely excited about Ruby/Rails again!
- bigDinosaur 4y agoWhat is a typical response? You mean an app that minimally read from the DB, or just to return any HTTP response at all? I assume the first.
- girishso 4y agoYes the first, minimally read from the DB and send data in response.
- WJW 4y agoNot OP, but looking at our metrics the first response I picked was a load for a blog article. It does 3 db queries (because it is not very well optimized, the blog is not super high traffic), one for the tenant, one for the actual article and finally one for the (image of the) team member who wrote the article. This endpoint runs in ~26 milliseconds average, 6 ms of which are in ActiveRecord and 19 ms are in ActionController. I assume the remaining millisecond is an artifact of rounding in the monitoring and/or some overhead in other bits of the framework. Most other endpoints are pretty similar, though there are some internal queries for reporting etc that take much longer of course.
- ryanbrunner 4y agoAt the point that you have large or lots of queries on the DB or something similar, you really aren't talking about Ruby performance any more, and you'd be dealing with more or less the same problem in any language. But in my experience, here's some data from one of our most hit routes: Basically this route will do 5 fast DB queries (load a single record by PK), a couple of Redis lookups (one of which is returning fairly large keys). Our mean performance is 44 ms, 95th percentile is 80 ms. Almost all of that is taken up by DB / redis, actual ruby execution is not quite a rounding error, but would not be an optimization target. The controller itself is fairly typical - 3 or 4 before actions, a decent amount of object instantiation, some branching / logic. It doesn't involve service classes and just models. Ruby / Rails done in a fairly "vanilla" way can be pretty performant. We've had some performance issues with our app, but Ruby itself has never been the problem.
- transfire 4y ago“Just throw a compiler at it!” If you really want a speed bump then that’s exactly what you want to do. And I think Ruby’s overlooking a golden opportunity to join forces with Crystal.
- basilgohar 4y agoThis is not true at all. Unoptimized, sloppy code will still leave a lot of performance on the table, whether compiled or interpreted. You might speed it up, but you'll still have problems scaling and your external services will still be hit just as hard (e.g., excessive queries vs. intelligent ones).
- Banana699 4y agoI generally agree with you that compiling to machine code is not a magic wand, code that does more is always slower than code that does less in a fair fight. But interpretation adds additional overhead to make slow code even slower, the more complex\unoptimized the interpreter the more it adds overhead. It's good for a language to have a baseline expectations of "Don't make things worse", shit code will always be slow, but at least it's not made slower by things outside of the developer's responsibilities.
- pjmlp 4y agoThat is what a JIT is, a dynamic compiler.
- jrochkind1 4y ago> What kind of speedup is available for big Rails applications? If 90% of the time in an application is spent in database calls, then there’s little opportunity for improvement via JIT technologies. This is written as speculation of course, but it matches some long-time "conventional wisdom", that most Rails apps spend most of their time on I/O waiting for something external. That may have been true once, but i don't believe it's true anymore. My Rails apps now, when things like "n+1 database query problems" are properly eliminated, all spend more of their time on CPU than I/O, 60-80%. (I think most of that CPU time is ActionView rendering. Unsure about CPU parts of ActiveRecord, that aren't waiting on DB, but converting db results to in-memory AR objects etc). When this comes up and I ask around on social media who else has actually looked at their app and can confirm or deny, almost all who respond agree. I would be curious to see here too. Definitely some more systematic rather than anecdotal investigation is called for. But I think it's time to stop repeating that it's likely that a Rails app is I/O-bound rather than CPU-bound, my experience leads me to think the reverse is currently likely. [*edit* after I wrote this comment, I noticed multiple other comments in this thread saying basically the same thing. OK, so. Can we put this rumor to rest?]
- technion 4y agoMy preference at this point is rail's in api mode with a separate javascript fronted. I haven't had had to scale enough to really see it, but I often see rendering discussed as CPU heavy and I do wonder how much it helps that all my rendering is the OJ gem.
- aantix 4y agoWith proper nested catching, you can see response times in 50ms. No need to over complicate the views with a separate js front end.
- xoac 4y agoIf you go this route you end up having a drastically more complicated front end, and usually without the speed benefit. Unless you really need a SPA I would stay away.
- 4y ago
- drewbug01 4y agoI would summarize the article as: - Ruby would benefit from object shapes and JIT compiling - Good news, both of those are being added / improved - The jury is out about C extensions I hope ruby has a renaissance in the next few years. People can and will howl about its performance, but it’s far faster than it was even a few years ago, let alone 10. The changes mentioned in the article, the async gem, and ractors are all exciting things that could push it even faster and unlock previously unavailable design patterns. Byte for byte, it’s one of the nicest languages (in my opinion of course), and for many use-cases Rails is like having developer superpowers. I hope renewed excitement leads to more investment from folks, because it’s honestly just such a nice, ergonomic ecosystem to use.
- deleted 4y ago[deleted]
- UncleOxidant 4y agoI'm trying to remember why Ruby fell out of favor ~10 years ago. I don't think it was all down to performance because it was mostly on par with Python. I seem to recall that it came down to community practices that were a little too freewheeling - widespread monkey patching, etc. and these had become reflected in Ruby's flagship product (Rails). (I used to do a lot of Ruby programming in the 2001-2010 timeframe and like the language, but it's mostly been Python since then)
- fny 4y agoJavaScript took over the world. Then data science became a thing.
- UncleOxidant 4y agoSure, but Ruby could've been a player in data science as well. But for some reason Python won handily in that space. Was it all down to numpy, scipy, etc?
- 4y ago
- ksec 4y agoI am pretty sure Shopify is still hiring. May be the author wants to work on next gen Ruby VM as well?
- aardvark179 4y agoI can't speak for Chris over at Shopify, but Oracle is looking for people to join us working on TruffleRuby.
- Dowwie 4y agoIsn't Elixir a "faster ruby"?
- nazka 4y agoYes but it has its own ecosystem. It can't use gems from Ruby. The main selling point for this is because they use the Erlang VM and that thing is uncrashable. Thought from the start to be fault-tolerant and distributed. So boosting the speed of Ruby itself is always a good news for everybody using it.
- jrochkind1 4y agoNot really, it's a pretty different language that in some ways looks superficially like ruby. To start with, ruby is object-oriented, everything in ruby is an Object of a Class; while Elixir is functional, and doesn't even have Objects and Classes. But sure, other languages than ruby certainly exist, some of which will be faster, in various use cases/contexts!
- pantulis 4y agoNo it's not. You could describe Elixir, with a lot of caveats, as a "ruby-fied Erlang" because a lot of the niceties are there. It's faster than Ruby, but it's totally not Ruby. Perhaps the "faster ruby" moniker could be arguably assigned to Crystal.
- exabrial 4y agoAnother thing though: If at least one of your constraints are speed, a dynamically language probably shouldn’t be your first choice. If you need to maintain memory safety, Java or Rust would be more applicable. That being said, once you have a codebase written in a certain language, you might just be stuck there due to hired talent being familiar with it. I don’t think I’ve really seen a company pull off a polygot move cheaply. Instead, a parallel move to something like Crystal may require minimal re-training. … just thoughts I would have if I were in charge of investing R&D time at a large org like the ones mentioned in the article.
- eropple 4y agoCrystal is only superficially similar to Ruby and doesn't seem to get you very far if you're a Ruby shop. It's not a bad language, but it is different, and there's probably more value to be had not trying to adhere to some similar-ish syntax and adopting something more mainstream.
- petepete 4y agoI agree, I think the fact that the standard libraries are really similar is a huge help for people transferring.
- pizza234 4y agoRust is overkill for projects that don't need very high scale, also because of the paradigm shift of the frameworks. Golang is a more conventional choice, for the opposite reason. Crystal is not ready for large scale deployment, which includes the fact that is not well supported as more widespread languages (e.g. on AWS).
- lgreiv 4y agoI agree insofar as that there is no dedicated runtime for crystal in AWS Lambda, but it can well be used there or in fargate containers. I wrote a (proof of concept) minimal runtime for Lambda some time ago [1] and had pretty decent results. Given that both fly.io and Heroku have working buildpacks for Crystal/Lucky, I’d say you can serve pretty heavy load before you’d need to reach for more specialized machines. Of course, that depends on the nature of your application as well. Maybe my understanding of 'large scale' is different from yours, though. [1] https://github.com/lagr/aws_lambda_runtime https://github.com/lagr/aws_lambda_runtime
- citrin_ru 4y ago> If 90% of the time in an application is spent in database calls, then there’s little opportunity for improvement via JIT technologies. Even if it is true (for a given application) it doesn't mean that you don't need to optimize CPU usage. It's just mean that you may run out of RAM first (if you don't use async I/O) before you'll saturate CPU. Or may not. First, in most non small projects database and app (e. g. Ruby) layers usually either use separate hardware or shared HW but we can account hardware cost for each of them. If DB layer uses much more HW than app layer then yes, it makes little sense to reduce CPU usage by Ruby. But usually an app layer is bigger, especially if a language like Ruby is used. Let's focus on the app layer assuming our DB layer works well (request latency can be non negligible - if it is stable under any load we have - that's fine). Assume we need N servers with Ruby - let's look what prevents us from using N/2 servers and saving money: for web apps it is either CPU or RAM (disk I/O handled by DB layers, 10Gbit network bandwidth should not be a bottleneck in most cases). For contemporary server hardware and web apps in my experience it is more common to saturate CPU before you'll run out of RAM so by reducing CPU usage you can save money. But your mileage may vary.
- cercatrova 4y agoSomewhat related, what do people think of Crystal (a compiled language with types with Ruby-like syntax)? How is it for web applications? Is there a Rails equivalent for it yet?
- WJW 4y agoCrystal is fine, we wrote a microservice in it for a particularly CPU-intensive task and it performed as promised. It definitely loses more and more Ruby-likeness as you start optimizing though. There are a few Rails-like frameworks but they all lack what Rails has: critical mass. Rails has over 4500 contributors and quite a few people working on it full-time at companies like Github and Shopify. Almost everyone in the Ruby community knows and uses Rails, and almost every gem available takes Rails into consideration. The Crystal web framework with the most contributors seems to be Amber, with less than 100 contributors. I don't mean to disparage Crystal, as I like the concepts that the language is built on, but at the same time I don't see it catching up to the Rails juggernaut anytime soon.
- dwheeler 4y agoI think implementing shapes is very likely to significantly improve the raw compute speed of Ruby applications. That said, if you're implementing an application in Rails, there are a number of low-hanging fruit that will significantly improve performance. In most cases they'd apply in any language, but since it's trivial to get "something running" in Rails, it's easy to forget the basics for performance. E.g.: * Index your database for all queries that matter. * Use caches. In the case of Rails, use partials; they are amazingly effective at improving performance. * Use a tool to detect N+1 queries, and fix them. * When querying with an ORM (ActiveRecord in Rails), request just the specific fields you need instead of "downloading everything" into the object. Getting unnecessary fields increases the database response, the memory use, and the garbage collection effort, and all of that unnecessary work eventually adds up. In general, you can get a lot of performance improvements by doing only what needs to be done and nothing else. Caching to avoid repeated work, requesting only the data you need, etc., can provide a lot of performance improvement and often require relatively little effort.
- Syzygies 4y agoI love Ruby so much as a scripting language, I ask myself why I don't just use it as an "acceptable lisp" [1] for my research, rather than Haskell. Haskell is more easily parallelized than any other language I've experienced. Alas, my experiments with Ruby 3.1 parallel extensions flunked. Who cares about single core speed? I was always put off by Rust syntax (it's a fly-pollinated flower, to attract C programmers), but I'm starting to get the genius in its machine model. Learning Rust forces one to understand what's happening at the machine level, but offers better control as a reward. Its expressiveness is a lot closer to Ruby than I had imagined, it won't put me back in the C99 stone ages. So my provisional answer is that Rust is a Faster Ruby. [1] http://www.randomhacks.net/2005/12/03/why-ruby-is-an-acceptable-lisp/ http://www.randomhacks.net/2005/12/03/why-ruby-is-an-accepta...
- tomphoolery 4y ago> What kind of speedup is available for big Rails applications? When working with both ActiveRecord and Mongoid, I noticed that the vast majority of time spent outside of the database is in runtime type-checking and type conversion. That is, when an object is passed in from the DB, it takes the model a bit of time to get all of the data set up correctly. This doesn't become a huge problem unless you have a lot of embedded data in the record, which tends to happen more frequently in MongoDB.