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

flushCaches should respect permitCacheFlushMode. Also flush caches which have a NULL cache_date #21430

Merged
merged 2 commits into from
Sep 21, 2021

Conversation

mattwire
Copy link
Contributor

Overview

Found via experimental #21384.

There is an option to prevent cache flushes CRM_Core_Config::isPermitCacheFlushMode() (currently only used by the importer) but it's only respected if we flush caches via the CRM_Contact_BAO_Contact_Utils::clearContactCaches() function which is bypassed by both opportunistic and deterministic (group contact) cache flushes. This means it's quite likely caches will be flushed anyway probably triggering deadlocks.

A group contact cache can have a NULL cache date if (1) it's never been built, (2) it's been invalidated but not flushed. But flushCaches() will only clear cache entries for groups which have an expired cache date.

Before

Group contact cache flushed even if CRM_Core_Config::isPermitCacheFlushMode() returns FALSE when opportunistic/deterministic cache flush is triggered.

Group contact cache not cleared if it has a NULL cache date.

After

Group contact cache not flushed if CRM_Core_Config::isPermitCacheFlushMode() returns FALSE when opportunistic/deterministic cache flush is triggered.

Group contact cache cleared if it has an expired or NULL cache date.

Technical Details

#21384 fails cache tests because the tests are testing things in a very specific way which is not necessarily how the cache should work. Looking into improving those tests identified the two issues in this PR which make fixing those tests much easier and should make the group contact cache more consistent as well as reduce deadlocks (during import at least).

Comments

@eileenmcnaughton

@civibot
Copy link

civibot bot commented Sep 10, 2021

(Standard links)

@civibot civibot bot added the master label Sep 10, 2021
@eileenmcnaughton
Copy link
Contributor

eileenmcnaughton commented Sep 10, 2021

So I feel like we have some shifting goal posts over what a cache date of NULL means. I guess it probably originally meant

  • never been built of
  • has been cleared out in order to be rebuilt

But along the way we started setting NULL to indicate 'should be rebuilt' and it stopped being a given that it would already be cleared out. So, I think this is correct in our evolution of this code.

However, I think this could cause extra queries - which you are trying to avoid

Say we have 100 groups and 50 of them are never used. Every time this function is called those 50 will be NULL - so even if no groups are out of date and we are not about to build on of the unused groups they will be found by the query and $expiredGroups won't be empty.

Maybe handling expired & null separately makes sense with a check first eg

IF (
// run query like
SELECT id FROM civicrm_group_contact_cache gc INNER JOIN civicrm_group g WHERE g.cache_date IS NULL LIMIT 1) {
then 
  DELETE per ^^ but without the limit 

@eileenmcnaughton
Copy link
Contributor

Hmm further thought - maybe the part above that matters is the 'do a quick get to see if any rows need to be deleted before doing a potentially locking query on the group contact cache.

ie that could be done regardless of how the group is invalidated.

In general the problem is too many locking queries - an extra get with LIMIT = 1 is not expensive.

Comment on lines 251 to 253
if (!CRM_Core_Config::isPermitCacheFlushMode()) {
return;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@eileenmcnaughton Do you think this bit is ok as-is? If so I'll split it off into it's own PR and work through the more complex bits separately.

Copy link
Contributor

Choose a reason for hiding this comment

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

@mattwire yeah I think it is

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok see #21468

Comment on lines 263 to 268
$groupsToFlushSQL = 'SELECT group_id FROM civicrm_group_contact_cache gc INNER JOIN civicrm_group g
WHERE cache_date <= %1 OR cache_date IS NULL GROUP BY group_id';
$groupsDAO = CRM_Core_DAO::executeQuery($groupsToFlushSQL, $params);
$expiredGroups = [];
while ($groupsDAO->fetch()) {
$expiredGroups[] = $groupsDAO->id;
$expiredGroups[] = $groupsDAO->group_id;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@eileenmcnaughton Alternative based on your suggestion. This only runs the delete query if there is actually data to be cleared out. This also prevents a DELETE being run when cache_date was set but expired and the group had no records (or was already cleared somehow!).

Copy link
Contributor

Choose a reason for hiding this comment

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

@mattwire my suspicion is that inclusing the civicrm_group_contact_cache in the query will mean it potentially returns thousands and thousands or results. I think 2 quick queries (1 to get the groups & 1 to check if there is at least one row to delete) will be quicker than 1 slow one

@mattwire mattwire force-pushed the flushcache branch 2 times, most recently from fe5ca48 to c34f9a0 Compare September 17, 2021 10:54
@mattwire
Copy link
Contributor Author

@eileenmcnaughton Ok, here's an alternative. 2 quick select queries followed by the DELETE/UPDATE only if necessary.

$params = [1 => [self::getCacheInvalidDateTime(), 'String']];
$groupsDAO = CRM_Core_DAO::executeQuery("SELECT id FROM civicrm_group WHERE cache_date <= %1", $params);
$groupsThatMayNeedToBeFlushedSQL = 'SELECT id FROM civicrm_group WHERE saved_search_id IS NOT NULL AND (cache_date <= %1 OR cache_date IS NULL)';
Copy link
Contributor

Choose a reason for hiding this comment

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

this needs to be (saved_search_id IS NOT NULL OR children <> '')

I'm not sure if children would be NULL or an empty string but <> '' should get both

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated!

@eileenmcnaughton
Copy link
Contributor

test fail unrelated

test this please

@eileenmcnaughton
Copy link
Contributor

@mattwire this seems right with the one change to also include 'children'

@eileenmcnaughton
Copy link
Contributor

OK - I think this is good now.

@mattwire mattwire merged commit 6230851 into civicrm:master Sep 21, 2021
@mattwire mattwire deleted the flushcache branch September 21, 2021 22:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants