Cron Jobs in Magento 2: How to Adjust Schedules and Cron Groups Seamlessly
Tags: magento2, adobecommerce, webdev, mageos
Published: 2023-09-22 18:53:56

## Introduction

Hello!

In today's article, I'll cover a topic that's not too tricky, but can be a bit annoying without some useful tips. Specifically, we'll dive into Magento 2 ( Mage-OS / Adobe Commerce ) to discuss cron jobs, their configurations, and the process of rescheduling them and altering their execution group.

You might wonder, why bother changing the schedule or group of a cron job? Let's find out.
 

## Why Adjust the Cron Schedule?

Occasionally, a cron job might execute too frequently, leading to server load issues or even deadlocks. On the other hand, if it doesn't run often enough, you could be left waiting for emails that take an hour to send. This scenario becomes particularly tricky when these cron jobs are associated with third-party extensions.

Let's imagine we are using 3rd party Feed and Mailing extensions, and we are not OK with their cron jobs schedule.  
How can you modify the execution patterns of these cron jobs without tampering with the third-party code directly? By implementing changes in your own codebase – your module. Let's explore this next.

## Rescheduling Cron Jobs

Let's have a look at the cron job of the Feed extension `feed_generation`.  

Here is its definition:
```xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="default">
      ...
        <job name="feed_generation" instance="\Feed\Cron\FeedGenerator" method="execute">
            <schedule>* * * * *</schedule>
        </job>
      ...
    </group>
</config>
```

Executing every minute, this job can impose a significant server load. We certainly don't need a new feed every 60 seconds!

To modify its frequency, we'll craft a module, `Devto_ChangeFeedSchedule`, and incorporate an `etc/config.xml` file within.  
So, the structure would look like this:
```
Devto_ChangeFeedSchedule/
│
├── etc/
│   ├── config.xml      # our config file
│   └── module.xml      
│
├── registration.php    
│
└── composer.json       
```

The `etc/config.xml` should contain:

```xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <crontab>
            <default> <!-- cron group -->
                <jobs>
                    <feed_generation> <!-- job name -->
                        <schedule>
                            <cron_expr>0 * * * *</cron_expr> <!-- new cron execution schedule -->
                        </schedule>
                    </feed_generation>
                </jobs>
            </default>
        </crontab>
    </default>
</config>
```

And last but not least - add sequence into `etc/module.xml` to declare dependency over Feed module and configs apply order:

```
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Devto_ChangeFeedSchedule" >
        <sequence>
            <!-- makes Devto_ChangeFeedSchedule configs be applied after Feed extension configs -->
            <module name="Vendor_FeedExtesnion"/>
        </sequence>
    </module>
</config>
```

Once the module `Devto_ChangeFeedSchedule` is activated and the cache cleared, your new job schedule will take effect - run every hour but not a minute.

But what about cron groups? Let's delve into that next.

## The Need to Change Cron Group

Imagine we're still working with the Feed and Mailing third-party extensions. These extensions utilize distinct cron groups, each operating as an isolated process. Thanks to this, cron groups can run concurrently/parallely, using individual processes.

Now, suppose these two cron jobs conflict, resulting in data inconsistencies in the Feed and Emails. Worse yet, `deadlocks` can crop up. You might ask, **"Why the deadlocks?"**  
Well, imagine these extensions read & write the same table, say `sales_order_item`. When they run simultaneously, the stage is set for potential deadlocks. 🤷‍♂️

If only these jobs were executed sequentially! By relocating them to a shared group, you can ensure they no longer run in parallel.

## Altering the Cron Job's Group

A straightforward (though not perfect) solution would be to transfer the Mailing extension's cron job to the `default` group, where the Feed extension's job resides. Here's the Mailing cron job definition:

```xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="mailing_extension"> <!-- mailing cron group -->
      ...
        <job name="send_mails_from_mailing_extension" instance="\Mailing\Cron\SendMailsCron" method="execute">
            <schedule>*/20 * * * *</schedule>
        </job>
      ...
    </group>
</config>
```

However, a small, **non-intuitive trick** is essential here: Simply changing the group by redefining `crontab.xml` won't work. Instead, you need to craft a new cron job while deactivating the old one. This step is necessary since `crontab.xml` configurations (especially groups) can't be overridden, only augmented.

For instance, merely defining the `send_mails_from_mailing_extension` cron job in the `default` group would make it run in both the `default` and `mailing_extension` groups.

To disable `send_mails_from_mailing_extension`, an easy trick is to schedule it for February 30th—a date that doesn't exist!

<u>nerd fact 🤓:</u> _actually February 30 happened in Sweden in 1712_

Following our earlier approach, we'd add the following content to the `etc/config.xml` in our module, `Devto_ChangeMailingJobGroup`:

```xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <crontab>
            <default>
                <jobs>
                    <!-- Disable cron execution by scheduling it to Feb 30th. -->
                    <send_mails_from_mailing_extension>
                        <schedule>
                            <cron_expr>0 0 30 2 *</cron_expr>
                        </schedule>
                    </send_mails_from_mailing_extension>
                </jobs>
            </default>
        </crontab>
    </default>
</config>
```

Next, define a **NEW** cron job in the `default` group:

```xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="default"> <!-- default cron group -->
      ...
        <!-- added devto prefix to cron name -->
        <job name="devto_send_mails_from_mailing_extension" instance="\Mailing\Cron\SendMailsCron" method="execute">
            <schedule>*/20 * * * *</schedule>
        </job>
      ...
    </group>
</config>
```

And do not forget to add Mailing module sequence into `etc/module.xml`:

```
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Devto_ChangeMailingJobGroup" >
        <sequence>
            <!-- makes Devto_ChangeMailingJobGroup configs be applied after Mailing extension configs -->
            <module name="Vendor_MailingExtesnion"/>
        </sequence>
    </module>
</config>
```

After all changes, the module structure should look like this:
```
Devto_ChangeMailingJobGroup/
│
├── etc/
│   ├── config.xml      # our config file
|   ├── crontab.xml     # our crontab definition file  
│   └── module.xml      
│
├── registration.php    
│
└── composer.json       
```

Once `Devto_ChangeMailingJobGroup` is active and the cache refreshed, the `send_mails_from_mailing_extension` job will be disabled. Meanwhile, its counterpart, `devto_send_mails_from_mailing_extension`, will operate in the `default` cron group alongside the Feed cron job.

## A Word of Caution on the Default Group  

It's vital to recognize that Magento's native cron jobs already densely populate the `default` cron group. Overloading it might cause "traffic jams" if:

1. **Jobs have lengthy runtimes:** If your tasks take too long to finish, they could end up waiting in line, slowing things down.
2. **Overfrequent Scheduling:** Setting jobs to run too often can result in backlogs and potential overlaps.
3. **Overpopulation:** Filling the group with too many tasks, even if they are quick, might overwhelm the system.

So, while the default group is convenient, treat it like a city's main highway during rush hour. It can handle a lot, but there's a limit before things get clogged. If you have too many heavy tasks or they're scheduled too frequently, you'll likely end up with delays.   

In short, be mindful of the workload you're placing on the `default` group and consider creating your own cron groups to avoid conflict of processes.

## Conclusion

And that's the lowdown on Magento's cron jobs, how to shuffle their schedules, and adjust their groups without diving into third-party code directly. With the knowledge you've gathered here, tweaking those pesky cron jobs should be much easier 😊


Thank you for reading!