Slow down Laravel worker - laravel

I have hooked up Redis with Laravel for queuing emails and all is fine...
but in dev environment I use mailtrap.io (free version).
And the problem is that mailtrap allows to receive only 2 emails per second, so I never get all the emails that are queued, because redis sends emails like crazy... maybe 10 per/s
Is there a way somehow to slow down the queue so that it sends max 2 mails per second?

Yes It is a solution and they're called jobs :)
you can create a file to send emails exclusively in the Jobs Folder and a class like this
class SendPushNotification extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
protected $pushNotification;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct(PushNotification $pushNotification)
{
$this->pushNotification = $pushNotification;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$this->pushNotification->send();
}
}
And then call the class in the controller
$this->dispatch(new SendPushNotification($pushNotification))->delay(1);
Delay is for seconds you can create a constructor with an array to receive two emails o many you want maybe some var than can be changed by the .env to change the number of emails per second

Related

Laravel broadcasting - use job ID as an 'order' property

I want to send some sort of (unique, auto-incrementing) number as part of the payload of an event - so that the consumer can, for example, know it should ignore an 'updated' event if the event is older than a previous 'update' event it received.
I see I can add a broadcastWith method to my event, where I could add such a number, which I'm storing in some table.
But, I don't really need to create a new number. The ID of the job in the jobs table will work just fine. So, how can I make Laravel automatically add a property, say order, to this event before it is broadcast and make the value of order to id column from the jobs table? Is there a way to get it in the broadcastWith method?
I had previously thought of using a timestamp as the 'order' but of course that won't help me or the consumer when two events have been created in a short-a timeframe as a computer can create two events.
UPDATE
Looks like I haven't worded it well and people are confused as to what I'm looking for. In hindsight, I shouldn't of added the criteria that it must be the job id that gets included in the payload. The main thing I'm after is a unique, auto-incrementing ID in each broadcast event. For example, I have an UserUpdated event. Say the a user is updated twice - my SPA that is consuming the events needs to know which event is the newer one, otherwise the SPA might display outdated info. If the events are delivered sequentially, then this problem won't happen. But, especially as I'm relying on a third-party service (Pusher) to deliver the events to the SPA, I don't want to assume / trust that the events will always be delivered in the same order they were sent to Pusher.
Hi such a nice requirement, i have been working on a POCO and here are some snippets, you do not need broadcast at all. Of course you need to have the queue worker up and running
Running The Queue Worker
On your order controller, i guess you need the update method to dispatch the job before commiting.
function update(Request $req)
{
$data= Order::find($req->id);
$data->amount=$req->amount; //example field
PostUpdateOrderJob::dispatch($data)->beforeCommit();
$data->save();
}
Important: Why before commit and not after commit: Setting the after_commit configuration option to true will also cause any queued event listeners, mailables, notifications, and broadcast events to be dispatched after all open database transactions have been committed.
Your PostUpdateOrderJob class
?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\Order;
use Throwable;
class PostUpdateOrderJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The number of times the job may be attempted.
*
* #var int
*/
public $tries = 25;
/**
* The maximum number of unhandled exceptions to allow before failing.
*
* #var int
*/
public $maxExceptions = 3;
protected $order;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct(Order $order)
{
$this->order=$order;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$this->order->update(['jobid'=>$this->job->getJobId()]);
}
public function failed(Throwable $exception)
{
// Send user notification of failure, etc...
//Several options on link below
//https://laravel.com/docs/9.x/queues#dealing-with-failed-jobs
}
}
Well php has a function called time() which returns the current unix time in seconds, so you can just use that in your broadcast event.
In your broadcast event, you can add this time to the public class properties, which would then be available through the event payload:
class MyBroadcastEvent implements ShouldBroadcast
{
public $time;
public function __construct()
{
$this->time = time();
}
}
I'm not sure if this is exactly what you need, your question is kind of confusing to be honest.

Notification Testing no record created during tests in notifications table

