Skip to content

Recover/sync local mice/exp with parent DB - #350

Open
maryapp wants to merge 75 commits into
mainfrom
mary/base_mice_exp
Open

maryapp wants to merge 75 commits into
mainfrom
mary/base_mice_exp

Conversation

@maryapp

@maryapp maryapp commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds run_base.py recovery/sync tooling to rebuild local mice / exp from GUI .npy, sync the mice registry from parent via host mysqldump (mysql.mk), and populate base.Base after recover.
  • Hardens multi-user Docker ops (UID/GID exec, compose fallbacks, latin1 session_notes, water-restriction ___ FK mirror) and logs the live MySQL target before destructive modes.
  • Production use: this branch was run on the production rig (server3) under the Linux account master to restore local base/exp/mice after an outdated local registry — recover, day checks/fixes, and orphan cleanup against the local DB only (not main).

The diff looks big because it includes the recovery tool, the sync rewrite, day repair, DJ2 workarounds, and ops hardening that were needed to actually run this in production — not because every file is a new feature forever, + tests

Test plan

  • On a rig with local Docker DB: make -f mysql.mk sync-mice-from-main then python run_base.py recover_base inside make bash
  • Confirm base.Base grows after recover; Dataset−Base gap is only Latencytest* / known failures
  • python run_base.py check_session_days (and fix_session_days dry-run / --force if needed)
  • Dry-run cleanup_orphans / cleanup_mice and verify the log shows DJ_HOST + live host:port (not DJ_MAIN_HOST)
  • Re-run recover for SessionScoreSheet cases that previously failed on __water_restriction FK

maryapp and others added 30 commits July 4, 2026 13:37
Single configure_runtime() entry point suppresses noisy DataJoint UserWarnings and lineage INFO messages by default, with --verbose to restore DEBUG output.

Co-authored-by: Cursor <cursoragent@cursor.com>
populate_pending() restricts each populate call to keys not yet in the target table and not listed in FailedSession, avoiding redundant work on every cron run.

Co-authored-by: Cursor <cursoragent@cursor.com>
FailedSession skips, already-populated keys, folder checks, and dataframe fetch progress are now DEBUG by default; use --verbose to see them.

Co-authored-by: Cursor <cursoragent@cursor.com>
Each cron step now logs elapsed seconds on completion or failure, making slow populate stages easy to spot.

Co-authored-by: Cursor <cursoragent@cursor.com>
Re-enables the dataset existence check so cron no longer re-processes every pickle file on each run.

Co-authored-by: Cursor <cursoragent@cursor.com>
Run once via `python run.py maintenance` to clear semantic-check warnings on populate.

Co-authored-by: Cursor <cursoragent@cursor.com>
Skip and file move now require every planned table row to exist (not just Dataset). Population runs with move=False, then pickle/npy move to /data/processed only on full success, with Dataset paths synced afterward.

Co-authored-by: Cursor <cursoragent@cursor.com>
Query Session by explicit mouse_name keys instead of joining Mouse table expressions, avoiding lineage errors when building the GUI menu.

Co-authored-by: Cursor <cursoragent@cursor.com>
Provides make targets to inspect replica status and local session/stub mouse rows without ad-hoc SQL.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ons.

Stub mice during ingest, pull full metadata from the main DB via DJ_MAIN_HOST, and limit fetch_data dropdowns to mice with local sessions.

Co-authored-by: Cursor <cursoragent@cursor.com>
…files.

Add POPULATE_BASE flag, stub-mouse hook on populate, processed-folder GUI backfill, and --no-populate-base CLI switch.

Co-authored-by: Cursor <cursoragent@cursor.com>
Day numbering now considers all .npy files in both locations so archived and incoming sessions stay consistent.

Co-authored-by: Cursor <cursoragent@cursor.com>
Checks replication is off, cleans orphan exp/mice rows, repopulates base from disk, and documents main↔local sync steps.

Co-authored-by: Cursor <cursoragent@cursor.com>
When sessions are not yet in the DB, infer day from each mouse's own session dates on disk instead of sharing one counter across all mice.

Co-authored-by: Cursor <cursoragent@cursor.com>
…nly.

Base schema modes (sync_mice, recover_base, fetch with base populate) move to run_base.py on the base branch; run.py populate skips exp/mice tables.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep base-schema workflows (run_base, optional POPULATE_BASE, mouse sync)
while adopting main's DJ2 cron fixes (barcodes, summary emails, populate_pending).

Co-authored-by: Cursor <cursoragent@cursor.com>
Automate local→parent Session push, document recover/sync steps and
replication safety for cleanup, and cover the new helpers in pytest.

Co-authored-by: Cursor <cursoragent@cursor.com>
run.py populate again honors POPULATE_BASE (default on) so GUI ingest
writes base tables; docs clarify run_base is for recover/sync only.

Co-authored-by: Cursor <cursoragent@cursor.com>
Fetch mice from main then upsert locally; slim run_base to recover/sync
modes; base populate follows GUI; remove processed backfill on every cron.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Restrict sync_exp to this lab’s Dataset sessions (skip collab), use replace for sync_mice upserts so Sessions are not cascaded, and document that main is never deleted while local orphans require no Dataset.

Co-authored-by: Cursor <cursoragent@cursor.com>
Restrict recover populate to Dataset-backed GUI files, match sync_exp on mouse/doe/attempt with parent day assignment, sync Strain before Mouse replace, and upgrade stubs from full npy metadata.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Pull Dataset/session mice from parent before base populate, rebuild from all unpopulated GUI files, and run Dataset-orphan cleanup after (dry-run unless --force).

Co-authored-by: Cursor <cursoragent@cursor.com>
sync_mice now includes mouse names from data/processed GUI files so parent registry pull works before Dataset/Session rows exist.

Co-authored-by: Cursor <cursoragent@cursor.com>
recover_base no longer runs sync_mice or orphan cleanup; docs and cleanup_orphans mode spell out the operator order.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Fall back to DJ_USER/DJ_PWD (and optional USE_DJ_CREDS=1) for the local DB only.

Co-authored-by: Cursor <cursoragent@cursor.com>
maryapp and others added 17 commits August 8, 2026 19:07
…e not required.

Co-authored-by: Cursor <cursoragent@cursor.com>
Host-mapped Docker ports (e.g. :3309 vs :3306) both report @@PORT=3306 inside the container, which falsely aborted sync.

Co-authored-by: Cursor <cursoragent@cursor.com>
Tests mocked dict rows; real pymysql returns (name, value) tuples, so .get("Value") raised AttributeError right after the replication check.

Co-authored-by: Cursor <cursoragent@cursor.com>
Also print full tracebacks from run_base so DataJoint's bare Uncaught exception line is not the only clue.

Co-authored-by: Cursor <cursoragent@cursor.com>
… to.

Local DBs had both _ and __ water_restriction tables; DJ populated _ while the FK required __, so score sheets failed after a successful WaterRestriction insert.

Co-authored-by: Cursor <cursoragent@cursor.com>
…session_days.

Skipping failed .npy day updates let wrong exp.Session.day values get ingested; fail hard instead and expose a doe-timeline consistency check.

Co-authored-by: Cursor <cursoragent@cursor.com>
Drop unused diagnostic targets; dump Strain/SurgeryType before Mouse without REPLACE so local FK parents are not deleted.

Co-authored-by: Cursor <cursoragent@cursor.com>
Dry-run by default; --force migrates Session, SessionScoreSheet, and base.Base when the target PK is free.

Co-authored-by: Cursor <cursoragent@cursor.com>
DataJoint 2 dropped delete(safemode=); use config safemode, and treat same-doe target PKs as a resume from a failed prior run.

Co-authored-by: Cursor <cursoragent@cursor.com>
…re exit 125.

When docker ps fails (e.g. stale group membership), print the real error and a newgrp hint.

Co-authored-by: Cursor <cursoragent@cursor.com>
Handle compose failure explicitly under set -e and print a force/newgrp hint instead of aborting with no message.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ssing.

Avoids 'unable to find user' after switching host accounts, and clearer errors / docker-compose v1 fallback when Compose V2 is not installed.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ip base_install.

Coerce Unicode notes for latin1 MySQL, fall back when /app/logs is not writable, allow SKIP_BASE_INSTALL, and log run_base failures with traceback.

Co-authored-by: Cursor <cursoragent@cursor.com>
Makes cleanup/fix dry-runs and --force apply steps print host:port/uuid so deletes cannot be confused with DJ_MAIN_HOST.

Co-authored-by: Cursor <cursoragent@cursor.com>
…er recover.

Mirror into __water_restriction even when the _ table already has the row, and run Base.populate at the end of recover_base so Dataset links appear.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
An occupier at the target day with a different doe is itself a day mismatch, so the plan correctly contains two rows; assert the conflict on the blocked rekey.

Co-authored-by: Cursor <cursoragent@cursor.com>
@maryapp

maryapp commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

cc @arturoptophys, @lecriste exp and mice/base have been updated, again, I don't like the idea of doing it manually (aka sync - change days. move .npy files from processed, so here is the code that takes care of it)

@maryapp
maryapp marked this pull request as ready for review August 8, 2026 22:12
@maryapp

maryapp commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

Note (DataJoint _ vs __ — upstream naming change)

