I have no strong opinion on serverless but I’ve used Rails for 13 years now (in massive $multi-million SaaS products as well as hobby projects) and it still makes me happy. I keep thinking about learning Node and React but I just can’t be bothered because Rails lets me get things done so quickly while also being a joy to write Ruby and Rspec.
Regarding hosting, I think this is actually a great time to host Rails apps either via Heroku, via the new Digital Ocean k8s app cluster or – and this is my new favourite tool – using Hatchbox.io to deploy apps to Digital Ocean or AWS. It’s a dream!
I’m also a big fan of rails, but I’ve experienced a lot of problems scaling it. A lot of the problems ultimately came down to the simple fact that Ruby is really, really slow. At a certain scale you end up forced to develop infrastructure on a different stack to keep up with the CPU load. I never ran into that so quickly when building similar systems with java, go, and C#.
I wanna say something nice about rails too so I’ll say I have never seen a team so quickly deliver high quality web app features than a well oiled rails team. It’s something to behold.
This very much depends on the use case. If you are truly CPU-bound than yes, ruby is a bad choice. Similarly, if you are IO-bound or otherwise need a lot of concurrency ruby is not great. I also think it's problematic for large code bases with hundreds of developers. However, there is a huge swath of web apps and APIs that just need to pull a bit of data from a DB or two, munge it together and send it to the client. Rails is perfectly horizontally scalable and plenty fast for this even at scale given Amdahl's law.
That said, there are definitely some self-inflicted wounds people run into here. Of course there are the oft-cited expert beginner problems like n+1 queries and missing indices, but there's a more subtle issue as well: ActiveRecord itself is so convenient that it discourages writing baseline efficient code even when you need it. Basically any time you need to process a medium to large amount of data, ActiveRecord acts like a sort of super boxed type (to use Java terminology) adding a huge amount of overhead. Yes it's true that ruby doesn't even have true primitive types that can achieve the performance of Go or Java, but often times ruby primitives are plenty fast, you just have to be willing to write a bit of extra code, and ActiveRecord as an ORM has plenty of escape hatches at various levels of granularity to facilitate this (eg. pluck, find_by_sql, update_all, execute, etc).
I definitely agree that when writing Rails at scale, it's extremely important to view ActiveRecord as just another tool in the toolbox, and not always effective for every use case. I've worked in places that had a dogmatic aversion to ever writing raw SQL, and to a one they always suffered for it in performance sensitive code.
At the places who did handle this well, the "high-performance code" that needs to be hand-tuned SQL is usually much smaller than you think (a few queries here and there), and ActiveRecord is still great for your simple queries or for smallish tables.
Could you go into more detail about what you see as issues with IO bound tasks?
My understanding is that MRI Ruby provides non-block IO operations if you wrap them in a thread and that it is only CPU bound tasks that are blocked by the GVL.
Is there some other issue related to that?
(JRuby provides fully multi-threading for cpu bound tasks without GVL).
"My understanding is that MRI Ruby provides non-block IO operations if you wrap them in a thread and that it is only CPU bound tasks that are blocked by the GVL."
All IO operations in ruby are subject to the GIL (global interpreter lock).
GVL is an implementation detail rather than a feature of the language (I believe the term Global VM Lock replaced GIL in the standard library, sometime around ruby 2.0ish I think).
JRuby, for example, has no GVL including for CPU based code; everything there can run in parallel.
Even in MRI Ruby though, wrapping IO operations in a thread allows you to release the GVL when the IO operation blocks.
e.g.
5.times.map do
Thread.new do
Net::HTTP.get('example.com', '/index.html')
end
end.each(&:join)
Will perform those network requests in parallel rather than sequencially. This is how ActiveRecord can perform asynchronous database calls in parallel on MRI Ruby.
I got that HTTP example from[1], which has a good write up but it's also covered in Working with Ruby Threads by Jesse Storimer[2].
I asked the original question because in the Concurrent-Ruby Readme they discuss Ruby's mutable references and the possibility of thread safety issues because of that.[3]
Rails makes it really easy to do something 10 different ways to get the same result. Unfortunately, most of which aren't the most performant way. In my 10 years of building Rails apps of all different sizes, and seeing some very mature apps in production, this is the most common culprit I've seen.
I currently work on a rather large Rails app for a site that most of us here use. A common pattern for our performance pitfalls are things like this:
Tag.all.map(&:name)
versus
Tag.all.pluck(:name)
Using `#map` will instantiate a `Tag` object and do all the slow(er) metaprogramming to get you the syntactic sugar that makes interacting with an ActiveRecord object a treat. It does this, and then you only hit `#name`, completely wasting all that effort.
`#pluck` will change the SQL from `select * from tags` to `select tags.name from tags` and never instantiate a `Tag` object, instead short-circuiting and directly fetching the resulting SQL rows — which comes back as an array. It's something along the lines of:
The first example loops over the loaded `@user.tags`, loads them if they're not already `#loaded?`, selects ones that are `type == 'LanguageTag'`, only to grab the `#id`.
The second example joins the two resulting SQL statements and calls `#to_sql` on the second statement, building one query from two queries.
Are these times when the first example would be preferred? Yeah, plenty! If your result is already `#loaded?`, then you probably don't need to hit the database again. But for this example and the ones I'm stumbling across while getting our company up-to-speed on "good ways", these are the the commonalities.
Save for only very recently, the company I work for hasn't put emphasis on real-world Ruby/Rails skills, instead "if you can code at a high level for any language, we think you can make reasonable contributions to our codebase." This has lead to hiring skilled developers that just don't know that there's a more preferred way of doing things in Rails for different contexts.
I would refactor your second example into an exists using Arel, because at best the IN will result in the same performance. At worst it will be significant my slower. There are also particular issues with NOT IN and NULL. This is at least true in PG.
I also deal with a lot of scale, the issues people have here doesn’t match my reality. I think people have issues and rather than looking at what is fundamentally happening with their call patterns, they jump to calling out rails itself.
Rails does have some specific issues, but you’d have to go pretty deep to see them and boot times are terrible.
The general problem you describe - n + 1 queries caused by needless iterating over / instantiating Ruby objects when a simple SQL query would do - is certainly a very common newbie mistake, but it's just that: a newbie mistake.
Confusion here simply shouldn't be a problem for even a moderately seasoned developer, and if they do make such a mistake (because hey, we all make mistakes...) in performance-sensitive code they could quickly recognize it for what it is - a bug - and fix it.
If you're hiring junior developers, on the other hand? Sure! But you should know what you're getting, and your code review / mentoring process should get them straight.
I'm not sure I really understand how this is Ruby's or Rails' fault, unless your premise is "ORMs considered harmful" - in which case, ActiveRecord is far from alone here, and that's a different sort of conversation.
Your examples aren't really doing the same thing in two different ways. Map (and select, collect, each, and some others) iterate over an enumerable. Pluck and select are active record methods that generate SQL.
It's a good example of choosing magic/brevity over expressiveness. You don't know that Tag.all.map calls SQL because it's not something you explicitly tell it to do. That's the real issue with Ruby & Rails. The magic lets you do some powerful stuff but sometimes it's hard to tell what exactly is happening.
That's why I said they produce the same result instead of saying they do the same thing. If you read further in my example, I mention that they perform very differently under the hood.
That's a consequence of using ORMs. ORMs are a horrible performance mess. Just straight up write SQL. I'm sure Ruby has enough metaprogramming magic to not have to deal with cursors manually.
Would love to hear what you're doing that's got Ruby pegging the CPU. I've ran a few rather large Rails deployments over the years and it's rare to crush the app tier first, so I wonder if there's something unique here that we can make Rails better at.
Have you used fragment caching (in "Russian Doll" style)? In my experience, that's the key to making Rails applications fast. Ruby is slow, so there are definitely situations where it's not a good choice, but in many "basic applications" caching makes that irrelevant. Language speed is irrelevant when you don't run much code :-).
Rails is great if what you’ve got is read-mostly cachable content being served up. In an environment with high rates of incoming data, most of which can’t be reliably cached, things get more interesting and you start needing to scale horizontally a lot more and leaning on the database.
Rails handled Black Friday and Cyber Monday traffic for an ecommerce company I used to work for just fine. If you are making money, it's worth the cost of 50 lowend VPSes autoscaled (we could have done with a lot less, too).
If we were using Java it would have taken us three times the people and four times as long to build the site, and we all would have been laid off.
> Rails handled Black Friday and Cyber Monday traffic for an ecommerce company I used to work for just fine.
That says nothing about the added cost of running inefficient services, which require additional nodes to serve the same requests and thus increase operational cost and also risk to perform the same service.
Really? Our frontend servers handle 50 rps and cost $20 each and are nowhere near peak utilization. If anything ever needs scaling its the database. What level of traffic are you talking about?
I don’t want to second guess technical decisions I know nothing about but: no, Rails shouldn’t be streaming video. But 2k requests per second with mostly text content being shuffled around sounds absolutely doable with Rails. The cost benefit of easier development should definitely not be understated as well.
But that being said, the primary driver for tools should be what the developers know and ease of access finding developers who know this technology. If the city you work in mostly has PHP developers, PHP is a great choice. Similar for Java, Haskell, Lisp, etc. My point is that the tool ”Rails” definitely is adequate for this problem (minus streaming video...). Look at Shopify, Github or any other massive Rails app
If a Rails back-end streamed video, there wouldn't literally be a loop written in Ruby shoving bytes back and forth stored in Ruby arrays or buffers. It would be farmed off to something appropriate. You wouldn't necessarily want that machine to be doing it, using any middleware.
Yes, this matches my experience. I've been doing Rails full-time for about 6 years and any "slowness" has been the result of some problem, not Rails itself. 99% of the time this is an uncached n+1 query situation, or some beastly slow database operation that needs to be moved to background processing, things that would be problems in any framework or even some sort of bare-metal asm solution that nonetheless relies upon an external storage layer. =)
In a CRUD app the Rails layer should be extremely thin and the storage layer(s) should be doing nearly all of the heavy lifting.
There is a level of traffic at which even a "properly" thin Rails layer becomes the bottleneck, relative to many other frameworks.
TechEmpower benchmarks suggest it is around 2,500 requests per second in their "multiple queries" benchmark. In a more real-world scenario that might be 1,000 req/sec or less.
If one is attempting to serve more requests than this per minute then yes, perhaps Rails is the bottleneck. Admittedly, Rails' large memory consumption relative to other frameworks means it can be tough (well, technically easy, but expensive) to scale horizontally at this point.
it seems like a lot of my career has been optimizing SQL queries in Rails apps... which is often just adding the correct indexes. Kind of a lot of Rails devs just don’t know to do that
Slowness seems to be an overblown issue when it comes to the backend. Hardware is cheap compared to developers and app servers scale horizontally by default; if a language is actually slow but otherwise provides good productivity it's almost always cheaper to throw more hardware at it than move to a "faster" language (there's a reason we don't typically write web apps in C).
Isn't normal Rails the fast, scalable and easy to pick up framework for a Rails developer? Elixir has hype factor, but it still lacks the library ecosystem of Ruby. It's entirely possible to get to github scale with just Ruby and not have to learn all the gotchas of BEAM and OTP.
I've been using Elixir in production for four years now, in a fairly complex SaaS app, and I can comfortably say I've never had to "learn all the gotchas of BEAM and OTP". IMO Elixir/Phoenix do a really good job of abstracting all of that away from you, while still making it easy enough to tap into their intricacies if needed (e.g. if you want to write a GenServer for something).
Lack of library support was a bit annoying at first. But then I realized that, back when I used to take advantage of existing libraries in Ruby/Rails, in the vast majority of situations I was just utilizing a small portion of those libraries anyway. It ended up easy enough to write my own code for those features.
I have written Rails for 8 years and Elixir for 3.
Phoenix is essentially what Rails should have been.
It has the upsides of Rails without the connected antipatterns, native technical debt.
I can also confirm that there isn't a need to learn BEAM gotchas (which i did learn). One of my ex colleagues is a junior dev who has been using elixir for 3 years now and didn't need to use those special aspects of BEAM.
As a Ruby expert, Elixir is substantially easier, learning BEAM is a joke (and very instructive) compared to learning the entirety of the Ruby object model.
I can't think of reasons to use Rails nowdays. If you are in the CRUD apps market, use Postgrest or similar products. As for the rest sure, choose any language you want, the framework is way less relevant (arguably: detrimental) at that point.
The industry has proven extensively that for non-trivial projects, Rails is damaging due to ActiveRecord.
Second this. I did rails for years and liked it a lot, but there were pain points, particularly as a project scaled.
Elixir/Phoenix solves a lot of these pain points and is a joy to work with. If you like Ruby/Rails then you already know quite a bit of Phoenix. Schedule yourself 4 hours to follow a tutorial or book.
For the language and the framework the official tutorial/documentation are pretty good.
Also Pragmatic Programmers has very good books on both, written by the creators.
I love Phoenix but I will disagree with the OP in 2 points.
Yes probably Elixir/Phoenix will manage better thousands of concurrent connections, but if you are doing personal projects or are a start-up that condition is irrelevant in the meantime. So that only lefts us with the big established applications. That does not mean Phoenix is not good, it only means you will not notice a difference with Rails for most of your projects.
The second thing, that OP failed to mention is that the Rails ecosystem is at least an order of magnitude larger. From the gems available, job opportunities,developers available, instructional material, SOP for many tasks and so on.That is boring, but valuable.
Programming Phoenix is good. You might want to look at something Elixir specific too, I liked Programming Elixir although I've heard good things about Elixir in Action as well.
The official online documentation is pretty good too.
Boring tech is great if your primary concern is writing business logic. Ideally, this should always be true, but of course the reality is that people's jobs often have them working on products they don't really care for. And of course, it's easy to get distracted by tech anyway. Experience also comes into play when it comes to loving "boring" tech—it took my around a decade before I was sufficiently jaded that almost all I want to do is write actual business logic. I still love refactoring, though :)
> it took my around a decade before I was sufficiently jaded that almost all I want to do is write actual business logic
It's comforting to see I'm not alone. I just wanna ship clean code and contribute value to the business, while all the young bucks in my team are more interested in rewriting our REST apis in Graphql (with all kinds of rationalizations). Looks like the younger you are the more eager you are to explore new tech and the older you are the more keen you are in just delivering value in a boring old stack.
Ha, ya, my work recently mandated moving everything to graphql. My team started a greenfield project so there was no need to convert anything, though the learning curve was high with apollo. Graphql is nice, but as the only consumers of our API, I don't see it as a win over REST. At this point, though, we're proficient enough that it doesn't get in our way.
Having learned web dev around five years to go, my main stack has been Node/Express or Firebase (serverless) with React front-ends.
However, I got tired of the lack in conventions in Express, and Firebase has quite a few gotchas and limitations, so I'm moving to good old Django. I'm delighted thus far, I love not having to reinvent the wheel every time I start a project or add a feature.
I'm probably keeping React/Next.js as my primary working tools for the front-end nonetheless, but that's just me because I feel highly productive with them and I've become accustomed to the decoupled client/API architecture.
I can see the merits of SSR with templating as seen in RoR or Python, and I'm happy to have that tool in my belt. But conversely I also think it's worth for pretty much everyone to learn React (or Vue, Svelte, or what have you) because they do open different possibilities in UX engineering and app architecture, and the market is quite hot for them too.
Is there a good way to turn your rails app into a mobile app these days? I remember seeing something from turbolinks showing a way to make hybrid mobile apps using turbolinks.
It’s been a while. I tried to hack something like this together - without having done any android dev before - and I understand why it’s taking a while. It’s not as simple as I’d imagined it would be.
What's the benefit of moving from Rails to Node and React productivity-wise? On the backend side, Rails comes with an ORM, authentication, templating system and many more out of the box, while on Node you'll need to mix and match a bunch of 3rd party libs or roll your own, which might be a good thing depending on your priorities but certainly kills productivity. On the frontend side, you can always use react with rails backend, but developing traditional template-based ui is usually faster than developing a react app and you can take advantage of rails templating/rendering feature to speed up development.
Even in mid-sized projects there's usually one or two pages with sufficiently complicated front-ends that it's worth having something like React in your toolbox, though it might be Vue or Svelte.
You don't have to make the whole app a SPA to use these tools. You can just use it in those few pages that really need it.
Meh, put React into your build, maintain the thing, make the website download and parse React - all that just for one page? Better be a damn dynamic page. I wouldn't do it even if it's a big form with some dynamic fields.
It's very easy to load React only on the page that needs it. Use dynamic ES module import, or link the standalone builds only on the pages that use it.
Alternative like Preact is very small and can be easily (pre)cached on the client side.
Why bother? I work on a very UI-heavy interactive app (it's a gantt chart) for work and therefore have been doing react daily for 1.5 years. React is probably making life a little easier here, and I don't hate it, but I don't love it either. If I didn't love my team so much I would be looking for a job working on a more classic web app in Rails. And when I profess my appreciation for Rails, I could just as easily be talking about Django, Phoenix, or any other _opinionated_ backend framework. [edited for spelling]
I know some Rails shops that are switching to Stimulus Reflex for some UI-heavy things, it works like Phoenix LiveViews, you can briefly see how CodeFund uses it for their interfaces here (40 seconds in): https://www.youtube.com/watch?v=F5hA79vKE_E&feature=youtu.be...
I'm aware of Stimulus Reflex though never tried it out! :) I've been playing with LiveView a lot recently which is fantastic. I would love to switch to stimulus but we're pretty heavily bought into React at this point.
Every time I read that one of the “downsides” of RoR is that it “doesn’t sound cool” I know I’m in the right place! I’ll have mature teammates who understand the right tool for the right job. I won’t have to work with magpie developers with an acute case of not-invented-here syndrome.
Granted Rails isn’t the answer to everything. I’ve been loving what we can accomplish with elixir/Phoenix. But I’ll leave the hipsterism to the JS community and marvel as they reinvent wheels over and over again.
> (...) I’ll have mature teammates who understand the right tool for the right job. I
Honest question: what leads you to believe that Ruby on Rails, or even Ruby, is the right tool for the right job? You didn't even mentioned the job, so why do you automatically assume Ruby is the right tool?
Additionally, by ignoring popularity you're also ignoring availability of documentation and examples and mindshare. You're also ignoring experience, and prior onboarding into a language stack, which automatically means odds are anyone onboarding into the project will quickly be up and running.
Speaking as someone who was forced to onboard into a Ruby project just because a predecessor jumped on the bandwagon, the experience was an unmitigated disaster. A minor onboarding task that consisted of tweaking a hard codes settin in a module required me and a couple of colleagues to spend a few days a) learning a brand new programming language, b) learning exotic frameworks, c) getting acquainted with idiomatic Ruby, d) learning how to troubleshoot and debug Ruby applications, e) properly setup a software dev environment, and f) finally fix the issue.
If my predecessor opted to use Python instead of succumbing to bandwagons and resume-driven development practices, the same thing would take a couple of man/hours.
How does can this sort of snafu pass off as the right tool for the right job?
Sounds like this is less about whether RoR was the right tool for the job and more that no one other the implementer was familiar with Ruby/Rails (and maybe not even them).
This might mean that this wasn't the right tool _for your team_, but it's not a comment on what jobs are a good fit with RoR.
> Sounds like this is less about whether RoR was the right tool for the job and more that no one other the implementer was familiar with Ruby/Rails (and maybe not even them).
Isn't that already an operational problem? I mean, consider the mental burden alone of being forced to onboard to a completely different and relatively obscure tech stack, including the quirky programming language that serves as it's base, and all just to keep a web service up and running.
> a) learning a brand new programming language, b) learning exotic frameworks, c) getting acquainted with ..language.., d) learning how to troubleshoot and debug ... applications, e) properly setup a software dev environment, and f) finally fix the issue.
Applies with every language and framework one is not familiar with.
> If my predecessor opted to use Python instead of succumbing to bandwagons and resume-driven development practices, the same thing would take a couple of man/hours.
That's my opinion but after maintaining some large Rails apps and large Django apps, I would still take Rails any day. Just the testing quality and the batteries included makes it worth it, whereas the main downsides of Rails (speed and lack of typing) are also there anyways on a Django project.
That aside, if people building the project don't have any experience with what they are doing, it will be messy regardless of the framework or the language.
Holy crap! you were paid to work on a Rails app and you actually had to learn Ruby/Rails to get stuff done? The horror! I would seek compensation from my employer for that, not way is that legal.
I get the point, it's not completely invalid.
And still, calling Rails "obscure" is a stretch. In web dev world it's still a pretty dominant framework. There shouldn't really be any problem to hire Ruby guys afaik.
It sounds like more than RoR. Anecdata counterpoint: I’ve worked on a few RoR projects that were well architected and only took me a few days to fully ramp up.
I too have had experience after experience where every Ruby on Rails project in maintenance mode is a complete disaster.
It seems like it’s great for quickly building things, but it is unmaintainable, unless you wrote the thing. I am very flexible otherwise, but Ruby on Rails is banned from any project I’m involved in.
My experience is the opposite - Rails projects tend to have tests and maintaining is easy because the test suite is end to end and means something.
That said, Rails projects without tests aren't fun. Besides missing tests, I've also seen fat controllers, bad schema design, and not using Rubocop make more issues with maintenance, but usually I interpret as lack of understanding how to use Rails, not Rails being to blame.
For people in a similar situation, I'd strongly suggest giving Phoenix (on Elixir) a look. It's a "boring" Rails style server side rendered web framework, but with a few extra powers:
* Elixir is a great functional programming language
* When your backend needs to do more complex or longer running work it's just more Elixir rather than complicated architecture involving task queues etc. (because you have the concurrency of the BEAM to manage it)
* You have options like LiveView for your frontend. You don't have to use it, but it's there.
I agree with what you said about testability for Lambda style architectures. How do you spin up an offline version of some or all of that to see if it works? The BEAM world is nice for me because you can run a similar architecture of separate things (albeit all in one language), but have a much better testing setup.
The Phoenix framework was created by Chris McCord. He was a long time Rails developer who tried to create a LiveView equivalent in Ruby/Rails but concluded that the concurrency capabilities just resulted in too many edge cases that didn't work well. That process led to him working more with Erlang and Elixir, then creating Phoenix.
I have never used Stimulus or Stimulus Reflex, but based on his output Chris is a smart guy and an excellent developer who's opinion I therefore give some weight to.
No doubt, but there are some pretty awesome wizards working on StimulusReflex (and CableReady which it sits atop) as well, so I think it mostly depends on if you're keen to program in Elixr or Ruby.
I'm curious, and I know this is a random thread to post this question, but how do you handle the fact that the Elixir process might be shut down with regards to necessary persistence of any message queues? Put differently: I consider both message queues and databases in my system to be part of the stateful workloads and the web app part of the stateless workloads (can-be-killed-at-any-time). How do you handle the case where your stateful workload (queueing) now lives within your Elixir process which might be killed at any instance?
The terminology is a bit ambiguous here with the word 'process' so I'm not 100% sure precisely what you're asking. The BEAM (Elixir VM) uses the the term 'process' to describe its green threads/fibres. The BEAM runs as one OS process, with a thread per core of your machine and schedules each of the application green threads onto one of those threads. BEAM processes are cheap and quick to create and come and go regularly. The BEAM itself is very stable and will stay up permanently (potentially even through SW upgrade if you want). Whenever I say process below I mean a BEAM process.
When you use Phoenix, the Web framework creates a process to handle the specific request. By default everything you do happens in that process (give or take some database stuff). If you make a programming error that process will 'crash' (again a BEAM term that is more like an exception) and the user will get an error. No other processes will be affected, regardless of whether they are to do with other requests or anything else.
If you want a work queue, you would create a separate set of BEAM processes (or maybe just one), probably at startup. In your request handling you would send a message to that work queue process asking it to do things. Your request can block (not affecting any other requests) waiting for it if necessary, or it can return so you can get the result later. If the Web request times out or crashes or whatever, that will kill the handler process, but will leave the work queue process alone.
One other note - under the hood it's all message passing between processes, but to the programmer each process is probably just a Gen Server (an abstraction) and the message send and reply is just a function call. But normal Phoenix stuff doesn't even have that because the Web server does the 'process machinery' for handling requests.
If you want a persistent task queue in Elixir then I would use a library like oban [1] that uses postgres to manage the jobs, a nice benefit of it is that you can enqueue your jobs as part of a database transaction (and Ecto.Multi gives you a nice interface to transactions). And just because you can have a message queue directly inside your application server doesn't mean you need to or that it's a good fit for every project.
Kill at any time is dangerous for stateful Erlang / Elixir workloads. But there are several patterns:
1. Some process group libraries can handle graceful shutdown, which migrates the processes to other nodes. When you kill one of the nodes, it's process goes to other nodes in like tens of seconds.
2. Invent some virtual actor pattern like Microsoft Orleans.
3. Just backup processes states to external storage. Even not for shutting down nodes scenarios, people still tend to backup process state to ETS or stateful processes so that stateless processes can crash-then-revive without troubles.
I've seen a lot more discussion of Ruby recently than in past years, and maybe I'm just finding what I'm on the lookout for, but either way it makes me happy :)
I think it's past the latest hate cycle to good all boring now, kinda like php but with a bit of a better rep. I'm not sure actually why php and ruby are so despised.
I'm a committed Rubyist but I'd jump into using PHP/Laravel in a heartbeat if I couldn't use Rails any more. It seems way more sensible and well-designed than the terrible state of affairs you find in JS backend land. (shudder!)
I personally quit PHP around 2013 because of the inconsistent standard library. I spent more time looking up the docs than developing because the language just wasn't intuitive compared to let's say Python. However, beyond that, PHP has some pretty great frameworks nowadays (and I am forever grateful to Laravel for teaching me the concepts of an MVC framework, which proved useful when I switched to Django).
It is not that Ruby suddenly popular again, the scaling issues is still there, Scaling not impossible but mostly have to do with being expensive. If you are an average dev in UK or US with 100K+ Salary of course scaling is cheap. But not everyone has that luxury or operate on the same budget. But the combination of Ruby and Rails getting faster and Hardware is finally getting cheaper. ( 128 Core EPYC, on a 2 Socket Server Changes the Unit Cost of Core per VM across the whole industry ) Along with wages rises across other part of the world.
And the world has finally woken up to may be Javascript Ecosystem isn't exactly what they have hoped for in the backend.
> A lambda publishes a message to SNS, another one picks it up and writes something to DynamoDB, the third one takes that new record and sends an email…
Or, you know, just do related functionality in the same AWS Lambda function. While I would probably do the sending of an e-mail asynchronously as well (as AWS Lambda function triggered by a DynamoDB stream), the indirection over SNS to write something to DynamoDB seems overly complex.
Why would you do something asynchronously with a Serverless architecture you'd do synchronously with Ruby on Rails?
> […] and random hardcoded configs in AWS console.
Just don't do that. A Serverless application will always be a pita if it relies on manual configuration. Ensure all relevant configuration is part of Infrastructure as Code (e.g. CloudFormation or Terraform).
> Impossible to replicate production environment
If the project is set up properly with Infrastructure as Code replicating the production environment is as easy as it can possibly get. And because it's Serverless, if an environment isn't used, it usually doesn't even incur notable costs.
There is no silver bullet and I believe Serverless as well as other approaches have their place, but what I miss are similar posts about great experiences with Serverless applications. I for myself wouldn't want to go back and I'm really happy with the problems Serverless applications solve.
The impossible to replicate the production environment I think was referred to impossible to replicate locally, on your machine.
At my job I use AWS serverless services and I get a lot of frustration not being able to test and debug code offline. Having each time to upload some code to debug it is time consuming. Also you have to rely only on logs to debug, you obviously cannot use a debugger, and thus the solution is to insert a ton of print statements in the code and remove them, which is not a problem to me (I usually do that even in code that I debug locally) but the service to read these logs (ColoudWatch) is not great at all, you don't even have all your logs in one place, it's a mess.
I think serverless is overrated, sure it maybe the right tool for a simple project, but when the complexity grows it's best to use other more classical solutions.
I agree. Iteration speed and the ability to debug applications are the biggest downsides for me as well when developing Serverless applications.
To make debugging at least a bit easier, I prefer doing structured logging (e.g. by utilizing AWS Lambda Powertools [1]) to get the ability to query the logs efficiently with CloudWatch Insights [2]. Also AWS X-Ray [3] is really valuable to understand how a Serverless application behaves.
"AWS's Serverless Application Model explicitly supports local testing."
It doesn't provide any additional capability that was already there. Ie, I could already run my arbitrary function from the CLI by building a simple wrapper that reads a JSON file and sends it in. Or wrap it in a simple HTTP server.
The debugging story using that tool is worse than how I would already do it pre-SAM and they don't even have documentation for every runtime they officially support:
It doesn't allow you to test any of the interesting bits, which is what the blog post was alluding to.
- What happens if I have an SQS Lambda with 100 concurrency and a few of the containers get into a bad state? Do the other ones keep going or does the whole thing grind to a halt?
- What happens if I have a Lambda that consumes from 50 Kinesis shards and some of the containers get into a bad state?
- What happens if my Lambda throws an exception, what happens to my SQS message or Kinesis record?
If you outsource the plumbing you can't actually test the system locally you can only test "units" of it and in a lot of cases you either have to do remote tests with print debugging to figure out what's going on or what is more common is blindly assume the plumbing works how you intend, ship it to Prod and then fix problems as they occur.
> It doesn't allow you to run all the other services like DynamoDB, SNS, S3, etc. locally.
Sounds like you're mixing up how a test pyramid works.
AWS SAM supports running unit tests locally. Your remarks refer to integration tests. Integration tests are expected to be performed on environments that mimic either subsets of the production environment, or the whole production environment.
As long as you use terraform/cloudformation/sam, it should be fairly simple. You deploy everything with your name as a suffix. Most of the resources will be billed on usage only as well, so you don't even pay more than a few cents for testing like that.
SAM is able to run AWS Lambda functions locally and to emulate some API Gateway behavior. It doesn't allow you to run all the other services like DynamoDB, SNS, S3, etc. locally. As Serverless applications often rely on such additional AWS services, the ability of SAM to run AWS Lambda functions locally doesn't really help for running such applications locally.
DynamoDB and AWS Step Functions are notable exceptions to the fact that AWS services are usually not available locally. The vast majority of AWS services isn't available outside of AWS.
Ugh, I rewrote my serverless service for exactly these reasons. It felt magical the first time where you can deploy a service without spinning up a server so I put up with all these annoyances. But then docker gains popularity and suddenly there is no practical advantage in using serverless anymore, at least for me. I rewrote the service in django, packaged it as a docker image, then run it on any platform that can run docker.
On the other hand, I use cloudflare worker on a bunch of my projects. Unlike aws serverless, cloudflare worker is actually useful because it can do many things that can't be done by traditional backend, such as running and intercepting requests on the edge servers that sit between your server and your visitors.
I can't comment on lambda@edge as it wasn't available when I was still using aws lambda a few years ago. My current use case is probably supported on lambda@edge if I were still on aws as I'm mostly only use them to implement smart caching and routing.
> At my job I use AWS serverless services and I get a lot of frustration not being able to test and debug code offline.
For my purposes debugging is as simple as crafting a payload and feeding it through the handler locally. I match credentials locally with what my lambda runs as. Since it’s a monolith function it behaves basically the same way, including the usage of a debugger.
To make life a little easier any time there’s an exception it persists that ordinal payload so I can just replay it locally.
But I agree that serverless is mostly overrated for most use cases.
Completely agree. Most of the criticism I've seen comes from people who don't have a good grasp of the platform or tools, or haven't adapted their style to the "serverless mindset".
There's definitely a new technique I've had to teach myself in order to build serverless systems effectively. Today I'm getting great results from the approach.
Yes, absolutely. And to be honest to get started with it for a software developer which doesn't have any "cloud" experience is pretty rough. Suddenly that developer needs to have a understanding of AWS in general, IAM, CloudFormation and all the services which can be utilized to avoid having to write custom code. It's a steep and long learning curve.
Fully agree. The problem gets even worse at bigger orgs where there's a separate, security oriented, team which is responsible for setting/managing IAM roles and KMS keys. I think the AWS ecosystem can be very discouraging for younger engineers in these situations. A innovative PoC in your downtime seems a lot more daunting when you need to learn about 4-5 AWS services that are unrelated to code execution and you have make a request to devops for a new IAM role just to locally test out a prototype that you built over the weekend.
I'm just really sad reading all this comments, knowing that some people will take the decision based on them, PLEASE DON'T, most of the commenters have no idea about what they are talking about.
One of AWS's major flaws is that it provides an encouraging UI that almost begs new teams to create a special snowflake manual configuration.
The AWS UI should ideally be refactored to be a frontend for an infrastructure as code service like CloudFormation.
One of the other problems with AWS+CF though is that it doesn't support every option from every service, e.g. populating secrets was a hassle when I used it.
Why wouldn't Terraform be sufficient? I use Terraform to manage real deployments, but the UI is very useful to scan the environment. This is partly because the aws-cli is so awful^h^h^h^h^h hard to use as an interactive tool.
I like that AWS offers the management console. It can be great for quick prototyping and checking out new services where you don't know yet what to expect. An absolute no-go however is that the management console sometimes uses functionality which isn't available as an official API! One of the most prominent examples for that is probably that the management console does support U2F tokens, while the official API doesn't [1].
CloudFormation lagging behind in terms of features is mainly a matter of priorities and I agree, feature parity from day 1 on would be great. However the way they do it allows them to ship features more quickly, so that's something as well.
In the past CloudFormation support was even worse as the CloudFormation team was responsible for implementing the support for new features of all services in CloudFormation. And with an ever growing list of services and features, that was something which simply didn't scale. From my understanding that changed however, so that most service teams are now responsible for CloudFormation support of their service themselves.
> One of the other problems with AWS+CF though is that it doesn't support every option from every service, e.g. populating secrets was a hassle when I used it.
That's because creating a secret in AWS Secrets Manager is not a control plane operation (which is what CloudFormation usually implements), but a data plane operation (which CloudFormation usually doesn't implement). However in this particular case it has been apparently painful enough, as creating secrets can be necessary to be able to deploy other resources (e.g. for authentication to the Aurora Data API), that AWS implemented this data plane operation in CloudFormation [2]. The same is true for creating parameters in the SSM Parameter Store [3], while it's still not possible to create objects in S3 or items in DynamoDB without custom CloudFormation resources.
> If you ever dealt with cloudformation you’d know it’s absolutely enormous PITA.
Is it? For me it's working fine.
What problems are you facing when writing CloudFormation templates and what better alternative to declare infrastructure do you suggest?
Most problems I see are people being not aware of the possible attributes of resources or doing indentation mistakes when it's a YAML template. Most of these problems can be avoided by using cfn-lint [1] to ensure the template is valid.
CloudFormation is slow (custom resources take hours to time out), it's unpredictable (behavior is different for different services). I wish it didn't use stacks (drift happens and can only be fixed manually) and was just idempotent like ansible. It also doesn't come with sane defaults so you have to supply a load of boilerplate code (cdk fixes this using constructs).
I've been tinkering with Serverless (on AWS) for some client work recently and my view is about the same as this post. Every complaint he listed is what I encountered too. It's one of the most unfriendly dev experiences I've seen in ~20 years of development.
OP's post contains:
> What started as a simple function, grew into a bunch of serverless functions, SNS topics, S3 buckets, DynamoDB tables. All bound together with plenty of YAML glue, schema-less JSON objects passed around and random hardcoded configs in AWS console.
With SAM, it's still a ton of boilerplate too.
And honestly, even most simple functions are going to use SNS topics, S3 buckets and CloudWatch so you can see your logs. Probably a datastore too. Or if you want to access the Lambda over HTTP instead of an SNS trigger, then you'll need something like API gateway or a load balancer too.
It's even more fun if you decide to write to an RDS instance that's locked down by IP address and inside of a VPC while at the same time your Lambda needs external internet access. The amount of hoop jumping to get all of that to work was unreal. And it ended up costing $33 / month just to have a NAT set up to route all of the internet traffic between these services.
The infrastructure complexity was more than anything I've ever experienced in any tech stack. But I thought the goal of Serverless was to avoid infrastructure?
Then there's the whole problem of wanting to wire up your main non-serverless service wanting to trigger a Lambda using SNS topics. That's fine until you want to be able to develop the project locally, except SAM and other comparable tools don't mock out SNS. So now you're stuck either having to pay to have your dev environment running on AWS or set up something like localstack to mock out a bunch of AWS services (which isn't free either btw).
The messed up thing is, after all that, it was to be able to run about 20 lines of Python business logic. All it did was call out to a 3rd party API service and store the results into a database.
I recently migrated part of a platform over to a distributed architecture with a heavy reliance on serverless functions. We had very specific reasons for doing it that I wont get into, but I can confirm that its an unbelievably bad experience from a development standpoint. We tried all the usual tools like localstack and SAM local and it all just suuuuucked. Something that would have been a day or twos worth of work with a tranditional api endpoint would stretch out to weeks. We ended up getting fed up and put all the calls to our serverless functions behind interfaces. Then, when running locally, we swapped out the transport layer to call a simple webserver that can call the the functions directly. We've been leveraging that approach for a few months now and its smoothed out a lot of issues for us. The downside of course is that you aren't using the same networking as whats deployed but so far it hasn't been as much of a problem as I was afraid it would be and our velocity has increased quite a bit.
I would go as far as to say the unfriendly local dev experience is almost the point. They want to lock you into having to use their platform to get anything done. They want to make this stuff as difficult to abstract as possible. They want you to use their proprietary methods for code sharing (looking at you lambda layers...). All of it is designed so that you simply can't run your logic without tying yourself inextricably to their platform. It's a spiderweb that you don't notice until you are trapped in it.
Whenever I see Medium articles of insane serverless architectures used at the startup stage, my stomach drops. Serverless computing isn’t about fast iteration (for CRUD app development) or even “pay for what you use” (for CRUD app development).
Serverless is typically hard to setup and hard to iterate upon. You want to have a pretty clear understanding of the specific use case you’re addressing beforehand so you can architect the system well, which isn’t easy for a solo developer / small team who just wants to setup a little function in the cloud.
Actually, the best use case for serverless is:
- subscription to AWS specific events. It’s all built in and easy to plug into.
- elastic ETL pipelines. This is a very enterprise focused use case which involves processing GBs/TBs of data with complex transformations. The variability in data throughput makes serverless an elegant solution. Also, your ETL pipeline isn’t going to change in the same way that a product focused use case of serverless will change.
> And it ended up costing $33 / month just to have a NAT set up to route all of the internet traffic between these services.
Yep, this matches my experiences with AWS too.
The stuff that you THINK might cost an arm and a leg, are actually really cheap. Billions of lambda calls with thousands running concurrently? meh. Huge SNS/SQS-queues? Meh. Terabytes of data stored in S3? A drop in the bucket.
BUT.
Too many PUT-operations on S3? $$$$$expensive$$$$$ Too much IO on an Aurora instance? Pucker up! Need some sane network configuration? Prepare to pay up the wazoo for gateways.
Yes, all that can be worked around, but it's really non-intuitive to actually see where the pain points in a project will eventually be.
I pretty much just start all of my projects, no matter how small, with Django. Working with microframeworks or serverless is great at first but as soon as you need any more of the typical features that a webapp requires then you start pulling in dependencies or re-implementing half of Django yourself anyway. First you just need a little url router to some basic views. OK that's great, anything can do that. Now you need to validate some forms. Now you need to manage some session cookies. Now you need to let users reset their passwords. Now you need to look at your data and maybe export a csv. It just goes on and on.
What you wrote about is something that I really struggled with wrt serverless when I first started learning it. Lambda, at this point, feels to me like doing a php project without using any framework. You are doing it all yourself, all the tedious stuff.
I have started to mix Zappa and Django to get the best of both Django and Serverless. So far, I like it. I have not hit any considerable scale yet, so I wonder if Django is too heavy, or just right. It seems fine so far. The other plus - it’s all open source tech, so it’s easy to move away from Lambda if I need to.
It’s even better, too, because you CAN break out some more isolated functionality into stand alone lambdas when they start to get clumsy inside a typical Django proj.
You can have the best of both worlds with Ruby Jets https://rubyonjets.com/. Build micro services inside of a monolith. Might not make sense, but you're essentially building one app that deploys to micro services in AWS, in your favorite language and a framework that is essentially Rails.
I like using the “serverless monolith” architecture. You use a catch-all API gateway hooked up to a single lambda. You write it the same as any standard HTTP server and let an adapter handle the lambda bits[1]. During development you run it as a regular HTTP server and connect directly to localhost. To deploy it all you need is a shell script that zips and uploads your binary to lambda. All state is stored in DynamoDB. One log stream for everything.
The downside is dealing with AWS events. There’s a few ways to handle it. You can write it into your monolith, toggling behavior with say an environment variable. You can sometimes work around it, for example avoiding the S3 file uploaded event by having clients ping your HTTP API on upload completion. Of course, there are trade offs for all the techniques. You can easily reconfigure a lambda to retry on failure but you’d have to manually implement that client-side. You can also always just add another lambda if you’re ready.
To put it another way: you probably don’t need to write that record to a queue which gets written to DynamoDB, whose stream calls another lambda etc etc. Just do that stuff within a regular old HTTP handler until the fanciness is absolutely necessary.
For personal projects, I appreciate how serverless scales to zero. I don’t have to worry about axing projects with minimal usage.
As I wrote in my last article [0], serverless advocates pushed too much FaaS in the last years.
FaaS is awesome, but should be the last resort, if you throw too many functions at a problem you can easily end up with a distributed monolith, which is often worse than a regular one.
It’s more like you have one Lambda instance that is always running (sure think of it like one server) that can serve the first page instantly while the other ten thousands Lambdas take half a second to warm up. That first Lambda responds instantly so it hides the half second response for the rest of the application.
It really makes sense if you have traffic that spikes at certain times per day (think like fantasy football). There’s going to be times when no one is using your application but when they do, there are going to be dozens or hundreds of people suddenly using it. So the first person that logs on hits the provisioned Lambda instantly and then AWS warms up the rest of the Lambdas as more traffic comes in.
We experimented with Lambda and found it completely unsuitable for any workload that has a "thundering herd" problem. If you go from zero to 1,000 requests/second then it can take up to 30 minutes to fully scale for that inbound traffic. And pre-provisioning for that sudden peak concurrency is very expensive. I think Lambda probably has its place in some kind of backoffice task runner architecture, but it's really not good for serving web traffic that might be occasionally spiky.
Have you talked to AWS about why that's the case? AWS Lambda should handle that order of magnitude just fine, as you have an initial burst concurrency quota of at least 500 requests per region (which you also can request to get increased). After that scaling should be additional 500 concurrent requests per minute. [1]
Also consider giving Crystal lang a look, in particular the Amber and Lucky web frameworks. Amber in particular is designed to feel just like Rails and accomplishes that pretty well in my opinion.
Crystal is designed to feel like Ruby but to be "fast like C". I think in reality by adding a type system it manages to be better than Ruby.
Small but very welcoming community with a number of startups and companies running things in production. Crystal itself is doing well with the 1.0 release looming very soon.
Crystal itself has stabilized quite nicely. Amber/lucky are a bit slow to update, but all of my production apps use either Kemal or Spider Gazelle which are too lightweight to matter in that regard.
How are you finding managing infra for rails? Is disaster recovery quick for you?
I'm the author of openfaas and hear what you say about the fragmentation for using AWS Lambda. We recently made a rails-esq version of OpenFaaS for users who have a single small app like yourself. I would be interested in your impressions. It's all open source and is a bit more of a more modern stack. On the upside it uses containers and you can write in any language.
I’m not sure the issues experienced are due to serverless rather than the architecture chosen. A monolithic app vs services.
Why split up the serverless functionality into separate functions connected with SNS etc? You can call the same ruby code from a single serverless lambda endpoint invoked from a http call in the same way you would from a rails process.
Rails is great, but you could run Rails on a serverless architecture (AWS) too if you really wanted to, and you could create a multi service architecture of Rails components that would be equally hard to maintain and develop.
Serverless has its place. I am not sure if I'd chose to build a shopping cart on Serverless just yet (Next.js' recent forays notwithstanding), but for something that is stateless (like content distribution, capturing metrics and logs, serving static webpages)...oh, is it a breeze.
That said, I see many promising upstarts and incumbents in the space trying to make stateful Serverless easier by the day. Things can only improve and improve they will. I'm bullish.
AWS Amplify[1] actually has neat tooling for serverless projects and lets to deal with just the core of your product and worry less about infra. Admittedly, it helps if you know the underlying connections but in my experience that's true with rails too. Building a blog with rails isn't the same as a more complex product.
One thing you have to realize, is that "enterprise" architectures are as much about workforce management as they are about efficiency, or scalability, or et al.
There's a limit to how reduced data, and functions that work on data, and the transports and relationships between it all, can get. Let's call this residual mass "goo". Well, the goo has to go _somewhere_.
For those in this predicament, consider mine -- I'm about to migrate a non-serverless collection of nodejs microservices to Rails via Ruby on Jets, which apparently has full support for running standard Rails apps these days. I can't imagine something more powerful than a Rails app running in a Lambda.
I moved to Phoenix and it is wonderful. Except... Ecto just isn't as easy as ActiveRecord and you can't do things the way you would prototype things in IRB. So, yeah, on one hand, I have crazy performance and LiveView etc, but on other, it is huge hassle just to hack around the way I did before.
Having worked with AWS serverless for a couple of years, I think the architecture is not well suited for replacing a monolithic application. If horizontal scaling is an issue, I think containers are better suited. Serverless seems to be a better fit for ETL/Data Transformation workflows.
serverless (well, lambdas) would be very expensive for any truly significant ETL work. if you have long-running jobs of any kind, that's precisely the opposite of where serverless shines.
I'm a single developer and I'm glad I chose serverless for my project. I never have to worry about scale, and for a single developer that's important, as there aren't 10 other developers to leverage when scale becomes an issue after the fact.
I also don't have to pay for what I'm not using, also important for a single developer. If I had chosen a more traditional approach of using EC2 to host a server, I would be paying quite a lot more for my project to sit idle on an EC2 instance 24/7/365 if I'm not getting enough traffic to support paying for the idle server.
I'm looking at Blazor/wasm nowadays and it's causing me an awful lot of nostalgia when I think back to the early days of Rails (if '08 can be called early days per-se), because of the shit show back then with various tech stacks and the grunt work required to get a site up with some DB backed data source and whatnot.
And I feel like today we are back to some kind of a point of having to shoehorn FE/BE tech together unless we use some isomorphic approach which, predominantly, forces the use of JS.
And I remember DHH commenting on this regarding Java and saying something to the tune of "Why would I write Java when Ruby is so much better?". Back then I thought such a statement was too indignant, like someone getting on their high horse about a particular kind of building material that is better above all else. But nowadays, I see the wisdom behind it all the same.
I just want simple bloody tech that treats the browser as a layout and input handling engine and lets me write code in any language I see fit, whether it's Ruby, F#, C#, Clojure, whatever. I'm hoping Blazor or something like it can deliver on that vision.
Server side Blazor is promissing to me. Simpler development by merging frontend and backend into one C# codebase but might not work well on unstable connections due to websockets. Some of my clients fall into this case.
I'm keeping a close eye on it. I also need to test how it scales with regards to handling traffic.
Regarding hosting, I think this is actually a great time to host Rails apps either via Heroku, via the new Digital Ocean k8s app cluster or – and this is my new favourite tool – using Hatchbox.io to deploy apps to Digital Ocean or AWS. It’s a dream!