redis-smq 9.0.0-next.6 → 9.0.0-next.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/cjs/src/common/redis-client/scripts/lua/publish-message.lua +5 -4
  3. package/dist/cjs/src/common/redis-client/scripts/lua/publish-scheduled.lua +142 -107
  4. package/dist/cjs/src/common/redis-client/scripts/lua/requeue-delayed.lua +75 -37
  5. package/dist/cjs/src/common/redis-client/scripts/lua/requeue-immediate.lua +62 -29
  6. package/dist/cjs/src/common/redis-client/scripts/lua/requeue-message.lua +59 -41
  7. package/dist/cjs/src/common/redis-client/scripts/lua/shared-procedures/publish-message.lua +12 -1
  8. package/dist/cjs/src/consumer/message-handler/_/_delete-ephemeral-consumer-group.d.ts +4 -0
  9. package/dist/cjs/src/consumer/message-handler/_/_delete-ephemeral-consumer-group.d.ts.map +1 -0
  10. package/dist/cjs/src/consumer/message-handler/_/_delete-ephemeral-consumer-group.js +11 -0
  11. package/dist/cjs/src/consumer/message-handler/_/_delete-ephemeral-consumer-group.js.map +1 -0
  12. package/dist/cjs/src/consumer/message-handler/_/_generate-ephemeral-consumer-group-id.d.ts +2 -0
  13. package/dist/cjs/src/consumer/message-handler/_/_generate-ephemeral-consumer-group-id.d.ts.map +1 -0
  14. package/dist/cjs/src/consumer/message-handler/_/_generate-ephemeral-consumer-group-id.js +7 -0
  15. package/dist/cjs/src/consumer/message-handler/_/_generate-ephemeral-consumer-group-id.js.map +1 -0
  16. package/dist/cjs/src/consumer/message-handler/_/_prepare-consumer-group.d.ts +4 -0
  17. package/dist/cjs/src/consumer/message-handler/_/_prepare-consumer-group.d.ts.map +1 -0
  18. package/dist/cjs/src/consumer/message-handler/_/_prepare-consumer-group.js +44 -0
  19. package/dist/cjs/src/consumer/message-handler/_/_prepare-consumer-group.js.map +1 -0
  20. package/dist/cjs/src/consumer/message-handler/dequeue-message/dequeue-message.d.ts +1 -3
  21. package/dist/cjs/src/consumer/message-handler/dequeue-message/dequeue-message.d.ts.map +1 -1
  22. package/dist/cjs/src/consumer/message-handler/dequeue-message/dequeue-message.js +1 -41
  23. package/dist/cjs/src/consumer/message-handler/dequeue-message/dequeue-message.js.map +1 -1
  24. package/dist/cjs/src/consumer/message-handler/message-handler.d.ts +8 -6
  25. package/dist/cjs/src/consumer/message-handler/message-handler.d.ts.map +1 -1
  26. package/dist/cjs/src/consumer/message-handler/message-handler.js +80 -16
  27. package/dist/cjs/src/consumer/message-handler/message-handler.js.map +1 -1
  28. package/dist/cjs/src/consumer/message-handler/workers/publish-scheduled.worker.d.ts.map +1 -1
  29. package/dist/cjs/src/consumer/message-handler/workers/publish-scheduled.worker.js +7 -2
  30. package/dist/cjs/src/consumer/message-handler/workers/publish-scheduled.worker.js.map +1 -1
  31. package/dist/cjs/src/consumer/message-handler/workers/reap-consumers.worker.d.ts +1 -0
  32. package/dist/cjs/src/consumer/message-handler/workers/reap-consumers.worker.d.ts.map +1 -1
  33. package/dist/cjs/src/consumer/message-handler/workers/reap-consumers.worker.js +23 -14
  34. package/dist/cjs/src/consumer/message-handler/workers/reap-consumers.worker.js.map +1 -1
  35. package/dist/cjs/src/consumer/message-handler/workers/requeue-delayed.worker.d.ts.map +1 -1
  36. package/dist/cjs/src/consumer/message-handler/workers/requeue-delayed.worker.js +12 -3
  37. package/dist/cjs/src/consumer/message-handler/workers/requeue-delayed.worker.js.map +1 -1
  38. package/dist/cjs/src/consumer/message-handler/workers/requeue-immediate.worker.d.ts.map +1 -1
  39. package/dist/cjs/src/consumer/message-handler/workers/requeue-immediate.worker.js +9 -3
  40. package/dist/cjs/src/consumer/message-handler/workers/requeue-immediate.worker.js.map +1 -1
  41. package/dist/cjs/src/message-manager/_/_requeue-message.d.ts.map +1 -1
  42. package/dist/cjs/src/message-manager/_/_requeue-message.js +4 -2
  43. package/dist/cjs/src/message-manager/_/_requeue-message.js.map +1 -1
  44. package/dist/cjs/src/producer/_/_publish-message.d.ts.map +1 -1
  45. package/dist/cjs/src/producer/_/_publish-message.js +5 -0
  46. package/dist/cjs/src/producer/_/_publish-message.js.map +1 -1
  47. package/dist/esm/src/common/redis-client/scripts/lua/publish-message.lua +5 -4
  48. package/dist/esm/src/common/redis-client/scripts/lua/publish-scheduled.lua +142 -107
  49. package/dist/esm/src/common/redis-client/scripts/lua/requeue-delayed.lua +75 -37
  50. package/dist/esm/src/common/redis-client/scripts/lua/requeue-immediate.lua +62 -29
  51. package/dist/esm/src/common/redis-client/scripts/lua/requeue-message.lua +59 -41
  52. package/dist/esm/src/common/redis-client/scripts/lua/shared-procedures/publish-message.lua +12 -1
  53. package/dist/esm/src/consumer/message-handler/_/_delete-ephemeral-consumer-group.d.ts +4 -0
  54. package/dist/esm/src/consumer/message-handler/_/_delete-ephemeral-consumer-group.d.ts.map +1 -0
  55. package/dist/esm/src/consumer/message-handler/_/_delete-ephemeral-consumer-group.js +8 -0
  56. package/dist/esm/src/consumer/message-handler/_/_delete-ephemeral-consumer-group.js.map +1 -0
  57. package/dist/esm/src/consumer/message-handler/_/_generate-ephemeral-consumer-group-id.d.ts +2 -0
  58. package/dist/esm/src/consumer/message-handler/_/_generate-ephemeral-consumer-group-id.d.ts.map +1 -0
  59. package/dist/esm/src/consumer/message-handler/_/_generate-ephemeral-consumer-group-id.js +4 -0
  60. package/dist/esm/src/consumer/message-handler/_/_generate-ephemeral-consumer-group-id.js.map +1 -0
  61. package/dist/esm/src/consumer/message-handler/_/_prepare-consumer-group.d.ts +4 -0
  62. package/dist/esm/src/consumer/message-handler/_/_prepare-consumer-group.d.ts.map +1 -0
  63. package/dist/esm/src/consumer/message-handler/_/_prepare-consumer-group.js +41 -0
  64. package/dist/esm/src/consumer/message-handler/_/_prepare-consumer-group.js.map +1 -0
  65. package/dist/esm/src/consumer/message-handler/dequeue-message/dequeue-message.d.ts +1 -3
  66. package/dist/esm/src/consumer/message-handler/dequeue-message/dequeue-message.d.ts.map +1 -1
  67. package/dist/esm/src/consumer/message-handler/dequeue-message/dequeue-message.js +3 -44
  68. package/dist/esm/src/consumer/message-handler/dequeue-message/dequeue-message.js.map +1 -1
  69. package/dist/esm/src/consumer/message-handler/message-handler.d.ts +8 -6
  70. package/dist/esm/src/consumer/message-handler/message-handler.d.ts.map +1 -1
  71. package/dist/esm/src/consumer/message-handler/message-handler.js +80 -18
  72. package/dist/esm/src/consumer/message-handler/message-handler.js.map +1 -1
  73. package/dist/esm/src/consumer/message-handler/workers/publish-scheduled.worker.d.ts.map +1 -1
  74. package/dist/esm/src/consumer/message-handler/workers/publish-scheduled.worker.js +7 -2
  75. package/dist/esm/src/consumer/message-handler/workers/publish-scheduled.worker.js.map +1 -1
  76. package/dist/esm/src/consumer/message-handler/workers/reap-consumers.worker.d.ts +1 -0
  77. package/dist/esm/src/consumer/message-handler/workers/reap-consumers.worker.d.ts.map +1 -1
  78. package/dist/esm/src/consumer/message-handler/workers/reap-consumers.worker.js +23 -14
  79. package/dist/esm/src/consumer/message-handler/workers/reap-consumers.worker.js.map +1 -1
  80. package/dist/esm/src/consumer/message-handler/workers/requeue-delayed.worker.d.ts.map +1 -1
  81. package/dist/esm/src/consumer/message-handler/workers/requeue-delayed.worker.js +12 -3
  82. package/dist/esm/src/consumer/message-handler/workers/requeue-delayed.worker.js.map +1 -1
  83. package/dist/esm/src/consumer/message-handler/workers/requeue-immediate.worker.d.ts.map +1 -1
  84. package/dist/esm/src/consumer/message-handler/workers/requeue-immediate.worker.js +9 -3
  85. package/dist/esm/src/consumer/message-handler/workers/requeue-immediate.worker.js.map +1 -1
  86. package/dist/esm/src/message-manager/_/_requeue-message.d.ts.map +1 -1
  87. package/dist/esm/src/message-manager/_/_requeue-message.js +4 -2
  88. package/dist/esm/src/message-manager/_/_requeue-message.js.map +1 -1
  89. package/dist/esm/src/producer/_/_publish-message.d.ts.map +1 -1
  90. package/dist/esm/src/producer/_/_publish-message.js +6 -1
  91. package/dist/esm/src/producer/_/_publish-message.js.map +1 -1
  92. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -3,6 +3,24 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [9.0.0-next.8](https://github.com/weyoss/redis-smq/compare/v9.0.0-next.7...v9.0.0-next.8) (2025-10-18)
7
+
8
+ ### ✨ Features
9
+
10
+ - **redis-smq:** make consumerGroupId optional for PubSub queue consumers ([0529efd](https://github.com/weyoss/redis-smq/commit/0529efd3ab38ba6f423143af344b30d2ef1cc4a4))
11
+
12
+ ### 🐛 Bug Fixes
13
+
14
+ - **redis-smq:** check consumer group existence when relevant ([1711ac1](https://github.com/weyoss/redis-smq/commit/1711ac1db83ae8ca1e65947c33ca2878f49e15ed))
15
+
16
+ ### 📝 Documentation
17
+
18
+ - **redis-smq:** clarify consumer group behavior for PubSub queues ([84f0e48](https://github.com/weyoss/redis-smq/commit/84f0e484bdca19085d067d738bb9d1adaa825ca1))
19
+
20
+ ## [9.0.0-next.7](https://github.com/weyoss/redis-smq/compare/v9.0.0-next.6...v9.0.0-next.7) (2025-10-13)
21
+
22
+ **Note:** Version bump only for package redis-smq
23
+
6
24
  ## [9.0.0-next.6](https://github.com/weyoss/redis-smq/compare/v9.0.0-next.5...v9.0.0-next.6) (2025-10-13)
7
25
 
8
26
  **Note:** Version bump only for package redis-smq
@@ -8,13 +8,14 @@
8
8
  -- KEYS[3]: keyQueuePending
9
9
  -- KEYS[4]: keyQueueScheduled
10
10
  -- KEYS[5]: keyQueueMessages
11
- -- KEYS[6]: keyMessage
11
+ -- KEYS[6]: keyQueueConsumerGroups
12
+ -- KEYS[7]: keyMessage
12
13
  --
13
14
  -- ARGV layout:
14
15
  -- ARGV[1-11]: Queue property keys and scheduling values
15
- -- ARGV[12-34]: Message property keys (22 keys)
16
- -- ARGV[35-57]: Message property values (22 values)
17
-
16
+ -- ARGV[12-34]: Message property keys (23 keys)
17
+ -- ARGV[35-57]: Message property values (23 values)
18
+ -- ARGV[58]: consumerGroupId
18
19
  --
19
20
  -- This script depends on 'shared-procedures/publish-message.lua'.
20
21
  -- The content of 'shared-procedures/publish-message.lua' must be prepended to this script before loading it into Redis.
@@ -1,27 +1,29 @@
1
1
  -- Description:
2
2
  -- Publishes due scheduled messages for a SINGLE queue. This script handles two cases:
3
- -- 1. Simple scheduled message: The message is moved to the pending queue.
4
- -- 2. Repeating scheduled message: A new message is created and published, and the original is rescheduled.
3
+ -- 1. Simple scheduled message: The message is moved to the pending queue. If the target consumer group is gone, it is moved to the dead-letter queue.
4
+ -- 2. Repeating scheduled message: A new message is created and published. If the target consumer group is gone, the original repeating message is dead-lettered to terminate its cycle, and the new message for the current firing is discarded.
5
5
  --
6
6
  -- This script depends on 'shared-procedures/publish-message.lua'.
7
7
  -- The content of 'shared-procedures/publish-message.lua' must be prepended to this script before loading it into Redis.
8
8
  --
9
9
  -- KEYS:
10
- -- Static Keys (1-5):
10
+ -- Static Keys (1-7):
11
11
  -- KEYS[1]: keyQueueProperties
12
12
  -- KEYS[2]: keyQueuePending
13
13
  -- KEYS[3]: keyQueueMessages
14
14
  -- KEYS[4]: keyQueuePriorityPending
15
15
  -- KEYS[5]: keyQueueScheduled
16
- -- Dynamic Keys (6...): A repeating pair for each message.
16
+ -- KEYS[6]: keyQueueDeadLettered
17
+ -- KEYS[7]: keyQueueConsumerGroups
18
+ -- Dynamic Keys (8...): A repeating pair for each message.
17
19
  -- - keyMessage (for the new message, or an empty placeholder)
18
20
  -- - keyScheduledMessage (for the original message)
19
21
  --
20
22
  -- ARGV:
21
- -- ARGV[1-32]: A list of all EQueueProperty and EMessageProperty constants.
22
- -- ARGV[33...]: A flat list of repeating parameters for each message.
23
+ -- ARGV[1-34]: A list of all EQueueProperty and EMessageProperty constants.
24
+ -- ARGV[35...]: A flat list of repeating parameters for each message.
23
25
  --
24
- -- ARGV structure per message (12 parameters):
26
+ -- ARGV structure per message (13 parameters):
25
27
  -- 1. messageId (new message ID, or '')
26
28
  -- 2. message (new message body, or '')
27
29
  -- 3. messagePriority
@@ -34,6 +36,7 @@
34
36
  -- 10. scheduledMessageCronFired
35
37
  -- 11. scheduledMessageRepeatCount
36
38
  -- 12. scheduledMessageEffectiveScheduledDelay
39
+ -- 13. consumerGroupId
37
40
  --
38
41
  -- Returns:
39
42
  -- - The number of successfully processed messages.
@@ -45,6 +48,8 @@ local keyQueuePending = KEYS[2]
45
48
  local keyQueueMessages = KEYS[3]
46
49
  local keyQueuePriorityPending = KEYS[4]
47
50
  local keyQueueScheduled = KEYS[5]
51
+ local keyQueueDeadLettered = KEYS[6]
52
+ local keyQueueConsumerGroups = KEYS[7]
48
53
 
49
54
  -- Early exit if the queue does not exist. This check is now done only once per batch.
50
55
  local queueType = redis.call("HGET", keyQueueProperties, ARGV[1] -- EQueuePropertyQueueType
@@ -53,48 +58,50 @@ if queueType == false then
53
58
  return 0
54
59
  end
55
60
 
56
- -- Queue Property Constants (ARGV[1-7])
61
+ -- Queue Property Constants (ARGV[1-8])
57
62
  local EQueuePropertyQueueType = ARGV[1]
58
63
  local EQueuePropertyMessagesCount = ARGV[2]
59
64
  local EQueuePropertyPendingMessagesCount = ARGV[3]
60
65
  local EQueuePropertyScheduledMessagesCount = ARGV[4]
61
- local EQueuePropertyQueueTypePriorityQueue = ARGV[5]
62
- local EQueuePropertyQueueTypeLIFOQueue = ARGV[6]
63
- local EQueuePropertyQueueTypeFIFOQueue = ARGV[7]
64
-
65
- -- Message Status Constants (ARGV[8-9])
66
- local EMessagePropertyStatusPending = ARGV[8]
67
- local EMessagePropertyStatusScheduled = ARGV[9]
68
-
69
- -- Message Property Constants (ARGV[10-32])
70
- local EMessagePropertyId = ARGV[10]
71
- local EMessagePropertyStatus = ARGV[11]
72
- local EMessagePropertyMessage = ARGV[12]
73
- local EMessagePropertyScheduledAt = ARGV[13]
74
- local EMessagePropertyPublishedAt = ARGV[14]
75
- local EMessagePropertyProcessingStartedAt = ARGV[15]
76
- local EMessagePropertyDeadLetteredAt = ARGV[16]
77
- local EMessagePropertyAcknowledgedAt = ARGV[17]
78
- local EMessagePropertyUnacknowledgedAt = ARGV[18]
79
- local EMessagePropertyLastUnacknowledgedAt = ARGV[19]
80
- local EMessagePropertyLastScheduledAt = ARGV[20]
81
- local EMessagePropertyRequeuedAt = ARGV[21]
82
- local EMessagePropertyRequeueCount = ARGV[22]
83
- local EMessagePropertyLastRequeuedAt = ARGV[23]
84
- local EMessagePropertyLastRetriedAttemptAt = ARGV[24]
85
- local EMessagePropertyScheduledCronFired = ARGV[25]
86
- local EMessagePropertyAttempts = ARGV[26]
87
- local EMessagePropertyScheduledRepeatCount = ARGV[27]
88
- local EMessagePropertyExpired = ARGV[28]
89
- local EMessagePropertyEffectiveScheduledDelay = ARGV[29]
90
- local EMessagePropertyScheduledTimes = ARGV[30]
91
- local EMessagePropertyScheduledMessageParentId = ARGV[31]
92
- local EMessagePropertyRequeuedMessageParentId = ARGV[32]
66
+ local EQueuePropertyDeadLetteredMessagesCount = ARGV[5]
67
+ local EQueuePropertyQueueTypePriorityQueue = ARGV[6]
68
+ local EQueuePropertyQueueTypeLIFOQueue = ARGV[7]
69
+ local EQueuePropertyQueueTypeFIFOQueue = ARGV[8]
70
+
71
+ -- Message Status Constants (ARGV[9-11])
72
+ local EMessagePropertyStatusPending = ARGV[9]
73
+ local EMessagePropertyStatusScheduled = ARGV[10]
74
+ local EMessagePropertyStatusDeadLettered = ARGV[11]
75
+
76
+ -- Message Property Constants (ARGV[12-34])
77
+ local EMessagePropertyId = ARGV[12]
78
+ local EMessagePropertyStatus = ARGV[13]
79
+ local EMessagePropertyMessage = ARGV[14]
80
+ local EMessagePropertyScheduledAt = ARGV[15]
81
+ local EMessagePropertyPublishedAt = ARGV[16]
82
+ local EMessagePropertyProcessingStartedAt = ARGV[17]
83
+ local EMessagePropertyDeadLetteredAt = ARGV[18]
84
+ local EMessagePropertyAcknowledgedAt = ARGV[19]
85
+ local EMessagePropertyUnacknowledgedAt = ARGV[20]
86
+ local EMessagePropertyLastUnacknowledgedAt = ARGV[21]
87
+ local EMessagePropertyLastScheduledAt = ARGV[22]
88
+ local EMessagePropertyRequeuedAt = ARGV[23]
89
+ local EMessagePropertyRequeueCount = ARGV[24]
90
+ local EMessagePropertyLastRequeuedAt = ARGV[25]
91
+ local EMessagePropertyLastRetriedAttemptAt = ARGV[26]
92
+ local EMessagePropertyScheduledCronFired = ARGV[27]
93
+ local EMessagePropertyAttempts = ARGV[28]
94
+ local EMessagePropertyScheduledRepeatCount = ARGV[29]
95
+ local EMessagePropertyExpired = ARGV[30]
96
+ local EMessagePropertyEffectiveScheduledDelay = ARGV[31]
97
+ local EMessagePropertyScheduledTimes = ARGV[32]
98
+ local EMessagePropertyScheduledMessageParentId = ARGV[33]
99
+ local EMessagePropertyRequeuedMessageParentId = ARGV[34]
93
100
 
94
101
  -- Loop constants
95
- local INITIAL_ARGV_OFFSET = 32
96
- local INITIAL_KEY_OFFSET = 5
97
- local PARAMS_PER_MESSAGE = 12
102
+ local INITIAL_ARGV_OFFSET = 34
103
+ local INITIAL_KEY_OFFSET = 7
104
+ local PARAMS_PER_MESSAGE = 14
98
105
  local KEYS_PER_MESSAGE = 2
99
106
  local keyIndex = INITIAL_KEY_OFFSET + 1
100
107
  local processed_count = 0
@@ -113,83 +120,111 @@ for argvIndex = INITIAL_ARGV_OFFSET + 1, #ARGV, PARAMS_PER_MESSAGE do
113
120
  local scheduledMessageCronFired = ARGV[argvIndex + 9]
114
121
  local scheduledMessageRepeatCount = ARGV[argvIndex + 10]
115
122
  local scheduledMessageEffectiveScheduledDelay = ARGV[argvIndex + 11]
123
+ local consumerGroupId = ARGV[argvIndex + 12]
124
+ local messageDeadLetteredAt = ARGV[argvIndex + 13]
116
125
 
117
126
  -- Extract keys for the current message
118
127
  local keyMessage = KEYS[keyIndex]
119
128
  local keyScheduledMessage = KEYS[keyIndex + 1]
120
129
  keyIndex = keyIndex + KEYS_PER_MESSAGE
121
130
 
131
+ local group_exists = true
132
+ if consumerGroupId and consumerGroupId ~= '' then
133
+ if redis.call("SISMEMBER", keyQueueConsumerGroups, consumerGroupId) == 0 then
134
+ group_exists = false
135
+ end
136
+ end
137
+
122
138
  -- Case 1: A simple scheduled message is due. Move it to the pending queue.
123
139
  if newMessageId == '' then
124
- if queueType == EQueuePropertyQueueTypeFIFOQueue then
125
- redis.call("LPUSH", keyQueuePending, scheduledMessageId)
126
- elseif queueType == EQueuePropertyQueueTypeLIFOQueue then
127
- redis.call("RPUSH", keyQueuePending, scheduledMessageId)
128
- else -- Priority queue
129
- redis.call("ZADD", keyQueuePriorityPending, newMessagePriority, scheduledMessageId)
140
+ -- Always remove from scheduled queue first
141
+ if redis.call("ZREM", keyQueueScheduled, scheduledMessageId) == 1 then
142
+ redis.call("HINCRBY", keyQueueProperties, EQueuePropertyScheduledMessagesCount, -1)
143
+
144
+ if group_exists then
145
+ if queueType == EQueuePropertyQueueTypeFIFOQueue then
146
+ redis.call("LPUSH", keyQueuePending, scheduledMessageId)
147
+ elseif queueType == EQueuePropertyQueueTypeLIFOQueue then
148
+ redis.call("RPUSH", keyQueuePending, scheduledMessageId)
149
+ else -- Priority queue
150
+ redis.call("ZADD", keyQueuePriorityPending, newMessagePriority, scheduledMessageId)
151
+ end
152
+ redis.call("HINCRBY", keyQueueProperties, EQueuePropertyPendingMessagesCount, 1)
153
+
154
+ -- Update all relevant properties for the scheduled message
155
+ redis.call(
156
+ "HSET", keyScheduledMessage,
157
+ EMessagePropertyPublishedAt, scheduledMessagePublishedAt,
158
+ EMessagePropertyStatus, EMessagePropertyStatusPending,
159
+ EMessagePropertyLastScheduledAt, scheduledMessageLastScheduledAt,
160
+ EMessagePropertyScheduledTimes, scheduledMessageScheduledTimes,
161
+ EMessagePropertyScheduledCronFired, scheduledMessageCronFired,
162
+ EMessagePropertyScheduledRepeatCount, scheduledMessageRepeatCount,
163
+ EMessagePropertyEffectiveScheduledDelay, scheduledMessageEffectiveScheduledDelay
164
+ )
165
+ else
166
+ -- Group does not exist, dead-letter the message
167
+ redis.call("RPUSH", keyQueueDeadLettered, scheduledMessageId)
168
+ redis.call("HINCRBY", keyQueueProperties, EQueuePropertyDeadLetteredMessagesCount, 1)
169
+ redis.call(
170
+ "HSET", keyScheduledMessage,
171
+ EMessagePropertyStatus, EMessagePropertyStatusDeadLettered,
172
+ EMessagePropertyDeadLetteredAt, messageDeadLetteredAt)
173
+ end
174
+ processed_count = processed_count + 1
130
175
  end
131
- redis.call("ZREM", keyQueueScheduled, scheduledMessageId)
132
- redis.call("HINCRBY", keyQueueProperties, EQueuePropertyPendingMessagesCount, 1)
133
- redis.call("HINCRBY", keyQueueProperties, EQueuePropertyScheduledMessagesCount, -1)
134
-
135
- -- Update all relevant properties for the scheduled message
136
- redis.call(
137
- "HSET", keyScheduledMessage,
138
- EMessagePropertyPublishedAt, scheduledMessagePublishedAt,
139
- EMessagePropertyStatus, EMessagePropertyStatusPending,
140
- EMessagePropertyLastScheduledAt, scheduledMessageLastScheduledAt,
141
- EMessagePropertyScheduledTimes, scheduledMessageScheduledTimes,
142
- EMessagePropertyScheduledCronFired, scheduledMessageCronFired,
143
- EMessagePropertyScheduledRepeatCount, scheduledMessageRepeatCount,
144
- EMessagePropertyEffectiveScheduledDelay, scheduledMessageEffectiveScheduledDelay
145
- )
146
- processed_count = processed_count + 1
147
176
  else
148
- -- Case 2: A repeating message is due. Publish a new message and reschedule the original.
149
- if scheduledMessageNextScheduleTimestamp == "0" then
150
- -- This is the last repetition, so we don't reschedule.
151
- -- We just remove it from the scheduled set.
152
- redis.call("ZREM", keyQueueScheduled, scheduledMessageId)
153
- else
177
+ -- Case 2: A repeating message is due.
178
+ if group_exists then
179
+ -- Group exists: Publish a new message and reschedule the original.
180
+
154
181
  -- Reschedule the original message for its next occurrence
155
182
  redis.call("ZADD", keyQueueScheduled, scheduledMessageNextScheduleTimestamp, scheduledMessageId)
156
- end
157
183
 
158
- -- Update all relevant properties for the original scheduled message
159
- redis.call(
160
- "HSET", keyScheduledMessage,
161
- EMessagePropertyLastScheduledAt, scheduledMessageLastScheduledAt,
162
- EMessagePropertyScheduledTimes, scheduledMessageScheduledTimes,
163
- EMessagePropertyScheduledCronFired, scheduledMessageCronFired,
164
- EMessagePropertyScheduledRepeatCount, scheduledMessageRepeatCount,
165
- EMessagePropertyEffectiveScheduledDelay, scheduledMessageEffectiveScheduledDelay
166
- )
167
-
168
- -- Publishing the new message
169
- -- The order of keys must exactly match the 'publish-message.lua' script.
170
- local pKeys = {
171
- keyQueueProperties,
172
- keyQueuePriorityPending,
173
- keyQueuePending,
174
- keyQueueScheduled,
175
- keyQueueMessages,
176
- keyMessage
177
- }
178
- -- The order of arguments must exactly match the 'publish-message.lua' script.
179
- local pArgs = {
180
- -- Queue properties (1-7)
181
- EQueuePropertyQueueType, EQueuePropertyMessagesCount, EQueuePropertyPendingMessagesCount, EQueuePropertyScheduledMessagesCount, EQueuePropertyQueueTypePriorityQueue, EQueuePropertyQueueTypeLIFOQueue, EQueuePropertyQueueTypeFIFOQueue,
182
- -- Message priority and scheduling (8-11)
183
- newMessagePriority, '', EMessagePropertyStatusScheduled, EMessagePropertyStatusPending,
184
- -- Message Property Keys (12-34, 23 keys)
185
- EMessagePropertyId, EMessagePropertyStatus, EMessagePropertyMessage, EMessagePropertyScheduledAt, EMessagePropertyPublishedAt, EMessagePropertyProcessingStartedAt, EMessagePropertyDeadLetteredAt, EMessagePropertyAcknowledgedAt, EMessagePropertyUnacknowledgedAt, EMessagePropertyLastUnacknowledgedAt, EMessagePropertyLastScheduledAt, EMessagePropertyRequeuedAt, EMessagePropertyRequeueCount, EMessagePropertyLastRequeuedAt, EMessagePropertyLastRetriedAttemptAt, EMessagePropertyScheduledCronFired, EMessagePropertyAttempts, EMessagePropertyScheduledRepeatCount, EMessagePropertyExpired, EMessagePropertyEffectiveScheduledDelay, EMessagePropertyScheduledTimes, EMessagePropertyScheduledMessageParentId, EMessagePropertyRequeuedMessageParentId,
186
- -- Message Property Values (35-57, 23 values)
187
- newMessageId, EMessagePropertyStatusPending, newMessage, '', newMessagePublishedAt, '', '', '', '', '', '', '', '0', '', '', '0', '0', '0', '0', '0', '0', scheduledMessageId, ''
188
- }
189
- -- Publish the new message by calling the shared publish_message function
190
- local result = publish_message(pKeys, pArgs)
191
- if result ~= 'OK' then
192
- return 'PUBLISH_ERROR:' .. newMessageId .. ':' .. result
184
+ -- Update all relevant properties for the original scheduled message
185
+ redis.call(
186
+ "HSET", keyScheduledMessage,
187
+ EMessagePropertyLastScheduledAt, scheduledMessageLastScheduledAt,
188
+ EMessagePropertyScheduledTimes, scheduledMessageScheduledTimes,
189
+ EMessagePropertyScheduledCronFired, scheduledMessageCronFired,
190
+ EMessagePropertyScheduledRepeatCount, scheduledMessageRepeatCount,
191
+ EMessagePropertyEffectiveScheduledDelay, scheduledMessageEffectiveScheduledDelay
192
+ )
193
+
194
+ -- Publishing the new message
195
+ local pKeys = {
196
+ keyQueueProperties,
197
+ keyQueuePriorityPending,
198
+ keyQueuePending,
199
+ keyQueueScheduled,
200
+ keyQueueMessages,
201
+ keyQueueConsumerGroups,
202
+ keyMessage
203
+ }
204
+ local pArgs = {
205
+ EQueuePropertyQueueType, EQueuePropertyMessagesCount, EQueuePropertyPendingMessagesCount, EQueuePropertyScheduledMessagesCount, EQueuePropertyQueueTypePriorityQueue, EQueuePropertyQueueTypeLIFOQueue, EQueuePropertyQueueTypeFIFOQueue,
206
+ newMessagePriority, '', EMessagePropertyStatusScheduled, EMessagePropertyStatusPending,
207
+ EMessagePropertyId, EMessagePropertyStatus, EMessagePropertyMessage, EMessagePropertyScheduledAt, EMessagePropertyPublishedAt, EMessagePropertyProcessingStartedAt, EMessagePropertyDeadLetteredAt, EMessagePropertyAcknowledgedAt, EMessagePropertyUnacknowledgedAt, EMessagePropertyLastUnacknowledgedAt, EMessagePropertyLastScheduledAt, EMessagePropertyRequeuedAt, EMessagePropertyRequeueCount, EMessagePropertyLastRequeuedAt, EMessagePropertyLastRetriedAttemptAt, EMessagePropertyScheduledCronFired, EMessagePropertyAttempts, EMessagePropertyScheduledRepeatCount, EMessagePropertyExpired, EMessagePropertyEffectiveScheduledDelay, EMessagePropertyScheduledTimes, EMessagePropertyScheduledMessageParentId, EMessagePropertyRequeuedMessageParentId,
208
+ newMessageId, EMessagePropertyStatusPending, newMessage, '', newMessagePublishedAt, '', '', '', '', '', '', '', '0', '', '', '0', '0', '0', '0', '0', '0', scheduledMessageId, '',
209
+ consumerGroupId
210
+ }
211
+ local result = publish_message(pKeys, pArgs)
212
+ if result ~= 'OK' then
213
+ return 'PUBLISH_ERROR:' .. newMessageId .. ':' .. result
214
+ end
215
+ else
216
+ -- Group does not exist. Dead-letter the original repeating message to stop its cycle.
217
+ -- The new message for this firing is simply discarded.
218
+ if redis.call("ZREM", keyQueueScheduled, scheduledMessageId) == 1 then
219
+ redis.call("RPUSH", keyQueueDeadLettered, scheduledMessageId)
220
+ redis.call(
221
+ "HSET", keyScheduledMessage,
222
+ EMessagePropertyStatus, EMessagePropertyStatusDeadLettered,
223
+ EMessagePropertyDeadLetteredAt, messageDeadLetteredAt
224
+ )
225
+ redis.call("HINCRBY", keyQueueProperties, EQueuePropertyScheduledMessagesCount, -1)
226
+ redis.call("HINCRBY", keyQueueProperties, EQueuePropertyDeadLetteredMessagesCount, 1)
227
+ end
193
228
  end
194
229
  processed_count = processed_count + 1
195
230
  end
@@ -1,17 +1,20 @@
1
- -- Description:
2
1
  -- Re-queues messages from the delayed sorted set to the pending queue.
3
2
  -- This script is designed for batch processing to improve performance. It moves messages
4
3
  -- that are due for a retry back into the active processing queue.
5
4
  -- It atomically removes messages from the delayed queue to prevent race conditions
6
5
  -- and updates message status and the 'lastRetriedAttemptAt' timestamp.
7
6
  -- It supports FIFO, LIFO, and Priority queues.
7
+ -- For PUB/SUB queues, if a message's target consumer group has been deleted,
8
+ -- the message is moved to the dead-letter queue instead of being re-queued.
8
9
  -- Queue counters are updated once per batch for efficiency.
9
10
  --
10
11
  -- KEYS:
11
- -- Static Keys (1-2):
12
+ -- Static Keys (1-4):
12
13
  -- KEYS[1]: keyQueueProperties (HMAP)
13
14
  -- KEYS[2]: keyQueueDelayed (ZSET)
14
- -- Dynamic Keys (3...):
15
+ -- KEYS[3]: keyQueueDeadLettered (LIST)
16
+ -- KEYS[4]: keyQueueConsumerGroups (SET)
17
+ -- Dynamic Keys (5...):
15
18
  -- A flat list of repeating keys for each message.
16
19
  --
17
20
  -- KEYS structure per message (3 keys):
@@ -20,33 +23,39 @@
20
23
  -- 3. keyQueuePriorityPending (for Priority)
21
24
  --
22
25
  -- ARGV:
23
- -- Static ARGV (1-9):
24
- -- ARGV[1-8]: A list of all EQueueProperty and EMessageProperty constants.
25
- -- ARGV[9]: Current timestamp.
26
- -- Dynamic ARGV (10...):
26
+ -- Static ARGV (1-12):
27
+ -- ARGV[1-11]: A list of all EQueueProperty and EMessageProperty constants.
28
+ -- ARGV[12]: Current timestamp.
29
+ -- Dynamic ARGV (13...):
27
30
  -- A flat list of repeating parameters for each message.
28
31
  --
29
- -- ARGV structure per message (2 parameters):
32
+ -- ARGV structure per message (3 parameters):
30
33
  -- 1. messageId
31
34
  -- 2. messagePriority
35
+ -- 3. consumerGroupId
32
36
  --
33
37
  -- Returns:
34
- -- - The number of messages successfully processed and re-queued.
38
+ -- - The number of messages successfully processed (re-queued or dead-lettered).
35
39
 
36
40
  -- Static Keys
37
41
  local keyQueueProperties = KEYS[1]
38
42
  local keyQueueDelayed = KEYS[2]
43
+ local keyQueueDeadLettered = KEYS[3]
44
+ local keyQueueConsumerGroups = KEYS[4]
39
45
 
40
46
  -- Static ARGV
41
47
  local EQueuePropertyQueueType = ARGV[1]
42
48
  local EQueuePropertyDelayedMessagesCount = ARGV[2]
43
49
  local EQueuePropertyPendingMessagesCount = ARGV[3]
44
- local EMessagePropertyStatus = ARGV[4]
45
- local EMessagePropertyStatusPending = ARGV[5]
46
- local EMessagePropertyLastRetriedAttemptAt = ARGV[6]
47
- local EQueuePropertyQueueTypeLIFOQueue = ARGV[7]
48
- local EQueuePropertyQueueTypeFIFOQueue = ARGV[8]
49
- local timestamp = ARGV[9]
50
+ local EQueuePropertyDeadLetteredMessagesCount = ARGV[4]
51
+ local EMessagePropertyStatus = ARGV[5]
52
+ local EMessagePropertyStatusPending = ARGV[6]
53
+ local EMessagePropertyStatusDeadLettered = ARGV[7]
54
+ local EMessagePropertyDeadLetteredAt = ARGV[8]
55
+ local EMessagePropertyLastRetriedAttemptAt = ARGV[9]
56
+ local EQueuePropertyQueueTypeLIFOQueue = ARGV[10]
57
+ local EQueuePropertyQueueTypeFIFOQueue = ARGV[11]
58
+ local timestamp = ARGV[12]
50
59
 
51
60
  -- Early exit if the queue does not exist
52
61
  local queueType = redis.call("HGET", keyQueueProperties, EQueuePropertyQueueType)
@@ -55,19 +64,21 @@ if queueType == false then
55
64
  end
56
65
 
57
66
  -- Constants for parameter counts
58
- local INITIAL_KEY_OFFSET = 2
59
- local INITIAL_ARGV_OFFSET = 9
60
- local PARAMS_PER_MESSAGE = 2
67
+ local INITIAL_KEY_OFFSET = 4
68
+ local INITIAL_ARGV_OFFSET = 12
69
+ local PARAMS_PER_MESSAGE = 3
61
70
  local KEYS_PER_MESSAGE = 3
62
71
 
63
72
  local keyIndex = INITIAL_KEY_OFFSET + 1
64
- local processed_count = 0
73
+ local requeued_count = 0
74
+ local dead_lettered_count = 0
65
75
 
66
76
  -- Process messages in batches
67
77
  for argvIndex = INITIAL_ARGV_OFFSET + 1, #ARGV, PARAMS_PER_MESSAGE do
68
78
  -- Extract message parameters
69
79
  local messageId = ARGV[argvIndex]
70
80
  local messagePriority = ARGV[argvIndex + 1]
81
+ local consumerGroupId = ARGV[argvIndex + 2]
71
82
 
72
83
  -- Get the message keys for this iteration from KEYS
73
84
  local keyMessage = KEYS[keyIndex]
@@ -79,30 +90,57 @@ for argvIndex = INITIAL_ARGV_OFFSET + 1, #ARGV, PARAMS_PER_MESSAGE do
79
90
  -- If ZREM returns 0, the message was not found (e.g., already processed),
80
91
  -- so we skip it to prevent corrupting queue counters.
81
92
  if redis.call("ZREM", keyQueueDelayed, messageId) == 1 then
82
- -- Move the message to the pending queue.
83
- if queueType == EQueuePropertyQueueTypeFIFOQueue then
84
- redis.call("LPUSH", keyQueuePending, messageId)
85
- elseif queueType == EQueuePropertyQueueTypeLIFOQueue then
86
- redis.call("RPUSH", keyQueuePending, messageId)
87
- else -- Priority queue
88
- redis.call("ZADD", keyQueuePriorityPending, messagePriority, messageId)
93
+ local group_exists = true
94
+ if consumerGroupId and consumerGroupId ~= '' then
95
+ if redis.call("SISMEMBER", keyQueueConsumerGroups, consumerGroupId) == 0 then
96
+ group_exists = false
97
+ end
89
98
  end
90
99
 
91
- -- Update message properties
92
- redis.call("HSET", keyMessage,
93
- EMessagePropertyStatus, EMessagePropertyStatusPending,
94
- EMessagePropertyLastRetriedAttemptAt, timestamp
95
- )
100
+ if group_exists then
101
+ -- Move the message to the pending queue.
102
+ if queueType == EQueuePropertyQueueTypeFIFOQueue then
103
+ redis.call("LPUSH", keyQueuePending, messageId)
104
+ elseif queueType == EQueuePropertyQueueTypeLIFOQueue then
105
+ redis.call("RPUSH", keyQueuePending, messageId)
106
+ else -- Priority queue
107
+ redis.call("ZADD", keyQueuePriorityPending, messagePriority, messageId)
108
+ end
96
109
 
97
- processed_count = processed_count + 1
110
+ -- Update message properties
111
+ redis.call("HSET", keyMessage,
112
+ EMessagePropertyStatus, EMessagePropertyStatusPending,
113
+ EMessagePropertyLastRetriedAttemptAt, timestamp
114
+ )
115
+
116
+ requeued_count = requeued_count + 1
117
+ else
118
+ -- The consumer group does not exist. Dead-letter the message.
119
+ redis.call("RPUSH", keyQueueDeadLettered, messageId)
120
+
121
+ -- Update message properties
122
+ redis.call("HSET", keyMessage,
123
+ EMessagePropertyStatus, EMessagePropertyStatusDeadLettered,
124
+ EMessagePropertyDeadLetteredAt, timestamp
125
+ )
126
+
127
+ dead_lettered_count = dead_lettered_count + 1
128
+ end
98
129
  end
99
130
  end
100
131
 
101
132
  -- After processing the whole batch, update queue counters once.
102
- -- This is much more efficient than updating them inside the loop.
103
- if processed_count > 0 then
104
- redis.call("HINCRBY", keyQueueProperties, EQueuePropertyDelayedMessagesCount, -processed_count)
105
- redis.call("HINCRBY", keyQueueProperties, EQueuePropertyPendingMessagesCount, processed_count)
133
+ if requeued_count > 0 then
134
+ redis.call("HINCRBY", keyQueueProperties, EQueuePropertyPendingMessagesCount, requeued_count)
135
+ end
136
+
137
+ if dead_lettered_count > 0 then
138
+ redis.call("HINCRBY", keyQueueProperties, EQueuePropertyDeadLetteredMessagesCount, dead_lettered_count)
139
+ end
140
+
141
+ local total_processed = requeued_count + dead_lettered_count
142
+ if total_processed > 0 then
143
+ redis.call("HINCRBY", keyQueueProperties, EQueuePropertyDelayedMessagesCount, -total_processed)
106
144
  end
107
145
 
108
- return processed_count
146
+ return total_processed