Queue System
The Volāre application implements a robust queue infrastructure for handling asynchronous tasks using PostgreSQL for development and Amazon SQS for production.
Overview
Section titled “Overview”Key Features:
- Database-driven queues for development (PostgreSQL)
- Automatic worker management via Docker Compose
- Production-ready configuration for Amazon SQS
- Built-in monitoring and debugging commands
- Graceful restarts and failure handling
Architecture
Section titled “Architecture”Components
Section titled “Components”Application Code -> Queue Connection -> PostgreSQL Jobs Table | v Queue Worker Container -> Job Handler -> Success/FailureInfrastructure
Section titled “Infrastructure”Development Environment:
- Queue Driver: PostgreSQL database driver
- Worker Process: Dedicated Docker container (
queueservice) - Management: Docker Compose (no Supervisor needed)
- Persistence: Database tables (
jobs,failed_jobs,job_batches)
Production Environment:
- Queue Driver: Amazon SQS
- Worker Process: EC2 instances or ECS containers
- Management: AWS infrastructure
Queue Priority
Section titled “Queue Priority”The main worker processes queues in priority order:
aerticket-tickets- Ticket issuance (highest priority)aerticket-fastlane- Fast-lane booking operationsaerticket-bookings- Booking creationaerticket-voids- Ticket void operationspipedrive-sync- Pipedrive CRM sync jobs (Lead/Booking/Client/Passenger/BookingUpsell/BookingNote)offer-recalculations- Bulk offer flight/price recalculation sweepsdefault- General tasks
pipedrive-sync is listed before default so Pipedrive sync jobs aren’t starved when default has a backlog. The queue name is configurable via the PIPEDRIVE_QUEUE env var (default pipedrive-sync, defined in backend/config/pipedrive.php).
flight-searches is not on the main worker — it runs on a dedicated queue-flights service (see Flight Search Worker) so a burst of customer-facing Aerticket searches can’t delay ticketing/booking jobs, and the main worker’s admin sweeps (offer-recalculations) can’t slow customer-driven cache fills.
Quick Start
Section titled “Quick Start”Dispatching Jobs
Section titled “Dispatching Jobs”use App\Jobs\TestQueueJob;
// Simple dispatchTestQueueJob::dispatch('Hello, Queue!');
// Delayed dispatch (5 minutes)TestQueueJob::dispatch('Delayed message') ->delay(now()->addMinutes(5));
// Custom queueTestQueueJob::dispatch('High priority') ->onQueue('high-priority');Monitoring
Section titled “Monitoring”# Check queue status./vendor/bin/sail artisan queue:monitor
# Watch logs in real-time./vendor/bin/sail artisan tail
# Check failed jobs./vendor/bin/sail artisan queue:failedConfiguration
Section titled “Configuration”Environment Variables
Section titled “Environment Variables”# Queue driver: database (dev) or sqs (production)QUEUE_CONNECTION=database
# Database queue settingsDB_QUEUE_CONNECTION=pgsqlDB_QUEUE_TABLE=jobsDB_QUEUE=defaultDB_QUEUE_RETRY_AFTER=90
# Job retry settingsQUEUE_RETRY_AFTER=90QUEUE_MAX_ATTEMPTS=3Docker Compose Configuration
Section titled “Docker Compose Configuration”queue: build: context: './vendor/laravel/sail/runtimes/8.5' dockerfile: Dockerfile volumes: - '.:/var/www/html' networks: - sail depends_on: - pgsql - redis command: php artisan queue:work --queue=aerticket-tickets,aerticket-fastlane,aerticket-bookings,aerticket-voids,pipedrive-sync,offer-recalculations,default --sleep=3 --tries=3 --max-time=3600Worker Behavior:
- Polls every 3 seconds for new jobs
- Max 3 retry attempts per job
- Max 1 hour runtime before graceful restart
- Auto-restart on container failure
Flight Search Worker (queue-flights)
Section titled “Flight Search Worker (queue-flights)”A second Compose service runs a dedicated worker for customer-facing flight cache work (SearchFlightCacheJob, CascadeFlightSearchForDateJob), isolated on the flight-searches queue:
queue-flights: command: php artisan queue:work --queue=flight-searches --sleep=3 --tries=3 --max-time=3600Isolating it keeps a burst of Aerticket searches from delaying ticketing/booking jobs, and keeps the main worker’s admin sweeps (offer-recalculations) from slowing customer-driven cache fills.
Source: backend/compose.yaml (queue and queue-flights services)
Production Worker Memory (queue-worker.sh)
Section titled “Production Worker Memory (queue-worker.sh)”In production the queue worker runs through the docker/common/queue-worker/queue-worker.sh wrapper, which passes --memory="$QUEUE_WORKER_MEMORY" (default 384 MB) to queue:work and validates that the value is a positive integer. Making the memory limit configurable via the QUEUE_WORKER_MEMORY env var fixed a memory-driven restart loop (#2220). The prod wrapper runs a single worker over the combined queue list (aerticket-tickets,aerticket-fastlane,aerticket-bookings,aerticket-voids,pipedrive-sync,offer-recalculations,flight-searches,default).
Source: backend/docker/common/queue-worker/queue-worker.sh
Common Operations
Section titled “Common Operations”Restarting Workers
Section titled “Restarting Workers”# Signal workers to restart after current job./vendor/bin/sail artisan queue:restart
# Or restart the Docker container./vendor/bin/sail restart queueHandling Failed Jobs
Section titled “Handling Failed Jobs”# List all failed jobs./vendor/bin/sail artisan queue:failed
# Retry a specific failed job./vendor/bin/sail artisan queue:retry {job_id}
# Retry all failed jobs./vendor/bin/sail artisan queue:retry all
# Flush all failed jobs./vendor/bin/sail artisan queue:flushMonitoring Workers
Section titled “Monitoring Workers”# Check worker statusdocker ps | grep queue
# View worker logs./vendor/bin/sail logs queue
# Follow worker logs in real-time./vendor/bin/sail logs -f queueAerTicket Queue Jobs
Section titled “AerTicket Queue Jobs”Ticket Issuance Queue
Section titled “Ticket Issuance Queue”Queue Name: aerticket-tickets
Purpose: Process ticket issuance requests with highest priority
Jobs:
AerticketIssueTicketJob- Issues tickets for confirmed bookings
Configuration:
public int $tries = 3;public int $timeout = 120;public array $backoff = [30, 60, 180];Booking Creation Queue
Section titled “Booking Creation Queue”Queue Name: aerticket-bookings
Purpose: Process flight booking creation asynchronously
Jobs:
AerticketCreateBookingJob- Creates flight bookings from admin panelCreateCheckoutFlightBookingJob- Checkout flight booking job triggered from booking admin actionsAerticketRetrieveBookingJob- Retrieves PNR details
Checkout Flight Booking (Per-Leg):
After successful checkout payment, bookings with flights transition to pending_flight_booking. Admin users trigger CreateCheckoutFlightBookingJob from the booking detail page (Book All Flights / Retry per-leg). One job is dispatched per unbooked leg.
Configuration:
public int $tries = 3;public int $maxExceptions = 2;public int $timeout = 420;public array $backoff = [120];public int $uniqueFor = 480; // ShouldBeUnique per booking+legFeatures:
- Implements
ShouldBeUniquewithuniqueId = "checkout-flight:{bookingId}:{legIndex}" - Idempotency: Per-leg check via
FlightBooking::where(booking_id, leg_index)->exists()with row-level lock - International legs: re-searches round-trip, matches by flight numbers + price
- Domestic legs: searches one-way, matches by flight numbers + price
- Differentiated retry:
noMatchingFarereleases with 5-minute delay (fare refresh), other errors use 120s backoff - Notifies admins via Filament on per-leg success and all-legs-complete
- Creates
FlightBookingwithbooking_id,leg_index,flight_type - All-legs-booked check: only transitions to
flights_confirmedwhen ALL legs haveFlightBookingrecords
Source: backend/app/Jobs/CreateCheckoutFlightBookingJob.php
Related: Checkout API - Admin-Driven Flight Booking
Flight Search Queue
Section titled “Flight Search Queue”Queue Name: flight-searches
Purpose: Process dynamic flight cache searches asynchronously for bulk operations
Jobs:
SearchFlightCacheJob- Executes flight search for a single cache entry
Configuration:
public int $tries = 3;public int $timeout = 90;public array $backoff = [30, 60, 120];Features:
- User attribution for activity logging
- Automatic failure handling with status update
- Structured logging to Grafana Loki
- Activity log entries for audit trail
Usage:
use App\Jobs\SearchFlightCacheJob;
// Dispatch with user attributionSearchFlightCacheJob::dispatch($cacheId, $userId) ->onQueue('flight-searches');
// Via service (recommended)$service = app(DynamicFlightCachePopulatorService::class);$result = $service->dispatchSearchJobs($entries, $userId);Source: backend/app/Jobs/SearchFlightCacheJob.php
Related: Dynamic Flight Cache for full documentation
Offer Recalculation Queue
Section titled “Offer Recalculation Queue”Queue Name: offer-recalculations (RecalculateOfferFlightsJob::QUEUE_NAME)
Purpose: Run bulk offer flight/price recalculation sweeps off the main request path, isolated from customer-facing flight searches.
Jobs:
RecalculateOfferFlightsJob— recalculates an offer’s bound flights and prices
Consumed by the main queue worker (dev/local) and by the combined production worker.
Source: backend/app/Jobs/RecalculateOfferFlightsJob.php
Pipedrive Sync Queue
Section titled “Pipedrive Sync Queue”Queue Name: pipedrive-sync (configurable via PIPEDRIVE_QUEUE)
Purpose: Push CRM-relevant model changes to Pipedrive asynchronously via retryable jobs.
Dispatched by: PipedriveSyncObserver, registered in app/Providers/AppServiceProvider.php for Client, Booking, Passenger, BookingUpsell, BookingNote, and Lead.
The worker must include this queue in its --queue= list, or observer-dispatched jobs will accumulate unprocessed in the jobs table. Newsletter subscriptions are unaffected because NewsletterSubscriptionController calls Pipedrive inline from the HTTP request.
Related: Leads API - Pipedrive Sync for the full sync flow, custom fields, and operational requirements.
Security Best Practices
Section titled “Security Best Practices”Validate Job Data
Section titled “Validate Job Data”public function __construct( public int $userId, public string $action) { if (!User::find($userId)) { throw new \InvalidArgumentException('Invalid user ID'); }}Encrypt Sensitive Data
Section titled “Encrypt Sensitive Data”use Illuminate\Contracts\Queue\ShouldBeEncrypted;
class ProcessPayment implements ShouldQueue, ShouldBeEncrypted{ public function __construct( public string $creditCardNumber ) {}}Rate Limiting
Section titled “Rate Limiting”use Illuminate\Queue\Middleware\RateLimited;
public function middleware(): array{ return [new RateLimited('api-calls')];}Troubleshooting
Section titled “Troubleshooting”Worker Not Processing Jobs
Section titled “Worker Not Processing Jobs”- Check worker is running:
docker ps | grep queue- Check worker logs:
./vendor/bin/sail logs queue- Restart worker:
./vendor/bin/sail restart queueJobs Failing Repeatedly
Section titled “Jobs Failing Repeatedly”- Check failed job details:
./vendor/bin/sail artisan queue:failed- Review exception:
./vendor/bin/sail artisan tinker>>> DB::table('failed_jobs')->latest()->first()->exception;- Fix issue and retry:
./vendor/bin/sail artisan queue:retry allConnection Timeouts
Section titled “Connection Timeouts”- Increase timeout in job:
public $timeout = 120; // 2 minutes- Increase retry_after:
DB_QUEUE_RETRY_AFTER=180