Skip to content
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

fix concurrency archiving overview goals #18243

Merged
merged 27 commits into from Nov 15, 2021
Merged

Conversation

peterhashair
Copy link
Contributor

@peterhashair peterhashair commented Oct 29, 2021

Description:

Fixes: #18088
Fix concurrency archiving overview goals with multiple ajax requests. Used a flock lock on the api request. Not the best solution, but should reduce the number of multiple archiving.

Review

@peterhashair
Copy link
Contributor Author

@tsteur I think that PR fixed the concurrency for the goal overview archive

add flock lock on api end point
@tsteur
Copy link
Member

tsteur commented Nov 1, 2021

btw @peterhashair it be great if any lock would be only used if Rules::isArchivingEnabledFor() && !SettingsServer::isArchivePhpTriggered(); because only then we will actually need it.

That means the lock will be only used if browser archiving is used or when a range is being requested. This way we avoid the overhead of locks when it's doing the regular archiving using cron as the cli archiver already pretty much makes sure we won't archive the same date concurrently.

Also ideally the solution would automatically work for all reports if possible. If we only fix it eg in Goals then we could as well just use the JS fix to only request one widget instead of all of them

Peter Zhang and others added 8 commits November 2, 2021 11:09
add concurreny lock
remove extra lock check
update lock
update load test
add lock test
add mysql lock
upload lock
@peterhashair
Copy link
Contributor Author

peterhashair commented Nov 3, 2021

@tsteur I locked Rules::isArchivingEnabledFor() && !SettingsServer::isArchivePhpTriggered(); those two, it still has 1 duplicated after I tried some task 10 times. Is there any more places needs to lock?

