Skip to content

[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

Merged

Conversation

rosamarsky
Copy link
Contributor

@rosamarsky rosamarsky commented Oct 23, 2018

  • Upd docs

@makasim makasim changed the title Changed consume process [dbal] Changed consume process Oct 23, 2018
{
$this->uuid = $uuid;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consumerId

$this->redeliveryDelay = 600; // 10 minutes
}

public function getUuid(): UuidInterface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getConsumerId(): string

$this->context = $context;
$this->queue = $queue;
$this->dbal = $this->context->getDbalConnection();

$this->pollingInterval = 1000;
$this->redeliveryDelay = 600; // 10 minutes
Copy link
Member

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.

}

protected function receiveMessage(): ?DbalMessage
{
$this->refreshRedeliveredMessages();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$this->redeliverMessages();

@makasim makasim changed the title [dbal] Changed consume process [dbal] Introduce redelivery support based on visibility approach. Oct 23, 2018
@rosamarsky rosamarsky force-pushed the dbal-subscription-consumer-improvements branch 4 times, most recently from faa1d8e to 0d44dff Compare October 24, 2018 15:33
* @var int
*/
private $pollingInterval;
private $redeliveryDelay = 1200000;
Copy link
Member

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.

}

protected function receiveMessage(): ?DbalMessage
private function receiveMessage(): ?DbalMessage
Copy link
Member

@makasim makasim Oct 27, 2018

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

@@ -115,33 +97,54 @@ public function reject(Message $message, bool $requeue = false): void

return;
}

$this->deleteMessageByDeliveryId($message->getDeliveryId());
Copy link
Member

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.

if (false == $dbalMessage) {
$now = (int) time();
$redeliveryDelay = $this->getRedeliveryDelay() / 1000; // milliseconds to seconds
$deliveryId = Uuid::uuid1()->toString();
Copy link
Member

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()

$deliveryId = Uuid::uuid1()->toString();

// get top message from the queue
$message = $this->fetchMessage($now);
Copy link
Member

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.


$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()) {
Copy link
Member

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

@rosamarsky rosamarsky force-pushed the dbal-subscription-consumer-improvements branch from 0d44dff to 5f4ef90 Compare October 29, 2018 16:15

$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']);
Copy link
Member

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)

}

protected function receiveMessage(): ?DbalMessage
private function deleteMessage(?string $deliveryId): void
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Null is not allowed.

Copy link
Member

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.

->addOrderBy('priority', 'desc')
->addOrderBy('published_at', 'asc')
Copy link
Member

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

@makasim
Copy link
Member

makasim commented Oct 30, 2018

@makasim
Copy link
Member

makasim commented Oct 30, 2018

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

return null;
}

usleep($this->pollingInterval * 1000);
$this->markMessageAsDeliveredToConsumer($message, $deliveryId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

markMessageDelivered

protected function markMessageAsDeliveredToConsumer(array $message, string $deliveryId): void
{
$now = time();
$redeliveryDelay = $this->getRedeliveryDelay() / 1000; // milliseconds to seconds
Copy link
Member

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();
Copy link
Member

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


abstract public function getConnection(): Connection;

protected function fetchMessage(array $queues): ?array
Copy link
Member

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.

@rosamarsky rosamarsky force-pushed the dbal-subscription-consumer-improvements branch from 498ccff to 311bfdf Compare October 30, 2018 16:23
@rosamarsky rosamarsky force-pushed the dbal-subscription-consumer-improvements branch from 311bfdf to 03c40ab Compare October 30, 2018 16:26
return $this->context;
}

public function getConnection(): Connection
Copy link
Member

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
Copy link
Member

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
{
Copy link
Member

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);

}

private function fetchMessage(int $now): ?array
private function redeliverMessages(): void
Copy link
Member

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.

try {
$now = time();

$this->getConnection()->beginTransaction();
Copy link
Member

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


abstract public function getConnection(): Connection;

protected function fetchMessage(array $queues, string $deliveryId, int $redeliveryDelay): ?array
Copy link
Member

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();
Copy link
Member

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
Copy link
Member

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;
}

Copy link
Member

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.

@rosamarsky rosamarsky force-pushed the dbal-subscription-consumer-improvements branch 2 times, most recently from 2a1d940 to 84dca99 Compare October 30, 2018 18:05
@rosamarsky rosamarsky force-pushed the dbal-subscription-consumer-improvements branch from 84dca99 to 50f3f07 Compare October 30, 2018 18:13
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()) {
Copy link
Member

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

Copy link
Member

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);
Copy link
Member

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
Copy link
Member

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.

@rosamarsky rosamarsky force-pushed the dbal-subscription-consumer-improvements branch from e979200 to a82d298 Compare October 31, 2018 16:12
@@ -108,6 +109,8 @@ public function reject(Message $message, bool $requeue = false): void
InvalidMessageException::assertMessageInstanceOf($message, DbalMessage::class);

if ($requeue) {
$message->setRedelivered(false);
Copy link
Member

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

@rosamarsky rosamarsky force-pushed the dbal-subscription-consumer-improvements branch 3 times, most recently from 189ed8c to 24129f1 Compare October 31, 2018 17:55
@rosamarsky rosamarsky force-pushed the dbal-subscription-consumer-improvements branch from 24129f1 to 239161b Compare October 31, 2018 17:59
@makasim makasim merged commit dee836c into php-enqueue:master Oct 31, 2018
@makasim
Copy link
Member

makasim commented Oct 31, 2018

Thank you Roman for your work!

@makasim makasim added this to the 0.9 milestone Nov 15, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants