Settings

Theme

Show HN: FrankenPHP, an app server for PHP written in Go

frankenphp.dev

368 points by kdunglas 3 years ago · 92 comments (91 loaded)

Reader

pbowyer 3 years ago

Good. PHP-FPM needs a challenger as anyone who has tried to debug it or its pools knows. Or to tune it (so many modes, so many configuration options).

Litespeed's PHP LSAPI [1] shows how good performance can be with other setups. It'll be great if FrankenPHP gets to the same state.

1. https://www.litespeedtech.com/open-source/litespeed-sapi/php

  • apocalyptic0n3 3 years ago

    PHP-FPM can be so unreliable too. It'll just go down randomly without any warning or logs at all. As you said, it's impossible to debug what happens there and all you can really do is setup monitoring to detect it went down and automatically restart it.

    • xorcist 3 years ago

      That's interesting, in my experience php-fpm has been very dependable even in demanding situations where we migrate over to new backends without missing a request. PHP applications can be a different story, but php-fpm provides enough knobs to restart problematic applications on demand. It's probably that last part of a PHP stack I'd want to replace.

      Debugging application servers in production comes with its own set of difficulties, but I don't see how this one is worse than others. If anything, the ability to start new sockets without restarting the process is a plus.

      • kijin 3 years ago

        PHP-FPM has a ton of debugging, logging, and performance-tuning options, but in most popular distros most of them are disabled by default. If you get a Dockerfile from somewhere and just tweak pm.max_children, you're likely to end up with all the helpful stuff commented out.

        • xorcist 3 years ago

          Not reading the manual for software your production environment is built on? Surely you are joking.

    • noduerme 3 years ago

      Have to chime in with others... FPM has definitely been the most reliable and easily tweakable system I've had in almost 20 years of PHP work, particularly since I run a lot of legacy code off the same server in different versions of PHP.

      I feel like problems with monitoring may be down to lack of proper logging or experience. It's not like FPM is going to leak more memory than your code would in other running contexts. It doesn't "go down" since it spawns a new process for every request. My cold read is that there's something wrong with the code you're running, not with FPM.

    • jijji 3 years ago

      PHP-FPM is very easy to debug. It's as simple as setting it up to use a listening socket in www.conf (located in i.e. /etc/php/8.1/fpm/pool.d) and then running a packet sniffer (i.e. ngrep) to listen on that socket, all messages back and forth are visible at that point.

      • lapser 3 years ago

        I don't know if I'd call usage of a packet sniffer "easy to debug". Seems like it's reasonable to expect some debug option that is easy to activate in dev environments that will give you the relevant information.

        • jijji 3 years ago

          The back and forth communication is all that you care about with php-fpm, and rarely do you need to actually see it, unless you have php-fpm configured wrong for instance. The majority of debugging a php application means turning on debugging (i.e. ini_set 'display_errors' On and error_reporting('E_ALL')) and tailing a /var/log/nginx log file looking at what happened in your application.

    • amq 3 years ago

      PHP-FPM has been extremely reliable in my experience.

    • whenlambo 3 years ago

      Been using php-fpm with nginx for many-many years (and with caddy since recently) and had zero problems with it.

    • habibur 3 years ago

      My experience had been the opposite.

      PHP-FPM had been more resilient than the other services I run. Rock solid.

abrztam 3 years ago

What is the difference between this and Roadrunner? It seems to do the same stuff.

