3 ms·
The reason why CGI is slow is because it is missing many things that would typically be cached in persistent processes. Loading and starting the executable or i
by kevincox 3y ago
The reason why CGI is slow is because it is missing many things that would typically be cached in persistent processes. Loading and starting the executable or interpreter can be slow (especially if you have lots of imports) but that probably isn't the biggest issue. Bigger issues are backend connections such as databases or external APIs. If those are on localhost than it is entirely possible that CGI will perform well enough for your application. But even if they are a handful of milliseconds away it can add up real quickly.
If you don't have these issues than by all means use CGI. There is nothing wrong with it. But at some point having an in-memory cache is just going to be faster, even if your executable and various other dependence are cached by the OS page cache. Your in-memory "cache" will have all of the common data it needs pre-parsed and ready to use and may have backend connections already warmed up.
- mrweasel 3y agoIsn't web-functions/lambda going to have similar issues? They'll perform well enough, if they aren't terribly complex, but adding to many dependencies start up times could be an issue.
- mplewis 3y agoSome web function runtimes like AWS Lambda let you put dependency initialization outside the function itself. Depending on what the runtime does, you may get to keep these cached values for future invocations.
- kevincox 3y agoSort of, but lambda-like runtimes typically act like something in between process-per-request and a long-lived persistent process. AWS lambda at least will reuse the same execution environment for multiple requests. So simple things like caching in a hashmap or using a database connection pool will work and avoid these startup costs. You still get CGI-like behaviour on cold starts but once warm they perform much more like a persistent process. This ends up being a pretty good trade off for many use cases because you use little resources when idle and get pretty good latency and resource usage when busy.
- tsimionescu 3y agoFrom my experience with a slow start AWS lambda, the cold start times really bite you every time you have a spike in concurrent requests, even a small one. We had a pretty pathological case (a Python lambda with rather a lot of code which also required a network adapter to be deployed inside a VPC), but the point still stands even for better behaved lambdas: if you have a steady 1000 concurrent connections say, you'll be OK for days. But if then 100 more concurrent connections need spinning up, you'll have 100 users taking the slow path, which may be their first experience of your service.
- kevincox 3y agoYes, it is a tradeoff. I don't know about AWS Lambda specifically but this sort of behaviour can be mitigated with pre-warming. So you always maintain N% extra capacity. Then you allow the instance to start up before actually sending any requests there. Of course if you scale up >N% faster than your instances can start up you will still hit a cold start, but if your app starts relatively quickly and you maintain a decent buffer that should be fairly rare.
- Buttons840 3y agoI wonder what percentage of users would place the blame on your service? I know my entire life I've had unreliable Internet connections and so my instinct is to first shake my fist at my ISP.
- markstos 3y agoYes, and Lambas have become far more complex than CGI.
- elric 3y agoI think the problems you highlight are mostly orthogonal to CGI. You absolutely can use an in-memory cache with CGI. You could even use shared memory if you were so inclined. Startup cost is potentially an issue, but the trick there is to make sure the CGI bits are tiny and can delegate heavy duty work to longer running processes. You don't have to execute the entire workflow within the CGI script.
- spiffytech 3y agoAre there any advantages to CGI talking to another process (IPC I guess?), or is it just serving traffic with a long-lived process, but with extra steps?
- kevincox 3y agoBasically a long-lived process with extra steps. But if all it does is hold a few file descriptors open it can minimize your long-lived state (which can reduce bugs) and in-use memory between requests.
- elric 3y agoAside from simplicity, there aren't any major advantages AFAIK. Even in 2024, it only takes about 5 minutes to set up a system with Lighttpd and CGI. When all you have to do is expose an existing binary over the web, then HTTP<->CGI<->Binary is pretty convenient. Just write a tiny CGI wrapper and you can call it a day. Edit: missed a bit of context, so here's a more relevant answer: Long lived process with extra steps, yes, but the key part is that the CGI binary (and the webserver) are sacrificial. They handle input validation and all that nasty stuff, while the real application is safely tucked away. If the CGI binary dies, or the web server crashes, things become unreachable but otherwise operational. It's occasionally a useful characteristic.
- vidarh 3y agoDepends how much you push over the process boundary. The main benefit of a CGI model is guaranteed cleanup of resources, so the more you keep in the CGI side of that boundary, the less potential you have for resource leaks, or data leaking between requests. If you just push your entire app into a long lived process you gain nothing. If you e.g. "only" use it to cache resources that are slow to obtain access to (e.g. connections to slow starting resources, data that is slow and expensive to parse), then it could be a win. But it's been years since the last time I felt that win was sufficiently worth it to design a system that way. In theory, I still love the idea of a guaranteed clean slate, but I'm not willing to give up the slow starting language runtimes I prefer today (e.g. Ruby) for the sake of it.
- vidarh 3y agoCGI weren't all that slow even 25 years ago if you minded exactly the things you mention. That is: Compiled, statically linked code, that also took advantage of the fact the process would die (which meant e.g. not freeing memory, or doing any other unnecessary cleanup), and get external APIs out of band where possible unless their latency were low. I built a webmail service like that back then, and we measured vs. FastCGI and other options and it performed well. Wouldn't necessarily do it again, because it was a product of competing against slow interpreted languages on a whole server park slower than my laptop, where you could recover from the process creation overhead just by being faster. Things like the static linking gave us a double digit percentage reduction in latency over the "naive" CGI approach, and cut the advantage of persistent processes right down. Being able to drop almost all memory frees helped a lot too (we'd only free particularly large objects, otherwise just let the OS free everything at once when the process died) Overall we were mostly IO bound despite using CGI even with the significantly worse process creation overhead of Linux back then (it was never "bad" compared to e.g. Windows as far as I remember, but there were significant efforts to improve it)