Don't load subscriber IDs in memory when preparing tasks from static segments [MAILPOET-903]
This commit is contained in:
@ -78,16 +78,14 @@ class SendingQueue extends APIEndpoint {
|
|||||||
} else {
|
} else {
|
||||||
$segments = $newsletter->segments()->findArray();
|
$segments = $newsletter->segments()->findArray();
|
||||||
$finder = new SubscribersFinder();
|
$finder = new SubscribersFinder();
|
||||||
$subscribers = $finder->getSubscribersByList($segments);
|
$subscribers_count = $finder->addSubscribersToTaskFromSegments($queue->task(), $segments);
|
||||||
$subscribers = Helpers::flattenArray($subscribers);
|
if(!$subscribers_count) {
|
||||||
if(!count($subscribers)) {
|
|
||||||
return $this->errorResponse(array(
|
return $this->errorResponse(array(
|
||||||
APIError::UNKNOWN => __('There are no subscribers in that list!', 'mailpoet')
|
APIError::UNKNOWN => __('There are no subscribers in that list!', 'mailpoet')
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
$queue->status = null;
|
$queue->status = null;
|
||||||
$queue->scheduled_at = null;
|
$queue->scheduled_at = null;
|
||||||
$queue->setSubscribers($subscribers);
|
|
||||||
|
|
||||||
// set newsletter status
|
// set newsletter status
|
||||||
$newsletter->setStatus(Newsletter::STATUS_SENDING);
|
$newsletter->setStatus(Newsletter::STATUS_SENDING);
|
||||||
|
@ -78,10 +78,9 @@ class Scheduler {
|
|||||||
// ensure that subscribers are in segments
|
// ensure that subscribers are in segments
|
||||||
|
|
||||||
$finder = new SubscribersFinder();
|
$finder = new SubscribersFinder();
|
||||||
$subscribers = $finder->getSubscribersByList($segments);
|
$subscribers_count = $finder->addSubscribersToTaskFromSegments($queue->task(), $segments);
|
||||||
$subscribers = Helpers::flattenArray($subscribers);
|
|
||||||
|
|
||||||
if(empty($subscribers)) {
|
if(empty($subscribers_count)) {
|
||||||
return $this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
|
return $this->deleteQueueOrUpdateNextRunDate($queue, $newsletter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +90,6 @@ class Scheduler {
|
|||||||
|
|
||||||
// queue newsletter for delivery
|
// queue newsletter for delivery
|
||||||
$queue->newsletter_id = $notification_history->id;
|
$queue->newsletter_id = $notification_history->id;
|
||||||
$queue->setSubscribers($subscribers);
|
|
||||||
$queue->status = null;
|
$queue->status = null;
|
||||||
$queue->save();
|
$queue->save();
|
||||||
// update notification status
|
// update notification status
|
||||||
@ -102,10 +100,8 @@ class Scheduler {
|
|||||||
function processScheduledStandardNewsletter($newsletter, $queue) {
|
function processScheduledStandardNewsletter($newsletter, $queue) {
|
||||||
$segments = $newsletter->segments()->findArray();
|
$segments = $newsletter->segments()->findArray();
|
||||||
$finder = new SubscribersFinder();
|
$finder = new SubscribersFinder();
|
||||||
$subscribers = $finder->getSubscribersByList($segments);
|
$subscribers_count = $finder->addSubscribersToTaskFromSegments($queue->task(), $segments);
|
||||||
$subscribers = Helpers::flattenArray($subscribers);
|
|
||||||
// update current queue
|
// update current queue
|
||||||
$queue->setSubscribers($subscribers);
|
|
||||||
$queue->status = null;
|
$queue->status = null;
|
||||||
$queue->save();
|
$queue->save();
|
||||||
// update newsletter status
|
// update newsletter status
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
|
|
||||||
namespace MailPoet\Segments;
|
namespace MailPoet\Segments;
|
||||||
|
|
||||||
|
use MailPoet\Models\ScheduledTask;
|
||||||
|
use MailPoet\Models\ScheduledTaskSubscriber;
|
||||||
use MailPoet\Models\Segment;
|
use MailPoet\Models\Segment;
|
||||||
use MailPoet\Models\Subscriber;
|
use MailPoet\Models\Subscriber;
|
||||||
|
use MailPoet\Util\Helpers;
|
||||||
use MailPoet\WP\Hooks;
|
use MailPoet\WP\Hooks;
|
||||||
|
|
||||||
class SubscribersFinder {
|
class SubscribersFinder {
|
||||||
@ -18,7 +21,7 @@ class SubscribersFinder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function findSubscribersInSegment($segment, $subscribers_to_process_ids) {
|
private function findSubscribersInSegment($segment, $subscribers_to_process_ids) {
|
||||||
if($segment['type'] === Segment::TYPE_DEFAULT || $segment['type'] === Segment::TYPE_WP_USERS) {
|
if($this->isStaticSegment($segment)) {
|
||||||
$subscribers = Subscriber::findSubscribersInSegments($subscribers_to_process_ids, array($segment['id']))->findMany();
|
$subscribers = Subscriber::findSubscribersInSegments($subscribers_to_process_ids, array($segment['id']))->findMany();
|
||||||
return Subscriber::extractSubscribersIds($subscribers);
|
return Subscriber::extractSubscribersIds($subscribers);
|
||||||
}
|
}
|
||||||
@ -41,7 +44,7 @@ class SubscribersFinder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function getSubscribers($segment) {
|
private function getSubscribers($segment) {
|
||||||
if($segment['type'] === Segment::TYPE_DEFAULT || $segment['type'] === Segment::TYPE_WP_USERS) {
|
if($this->isStaticSegment($segment)) {
|
||||||
return Subscriber::getSubscribedInSegments(array($segment['id']))->findArray();
|
return Subscriber::getSubscribedInSegments(array($segment['id']))->findArray();
|
||||||
}
|
}
|
||||||
$finders = Hooks::applyFilters('mailpoet_get_subscribers_in_segment_finders', array());
|
$finders = Hooks::applyFilters('mailpoet_get_subscribers_in_segment_finders', array());
|
||||||
@ -54,6 +57,91 @@ class SubscribersFinder {
|
|||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isStaticSegment($segment) {
|
||||||
|
return $segment['type'] === Segment::TYPE_DEFAULT || $segment['type'] === Segment::TYPE_WP_USERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSubscribersToTaskFromSegments(ScheduledTask $task, array $segments) {
|
||||||
|
// Prepare subscribers on the DB side for performance reasons
|
||||||
|
$staticSegments = array();
|
||||||
|
$dynamicSegments = array();
|
||||||
|
foreach($segments as $segment) {
|
||||||
|
if($this->isStaticSegment($segment)) {
|
||||||
|
$staticSegments[] = $segment;
|
||||||
|
} else {
|
||||||
|
$dynamicSegments[] = $segment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$count = 0;
|
||||||
|
if(!empty($staticSegments)) {
|
||||||
|
$count += $this->addSubscribersToTaskFromStaticSegments($task, $staticSegments);
|
||||||
|
}
|
||||||
|
if(!empty($dynamicSegments)) {
|
||||||
|
$count += $this->addSubscribersToTaskFromDynamicSegments($task, $dynamicSegments);
|
||||||
|
}
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addSubscribersToTaskFromStaticSegments(ScheduledTask $task, array $segments) {
|
||||||
|
$segment_ids = Helpers::arrayColumn($segments, 'id');
|
||||||
|
Subscriber::rawExecute(
|
||||||
|
'INSERT INTO ' . MP_SCHEDULED_TASK_SUBSCRIBERS_TABLE . '
|
||||||
|
(task_id, subscriber_id, processed)
|
||||||
|
SELECT DISTINCT ? as task_id, subscribers.`id` as subscriber_id, ? as processed
|
||||||
|
FROM ' . MP_SUBSCRIBER_SEGMENT_TABLE . ' relation
|
||||||
|
JOIN ' . MP_SUBSCRIBERS_TABLE . ' subscribers ON subscribers.id = relation.subscriber_id
|
||||||
|
WHERE subscribers.`deleted_at` IS NULL
|
||||||
|
AND subscribers.`status` = ?
|
||||||
|
AND relation.`status` = ?
|
||||||
|
AND relation.`segment_id` IN (' . join(',', array_map('intval', $segment_ids)) . ')',
|
||||||
|
array(
|
||||||
|
$task->id,
|
||||||
|
ScheduledTaskSubscriber::STATUS_UNPROCESSED,
|
||||||
|
Subscriber::STATUS_SUBSCRIBED,
|
||||||
|
Subscriber::STATUS_SUBSCRIBED
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return \ORM::get_last_statement()->rowCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addSubscribersToTaskFromDynamicSegments(ScheduledTask $task, array $segments) {
|
||||||
|
$count = 0;
|
||||||
|
foreach($segments as $segment) {
|
||||||
|
$count += $this->addSubscribersToTaskFromDynamicSegment($task, $segment);
|
||||||
|
}
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addSubscribersToTaskFromDynamicSegment(ScheduledTask $task, $segment) {
|
||||||
|
$finders = Hooks::applyFilters('mailpoet_get_subscribers_in_segment_finders', array());
|
||||||
|
$count = 0;
|
||||||
|
foreach($finders as $finder) {
|
||||||
|
$subscribers = $finder->getSubscriberIdsInSegment($segment);
|
||||||
|
if($subscribers) {
|
||||||
|
$count += $this->addSubscribersToTaskByIds($task, $subscribers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addSubscribersToTaskByIds(ScheduledTask $task, array $subscribers) {
|
||||||
|
Subscriber::rawExecute(
|
||||||
|
'INSERT INTO ' . MP_SCHEDULED_TASK_SUBSCRIBERS_TABLE . '
|
||||||
|
(task_id, subscriber_id, processed)
|
||||||
|
SELECT DISTINCT ? as task_id, subscribers.`id` as subscriber_id, ? as processed
|
||||||
|
FROM ' . MP_SUBSCRIBERS_TABLE . ' subscribers
|
||||||
|
WHERE subscribers.`deleted_at` IS NULL
|
||||||
|
AND subscribers.`status` = ?
|
||||||
|
AND subscribers.`id` IN (' . join(',', array_map('intval', $subscribers)) . ')',
|
||||||
|
array(
|
||||||
|
$task->id,
|
||||||
|
ScheduledTaskSubscriber::STATUS_UNPROCESSED,
|
||||||
|
Subscriber::STATUS_SUBSCRIBED
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return \ORM::get_last_statement()->rowCount();
|
||||||
|
}
|
||||||
|
|
||||||
private function unique($subscribers) {
|
private function unique($subscribers) {
|
||||||
$result = array();
|
$result = array();
|
||||||
foreach($subscribers as $subscriber) {
|
foreach($subscribers as $subscriber) {
|
||||||
|
@ -381,7 +381,7 @@ class SchedulerTest extends \MailPoetTest {
|
|||||||
$updated_newsletter = Newsletter::findOne($newsletter->id);
|
$updated_newsletter = Newsletter::findOne($newsletter->id);
|
||||||
expect($updated_newsletter->status)->equals(Newsletter::STATUS_SENDING);
|
expect($updated_newsletter->status)->equals(Newsletter::STATUS_SENDING);
|
||||||
// SubscribersFinder is used for getting subscribers
|
// SubscribersFinder is used for getting subscribers
|
||||||
$finder->verifyInvoked('getSubscribersByList');
|
$finder->verifyInvoked('addSubscribersToTaskFromSegments');
|
||||||
}
|
}
|
||||||
|
|
||||||
function testItFailsToProcessPostNotificationNewsletterWhenSegmentsDontExist() {
|
function testItFailsToProcessPostNotificationNewsletterWhenSegmentsDontExist() {
|
||||||
@ -447,7 +447,7 @@ class SchedulerTest extends \MailPoetTest {
|
|||||||
->findOne();
|
->findOne();
|
||||||
expect($updated_notification_history->status)->equals(Newsletter::STATUS_SENDING);
|
expect($updated_notification_history->status)->equals(Newsletter::STATUS_SENDING);
|
||||||
// SubscribersFinder is used for getting subscribers
|
// SubscribersFinder is used for getting subscribers
|
||||||
$finder->verifyInvoked('getSubscribersByList');
|
$finder->verifyInvoked('addSubscribersToTaskFromSegments');
|
||||||
}
|
}
|
||||||
|
|
||||||
function testItFailsToProcessWhenScheduledQueuesNotFound() {
|
function testItFailsToProcessWhenScheduledQueuesNotFound() {
|
||||||
|
@ -5,14 +5,19 @@ namespace MailPoet\Segments;
|
|||||||
require_once('FinderMock.php');
|
require_once('FinderMock.php');
|
||||||
|
|
||||||
use Codeception\Util\Stub;
|
use Codeception\Util\Stub;
|
||||||
|
use MailPoet\Models\ScheduledTask;
|
||||||
|
use MailPoet\Models\ScheduledTaskSubscriber;
|
||||||
use MailPoet\Models\Segment;
|
use MailPoet\Models\Segment;
|
||||||
use MailPoet\Models\Subscriber;
|
use MailPoet\Models\Subscriber;
|
||||||
use MailPoet\Models\SubscriberSegment;
|
use MailPoet\Models\SubscriberSegment;
|
||||||
|
use MailPoet\Tasks\Sending as SendingTask;
|
||||||
use MailPoet\WP\Hooks;
|
use MailPoet\WP\Hooks;
|
||||||
|
|
||||||
class SubscribersFinderTest extends \MailPoetTest {
|
class SubscribersFinderTest extends \MailPoetTest {
|
||||||
|
|
||||||
function _before() {
|
function _before() {
|
||||||
|
\ORM::raw_execute('TRUNCATE ' . ScheduledTask::$_table);
|
||||||
|
\ORM::raw_execute('TRUNCATE ' . ScheduledTaskSubscriber::$_table);
|
||||||
\ORM::raw_execute('TRUNCATE ' . Segment::$_table);
|
\ORM::raw_execute('TRUNCATE ' . Segment::$_table);
|
||||||
\ORM::raw_execute('TRUNCATE ' . SubscriberSegment::$_table);
|
\ORM::raw_execute('TRUNCATE ' . SubscriberSegment::$_table);
|
||||||
\ORM::raw_execute('TRUNCATE ' . Subscriber::$_table);
|
\ORM::raw_execute('TRUNCATE ' . Subscriber::$_table);
|
||||||
@ -45,6 +50,7 @@ class SubscribersFinderTest extends \MailPoetTest {
|
|||||||
));
|
));
|
||||||
SubscriberSegment::resubscribeToAllSegments($this->subscriber_2);
|
SubscriberSegment::resubscribeToAllSegments($this->subscriber_2);
|
||||||
SubscriberSegment::resubscribeToAllSegments($this->subscriber_3);
|
SubscriberSegment::resubscribeToAllSegments($this->subscriber_3);
|
||||||
|
$this->sending = SendingTask::create();
|
||||||
}
|
}
|
||||||
|
|
||||||
function testGetSubscribersInSegmentDefaultSegment() {
|
function testGetSubscribersInSegmentDefaultSegment() {
|
||||||
@ -70,7 +76,7 @@ class SubscribersFinderTest extends \MailPoetTest {
|
|||||||
$mock
|
$mock
|
||||||
->expects($this->once())
|
->expects($this->once())
|
||||||
->method('getSubscriberIdsInSegment')
|
->method('getSubscriberIdsInSegment')
|
||||||
->will($this->returnValue(array($this->subscriber_1)));
|
->will($this->returnValue(array($this->subscriber_1->id)));
|
||||||
|
|
||||||
remove_all_filters('mailpoet_get_subscribers_in_segment_finders');
|
remove_all_filters('mailpoet_get_subscribers_in_segment_finders');
|
||||||
Hooks::addFilter('mailpoet_get_subscribers_in_segment_finders', function () use ($mock) {
|
Hooks::addFilter('mailpoet_get_subscribers_in_segment_finders', function () use ($mock) {
|
||||||
@ -89,7 +95,7 @@ class SubscribersFinderTest extends \MailPoetTest {
|
|||||||
$mock
|
$mock
|
||||||
->expects($this->exactly(2))
|
->expects($this->exactly(2))
|
||||||
->method('getSubscriberIdsInSegment')
|
->method('getSubscriberIdsInSegment')
|
||||||
->will($this->returnValue(array($this->subscriber_1)));
|
->will($this->returnValue(array($this->subscriber_1->id)));
|
||||||
|
|
||||||
remove_all_filters('mailpoet_get_subscribers_in_segment_finders');
|
remove_all_filters('mailpoet_get_subscribers_in_segment_finders');
|
||||||
Hooks::addFilter('mailpoet_get_subscribers_in_segment_finders', function () use ($mock) {
|
Hooks::addFilter('mailpoet_get_subscribers_in_segment_finders', function () use ($mock) {
|
||||||
@ -146,4 +152,51 @@ class SubscribersFinderTest extends \MailPoetTest {
|
|||||||
expect($subscribers)->count(1);
|
expect($subscribers)->count(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testItAddsSubscribersToTaskFromStaticSegments() {
|
||||||
|
$finder = new SubscribersFinder();
|
||||||
|
$subscribers_count = $finder->addSubscribersToTaskFromSegments(
|
||||||
|
$this->sending->task(),
|
||||||
|
array(
|
||||||
|
array('id' => $this->segment_1->id, 'type' => Segment::TYPE_DEFAULT),
|
||||||
|
array('id' => $this->segment_2->id, 'type' => Segment::TYPE_DEFAULT),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect($subscribers_count)->equals(1);
|
||||||
|
expect($this->sending->getSubscribers())->equals(array($this->subscriber_2->id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function testItDoesNotAddSubscribersToTaskFromNoSegment() {
|
||||||
|
$finder = new SubscribersFinder();
|
||||||
|
$subscribers_count = $finder->addSubscribersToTaskFromSegments(
|
||||||
|
$this->sending->task(),
|
||||||
|
array(
|
||||||
|
array('id' => $this->segment_1->id, 'type' => 'UNKNOWN SEGMENT'),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect($subscribers_count)->equals(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testItAddsSubscribersToTaskFromDynamicSegments() {
|
||||||
|
$mock = Stub::makeEmpty('MailPoet\Segments\FinderMock', array('getSubscriberIdsInSegment'));
|
||||||
|
$mock
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getSubscriberIdsInSegment')
|
||||||
|
->will($this->returnValue(array($this->subscriber_1->id)));
|
||||||
|
|
||||||
|
remove_all_filters('mailpoet_get_subscribers_in_segment_finders');
|
||||||
|
Hooks::addFilter('mailpoet_get_subscribers_in_segment_finders', function () use ($mock) {
|
||||||
|
return array($mock);
|
||||||
|
});
|
||||||
|
|
||||||
|
$finder = new SubscribersFinder();
|
||||||
|
$subscribers_count = $finder->addSubscribersToTaskFromSegments(
|
||||||
|
$this->sending->task(),
|
||||||
|
array(
|
||||||
|
array('id' => $this->segment_2->id, 'type' => ''),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect($subscribers_count)->equals(1);
|
||||||
|
expect($this->sending->getSubscribers())->equals(array($this->subscriber_1->id));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user