https://github.com/roadrunner-server/roadrunner

  • kdunglasOP 3 years ago

    The approach isn't the same:

    Roadrunner executes php-cli and connects it to its web server through GRPC; FrankenPHP uses an ad-hoc SAPI, it is more like Apache's mod_php, the Go code uses the PHP interpreter as a library, it's all in the same process.

    RoadRunner only has a worker mode, and can only work with compatible apps; FrankenPHP has a "standard" mode compatible with all existing applications, and a worker mode that requires some code changes (like RR).

    RoadRunner runner uses PSR-7 HTTP messages; FrankenPHP uses plain old superglobals and streams (both in normal and in worker modes), they are reset after each handled request.

    RoadRunner is battle-tested and production ready; FrankenPHP is experimental and not yet ready for production use.

    FrankenPHP can also be used as a Go library to integrate PHP into any Go program or server (theoretically, it should be possible to integrate FrankenPHP into Traefik or the local Symfony web server, which are written in Go).

    • rustatian-me 3 years ago

      Hey @kdunglas :) Nice project :) I just wanted to add a few notes:

      > Roadrunner executes php-cli and connects it to its web server through GRPC;

      RR communicates with the PHP process via pipes (stdin/out/err) via our protocol (similar to the IP protocol, we also have a tcp/unix-socket options and shared memory is coming soon). The RR server itself then has various options to connect to the world:

      - gRPC (w/o the need to install the `gRPC-php` extension), HTTP (with a lot of different middleware), HTTPS, fCGI. - Queues: kafka, rabbitmq, sqs, beanstalk, nats (with like priorities and so on) - KV/noSQL: memcached, redis, boltdb, memory. - Workflows: Temporal.

      I might forget smt, but the main idea is to have a very extensible server. With very little knowledge about Go, you may extend it with your plugins and build your RR binary. Or even embed the RR server into your Go application (https://github.com/roadrunner-server/roadrunner/tree/master/...).

    • bogdanu 3 years ago

      Since it's not using PSR-7, does it mean that you could even run WordPress?

      • kdunglasOP 3 years ago

        I haven't tested it yet, but yes it's a stated goal.

        • bdg 3 years ago

          Just some experiences I've had putting large PHP frameworks into strange spaces:

          1. Most PHP frameworks are designed to have all state destroyed at the end of a request. I was trying to integrate a commercial ecommerce framework with something like Road Runner and another one that I forget the name of. The framework had a DI system which provides each module with its own private instance of all injected instances, so having a "worker" that doesn't "boot" everything each request sounded like a good idea (boot was expensive, and a lot of logic was storing module-specific state in module-private instances). I hit a few barriers inside the framework, but actually a lot of them were due to dependencies on PHP global state following state-of-the-art conventions and best practices. It lead to spooky side-effects like cache from one page view loading into the next, and worse. Getting frameworks to run in a loop in PHP can often lead to sharing state in code that was designed in a way that state is assumed to be destroyed soon.

          2. PHP depends on lots of unexpected things. If you're deep into language internals already you probably know this however. I was putting symfony2 into a PHP Unikernel a long time ago, and it drove me a bit crazy because everything in the file system, SAPI, locales, etc... it was all missing bridges to something it expected the OS to provide. I ended up making an immutable FS with Nginx and PHP all static linked to each other, but it was really just enough for a POC, a real production ready env would have been a lot more effort. The point is, PHP has a lot of unexpected "hooks" into environments it has grown up around that might be well hidden.

          Anyway, really cool project and I like the concept of using a SAPI, I think it has big potential.

          • mst 3 years ago

            Re (1) how feasible would it be to basically teach the PHP VM the equivalent of fork() so you can do all the booting once but still have a fresh copy of the end result on each request?

            I mean, it's not going to be as cheap as getting the thing to actually run in a loop, but it might be enough cheaper than the booting process to be worthwhile.

            • eyberg 3 years ago

              This is one of the things FrankenPHP is dealing with (w/out fork).

              The big 'gotcha' here is that the heap is typically shared amongst threads in a process and that's where globals tend to live. However, you could make a heap per thread (which is kind of how some implementations of isolates work). You lose a bit of perf by doing this but it does deal with the global stomping problem.

              We've (NanoVMs) looked at this a few times. It can be done but as bdg mentioned most frameworks expect state to be in a completely clear so the real challenge is that you have to go in and deal with each framework itself (for instance using WP as an example).

              Anyways, I thought FrankenPHP was pretty cool so went ahead and package it up for Nanos: https://repo.ops.city/v2/packages/eyberg/frankenphp/0.0.1/x8... .

              If you had a php framework that wasn't so dependent on global state you could definitely make something way more performant.

              In general scripting languages and their usage of global state is a recurring concurrency issue but I'm hopeful that the isolate pattern will catch on in other languages to help alleviate it.

            • bdg 3 years ago

              It's a cool idea but I haven't explored this in detail so I don't have a good answer. I haven't touched PHP in 2 years but they have a new feature which might achieve some of those design goals you mentioned:

              https://www.php.net/manual/en/opcache.preloading.php

              > preload.php is an arbitrary file that will run once at server startup (PHP-FPM, mod_php, etc.) and load code into persistent memory.

              It's interesting to me, because in a big ecomm framework we were getting 80ms PHP page loads, with something like preload it could probably be moved down to 25 or 30ms.

  • floppydisc 3 years ago

    no. 1 feature: goofier branding

0xbadcafebee 3 years ago

So it's mod_php for Caddy, in reverse?

The traditional idea is to build a plug-in for the parent webserver. By essentially "making a fork" of Caddy, if you want to add other plugins to Caddy and then incorporate them into FrankenPHP, it's a lot more work. If instead you ship a PHP plugin to Caddy, you can manage Caddy instead and mix and match different functionality in one place.

But I guess it's heretical to suggest somebody use plugins in Go, if the whole idea is everything is a static binary.

  • mholt 3 years ago

    I don't think this forks Caddy. Rather, it is a Caddy plugin: https://github.com/dunglas/frankenphp/blob/main/caddy/caddy....

    It uses mainline Caddy: https://github.com/dunglas/frankenphp/blob/main/caddy/go.mod

    • 0xbadcafebee 3 years ago

      Can I use an officially released build of Caddy and have that official Caddy executable load FrankenPHP?

      • mcpherrinm 3 years ago

        Caddy plug-ins are statically compiled into the server. Generally you need to build your own binaries if you’re using plugins. There’s an official script, xcaddy, to make that easy.

        • mekster 3 years ago

          Hard to think this method takes off as people are reluctant to run their own binary that can't be updated with the rest of the system which in turn gives less users to report bugs and blogs about usage/reviews.

          • ratorx 3 years ago

            Depends on the package manager. Some package managers support overlays so you can keep your package modifications as a layer on top of the base package.

            I know you can using Nix for sure. I think Arch/pacman (Arch Build System) and Gentoo/portage (since it is source-based) also have this concept, but I’ve never used it. No clue about other package managers.

            The downside is that package updates will require a recompilation, but you can use binary caches to centralise this slightly.

      • kdunglasOP 3 years ago

        Definitely! (using xcaddy and compiling PHP yourself, for now)

  • mike_d 3 years ago

    Early Hints breaks a lot of the common APIs (like FastCGI/FPM) where you are expected to have one request and one response. I don't know how Caddy specifically works, but I suspect that may be the reason for the fork.

    • mholt 3 years ago

      Early Hints support was added to Caddy's proxy by Kevin Dunglas, the author of FrankenPHP. No fork required for Early Hints!

  • tomcam 3 years ago

    I love the idea but as far as I can tell there isn’t a portable way to do plug-ins in go that works with windows, is there?

  • chrsig 3 years ago

    go does have some support for dynamically loaded shared libraries. I haven't used it, so I can't speak to how good it is, or any pitfalls.

    https://pkg.go.dev/plugin

    • philosopher1234 3 years ago

      It is not good. I don’t know of any major projects that use it. Instead people: * compile in plugins

      * run plugins as separate processes and expect them to implement a specific RPC api

      Compiling in plugins is more practical in Go then it might be in another language, due to the regularity of the go build system.

b_sanchez 3 years ago

I was in the conference (20221014, afup 2022), where frankenphp was released live. The conference was really interesting and answer many (if not all) of the questions raised in comments.

I'v checked and unfortunately the video recording is not available for now, I will post a link here as son as available.

p4bl0 3 years ago

If I understand it correctly the idea of worker mode is to have a persistent application running where you can have the same objects in memory from one request to another rather than relaunching the app from scratch (requiring files, constructing objects, fetching data from some database) again for each request.

Is that it?

borancar 3 years ago

This is amazing, and way more advanced than what was there. Going to definitely use it on the next project.

Previously, there was https://github.com/deuill/go-php which was PHP5 and PHP7, but you needed to build PHP with ZTS. I had to forke it to focus on PHP5 and bring some improvements - my primary goal was to port some legacy PHP over iteratively via the Strangler pattern. If it can still be useful to some, the fork is here - https://github.com/borancar/go-php

NorwegianDude 3 years ago

Not really sure what the pros of this is. Simple to deploy in a docker image? Didn't know that was an issue. I guess performance also takes a hit, and that worker mode is a good amount slower than Swoole?

Some benchmarks against mod_php, nginx+php-fpm and swoole would be nice.

timw4mail 3 years ago

So after looking at the slidedeck on the authors blog, I'm rather confused. How does FrankenPHP keep the code in memory if each request is in a separate memory space?

nobleach 3 years ago

I've been watching how Go and Rust tooling has been finding its way into the JavaScript ecosystem. I've been out of the PHP realm for about 10 years but I did find RoadRunner for PHP at one point. That's also an app server written in Go I believe. I wonder how this compares.

  • fideloper 3 years ago

    My understanding is RoadRunner is more like FrankenPHP's "worker mode" (RoadRunner only makes sense in context of Laravel Octane), where as FrankenPHP can run "normally" as well.

  • jedisct1 3 years ago

    Don't forget Zig, with Bun.

seabrookmx 3 years ago

So.. it's like gunicorn (pre-fork web server) but for PHP and built on top of Caddy?

Looks neat!

jeffersonheard 3 years ago

If this isn't pronounced Frankenphip I will be disappointed.

tiffanyh 3 years ago

I might be missing the obvious but why would you add extra complexity to your infrastrucutre setup when PHP can be run natively from within caddy, apache, nginx via fastcgi.

  • fideloper 3 years ago

    Removing php-fpm + nginx from a container sounds AMAZING to me. If I can just have one thing in a container (plus a code base), that would be a LOT simpler than:

    nginx, php-fpm, some init system, and the convoluted configuration needed to get logs out via Docker's logging mechanism.

    • nickjj 3 years ago

      What about having 1 Dockerfile but run php-fpm in 1 container with nginx in a 2nd container?

      This pattern is common for background workers too, such as running gunicorn + celery in separate containers (Python tools) but the same image is used for both. You can change the CMD at runtime by overwriting it (for example the `command` property in Docker Compose and Kubernetes).

      This avoids needing to hack around things at the Docker level to install an init system and it gives you a way to split things out at runtime so you individually scale and log them as needed.

      It does mean a change in your app would restart nginx since the image would change for both but this isn't that big of a deal. If that was a deal breaker then you could create separate images for each one to still avoid an init system running in your container.

      • fideloper 3 years ago

        Very doable. Although on systems like Fly where you attempt to build one container to run an app, it’s a bit overkill.

        What I dislike most about php-fpm is the logging mechanism (or lack thereof). You need to configure it to capture stderr from php processes and then have PHP send logs to stderr. Sorta wonky.

        • nickjj 3 years ago

          > Although on systems like Fly where you attempt to build one container to run an app, it’s a bit overkill.

          I never used Fly but hosting a web + worker combo is common in a lot of tech stacks and with Docker it's really common to use 1 image to drive both containers with a different command. If Fly doesn't support that then I'd suggest hosting things elsewhere. Docker Compose and Kubernetes supports this no problem, it's a really basic / day 1 use case.

    • PlutoIsAPlanet 3 years ago

      I've recently changed our nginx/php-fpm containers to caddy/php-fpm

      Caddy has a supervisor plugin, so can start php-fpm itself and the containers entrypoint can be Caddy, which achieves similar objectives here that Caddy becomes a PHP application server.

      • francislavoie 3 years ago

        FrankenPHP will perform better than php-fpm though, in worker mode, because it doesn't need to bootstrap fully on every request. In the conference slides, Kevin showed php-fpm had a 12ms request latency, whereas FrankenPHP had 3ms.

        But yes, the supervisor plugin is definitely nice to be able to wrap up Caddy + php-fpm in a single container. Makes shipping it easier, especially with the PHP code (because both Caddy and php-fpm need access to the code; Caddy so it can serve static files and check for the existence of PHP files, and php-fpm to actually run your code).

        • mekster 3 years ago

          So, not for everyone, especially when you have to manage your own binary.

          No one can feel 0.01s faster load time and there will be a dozen more things to tune in your app than shave that tiny bit off.

          • francislavoie 3 years ago

            There's really not much to manage. It's not in any way a challenge, especially if you ship it as a Docker container.

            It's true it's probably not for everyone though; the worker mode will not work with legacy apps that heavily use globals and statics, and works best with frameworks that have infrastructure in place to reset in-memory state on each request. Symfony and Laravel are set up for this, for example. API Platform as well, which the author of FrankenPHP himself authored.

            The decreased latency means you can serve more requests with the same hardware, with less of an energy cost. That's a win-win. It's not simply about the user experience.

            Also remember that loading a webpage often takes many requests. Every little bit shaved off of one request is multiplied. Add onto this that it supports 103 Early Hints which can tell the client to start loading static assets ahead of time, this dramatically reduces total page load times because it avoids the cascade (i.e. browser needing to wait to read the HTML to know what JS/CSS to load). That definitely has a noticeable effect to users.

  • mholt 3 years ago

    With this you don't need FastCGI. FrankenPHP is simpler and less complex: just run it directly from your Caddy web server.

  • CoolCold 3 years ago

    Short answer would be: sole solo developer experience (DX).

    Longer answer: avoiding infrastructure setup - for those who has infrastructure it makes no sense, for those who knows how to run things in Docker and not need much beyond it - eliminates the need of getting familiar with Docker Compose.

    Having php stay in memory should make performance benefits [and make sense for the project], but that's different story.

codegeek 3 years ago

I see a bit of C as well ? Also, do we need to use Docker ? I am very interested in trying but wanted to check.

If it is Go, can I not just compile the binary and execute ?

  • francislavoie 3 years ago

    C is necessary because PHP is written in C. So CGO is used to interface with PHP directly from Go, from a Caddy plugin.

    Docker is definitely not necessary, but it is the easiest way to ship something that just works. Since you need a bunch of build dependencies to compile PHP, the installation steps are different for every distro to pull those in with whatever's your package manager.

  • TheRealPomax 3 years ago

    When do things ever "just compile"? I assume the Docker image is because this is a pre-alpha and the docker image ensures that no one needs to go through hours of dependency/config hell because the docker image is set up with everything necessary already, letting you focus on alpha-testing this and reporting bugs.

    • calvinmorrison 3 years ago

      typically I find CMake would do this, or Make, to verify dependencies.

      Coupled with a packaging system, like debian gives you, this is all pretty straightforward.

      I ran into this yesterday, and turns out I don't want to install docker just to build a program...

      • TheRealPomax 3 years ago

        Right, but there are as many setups as there are potential users, so even if Debian works, that doesn't mean other linux flavours will work as easily, or flavours of BSDs, and then also Macs, and even WSL, or even just plain old Windows.

        Having an "everyone gets the same thing, so no one wastes time on bootstrapping" solution is a perfect use-case for Docker. And then once the bugs have been found and fixed, and the code is production-ready, you can focus on documenting and scripting the setup procedures for the various operating systems.

        • andirk 3 years ago

          As an aside, may I ask what people's opinion of running docker containers in prod is? The joke "But it works on my local--" "Then ship your local".

          • francislavoie 3 years ago

            The big win of Docker for me is parity between dev and prod. I absolutely run on Docker in prod. It is not just a dev tool.

          • tracker1 3 years ago

            Been using docker in prod for over 5 years now...

        • badcppdev 3 years ago

          I don't understand. I thought you needed different docker files for different architecture? x64 vs M1 chips?

      • TheRealPomax 3 years ago

        Except of course in this case that's not what you'd be doing. You'd be installing Docker to help beta-test a program, not "just build a program". If you want to help an open source project succeed, having to install Docker is kind of a trivial cost. If you want to use this... probably not a good idea, it's extremely not ready for general use =)

      • gocartStatue 3 years ago

        Or Nix :)

