FASE 7d: Lazy Loading + Minificación + Auto-serve minified
Cambios implementados: 1. Lazy loading de imágenes: - catalog.js: loading="lazy" decoding="async" en part cards y detail panel - inventory.js: lazy loading en imagen de detalle de item 2. Minificación de assets: - scripts/minify-assets.sh: minifica JS (terser) y CSS para POS y Dashboard - 25 archivos .min.js + 5 .min.css generados en pos/static/ - 14 archivos .min.js + 8 .min.css generados en dashboard/ 3. Nginx auto-serve minified: - try_files $1.min.js antes de servir .js original - try_files $1.min.css antes de servir .css original - Transparente para los templates HTML (cero cambios en HTML) 4. Cache warming script: - scripts/warm_vehicle_cache.py: pobla Redis con vehicle info por batches - Mitiga DISTINCT ON + 4 JOINs sobre 2B filas - Corre en background, procesa ~1.5M parts Tests: 73/73 pasando
This commit is contained in:
84
scripts/warm_vehicle_cache.py
Executable file
84
scripts/warm_vehicle_cache.py
Executable file
@@ -0,0 +1,84 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user