#!/usr/bin/env python3 """Warm Redis cache for vehicle info (part_vehicle_preview alternative). Runs in batches over all parts in the catalog, populating nexus:vehicle:{part_id} keys in Redis. This eliminates the DISTINCT ON + 4 JOINs query on vehicle_parts (2B rows) for cached parts. Usage: export MASTER_DB_URL="postgresql://..." export REDIS_URL="redis://localhost:6379/0" python3 warm_vehicle_cache.py """ import os, sys, json, time sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'pos')) import psycopg2 import redis def _fix_dsn(dsn): if dsn and 'host=' not in dsn and '@/' in dsn: dsn = dsn.replace('@/', '@localhost/') return dsn MASTER_DB_URL = _fix_dsn(os.environ.get('MASTER_DB_URL', 'postgresql://postgres@localhost/nexus_autoparts')) REDIS_URL = os.environ.get('REDIS_URL', 'redis://localhost:6379/0') BATCH_SIZE = 5000 TTL_SECONDS = 3600 def main(): print("Connecting to master DB and Redis...") conn = psycopg2.connect(MASTER_DB_URL) cur = conn.cursor() r = redis.from_url(REDIS_URL, decode_responses=True) r.ping() # Get all part_ids cur.execute("SELECT id_part FROM parts WHERE oem_part_number IS NOT NULL ORDER BY id_part") all_ids = [r[0] for r in cur.fetchall()] total = len(all_ids) print(f"Total parts to warm: {total}") processed = 0 cached = 0 start = time.time() for i in range(0, total, BATCH_SIZE): batch = all_ids[i:i + BATCH_SIZE] cur.execute(""" SELECT DISTINCT ON (vp.part_id) vp.part_id, b.name_brand, m.name_model, y.year_car FROM vehicle_parts vp JOIN model_year_engine mye ON mye.id_mye = vp.model_year_engine_id JOIN models m ON m.id_model = mye.model_id JOIN brands b ON b.id_brand = m.brand_id JOIN years y ON y.id_year = mye.year_id WHERE vp.part_id = ANY(%s) ORDER BY vp.part_id, y.year_car DESC """, (batch,)) pipe = r.pipeline() batch_cached = 0 for row in cur.fetchall(): info = f"{row[1]} {row[2]} {row[3]}" pipe.setex(f'nexus:vehicle:{row[0]}', TTL_SECONDS, info) batch_cached += 1 pipe.execute() processed += len(batch) cached += batch_cached elapsed = time.time() - start rate = processed / elapsed if elapsed > 0 else 0 print(f" [{processed}/{total}] cached={batch_cached} ({rate:.0f}/s)") cur.close() conn.close() print(f"\nDone. Cached {cached} vehicle entries in {elapsed:.0f}s") if __name__ == '__main__': main()