nonoesp 3 years ago

It'd be great to see how a Laravel app could run on FrankenPHP and see if there are speed gains.

My current setup is a DigitalOcean Droplet with Nginx and php-fpm.

simlevesque 3 years ago

Félicitation !

Do you have any success story with this application server ?

treahauet 3 years ago

Congratulations! This looks neat!

green-salt 3 years ago

I'll have to try this out!

chx 3 years ago

Looking at the forked PHP source https://github.com/php/php-src/compare/master...dunglas:php-... I do not expect compatibility issues.

k__ 3 years ago

PHP is already FrankenPerl, and Perl FrankenAwk?

What happened to the times where some crazy person would simply slap together an interpreter and call it a language?

Somehow, language creation got more and more sophisticated these days.

  • mhd 3 years ago

    Really? Aren't there a lot of slapped together LLVM frontends these days?

  • bdg 3 years ago

    In general I wish the "culture" of programmers would differentiate the "language" itself from the "things it is linked with" and "the ecosystem of available packages". We get a lot of pointless arguments where person 1 is talking about case 2, and another is thinking about case 3.

    PHP, Ruby, and Node all use things like cURL or the same PCRE regex lib, but I've seen uninformed or misguided arguments about "how Node is better than PHP at making HTTP requests because of axios", and not "I like Axios more than Guzzle3".

password4321 3 years ago

PHP in .NET: https://www.peachpie.io

Keyboard Shortcuts

j
Next item
k
Previous item
o / Enter
Open selected item
?
Show this help
Esc
Close modal / clear selection