We have MySQL tables with a double underscore (__), e.g. mouse_score_sheet__water_restriction. Newer DJ (incl. DJ2) effectively moved to a single underscore (_), e.g. mouse_score_sheet_water_restriction, and the __ form is deprecated / no longer what inserts target.

That mismatch breaks local DBs that still have FKs (notably exp.session_score_sheet → water restriction) pointing at the old __ table, while DataJoint and mysqldump sync write into _. Result: SessionScoreSheet FK failures even though the _ row “exists.”

This branch works around that DJ naming change by mirroring (mouse_name, doc) into __ when needed during recover/populate, and by mapping _ in the host mice sync dump load. (cc @lecriste to investigate the bug)

@maryapp maryapp added the enhancement New feature or request label Aug 8, 2026
@maryapp maryapp assigned lecriste and unassigned maryapp Aug 11, 2026
@maryapp

maryapp commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator Author

Why not DataJoint dual-connect

A Python sync_mice used to connect to main, fetch the mice registry, then
switch back to local and upsert — all in one DataJoint process. That was
dropped (ce2d583, 97d3641) because DataJoint 2.x does not reliably support
two MySQL endpoints in one process.
dj.conn() is a singleton. Imported mice / exp Schema objects keep
their own .connection. dj.conn(reset=True) often left the TCP session and
the schema objects on the previous server.

Well... @lecriste, @arturoptophys, happy to know how and why it worked for you for mouse hockey, may it be the case since it's 2 diff servers?...

What actually failed:

  1. TLS — main is plain TCP. use_tls=False in config was ignored; DJ still
    SSL-wrapped and crashed.
  2. Same IP, two ports — local :3309 vs main :3306. Changing host without
    port (or trusting config after reset) still queried local.
  3. Sticky TCP — after “restore”, config said :3309 but live @@port was
    still 3306.
  4. Sticky schemasmice.Mouse() stayed on the previous session. Sync
    could count local rows while dj.conn() was on main — or write to main.
  5. Wrong upserts even on the right host — case-sensitive names; parent
    __ part tables vs local _; missing Strain / SurgeryType FKs; replace=True
    on Strain deleted a parent PK that local Mouse already referenced.

Safeguards (CONNECTION_ID(), refuse local endpoint, verify @@port) mostly
proved the switch was still wrong. Dual-connect was unsafe in both directions:
read the wrong DB, or write to main.

run_base.py no longer has sync_mice. Incomplete mice warn you to run the
host mysql target instead.

Dual DataJoint (Python, one process)

Pros

  • Same stack as the rest of the pipeline (run_base.py, table classes, types).
  • Can filter incomplete local mice in Python before fetch.
  • No host mysql / mysqldump required.
  • One command inside make bash.

Cons

  • DJ2 singleton + schema objects do not support two DBs.(?? is it true)
  • Easy to read/write the wrong server (including main).
  • SSL, port, and rebind bugs are opaque.
  • replace=True vs FKs is easy to get wrong.
  • Hard to test; failed in production after many patches.

MySQL bash (mysqldumpmysql)

Current path: make -f mysql.mk sync-mice-from-main
scripts/sync_mice_mysql.sh. Two separate mysql clients, no DJ singleton.

Pros

  • No shared session — dump from DJ_MAIN_*, load into local DJ_HOST.
  • Never deletes on main (dump only).
  • Explicit: INSERT IGNORE for lookups, REPLACE for manuals,
    FOREIGN_KEY_CHECKS=0.
  • Rewrites ___ when table names differ.
  • Identifies servers by @@server_uuid, not @@port (Docker’s internal port
    is often 3306).
  • Optional filter: MOUSE=Name1,Name2.
  • Easy to inspect the SQL dump.

Cons

  • Host-side only (mysql + mysqldump on PATH), not inside make bash.
  • Bypasses DataJoint (no DJ validation, lineage, or Python types).
  • REPLACE can overwrite local Mouse / score-sheet rows.
  • Table list and ___ mapping live in the script.
  • Credentials in .env; dump files briefly on disk.
  • Replication still matters if local is a replica.

Bottom line: dual DJ looked cleaner in code but was unsafe because DJ2 would
not reliably leave main. Bash is dumber and split across host vs container, but
it copies the right rows to the right DB.

cc @arturoptophys, so to consider what approach to use... I feel like mysql is more stable

@maryapp

maryapp commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator Author

version from vr4mice docker on server3: datajoint==2.2.4

@maryapp

maryapp commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator Author

TODOS: (cc @lecriste)

  • consider how we sync (Dual DJ VS Mysql)
  • sync back exp/mice to (-aux + auto replication to Main)
  • have the sync in cronjob (the current one)
  • change path in fetch_menu (since generate )

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants