Skip to content

Conversation

@mnapoli
Copy link
Member

@mnapoli mnapoli commented Oct 3, 2021

Following discussions and iterations with @Nyholm, he identified the following ways to improve the DX for Symfony users:

  • be able to skip PHP-FPM -> handle HTTP requests directly with the Symfony Kernel. Why:
    • may provide a performance boost by keeping the Symfony process alive between requests (boot only once, handle multiple requests with the same Kernel)
    • may help sharing database connections by keeping them alive in the same process
  • be able to handle Lambda events with Symfony services (not classic Bref file handlers)

This PR exposes a possible solution. It integrates with Bref's PSR-11 support for resolving handlers: handlers are resolved from the Symfony container.

Principle

The bref/symfony-bridge package automatically registers the Symfony Kernel: it will be used by Bref to resolve Lambda handlers.

Example:

# serverless.yml
functions:

    # HTTP with PHP-FPM, the usual approach (no changes in this PR, it's still the recommended approach)
    web1:
        handler: public/index.php
        layers:
            - ${bref:layer.php-80-fpm}
        events:
            - httpApi: '*'

    # HTTP without FPM: the kernel handles HTTP requests directly in a single PHP process
    web2:
        handler: App\Kernel
        layers:
            - ${bref:layer.php-80}
        events:
            - httpApi: '*'
        environment:
            # The Symfony process will restart every 100 requests
            BREF_LOOP_MAX: 100

    # Handle any other event, e.g. SQS, with a Symfony service:
    sqsHandler:
        handler: App\Service\MySqsService
        layers:
            - ${bref:layer.php-80}
        events:
            # Read more at https://github.com/brefphp/symfony-messenger
            - sqs: [...]
        environment:
            # The Symfony process will restart every 100 invocations
            BREF_LOOP_MAX: 100

How it works

With this PR, Bref delegates resolving the handler to the Symfony container.

File names still work (e.g. public/index.php): these will still be resolved by Bref as usual.

Class names are resolved by Symfony's container (e.g. App\Kernel or App\Service\MySqsService).

There is one particular case: App\Kernel (the Symfony kernel) is integrated with Bref (PSR-15 integration in Bref) to handle API Gateway's HTTP events.

Performances

I deployed a new Symfony 5.3 application with a single "Hello world" route. It runs in APP_ENV=prod in Lambda, but deployed without cache (I wasn't benchmarking the cold starts so I left it up to Symfony to create the cache on the first invocation in Lambda).

I measured the Lambda "Duration" for 500 hot invocations:

  • FPM (traditional approach):
    • median: 2.5ms
    • p90: 3ms
  • App\Kernel in the function runtime, with the process kept alive:
    • median: 1.2ms
    • p90: 1.4ms

Again, cold starts are intentionally omitted because AFAIK they aren't impacted here.

Conclusion: web apps could save ~1ms over their response time. They may be able to save more the Kernel boot is long, or by keeping some connections (e.g. db connections) alive.

@mnapoli mnapoli added the enhancement New feature or request label Oct 3, 2021
}
},
"files": [
"src/bootstrap.php"
Copy link
Member Author

Choose a reason for hiding this comment

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

This file registers the Symfony container in Bref. It will be called for every invocation/request.

@mnapoli
Copy link
Member Author

mnapoli commented Oct 4, 2021

Performance improvements may actually be better than just shaving the 1ms of booting the Symfony Kernel: https://blog.laravel.com/vapor-octane-support-is-now-available

@t-richard
Copy link
Member

t-richard commented Oct 4, 2021

First, thanks to everyone involved in moving this forward 💚

If I understand correctly, this is NOT using the Symfony Runtime and https://github.com/php-runtime/bref would become useless.

Also, I suppose the referenced services like App\Service\MySqsService need to be Lambda handlers registered as a Symfony service for this to work. Right ?

The approach seems very pragmatic given what Bref already allows to do with PSR-7/15 and PSR-11.

I think it makes a ton of sense and the benefit could be huge (see below).

Looking at Vapor + Octane, they have an interesting feature which is limiting the time the DB connection is kept open. Didn't looked at how they did it but it could be interesting to do the same to avoid keeping DB connections stale for too long and running out of connections.

For example, I have a project that runs a scheduled Lambda once a week but this lambda sends dozens of SQS messages each of those triggering a Lambda doing the same (I was sweaty when working on this 😰). In a couple of minutes, I invoke hundreds of different Lambda containers, if thay all kept a connection open i could rapidly run out of connections.


I've tried this on a project of mine using the following

  • PHP 7.4
  • Symfony 5.3
  • API Platform + EasyAdmin
  • Doctrine with Postgres (4 SQL queries on this endpoint)
  • This bridge and pre-warmed cache
  • API Gateway REST API

Running a bunch of benchmarks I noticed an average warm response time of 500ms with the current stack and 300ms when using this PR 🎉 The same gap was noticed in the Cloudwatch Lambda execution times.

Cold starts didn't seemed to be impacted by this change.

This is very empiric and on a small dataset but the benefit seem large enough to be worth it.

Let me know if i can help 🙂

@Nyholm
Copy link
Collaborator

Nyholm commented Oct 4, 2021

Thank you for this PR. I also appriciate the work to move this forward.

I like the implementation and I think it makes sense. This is a Symfony specific approach and I see that there are a lot more steps compared to an alternative.

PHP-FPM:
Start -> Boot FPM -> Lambda Request -> fcgi Request -> PHP super globals -> Application (which turns it in to Http Foundation request)

This PR (web2 (App/Kernel):
Start -> Lambda Request -> PSR7 Request -> Http Foundation Request -> Application

Alternative:
Start -> Lambda Request -> Http Foundation Request -> Application


As @t-richard points out. There is no use of the runtime component. So any special logic you have in your index.php will only run locally, and not on Lambda.

/**
* Create and return the Symfony container.
*/
private function symfonyContainer(): ContainerInterface
Copy link
Collaborator

Choose a reason for hiding this comment

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

What if I want to get a container but not from a Symfony Kernel?
Or is this exclusively a Symfony solution?

Copy link
Member

Choose a reason for hiding this comment

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

I guess in this case you would use the default Bref PSR-11 support and not bother with this bridge ?

https://github.com/brefphp/bref/blob/master/src/Bref.php#L27

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes this is 100% about Symfony (since this is in the symfony bridge project)

@mnapoli
Copy link
Member Author

mnapoli commented Dec 28, 2021

Last steps: resolve the $context parameter to pass to closures.

@mnapoli
Copy link
Member Author

mnapoli commented Dec 30, 2021

I am going to merge and release a beta version. Let's see if everything works out fine!

I'm happy to say I think we finally have a solution that implements and supports Symfony Runtime, and still provides a great user experience with the direction integration with the container!

@mnapoli mnapoli changed the title Handle requests with the Kernel + handle events with Symfony services Handle requests with the Kernel + handle events with Symfony services - Symfony Runtime integration Dec 30, 2021
@mnapoli mnapoli merged commit 02450b7 into master Dec 30, 2021
@mnapoli mnapoli deleted the support-class-handlers branch December 30, 2021 15:08
@bakurin
Copy link

bakurin commented Feb 25, 2022

Could you help me to understand how I could use Symfony runtime with Docker image? I build my Docker image like this:

FROM bref/php-81
...
CMD ["public/index.php"]

But Lamda fails with error PHP Warning: Undefined array key "APP_ENV" in /var/task/public/index.php in line 10
Did I miss something? Any input is appreciated.

@mnapoli
Copy link
Member Author

mnapoli commented Feb 26, 2022

Please open a separate issue (or rather a GitHub discussion since this is more about support than a bug.

In your case I think you would need to set App\Kernel instead of public/index.php in CMD.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants