#5490 "A day of one metric reads fast even cold". A cold 24 h series read measured 18.3 s against 63 ms warm (#5485): every sample's row sat on its own heap page. ix_plugin_metrics_series_value carries `value` (INCLUDE), so the read is an index-only scan; it replaces the plain series index from 0023. It is built once, with CREATE INDEX CONCURRENTLY from a task started after the app is serving, never in a migration, so the boot isn't held while it builds over the live table (#5220) and ingest and pages carry on. A build cut short by a restart leaves an INVALID index; the next boot drops and rebuilds it. Later boots find it present and only check. Postgres keeps it current on each insert, as it did the index it replaces. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
54 lines
2.0 KiB
Python
54 lines
2.0 KiB
Python
"""Integration: the covering series index on plugin_metrics (#5490), built
|
|
after boot against Postgres. It replaces the plain series index, a later boot
|
|
only checks, and a day of one series is then read from the index alone."""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
import os
|
|
|
|
import pytest
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
|
|
@pytest.fixture
|
|
def app():
|
|
if not os.environ.get("STEWARD_DATABASE_URL"):
|
|
pytest.skip("needs Postgres")
|
|
from steward.app import create_app
|
|
return create_app(testing=False)
|
|
|
|
|
|
def test_the_series_index_is_built_once_and_replaces_the_plain_one(app):
|
|
from sqlalchemy import text
|
|
from steward.core.metric_index import REPLACES, SERIES_INDEX, ensure_series_index
|
|
|
|
log = logging.getLogger("test")
|
|
|
|
async def _indexes():
|
|
async with app.db_sessionmaker() as s:
|
|
return dict((await s.execute(text(
|
|
"SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'plugin_metrics'"))).all())
|
|
|
|
async def _go():
|
|
first = await ensure_series_index(app._db_engine, log)
|
|
got = await _indexes()
|
|
assert SERIES_INDEX in got and REPLACES not in got
|
|
assert "INCLUDE (value)" in got[SERIES_INDEX]
|
|
assert await ensure_series_index(app._db_engine, log) == "present"
|
|
assert first in ("built", "present")
|
|
|
|
# One series over a day is answered from the index alone.
|
|
async with app.db_sessionmaker() as s:
|
|
await s.execute(text("SET enable_seqscan = off"))
|
|
await s.execute(text("SET enable_bitmapscan = off"))
|
|
plan = "\n".join(r[0] for r in (await s.execute(text(
|
|
"EXPLAIN SELECT date_trunc('hour', recorded_at), avg(value) FROM plugin_metrics "
|
|
"WHERE source_module = 'host_agent' AND resource_name = 'x' "
|
|
"AND metric_name = 'cpu_pct' AND recorded_at >= now() - interval '1 day' "
|
|
"GROUP BY 1"))).all())
|
|
assert f"Index Only Scan using {SERIES_INDEX}" in plan, plan
|
|
|
|
asyncio.run(_go())
|