13 ms·
The article points out that arguably the best part of PHP is the "shared nothing lifecycle". That each request starts new, and the process dies at the end of th
by Klathmon 10y ago
The article points out that arguably the best part of PHP is the "shared nothing lifecycle". That each request starts new, and the process dies at the end of the request. It's by far my favorite part, and I completely agree that it makes "reasoning about" (boy do I hate that phrase...) your program much easier.
Why are there no other "competitors" in this space? Why do most other languages go with the alternative route of using an event loop or daemonizing your "server" to serve up requests? I can understand why Node.js does it (otherwise the async nature would just be an annoyance for no gain), but why haven't there been any other server-side languages that not only work well like this, but actively target it?
It seems like it would be easier for everyone involved. You wouldn't need much of a GC when the process is killed every time, you don't need to worry about async IO or multi-threading or catching errors or any of the other annoyances that come with most of the traditional "event loop" way of doing things.
So what's the catch?
- germano 10y agoElixir and the Phoenix framework work like this, with a process spawned per request.
- monocasa 10y ago'Process' is an extremely overloaded term. It's more of a green thread for Erlang VM applications.
- wtetzner 10y agoIt is a green thread, but Erlang's green threads each have their own heap/garbage collector, so they're not sharing memory.
- Klathmon 10y agoIve been hearing nothing but good things about Elixir, I may have to check it out. Any recommended places to start with?
- artificial 10y agohttp://elixirforum.com http://elixirforum.com is a good place to check out. There are a few excellent books out there to help you get started with OTP, which makes Elixir beastly, and Phoenix (a web framework) as well. The forums are an excellent place to find deals on books, too.
- anarazel 10y agoThis is my least favorite part. As soon as your program gets more complicated the amount of repeated parsing, compilation and setup you need to do for every request causes massive speed and structural issues.
- leeoniya 10y ago> the amount of repeated parsing Which should be near 0 if you can be bothered to enable OPcache in php.ini. Not only parsing/disk reading but also bytecode generation are bypassed.
- anarazel 10y agoSure. But that doesn't really address the problem, just makes it smaller. You still need to reinstantiate a lot of runtime state.
- leeoniya 10y agoI can't tell you how much more "runtime state" exists in PHP than in say Java. In both cases you need to initialize some sort of security-aware context. Once you've eliminated parsing, disk i/o and compilation phases. You should be on pretty even ground of bytecode vs bytecode. Java has a Hotspot JIT, so it will probably win there. I think a JIT is coming to PHP as well since the architectural rework that went into PHP7.
- bpicolo 10y agoThere's more too it than that though, yeah? Say you use a service that uses oauth2 authentication. You need to do a request to pull in a oauth token. If you lose all state between requests, you waste time re-fetching tokens. In PHP, persisting this sort of thing between requests becomes a painful task instead of a triviality
- Klathmon 10y agoBut in most languages http requests should be stateless. Even in node or Java you should be hitting the db to pull that info anyway, as sharing the last info in the process leads to scaling issues as well as big problems if the application crashes. The alternative is crazy sharding systems which are becoming more and more difficult as devices are less and less "static"
- kuschku 10y ago> So what's the catch? About 2-3 orders of magnitude in performance. That’s the catch. And it’s the reason why even with PHP you use things as opcaches, cache database results in external daemons, you use process pools with fastcgi instead of actually creating new processes, etc. And hacking those things on top of PHP make your program even worse to reason about than just using a daemonized system with actor framework.
- smsm42 10y agoUnless you're writing in pure hand-optimized assembly language, 3 orders of magnitude in performance is way more than you could claim. PHP web requests commonly execute in hundreds of ms (of course, here I generalize mercilessly, but that covers most of cases I know of and that's what most sites aim for). So you say your non-PHP requests which would do the same thing would finish in hundreds of microseconds? I have very hard time believing it. > And it’s the reason why even with PHP you use things as opcaches, cache database results in external daemons You make it sound as if the idea of caching was invented by PHP programmers to address language deficiencies. Nothing can be further from the truth. Everybody uses caches, because it's faster. If you don't use DB cache, there's no way in the universe you can make you app fast under any significant load. > And hacking those things on top of PHP make your program even worse to reason about You have very strange view of performance design if you think using caches is "hacking something on top" and has to be avoided. > than just using a daemonized system with actor framework. This looks like a complete non-sequitur - you could use actor framework and still use all the things described above, and do it in PHP, and thousands of people do exactly that. On not in PHP, if you'd like - this pattern is completely orthogonal to the language used.
- tracker1 10y agoThe system now used to serve GoDaddy's Website Builder content is written in node, deployed to clusters behind load balancers, and from there connecting to Cassandra clusters for the data... iirc, the average turn around time for a given request (including dynamic ones) is less than 12ms from the node server receiving the request to delivering the response, under a load of many millions of requests per second to the system. Disclosure, I worked at GD in Website Builder when this was being developed, but haven't been there for over 2 years now.
- tyingq 10y ago>>Why are there no other "competitors" in this space? There are. Any language that supports either fastcgi, or runs as an apache plugin (or similar) functions in the "each request starts new" space. There's certainly others, but Python and Perl are both good examples. Starting new with each request does, of course, mean lots of re-work that shouldn't have to be done with each request. It does initially keep complexity down, but at scale, the applications I've worked with have to use a shared cache (like acpu) to avoid that rework and meet performance goals.
- Klathmon 10y agoI mean more of languages that actually "target" this kind of setup. I know it can result in a lot of "extra work", but I really feel like most of the pain of that could be abstracted away by the runtime. Nginx/apache setup a PHP process before the request comes in, so why couldn't you expose that point to your language and let the programming language do work before that point, even "snapshot"ing it to avoid re-doing that work for each request? It just seems like there are better ways of solving this problem, because an event loop is such a "hammer" solution to the problem.
- jstimpfle 10y agoI think python has exactly that with WSGI. With this protocol you make a module containg an "application" function object which is called by the server when a request arrives. On module load time you can do any required setup work. (On my systems, by default there is some reuse of the loaded module, so you can leak state between requests. However it should be possible to avoid reused processes).
- ryanlm 10y agoCan you set up Python to have the same pattern as PHP? I've always had to proxy the web server to the Python process running HTTP.
- tyingq 10y agoPython via fastcgi, wscgi, etc, is the same pattern. Or mod_python inside Apache.
- wvenable 10y agoThe startup time can be pretty extreme -- although you can mitigate much of this with various caching strategies but the typical PHP request involves reading dozens to maybe hundreds of files from disk, compiling them into byte code, and then executing that byte code, and then deallocating everything. And even if you cache everything, you still have the code necessary to setup the runtime environment of you application which runs on every request and does the same thing. But still, I do actually like that model and it does have many advantages for development. But it's really hard to work in PHP, the language, after using something better.
- alaaibrahim 10y ago> although you can mitigate much of this with various caching strategies but the typical PHP request involves reading dozens to maybe hundreds of files from disk, compiling them into byte code, and then executing that byte code, and then deallocating everything. For the conversion to bytecode, that is what apc is for.
- Jach 10y agoStill often faster than for whatever unholy filter chain of jetty services to get through the stack to the code I care about.
- Siyo 10y agoThere are plenty of competitors in this space, they just don't use the CGI model of spawning a new _OS_ process for every request. You have Erlang/Elixir that have their own lightweight processes that don't share memory and have independent GC. And you can build similar systems on Java/Go. The difference is that in those language you have a choice. There are certain types of applications that you just can't reasonably make in PHP.
- sciurus 10y ago"they just don't use the CGI model of spawning a new _OS_ process for every request" Which isn't what PHP does, fwiw.
- Siyo 10y agoWhat do you mean exactly? I mean yeah, it's a bit different with FastCGI when you have a pool of persistent processes. But you still basically boot up your whole application every time a request comes in. Opcode caching helps with loading code, but not execution. And you're still working with OS processes for concurrency, which is not ideal.
- sitharus 10y agoThe catch is overhead. There are things you need at the start of each request - global configuration, database connections etc - that other systems do once at startup. PHP has to do this every request.
- sfrailsdev 10y agoProbably because allocating a thread to every request is less efficient then a bunch of event loops with async. Ideally you do both, but a lot of languages choose to make concurrency something done at the process level and are event driven asynchronous web servers which are deployed as multiple processes, which adds a lot of overhead compared to threads. I think Golang has a nice balance with goroutines, as I understand things.
- grok2 10y agoPerformance/scaling?
- bkanber 10y agoPHP is perhaps one of the easiest technologies to scale horizontally, because of its statelessness.
- egh5oon 10y agoHA!
- bkanber 10y agoYou should add substance to your comment. If you disagree, say why.
- aikah 10y ago> PHP is perhaps one of the easiest technologies to scale horizontally, because of its statelessness. PHP is no more stateless than any other language out there. Statelessness is a design choice, not something PHP gives you for free. If your app write things on the local disk or use the default PHP session handler then it is not scalable by default. In order to write code that scales you need to make it scalable, it's not something automatic and certainly not special to PHP.
- bkanber 10y agoI would argue that PHP is more stateless than Node, for instance. Things that happen in previous requests do not affect following requests in PHP, unless you're explicitly using shared state. That's not the case in node, it isn't the case with most Java HTTP server implementations, and it wasn't the case in Rails back in the day (don't know how it is now).
- minitech 10y agoAny language is stateless until you explicitly share state; the other languages just make it easier to share state. It’s not a bad design decision – makes them more versatile, or at least more cleanly usable outside of serving webpages – but is something to avoid in similar applications for sure.
- Kenji 10y agoExactly. The "shared nothing lifecycle" really lets the programmer (knowingly or unknowingly) embrace RESTful design. Too bad the language doesn't have strict typing like Java. Otherwise it'd be even more convenient and safer in my eyes.
- kuschku 10y agoPHP 7 actually has strict typing like java, but no generics. It’s really interesting, last time I worked with PHP was a few years ago, then recently I wrote something in PHP 7, and it is a lot safer. (But still no compile time type checking because there is no compile time).
- Nullabillity 10y agoBecause it's very wasteful to keep reinterpreting the same page every time, as opposed to just running it in RAM from a warm JIT. You can get the same reasoning benefits in pretty much any language by just not using any global variables. Yes, you're still running a framework/server with internal state, but with PHP you've got the same "issue" with Apache/Nginx.
- bkanber 10y ago> very wasteful Define "very". Because for me it's more like "a few hundred microseconds per request".
- deleted 10y ago[deleted]
- TazeTSchnitzel 10y ago> Because it's very wasteful to keep reinterpreting the same page every time, as opposed to just running it in RAM from a warm JIT. You are running it from RAM. PHP's OPcache, and HHVM, will both compile the code only once (well, HHVM will compile it multiple times due to JIT, but it only touches the disk once) and cache it.
- davidw 10y ago> "reasoning about" (boy do I hate that phrase...) A bit tangential, but I find myself disliking it too, for no good reason that I can think of. Perhaps it's something of a crutch in that it's really "I like it more, but 'reason' sounds better" ? Not sure that's quite it, either, as in the above case with a "new everything per request", it's certainly true that it's easier to think about what's happening.
- jandrese 10y agoI like the old hacker term for "reasoning about". Grok. It's much easier to grok a program when everything just runs from top to bottom and doesn't jump to a different third party library every 3 lines or have 15 layers of indirection and frameworks.
- VintageCool 10y agoI think that the abstracted language makes it easier to admit that something is difficult to reason about.
- davidw 10y ago"Grok" is good. Perhaps it's that it sounds less pretentious, somehow?
- williamgb 10y agoInteresting! I have somehow developed the opposite opinion. The phrase "to reason" is plain English and uncomplicated. Grok, by comparison, is arcane and to the uninitiated is semantically opaque. The latter strikes me as a more suitable vehicle for demonstrating pretension.
- davidw 10y agoFair enough. I never claimed my dislike of "reasoning about" things was rational! 'Grok' definitely has a context, that likely does not include talking to non-hacker types like clients, board members, or whatever.
- pcwalton 10y ago> You wouldn't need much of a GC when the process is killed every time Yes, you do. You'd be surprised how easily memory balloons out of control unless you regularly run a GC. Log minor collections sometime in Java or JS engines if you don't believe me… Even worse, if you don't run GC regularly, you will suffer terrible locality as execution continually grows the heap (not to mention the overhead of continually requesting free pages from the OS).
- tracker1 10y agoSorry, but GP is right here... you really don't need GC if the entire process ends and the memory is completely freed at the end of the request. Not that I feel cgi is the most efficient way to handle requests... but not needing the complexity of GC could be one advantage of that approach...
- pcwalton 10y agoIt may seem that way at first, but space leaks during individual requests are way too easy otherwise. Think about something as simple as, say, parsing Mustache templates. The parser is probably internally making lots of little substrings to record the names of directives as it comes to them. You would think that this is O(1) in space, since you only need one (or a small number) of strings at a time, but without a GC the space requirements balloon to O(size of input). Being able to dump the memory at the end of a request doesn't help you if you keep blowing out your cache during each one. I speak from experience: there have been many attempts to mandatorily delay collection until the end of the event loop in browsers, to achieve greater responsiveness. It has always failed. You can add heuristics to try to preferentially collect at the end of the event loop, but you really need to be able to GC anytime.
- tracker1 10y agoI'm not completely disagreeing... I've worked on pipeline transformers in node where I exposed gc and ran it after each item in the pipeline was done. It would grow insane without it... likewise I can see that if you're transforming a lot of records for delivery in PHP it could bloat as well. However, if you're handling requests well within a given memory threshold, it isn't always something you should worry about until it becomes a problem. In which case, you'll probably want to look at something like go, rust, erlang or others to handle improvements, or break off heavy parts into sub/micro-services.
- mikey_p 10y agoThe downside is literally exactly the same as the upside: you have to bootstrap from literally nothing for every single request. Think about how ActiveRecord in Rails reads from the DB schema to generate it's magic methods, etc. This is usually done on startup or at least cached in memory once it's loaded. This is something that you have to manage more carefully with a PHP app since you can't have anything initialized before a request starts. Code paths, auto-loaders, database connections, config files, etc. All that has to happen over and over for each request. You can cache that stuff, but it's still overhead that isn't central to handling a request. There's actually work being done to build PHP frameworks that get around this with ReactPHP, ZeroMQ, Photon, etc. Essentially this is what HHVM does as well, by at least keeping your compiled bytecodes in memory (similar to APC/eAccelarator/ZendOptimizer, etc). See: http://reactphp.org http://reactphp.org http://www.photon-project.com http://www.photon-project.com https://gnugat.github.io/2016/04/13/super-speed-sf-react-php.html https://gnugat.github.io/2016/04/13/super-speed-sf-react-php... Even the idea of an alternative runtime for PHP (ala HHVM) is nothing new: http://quercus.caucho.com http://quercus.caucho.com
- toast0 10y ago> Code paths, auto-loaders, database connections, config files, etc. All that has to happen over and over for each request. Do you most of those need to happen at all, ever? In my mind, auto-loader is a sign that nobody knows what's being loaded (if they did, they would just require it). Requires should be full pathed (because have you seen what happens when you search the include path). Mysql_pconnect has been doing connection pooling for a long time, mysqli supports it too (so yes, you do need to connect sometimes), anyway, if you're loading a framwork, loading that probably takes longer than connecting to a mysql server in the same colo.
- dpcx 10y agoIMHO, developer time is more expensive than CPU time; I don't want or need to have to write out all of those requires. That's the job of the autoloader. And if I'm writing my code remotely well enough, each section only uses the things that it needs. Granted, there may be some things that each request get that they don't need with the autoloader. But I can optimize later. "Make it work, make it right, make it fast"
- flushandforget 10y agoMany CGI scripts were coded like this. Here is an example in C: https://www.cs.tut.fi/~jkorpela/forms/cgic.html https://www.cs.tut.fi/~jkorpela/forms/cgic.html Php was born out of trying to reduce a lot of that cruft and blending code and html output easily. And that's what initially made it a winner.
- dom0 10y ago> I completely agree that it makes "reasoning about" (boy do I hate that phrase...) your program much easier. Actually I have never seen that this was a problem in practice with the application server model. I therefore don't think this is a relevant argument.
- suneilp 10y agoStarting new, regardless of caching data/bytecode, still means you have to spend time bootstrapping the application. Other languages, whether directly or indirectly manage cleanup via GC or recycle processes, like UWSGI for Python. I also don't find any particular benefits over other languages with "reasoning about". As a PHP application grows and a team restructures it into something more mature it ends up looking the same minus the event loop (which just lives somewhere else).
- weberc2 10y agoThe real question is why does PHP depend on globals or thread locals for request state at all? PHP's "new process per request" is a solution to a problem that PHP created for itself. In other languages, the request handler receives the request state as an argument, so when the request completes there is no state to reset. > It seems like it would be easier for everyone involved. You wouldn't need much of a GC when the process is killed every time, you don't need to worry about async IO or multi-threading or catching errors or any of the other annoyances that come with most of the traditional "event loop" way of doing things. Firing up a new process (or even a new thread) for each request is very expensive--much more so than a generational GC. Async IO also mitigates this (why have 10K parked threads consuming memory all your memory if all of them are stalled, waiting for database)? Services that spawn a thread for each request run into the C10K problem--they can only serve 10K simultaneous clients. Regardless of your concurrency model, you can't ignore errors--you still need to dispatch the right status codes and error information (not to mention logging, etc). You should check out Go. You write concurrent code and the runtime effectively manages a thread pool under the covers so you get all of the performance benefits of async programming with the developer-friendliness of synchronous programming. In the case of a web app, you just need to write your request handlers--the web server manages the request concurrency for you, and the language's runtime manages the parallelism.
- tyingq 10y ago>>PHP's "new process per request" is a solution to a problem that PHP created for itself True, but I'm not aware of any notable language that did it differently in the mid 1990's when PHP was rolled out. PHP does not spawn a new OS process per request these days. It does spawn a new interpreter, but not a new OS process. Combined with the now bundled opcache, the re-work per request is limited to the developer's own initialization code if they don't choose to use a user-space shared cache.
- weberc2 10y ago> True, but I'm not aware of any notable language that did it differently in the mid 1990's when PHP was rolled out. The OP's question wasn't scoped to the mid-90s. > PHP does not spawn a new OS process per request these days. It does spawn a new interpreter, but not a new OS process. Like I said, the same holds true even for an OS thread. Anyway, if it's not a new OS process, then some sort of GC is needed, contrary to the OP's statement. > Combined with the now bundled opcache, the re-work per request is limited to the developer's own initialization code if they don't choose to use a user-space shared cache. Again, these are all solutions to problems PHP invented for itself. Other languages don't require special caching mechanisms; they get by on normal scoping rules and shared memory.
- awinder 10y agoI may be waaay off base here, but wouldn't amazon's lambda function architecture sort of mirror the benefits of shared-nothing? It doesn't necessarily work like that under the hood, at least if I remember correctly your lambda function may live on past one request, but you must code as if that were the case
- bkanber 10y agoIt does, but it comes with deep tie-in to AWS as well. But also, you don't typically use lambda to serve pages or a full application, more commonly it's used for specific processing tasks, individual endpoints, and generally code that can run standalone.
- kevingadd 10y agoI worked on a large-scale (30m+ users, tons of concurrent sessions and high request rates) site + online service + native client setup in PHP for years. This was never actually an asset. Startup times were huge, lack of shared state meant extra requests to database servers (and to memcache instances to avoid database round trips) to get the same data per-request. Each request got longer and a given page or api call might issue many duplicate requests in a short period of time. Plus since PHP doesn't have viable threading or async (definitely didn't at the time) each memcache or sql request had to be synchronous, resulting in even more wasted time. At the very least memcache had good support for batch operations so we could manually batch our queries. Overall though this is not an asset. It's only the 'best' part of PHP if you don't want to think about writing good software that manages shared state. To be fair, many applications don't actually benefit from being good code, so PHP is probably a good fit for those.
- aikah 10y ago> Why are there no other "competitors" in this space? You can do the exact same thing with any language that supports CGI. You can compile a Go binary, put it on an Apache server and you'll get the "shared nothing lifecycle" just like PHP.
- antjanus 10y agoNow hang on, doesn't Go do that?
- skarap 10y agoIn fact you can create a "daemonized" webserver in PHP and handle each request in a special handler function. For async case there is ReactPHP (http://reactphp.org/ http://reactphp.org/), don't know any product for sync case, but creating one is certainly possible. And you can also set up Ruby, Python, Rust, Haskell, Java, etc to create a new interpreter/vm/whatever for every request. All of those languages have libraries to support CGI. Using the same OS process but restarting the interpreter/vm will be a bit more complex, but absolutely possible. You can even implement the "every file is an entry point" approach for at least Python and Ruby. The difference is what is the intended use for each platform. E.g. creating a new OS process, loading the PHP interpreter, bootstrapping Wordpress, getting some data from the DB and rendering a page is possible under 600ms while creating a new OS process, loading the CPython, bootstrapping Django, getting some data from the DB and rendering a page will easily take you 4 seconds. This is because Django developers didn't expect you'll be doing that, so they put a lot of stuff in the initialization step (system checks, creating db connections, setting up logging, ...).
- di4na 10y agoErlang/Elixir would have a word. :) The catch are couple. First Concurrency is _hard_ and relying on the OS scheduler would have been really har don ressource until recently, which is why nearly noone did it. That used ot be a wart of php, only hidden by Moore's Law and recent work on OS schedulers. But yeah. Erlang do this since the 80s
- SideburnsOfDoom 10y ago> you don't need to worry about async IO or multi-threading Isn't there a downside to that? If you want to do multi-threading, you can't just do `ThreadPool.StartNew(() => doSomething())`. You're SOL.
- deleted 10y ago[deleted]
- gkya 10y agoIt's interesting that so little know what is Cgi here. It's the Common Gateway Interface and it uses environment variables to communicate to the Cgi scripts and thus the Cgi script can be in whatever language you wish. You don't even need a lib, reading env is enough. Its possible to write your Cgi scripts in assembly. So this process-per-request thing is not exclusively a Php thing.
- Klathmon 10y agoI know of CGI scripts (and the newer faster alternatives), I was wondering why more languages don't specifically target that kind of setup, playing to its benefits and trying to minimize the downsides.
- gkya 10y agoOh, sorry. I guess it's two-fold then: a) it's not as popular and cool, and then b) not much needs to be done to target it anyways, as most often being a good unix citizen suffices, you get the input and write the output to stdout, what's in between is application realm and whatever you do is up to you.
- Klathmon 10y agoNo worries! I completely understand the "trendy" part. Sadly even language/frameworks need marketing, and doing things in a "wasteful" and "slower" way but simpler is gonna be a tough sell even if it might be the best move. I just feel that there is a space for a "better PHP". One that uses a better base language, but still keeps the benefits from the language like the simplicity of the "think edit reload" cycle, the shared nothing architecture that makes programs easier to write and simpler to test, and the horizontal scaling.
- gkya 10y agoI've not worked on many CGI stuff (other than little things I use myself) but Python has all the housekeeping done. See this other post of mine that has an example of an interactive hello-world: https://news.ycombinator.com/item?id=12712067 https://news.ycombinator.com/item?id=12712067 . And then, Perl is used for all sorts of CGI stuff. Both have good database support. CGit [1] is a git web interface written in C. [1] https://git.zx2c4.com/cgit/about/ https://git.zx2c4.com/cgit/about/ P.S. An important thing that contributes to the success of PHP is the amount of hosting services that support it.