[HN Gopher] The Mythical IO-Bound Rails App
       ___________________________________________________________________
        
       The Mythical IO-Bound Rails App
        
       Author : thunderbong
       Score  : 179 points
       Date   : 2025-01-25 08:47 UTC (14 hours ago)
        
 (HTM) web link (byroot.github.io)
 (TXT) w3m dump (byroot.github.io)
        
       | pdhborges wrote:
       | Very nice discussion. A lot of times I see people assuming that
       | Web Apps are IO bound anyway to justify technical decisions.
       | Nevertheless here am I looking at our stats: 60% is python, 40%
       | is IO and Postgres is getting faster than Python at a faster
       | pace.
        
       | gtirloni wrote:
       | I didn't get the point about YJIT only helping 15-30% in real
       | apps while IO-less benchmarks see 2-3x. It seems like a
       | counterpoint to the main argument of the article.
       | 
       | Instead of guessing if the DB is performing badly by looking at
       | Rails, why not go directly to the DB?
        
         | byroot wrote:
         | Someone else pointed the same thing on bluesky, so I guess this
         | part of my post isn't very clear.
         | 
         | First, I/O bound isn't necessarily a very well defined term,
         | but generally speaking, it implies that you can't substantially
         | speedup such system by speeding up code execution. The fact
         | that YJIT did yield substantial performance improvement in the
         | real world suggest many Rails apps aren't in fact strictly I/O
         | bound.
         | 
         | Now about the 15-30% vs 2-3x, what I mean by that is the
         | benchmark where YJIT yield this much are mostly micro-
         | benchmarks, on more complex and heterogenous code, the gains
         | are much smaller, hence we can assume the "Ruby" part of these
         | applications wasn't improved by this much.
         | 
         | So for YJIT to be able to yield `15-30%` latency gains, this
         | latency must have been in large part composed of Ruby
         | execution. One last thing to note is that YJIT can only speedup
         | pure Ruby code, in a Rails application, a large part of the CPU
         | time isn't in pure Ruby code, but in various methods
         | implemented in C that YJIT can't speedup.
         | 
         | Ultimately the lobste.rs maintainer kindly offered to run an
         | experiment in production, so we should soon see if my
         | assumptions hold true or if I was way off, at least for that
         | particular app: https://github.com/lobsters/lobsters/pull/1442
         | 
         | Edit:
         | 
         | > Instead of guessing if the DB is performing badly by looking
         | at Rails
         | 
         | That isn't really the topic though. The question is more about
         | how much Ruby's GVL is actually released, and its implications.
        
         | SkiFire13 wrote:
         | The point was that 15-30% is a very big speedup for a change
         | that impacts what is supposed to take a very small part of the
         | total time. See also Amdahl's law [1]
         | 
         | Suppose that the CPU-intensive part of the program was actually
         | speed up by 3x. This means that previously it took 1.5x as much
         | time as the one that was saved, so thoughly on the order of
         | 22-45% of the total time of your program. In practice the
         | speedup was probably lower than 3x, which means the 22-45%
         | figure would be even higher.
         | 
         | These are really really high number for a system that was
         | supposed to spend most of the time (>90%) blocked in IO (which
         | was not speed up by YJIT).
         | 
         | For comparison, if the CPU-intensive part actually took 10% of
         | the time, then 10% would be maximum speedup you could get
         | (which is way lower than 15-30%) and a 3x speedup would only
         | result in a ~6% speedup.
         | 
         | [1]: https://en.m.wikipedia.org/wiki/Amdahl%27s_law
        
       | rf15 wrote:
       | Usually it's the inefficiencies of architectural decisions (your
       | algos, layouts, etc.) you put into that rails app.
        
         | scosman wrote:
         | Sometimes. But if you have any real processing to do, it can be
         | hard/impossible to do it quickly in ruby. You end up writing c
         | extensions, or another app server in another language for the
         | hard stuff.
        
         | ndriscoll wrote:
         | Right. Simple test: check the throughput your database can do
         | with bulk queries. Do a single query select of an xml or json
         | struct that outputs the page rails would return (so one
         | rendered page per row). Select enough rows for the query to
         | take 1-10s to get a sense for rendered pages/second. Do the
         | same for inserts/updates. If you're not close to what postgres
         | alone can do with bulk queries, you're obviously not IO bound.
         | 
         | Other sanity check: nvme drives are ~10000x faster than hard
         | disks were. Are you doing 10000x more useful disk IO than
         | people were doing with hard disks?
        
           | anarazel wrote:
           | For typical rails like applications that are "bottlenecked on
           | the database", latency is a much bigger factor than actual
           | query processing performance. Even if careful about placement
           | of the database and "application servers".
           | 
           | Just as an example, here's the results of pgbench -S of a
           | _single_ client, with pgbench running on different servers (a
           | single pkey lookup):
           | 
           | pgbench on database server: 36233 QPS
           | 
           | pgbench on a different system (local 10gbit network): 2860
           | QPS
           | 
           | It's rare to actually have that low latency to the database
           | in the real world IME.
           | 
           | If the same pkey lookup is executed utilizing pipelining, I
           | get ~145k QPS from both locally and remotely.
        
             | ndriscoll wrote:
             | That's still an architectural thing. If CPU isn't an issue,
             | why would you be running your application server on a
             | different machine from your database? Connecting to a unix
             | domain socket will give better latency and throughput and
             | security as long as you can scale vertically.
             | 
             | Even if you have it on another machine, it's on the same
             | switch, right (also an architectural choice)? So latency
             | should be sub millisecond, and irrelevant for a single
             | request. So we assume we're talking about many requests,
             | but then why not combine/batch your queries (also an
             | architectural choice)? Now latency (and other things like
             | database latches and fsyncs. Very important.) is amortized
             | and again irrelevant. You should be able to hit close to
             | the pure pg bulk query case (and e.g. a rust application
             | server can do this in practice for me with a 4 core machine
             | and an old SATA SSD with ~50k read IOPS).
             | 
             | Point is, the optimal case for building a web page is
             | probably to do pg queries that return the xml of the page
             | (letting you do subselects to combine everything into one
             | query). So see how fast that is, and if your application
             | can't match that, the database is not your bottleneck.
        
               | anarazel wrote:
               | > That's still an architectural thing. If CPU isn't an
               | issue, why would you be running your application server
               | on a different machine from your database?
               | 
               | Because that'll be too much of a scalability limitation.
               | Rails etc are rather CPU heavy. In cloud environments
               | it's also typically much more feasible to scale the
               | stateless parts up and down than the database.
               | 
               | > Even if you have it on another machine, it's on the
               | same switch, right (also an architectural choice)?
               | 
               | Yep, was on the same switch in my example.
               | 
               | > So latency should be sub millisecond, and irrelevant
               | for a single request.
               | 
               | In my testcase it was well below a millisecond (2.8k QPS
               | on a single non-pipelined connection would not be
               | possible, it implies a RTT <= 0.35ms), but I don't at all
               | agree that that makes it irrelevant for a single request.
        
         | whstl wrote:
         | IME the Rails ecosystem and community pushes teams into
         | architectural decisions that don't scale quite well.
         | 
         | In small apps it's easy to keep track of the number of queries
         | going to the DB per http request and their cost, but with
         | bigger apps what I often find is ActiveRecord, services,
         | policies, all calling the database way too much and too often.
         | 
         | Monitoring which individual queries are slow is easy, but it
         | doesn't help if operations are overcomplicated. Even if you
         | chase all obvious N+1s, you might still have "sets of queries
         | that could have been joins" but are in different classes, so
         | it's difficult to notice.
        
       | scosman wrote:
       | "CPU starvation looks like IO to most eyes"
       | 
       | Yes! And add in that you are holding a database connection for
       | longer than needed, putting scaling pressure on your DB, which is
       | the harder thing to scale.
        
         | PaulHoule wrote:
         | I'm not sure if it is the same phenomenon but I was really
         | disappointed with the performance of a python aiohttp server
         | [1] when I went from the application serving an html page with
         | maybe 2-3 small images that would stick in the cache (icons)
         | and instead was serving 20-50 photographic images per page,
         | particularly over the wrong direction of an ADSL connection.
         | 
         | I thought this was low CPU but it was not low CPU enough, in
         | the end I switched to gunicorn (running in WSL) with IIS
         | fronting and serving the images (woulda been nginix in a pure-
         | Linux environment) I think the tiny amount of CPU to serve the
         | images was still getting blocked by requests that used a little
         | more CPU. With gunicorn I'm not afraid to add tasks that crunch
         | on the CPU a little harder too.
         | 
         | [1] built a lot of mini-apps that worked great before
        
         | WJW wrote:
         | Having the connection for longer than needed only puts more
         | pressure on databases on which connections are a scarce
         | resource (basically only postgres, and even then it's easily
         | fixed with pgbouncer). When using a database with a better
         | connection model, holding a connection too long will actually
         | _decrease_ total load on the database because each connection
         | is idle for a larger percentage of the time.
        
       | Azerty9999 wrote:
       | There is a highly relevant follow-up post by the author here,
       | too: https://byroot.github.io/ruby/performance/2025/01/23/io-
       | inst...
        
       | jupp0r wrote:
       | When people say these apps are IO bound, what they actually mean
       | is that they are memory bound. While Rails is waiting for IO due
       | to its lack of concurrency support and the ubiquitous use of
       | global variables in the ecosystem, a passenger worker is taking
       | up >300mb of memory for a medium sized Rails app. Server memory
       | limits the amount of workers you can have waiting like that,
       | which in turn limits overall throughput per server.
        
         | caseyohara wrote:
         | That you mention Passenger makes me wonder how outdated your
         | comment is. The most recent Rails community survey reported
         | only 13% using Passenger.
         | 
         | https://railsdeveloper.com/survey/2024/
        
         | byroot wrote:
         | That may be true for very memory constrained platforms like
         | Heroku, but is a non issue at larger scale.
         | 
         | Taking your figure of 300MiB per process, and assuming you
         | might want to run 1.5 process per core to account for IO-wait,
         | that's 450MiB per core (ignoring copy-on-write).
         | 
         | If you look at various hosting offering, most offer something
         | like 4GiB of RAM per core (e.g. EC2 "general purpose"
         | instances).
         | 
         | As for the lack of concurrency support, Active Record has
         | asynchronous queries since a few versions, and is now even
         | async/fiber compatible. So if you truly have an app that would
         | benefit from more concurrency, you can do it.
        
         | pydry wrote:
         | No, it just means that they spend most of their time waiting
         | around for database and network queries to finish.
        
         | marcosdumay wrote:
         | They mean the app takes data from one place, does a trivial
         | computation, and pushed it at another place. So pushing data
         | around is all the application does.
         | 
         | And they don't know both such places are actually faster than
         | the trivial computation because their code runs way slower than
         | the ones at the origin and destination.
        
       | karmakaze wrote:
       | The discussion in the referenced Rails issue "Set a new default
       | for the Puma thread count"[0] to me is much more telling of IO vs
       | CPU than this simplified post.
       | 
       | There are many assumptions here especially not considering
       | anything about the database itself. The Rails issue also
       | considers benchmarks which when benchmarking Rails would
       | configure the database to not be the bottleneck. That's not true
       | of real systems. The advice to use async queries for an IO-bound
       | app could backfire if the reason the queries are slow is because
       | the database is overloaded--adding concurrent queries only
       | increases its thrashing and latency.
       | 
       | The best thing to do is consider the whole system. Don't throw
       | the everything that's not the Rails app as IO. Is it actually
       | doing network IO, or is it CPU, memory, or IO bound in the
       | database? Maybe its not even a lack of CPU proper on the database
       | but that it's being wasted on write contention/locks. Only then
       | will you be able to choose the right course. Another way to go is
       | blindly try different configurations and use what works well
       | without full understanding, which is fine until you have an
       | outage you can't explain then scale everything just-in-case.
       | 
       | The ways I've seen this play out:
       | 
       | - small apps (few users) just need good schemas and queries
       | 
       | - medium apps scale(*) webapp instances that solves client CPU &
       | memory and vertically scale single database writer instance
       | 
       | - large apps scale database via sharding or
       | federation/microservices
       | 
       | - other apps may not be able to shard a single tenant so also
       | scale via federation/microservices
       | 
       | (*) Even if webapps are the CPU/memory bottleneck, overscaling
       | them can make the problem of too many connections to the database
       | for long held transactions.
       | 
       | [0] https://github.com/rails/rails/issues/50450
        
       | juwfass wrote:
       | Optimizing rails is zero sum (it's ruby after all). Do yourself a
       | favor and use pod affinity to schedule rails right next to
       | database while your coworker rewrites it in Elixir/Zig stack.
        
         | citizenpaul wrote:
         | Your comment is confusing. Are you reccomending pod scheduling
         | as a band aid while you rewrite the app in something else? Or
         | making fun of trying to rewrite?
        
           | juwfass wrote:
           | Forgot to add /s. but yes making fun of both. This is my
           | team, all ex-FAANG.
        
       | freedomben wrote:
       | I was skeptical given the title that the article could defend
       | it's claim, but the article was strong and made great points. The
       | only thing I would add (which I concur probably doesn't belong in
       | the article itself):
       | 
       | Most databases make it trivial to see how long your queries are
       | taking. Use that to help you identify the performance
       | bottlenecks! If a request/response cycle takes 200 ms from the
       | entrypoint of your rails server, and the database query reported
       | (from the db) took 1 ms, that's a whole lot different than if the
       | db query took 150 ms.
       | 
       | On scaling in general, in my experience inefficient queries are
       | the vast majority of performance problems for most apps. It's
       | _usually_ fairly easy to identify these as different endpoint
       | will perform very differently so you can get an idea of the
       | baseline and identify aberrations. If that 's happening then
       | check your queries. If the whole app (baseline) is slow, TFA has
       | phenonmenal guidance.
        
       | hamilyon2 wrote:
       | There is a certain scale at which this may ring true. This also
       | may by a a tautological statement: when your architecture is
       | right, your tables are well designed out, your queries are
       | optimized to the limit, and you are smart with caching. Of
       | course, now CPU is your problem.
        
       | mike_hearn wrote:
       | That's basically right except it's worth noting the belief that
       | RDBMS can't scale horizontally is wrong. Most RDBMS engines can't
       | do that, but an example of one that can is Oracle. RAC clusters
       | scale write masters horizontally up to fairly large cluster sizes
       | (e.g. 32 write masters works OK) and obviously read replicas can
       | be added more or less indefinitely.
       | 
       | RAC isn't sharding. The Oracle JDBC drivers do support automatic
       | sharding across independent databases or clusters too, but RAC is
       | full horizontal scaling of full SQL including joins.
       | 
       | It's also worth noting that if your Ruby app is CPU constrained
       | you could take a look at TruffleRuby. It runs on the JVM, has a
       | full JIT compiler more powerful than YJIT, can deal with native
       | modules, and doesn't have a global interpreter lock (threads do
       | really run in parallel).
        
         | packetlost wrote:
         | This is what I mean when I say Oracle's DB product is actually
         | pretty good. Say what you will about Oracle the company, but
         | their core products are good at their respective domains.
        
           | mike_hearn wrote:
           | Yeah there's a lot of other neat tricks you can do with it.
           | Someone else in this thread observes that a major issue for
           | most Rails apps is latency. Loading a page does 100 blocking
           | queries serially which means 100 roundtrips (minimum), the
           | latency from that adds up and before you know it your fast
           | computers are taking seconds to render a page.
           | 
           | Although it often gets lost inside app frameworks, you can
           | use the Oracle drivers to dispatch every query at once in one
           | request, or even just execute a stored procedure that has all
           | the queries for a page registered inside the DB then work
           | through the results from each query client-side. So you can
           | really optimize the latency heavily if you can structure your
           | app in such a way that it can exploit such things. JDBC
           | supports this for instance but Active Record doesn't.
        
         | Andys wrote:
         | CockroachDB can too, although its licensing has made it
         | unpopular.
        
         | tcoff91 wrote:
         | Due to Amdahl's law, you surely don't get 32x write throughput
         | from having 32 write masters however.
        
           | mike_hearn wrote:
           | It depends a lot on what the query patterns are. If your
           | writes are naturally spread out then yes you can get close
           | because the 32x will be writing to 32 different parts of the
           | database and there won't be cache blocks pingponging around.
           | If all the writes are contending on the same parts of the
           | same tables then you won't get 32x indeed because your
           | queries will be waiting on each other to release locks etc.
           | 
           | At least that's my understanding. I've not tuned an app that
           | runs on such a cluster.
        
           | yuliyp wrote:
           | Amdahl's law doesn't talk about throughput, but latency. You
           | can absolutely get 32x more throughput by having 32 parallel
           | backends. Each individual write won't be 32x faster e2e, but
           | that's not really the point.
        
       | jerf wrote:
       | Another thing I have seen that sneaks up on dynamic scripting
       | language programs is that maybe at first, your code is mostly
       | blocked on database. You prototype something that mostly gets
       | some stuff from the DB and blasts it out on optimized C-based
       | code paths (like JSON encoding) without hardly even looking at it
       | that are fairly fast.
       | 
       | But then you need some authentication. And you need
       | authorization, and more deeply than just "yea" or "nay" but doing
       | non-trivial work to determine on some complex resource tree what
       | is and is not permitted. And then you're taking a couple of
       | queries and putting it together with some API call results in
       | scripting code with some non-trivial logic. And then it grows
       | beyond your dev prototype of 10 values to 100,000. And you use a
       | few convenient features of your language without realizing the
       | multiplicative slowdown they impose on your code. You add some
       | automated plugins for turning DB values into these other things
       | without realizing how much that takes. And you add some metadata-
       | driven interpreter action based on what the user is asking for.
       | And one feature at a time, you just keep stacking more and more
       | on.
       | 
       | And it can really sneak up on you, but in a mature scripting
       | language codebase it's really easy for it to not just not be
       | blocked on database IO, but for IO to ultimately be such a tiny
       | portion of the runtime that even if the IO was completely free
       | that you'd still have a slow page.
       | 
       | Computers are _very_ fast, and very good at what they do, and
       | there 's a lot of power to play with in a lot of cases... but it
       | isn't always quite as much as programmers can think it is.
        
         | snovv_crash wrote:
         | I guess this is part of the reason for C++ having zero-cost
         | abstractions.
        
         | swatcoder wrote:
         | You're not really wrong, and what you're talking about is not
         | really related to language choice.
         | 
         | What its related to is the narrative you shared in your second
         | paragraph, which I think you were writing mostly for color,
         | perhaps seeming to you as the only or necessary story of how
         | software gets made.
         | 
         | "And then", "And then", "And then"... "One feature at a time,
         | you just keep stacking more and more on."
         | 
         | There's no foresight. There's no planning. There's no
         | architecture. There's no readiness. There's no preparation.
         | Tomorrow's needs are always beyond the horizon and today's are
         | to be approached strictly on their own terms.
         | 
         | If you've taught software development to anyone, you're used to
         | seeing this behavior. Your student doesn't know what they don't
         | know (because you haven't taught them yet), and they're
         | overwhelmed by it, so they just scramble together whatever they
         | can to make it past the next most immediate hurdle.
         | 
         | Much (not all) of the industry has basically enshrined that as
         | the way for whole teams and organizations to work now,
         | valorizing it as an expression of agility, nimbleness, and
         | efficiency, and humility.
         | 
         | But it inevitably results in code and modules and programs and
         | services and systems where nothing fully snaps together and
         | _operational_ efficiency is lost at every interface, and
         | implementations for new features /challenges need to get
         | awkwardly jammed in as new code rather than elegantly fleshed
         | out as prefigured opportunities.
         | 
         | So you're right that most modern projects eventually just
         | become swamped by these inefficiencies, with no ripe ways to
         | resolve them. But it's not because of Rails vs Go or something,
         | it's because pretty much everyone now aspires to build
         | cathedrals without committing to any plans for them ahead of
         | time.
         | 
         | What they gain for that is some comfort against FOMO, because
         | they'll never face yesterday's plan conflicting with tomorrow's
         | opportunity (since there is no plan). But they lose a lot too,
         | as you call out very well here.
        
           | ljm wrote:
           | This has been a real challenge in the early startup setting
           | where everything is built to a prototypical standard with the
           | belief that you're basically buying time until runway is no
           | longer a problem.
           | 
           | And whatever truth there is in that, it's all out of balance
           | and lacks pragmatism. Sometimes the pragmatic choice isn't to
           | keep slapping shit together, it's to step back and see what
           | the big picture is. Even if you don't have PMF yet, you need
           | to design for _something_ on the off chance it succeeds or
           | you need to pivot.
           | 
           | And it's not even a pure tech issue, it can be as simple as
           | thinking how a team of engineers could contribute, how you
           | could onboard someone new and have them up and running
           | quickly, what happens if you fire someone who kept everything
           | in their head...
        
           | nrr wrote:
           | "There's no foresight. There's no planning." Couple that with
           | "as an expression of agility," and it really rings true to
           | me. I've worked in enough shops where the contractual
           | obligations preclude any ability to slow down and put
           | together a plan. A culture where you're forced to go from an
           | angry phone call from the suits to something running in
           | production in mere hours is a culture that finds building
           | bookcases out of mashed potatoes acceptable.
           | 
           | The best environment I've ever worked in was, ironically
           | enough, fully invested in Scrum, but it wasn't what's typical
           | in the industry. Notably, we had no bug tracker[0], and for
           | the most part, everyone was expected to work on one thing
           | together[1]. We also spent an entire quarter out of the year
           | doing nothing but planning, roleplaying, and actually working
           | in the business problem domain. Once we got the plan
           | together, the expectation was to proceed with it, with the
           | steps executed in the order we agreed to, until we had to re-
           | plan[2].
           | 
           | With the rituals built in for measuring and re-assessing
           | whether our plan was the right one through, e.g., sprint
           | retrospectives, we were generally able to work tomorrow's
           | opportunity into the plan that we had. With the understanding
           | that successfully delivering everything we'd promised at the
           | end of the sprint was a coin toss, if we were succeeding a
           | lot, it gave us the budget to blow a sprint or two on chasing
           | FOMO and documenting what we learned.
           | 
           | 0: How did we address bugs without a bug tracker? We had a
           | support team that could pull our andon cord for us whenever
           | they couldn't come up with a satisfactory workaround (based
           | on how agonizing it was for everyone involved) to behavior
           | that was causing someone a problem. Their workarounds got
           | added to the product documentation, and we got a product
           | backlog item, usually put at the top of the backlog so it'd
           | be addressed in the next sprint, to make sure that the
           | workaround was, e.g., tested enough such that it wouldn't
           | break in subsequent revisions of the software. Bad enough
           | bugs killed the sprint and sent us to re-plan. We tracked the
           | product backlog with Excel.
           | 
           | 1: Think pairing but scaled up. It's kinda cheesy at first,
           | but with everyone working together like this, you really do
           | get a lot done in a day, and mentoring comes for free.
           | 
           | 2: As it went: Re-planning is re-work, and re-work is waste.
        
         | hinkley wrote:
         | I worked on a NodeJS app that was cpu bound long before I got
         | there. Wages of making a zero code SaaS and thinking they could
         | dynamically render all pages and charge customers a premium for
         | it forever. Started losing out to simpler alternatives.
         | 
         | At one point I shaved almost 20% off TTFB just by cutting the
         | setup overhead of all that telemetry, tracing, logging, feature
         | toggle, etc code that had become idiomatic and set up first
         | thing every time you instantiated an object. By my estimate I
         | cut the overhead by less than 2/3 (couple big dumb mistakes,
         | and less than 1/2 of what was left after), so that's still over
         | 1/8th of the overall page weight. All before a single service
         | call. But at least I think I pulled the slope of the perf
         | regression line down below the combined trend of faster VMs and
         | faster EC2 instances.
         | 
         | Bookkeeping and other crosscutting concerns add up,
         | particularly when every new feature gets it. It's that old joke
         | about how you can put so many gauges on an engine that you know
         | its precise speed at all times, but that speed is 0.00.
        
       | phamilton wrote:
       | Beyond application performance, I've also found a non-trivial
       | drag from the complexity of scaling Rails horizontally. Databases
       | need connection pools, which must be external. Processes vs
       | threads ratio need to be tuned and retuned and the application
       | evolves. Deployments take longer to fully roll out a hundred
       | containers. It's a bunch of tiny bits of complexity that add up.
       | 
       | Moving to a fully concurrent runtime (rust, golang, JVM, beam,
       | etc) suddenly makes so much of that no longer a concern.
        
       | hyperman1 wrote:
       | I have a chain of applications. A postgres spits out data. A java
       | application makes a CSV from it. A apache+php application takes
       | that csv, removes the first column (we don't want to publish the
       | id), then sends it to the destination. Both postgres and java do
       | significant transformations, but the bottleneck for that chain is
       | the PHP code. I already optimized it, and it is now literally 3
       | lines of code: read line, remove everything before first ',' ,
       | write line. This speeds up things enormously from the previous
       | php doing fgetcsv/remove column/fputcsv, but still removing the
       | PHP (keeping only apache ) from the chain doubles the speed of
       | the csv download.
        
         | weaksauce wrote:
         | why does the java application/postgres output the id instead of
         | omitting it in the first place?
        
           | aetherson wrote:
           | Adding PHP to your stack in order to drop a column from a
           | result is a WILD decision.
        
       | FridgeSeal wrote:
       | I've seen "oh it's IO-bound, there's no point optimising for
       | performance" rolled out so many times, I'm convinced it's
       | basically lost any intended meaning and as simply a cover-all
       | defence, that many devs don't appreciate how quick current
       | databases and IO actually is.
        
         | anarazel wrote:
         | The absurd thing about it is that IO bound code IME often is
         | way easier to optimize than CPU bound code. Adding batching or
         | pipelinining is often almost mechanical work and can give huge
         | speedups.
        
       | nokun7 wrote:
       | Great breakdown of the "IO bound Rails app" myth! I really
       | appreciate the focus on profiling and understanding actual
       | application behavior rather than relying on assumptions. The
       | points about threading, database interactions, and context-switch
       | overhead are spot on and offer a fresh perspective on how
       | performance issues are often misunderstood.
       | 
       | It's interesting how easy it is to default to surface-level fixes
       | instead of addressing the root cause. Have you thought about
       | adding a case study or some benchmarks for common workloads? It
       | could really help teams put these ideas into practice.
        
         | byroot wrote:
         | The lobste.rs maintainer kindly offered to collect data in
         | production: https://github.com/lobsters/lobsters/pull/1442
         | 
         | I haven't taken the time to fully analyze the data, but surface
         | level show lobster.rs is indeed ~40% IO, as suspected:
         | https://gist.github.com/byroot/e1fcfd1f8e172f5d9c5bfecefebdb...
        
       | alkonaut wrote:
       | The article gives an example of how apps are usually benchmarked
       | with log queries. But in a real world scenario wouldn't you just
       | analyze what the app is actually doing and see whether what
       | fraction it actually waits for IO, waits for sync primitives, or
       | something else? Why do logging for this? It's the performance
       | equivalent of printf debugging.
        
         | byroot wrote:
         | I use simple logging code to show how the data is collected.
         | 
         | In a real production setup, that timing would be reported into
         | Datadog / NewRelic or whatever APM service, but collected in a
         | similar way.
        
           | alkonaut wrote:
           | Sure but if you look at that logging data which you might be
           | collecting regardless, adn you see anything that in the
           | slightest way suggests a performance issue, or if you look at
           | your hosting bill and wonder where the money is going, or if
           | you need fast CPUs or fast disks etc, just having good
           | recorded perf data would be one of the first things you'd do?
        
             | byroot wrote:
             | I guess I really don't understand what you are trying to
             | say.
        
               | alkonaut wrote:
               | I'm saying you'd just record perf stats with a normal
               | profiler and never do any sort of decision based on what
               | you read in a log?
        
       ___________________________________________________________________
       (page generated 2025-01-25 23:00 UTC)