-
-
Notifications
You must be signed in to change notification settings - Fork 223
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
!!! FEATURE: Publishing Version 3 #5301
!!! FEATURE: Publishing Version 3 #5301
Conversation
…also being temporary
Neos.ContentRepository.Core/Classes/CommandHandlingDependencies.php
Outdated
Show resolved
Hide resolved
…se-and-partial-publish-in-memory-event-stream
…ace-command-handler' into task/schnappsidee-rebase-and-partial-publish-in-memory-event-stream
…ebase to use yield Also the object wiring was cleaned up and simplified. A full recursion is now no longer but was never needed: Eg rebase a CreateWorkspace command or the likes.
...alTests/Tests/Behavior/Features/W6-WorkspaceRebasing/02-RebasingWithAutoCreatedNodes.feature
Outdated
Show resolved
Hide resolved
...ioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/03-MoreBasicFeatures.feature
Outdated
Show resolved
Hide resolved
Neos.ContentRepository.Core/Classes/CommandHandler/CommandSimulator.php
Outdated
Show resolved
Hide resolved
Neos.ContentRepository.Core/Classes/CommandHandler/ControlFlowAwareCommandHandlerInterface.php
Outdated
Show resolved
Hide resolved
Neos.ContentGraph.DoctrineDbalAdapter/src/DoctrineDbalContentGraphProjection.php
Show resolved
Hide resolved
A partial publish results in events annotated for "live" workspace yet are not in the "live" content stream. This is due to a fork of the live content stream being created with the partially published events in, which is then published to the actual live content stream. This leaves behind duplicate events both containing "workspaceName: live" yet only one of them is in the live content stream. A catchup or replay will fail however due to duplicate database entries for the reflections relying on anything with "workspaceName: live" being actually in the live content stream. The provided test fails showing the behavior.
by creating live changes we ensure that the rebase is never a noop
…kspace doesnt need to publish
... and that the user content stream did not change and is still open!
…e the content stream is closed
…atchingPart ... as we dont do two forks anymore but try to handle the commands in simulation
Do we see any risks in that the one connection used here is shared between ORM and eventstore and the CR? Given we must ensure nothing commits the transaction we started for the simulation. So no hooks, signals or anything that might lead to userland code being executed that could in some way start/commit a transaction. |
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 commented the bits and pieces where you guys might want to add your last bit of mustard to:) (the changes of the last two days without christians help xd)
/** | ||
* @internal | ||
*/ | ||
final readonly class InitiatingEventMetadata |
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.
new utility to encapsulate access instead of passing magic strings around :)
see 4ee595e
Neos.ContentRepository.Core/Classes/Feature/WorkspaceRebase/CommandThatFailedDuringRebase.php
Outdated
Show resolved
Hide resolved
Neos.ContentRepository.Core/Classes/CommandHandler/CommandHandlingDependencies.php
Show resolved
Hide resolved
...ioralTests/Tests/Behavior/Features/W8-IndividualNodePublication/03-MoreBasicFeatures.feature
Show resolved
Hide resolved
@@ -41,6 +41,7 @@ Feature: If content streams are not in use anymore by the workspace, they can be | |||
When the command RebaseWorkspace is executed with payload: | |||
| Key | Value | | |||
| workspaceName | "user-test" | | |||
| rebaseErrorHandlingStrategy | "force" | |
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.
for these tests ... as we dont make changes live and rebase is now a noop if the workspace is not outdated, we made "force" reflect the old behaviour to always fork even if there are no changes in the base.
try { | ||
$eventsToPublish = $this->commandBus->handle($commandInWorkspace); | ||
} catch (\Exception $exception) { | ||
$this->commandsThatFailedDuringRebase = $this->commandsThatFailedDuringRebase->withAppended( | ||
new CommandThatFailedDuringRebase( | ||
$rebaseableCommand->originalSequenceNumber, | ||
$rebaseableCommand->originalCommand, | ||
$exception | ||
) | ||
); | ||
|
||
return; | ||
} |
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.
as this got really repetitive over the 5 usages i added the logic to collect the failures to the command simulator.
$commandSimulator = $this->commandSimulatorFactory->createSimulator($baseWorkspace->workspaceName);
$commandSimulator->run(
static function ($handle) use ($rebaseableCommands): void {
foreach ($rebaseableCommands as $rebaseableCommand) {
$handle($rebaseableCommand);
}
}
);
if ($commandSimulator->hasCommandsThatFailed()) {
throw WorkspaceRebaseFailed::duringPublish($commandSimulator->getCommandsThatFailed());
}
i thought of returning it first but that would mean that the return value of $handle
has to be manually collected or we create a lot of logic in the run
closure ... as we do the same with $commandSimulator->eventStream()
i think its fine to do:) The object has state already ^^
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.
Does it have state? I thought by putting handle as closure we got rid of all the state? But anyways looks good.
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.
not itself ... it was readonly
but as the class contains and exposes the in-memory event store this is really stateful already ;)
/** | ||
* @throws WorkspaceRebaseFailed is thrown if the workspace was outdated and an automatic rebase failed due to conflicts. | ||
* No changes would be published for this case. | ||
*/ | ||
private function publishNodes( | ||
ContentRepository $contentRepository, | ||
WorkspaceName $workspaceName, | ||
NodeIdsToPublishOrDiscard $nodeIdsToPublish | ||
): void { | ||
/** | ||
* TODO: only rebase if necessary! | ||
* Also, isn't this already included in @see WorkspaceCommandHandler::handlePublishIndividualNodesFromWorkspace ? | ||
*/ | ||
$contentRepository->handle( | ||
RebaseWorkspace::create($workspaceName) | ||
); | ||
|
||
$contentRepository->handle( | ||
PublishIndividualNodesFromWorkspace::create( |
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 throw now the WorkspaceRebaseFailed
in case one of the commands could not be handled, previously we would just throw the raw exception in PublishIndividualNodesFromWorkspace
instead.
The WorkspaceRebaseFailed
was only due to the now obsolete RebaseWorkspace
here which is required for the ui to react accordingly: neos/neos-ui#3769
see 8ad0a3e
I am still happy with this, thanks for doing the extra mile. |
see discussion https://neos-project.slack.com/archives/C04PYL8H3/p1730111291595599 otherwise the logic got way to complex :D previously a publishing without changes is a no-op if up to date and rebase if outdated
c8d81e4
to
38835ca
Compare
…at failed instead we can just generate a uuid as the `sequenceNumber` was also unique for each command that failed see neos/neos-development-collection#5301 (comment)
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 admire the amount of work you have put into this, thank you!
I don't feel qualified to do a proper review of the inner workings. But the parts that I understood make a lot of sense to me and the complexity is not that much higher – and you added quite a lot of tests, too.
So I'd say, let's get this merged asap so that we can test it further.
I just added some rather nitpicky comments, feel free to ignore those
Neos.ContentRepository.Core/Classes/CommandHandler/CommandBus.php
Outdated
Show resolved
Hide resolved
Neos.ContentRepository.Core/Classes/CommandHandler/CommandHandlerInterface.php
Outdated
Show resolved
Hide resolved
Neos.ContentRepository.Core/Classes/CommandHandler/CommandHandlingDependencies.php
Show resolved
Hide resolved
Neos.ContentRepository.Core/Classes/CommandHandler/CommandSimulator.php
Outdated
Show resolved
Hide resolved
Co-authored-by: Bastian Waidelich <[email protected]>
neos#5301 (comment) > Alright, at least according to the tests this works now. I also went through all Rebasable commands to check if the events get enriched. the Dimension ones were the only missing. IMHO we should centralize this behavior, I opted against doing it here though as I think we need to consider if there would have to be any more logic involved to decide what gets enriched with commands, in what way, when we override the command if there is already metadata and finally what the causation ids are. I guess we could ignore all these questions and centralize it, but it warrants a closer look and is therefore out of scope of this change.
Resolves: #5327
Resolves: #5303
Replaces: #5300
Replaces: #5302
Introduces simulated publishing for rebasing commands and their constraint checks
Bugfixes
Publish workspace with automatic rebase for changes
We discussed whether a publish should only be allowed if the workspace is up to date, as technically a non conflicting change could sneak in like: The pages title was changed in live and the to be published text references the old title.
But this is only true in theory. It's more annoying having to rebase whenever multiple people work on potentially completely different parts of the site (or even a different site altogether).
To avoid the mentioned issue we could introduce at some point an indicator if someone else is working on the same document (or if that document has new changes in the target workspace).
Allowing publish and publish individual nodes both even if the workspace was outdated optimises performance as both will now do a rebase as part of the publish strategy making an explicit rebase from the outside obsolete and thus speeding up the process a lot.
Publish/Discard individual vs all #5303
Publishing all changes but using the individual publish (like the neos ui does) will now lead to the event
WorkspaceWasPublished
being emitted as this more performant to handle in the projections.Similarly we do the same with discard individual and discard all
Publish/Discard/Rebase as no-op if there are no changes
Performance old vs new
We also discussed if the performance of leveraging the db's rollback is acceptable:
Tested with 1G memory limit but behat seems to have only used around 150Mb each run no matter how many nodes.
It seems that the publish is even slightly faster than the creation of the nodes until we exceed 5.000 newly created nodes. With 10.000 nodes it takes twice as much time.
The previous publish implementation wich can only work without changes in the target only copied the events which was quite performant, but as this is the edge-case as one needs to be up to date AND publish all changes (across sites) this optimisation was removed in favour of a more consistent behaviour that also is able to rebase if required.
The Neos Ui doesnt use the publish all but rather an explicit rebase coupled with a publish individual nodes. This is super slow and we are able to optimise this a lot by doing the rebase and publish in one step.
For the main use case the new implementation is not worse but even better than the old one as the explict rebase is extremely slow in combination with the the publish afterwards.
Disclaimer: The measurements are to be taken with a grain of salt. Christian also mentioned that the performance possibly gets worse at one point due to all the events being stored in memory (his 100k publication crashed due to memory limitation)
Implementation of the
CommandSimulator
The CommandSimulator is used during the publishing process, for partial publishing and workspace rebasing.
For this case, we want to apply commands including their constraint checks step by step, to see whether this
set of commands applies cleanly without errors, and which events would be created by them, but we do NOT
want to commit the updated projections or events.
Internally, we do the following:
projection updates) (via
CommandSimulator::run()
).CommandSimulator::handle()
previously modified projection state which is not committed)
-> note to avoid full recursion the workspace command handler is not included in the bus
ContentGraphProjectionInterface::inSimulation()
This is quite performant because we do not need to fork a new content stream
Implementation of graph projection
inSimulation($fn)
We introduce a new dedicated method
inSimulation
for simulated rebasing on theContentGraphProjectionInterface
(the only projection needed for soft constraint checks)The implementation must ensure that the function passed is invoked and that any changes via
ContentGraphProjectionInterface::apply()
are executed "in simulation" e.g. NOT persisted after returning.The projection state
ContentGraphReadModelInterface
must reflect the current changes of the simulation as well during the execution of the function.For the doctrine content graph projection this is done by leveraging a transaction and rollback.
We discussed if relying on the global
Connection
(wired via flows and the entity manger) is safe as it must be ensured that NO one can hook into the process during rebase and commit the simulated state, especially not our own code in the projection or sub classes. By leveraging$connection->setRollbackOnly()
(which will be unset after the rollback) we can exactly do that which is of course only a slim layer but gives enough insurance and doesn't come with the costs of having too many dedicated connections.Upgrade instructions
Migration will be possible via #5297
Review instructions
Relation to other changes:
1.) Requires #5272 as the content graph projection is required as special projection for the simulation with a
inSimulation
method.2.) Based on #5315 to introduce
yield
for command handlers3.) As part of the migration we require the content streams to be pruned. This is currently unstable and will be fixed with the content stream pruner overhaul followup: #5297. The change will also remove the left-over content stream commands that are not required by the workspace command handler anymore.
Checklist
FEATURE|TASK|BUGFIX
!!!
and have upgrade-instructions