I am trying to test if notification is created in the database, in my local, this works as intended however the test environment has some strange behaviour.
$user->notify(new ExampleNotification());
$this->assertDatabaseCount('notifications', 1);
//returns green with QUEUE_CONNECTION=sync but red with QUEUE_CONNECTION=database
...
class ExampleNotification extends Notification implements ShouldBroadcast, ShouldQueue
{
use Queueable;
public function viaQueues(): array
{
return [
'database' => 'notifications'
];
}
/**
* Get the notification's delivery channels.
*
* #return array
*/
public function via(User $notifiable): array
{
return ['database']
}
ps: I do not use Notification::fake() anywhere in my tests
Does anybody know why the different queue connections would behave differently?
Any queue driver other than the sync one works asynchronously i.e. when you send a notification job it will be queued and then the queue worker will remove it from the queue and then send the actual notification.
This means if you send a notification during a test, even if the queue worker is running the notification will most likely not be sent before the test is done.
To avoid this you should either use the sync queue or use Notification::fake() and just check if a notification would have been sent.

Laravel: Send 1000 emails every hour using queue

I have a table users with 5000 records (5000 users) and in my server i can just send 1000 emails every hour.
How i can send 1000 emails every hour using queue ?
or how to make queues sleep inside loop?
EmailController:
class EmailController extends Controller
{
public function sendEmail(Request $request){
$event=(object)['content' => "Hello Laravel fans",'subject' => 'Test Email'];
$users=User::all();
App\Jobs\SendReminderEmail::dispatch($users,$event)
}
}
SendReminderEmail
class SendReminderEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $event;
public $email;
public $users;
public function __construct($users,$event)
{
$this->users = $users;
$this->event = $event;
}
public function handle()
{
foreach ($this->users as $user) {
Mail::to($user->email)->queue(new Reminder($this->event));
}
}
}
Laravel has a neat feature which fits your case perfectly, it's called Task Scheduling, instead of making the job sleep for one hour, you could, instead, call it every hour.
To do so add the job schedule to the schedule() method located on App\Console\Kernel like this:
protected function schedule(Schedule $schedule)
{
$schedule->job(new SendReminderEmail)->hourly();
}
I also would recommend you to make the job self contained, that will make this task much simpler, I'm thinking in something like this:
class SendReminderEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $event;
public $users;
public function __construct()
{
$this->users = User::all();
$this->event = (object)['content' => "Hello Laravel fans",'subject' => 'Test Email'];;
}
public function handle()
{
foreach ($this->users as $user) {
Mail::to($user->email)->queue(new Reminder($this->event));
}
}
}
Now you can just get rid of your controller because this job will be executed every hour automatically.
Remember that you will need to run a cron on your server to check if the job needs to be executed. You can also run it manually if you want to test it using php artisan schedule:run.
Hope this helps you.
Laravel has a built-in throttle feature for rate-limited jobs. From the docs:
If your application interacts with Redis, you may throttle your queued
jobs by time or concurrency. This feature can be of assistance when
your queued jobs are interacting with APIs that are also rate limited.
For example, using the throttle method, you may throttle a given type
of job to only run 10 times every 60 seconds. If a lock can not be
obtained, you should typically release the job back onto the queue so
it can be retried later:
Redis::throttle('key')->allow(10)->every(60)->then(function () {
// Job logic...
}, function () {
// Could not obtain lock...
return $this->release(10);
});
In your case, that might look like Redis::throttle(...)->allow(1000)->every(3600)->...
If you're not using Redis, another possible solution which is specific to queued mail is to add a delay. Again, from the docs:
Delayed Message Queueing
If you wish to delay the delivery of a queued
email message, you may use the later method. As its first argument,
the later method accepts a DateTime instance indicating when the
message should be sent:
$when = now()->addMinutes(10);
Mail::to($request->user())
->cc($moreUsers)
->bcc($evenMoreUsers)
->later($when, new OrderShipped($order));
Using this method would require you to calculate the delay for each email during the loop that dispatches emails to the queue. For example, every 1000th iteration of the loop you could increase the delay by 1 hour.
If you're using the Amazon SQS queue service, the same maximum delay of 15 minutes that applies to other queued jobs might also apply here (I'm not sure on this part), in which case you would have to come up with another solution for checking if you're over your rate limit and releasing the job back to the queue.

Laravel 5.5: how to get failed job ID and link it to the related object?

In Laravel 5.5, I've created a queue job like this:
class EmailJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private $emailModel;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct(MyEmail $emailModel)
{
//
$this->emailModel = $emailModel;
}
// ...
}
MyEmail is a model to store email data.
I use database driver on queue.
Now, I want to catch the failed job (in failed_jobs table), and link them to related MyEmail object (e.g: store in an attribute failed_job_id), then later on my app can manually retry specific email's failed job. How can I achieve this ?
I have a workaround approach, in which I grab all failed jobs through failed_jobs table, and check the payload one by one, determine if the MyEmail object has the id we want. but I don't think it's efficient.

Expire for Laravel Jobs

I parse some HTML pages and API endpoints, for example, every 5 minutes to track changes. For this purpose, I have created ParseJob where I do parsing and save changes to a database. ParseJob implements interface ShouldQueue and I have changed queue driver to Redis. In order to run the ParseJob on a regular basis, I have created ParseCommand and added it to schedule:
class ParseCommand extends Command
{
protected $signature = 'application:my-parse-command';
public function handle()
{
$this->dispatch(new ParseJob());
}
}
class Kernel extends ConsoleKernel
{
protected $commands = [
Commands\ParseCommand::class
];
protected function schedule(Schedule $schedule)
{
$schedule->command('application:my-parse-command')
->everyFiveMinutes();
}
}
And the queue worker is started as a daemon to process the queue. So, every 5 minutes ParseJob is pushed to the queue and the queue worker is processing the job.
Sometimes queue worker process crashes, freezes or for other reasons is not working. But jobs every 5 minutes are pushed into the queue. After an hour of downtime, I have 12 jobs in the queue but they are for that time irrelevant because I do not need to parse 12 times at a certain time, I want just one parse job.
So I want to set TTL for a job that works like expire command in Redis. How to do that? Or maybe you have an alternative solution?
As far a i know it is not possible to set explicitly a Job expiration into Laravel queues. A solution could be setting an expires_at property within your ParseJob and check before executing:
class ParseCommand extends Command
{
protected $signature = 'application:my-parse-command';
public function handle()
{
$this->dispatch(new ParseJob(Carbon::now()->addMinutes(5)));
}
}
then in your Job class
class ParseJob {
protected $expires_at;
public function __construct(Carbon $expires_at) {
$this->expires_at = $expires_at;
}
public function handle()
{
if(!Carbon::now()->gt($this->expires_at)) {
// Parse data
}
}
}
At larvel 8/9 in job
MyJObClass
public function retryUntil(): Carbon
{
return now()->addMinutes(10);
}
Laravel :: Queues #time-based-attempts

Resources