Skip to content

Database, SQL and API audit (17.10.2026) ​

Owner 17.10.2026 (SRC-1423): audit the database, the SQL scripts and the API; which tables hold the most data, and which data exists but is never used. Companion of the repo size audit (docs/ops/repo-size.md, in the nightly of 17.10.2026).

How it was measured. Production D1 nemo12-platform, read-only, through npm run seed -- scripts/noop-readonly.sql "<SELECT>" (the owner's rule: production only through GitHub Actions). D1 has no dbstat, so size is the sum of LENGTH(CAST(col AS BLOB)) over every column (data only, no indexes). "Read at runtime" means the table name appears in a worker's source (workers/*/src, the www worker in nemo12-www), tests and migrations excluded.

Summary ​

CountNote
Tables371281 with rows, 90 empty
Rows158,728
Data98.0 MBtop 5 tables hold 52 MB
Tables never read at runtime96 of them hold data (2.8 MB)
API paths671 (787 routes)61 have no caller in either repo

Biggest tables ​

TableMBRowsFinding
content_item_drafts19.914,892All from one item-generation run in 08.2026. 3,127 failed the quality gate; none of the 4,892 prompts exists in items. 13.2 MB of it is quality_json (evaluator output)
items11.2017,31017,224 published. 12,028 are in subjects the English-only direction removes: math 4,180, vietnamese 2,296, physics 1,994, chemistry 1,946, informatics 1,812, biology 800. quality_json 3.8 MB
articles8.02400www articles. body_md 2.82 MB is never read: the worker serves body_html
learner_model_versions6.572,022872 current versions; the 1,150 superseded ones hold 5.12 MB
seo_pages6.01191html 4.61 MB and data_json 0.74 MB are both served
ielts_micro_skill_items3.685,008Read
lab_content2.771,587Read
ai_teen_content_versions + _docs3.79297AI Teen leaves nemo12 (English-only direction)
learner_models2.05328Read
b21_indicators, b21_levelsabout 2.37,670b21 moved to conan1.com (redirect in flight)
illustrations1.08825Never read at runtime (see below)

Data that exists but nothing uses ​

WhatSizeEvidenceProposal
content_item_drafts19.9 MBOne-off run, none promoted to itemsExport to R2 (nemo12-content/archive/), then delete
items of the removed subjectsabout 8 MBSubjects leaving nemo12Delete with the subject removal (toan12-split, english-only-cleanup)
Superseded learner_model_versions5.1 MB1,150 of 2,022 rowsKeep the last 5 per learner and model kind in D1, older ones to R2 (the model-engine standard keeps every version; this moves them, it does not drop them)
articles.body_md2.8 MBOnly body_html is readStop writing it (nemo12-www seed), then drop the column (expand/contract)
illustrations1.1 MBSeeded by www (773 rows), learn (33), marlins (19); read by nothingStop seeding, drop the table
curriculum_lesson_gate_choices + _gate_quiz1.35 MB15,552 rows; marlins renders gates from apps/marlins/src/parentLessonGates/*.tsDrop the tables and scripts/seed-parent-gates-*.sql (4.5 MB in git), or make D1 the one source
curriculum_unit_specs, _unit_prereq_notes, _course_prereqs0.37 MB971 rows, never readDrop, or wire up the feature that wanted them
3 empty tables with no code0ai_teen_content_import, speak_recording_comments, speak_recording_feedbackDrop
87 empty tables that code does read0Features built, never used: essay 10, speak 10, live 7, partner 6, school 5, event 4, walk 4 ...Owner decision per feature; no size cost
Proxy email openssmall1,098 of 1,706 email_opens are mail-proxy fetches, not peopleCount only direct opens in open-rate figures

The parent course content lives twice: as TypeScript in apps/marlins/src/parentLessons/ (about 5.8 MB in git) and in the curriculum_lesson_* tables (filled by scripts/seed-parent-bodies-*.sql, 7.3 MB in git, pulled back with pull-parent-bodies.mjs). One source should win; D1 edited in Coral is the one the project's content rule points to.

Logs grow slowly: engine_runs and workflow_run_steps add about 2,000 rows a month each, under 1 MB in total. A 90-day retention is enough when they start to matter.

SQL files in nemo12.com scripts/ ​

KindFilesMBWhat to do
Generated (header says so, a generator exists)3019.5Generate right before loading, do not commit (same change as in nemo12-www)
One-off, already loaded (dated, backfill, fix, rebalance)150.8Archive to R2 with the date they ran
Hand-written701.1Review: most are also one-offs

Largest generated: seed-b21.sql 3.0 MB (goes with b21), seed-parent-bodies-1..4 7.3 MB, seed-parent-gates-1..4 4.5 MB (feeds tables nothing reads), seed-grammar.sql 1.3 MB, seed-videos.sql 0.7 MB (keep generating: nemo12-videos writes it).

API routes with no caller ​

671 paths in workers/api/openapi.snapshot.json; every /v1 and /e literal in both repos' apps, workers and packages matched against them. 61 have no caller:

  • External by design (keep): email links /e/o/{file}, /e/s/{token}, /e/u/{token}, /e/one-click/{token}, /email/preferences/{token}; /v1/health; /v1/auth/access-session, /v1/auth/mentor-grant.
  • Admin and editor screens (verify): 22 under /v1/admin (audit-log, dead-letters, engine-runs, foundry runs, learners, parents, workflow-runs ...), 9 under /v1/content/items/..., 5 under /v1/coral. Some may be called through a base-path constant the text match cannot follow.
  • Built, no screen: /v1/privacy/* (4: data usage, deletion requests, retention policies), /v1/consents (2), /v1/whale/* (3), /v1/public/schools, /v1/public/markmaps, /v1/public/chat-testimonials, /v1/public/ielts/micro-skills, /v1/parent/course-progress, /v1/parent/ebook-progress, /v1/partners/my-discount, /v1/event-templates.

A text match is a candidate list, not a verdict. Before removing a route, confirm zero hits in 30 days of production requests (Workers analytics per path). Privacy routes stay even with no screen if they back a legal promise; they need a screen instead.

Order ​

  1. Free, no product decision: drop illustrations and the 3 code-less empty tables; stop writing articles.body_md; count only direct email opens.
  2. Archive then delete content_item_drafts (19.9 MB, a fifth of the database).
  3. With the subject removals already in flight: items of removed subjects, AI Teen and b21 tables.
  4. One source for parent course content (Coral D1 or TypeScript), then drop the other copy and its seeds.
  5. Superseded learner model versions to R2.
  6. API: route hit counts for 30 days, then remove what stays at zero.

Result: about 98 MB of data down to about 50-55 MB, and about 25 MB fewer SQL files in git.