-
Notifications
You must be signed in to change notification settings - Fork 440
[dbal] Introduce redelivery support based on visibility approach. #581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[dbal] Introduce redelivery support based on visibility approach. #581
Conversation
rosamarsky
commented
Oct 23, 2018
•
edited by makasim
Loading
edited by makasim
- Upd docs
pkg/dbal/DbalConsumer.php
Outdated
{ | ||
$this->uuid = $uuid; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consumerId
pkg/dbal/DbalConsumer.php
Outdated
$this->redeliveryDelay = 600; // 10 minutes | ||
} | ||
|
||
public function getUuid(): UuidInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getConsumerId(): string
pkg/dbal/DbalConsumer.php
Outdated
$this->context = $context; | ||
$this->queue = $queue; | ||
$this->dbal = $this->context->getDbalConnection(); | ||
|
||
$this->pollingInterval = 1000; | ||
$this->redeliveryDelay = 600; // 10 minutes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be in milliseconds. 20 minutes looks better to me.
pkg/dbal/DbalConsumer.php
Outdated
} | ||
|
||
protected function receiveMessage(): ?DbalMessage | ||
{ | ||
$this->refreshRedeliveredMessages(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$this->redeliverMessages();
faa1d8e
to
0d44dff
Compare
pkg/dbal/DbalConsumer.php
Outdated
* @var int | ||
*/ | ||
private $pollingInterval; | ||
private $redeliveryDelay = 1200000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set the value in the constructor.
pkg/dbal/DbalConsumer.php
Outdated
} | ||
|
||
protected function receiveMessage(): ?DbalMessage | ||
private function receiveMessage(): ?DbalMessage |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move the from this method to receiveNoWait. Remove the method after moving
pkg/dbal/DbalConsumer.php
Outdated
@@ -115,33 +97,54 @@ public function reject(Message $message, bool $requeue = false): void | |||
|
|||
return; | |||
} | |||
|
|||
$this->deleteMessageByDeliveryId($message->getDeliveryId()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename to deleteMessage. the method should accept a message instance and throws an exception if delivery id is not set.
pkg/dbal/DbalConsumer.php
Outdated
if (false == $dbalMessage) { | ||
$now = (int) time(); | ||
$redeliveryDelay = $this->getRedeliveryDelay() / 1000; // milliseconds to seconds | ||
$deliveryId = Uuid::uuid1()->toString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer (string) Uuid::uuid1()
pkg/dbal/DbalConsumer.php
Outdated
$deliveryId = Uuid::uuid1()->toString(); | ||
|
||
// get top message from the queue | ||
$message = $this->fetchMessage($now); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the method could return null, if no message in the queue. but you do not check that a few lines later and assume the message is there.
pkg/dbal/DbalConsumer.php
Outdated
|
||
$this->dbal->commit(); | ||
|
||
if (empty($dbalMessage['time_to_live']) || ($dbalMessage['time_to_live'] / 1000) > microtime(true)) { | ||
if (empty($dbalMessage['time_to_live']) || $dbalMessage['time_to_live'] > time()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do not expire redelivered messages
0d44dff
to
5f4ef90
Compare
pkg/dbal/DbalContext.php
Outdated
|
||
$table->setPrimaryKey(['id']); | ||
$table->addIndex(['published_at']); | ||
$table->addIndex(['queue']); | ||
$table->addIndex(['priority']); | ||
$table->addIndex(['delayed_until']); | ||
$table->addIndex(['priority', 'published_at']); | ||
$table->addIndex(['redeliver_after']); | ||
$table->addUniqueIndex(['delivery_id']); | ||
$table->addIndex(['delivery_id']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove NOT unique index ( there is no need for two indexes)
pkg/dbal/DbalConsumer.php
Outdated
} | ||
|
||
protected function receiveMessage(): ?DbalMessage | ||
private function deleteMessage(?string $deliveryId): void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Null is not allowed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also add a check that the string is not empty.
pkg/dbal/DbalConsumer.php
Outdated
->addOrderBy('priority', 'desc') | ||
->addOrderBy('published_at', 'asc') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you please test the behavior of sorting when a priority is null
@rosamarsky could you update docs? use as an example https://github.com/php-enqueue/enqueue-dev/pull/585/files#diff-1ab2e57b012481304b916b4987c773b5L8 |
The code shared between Consumer and SubscriptionConsumer could go to a trait. Look at how it is done in redis https://github.com/php-enqueue/enqueue-dev/blob/master/pkg/redis/RedisConsumerHelperTrait.php |
pkg/dbal/DbalConsumer.php
Outdated
return null; | ||
} | ||
|
||
usleep($this->pollingInterval * 1000); | ||
$this->markMessageAsDeliveredToConsumer($message, $deliveryId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
markMessageDelivered
pkg/dbal/DbalConsumerHelperTrait.php
Outdated
protected function markMessageAsDeliveredToConsumer(array $message, string $deliveryId): void | ||
{ | ||
$now = time(); | ||
$redeliveryDelay = $this->getRedeliveryDelay() / 1000; // milliseconds to seconds |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the method could not be used in the trait. pass the delay as argument
; | ||
|
||
$sql = $query->getSQL().' '.$this->dbal->getDatabasePlatform()->getWriteLockSQL(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need a transaction in both consumer and subscription consumer
both should use getWriteLockSQL
pkg/dbal/DbalConsumerHelperTrait.php
Outdated
|
||
abstract public function getConnection(): Connection; | ||
|
||
protected function fetchMessage(array $queues): ?array |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be the only one method, it should return an array with a message and queue it was consumed from.
498ccff
to
311bfdf
Compare
311bfdf
to
03c40ab
Compare
pkg/dbal/DbalConsumer.php
Outdated
return $this->context; | ||
} | ||
|
||
public function getConnection(): Connection |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why public?
$timeout /= 1000; | ||
$startAt = microtime(true); | ||
$deliveryId = (string) Uuid::uuid1(); | ||
$redeliveryDelay = $this->getRedeliveryDelay() / 1000; // milliseconds to seconds |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the line states that redeliveryDelay
is in seconds.
public function receiveNoWait(): ?Message | ||
{ | ||
return $this->receiveMessage(); | ||
return null; | ||
} | ||
|
||
/** | ||
* @param DbalMessage $message | ||
*/ | ||
public function acknowledge(Message $message): void | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add an exception
InvalidMessageException::assertMessageInstanceOf($message, DbalMessage::class);
pkg/dbal/DbalConsumer.php
Outdated
} | ||
|
||
private function fetchMessage(int $now): ?array | ||
private function redeliverMessages(): void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the method should be in the consumer trait. It must be called in subscription consumer too.
pkg/dbal/DbalConsumerHelperTrait.php
Outdated
try { | ||
$now = time(); | ||
|
||
$this->getConnection()->beginTransaction(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be out of try ... catch
block
pkg/dbal/DbalConsumerHelperTrait.php
Outdated
|
||
abstract public function getConnection(): Connection; | ||
|
||
protected function fetchMessage(array $queues, string $deliveryId, int $redeliveryDelay): ?array |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if this method returns an instance of DbalMessage
. You have access to context so you can use a $context->convertMessage()
method.
$endAt = time() + $timeout; | ||
$now = time(); | ||
$timeout /= 1000; | ||
$deliveryId = (string) Uuid::uuid1(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it should be generated per message, hence the var init should be done before fetchMessage call.
$now = time(); | ||
$timeout /= 1000; | ||
$deliveryId = (string) Uuid::uuid1(); | ||
$redeliveryDelay = $this->getRedeliveryDelay() / 1000; // milliseconds to seconds |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already seconds
@@ -57,12 +94,8 @@ public function consume(int $timeout = 0): void | |||
$currentQueueNames = $queueNames; | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should call redeliverMessages
method. on every cycle.
2a1d940
to
84dca99
Compare
84dca99
to
50f3f07
Compare
return null; | ||
// get top message from the queue | ||
if ($message = $this->fetchMessage([$this->queue->getQueueName()], $redeliveryDelay)) { | ||
if ($message['redelivered'] || empty($message['time_to_live']) || $message['time_to_live'] > time()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the expired message has to be deleted from message, I think that expiration logic could be moved to fetchMessage method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I gave it another though, and I now I think we could cook a query that removes expired messages. just like redeliverMessage. the expiration in the code should still be preserved
@@ -111,106 +108,34 @@ public function reject(Message $message, bool $requeue = false): void | |||
InvalidMessageException::assertMessageInstanceOf($message, DbalMessage::class); | |||
|
|||
if ($requeue) { | |||
$this->context->createProducer()->send($this->queue, $message); | |||
$this->getContext()->createProducer()->send($this->queue, $message); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The redelivered flag should be forced false.
} | ||
|
||
private function fetchMessage(int $now): ?array | ||
private function deleteMessage(string $deliveryId): void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the method should be moved to trait.
e979200
to
a82d298
Compare
@@ -108,6 +109,8 @@ public function reject(Message $message, bool $requeue = false): void | |||
InvalidMessageException::assertMessageInstanceOf($message, DbalMessage::class); | |||
|
|||
if ($requeue) { | |||
$message->setRedelivered(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clone message and than force redelivered false
189ed8c
to
24129f1
Compare
24129f1
to
239161b
Compare
Thank you Roman for your work! |