-
-
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: Subscription Engine #5321
base: 9.0
Are you sure you want to change the base?
Conversation
# Conflicts: # Neos.ContentRepository.Core/Classes/ContentRepository.php
Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php
Outdated
Show resolved
Hide resolved
Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php
Outdated
Show resolved
Hide resolved
I had a quick look and id love to help with the wiring and sticking it all together :) We should probably really get a simple draft running of the catchups just getting their projection state first :) As that will require some work already |
…n they are registered for A catchup doesn't have access to the full content repository, as it would allow full recursion via handle and accessing other projections state is not safe as the other projection might not be behind - the order is undefined. This will make it possible to catchup projections from outside of the cr instance as proposed here: neos#5321
…n they are registered for A catchup doesn't have access to the full content repository, as it would allow full recursion via handle and accessing other projections state is not safe as the other projection might not be behind - the order is undefined. This will make it possible to catchup projections from outside of the cr instance as proposed here: neos#5321
Okay i thought further about our little content graph projection vs projection states vs event handlers dilemma and i think the solution is not necessary exposing just the in my eyes this object, being build by the content repository registry in some way, must reflect
that could look like: final readonly class ContentRepositoryGraphProjectionAndSubscribers
{
public function __construct(
public ContentGraphProjectionInterface $contentGraphProjection,
public Subscribers $subscribers, // must contain a subscriber for the $contentGraphProjection
public ProjectionStates $additionalProjectionStates, // must not contain the $contentGraphProjection state
) {
}
} or maybe a little more explicit so the factories dont have to deal with all the logic and we have control over the subscription ids: final readonly class ProjectionsAndCatchupHooksBetterVersionZwo
{
public function __construct(
public ContentGraphProjectionInterface $contentGraphProjection,
private Projections $additionalProjections,
private Subscribers $additionalSubscriber,
private array $catchUpHooksByProjectionClass
) {
}
public function getSubscribers(): Subscribers
{
$subscribers = iterator_to_array($this->additionalSubscriber);
$subscribers[] = new Subscriber(
SubscriptionId::fromString('contentGraphProjection'),
SubscriptionGroup::fromString('default'),
RunMode::FROM_BEGINNING,
new ProjectionEventHandler(
$this->contentGraphProjection,
$this->getCatchUpHooksForProjectionClass(ContentGraphProjectionInterface::class),
),
);
foreach ($this->additionalProjections as $projection) {
$subscribers[] = new Subscriber(
SubscriptionId::fromString(substr(strrchr($projection::class, '\\'), 1)),
SubscriptionGroup::fromString('default'),
RunMode::FROM_BEGINNING,
new ProjectionEventHandler(
$projection,
$this->getCatchUpHooksForProjectionClass($projection::class),
),
);
}
return Subscribers::fromArray($subscribers);
}
public function getAdditionalProjectionStates(): ProjectionStates
{
return ProjectionStates::fromArray(array_map(
fn ($projection) => $projection->getState(),
iterator_to_array($this->additionalProjections)
));
}
private function getCatchUpHooksForProjectionClass(string $projectionClass): ?CatchUpHookInterface
{
return $this->catchUpHooksByProjectionClass[$projectionClass] ?? null;
}
}
|
@mhsdesign thanks for your input! That's my current draft of the public function __construct(
private readonly ContentRepositoryId $contentRepositoryId,
EventStoreInterface $eventStore,
NodeTypeManager $nodeTypeManager,
ContentDimensionSourceInterface $contentDimensionSource,
Serializer $propertySerializer,
private readonly UserIdProviderInterface $userIdProvider,
private readonly ClockInterface $clock,
SubscriptionStoreInterface $subscriptionStore,
ContentGraphProjectionFactoryInterface $contentGraphProjectionFactory,
CatchUpHookFactoryInterface $contentGraphCatchUpHookFactory,
private readonly ContentRepositorySubscribersFactoryInterface $additionalSubscribersFactory,
) {
// ...
} |
As discussed that looks good ❤️ my idea had a flaw because i assumed the projection instance could be build by the registry which it CANNOT because we need factory dependencies.... and the thing with iterating over the event handlers to fetch their state via |
…n they are registered for A catchup doesn't have access to the full content repository, as it would allow full recursion via handle and accessing other projections state is not safe as the other projection might not be behind - the order is undefined. This will make it possible to catchup projections from outside of the cr instance as proposed here: neos/neos-development-collection#5321
…n they are registered for A catchup doesn't have access to the full content repository, as it would allow full recursion via handle and accessing other projections state is not safe as the other projection might not be behind - the order is undefined. This will make it possible to catchup projections from outside of the cr instance as proposed here: neos/neos-development-collection#5321
…n they are registered for A catchup doesn't have access to the full content repository, as it would allow full recursion via handle and accessing other projections state is not safe as the other projection might not be behind - the order is undefined. This will make it possible to catchup projections from outside of the cr instance as proposed here: neos/neos-development-collection#5321
Neos.ContentRepository.Core/Classes/Projection/CatchUpHook/CatchUpHookFactoryDependencies.php
Outdated
Show resolved
Hide resolved
# Conflicts: # Neos.ContentRepository.Core/Classes/ContentRepository.php # Neos.ContentRepository.Core/Classes/Factory/ContentRepositoryFactory.php # Neos.ContentRepositoryRegistry/Classes/ContentRepositoryRegistry.php # Neos.ContentRepositoryRegistry/Classes/Service/ProjectionService.php # Neos.ContentRepositoryRegistry/Classes/Service/ProjectionServiceFactory.php # phpstan-baseline.neon
Previously the `CatchUpHookInterface` contained a `onBeforeBatchCompleted` which was explained as follows: This hook is called directly before the database lock is RELEASED It can happen that this method is called multiple times, even without having seen Events in the meantime. If there exist more events which need to be processed, the database lock is directly acquired again after it is released. ---------- `onAfterBatchCompleted` behaves similar just that it runs when the lock was released. This allows us to run all the tasks that `onAfterCatchUp` could also do (using possibly transactions and commiting work) We dont ensure that `onAfterBatchCompleted` is only called if there are more events to handle, as this would complicate the code and technically without acquiring a lock there is never a guarantee for that, so hooks might as well encounter the case more often and have to deal with that.
okay so the long missed update. On the 3rd of December Basti, Denny and me discussed if going down road #5383 would be the right choice or what Basti said:
in short the result:
EDIT: Basti, Sebastian and me discussed this matter further on the 4th and we agreed that this is the solution. -> implemented via #5392 |
The return of the at least once delivery (for errors)Today Christian, Denny, Bastian and me rediscussed the use of save-points (as described here). As written above the save-point will only be used for REAL projection errors now and never rolled back if catchup errors occur. Then i thought that we could use transactions and save-points to wrap the setup call to ensure any migration - if it fails - can be rolled back, but i oversaw that So with all these conditions changed - and with the fact that there is no central dbal connection concept in the core and thus any calls to a global Of course would it be more "pretty" if the apply is rolled back from the projection as some events will definitely crash the projection if reapplied via |
Similarly to that doctrine migrations (`flow doctrine:migrate`) are also cannot be rollback in case of an error So we cannot wrap projection->setUp in transaction and errors (like in a migration) will be directly commited. Thus, it must be acted with care.
see #5321 (comment) the save-point will only be used for REAL projection errors now and never rolled back if catchup errors occur. With that change in code the save-points are less important because a real projection error should better be thrown at the start before any statements, and even if some statements were issued and a full rollback is done its unlikely that a reactivateSubscription helps that case. Instead, to repair projections you should replay
The decision to use batching for a replay but not for a full catchup (see #5321 (comment)) was rediscussed. The thesis was that we should not differentiate between the two - to avoid having problems like why does a work and not b. But we agreed on keeping that decision for the following reasons:
But we also should keep in mind:
|
…point-simplication TASK: Overhaul CatchUpHook error behaviour; At least once delivery for ERROR projections
…tchup-mechanism-3
…ption Adds test for these cases Additionally, we have to introduce a `FakeCatchUpHookFactory2` to have two hooks on one projection and prevent: > a CatchUpHookFactory of type "Neos\ContentRepository\TestSuite\Fakes\FakeCatchUpHookFactory" already exists in this set
This way we reduce complexity as we dont have to pass too many parameters by reference. Also it makes the case for `onBeforeCatchUp` (the first iteration) more explicit We dont need to worry about rollbacks as no errors should ever be thrown uncatched during catchup.
…" was not invoked this does not happen under normal circumstances but is technically possible as we release and reaquire a lock during batching.
…hCompleted and onAfterCatchUp
As the exception log and `CatchUpHadErrors` only accept one previous exception and to reduce possible bloat if to many exceptions are thrown. Instead, the exceptions should additionally be logged (next commit).
…rs (and clamping).
…tchup-mechanism-3
without batching we had invalid state because the position is none (0) while the event is applied and persisted an the projection.
as we use SubscriptionStatus::from
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.
LGTM
Related: #4746
Resolves: #4152
Resolves: #4908
migration to migrate the checkpoints to subscribers
TODO
SubscriptionEngine::reset()
(i.e. projection replay)Neos\ContentRepository\Core\Subscription
enough?)Optional: Allow to target specific groups/subscriptions via CLI commands(the SubscriptionEngine already supports this)use-> not possible asDateTimeImmutable::ATOM
for date formatting instead? But it clashes withdatetime_immutable
(useTypes::DATETIMETZ_IMMUTABLE
in schema)DATETIMETZ
is not supported by mysqlStatus->value
instead of name field!!!discoverNewSubscriptions
doesnt have a transaction?CatchupHadErrors
exception disturb the codeflow from dbal repos and doctrine orm things that are rolled back?Development
...getIteratorAggregate
for errors collection? Ensure errors are logged correctly by the throwable storage!--until
for debugging was removedProcessResult
as error? Should the cr throw?TODO pass the error subscription status to onAfterCatchUp, so that in case of an error it can be prevented that mails f.x. will be sent?CatchUpFailed
... but rather rename that and alsoSubscriptionEngineAlreadyProcessingException
toCatchUpError
and then have aErrorDuringCatchUp
?rename booting to booted?-> we agree that theING
is not a state, as it needs to be rather was.. but we dont have a better name