Hacker News new | past | comments | ask | show | jobs | submit login

Http clients have pipelining for parallel requests that doesn't require threads. Database calls in Rails 7 now have `load_async`. You can still have other services, outside of Rails. Would that cover it?

P.S. I'm a huge fan of Elixir/Phoenix, but didn't find the big need for concurrency in practice that Rails doesn't address somehow.




Yeah, there are small, niche solutions for solving concurrency for very specific use cases vs general support for this in the language. This doesn't help you when you are using the AWS SDK to make HTTP requests or when you want to hide latency of making a DB query and and HTTP request concurrently.

I'm currently working on a large Rails App in my day job and lack of concurrency support is a major limiting factor for the growth of the whole company (~$10B market cap).


I understand your general point, but these solutions are not niche. They cover almost everything needed in web dev. The niche problems are the ones that arise at scale, which is when a business typically has the resources to solve them. (Hint: you don't have to solve them in Rails).

Large app / company challenges don't apply to new apps. Statements like "I wouldn't start a new app without a great concurrency story." or "I wouldn't start a new app without microservices." etc don't make sense, because new apps have different priorities (i.e. finding the fit, surviving, staying relevant).

I went through a phase of building Elixir/Phoenix apps for 3 years, thinking that I will switch permanently, but ended up coming back to Rails due to higher productivity in that stack. This was in 2015-2018, so maybe Elixir/Phoenix has gotten more ergonomic since. I'm not sure.


You can build a great NodeJS monolith, be very productive and not end up in a corner where you have to spend years of effort fixing your early technology choices down the road. It's not a zero sum game and completely rewriting millions of lines of business logic that your company is relying on for revenue is a hard sell.


You're saying that NodeJS monolith comes with no downsides compared to Rails at early stages. We disagree here.

From my brief surveys, nodejs ecosystem comes with less security out of the box, less standardized project structures, fewer thoroughly-designed and supported packages (vs cutting edge experiments), more complex upgrade paths, more projects getting abandoned, all of which can slow you down every day. Friction from these can be deadly.

On the other hand, being in the corner at scale doesn't force you to rewrite everything. Just the piece that put you there. You can leave Rails app to handle most things, and extract parts of specialized infra as needed.

Would be curious to look at specifics — what kind of pains your company is experiencing with Rails today.


"being in the corner at scale doesn't force you to rewrite everything. Just the piece that put you there. You can leave Rails app to handle most things, and extract parts of specialized infra as needed."

You can't easily, because this now puts a network boundary between your Rails app and the part you extracted. A network boundary that requires you to do IO, which will (by default) cause you Rails app to blockingly wait for a response (vs not doing so in other runtimes, ie Node).

"Would be curious to look at specifics — what kind of pains your company is experiencing with Rails today."

Some things I've seen in the last 3 month:

* Rails timing out after 30s while allocating 500MB of memory (mostly) in ActiveRecord to compute 5MB of JSON to return to an API caller

* 90% of request latency of ~10s spent waiting for downstream services to respond to requests. Most of these could be fired off concurrently (ie `Promise.all` in node). 9s/10s this Rails worker is sitting around doing nothing and eating up ~300MB of memory.

* trying to extract out Authorization to a centralized service (so that other extracted services don't have to call into the monolith in order to make authorization decisions) is a major pain as the monolith now has to make calls out to the centralized auth system to in order to make authz decisions.


A internal network boundary is probably worth it for heavy jobs, since you usually don't want it to interfere with serving web requests (no matter the tech).

You probably already know what I would say to each of those examples.

> Rails timing out after 30s while allocating 500MB of memory (mostly) in ActiveRecord to compute 5MB of JSON to return to an API caller.

I can make a JS or Go program perform the same way. In fact the exact same thing happened in my shop with Go/Gorm. The key question is: how do you compute the 5mb of JSON? The devil is in those details. We changed the way we computed ours, and the issue was gone.

> 90% of request latency of ~10s spent waiting for downstream services to respond to requests. Most of these could be fired off concurrently (ie `Promise.all` in node). 9s/10s this Rails worker is sitting around doing nothing and eating up ~300MB of memory.

This sounds broken. Why is the worker doing nothing for 9 out of 10s? But like I said earlier, there are a bunch of ways to use HTTP1.1 pipelining to run them concurrently. (https://github.com/excon/excon and https://github.com/HoneyryderChuck/httpx support it, but you can also do that with Net::HTTP I believe) And you can still start threads, which are still concurrent while blocking on IO.

> trying to extract out Authorization to a centralized service (so that other extracted services don't have to call into the monolith in order to make authorization decisions) is a major pain as the monolith now has to make calls out to the centralized auth system to in order to make authz decisions.

This seems unrelated to Rails. Not sure why monolith can't continue handling authorization.


> This seems unrelated to Rails. Not sure why monolith can't continue handling authorization.

Agreed. You can totally keep some data in the monolith and some data in new services, and stitch them together if/as needed: https://www.osohq.com/post/distributed-authorization


"I can make a JS or Go program perform the same way. In fact the exact same thing happened in my shop with Go/Gorm. The key question is: how do you compute the 5mb of JSON? The devil is in those details. We changed the way we computed ours, and the issue was gone."

The problem is ActiveRecord in my case. The data layout is not great (lots of joins through relationships, I think 12 tables or so). ActiveRecord objects are HUGE compared to the few bytes of actual data they hold.

What do you use (except raw sql) in Ruby if you cannot use ActiveRecord? There is no other ORM that's optimized for fast reads and I don't feel like writing one.

I actually reimplemented the same API endpoint in Go using https://github.com/go-jet/jet and measured 10MB of allocations and essentially zero overhead over queries itself, a 50x speedup.

Don't get me wrong, this is not what the typical Rails shop will deal with, but it definitely shows where limitations of Rails lie and I'm dealing with stuff like that on a weekly basis in my job, and it's not even a large Rails app (3M LOC).


I agree that if you make a very complex query in ActiveRecord, it could eat a lot of resources.

That said, I would highly recommend going with raw queries for this sort of complexity, no matter the language. There are usually 2 kinds of queries: normal ORM-powered CRUD operations (which can get moderately complex), and hairy specialized report-style calculations. The latter I always recommend to keep in raw, well-written, well-commented SQL form. You can still wrap it into some nice object.

You could write them in something efficient like Jet or Elixir's Ecto, but for such a complex case I'd argue that you shouldn't obfuscate SQL at all. For all other cases ActiveRecord works well.

If you are serving these results in real time, something like materialized views (in postgres) would move the burden of calculation to when data changes, rather than when data is viewed.

And to tie it back to the original convo: a very efficient concurrent language doesn't solve these root causes, rather gives you more time not to address them, and allows you to get away with more neglect. There's some value in that, but you have to weigh it against the downsides mentioned in previous comments. If the language+framework is super efficient and has no downsides to its ecosystem and ergonomics, then there's no debate, I'd just use that.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: