Skip to content

Conversation

@AlliBalliBaba
Copy link
Contributor

#1509 introduced a new way to assign workers to requests without requiring path matching. This inspired me to create a worker configuration that would allow the worker file to sit outside of the public path and to generally optimize file access in the 'modern' framework case.

This PR achieves this with a 'php_worker' directive

:8000 {
    php_worker /anywhere/worker.php
}

#or

:8000 {
    php_worker {
        root /somewhere
        file_server # on by default
        file /anywhere/worker.php
        ...
    }
}

In a previous implementation the configuration looked like this. Let me know if you think this or something like this is better

:8000 {
    php_server {
        index worker {
            file /anywhere/worker.php
        }
    }
}

Additional performance benefits are:

  • the file_server only makes a single 'file-exists' check, all PHP routes fall back to the worker
  • less path calculations like 'sanitizedPathJoin'

This PR also does some refactoring to split up FrankenPHPApp and FrankenPHPModule

@henderkes
Copy link
Contributor

henderkes commented May 12, 2025

I feel like it's not very intuitive that defining one of those would let all php requests be served by that file. Is there any use case for this outside of situations where the whole app is served through a single entry point?

Or is this just a convenience wrapper to skip the php_server block? That would probably be a good thing, especially with the file_server checks. Would it match the performance of an @assets matcher + php directive?

@AlliBalliBaba
Copy link
Contributor Author

Yeah this essentially does 3 things:

  • shorthand for the common framework case (Symfony Runtime, Laravel Octane, ...)
  • allows worker outside of public directory
  • better performance.

If you think this can be made more intuitive somehow, let me know.

It is pretty much equivalent to the following configuration (quick local bench 20 CPU cores ~100.000 RPS)

{
    frankenphp {
        worker {
            num 7
            file public/worker.php
         }
    }
}

:8000 {
    php_server {
        root public
        try_files {path} worker.php
    }
}

Instead with php_worker (quick local bench 20CPU cores ~120.000 RPS):

{
    frankenphp
}

:8000 {
    php_worker {
        num 7
        file /anywhere/worker.php
        root public
    }
}

@AlliBalliBaba AlliBalliBaba marked this pull request as ready for review May 12, 2025 20:09
@henderkes
Copy link
Contributor

Hmm, on the one hand I like this because it's short and elegant, on the other it's unintuitive (needs good documentation) and I'm not sure if it solves an actual problem or only offers a shortcut - at the cost of an additional directive to maintain.

@AlliBalliBaba
Copy link
Contributor Author

As mentioned, the main benefit is being able to put the worker outside of the public directory. It's specifically designed for Frameworks like Symfony runtime, Laravel Octane, Yii, etc (probably a majority of worker mode users)

Having the option to put the worker file directly into src or vendor instead of public makes it more maintainable.

The performance benefit is due to reduced file operations since it's specifically designed for the 'single-entrypoint' use case.

I'm also fine with somehow integrating this into existing directives.
php_server worker {
or
php_server { index worker {
or
php_server { single_entrypoint worker {
...

@henderkes
Copy link
Contributor

henderkes commented May 17, 2025

As mentioned, the main benefit is being able to put the worker outside of the public directory. It's specifically designed for Frameworks like Symfony runtime, Laravel Octane, Yii, etc (probably a majority of worker mode users)

I agree, but I strongly doubt we will see that happening any time soon. I expect at least 10-15 years to pass until the major web servers support this - and until they do, frameworks can't change the index location.

I'm also fine with somehow integrating this into existing directives.
php_server worker {
or
php_server { index worker {
or
php_server { single_entrypoint worker {
...

I'm not sure. It would be better in a way because users could choose php or php_server depending on what they need. At the same time a distinct directive could offer all the benefit and create a cleaner config.

What has always irked me about the php directive is that it's necessary to define a asset matching handler and a rewrite before, which requires using a route wrapper.

php_worker {
    file /path/to/worker.php
    root /path/to/project/public
    env APP_ENV prod
    assets {
        path /assets/**
        path /bundles/**
    }
}

could work around that nicely, without a performance penalty.

In the end it's a question if the benefit is worth the maintenance burden. That's not up to me to say.

@AlliBalliBaba
Copy link
Contributor Author

I agree, but I strongly doubt we will see that happening any time soon. I expect at least 10-15 years to pass until the major web servers support this - and until they do, frameworks can't change the index location.

Frameworks not being able to change the index location is kind of the point. Laravel for instance will copy a file to the public directory at runtime as a workaround.

What has always irked me about the php directive is that it's necessary to define a asset matching handler and a rewrite before, which requires using a route wrapper. ... could work around that nicely, without a performance penalty.

The overhead of a route directive is not really significant, it's more about reducing unnecessary file operations.
The current php_server route setup was taken from caddy-fpm and tries to cover all cases, which makes it inefficient in some cases (and pretty complicated).

It would also be possible to instead add a special case for
try_files {file} index.php to make it more efficient.

What would you think about a match directive inside the worker to match it to certain paths in the public directory?

worker {
    file /anywhere/worker php
    match index php
}

@@ -0,0 +1,250 @@
package caddy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app.go?

frankenphp is redundant

"github.com/dunglas/frankenphp/internal/fastabs"
)

/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we use double-slash comments, and maybe can we just double quotes or backticks around frankenphp

"github.com/dunglas/frankenphp/internal/fastabs"
)

/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here (double-slashes and double quotes)

@@ -0,0 +1,749 @@
package caddy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

module.go?

@henderkes
Copy link
Contributor

henderkes commented May 24, 2025

What would you think about a match directive inside the worker to match it to certain paths in the public directory?

worker {
    file /anywhere/worker php
    match index php
}

I like this! It would be less maintenance than a new directive and looks like it would carry all the benefit, albeit being slightly more to write. Or, pulling the uno_reverse:

example.com {
    php {
        match assets/* file_server /anywhere/public
        match * worker /path/to/worker.php
    }
}

equivalent to:

example.com {
    php {
        root /anywhere/public/
        match assets/* file_server
        match * worker /path/to/worker.php
    }
}

or (now, with the directory limitation)

example.com {
    root /anywhere/public
    route {
        @assets {
            path /assets/*
        }
        file_server @assets
        rewrite worker.php
        php {
            root /anywhere/public/
            worker worker.php
        }
    }
}

@AlliBalliBaba
Copy link
Contributor Author

Hmm I'll probably split this PR up into a refactor section and a section that adds some kind of 'match' feature. Not sure yet how to implement the performance improvements in that case, but it's probably possible somehow

@henderkes
Copy link
Contributor

I'd wait what Kevin and Rob think about the match feature first. The refactoring is a great idea either way, though.

@AlliBalliBaba
Copy link
Contributor Author

I'll probably re-open a refactor PR once #1601 is merged

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants