Repository: netbox-community/netbox

Issue:
Allow redefining internally used background queues
### NetBox version

v3.3.8

### Feature type

Change to existing functionality

### Proposed functionality

Allow changing the used RQ queue for webhook scheduling, custom scripts and reports to a specific queue.

### Use case

Currently script/report and webhook execution uses the same queue ("default"). One RQ worker will only ever run [one job at a time](https://python-rq.org/docs/workers/):

> Each worker will process a single job at a time. Within a worker, there is no concurrent processing going on. If you want to perform jobs concurrently, simply start more workers.

For webhooks we want to only ever run one job at the time, as the order is very important to avoid a webhook referencing a related object that hasn't been sent in a webhook yet.

For scripts/reports the user might not care about the order of executions, but as it stands now if the user runs one rqworker, long running scripts will block both webhooks and other scripts from execution before it is done. 

If the user starts multiple rqworkers that will allow multiple scripts to run at a time, but it will also allow multiple webhooks to be sent at a time, which in turn can create situations where a newer webhook is done processing before an older one - imagine three workers all picking up a job at the same time, the webhook will potentially send the HTTP request at roughly the same time, which one goes first depends on CPU scheduling or other external factors.

Allowing webhooks to be configured to use a seperate queue will allow the user to start one worker for webhooks and multiple workers for script/report execution, without risking data consistency issues with webhooks.

### Database changes

None

### External dependencies

None


Discussion:
If accepted I'll implement it. We also need to sync up with netbox-docker as there needs to be changes to the default docker-compose file to add the new queue.
If adding the ability to set the queue priority for webhooks, would it be possible to add the ability to set the priority in general at the URL with maybe the flag ?priority=high, ?priority=default, ?priority=low. The usecase would be to prioritize manual triggered scripts to run before the background scripts.
@Bierfass - This feature request would be to set a static queue for webhooks, as there in my opinion is no reason to want to concurrently execute webhooks because of the mentioned pitfalls of the ordering not being perserved.

Allowing you to schedule scripts and reports in different queues is a good idea though, but would be another feature request. It would probably either be set in the Meta class of the script or as a form selection when executing the script though. Also note that the low, default and high queues only work as intended if running more than one rqworker as far as I can tell.
Just recapping our discussion from today's maintainers' meeting. This is a great idea and I'd like to be able to configure queues per process type. For example, something like this in `configuration.py`:

```python
QUEUE_MAPPINGS = {
    'webhooks': 'my-custom-queue',
    'reports': 'high',
}
```

This would grant users a good amount of flexibility to create and assign their own queues as needed (each of which can be serviced by however many RQ workers is appropriate).
Will this be extended to plugins as well, so that they register their keys in that map?

Resolving patch:
diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py
--- a/netbox/extras/models/models.py
+++ b/netbox/extras/models/models.py
@@ -21,6 +21,7 @@
 from extras.constants import *
 from extras.conditions import ConditionSet
 from extras.utils import FeatureQuery, image_upload
+from netbox.config import get_config
 from netbox.models import ChangeLoggedModel
 from netbox.models.features import (
     CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, JobResultsMixin, TagsMixin, WebhooksMixin,
@@ -681,7 +682,8 @@ def enqueue_job(cls, func, name, obj_type, user, schedule_at=None, *args, **kwar
             job_id=uuid.uuid4()
         )
 
-        queue = django_rq.get_queue("default")
+        rq_queue_name = get_config().QUEUE_MAPPINGS.get(obj_type.name, 'default')
+        queue = django_rq.get_queue(rq_queue_name)
 
         if schedule_at:
             job_result.status = JobResultStatusChoices.STATUS_SCHEDULED
diff --git a/netbox/extras/webhooks.py b/netbox/extras/webhooks.py
--- a/netbox/extras/webhooks.py
+++ b/netbox/extras/webhooks.py
@@ -5,6 +5,7 @@
 from django.utils import timezone
 from django_rq import get_queue
 
+from netbox.config import get_config
 from netbox.registry import registry
 from utilities.api import get_serializer_for_model
 from utilities.utils import serialize_object
@@ -78,7 +79,8 @@ def flush_webhooks(queue):
     """
     Flush a list of object representation to RQ for webhook processing.
     """
-    rq_queue = get_queue('default')
+    rq_queue_name = get_config().QUEUE_MAPPINGS.get('webhook', 'default')
+    rq_queue = get_queue(rq_queue_name)
     webhooks_cache = {
         'type_create': {},
         'type_update': {},
diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py
--- a/netbox/netbox/settings.py
+++ b/netbox/netbox/settings.py
@@ -101,6 +101,7 @@
 METRICS_ENABLED = getattr(configuration, 'METRICS_ENABLED', False)
 PLUGINS = getattr(configuration, 'PLUGINS', [])
 PLUGINS_CONFIG = getattr(configuration, 'PLUGINS_CONFIG', {})
+QUEUE_MAPPINGS = getattr(configuration, 'QUEUE_MAPPINGS', {})
 RELEASE_CHECK_URL = getattr(configuration, 'RELEASE_CHECK_URL', None)
 REMOTE_AUTH_AUTO_CREATE_USER = getattr(configuration, 'REMOTE_AUTH_AUTO_CREATE_USER', False)
 REMOTE_AUTH_BACKEND = getattr(configuration, 'REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend')
@@ -644,6 +645,11 @@ def _setting(name, default=None):
     'low': RQ_PARAMS,
 }
 
+# Add any queues defined in QUEUE_MAPPINGS
+RQ_QUEUES.update({
+    queue: RQ_PARAMS for queue in set(QUEUE_MAPPINGS.values()) if queue not in RQ_QUEUES
+})
+
 
 #
 # Plugins


Test patch: