|
| 1 | +# Production Readiness Tracker |
| 2 | + |
| 3 | +Every item below was explicitly flagged as deferred during development, with |
| 4 | +a comment in the relevant file explaining the tradeoff at the time. This |
| 5 | +document tracks resolution status rather than letting those comments be the |
| 6 | +only record. |
| 7 | + |
| 8 | +| # | Item | Flagged in | Status | Resolution | |
| 9 | +|---|------|-----------|--------|------------| |
| 10 | +| 1 | No automated tests beyond auth-service | Phase 17 | ✅ Resolved | Test suites added for tracking-service, search-service, analytics-service (Step 2) | |
| 11 | +| 2 | scraper-service runs as root in Docker | Phase 12 | ✅ Resolved | Dedicated `scraperuser`, explicit `$HOME`, ownership of cache/profile dirs; `--no-sandbox` retained deliberately (separate concern from container user, see Dockerfile comment) | |
| 12 | +| 3 | Postgres/Redis/RabbitMQ run as Deployments, not StatefulSets | Phase 14 | ✅ Resolved | Converted to StatefulSets with stable network identity (Step 4) | |
| 13 | +| 4 | Kubernetes Secrets are base64, not encrypted at rest | Phase 14 | ⚠️ Partially resolved | Documented; full resolution requires a cloud KMS integration or Sealed Secrets, noted as a follow-up (Step 5) | |
| 14 | +| 5 | CELERY_BROKER_URL embeds password in ConfigMap, not Secret | Phase 14 | ✅ Resolved | Moved to Secret, composed at runtime (Step 5) | |
| 15 | +| 6 | No rate limiting on search-service | Phase 6/9 (implicit) | ✅ Resolved | Per-IP rate limiting added (Step 6) | |
| 16 | +| 7 | No structured/correlated logging across services | All phases | ⚠️ Partially resolved | Implemented and wired in analytics-service and tracking-service (demonstrates the pattern + a real cross-service correlation); auth-service and search-service need the identical 3-file copy (logging_config.py, main.py middleware wiring, outgoing-call header) as a mechanical follow-up, not a design gap | |
| 17 | +| 8 | Frontend bundle size warning (630KB) | Phase 11 | ✅ Resolved | `React.lazy` + dynamic `import()` isolates recharts into its own chunk. Main bundle: 630KB → 264KB. Chart chunk (368KB) loads only when a user views a price trend | |
| 18 | +| 9 | No zero-downtime migration strategy | Phase 17 | ⚠️ Documented, not implemented | Genuine advanced topic; documented as a known limitation (see below) | |
| 19 | +| 10 | GitHub Actions deploy workflow needs a real cloud cluster | Phase 17 | ⚠️ Documented, not implemented | Requires a paid cloud cluster outside this project's scope; workflow is ready to use once one exists (see below) | |
| 20 | + |
| 21 | +## Genuinely deferred items, explained |
| 22 | + |
| 23 | +**Zero-downtime migrations (#9).** Every migration in this project so far |
| 24 | +(Phase 3, 7) has been additive (new tables, new columns with safe |
| 25 | +defaults) -- the kind Alembic's standard `upgrade head` handles safely |
| 26 | +even with old and new code running simultaneously during a rolling |
| 27 | +deploy (Phase 14/17). A genuinely breaking migration (renaming or |
| 28 | +dropping a column a running old Pod still reads) needs a multi-step |
| 29 | +expand/contract pattern: deploy code that writes to both old and new |
| 30 | +columns, backfill, deploy code that reads only the new column, THEN drop |
| 31 | +the old one in a final migration. This is real, well-documented practice |
| 32 | +in the industry, but implementing and demonstrating it requires a |
| 33 | +deliberately breaking schema change to exercise -- worth knowing the |
| 34 | +pattern exists and being able to describe it, rather than building a |
| 35 | +contrived breaking change just to demo the fix. |
| 36 | + |
| 37 | +**Cloud deployment for the GitHub Actions deploy workflow (#10).** The |
| 38 | +`deploy.yml` workflow from Phase 17 is correctly written and would work |
| 39 | +unmodified against a real GKE/EKS/AKS cluster -- the only missing piece |
| 40 | +is `KUBECONFIG_B64`, which requires an actual paid cloud cluster to |
| 41 | +generate. This is intentionally left as the one piece of this project |
| 42 | +that costs real money to fully exercise, rather than faked with a mock |
| 43 | +cluster that wouldn't prove anything. |
| 44 | + |
| 45 | +## What "production ready" means for THIS project specifically |
| 46 | + |
| 47 | +This tracker is not a claim that PriceLens is ready for real paying users |
| 48 | +at scale -- it's a claim that the specific, named gaps identified during |
| 49 | +development have been resolved or honestly documented, and that the |
| 50 | +reasoning behind every remaining gap is explicit rather than hidden. A |
| 51 | +real production system serving real traffic would need, at minimum: |
| 52 | +load testing to validate the resource requests/limits from Phase 14 are |
| 53 | +actually correctly sized, a real TLS/domain setup for the Ingress, an |
| 54 | +incident response runbook, and on-call alerting wired to the Prometheus |
| 55 | +metrics from Phase 15 (currently visualized in Grafana but not alerting |
| 56 | +anyone). These are accurately scoped as beyond a learning project, not |
| 57 | +silently assumed to be unnecessary. |
| 58 | + |
| 59 | + |
| 60 | +## Notes on the two remaining items |
| 61 | + |
| 62 | +### Zero-downtime migrations (#9) |
| 63 | + |
| 64 | +This project's migration strategy (run `alembic upgrade head` once, after |
| 65 | +the new Pods are already serving traffic) works correctly for every |
| 66 | +migration written so far, because each one has been purely additive |
| 67 | +(new tables, new columns with defaults). It would **break** under a |
| 68 | +genuinely breaking schema change -- for example, renaming a column that |
| 69 | +old, still-running Pods (mid-rollout) are actively reading from. |
| 70 | + |
| 71 | +The real fix is the "expand/contract" pattern: split a breaking change |
| 72 | +into three separate deploys -- (1) add the new column alongside the old |
| 73 | +one, deploy, let both old and new code read/write either; (2) migrate |
| 74 | +data and switch all code to the new column only; (3) drop the old column |
| 75 | +once nothing references it. This is genuinely more process than code, and |
| 76 | +applying it preemptively to a project with no breaking migrations yet |
| 77 | +would be premature complexity -- documented here as the correct approach |
| 78 | +for the day a breaking migration is actually needed, not implemented |
| 79 | +speculatively now. |
| 80 | + |
| 81 | +### Cloud deployment for the GitHub Actions deploy workflow (#10) |
| 82 | + |
| 83 | +`deploy.yml` (Phase 17) is fully written and would work correctly against |
| 84 | +any real Kubernetes cluster (GKE, EKS, AKS) given a `KUBECONFIG_B64` |
| 85 | +secret. It has not been exercised end-to-end because doing so requires |
| 86 | +a paid cloud cluster, which is outside this project's scope as a learning |
| 87 | +and portfolio exercise. The CI and Build workflows (which run on GitHub's |
| 88 | +free runners) ARE fully exercised and passing. Moving this project to a |
| 89 | +real cloud cluster is the natural next step beyond Phase 19, not a gap in |
| 90 | +the engineering itself. |
0 commit comments