try {
if (SettingsServer::isArchivePhpTriggered()) {
$requestedReport = Common::getRequestVar('requestedReport', '', 'string');
if (!empty($requestedReport)) {
$this->params->setArchiveOnlyReport($requestedReport);
}
}
// invalidate existing archives before we start archiving in case data was tracked in the past. if the archive is
// made invalid, we will correctly re-archive below.
if ($this->invalidateBeforeArchiving
&& Rules::isBrowserTriggerEnabled()
) {
$this->invalidatedReportsIfNeeded();
}
if (SettingsServer::isArchivePhpTriggered()) {
$this->logger->info("initiating archiving via core:archive for " . $this->params);
}
//pick select
list($visits, $visitsConverted) = $this->loadArchiveData();
if ($this->quickReturn) {
return [$visits, $visitsConverted];
}
//insert data
list($visits, $visitsConverted) = $this->prepareCoreMetricsArchive($visits, $visitsConverted);
list($idArchive, $visits) = $this->prepareAllPluginsArchive($visits, $visitsConverted);
if ($this->isThereSomeVisits($visits) || PluginsArchiver::doesAnyPluginArchiveWithoutVisits()) {
return [[$idArchive], $visits];
}
} catch (\Exception $e) {

@tsteur
Copy link
Member

tsteur commented Nov 3, 2021

@peterhashair In chat the other day I mentioned we need to should only lock when if ($this->params->isRootArchiveRequest() && !SettingsServer::isArchivePhpTriggered()) {. Maybe try if that helps (we'd definitely need a check to only lock in root archive request and not archive if the archiving is triggered).

If then there's still one duplicate that should be fine. It's also possible that the duplicate goes away once #18255 is merged

@peterhashair peterhashair marked this pull request as ready for review November 7, 2021 21:02
@peterhashair peterhashair added the not-in-changelog For issues or pull requests that should not be included in our release changelog on matomo.org. label Nov 7, 2021
@peterhashair peterhashair added this to the 4.6.0 milestone Nov 7, 2021
@peterhashair peterhashair added the Needs Review PRs that need a code review label Nov 7, 2021
Copy link
Member

@sgiehl sgiehl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a couple of comments

core/ArchiveProcessor/LoaderLock.php Outdated Show resolved Hide resolved
plugins/Goals/API.php Outdated Show resolved Hide resolved
core/Concurrency/Lock.php Outdated Show resolved Hide resolved
core/ArchiveProcessor/LoaderLock.php Outdated Show resolved Hide resolved
core/ArchiveProcessor/LoaderLock.php Show resolved Hide resolved
{
$row = Db::fetchOne('SELECT IS_FREE_LOCK(?);', array($this->id));

if ($row[0] === 1) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That won't work for all DB adapters. PDO and MySQLi differ in return types. Might be possible that a string is returned instead, so be good to cast that.
Btw. If you write some simple tests for that, you can see on travis if it works for both adapters.

core/ArchiveProcessor/Loader.php Outdated Show resolved Hide resolved
core/ArchiveProcessor/Loader.php Outdated Show resolved Hide resolved
core/ArchiveProcessor/Loader.php Outdated Show resolved Hide resolved
@sgiehl sgiehl removed the Needs Review PRs that need a code review label Nov 9, 2021
Co-authored-by: Stefan Giehl <stefan@matomo.org>
core/ArchiveProcessor/LoaderLock.php Outdated Show resolved Hide resolved
core/ArchiveProcessor/LoaderLock.php Outdated Show resolved Hide resolved
core/ArchiveProcessor/LoaderLock.php Outdated Show resolved Hide resolved
/**
* This class uses PluginsArchiver class to trigger data aggregation and create archives.
*/
class LoaderLock
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peterhashair can we write tests for this class to make sure it works?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tsteur add a test, simple one, let me know if that doesn't meet the requirements

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peterhashair can you move this test into a LoaderLockTest.php? For each class/file we also create a different test class.

What you additionally want to test is that

  • Create another lock with a different idea, check that the initially created lock doesn't also lock this lock with different ID. To make sure it only locks the specific ID.
  • A test where you enter a long ID with > 64 characters
  • Call "unlock" when it's not locked yet
  • Are there other edge cases that can be tested?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tsteur update it, but it seems like Travis number 7 angular test is started to fail. https://app.travis-ci.com/github/matomo-org/matomo/jobs/547651253. There may be a regression somewhere

core/ArchiveProcessor/Loader.php Outdated Show resolved Hide resolved
Peter Zhang added 5 commits November 10, 2021 17:08
remove unsed functions
update screen shot to question 1
update loaderlock test
Copy link
Member

@tsteur tsteur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peterhashair this worked nicely for me. I left a few more comments to look at.

There is also a test failing that needs to be fixed: https://app.travis-ci.com/github/matomo-org/matomo/jobs/547651249#L787
image

The failing angular test should be unrelated to this change.

I expect this to be the last iteration and will do a final test again after these changes.

core/ArchiveProcessor/Loader.php Outdated Show resolved Hide resolved

$lock = new LoaderLock($lockId);
$check = strlen($lock->getId())<64;
$this->assertEquals($check,1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just fyi (no change required).

Here you can use $this->assertTrue(strlen($lock->getId())<64); or even better $this->assertLessThanOrEqual(64,strlen($lock->getId()));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually in this case you would even want to check that $this->assertSame(64,strlen($lock->getId()));

because the test would otherwise pass even if an empty ID was returned so we can't actually check that the logic works.

The very best solution would be to not produce a random string, but created a fixed string like $lockId = str_pad('test', 128, '1');

and then test the exact returned value for the lock id like

$this->assertSame(' the generated lock id', $lock->getId())

This way you make sure that the method works as intended. This should be actually changed so we make sure it shortens the ID correctly as expected.

core/ArchiveProcessor/Loader.php Outdated Show resolved Hide resolved
core/ArchiveProcessor/LoaderLock.php Outdated Show resolved Hide resolved
Peter Zhang added 4 commits November 12, 2021 12:27
@peterhashair peterhashair added the Needs Review PRs that need a code review label Nov 15, 2021
@tsteur
Copy link
Member

tsteur commented Nov 15, 2021

@peterhashair I've made few tweaks like:

  • removing requires MySQL 5.6 comment as it's been there at least in 4.1 http://download.nust.na/pub6/mysql/doc/refman/4.1/en/miscellaneous-functions.html . Otherwise we would have needed to catch potential exceptions etc. Or did you maybe mean some other reason why it required MySQL 5.6 for the locks?
  • When the lockID is < 64 characters then we don't hash the ID. I mentioned this in the comment as there's not always a need to hash it
  • The log "initiating archiving via core:archive for..." wasn't always logged and moved it now into insertArchiveData.

I tested it again and worked nicely. If the tests pass then we can merge 🎉 . Made these changes quickly myself so we can merge it before the release to avoid another round of changes and reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Review PRs that need a code review not-in-changelog For issues or pull requests that should not be included in our release changelog on matomo.org.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Multiple requests may archive the same range request at the same time
3 participants