Skip to content

[Doctrine Messenger] Fix support for pgsql + pgbouncer. #53819

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 15, 2024
Merged

[Doctrine Messenger] Fix support for pgsql + pgbouncer. #53819

merged 1 commit into from
Feb 15, 2024

Conversation

jwage
Copy link
Contributor

@jwage jwage commented Feb 7, 2024

Q A
Branch? 6.4
Bug fix? yes
New feature? no
Deprecations? no
Issues
License MIT

Problem

When you use PgBouncer in front of a PostgreSQL server with transaction pooling mode enabled, the INSERT and the lastInsertId() happen in separate transactions which are separate connections/sessions when using PgBouncer. So the call to lastInsertId() fails with the following exception:

[Doctrine\DBAL\Exception\DriverException (7)]
An exception occurred in the driver: SQLSTATE[55000]: Object not in prerequisite state: 7 ERROR:  lastval is not yet defined in this session

Exception trace:
 at /app/vendor/doctrine/dbal/src/Driver/API/PostgreSQL/ExceptionConverter.php:87
Doctrine\DBAL\Driver\API\PostgreSQL\ExceptionConverter->convert() at /app/vendor/doctrine/dbal/src/Connection.php:1938
Doctrine\DBAL\Connection->handleDriverException() at /app/vendor/doctrine/dbal/src/Connection.php:1886
Doctrine\DBAL\Connection->convertException() at /app/vendor/doctrine/dbal/src/Connection.php:1253
Doctrine\DBAL\Connection->lastInsertId() at /app/vendor/symfony/doctrine-messenger/Transport/Connection.php:156
Symfony\Component\Messenger\Bridge\Doctrine\Transport\Connection->send() at /app/vendor/symfony/doctrine-messenger/Transport/DoctrineSender.php:46
Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineSender->send() at /app/vendor/symfony/doctrine-messenger/Transport/DoctrineTransport.php:72
Symfony\Component\Messenger\Bridge\Doctrine\Transport\DoctrineTransport->send() at /app/vendor/symfony/messenger/EventListener/SendFailedMessageForRetryListener.php:81
Symfony\Component\Messenger\EventListener\SendFailedMessageForRetryListener->onMessageFailed() at /app/vendor/symfony/event-dispatcher/EventDispatcher.php:220
Symfony\Component\EventDispatcher\EventDispatcher->callListeners() at /app/vendor/symfony/event-dispatcher/EventDispatcher.php:56
Symfony\Component\EventDispatcher\EventDispatcher->dispatch() at /app/vendor/symfony/messenger/Worker.php:198
Symfony\Component\Messenger\Worker->ack() at /app/vendor/symfony/messenger/Worker.php:174
Symfony\Component\Messenger\Worker->handleMessage() at /app/vendor/symfony/messenger/Worker.php:109

Solution

Wrap the INSERT and lastInsertId() with a single transaction, then when lastInsertId() is called, it will be within the same session that the message was inserted in.

In addition, this PR adds the ability to use PostgresSQL RETURNING id clause instead of calling lastInsertId() so we can get the id of the inserted message in one operation instead of two.

TODO:

  • Add test for table not found scenario when inserting a message.
  • Add tests for when lastInsertId returns false, int and string.
  • Is there a place where I can write an integration test for this behavior that already exists?
  • Investigate using pgsql RETURNING clause to simplify this. The insert can return the id after the message is inserted.
  • Squash commits to one clean commit before merge.

@jwage
Copy link
Contributor Author

jwage commented Feb 7, 2024

I need to work on this some more. Tests are failing.

@jwage
Copy link
Contributor Author

jwage commented Feb 7, 2024

I don't think these test failures are related to my changes.

@dunglas
Copy link
Member

dunglas commented Feb 7, 2024

For an integration test, you can do something like in #53733.

Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

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

Random comments :)

@jwage
Copy link
Contributor Author

jwage commented Feb 13, 2024

For an integration test, you can do something like in #53733.

Thanks @dunglas. This was helpful. I was able to add pgbouncer to the integration tests and write a test that runs through pgbouncer.

// handle setup after transaction is no longer open
if ($this->autoSetup && $e instanceof TableNotFoundException) {
$this->setup();
goto insert;
Copy link
Member

Choose a reason for hiding this comment

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

The goto looks ok here, but another option would be to use a loop. This may increase readability (even if it's more a question of personal taste here).

@fabpot
Copy link
Member

fabpot commented Feb 15, 2024

Thank you @jwage.

@fabpot fabpot merged commit 189bfeb into symfony:6.4 Feb 15, 2024
@jwage jwage deleted the fix-doctrine-messenger-pgbouncer branch February 15, 2024 07:55
This was referenced Feb 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants