Hacker News new | ask | show | jobs
by pqdbr 2135 days ago
Some of the listed optimizations were:

> We carefully vet what we eager-load depending on the type of request and we optimize towards reducing instances of N+1 queries.

> Reducing Memory Allocations

> Implementing Efficient Caching Layers

All of those steps seem pretty standard ways of optimizing a Rails application. I wished the article made it clearer why they decided to pursue such a complex route (the whole custom Lua/nginx routing and two applications instead of a monolith).

Shopify surely has tons of Rails experts and I assume they pondered a lot before going for this unusual rewrite, so of course they have their reasons, but I really didn't understand (from the article) what they accomplished here that they couldn't have done in the Rails monolith.

You don't need to ditch Rails if you just don't want to use ActiveRecord.

2 comments

(contributor here)

The project does still use code from Rails. Some parts of ActiveSupport in particular are really not worth rewriting, it works fine and has a lot of investment already.

The MVC part of Rails is not used for this project, because the storefront of Shopify works in a very different way than a CRUD app, and doesn’t benefit nearly as much. Custom code is a lot smaller and easier to understand and optimize. Outside of storefront, Shopify still benefits a lot from Rails MVC.

I’ll also add that storefront serves a majority of requests made to Shopify but it’s a surprisingly tiny fraction of the actual code.

Out of curiosity, why continue to implement in ruby? If milliseconds are important as you mention, interpreted languages will always be slower.
They have a very good thing going. Perhaps there is no great reason to bite off so much at one time. They can take their time and do that later if it makes enough sense. I would expect it would require a very substantial effort to rebuild their platform in a different language.

If you're 75/100 of where you want to be on performance, it can be easy to lose immense amounts of time chasing a 95/100 type ideal performance outcome when you can maybe far more easily get to 90/100 by making eg straight-forward caching improvements to what you already have and not have to rewrite all of your code.

Good enough is almost always underrated in tech. People destroy opportunity, time, money, and entire businesses chasing what supposedly lies beyond good enough.

John Carmack has a good example of this in his Joe Rogan interview [1], in how id Software burned six years on Rage, making incorrect (in hindsight) choices that involved trying to do too much. He regrets his old standard line and approach that it'll be done when it's done. He wishes they had made compromises instead and shipped Rage several years earlier. That's a pretty classic storyline in all of tech, taking on far too much when 85% good enough would have worked just as well most likely.

[1] https://youtu.be/udlMSe5-zP8?t=8630

very good point

very good example

>I’ll also add that storefront serves a majority of requests made to Shopify but it’s a surprisingly tiny fraction of the actual code.

I am guessing that small pieces of code will the target for TruffleRuby.

> because the storefront of Shopify works in a very different way than a CRUD app

Any interesting/successful patterns you can share/resources you can share on said patterns?

“Not a CRUD app” isn’t a design decision, it’s just that storefront is almost entirely read-only, and the views are merchant-provided Liquid code. Most of a shop's data can be accessed on any page; data dependencies are in large part defined by the view, not the controller.
Oh I Know. As a Rails developer for 10 years, I'm always interested what patterns are developed when straying away from what the original guts of Rails.
Shopify's storefront is based around a liquid renderer instance. If you look up how objects are added to the liquid context that is pretty similar to the overall pattern (or at least was back when I worked there, hi pushrax :)
Yep, the main idea is to set up the liquid interpreter with the right variables/methods and the right liquid templates, and evaluate the result. There’s a lot of code that runs around that, but the path-specific code is quite small.

Hello :)

If it’s rail, can you just expose the rails cache object in liquid? Give control to merchants. That would yield bigger speed improvements.
Someone replied but deleted right when I was posting this answer, so I'm replying to myself:

What I didn't understand was why the listed performance optimizations couldn't be implemented in the monolith itself and ensued the development of a new application, which is still Ruby.

In a production env, the request reaches the Rails controller pretty fast.

I know for a fact that the view layer (.html.erb) can be a little slow if you compare it to, say, just a `render json:`, but if you're still going to be sending fully-rendered HTML pages over the wire, the listed optimizations (caching, query optimization and memory allocation) could all be implemented in Rails itself to a huge extent, and that's what I'd love to know more about.

They talk about reducing memory allocations. My guess is the rest of the app is very large and they’re benefiting from not sharing memory and GC with that.

Of course, everything you said is true for a small-to-medium sized Rails application.

They likely could have explored a separate Rails app to meet this goal, but then they have to maintain the dependency tree and security risks twice. And if the Rails core refactors away any optimizations they make, they have to maintain and integrate with those.

There’s definitely some wiggle room and a judgement call here but their custom implementation has merit.

Don't forget that a Shopify store is 100% customizable by merchants using Liquid (Turing complete, not that you should try). There is no .html.erb layer. Think of Storefront Renderer as a Liquid interpreter using optimized presenters for the business models.
Liquid is designed so template authors don't have to be trusted. That's great and I wish it were more common.

Here's an example of a disclaimer that should be attached to most templating languages: https://mozilla.github.io/nunjucks/templating.html

Seconded! I've had to do a lot of weird stuff in django to get around this for our user templates.