I have observed this issue over some time, but had a hard time reproducing it. In rare instances during deployments, projections could loose their stream positions and start from the beginning.
This can happen in instances where a pcntl stop handler invokes a projection's stop() method before the projection has loaded the stream positions.
The problematic section is in the run() method, before the actual main loop:
// Initial State is ProjectionStatus::IDLE()
// $this->streamPositions is an empty array
if (! $this->projectionExists()) {
$this->createProjection();
}
$this->acquireLock();
if (! $this->readModel->isInitialized()) {
$this->readModel->init();
}
$this->prepareStreamPositions();
$this->load(); // << Only here $this->streamPositions will be initialized
Calling stop() from the signal handler before load() loaded the stream positions, causes a call to persist((). The persist call saves the uninitialized stream positions (an empty array).
This issue becomes more frequent / likely the longer it takes the projection to load the stream positions. Putting a sleep() call after the acquireLock() call in the example above makes it rather easy to reproduce (we have replaced the locking mechanism with metadata locks on a 60 second timeout, hence more likely for us to run into this issue).
The current workaround for us seems to be to introduce a new flag $this->streamPositionsLoaded and have an early return in persist() if positions have not been loaded yet.
private function persist(): void
{
if (!$this->streamPositionsLoaded) {
return;
}
$this->readModel->persist();
// ...
We are running this in testing at the moment to see if this actually fixes the issue. Any other input / feedback / solutions are welcome though.
I have observed this issue over some time, but had a hard time reproducing it. In rare instances during deployments, projections could loose their stream positions and start from the beginning.
This can happen in instances where a pcntl stop handler invokes a projection's
stop()method before the projection has loaded the stream positions.The problematic section is in the
run()method, before the actual main loop:Calling
stop()from the signal handler beforeload()loaded the stream positions, causes a call topersist((). The persist call saves the uninitialized stream positions (an empty array).This issue becomes more frequent / likely the longer it takes the projection to load the stream positions. Putting a
sleep()call after theacquireLock()call in the example above makes it rather easy to reproduce (we have replaced the locking mechanism with metadata locks on a 60 second timeout, hence more likely for us to run into this issue).The current workaround for us seems to be to introduce a new flag
$this->streamPositionsLoadedand have an early return inpersist()if positions have not been loaded yet.We are running this in testing at the moment to see if this actually fixes the issue. Any other input / feedback / solutions are welcome though.