-
Notifications
You must be signed in to change notification settings - Fork 115
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
Top page feature (performance enhancement) #774
Top page feature (performance enhancement) #774
Conversation
768daa0
to
d43a9f4
Compare
d43a9f4
to
a8bb500
Compare
Hey @mfendeksilverstripe, thanks so much for raising this PR 😄 I'll try to get it reviewed soon, but in the meantime: I've marked this as |
a8bb500
to
f873bc8
Compare
@Cheddam Thanks for the feedback, rebased on |
docs/en/advanced_setup.md
Outdated
``` | ||
DNADesign\Elemental\Models\BaseElement: | ||
extensions: | ||
topPage: DNADesign\Elemental\TopPage\FluentExtension |
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.
Suggestion: Do we need to use the associative array syntax? This will mess with almost every extension YML config I've seen which use indexed arrays for their extension syntax. If developers try to mix the two, the Symfony YML parser will barf. Also note Silverstripe's order of precedence when it comes to applying extensions. AFAIK, if developers attempt to declare >1 extension on a given class in a separate YML block, then only the latter will take and the former are clobbered.
docs/en/advanced_setup.md
Outdated
Instead, the newly created data objects will have a new top page data assigned based on the context. | ||
|
||
For example, duplicating a page with all of its blocks will create a new page and new blocks. | ||
All the new blocks will have the new page stored as their top page reference. This works even if the deplication tree contains other pages as tree nodes. |
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.
Typo: "duplication tree" or possibly what was meant was "duplicated tree"?
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.
Hey Mojmir, I've taken a look over the code and it looks good - the only suggestions I've made are some tidying of the language used in the docs and docblocks.
Regarding the block sharing limitation - have you tested this solution with Virtual Blocks? If that worked with this code, it'd provide an alternative to directly sharing blocks between pages for projects that want to adopt this functionality.
My biggest question is: Does this make more sense as a separate module? It's a lot of additional code to add a fairly specific feature, and it can't add this functionality by default, because it might impact existing usages of the module.
We could look at deprecating the paradigm of directly sharing blocks, and shift this PR back to targeting the next major and enabled by default, but I'm not sure what portion of real projects use block sharing and would be impacted. For projects that do use that paradigm, upgrading could require a lot of refactoring and migration work for them to shift to Virtual Blocks, and at worst it may simply be impossible to upgrade, leaving those sites at a dead-end. There are no immediate plans to release a new major, either, so this functionality would be sitting in master without a tag for a while.
If this was a separate module, we could add it to the suggested package config for Elemental to increase its visibility as a drop-in performance enhancement, and developers could evaluate and adopt it immediately.
These are just my thoughts on the matter, and I'm keen to get some input from other maintainers - cc/ @ScopeyNZ @kinglozzer @maxime-rainville.
I really appreciate the work you've put into this, and I'm excited for it see the light of day in whatever form it takes!
src/TopPage/DataExtension.php
Outdated
* Top page data cache for improved performance | ||
* intended owners of this extension are @see BaseElement and @see ElementalArea | ||
* applying this extension to just one of these owners will not hinder top page functionality | ||
* but the performance gain will be smaller | ||
* it is recommended to apply this extension to BaseElement and for setups with deeper block nesting | ||
* it is recommended to cover ElementalArea as well |
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 be simplified to:
* Provides a db-cached reference to the top-level page for improved read performance on projects
* with deeply nested block structures. Apply to @see BaseElement and @see ElementalArea.
(Not sure if there's a reason to explain why only applying it to one or the other impacts its usefulness - are there any downsides in applying it to both?)
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 downside is that you have to store more data in the DB and it may not be necessary if you have a flat block page (i.e. only one elemental area). Maybe the downside is acceptable in all cases and I should simplify the explanation.
src/TopPage/DataExtension.php
Outdated
* Find top level page of a block or elemental area | ||
* this is very useful in case blocks are deeply nested | ||
* | ||
* for example: | ||
* page -> elemental area -> block -> elemental area -> block | ||
* | ||
* this lookup is very performant as is safe to use in a template as well |
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 simplify to:
* Finds the top-level Page object for a Block / ElementalArea, using the cached TopPageID
* reference when possible.
src/TopPage/FluentExtension.php
Outdated
* Use this extension in case you use the Fluent module (https://github.com/tractorcow-farm/silverstripe-fluent) | ||
* for page localisation | ||
* this will keep track of the locale the nested data object is stored in |
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 be simplified to:
* Use in place of @see DataExtension if you use the Fluent module for page localisation.
*
* @link https://github.com/tractorcow-farm/silverstripe-fluent
src/TopPage/SiteTreeExtension.php
Outdated
* This extension is mandatory for any pages that need to support top page functionality | ||
* it is safe to apply this extension directly to Page as the functionality detects presence of elemental area | ||
* alternatively, this extension can be applied to top level block page or all block pages individually | ||
* in case no common parent exists |
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 be simplified to:
* This extension must be present on pagetypes that need to support Elemental TopPage functionality.
* It can be applied directly to Page, as it only takes effect in the presence of a ElementalArea.
src/TopPage/SiteTreeExtension.php
Outdated
* This is a very roundabout way on how to update top page reference for data objects | ||
* the main reason why we're doing it like this is the fact that duplication process goes top-down | ||
* when it comes to model creation, but it goes bottom-up when it comes to writes | ||
* as a consequence the relations are not available when model is written | ||
* | ||
* Instead of updating the page reference during model write, we will simply push the object into a list | ||
* and update the reference later when page is available | ||
* | ||
* note that when going top-down on the relationship tree the models are not written yet so we can't use | ||
* page IDs as identifiers | ||
* instead we use IDs of the original objects that we are cloning from | ||
* | ||
* Additionally, the duplication process has to support nested pages which makes things more complicated | ||
* this is handled by using a stack like data structure to keep track on multiple pages | ||
* each with it's own duplication list |
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 be simplified to something along these lines:
* Registers the given object to receive an updated TopPage reference after the duplication
* operation completes, ensuring the new Page is written to the database beforehand.
*
* The registry uses a stack-like structure to allow accurate tracking of objects during
* duplication operations that include nested pages.
(Feel free to adjust if I've misinterpreted anything, or missed crucial information.)
docs/en/advanced_setup.md
Outdated
|
||
The page reference data on the blocks can also be used for maintenance dev tasks as it's easy to identify which blocks belong to which pages in which locale. | ||
|
||
### Top page reference data during object duplication |
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 section might not be necessary - I'd see it being useful if it was describing a limitation, but instead it's just reassuring the developer that the code will work as expected.
$extension->addDuplicatedObject($this->owner); | ||
} | ||
|
||
protected function assignTopPage(Page $page): 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.
Needs a docblock
'TopPageLocale' => 'Varchar', | ||
]; | ||
|
||
protected function assignTopPage(Page $page): 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.
Needs a docblock
$this->processDuplication($original, (bool) $doWrite); | ||
} | ||
|
||
public function getDuplicationKey(): ?string |
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.
Needs a docblock
array_unshift($this->duplicatedPages, $key); | ||
} | ||
|
||
protected function processDuplication(Page $original, bool $written): 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.
Needs a docblock
@Cheddam Thank you for your feedback, I pushed up some documentation changes. I had a look at the I do recommend to look into deprecating the shared blocks approach and recommend the use of I'm not very keen on moving this to a separate module as this is just a performance fix. The performance is painfully slow in a deeply nested blocks setup and I see this as a severe issue of this module which should be fixed in this module. I will look into updating the feature to make it enabled by default and not break when a project is using shared blocks. |
962faf7
to
5efdb6a
Compare
@Cheddam As far as I can see the shared blocks feature is no longer there as Elemental area has many blocks, but block only has one parent. I enabled this performance improvement by default and rebased on |
c8ce31b
to
e87728d
Compare
@Cheddam All tests are now green :) |
@mfendeksilverstripe After discussion with community members on Slack, I've made an adjustment to disable the extensions by default, and reintroduced the documentation describing how to enable them. If you're happy with the change, we can merge and then raise a follow-up PR to |
@Cheddam Thanks I'll have a look at it today and I'll try to fix the unit tests ;-) |
8adec59
to
20e1c3e
Compare
@Cheddam Your changes look fine but I'm not sure why the Behat tests are now failing. Note that the tests passed before your change. Please look into it. |
The behat failure was caused by an upstream issue, which has since been resolved, so CI is now green 👍 |
Top page feature (performance enhancement)
Top page feature is a performance enhancement designed especially for projects with deeper nesting of objects. The deeper the nesting the higher performance gain.
This feature should be
Virtual Blocks
compatible.Performance issues
Currently, the bottom up data lookups (from block to page) are slow or almost impossible in some cases (when using Fluent and locale us unknown). These are the most common cases of such lookups:
CMS fields
- block level CMS fields depend on page datatemplates
- block level templates logic depends on page datadev tasks
- dev tasks needs to match block to pages (starting from block context)Deeply nested data structures may not be that uncommon either. Consider this example:
This module supports following bottom up data lookups:
ElementalArea::getOwnerPage()
- this method returns most immediate owner object not necessarily the top page, the method is cached in-memory but needs to be called several times on different objects when traversing bottom up. In our example above this method would returnAccordionBlock
when calling from deepest located elemental areaBaseElement::getPage()
- this method is just a cached (in-memory) proxy to theElementalArea::getOwnerPage()
so it doesn't necessarily return the top pageSolution
This feature stores the page reference on every block and every elemental area and it provides a simple API to lookup the top page. This feature is duplication safe.
Fluent module
integration support is available as well.Other changes
7.1
Related issues
#775