10 ms·
Even with things like Python, CGI is pretty fast these days. If your CGI script takes a generous 400 milliseconds of CPU to start up and your server has 64 cor
by kragen 1y ago
Even with things like Python, CGI is pretty fast these days. If your CGI script takes a generous 400 milliseconds of CPU to start up and your server has 64 cores, you can serve 160 requests per second, which is 14 million hits per day per server. That's a high-traffic site.
That is, if your web service struggles to handle single-digit millions of requests per day, not counting static "assets", CGI process startup is not the bottleneck.
A few years ago I would have said, "and of course it's boring technology that's been supported in the Python standard library forever," but apparently the remaining Python maintainers are the ones who think that code stability and backwards compatibility with boring technology are actively harmful things, so they've been removing modules from the standard library if they are too boring and stable. I swear I am not making this up. The cgi module is removed in 3.13.
I'm still in the habit of using Python for prototyping, since I've been using it daily for most of the past 25 years, but now I regret that. I'm kind of torn between JS and Lua.
- simonw 1y agoHere's the justification for removing cgi - https://peps.python.org/pep-0594/#cgi https://peps.python.org/pep-0594/#cgi Amusingly that links to https://peps.python.org/pep-0206/ https://peps.python.org/pep-0206/ from 14th July 2000 (25 years ago!) which, even back then, described the cgi package as "designed poorly and are now near-impossible to fix". Looks like the https://github.com/jackrosenthal/legacy-cgi https://github.com/jackrosenthal/legacy-cgi package provides a drop-in replacement for the standard library module.
- kragen 1y agoThat fails pretty hard at providing a rationale. Basically it says that CGI is an inefficient interface because it involves creating a new process! Even if that were true, "You shouldn't want to do such an inefficient thing" is very, very rarely a reasonable answer to a technical question like "How do I write a CGI script in Python?" or "How do I parse a CSV file in Python?" There are certainly some suboptimal design choices in the cgi module's calling interface, things you did a much better job of in Django, but what made them "near-impossible to fix" was that at the time everyone reading and writing PEPs considered backwards compatibility to be not a bad thing, or even a mildly good thing, but an essential thing that was worth putting up with pain for. Fixing a badly designed interface is easy if you know what it should look like and aren't constrained by backwards compatibility.
- simonw 1y agoThe main rationale is earlier in the PEP: https://peps.python.org/pep-0594/#rationale https://peps.python.org/pep-0594/#rationale
- deleted 1y ago[deleted]
- kragen 1y agoRight. Isn't that insane? If I hadn't found it by means of having modules removed that my code depended on, I would have thought it was satire. That policy, and the heinous character assassination the PSF carried out against Tim Peters, mean I can no longer recommend in good conscience that anyone adopt Python.
- hot_gril 1y agoIdk about the internal affairs, I just really don't like Python for web backend kind of things. It's taking them way too long to sort out parallelism and packaging, while NodeJS got both right from the start and gracefully upgraded (no 2->3 mess). Also I used Python way before JS, and I still like JS's syntax better. Especially not using whitespace for scope, which makes even less sense in a scripting language since it's hard to type that into a REPL.
- kragen 1y agoYeah, generally I feel like the indentation sensitivity was the right idea (the alternative evidently being worse compiler error messages, bugs like the `goto fail` vulnerability, and greater verbosity) but it causes real difficulties with the REPL, as well as with shell one-liners. Jupyter fixes the REPL problem, and it's a major advance in REPLs in a number of other ways, but it has real problems of its own.
- eurleif 1y agoNode.js actually had no parallelism at the start, other than the ability to manually spawn new processes. Worker threads were only added in 2018 with v10.5.0, and only stabilized in 2019 with v12. What Node.js had from the start was concurrency via asynchronous IO. And before Node.js was around to be JavaScript's async IO framework, there was a robust async IO framework for Python called Twisted. Node.js was influenced by Twisted[0], and this is particularly evident in the design of its Promise abstraction (which you had to use directly when it was first released, because JavaScript didn't add the async/await keywords until years later). [0] https://nodejs.org/en/about https://nodejs.org/en/about
- deleted 1y ago[deleted]
- WD-42 1y agoI don't get it. Having a complaint about Python removing CGI from the stdlib is well and fine. But then you say you'd rather consider JS, which doesn't even have a std lib? Lua doesn't have a CGI module in stdlib either.
- bravesoul2 1y agoNode.js provides the defacto standard lib for JS backend and its got a good feature set. That said these days I'd rather use Go.
- kragen 1y agoGolang seems pretty comfortable from the stuff I've done in it, but it's not as oriented toward prototyping. It's more oriented toward writing code that's long-term maintainable even if that makes it more verbose, which is bad for throwaway code. And it's not clear how you'd use Golang to do the kind of open-ended exploration you can do in a Jupyter notebook, for example. How would you load new code into a Golang program that's already running? Admittedly Python is not great at this either (reload has interacted buggily with isinstance since the beginning), but it does attempt it.
- bravesoul2 1y agoThere are live reload utilities for Go but yeah it comes down to recompiling and restarting - which is quick but any state is gone. I agree its not a rapid prototyping kind of language. AI assistance can help though.
- oblio 1y agoCan you load new code into a Node.js program that's running?
- rvnx 1y ago`eval()` to run new code and `delete require.cache` (to reload a module)
- bravesoul2 1y agoYeah high performance web used to be an art. Now it's find what you are doing that's stupidly wasteful that you did to ship fast, and stop doing that thing. Your app could add almost no latency beyond storage if you try.
- kqr 1y agoConsider Perl. It's not quite as batteries-included as Python, but it is preinstalled almost everywhere and certainly more stable than JS and Lua. (And Python.)
- kragen 1y agoYou may be interested in an article I wrote in ;login: 23 years ago: https://www.usenix.org/publications/login/june-2002-volume-27-number-3/python-or-perl-which-better https://www.usenix.org/publications/login/june-2002-volume-2... At the time Perl was the thing I used in the way I use Python now. I spent a couple of years after that working on a mod_perl codebase using an in-house ORM. I still occasionally reach for Perl for shell one-liners. So, it's not that I haven't considered it. Lua is in a sense absolutely stable unless your C compiler changes under it, because projects just bundle whatever version of Lua they use. That's because new versions of Lua don't attempt backwards compatibility at all. But there isn't the kind of public shaming problem that the Python community has where people criticize you for using an old version. JS is mostly very good at backwards compatibility, retaining compatibility with even very bad ideas like dynamically-typed `with` statements. I don't know if that will continue; browser vendors also seem to think that backwards compatibility with boring technology like FTP is harmful.
- kqr 1y agoHa, fun bit of history! Many of the listed problems with Perl can be configured away these days. I don't have time for a full list, but as two early examples: - `perl -de 0` provides a REPL. With a readline wrapper, it gives you history and command editing. (I use comint-mode forn this, but there are other alternatives.) - syscalls can automatically raise exceptions if you `use autodie`. Why is this not the default? Because Perl maintainers value backward compatible. Improvements will always sit behind a line of config, preventing your scripts from breaking if you accidentally rely on functionality that later turns out to be a mistake. https://entropicthoughts.com/you-want-technology-with-warts https://entropicthoughts.com/you-want-technology-with-warts https://entropicthoughts.com/why-perl https://entropicthoughts.com/why-perl
- 1y ago
- tonyhart7 1y agoYeah but 400ms is unacceptable this days
- kragen 1y agoThat's intended as an unreasonably high upper bound. On my cellphone, in Termux, python3 -m cgi takes 430–480ms. On my laptop it takes 90–150ms. On your server it probably takes less. I agree that tens of milliseconds of latency is significant to the user experience, but it's not always the single most important consideration. My ping time to news.ycombinator.com is 162–164ms because I'm in Argentina, and I do unfortunately regularly have the experience of web page loads taking 10 seconds or more because of client-side JS.
- cenamus 1y agoIf the whole site takes 5 seconds to fully hydrate and load its 20megs of JS I'll gladly take a server side rendered page that has finished loading in a second.
- IshKebab 1y agoI would rather take a server side rendered page that finishes loading in 100ms than 400ms.
- rvnx 1y agoNow using the solution of OP, the JS loaded website now takes 5.4sec instead of 5sec, a 10% slowdown that the users have to pay, and that will increase server costs.
- 0x000xca0xfe 1y agoAverage website loading time nowadays is measured in seconds rather than milliseconds...
- ksec 1y agoOn site like [1] Total Real Returns running on Crystal, response time could be sub 10ms but you are fundamentally limited by latency between you and server. Which could be 150ms if you visit to US server from Valeriepieris Circle, where 50% of population lives. [1] https://totalrealreturns.com https://totalrealreturns.com
- mjw1007 1y agoThe Python maintainers are removing the module _named_ cgi, but they're not removing the support for implementing CGI scripts, which is CGIHTTPRequestHandler in the http.server module. All that was in the cgi module was a few functions for parsing HTML form data.
- kragen 1y agoIt would be very difficult indeed to make it impossible to implement CGI scripts in Python; you'd have to remove its ability to either read environment variables or perform stdio, crippling it for many other purposes, so I didn't think they had done that. Even if they removed the whole http package, you could just copy its contents into your codebase. It's not about making Python less powerful. As a side note, though, CGIHTTPRequestHandler is for launching CGI programs (perhaps written in Rust) from a Python web server, not for writing CGI programs in Python, which is what the cgi module is for. And CGIHTTPRequestHandler is slated for removal in Python 3.15. The problem is gratuitous changes that break existing code, so you have to debug your code base and fix the new problems introduced by each new Python release. It's usually fairly straightforward and quick, but it means you can't ship the code to someone who has Python installed but doesn't know it (they're dependent on you for continued fixes), and you can't count on being able to run code you wrote yourself on an earlier Python version without a half-hour interruption to fix it. Which may break it on the older Python version.
- mjw1007 1y agoMy mistake. The support for writing CGI programs in Python is in wsgiref.handlers.CGIHandler .
- oasisaimlessly 1y ago[flagged]
- rvnx 1y agohttps://s3.amazonaws.com/pix.iemoji.com/images/emoji/apple/ios-12/256/direct-hit.png https://s3.amazonaws.com/pix.iemoji.com/images/emoji/apple/i...
- nurettin 1y ago<PHP> I would like to have a word.
- lucb1e 1y agoThe PHP interpreter (+mods) don't take anywhere near 400ms to start up, even on very old CPUs, though? Not sure what you mean
- nurettin 1y agoIt was a response to the person who complained about python's cgi being removed from stdlib and them wanting to go js or lua (both of which don't even have any supporting code for cgi)
- kragen 1y agoThe problem isn't that you can't write a CGI script in Python, or that it's hard to do so. The problem is that the Python standard library is being maintained under a policy of breaking new things intentionally every release, so nothing you wrote five or ten years ago works today. The `cgi` module is one thing that got broken, but there are a lot of them.
- pjmlp 1y agoI rather stick with PHP or JS, due to having a JIT in the box for such cases. Since I learnt Python starting in version 1.6, it has mostly been for OS scripting stuff. Too many hard learnt lessons with using Tcl in Apache and IIS modules, continuously rewriting modules in C, back in 1999 - 2003.
- int0x29 1y agoI don't think the JIT will help that much as each request will need to be JITed again. Unless Node and PHP are caching JIT output
- ChocolateGod 1y agoYes, both do.
- int0x29 1y agoOn the file system? CGI is a whole new process per request edit: Looks like yes for Node JS. I can't tell for PHP as I keep getting results for optcache which is different and in memory.
- pjmlp 1y agoMy point wasn't using them with CGI, rather that they have JIT in the box, nodejs with V8, PHP has zend and its own JIT since Facebook efforts with HipHop and later Hack (HHVM), both with server support as well, no need for CGI approach.
- vanviegen 1y agoNot sure what the current situation is, but PHP used to ship with APC, a shared memory cache, that enables this exactly. Even without filesystem overhead! I still miss PHP's simple deployment, execution and parallelization model, in these over-engineered asyncy JavaScripty days.
- fulafel 1y ago
- mathiaspoint 1y agoThe scary thing about CGI for me is more the shell insanity than the forking. Although I think after the shell shock RCE most servers probably switched to directly execing the CGI process.
- gsliepen 1y agoIndeed. There is no reason why CGI would need shells or scripting languages though, you can just write them in any programming language. It's not that hard; I wrote this pastebin clone in C: https://github.com/gsliepen/cbin/ https://github.com/gsliepen/cbin/
- mathiaspoint 1y agoIt's not an issue with the actual CGI program. It's hard to make exec alone work the way people expect without doing something like exec('sh', '-c',...) so a lot of servers were doing that.
- kragen 1y agoI think they were already doing that. I'd be very surprised if Apache's mod_cgi in 01997 had been spinning up a whole bash process to spawn off the CGI script as a subprocess! Certainly I haven't seen the passels of useless bash processes in ps that you would get with that approach.
- ksec 1y agoZen 6c is about to get 256 Core Per Socket, 512 vCPU / Thread, or 1024 vCPU in a Dual Socket System. That is 2560 Request Per Second ( or PageView ), and this doesn't even include caching. If I remember correctly that is about half of what StackExchange served on daily average over 8 servers. I am sure using Go or Crystal would have scale this at least 10x if not 20x. The problem I see is that memory cost isn't dropping which means somewhere along the graph the memory cost per process together will outweight whatever advantage this has. Still, sounds like a fun thing to do. At least for those of us who lived through CGI-Bin and Perl era.
- diordiderot 1y agoPeople use crystal?
- ksec 1y agoKagi is built with Crystal.
- chasil 1y agoThe problem child for classic CGI is Windows, where process creation is 100x slower than any other POSIX implementation. You can measure this easily, get a copy of Windows busybox and write a shell script that forks off a process a few thousand times. The performance difference is stark.
- kragen 1y agoThat's interesting. I hadn't thought about that. Still, fork() (plus _exit() and wait()) takes 0.7ms in Termux on my phone for small processes, as measured by http://canonical.org/~kragen/sw/dev3/forkovh.c http://canonical.org/~kragen/sw/dev3/forkovh.c. Are you really saying that it takes 70ms on Microsoft Windows? I don't have an installed copy here to test. Even if it does, that would still be about 15% of the time required for `python3 -m cgi`, so it seems unlikely to be an overriding concern for CGI programs written in Python, at least on manycore servers serving less than tens of millions of hits per day. Or does it also fail to scale across cores?
- chasil 1y agoLet's consider ten thousand forks. $ cat winforktest.sh #!/home/busybox-1.35 sh i=1; while [ $i -lt 10000 ]; do (echo $i); i=$((i+1)); done On Linux, this takes: $ cat /etc/redhat-release Red Hat Enterprise Linux release 8.10 (Ootpa) $ time ./winforktest.sh > /dev/null real 0m0.792s user 0m0.490s sys 0m0.361s On Windows, c:\users\x>ver Microsoft Windows [Version 10.0.22631.5472] c:\users\x>busybox | busybox head -2 BusyBox v1.37.0-FRP-5236-g7dff7f376 (2023-12-06 10:31:32 GMT) (mingw64-gcc 13.2.1-5.fc39; mingw64-crt 11.0.0-2.fc39; glob) c:\users\x>busybox sh ~ $ time ./winforktest > /dev/null real 3m 44.44s user 0m 0.32s sys 0m 4.90s Windows is quite a bit slower, at least with Busybox. I understand that WSLv1 presents a more efficient fork(), which is not really available to shims like Cygwin or mingw.
- kragen 1y agoThank you very much! So, that does 19999 fork()s in 224 seconds, which works out to about 11 milliseconds per fork(). (On my Linux box, it's actually doing clone(), and also does a bunch of wait4(), rt_sigaction(), exit_group(), etc., but let's assume the fork() is the bottleneck.) This is pretty slow, but it's still about 6× faster than the 70 milliseconds I had inferred from your "100× slower than any other POSIX". Also note that your Red Hat machine is evidently forking in 39μs, which is several times faster than I've ever seen Linux fork.
- lucb1e 1y ago> If your CGI script takes a generous 400 milliseconds of CPU to start up and your server has 64 cores, you can serve 160 requests per second ...and then you're wasting a 64-core server at 100% CPU load on just starting up and tearing down script instances, not yet doing any useful work. This is doing 160 startups per second, not requests per second Would be a pretty big program for it to require 400ms on a fast CPU though, but the python interpreter is big and if you have one slow import it's probably already not far off
- kragen 1y agoResponded at https://news.ycombinator.com/item?id=44483385 https://news.ycombinator.com/item?id=44483385.
- Levitating 1y ago> If your CGI script takes a generous 400 milliseconds of CPU to start up then that endpoint will have at least 400ms response times, not great
- chgs 1y agoI have cgi scripts which take dozens of seconds to run. Load time isn’t a problem for many uses.
- refulgentis 1y agoSurprised this is the top comment 12 hours in, should be intuitive to HN that 160 req/s on 64 cores is...not great!...and that's before the errors it takes to get that # (ex. all we're doing is starting the per-request executable)
- kragen 1y agoObviously you can do orders of magnitude better than 160 req/s! But if you only have tens of thousands of users or less, like all but the top thousand web sites in the world, there's at best very marginal benefit in doing so. It's spending effort to solve a problem that no longer exists. I agree that the number I gave is erroneous, but not in the direction you imply. 400ms is extremely conservative, and it's not just starting an executable. I took my 100-millisecond estimate for the time to start up Python and `import cgi`, which loads 126 modules in the version I have here on my cellphone, and multiplied it by a safety factor of 4. Even on my cellphone it doesn't take that long, although `python3 -m cgi` does, because it loads all those modules and then runs a debug dump script. The time I measured for `python3 -m cgi` on my laptop is 90–150ms. If all you are doing is starting a per-request executable, that will typically take more like 1 millisecond than 100 milliseconds. Perhaps you meant to suggest that the actual logic triggered by the request would increase this 400ms number significantly. Consider first the case where it would, for example because you do 200ms of computation in response to each request. In this case the best you could do by switching from CGI to a more efficient mechanism like FastCGI is a 3× increase in req/s. If that allows you to increase from 10 million requests per day on one server to 30 million, that could be worthwhile if you actually have tens of millions of requests per day. But it's kind of a marginal improvement; it saves you from needing two or three servers, probably. Now consider the case—far more common, I think—where your computation per request is small compared to our hypothetical 400 ms Python CGI overhead. Maybe you do 20ms of computation: 80 million machine instructions or so, enough to tie an IBM PC up for a few minutes. In such a case, the total one-core request time has gone from 400ms to 420ms, so 400ms is a very good estimate, and your criticism is invalid.
- lucb1e 1y ago> But if you only have tens of thousands of users or less Even on HN#1, you get like two requests per second for maybe 18 hours. That's three orders of magnitude below 200M/day. Shifting the argument to say that no hobby project needs this is easy. PHP being in common use already proves that starting up and tearing down all context is fine for >99% of websites. But the context we're in is an article saying one can do 200M requests per day with CGI-bin, not whether 99% of sites need that. CGI-bin is simply wasteful if the process takes 400ms before it starts doing useful work, not to mention a noticeable amount of lag for users. (Thankfully TFA specifies they're not doing that but are speaking of compiled languages) > your computation per request is [commonly] small compared to our hypothetical 400 ms Python CGI overhead. Maybe [that makes it go] from 400ms to 420ms At "only" 420ms total request time, I'd still want to take that prototype code out of this situation. Might be as easy as wrapping the code so that the former process entrypoint (__main__ in python) gets, instead, called by some webserver within the same process (Flask as the first python example that comes to mind). Going from 420ms request handling time to slightly over 20ms seems rather worth an hour of development time to me if you have this sort of traffic volume
- jwatte 1y agoIf you're necessarily going to go with Lua (not recommended, IMHO) you should at least try luau.
- kragen 1y agoThanks! What do you like and dislike about Luau and Lua in general?
- swyx 1y ago> I'm kind of torn between JS and Lua. why lua?
- kragen 1y agoSix months ago here, in response to a Lua-advocacy post that I didn't like very much, I wrote a comment where I gave an overview of what I like about Lua: https://news.ycombinator.com/item?id=42519070 https://news.ycombinator.com/item?id=42519070 Shortly after that, we had a lengthy thread about "how Lua compares to Python": https://news.ycombinator.com/item?id=42655158 https://news.ycombinator.com/item?id=42655158 Two years ago I commented https://news.ycombinator.com/item?id=38862372 https://news.ycombinator.com/item?id=38862372 listing Lua's worst flaws from my point of view.