answear/messenger-heartbeat-bundle
Symfony bundle that adds heartbeat/keepalive support to Messenger workers. Install via Composer and run messenger:consume or messenger:failed:retry with --keepalive to keep long-running workers alive and responsive.
Installation:
composer require answear/messenger-heartbeat-bundle
The bundle auto-registers in config/bundles.php via Symfony Flex.
Enable Heartbeat:
Add --keepalive to your existing messenger:consume and messenger:failed:retry commands:
php bin/console messenger:consume async --keepalive
php bin/console messenger:failed:retry --keepalive
Verify: Check logs for heartbeat activity (enabled by default in v3.2.0+):
grep -i "heartbeat\|keepalive" var/log/dev.log
Scenario: Long-running workers (e.g., processing large files) frequently disconnect due to idle timeouts in RabbitMQ. Solution:
supervisor.conf or deployment script to include --keepalive:
command=php bin/console messenger:consume async --keepalive
connection.last_activity logs to confirm heartbeats are active.Worker Initialization:
KeepaliveListener.--keepalive, it registers a global signal handler (e.g., SIGALRM) to send periodic pings.Heartbeat Mechanism:
Integration Points:
--keepalive is passed to messenger:consume/failed:retry.symfony/amqp-messenger (RabbitMQ, Qpid).KeepaliveInterface (e.g., custom heartbeat intervals).Selective Activation:
# Enable for specific queues only (via environment variables or config)
if (app()->environment('production')) {
$this->addOption('--keepalive', null, InputOption::VALUE_NONE, 'Enable heartbeat for production workers');
}
Custom Heartbeat Interval:
Modify the AMQP transport configuration in config/packages/messenger.yaml:
framework:
messenger:
transports:
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
options:
heartbeat_interval: 60 # Custom interval in seconds
Logging and Monitoring: Use Symfony’s logger to track heartbeat status:
// In a custom listener
public function onMessageSent(TransportSentEvent $event) {
if ($event->getTransport() instanceof AmqpTransport) {
$this->logger->info('Heartbeat active', [
'connection' => $event->getTransport()->getConnection()->getName(),
'last_activity' => $event->getTransport()->getConnection()->getLastActivityTime(),
]);
}
}
Docker/Kubernetes Deployments:
Ensure the --keepalive flag is passed in your container’s command:
# docker-compose.yml
services:
messenger:
command: php bin/console messenger:consume async --keepalive
--keepalive across all workers without benchmarking overhead (e.g., CPU/network usage).KeepaliveInterface to avoid conflicts.Symfony Version Mismatch:
composer why-not symfony/messenger:^7.2 to validate compatibility.AMQP Broker Incompatibility:
# config/packages/messenger.yaml
options:
heartbeat: 60 # Broker-level heartbeat (RabbitMQ-specific)
Signal Handler Conflicts:
SIGALRM) may interfere with other processes in shared environments (e.g., Docker containers with multiple workers).numprocs=1.Message Duplication:
skip_message_on_failure in listeners (v4.1.0+):
// config/packages/messenger.yaml
failure_transport: failed
transports:
failed:
dsn: '%env(FAILED_TRANSPORT_DSN)%'
options:
skip_message_on_failure: true
Log Overload:
config/packages/monolog.yaml:
handlers:
main:
level: warning # Suppress INFO-level heartbeat logs
Verify Heartbeat Activity:
# Check if keepalive is active
php bin/console debug:container | grep KeepaliveListener
# Tail logs for heartbeat events
tail -f var/log/dev.log | grep -i "heartbeat\|keepalive"
Connection Health Checks:
Use Symfony’s TransportConnection events to monitor status:
// src/EventListener/ConnectionListener.php
use Symfony\Component\Messenger\Transport\Connection;
public function onConnectionEstablished(ConnectionEvent $event) {
if ($event->getConnection() instanceof Connection) {
$this->logger->info('Connection established', [
'last_activity' => $event->getConnection()->getLastActivityTime(),
]);
}
}
AMQP-Specific Issues:
rabbitmqctl list_connections | grep -i "heartbeat\|idle"
heartbeat settings in the broker’s configuration file.Performance Profiling:
--keepalive:
# Compare with/without heartbeat
hyperfine --warmup 5 'php bin/console messenger:consume async --keepalive'
Custom Heartbeat Interval:
Implement Answear\MessengerHeartbeatBundle\Keepalive\KeepaliveInterface:
use Answear\MessengerHeartbeatBundle\Keepalive\KeepaliveInterface;
class CustomKeepalive implements KeepaliveInterface {
public function getInterval(): int {
return 45; // Custom interval in seconds
}
}
Register it in services.yaml:
services:
Answear\MessengerHeartbeatBundle\Keepalive\KeepaliveInterface: '@App\CustomKeepalive'
Preventing Signal Conflicts: Override the signal handler for specific environments:
// src/Keepalive/EnvironmentAwareKeepalive.php
use Answear\MessengerHeartbeatBundle\Keepalive\KeepaliveInterface;
class EnvironmentAwareKeepalive implements KeepaliveInterface {
public function __construct(private string $environment) {}
public function getInterval(): int {
return $this->environment === 'production' ? 60 : 30;
}
}
Integration with Monitoring: Export heartbeat metrics to Prometheus:
use Symfony\Component\Messenger\Transport\Connection;
use Prometheus\CollectorRegistry;
public function onMessageSent(TransportSentEvent $event) {
if ($event->getTransport() instanceof AmqpTransport) {
$registry = new CollectorRegistry();
$registry->getOrRegisterCounter('messenger_heartbeat_active', 'Heartbeat status')
->inc($event->getTransport()->getConnection()->isHeartbeatActive() ? 1 : 0);
How can I help you explore Laravel packages today?