02Bases de dades5 min

Els meus primers esquemes de dades gairebé mai no sobreviuen al primer mes

L'esquema del dia 1 és una hipòtesi sobre un domini que encara no entens. Assumir-ho canvia com l'escrius i quant dol canviar-lo.

Per Salva Castells

Imatge de portada: Sundaram Perumal, CC BY-SA 4.0.
En aquest post

En una app de generació de quadrants de torns, en dues setmanes vaig tocar l'esquema tres vegades de manera incompatible. Vaig crear una taula worker_verifications per desar el resultat de les comprovacions que feia el model sobre cada treballador. Cinc dies després la vaig esborrar sencera i vaig ficar la seva única columna útil, checked_rules, dins de worker_week_rules. Dos dies més tard vaig eliminar extra_closed_days de la taula weeks, que havia afegit en el redisseny anterior.

Cap d'aquests canvis no va ser per una decisió de producte nova. Van ser per entendre millor el domini.

L'esquema del dia 1 i el del dia 30

La taula que vaig esborrar tenia vuit columnes: ai_result, worker_hash, context_hash, verified_at, checked_rules i les claus. La idea era cachejar la verificació: si el hash del treballador i el del context no canviaven, no tornaves a cridar el model. Sona raonable.

El que va passar és que la unitat real del domini no era "una verificació", era "les regles d'un treballador en una setmana". I aquella fila ja existia. Les verificacions eren un atribut d'aquella fila, no una entitat. La migració que ho va arreglar és curta i es llegeix sola:

ALTER TABLE "worker_week_rules" ADD COLUMN "checked_rules" text DEFAULT '{}' NOT NULL;

UPDATE "worker_week_rules" wwr
SET "checked_rules" = wv."checked_rules"
FROM "worker_verifications" wv
WHERE wwr."week_id" = wv."week_id"
  AND wwr."worker_id" = wv."worker_id"
  AND wv."checked_rules" != '{}';

DROP TABLE "worker_verifications";
ALTER TABLE "worker_week_rules" DROP COLUMN "created_at";

Quatre sentències. El cost no va ser el SQL.

Quins canvis són senyal que vas encertar-la i quins que no

Hi ha dos tipus de canvi d'esquema i no s'assemblen gens.

  • Afegir una columna. checked_week_rules a weeks, calendar_rules a calendars. És un ALTER TABLE ADD COLUMN ... DEFAULT amb un default, es desplega sense aturar res, el codi vell continua funcionant. Això és el sistema creixent. És bon senyal.
  • Moure l'entitat de lloc. Esborrar una taula, reanomenar una columna, canviar de qui penja una dada. Això vol dir que la primera vegada vas modelar malament el domini, no que el domini hagi canviat.

En aquell mateix projecte vaig passar preferred_day_off integer a preferred_days_off text, perquè va resultar que un treballador podia preferir dos dies. Això també és dels bons: és informació nova sobre el món, no un error de modelatge. La migració cap en tres línies i conserva les dades amb un CAST.

El del tipus dolent es nota perquè el diff no és a la carpeta de migracions.

El cost real és a la UI, no al SQL

En una eina de documentació amb IA, vaig començar anomenant documents allò que l'usuari pujava. Un mes després el concepte correcte era contexts: trossos de text que l'agent podia llegir, citar i reescriure, i que podien venir d'una URL, no només d'un fitxer. El nom estava malament i arrossegava el model sencer.

El commit que ho va canviar no toca l'esquema. Toca la pàgina de xat, l'input, i esborra context-url-dialog, context-view i context-viewer sencers: 1.127 línies eliminades de cop. Un RENAME COLUMN de dos segons i un dia sencer tocant components.

I un cas encara més barat d'executar i més car de propagar: en aquell mateix projecte vaig reanomenar starCount a favoriteCount. El SQL són cinc línies en dues migracions. El diff toca l'esquema, dos fitxers d'accions de base de dades, i la UI que ho pintava.

Aquesta és la regla que me'n vaig endur: el cost de canviar un nom de columna és proporcional a quants components l'escriuen literalment. No al que costa la migració.

Com escric avui el primer esquema

Quatre coses que faig diferent des d'aleshores, i cap no és "dissenyar millor".

  • Anomeno per allò que és, no per d'on ve. documents era l'origen (un fitxer). contexts és el rol que juga la dada al sistema. El segon nom aguanta el canvi d'origen.
  • No creo una taula per a un atribut. Si la nova entitat sempre té exactament una fila per cada fila d'una altra taula, no és una entitat. És un grup de columnes. worker_verifications era això i vaig trigar cinc dies a veure-ho.
  • Un tipus per columna des del principi. En el redisseny gran vaig convertir alhora created_at de text a timestamptz, week_start de text a date i el JSON desat com a text a jsonb. Tot això ho hauria pogut posar bé el primer dia pel mateix preu. Arreglar-ho després és un ALTER COLUMN ... USING a cada taula i revisar tot el codi que comparava dates com a cadenes.
  • La UI no repeteix el nom de la columna. Un tipus derivat de l'esquema i un parell de funcions d'accés. Aleshores un RENAME COLUMN el caça el compilador en comptes d'un grep.

Quan sí que val la pena aturar-se

En un editor de plànols d'oficina vaig passar de tenir components DualDesk i QuadDesk separats a un únic MultiDesk amb nombre de places, i després el vaig refactoritzar una altra vegada per afegir places deshabilitades. Dues taules de dues i de quatre semblen dos casos; en realitat eren el mateix amb un paràmetre. Això sí que es veu el primer dia si t'atures deu minuts a preguntar si el 2 i el 4 són constants o són dades.

La regla que faig servir: si la diferència entre dos models és un número, és una dada. Aquí sí que val la pena pensar abans.

Per a la resta, escric l'esquema sabent que està malament. El que optimitzo no és encertar-la, és que arreglar-ho la setmana que ve costi una migració curta i no un dia tocant components.