- Implement lazy MongoDB client initialization in backend/db/mongo_client.py. - Create Gunicorn configuration to ensure MongoDB client is initialized per worker. - Refactor database access layer to support MongoDB with a new MongoLockedTable adapter. - Add migration script to transfer existing TinyDB data to MongoDB, preserving idempotency. - Update tracking event handling to ensure deterministic ordering with a monotonic sequence. - Modify tests to use mongomock for MongoDB integration and ensure existing tests pass. - Add integration test script to run tests against a local MongoDB Docker container. - Document environment variables and migration process in specs/feat-database-migration.md.
9.0 KiB
9.0 KiB
Feature: Migrate Backend Persistence from TinyDB to MongoDB Atlas
Overview
Goal: Replace the TinyDB JSON-file persistence layer in the Flask backend with MongoDB Atlas while keeping the existing API surface, model definitions, and test infrastructure largely unchanged. Introduce a migration script that exports existing TinyDB data into MongoDB.
User Story: As a developer and operator, I want the application to run on a managed, scalable document database so that concurrent access, multi-worker deployments, and production hosting are reliable.
Rules:
- Backend routes and response shapes must remain unchanged.
- Python models in
backend/models/keep theirfrom_dict()/to_dict()interface. - Every mutation must continue to emit SSE events through the existing broadcaster.
- The migration must be reversible (keep TinyDB files as a backup) and idempotent.
- All existing tests must continue to pass; new MongoDB-backed tests must be added.
Data Model Changes
Backend Model
- No changes to dataclass fields or
to_dict()/from_dict()output. - The model's
idfield is stored as MongoDB's_idprimary key. The originalidfield is removed from storage so documents do not contain duplicate values. - On read,
_idis mapped back toidand the document is wrapped as a TinyDB-compatibleDocumentwithdoc_idset. created_atandupdated_atremain epoch floats.
Frontend Model
- No changes. The API contract is preserved.
Backend Implementation
1. Database client initialization
- Create
backend/db/mongo_client.py. - Do not create a
MongoClientat module import; it is created lazily on first access. get_mongo_client()returns a cached process-level singleton so tests, scripts, and Flask requests all share the same client.- For Gunicorn multi-worker deployments,
backend/gunicorn.conf.pydefines apost_forkhook that callsinit_mongo_client()so each worker process gets its own client after forking. - Fail-fast settings for development:
MongoClient( os.getenv("MONGO_URI"), serverSelectionTimeoutMS=5000, connectTimeoutMS=5000, maxPoolSize=20, )
2. Collection mapping
- One MongoDB collection per existing TinyDB file:
children←children.jsontasks←tasks.jsonroutines←routines.jsonroutine_items←routine_items.jsonroutine_schedules←routine_schedules.jsonroutine_extensions←routine_extensions.jsonrewards←rewards.jsonimages←images.jsonpending_rewards←pending_rewards.json(legacy)pending_confirmations←pending_confirmations.jsonusers←users.jsontracking_events←tracking_events.jsonchild_overrides←child_overrides.jsonchore_schedules←chore_schedules.jsontask_extensions←task_extensions.jsonrefresh_tokens←refresh_tokens.jsonpush_subscriptions←push_subscriptions.jsondigest_action_tokens←digest_action_tokens.json
3. LockedTable MongoDB adapter
- Refactor
backend/db/db.pyso that each exported*_dbobject continues to expose the same methods:search(...),get(...),all(),insert(...),insert_multiple(...),update(...),remove(...),truncate(). - Implement a new
MongoLockedTableclass that mirrors the existingLockedTableAPI but delegates to a MongoDB collection. - Keep
LockedTable(TinyDB) as a fallback selectable byUSE_MONGODB=true|false(defaultfalse). - Store the model
idfield as MongoDB_idon writes; do not keep a duplicateidfield in storage. - Restore the model
idfield from_idon reads and return TinyDB-compatibleDocumentobjects withdoc_idset so existing callers that rely ondoc_idcontinue to work. - Support
update(fields, doc_ids=[...])and callable updater functions (update(lambda rec: ..., cond)) to match TinyDB behavior. - Reuse function names where possible to minimize API file changes.
4. Indexes
- Create indexes on application startup (
backend/main.py) and in the pytest session fixture. Index creation is intentionally not done at module import time so theMongoClientremains lazily initialized. - Required indexes:
- Unique primary key is provided by MongoDB
_id(mapped from modelid); no separateidindex is created. user_id— secondary onchildren,tasks,routines,routine_items,rewards,images,tracking_events,pending_confirmations,chore_schedules,task_extensions,refresh_tokens,push_subscriptions,digest_action_tokens.child_id— secondary onchild_overrides,chore_schedules,task_extensions,pending_confirmations,tracking_events.entity_id+entity_type— compound secondary onpending_confirmations,tracking_events,child_overrides.token— unique onrefresh_tokensanddigest_action_tokens.
- Unique primary key is provided by MongoDB
5. Migration script
- Create
backend/scripts/migrate_to_mongodb.py. - Reads each TinyDB JSON file in
data/db/(ortest_data/db/whenDB_ENV=test). - Inserts each
_defaultrecord into the corresponding MongoDB collection, mapping TinyDB integer keys to the record's ownidfield. - Skips records that already exist by
id(idempotent). - Backs up TinyDB files to
data/db/backups/<timestamp>/before first run. - Prints a summary of migrated records per collection.
- Usage:
cd backend python -m scripts.migrate_to_mongodb [--dry-run]
6. Environment variables
MONGO_URI— required whenUSE_MONGODB=true.MONGO_DB_NAME— optional database name; defaults parsed fromMONGO_URIor falls back tochore_db/chore_db_test/chore_db_e2ebased onDB_ENV/DATA_ENV.USE_MONGODB—trueto use MongoDB backend;falseto keep TinyDB.
7. Default data initialization
backend/db/default.py(initializeImages,createDefaultTasks,createDefaultRewards) must work with the new MongoDB-backed*_dbobjects. No API changes.
8. Scheduler compatibility
- Background schedulers (
account_deletion_scheduler,digest_scheduler, etc.) read/write through the same*_dbobjects, so they require no changes once the adapter is in place.
9. Tracking event ordering
db/tracking.pyadds a hidden monotonic_seqfield to tracking events on insert and sorts by(occurred_at, created_at, _seq)descending. This guarantees deterministic ordering when consecutive events share the same timestamp (common in fast tests).
10. Gunicorn / Docker deployment
backend/gunicorn.conf.pyprovides thepost_forkhook required by the wiki plan.backend/Dockerfilenow loads the config via-c gunicorn.conf.py.
Backend Tests
- Add
mongomockto test dependencies and createbackend/tests/test_mongo_adapter.pythat verifies CRUD operations againstmongomock. - Update
backend/tests/conftest.pyto setUSE_MONGODB=trueand pointMONGO_URIatmongomockfor the default test run. - Ensure every existing API test still passes with the MongoDB adapter by running
pytest tests/. - Add integration test script
backend/scripts/run_integration_tests.ps1that starts a local MongoDB Docker container, runs a targeted pytest suite, and tears down the container. - Add
frontend/.env.testwith MongoDB configuration and update Playwright webServer env to passUSE_MONGODB/MONGO_URIto the Flask backend; E2E database is reset via the existing/auth/e2e-seedendpoint.
Frontend Implementation
- No frontend changes are required. The API contract remains unchanged.
Frontend Tests
- Run
npx playwright testand confirm E2E tests pass against the MongoDB-backed Flask backend with the isolatedchore_db_e2edatabase.
Future Considerations
- After a production burn-in period, remove the TinyDB fallback and
LockedTablecode. - Add database connection health-check endpoint.
- Consider MongoDB transactions for multi-document operations (e.g., child deletion cascades).
Acceptance Criteria (Definition of Done)
Backend
backend/db/db.pyexports MongoDB-backed*_dbobjects whenUSE_MONGODB=true.backend/db/mongo_client.pyimplements lazy client initialization and a Gunicorn-compatible init hook.- All existing API endpoints continue to work without route or response changes.
backend/scripts/migrate_to_mongodb.pymigrates existing TinyDB JSON files idempotently.- Required unique and secondary indexes are created on application startup or migration.
- Unit tests pass with
mongomock(pytest tests/). - Integration tests pass against a local Docker MongoDB container (requires a running Docker daemon; script verified to start/stop container when daemon is available).
Frontend
- E2E setup and representative parent-mode smoke tests pass against the isolated
chore_db_e2emongomock database, with the DB reset via/auth/e2e-seed.
Operations
MONGO_URIand optionalMONGO_DB_NAMEenvironment variables are documented.- Rollback instructions exist to switch back to TinyDB by setting
USE_MONGODB=false.