-
Notifications
You must be signed in to change notification settings - Fork 34
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
publishRecursive() performance #450
Labels
Comments
Added a more refined implementation - we're currently using it for our fixtures generation and it yields about 30% speed increase which also includes extension points logic. This implementation is Fluent friendly. class PerformantPublishService
{
use Injectable;
/**
* @param DataObject|Versioned $model
* @return void
* @throws Exception
*/
public function publishRecursiveWithoutChangeSet(DataObject $model): void
{
if (!$model->hasExtension(Versioned::class)) {
// Model can't be published
return;
}
if (!$model->isInDB()) {
// Model isn't saved in the DB so we can't publish it
return;
}
$modelsToPublish = $this->findModelsToPublish($model);
$now = DBDatetime::now()->Rfc2822();
DBDatetime::withFixedNow($now, static function () use ($model, $modelsToPublish): void {
// get the last published version
$original = $model->isPublished()
? Versioned::get_by_stage($model->baseClass(), Versioned::LIVE)->byID($model->ID)
: null;
$model->invokeWithExtensions('onBeforePublishRecursive', $original);
/** @var DataObject|Versioned $modelToPublish */
foreach ($modelsToPublish as $modelToPublish) {
$modelToPublish->publishSingle();
}
$model->invokeWithExtensions('onAfterPublishRecursive', $original);
});
}
private function findModelsToPublish(DataObject $model): array
{
$models = [
$model,
];
$modelsToPublish = [];
/** @var DataObject|Versioned $model */
while ($model = array_shift($models)) {
// We won't be inspecting any models that aren't saved in the DB
if (!$model->isInDB()) {
continue;
}
// Mark model as needing publishing in case it needs to be published
if ($model->hasExtension(Versioned::class) && (!$model->isPublished() || $model->stagesDiffer())) {
$modelsToPublish[]= $model;
}
// Discover and add owned models for inspection
$relatedModels = $model
// We intentionally avoid "true" recursive traversal here as it's not performant
// (often the cause of memory usage spikes and longer execution time due to deeper stack depth)
// Instead we use "stack based" recursive traversal approach for better performance
// which avoids the nested method calls
->findOwned(false)
->toArray();
$models = array_merge($models, $relatedModels);
}
// Return the list in the reverse order, bottom first so we get to publish the root model as the last step
return array_reverse($modelsToPublish);
}
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Module version(s) affected
1.13.7
Description
Publish recursive performance seems to have some gaps. We started replacing this method with something more performant and also by using asynchronous processing such as queued jobs. Most notable issues we found are:
I've performed a benchmark on a content page that contains 21 content blocks, some with nested models and some with assets.
Benchmark for standard publishRecursive()
5.33
seconds - First time publish (no models have live version)4.02
seconds - Single model modified on draft & published via pageBenchmark for customised publish action
3.03
seconds - First time publish (no models have live version)1.10
seconds - Single model modified on draft & published via pageWe found that the customised approach is much more resilient to DB deadlocks too. Processing thousands of queued jobs that execute this type of publish is fast and has almost no issues.
The performance gap is even wider if you go to pages with 100+ content blocks and if you introduce parallel processing via queued jobs.
How to reproduce
Possible Solution
The provided code snippet is quite crude but it shows the opportunity for some options that might help with the performance of
publishRecursive()
. Here is a list of recommendations:with translation
wrapper, this might not be needed in some cases, if publishing of assets is involved the operation might be really slow due to slow shared drive on some infrastructure setups, translation wrap might cause DB deadlock and timeouts in such caseswith fixed now
Validations
silverstripe/installer
(with any code examples you've provided)The text was updated successfully, but these errors were encountered: