mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-07 03:15:56 +00:00
perf(sys-front-inj-matomo): cache a failed site list load so later vhosts skip the dead API
On a host whose Matomo was not deployed yet, every vhost injected before web-app-matomo asked SitesManager.getSitesWithAtLeastViewAccess again and waited out uri_retry (30 retries x 2 s): 6 vhosts, 84 to 98 s each, 8.7 minutes in galaxyserver full-2. matomo_site_cache only kept a loaded list. inject.yml now caches every executed load: the list on success, none on a failed request or an error mapping. A cache of none skips the cached-id step and getSitesIdFromSiteUrl, so the existing skip task turns the injection off for the vhost as before, without the retry wait. web-app-matomo resets the cache of every play host (delegate_facts, swarm workers included) after 07_config and before 08_reinject_consumers, so the re-injection and every later vhost load the list from the now running Matomo. Evidence: the real inject.yml with the real uri_retry against a closed port and a stub Matomo, 11 vhosts in 4 scenarios: the first probe took 68.6 s, the next two 0.4 and 0.3 s with matomo off; after the reset a known site came from the cache, an unknown one went getSitesIdFromSiteUrl -> addSite and was appended, a later vhost of that domain hit the cache. HEAD inject.yml took 68 s per vhost with the same outcome. The swarm reset reached a second host. ansible-lint passes; tests/unit/python/roles/sys-front-inj-matomo pins the gates and the reset order. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
7b2dfcfac7
commit
5b7284c4a4
4 files changed
+104
-8
No files matched your search
@@ -18,14 +18,16 @@
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
when: matomo_site_cache is not defined or matomo_site_cache.scope != matomo_site_cache_scope
|
||||
|
||||
- name: "🧊 Cache the Matomo site list for the remaining vhosts"
|
||||
- name: "🧊 Cache the Matomo site list, or its absence, for the remaining vhosts"
|
||||
ansible.builtin.set_fact:
|
||||
matomo_site_cache:
|
||||
scope: "{{ matomo_site_cache_scope }}"
|
||||
sites: "{{ matomo_site_list.json }}"
|
||||
when:
|
||||
- matomo_site_list.status is defined and matomo_site_list.status == 200
|
||||
- matomo_site_list.json is defined and matomo_site_list.json is not mapping
|
||||
sites: >-
|
||||
{{ matomo_site_list.json
|
||||
if matomo_site_list.status is defined and matomo_site_list.status == 200
|
||||
and matomo_site_list.json is defined and matomo_site_list.json is not mapping
|
||||
else none }}
|
||||
when: matomo_site_list is not skipped
|
||||
|
||||
- name: "🔧 Reset the Matomo site ID for '{{ domain }}'"
|
||||
ansible.builtin.set_fact:
|
||||
@@ -39,8 +41,7 @@
|
||||
{{ matomo_site_cache.sites | selectattr('main_url', 'in', matomo_site_url_variants)
|
||||
| map(attribute='idsite') | map('int') | list }}
|
||||
when:
|
||||
- matomo_site_cache is defined
|
||||
- matomo_site_cache.scope == matomo_site_cache_scope
|
||||
- matomo_site_cache.sites is not none
|
||||
- matomo_cached_site_ids | length > 0
|
||||
|
||||
- name: "🔎 Check if site {{ domain }} is already registered at Matomo"
|
||||
@@ -62,7 +63,7 @@
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
when:
|
||||
- matomo_site_id is none
|
||||
- matomo_site_list is skipped or (matomo_site_list.status is defined and matomo_site_list.status == 200)
|
||||
- matomo_site_cache.sites is not none
|
||||
|
||||
- name: "🔧 Take the site ID for '{{ domain }}' from the Matomo lookup"
|
||||
ansible.builtin.set_fact:
|
||||
|
||||
@@ -92,6 +92,17 @@
|
||||
delegate_to: "{{ STACK_HOST }}"
|
||||
run_once: true
|
||||
|
||||
- name: "🧹 Matomo | Forget the site list every host cached while Matomo did not answer yet"
|
||||
ansible.builtin.set_fact:
|
||||
matomo_site_cache:
|
||||
scope: ""
|
||||
sites:
|
||||
delegate_to: "{{ matomo_cache_host }}"
|
||||
delegate_facts: true
|
||||
loop: "{{ ansible_play_hosts }}"
|
||||
loop_control:
|
||||
loop_var: matomo_cache_host
|
||||
|
||||
- name: "📄 Matomo | Re-render consumer injections after the final compose flush"
|
||||
include_tasks: 08_reinject_consumers.yml
|
||||
when:
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT: Path = Path(__file__).resolve().parents[5]
|
||||
@@ -0,0 +1,81 @@
|
||||
import unittest
|
||||
|
||||
from jinja2 import Environment
|
||||
|
||||
from utils.cache.yaml import load_yaml_any
|
||||
|
||||
from . import PROJECT_ROOT
|
||||
|
||||
INJECT = PROJECT_ROOT / "roles" / "sys-front-inj-matomo" / "tasks" / "inject.yml"
|
||||
INSTALL_PATH = (
|
||||
PROJECT_ROOT / "roles" / "web-app-matomo" / "tasks" / "03_install_path.yml"
|
||||
)
|
||||
|
||||
SCOPE = "abc"
|
||||
SITES = [{"idsite": "3", "main_url": "https://a.example"}]
|
||||
|
||||
|
||||
def _tasks(path):
|
||||
return load_yaml_any(str(path)) or []
|
||||
|
||||
|
||||
def _guard(task):
|
||||
when = task["when"]
|
||||
return when if isinstance(when, list) else [when]
|
||||
|
||||
|
||||
def _eval(expressions, context) -> bool:
|
||||
env = Environment(autoescape=True)
|
||||
return all(bool(env.compile_expression(expr)(**context)) for expr in expressions)
|
||||
|
||||
|
||||
def _task(tasks, predicate):
|
||||
return next(task for task in tasks if predicate(task))
|
||||
|
||||
|
||||
class TestNegativeSiteCache(unittest.TestCase):
|
||||
def setUp(self):
|
||||
tasks = _tasks(INJECT)
|
||||
self.load = _task(tasks, lambda t: t.get("register") == "matomo_site_list")
|
||||
self.site_check = _task(tasks, lambda t: t.get("register") == "site_check")
|
||||
self.cached_id = _task(
|
||||
tasks,
|
||||
lambda t: "matomo_cached_site_ids" in str(t.get("vars", {})),
|
||||
)
|
||||
|
||||
def _state(self, sites, scope=SCOPE):
|
||||
return {
|
||||
"matomo_site_cache": {"scope": scope, "sites": sites},
|
||||
"matomo_site_cache_scope": SCOPE,
|
||||
"matomo_site_id": None,
|
||||
}
|
||||
|
||||
def test_a_cached_absence_skips_both_api_calls(self):
|
||||
state = self._state(None)
|
||||
self.assertFalse(_eval(_guard(self.load), state))
|
||||
self.assertFalse(_eval(_guard(self.site_check), state))
|
||||
self.assertFalse(_eval(_guard(self.cached_id), state))
|
||||
|
||||
def test_a_cached_list_still_asks_for_an_unknown_site(self):
|
||||
self.assertTrue(_eval(_guard(self.site_check), self._state(SITES)))
|
||||
|
||||
def test_a_cache_of_another_scope_reloads_the_list(self):
|
||||
self.assertTrue(_eval(_guard(self.load), self._state(None, scope="")))
|
||||
|
||||
|
||||
class TestMatomoDropsTheCacheBeforeReinjecting(unittest.TestCase):
|
||||
def test_the_reset_precedes_the_consumer_reinjection(self):
|
||||
tasks = _tasks(INSTALL_PATH)
|
||||
names = [str(t.get("include_tasks", "")) for t in tasks]
|
||||
reinject = names.index("08_reinject_consumers.yml")
|
||||
reset = _task(
|
||||
tasks[:reinject],
|
||||
lambda t: "matomo_site_cache" in t.get("ansible.builtin.set_fact", {}),
|
||||
)
|
||||
self.assertEqual(
|
||||
reset["ansible.builtin.set_fact"]["matomo_site_cache"]["scope"], ""
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user