Hem construït les dues meitats del sistema de BiblioRed: biblioredb a PostgreSQL, amb set taules, claus foranes i consultes multitaula; i bibliored a MongoDB, amb tres col·leccions dissenyades a partir de les consultes que l'aplicació necessita servir. Ara toca posar les dues meitats cara a cara i respondre la pregunta que tot professional acaba havent de respondre davant d'algú que decideix un pressupost: quan cadascuna, i per què?
Pel camí hem anat ajornant tres peces teòriques, i aquesta és la seva lliçó. El teorema CAP —probablement el concepte pitjor explicat de tota la disciplina—, el seu refinament PACELC, i el contrast entre ACID i BASE amb el que la consistència eventual significa de debò per al lector que acaba de publicar una ressenya i no la veu.
I hi ha un matís que canvia la conversa sencera i que gairebé cap comparativa antiga no recull: la frontera s'ha difuminat. PostgreSQL guarda documents JSON i els indexa; MongoDB té transaccions multidocument des del 2018. L'elecció del 2026 no s'assembla a la del 2012, i decidir amb els arguments del 2012 és la manera més comuna d'equivocar-se.
Acabarem amb una guia de decisió honesta, els mites més repetits desmuntats, i el nom de l'arquitectura a la qual ha arribat BiblioRed: la persistència poliglota.
Contingut
- Comparació dimensió per dimensió
- El teorema CAP, ben explicat
- PACELC: el refinament necessari
- ACID enfront de BASE
- Què significa "eventualment consistent" per a un lector de BiblioRed
- Consistència ajustable: quòrum,
writeConcernireadConcern - La frontera s'ha difuminat (I): PostgreSQL amb
jsonb - La frontera s'ha difuminat (II): transaccions a MongoDB
- La mateixa consulta als dos mons
- Guia de decisió
- Mites desmuntats
- Persistència poliglota: l'arquitectura de BiblioRed
- Errors habituals i consells
- Exercicis
- Conclusió
- Comparació dimensió per dimensió
| Dimensió | Relacional (PostgreSQL) | Documental (MongoDB) |
|---|---|---|
| Model de dades | Taules, files i columnes; valors atòmics a cada cel·la | Documents imbricats amb arrays i subdocuments |
| Esquema | Explícit, obligatori, validat pel servidor abans d'escriure | Flexible; validació opcional amb $jsonSchema |
| Canviar l'esquema | ALTER TABLE + migració + finestra de desplegament |
Escriure el camp nou; versionatge amb esquema_v |
| Llenguatge de consulta | SQL, estàndard ISO des del 1987, portable entre productes | API pròpia + canalització d'agregació; no portable |
| Relacions | JOIN al servidor, entre qualsevol parell de taules |
Referències resoltes per l'aplicació; $lookup com a excepció |
| Integritat referencial | Declarativa: FOREIGN KEY amb cinc accions ON DELETE |
Inexistent; responsabilitat de l'aplicació |
| Altres restriccions | NOT NULL, UNIQUE, CHECK, dominis |
Índexs únics i $jsonSchema |
| Transaccions | ACID completes, multitaula, des de sempre; nivells d'aïllament | Atòmiques per document; multidocument des del 2018, amb cost |
| Escalat de lectura | Rèpliques de lectura | Secundaris del conjunt de rèpliques |
| Escalat d'escriptura | Vertical; particionat possible però complex | Horitzontal per particionat, integrat |
| Consistència per defecte | Forta | Forta en llegir del primari; eventual en llegir de secundaris |
| Dades heterogènies | Columnes nul·les, taules per tipus o EAV | Natural |
| Consultes imprevistes | Excel·lent: l'esquema normalitzat respon al que no es va preveure | Limitada al disseny; pot exigir redisseny |
| Anàlisi i informes | Natiu; totes les eines de BI parlen SQL | Canalització d'agregació; connectors per a BI |
| Maduresa | Més de 45 anys de teoria i producte | Prop de 20 anys, amb evolució ràpida |
| Eines | Ecosistema enorme: ORM, migracions, BI, auditoria, rèplica lògica | Ampli i en creixement; menor en nínxols |
| Talent disponible | Molt ampli: SQL és coneixement bàsic | Més escàs, sobretot en modelatge avançat |
| Cost operatiu | Baix en un node; alt si es distribueix | Baix en un node; mitjà distribuït, però previst per disseny |
| Casos d'ús ideals | Transaccions, finances, inventari, informes, integritat crítica | Catàlegs, continguts, perfils, esdeveniments, estructures canviants |
| Casos d'ús desaconsellats | Dades molt heterogènies i esquemes que canvien cada setmana | Dades molt relacionades amb consultes impredictibles i transaccions creuades |
Dues files mereixen un comentari, perquè solen decidir projectes i molt poques vegades s'esmenten a les presentacions comercials.
Talent disponible. SQL el coneix gairebé qualsevol persona amb formació tècnica. El modelatge documental avançat —el de la lliçó anterior— el domina molta menys gent. Una decisió tecnològica que l'equip no sap operar és una mala decisió, per molt correcta que sigui sobre el paper.
Consultes imprevistes. És l'avantatge més infravalorat del model relacional. Un esquema normalitzat respon a preguntes que ningú no havia pensat el dia que es va dissenyar. Un disseny documental respon molt bé a les preguntes per a les quals es va fer, i pot necessitar un redisseny per a les noves. Si no saps què et preguntaran d'aquí a dos anys, això és un argument a favor de SQL.
- El teorema CAP, ben explicat
Formulat per Eric Brewer el 2000 i demostrat formalment el 2002, el teorema CAP és el resultat teòric més citat i pitjor entès del món de les bases de dades distribuïdes.
Les tres lletres es refereixen a propietats d'un sistema distribuït —diversos nodes que es comuniquen per xarxa—:
- C, consistència (consistency): tota lectura retorna l'escriptura més recent. Tots els nodes veuen el mateix en el mateix instant. Compte: no és la C d'ACID; són conceptes diferents amb la mateixa lletra, i aquesta coincidència ha causat una confusió considerable.
- A, disponibilitat (availability): tota petició a un node que funciona rep una resposta, sense error i sense espera indefinida.
- P, tolerància a particions (partition tolerance): el sistema continua operant encara que es perdin missatges entre nodes.
El malentès
La formulació popular —"tria'n dues de tres"— és incorrecta i enganyosa. Suggereix que existeix un menú amb tres opcions: CA, CP i AP. I no és així.
La partició no és una opció de disseny: és un fet de la naturalesa. Els cables es trenquen, els commutadors es reinicien, els centres de dades s'aïllen. Si el teu sistema està distribuït, hi haurà particions, no pots renunciar a P, i per tant "CA" no existeix com a categoria real.
L'enunciat correcte és aquest:
Quan es produeix una partició de xarxa, un sistema distribuït ha de triar entre continuar responent amb dades possiblement desactualitzades (A) o deixar de respondre per no donar dades incorrectes (C). Mentre no hi ha partició, es poden tenir les dues coses.
flowchart TD
P{"Hi ha partició de xarxa<br/>ara mateix?"}
P -->|NO — el 99,9% del temps| OK["Es tenen C i A alhora.<br/>El teorema no diu res."]
P -->|SÍ| E{"Cal triar"}
E -->|Prioritza C| CP["CP: el node aïllat REBUTJA<br/>peticions abans que arriscar<br/>dades incorrectes.<br/>MongoDB, HBase, etcd"]
E -->|Prioritza A| AP["AP: el node aïllat RESPON<br/>amb el que té, encara que<br/>estigui desactualitzat.<br/>Cassandra, DynamoDB, Riak"]
L'exemple concret de BiblioRed
Imagina que la xarxa de la sucursal Sud es talla i el seu node queda aïllat de la resta. La Marta Alsina, des del portal, publica una ressenya que arriba al node principal. L'Ivan Pereda, connectat per la xarxa de la sucursal Sud, demana veure les ressenyes d'aquell llibre.
- Sistema CP (així es comporta MongoDB): el node aïllat sap que no pot confirmar que les seves dades estiguin al dia, així que retorna un error o rebutja l'operació. L'Ivan veu un missatge de "servei no disponible". Molest, però mai no veu informació incorrecta.
- Sistema AP (així es comporta Cassandra per defecte): el node aïllat respon amb el que té. L'Ivan veu les ressenyes sense la de la Marta. Quan la xarxa es restableix, els nodes se sincronitzen i la ressenya apareix. Servei sempre disponible, a canvi d'haver mostrat un estat antic.
Cap de les dues no és "millor". Depèn de què faci més mal al teu domini: per a un catàleg de biblioteca, AP és perfectament raonable —a ningú no li arruïna el dia no veure una ressenya durant trenta segons—. Per a un saldo bancari o per al registre d'un préstec amb recàrrec, la resposta és CP sense discussió.
On cau cada producte
| Producte | Comportament davant partició | Comentari |
|---|---|---|
| PostgreSQL en un node | El teorema no s'hi aplica | Sense distribució no hi ha partició possible |
| PostgreSQL amb rèpliques síncrones | CP | Si la rèplica no confirma, l'escriptura no es completa |
| MongoDB (conjunt de rèpliques) | CP per defecte | Sense majoria no hi ha primari, i sense primari no hi ha escriptures |
| Cassandra | AP, ajustable a CP | El nivell de consistència es tria per operació |
| Redis | Depèn de la configuració | En clúster, tendeix a AP |
| Neo4j (clúster) | CP | Prioritza la correcció del graf |
La fila de Cassandra és important: els productes moderns no trien una casella fixa. Ofereixen un comandament que l'aplicació ajusta operació per operació, i ho veurem a l'apartat 6.
- PACELC: el refinament necessari
CAP té un problema pràctic: només diu alguna cosa sobre el moment de la partició, que és el 0,1 % del temps. No diu res del que passa el 99,9 % restant. I allà també hi ha decisions importants.
Daniel Abadi va proposar el 2012 l'extensió PACELC, que es llegeix així:
Si hi ha Partició (P), triar entre Disponibilitat (A) i Consistència (C); En cas contrari (E, else), triar entre Latència (L) i Consistència (C).
La segona meitat és l'aportació valuosa. Fins i tot amb la xarxa perfecta, mantenir tots els nodes perfectament sincronitzats costa temps: cada escriptura ha d'esperar la confirmació de les rèpliques abans de donar-se per bona. És un intercanvi permanent entre rapidesa i certesa.
| Sistema | Classificació PACELC | Traducció |
|---|---|---|
| PostgreSQL amb rèplica síncrona | PC/EC | Davant partició prioritza consistència; i sense partició també, encara que costi latència |
| MongoDB (configuració per defecte) | PC/EC | Consistència primer en tots dos escenaris |
| MongoDB llegint de secundaris | PC/EL | Consistent davant partició, però accepta dades una mica endarrerides a canvi de latència baixa |
| Cassandra | PA/EL | Disponibilitat davant partició i latència baixa la resta del temps |
| DynamoDB | PA/EL (configurable) | Es pot demanar lectura fortament consistent pagant més i esperant més |
El que cal endur-se de PACELC: la consistència no és gratis ni tan sols quan tot funciona bé. Cada vegada que un sistema garanteix que veus l'última dada, algú ha esperat. La pregunta de disseny no és "vull consistència?" —tothom la vol—, sinó "quanta latència estic disposat a pagar per ella, en aquesta operació concreta?".
I la resposta sol ser diferent segons l'operació. A BiblioRed:
- Registrar la devolució d'un exemplar amb recàrrec: màxima consistència, la latència és igual.
- Mostrar la llista de ressenyes d'un llibre: mínima latència, un desfasament d'un segon no molesta ningú.
Que siguin respostes diferents dins del mateix sistema és exactament el punt.
- ACID enfront de BASE
Són els dos models de garanties, i les seves sigles estan triades amb humor de químic: un àcid i una base.
ACID
Ho veurem en profunditat a la lliçó 06-01. Aquí, el contrast conceptual:
| Lletra | Nom | Què garanteix |
|---|---|---|
| A | Atomicitat | La transacció s'aplica sencera o no s'aplica res |
| C | Consistència | En acabar, totes les regles de l'esquema es compleixen |
| I | Aïllament | Les transaccions simultànies no s'interfereixen |
| D | Durabilitat | El que s'ha confirmat sobreviu a un tall de corrent |
Exemple a BiblioRed: registrar un préstec insereix una fila a prestecs i canvia exemplars.estat a prestat. Amb ACID, o passen les dues coses o cap. Mai no queda un préstec d'un exemplar que continua figurant com a disponible.
BASE
L'acrònim alternatiu que descriu com es comporten molts sistemes distribuïts:
| Lletra | Nom | Què significa |
|---|---|---|
| BA | Basically Available (bàsicament disponible) | El sistema respon sempre, encara que de vegades amb dades antigues o parcials |
| S | Soft state (estat tou) | L'estat del sistema pot canviar sense que arribi una escriptura nova, perquè les rèpliques s'estan posant al dia |
| E | Eventually consistent (consistència eventual) | Si deixen d'arribar escriptures, totes les rèpliques convergeixen al mateix valor en algun moment |
La paraula clau de BASE és "eventualment", i amaga la pregunta que cal fer sempre: eventualment quan? La teoria no ho diu; la pràctica sí, i en sistemes ben dimensionats sol ser de mil·lisegons a uns pocs segons.
| ACID | BASE | |
|---|---|---|
| Prioritat | Correcció | Disponibilitat |
| Consistència | Immediata i garantida | Eventual |
| Disponibilitat davant fallada | Es pot degradar | Es manté |
| Complexitat per al desenvolupador | Baixa: el gestor se n'encarrega | Alta: el codi gestiona el desfasament |
| Escalat | Més difícil de distribuir | Dissenyat per distribuir-se |
| Dominis típics | Banca, inventari, facturació, préstecs | Catàlegs, continguts socials, telemetria, memòries cau |
Un avís freqüent i necessari: ACID i BASE no són una elecció de producte, són una elecció d'operació. MongoDB et pot donar ACID en una transacció multidocument i BASE en una lectura des d'un secundari. PostgreSQL amb rèpliques asíncrones també lliura lectures eventualment consistents. L'etiqueta correcta es posa a l'operació, no al logotip.
- Què significa "eventualment consistent" per a un lector de BiblioRed
La teoria s'entén millor amb l'escena concreta. La Marta Alsina publica una ressenya de "El mapa del temps" a les 10:25:03.
sequenceDiagram
participant M as Marta (navegador)
participant P as Primari
participant S as Secundari
participant I as Ivan (navegador)
M->>P: 10:25:03.000 escriu la ressenya
P-->>M: 10:25:03.012 confirmat
Note over P,S: la rèplica triga ~40 ms
I->>S: 10:25:03.030 demana les ressenyes del llibre
S-->>I: retorna 811 ressenyes — SENSE la de la Marta
P->>S: 10:25:03.052 el secundari aplica l'oplog
I->>S: 10:25:04.500 l'Ivan recarrega la pàgina
S-->>I: retorna 812 ressenyes — amb la de la Marta
Durant 40 mil·lisegons, dos usuaris del mateix sistema veuen realitats diferents. Això és la consistència eventual, i en aquest cas és completament inofensiva.
Ara la variant que sí que fa mal: la Marta recarrega la seva pròpia pàgina als 20 ms i la petició cau al secundari. La Marta no veu la seva pròpia ressenya. I aquest és l'escenari que genera incidències, perquè l'usuari està segur que ha premut "publicar" i el sistema sembla haver-ho perdut. Pot fins i tot tornar a publicar-la, i llavors n'hi haurà dues.
Aquest cas té nom i solució. S'anomena llegeix les teves pròpies escriptures (read-your-own-writes), i és una de les garanties de sessió que els sistemes distribuïts ofereixen:
| Garantia de sessió | Què assegura |
|---|---|
| Llegeix les teves pròpies escriptures | Un client veu sempre els seus propis canvis |
| Lectures monòtones | Un client mai no veu una versió anterior a una altra que ja va veure |
| Escriptures monòtones | Les escriptures d'un mateix client s'apliquen en el seu ordre |
| Lectures coherents amb l'escriptura | Si una escriptura depenia d'una lectura, l'ordre es respecta |
MongoDB implementa aquestes garanties amb les sessions causalment consistents, activades per defecte als controladors moderns:
const sessio = db.getMongo().startSession({ causalConsistency: true })
const ressenyes = sessio.getDatabase("bibliored").getCollection("ressenyes")
// La Marta publica la seva ressenya
ressenyes.insertOne({
_id: "RES-1813",
material: { material_id: "MAT-0331", titol: "El mapa del temps" },
soci: { soci_id: 14, nom_mostrat: "Marta Alsina" },
puntuacio: 5,
text: "Una novel·la que juga amb el temps sense marejar el lector.",
estat: "publicada",
data: new Date(),
esquema_v: 2
})
// A la MATEIXA sessió: garantit que la veu, encara que llegeixi d'un secundari
ressenyes.countDocuments({ "material.material_id": "MAT-0331" })
sessio.endSession()La regla pràctica de disseny, que val per a qualsevol sistema distribuït:
Després d'una escriptura, llegeix del primari o dins d'una sessió causal. La resta de lectures —llistats, cerques, navegació— poden anar a secundaris sense problema. Distingir aquestes dues rutes al codi és una de les millors inversions d'un sistema distribuït.
- Consistència ajustable: quòrum,
writeConcern i readConcern
writeConcern i readConcernEls sistemes moderns no t'obliguen a triar una vegada per sempre. Ofereixen un comandament per operació.
La idea del quòrum
Amb N rèpliques, es defineix quantes han de confirmar una escriptura (W) i de quantes es llegeix (R). La consistència forta està garantida quan:
Amb N = 3 rèpliques:
| W | R | W + R > N | Comportament |
|---|---|---|---|
| 1 | 1 | 2 > 3 ✗ | Rapidíssim, però pots llegir dades antigues |
| 2 | 2 | 4 > 3 ✓ | Equilibri habitual: majoria a tots dos costats |
| 3 | 1 | 4 > 3 ✓ | Escriptura lenta i fràgil, lectura rapidíssima |
| 1 | 3 | 4 > 3 ✓ | Escriptura rapidíssima, lectura lenta i fràgil |
La intuïció darrere de la fórmula: si escrius en 2 de 3 i llegeixes de 2 de 3, almenys un node és a tots dos conjunts i per tant té la dada nova. L'aritmètica és tot el truc.
writeConcern: quanta confirmació exigeixo en escriure
// Màxima seguretat: la majoria de nodes confirma I la dada és al disc
db.ressenyes.insertOne(
{ _id: "RES-1814", material: { material_id: "MAT-0412" }, puntuacio: 5,
estat: "publicada", data: new Date(), esquema_v: 2 },
{ writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
)// Màxima velocitat: no espero confirmació de ningú
db.activitat.updateOne(
{ _id: "ACT-14-2026-08-02" },
{ $push: { esdeveniments: { t: new Date(), tipus: "fitxa", material_id: "MAT-0331" } },
$inc: { num_esdeveniments: 1 } },
{ upsert: true, writeConcern: { w: 0 } }
)Fixa't en aquest acknowledged: false: el servidor no ha confirmat res. És acceptable per a un esdeveniment d'activitat —si se'n perd un entre 17 milions, no passa res— i inacceptable per a una ressenya que l'usuari es pensa que ha publicat.
| Opció | Significat | Ús a BiblioRed |
|---|---|---|
w: 0 |
Sense confirmació | Esdeveniments d'activitat |
w: 1 |
El primari ha escrit (per defecte) | Operacions normals |
w: "majority" |
La majoria de nodes el té | Ressenyes, moderació |
j: true |
Escrit al registre de diari, sobreviu a un tall | Dades que no es poden perdre |
wtimeout |
Mil·lisegons màxims d'espera | Sempre, amb majority |
Advertiment sobre wtimeout: si expira, l'operació retorna error però pot haver-se aplicat igualment. El temps d'espera limita quant esperes, no el que passa al servidor. L'aplicació ha d'estar preparada per reintentar de manera idempotent.
readConcern: quanta certesa exigeixo en llegir
// Lectura garantida: només dades confirmades per la majoria, mai reversibles
db.ressenyes.find({ "material.material_id": "MAT-0331" })
.readConcern("majority").limit(3)
// Lectura ràpida: el que tingui el node, encara que pugui revertir-se
db.cataleg.find({ etiquetes: "novella historica" })
.readConcern("local").limit(10)| Nivell | Què retorna | Ús |
|---|---|---|
local |
El que té el node, sense garanties (per defecte) | Llistats, cerques |
majority |
Només el confirmat per la majoria; mai no es reverteix | Dades que l'usuari ha modificat |
linearizable |
La lectura més recent possible, amb màxima latència | Molt rar; només per a lectures crítiques |
snapshot |
Una foto coherent en el temps | Dins de transaccions |
El concepte de reversió (rollback) explica per què majority importa. Si el primari accepta una escriptura, cau abans de replicar-la i un altre node és elegit primari, aquella escriptura desapareix: mai no va formar part de la majoria. Un client que l'hagués llegida amb local hauria vist una dada que va deixar d'existir. Amb majority, això no pot passar.
- La frontera s'ha difuminat (I): PostgreSQL amb
jsonb
jsonbAquí hi ha el matís que fa obsoletes gairebé totes les comparatives escrites abans del 2016.
PostgreSQL té el tipus jsonb: JSON binari, indexable i consultable amb operadors propis. És a dir: una base relacional que guarda documents.
CREATE TABLE cataleg (
material_id VARCHAR(12) PRIMARY KEY,
tipus VARCHAR(20) NOT NULL,
titol VARCHAR(200) NOT NULL,
idioma CHAR(2) NOT NULL,
actiu BOOLEAN NOT NULL DEFAULT TRUE,
metadades JSONB NOT NULL DEFAULT '{}'::jsonb,
CONSTRAINT ck_tipus CHECK (tipus IN ('llibre','dvd','revista','audiollibre'))
);
CREATE INDEX idx_cataleg_metadades ON cataleg USING GIN (metadades);
INSERT INTO cataleg (material_id, tipus, titol, idioma, metadades) VALUES
('MAT-0331','llibre','El mapa del temps','es',
'{"isbn":"9788401339097","editorial":"Edicions Vallmar","pagines":612,
"etiquetes":["novella historica","ciencia ficcio"]}'),
('MAT-0802','revista','Vallmar Cultural','ca',
'{"issn":"2604-1188","numero":42,"volum":7,"periodicitat":"mensual",
"etiquetes":["cultura local"]}'),
('MAT-0801','audiollibre','El mapa del temps','es',
'{"narrador":"Àlex Roure","durada_min":860,"format":"MP3",
"etiquetes":["novella historica","audio"]}');Observa el que s'acaba d'aconseguir: columnes fixes per al que és comú i validable —amb la seva PRIMARY KEY, el seu NOT NULL i el seu CHECK— i un document lliure per al que és específic de cada tipus. És exactament el problema de l'apartat 1 de la lliçó 03-01, resolt sense sortir de PostgreSQL.
-- Cercar per una clau dins del JSON: l'operador @> pregunta "conté això?"
SELECT material_id, titol
FROM cataleg
WHERE metadades @> '{"etiquetes":["novella historica"]}'; material_id | titol
-------------+-------------------
MAT-0331 | El mapa del temps
MAT-0801 | El mapa del temps
(2 rows)-- Extreure un camp del JSON i fer-lo servir com a columna
SELECT titol,
metadades ->> 'editorial' AS editorial,
(metadades ->> 'pagines')::int AS pagines
FROM cataleg
WHERE tipus = 'llibre'; titol | editorial | pagines
-------------------+--------------------+---------
El mapa del temps | Edicions Vallmar | 612
(1 row)-- Actualitzar un camp dins del JSON sense reescriure la resta
UPDATE cataleg
SET metadades = jsonb_set(metadades, '{pagines}', '618')
WHERE material_id = 'MAT-0331';Els operadors essencials de jsonb:
| Operador | Què fa | Exemple |
|---|---|---|
-> |
Extreu un camp com a jsonb |
metadades -> 'etiquetes' |
->> |
Extreu un camp com a text | metadades ->> 'editorial' |
#> / #>> |
Extreu per ruta imbricada | metadades #>> '{portada,gran}' |
@> |
Conté aquest fragment? | metadades @> '{"idioma":"ca"}' |
? |
Existeix aquesta clau? | metadades ? 'issn' |
jsonb_set |
Modifica un valor | jsonb_set(m, '{pagines}', '618') |
jsonb_array_elements |
Desplega un array (com $unwind) |
En un FROM lateral |
I l'índex GIN fa que les cerques per contingut del JSON facin servir índex en lloc de recórrer la taula, igual que un índex multiclau de MongoDB.
Què guanya PostgreSQL amb jsonb: flexibilitat d'esquema per a la part heterogènia, sense perdre JOIN, transaccions ACID multitaula, integritat referencial ni SQL.
Què continua fent millor MongoDB: actualitzacions parcials sobre documents grans, arrays profundament imbricats amb operacions riques ($push, $pull, operador posicional), particionat horitzontal integrat, i una ergonomia de treball amb documents que en SQL sempre resulta una mica més cerimoniosa.
Conseqüència pràctica de primer ordre: si el teu únic motiu per adoptar MongoDB és "necessito guardar unes dades amb forma variable", és molt probable que jsonb et resolgui el problema sense afegir una base de dades a la teva arquitectura. I una base de dades menys són còpies de seguretat menys, supervisió menys, actualitzacions menys i un expert menys que necessites contractar.
- La frontera s'ha difuminat (II): transaccions a MongoDB
El moviment invers també va passar. Durant anys, "MongoDB no té transaccions" va ser l'argument definitiu contra ella. Va deixar de ser cert:
- MongoDB 4.0 (2018): transaccions multidocument en conjunts de rèpliques.
- MongoDB 4.2 (2019): transaccions distribuïdes, també entre fragments.
const sessio = db.getMongo().startSession()
const bd = sessio.getDatabase("bibliored")
try {
sessio.startTransaction({
readConcern: { level: "snapshot" },
writeConcern: { w: "majority" }
})
// 1. Publicar la ressenya
bd.ressenyes.insertOne({
_id: "RES-1815",
material: { material_id: "MAT-0331", titol: "El mapa del temps", tipus: "llibre" },
soci: { soci_id: 16, nom_mostrat: "Núria Bastos", sucursal_id: 3 },
puntuacio: 4,
text: "Molt bona ambientació, tot i que el ritme baixa a la part central.",
estat: "publicada", vots_utils: 0, data: new Date(), esquema_v: 2
}, { session: sessio })
// 2. Actualitzar el camp calculat del catàleg (lliçó 03-03)
bd.cataleg.updateOne(
{ _id: "MAT-0331" },
{ $inc: { "valoracio.total_ressenyes": 1,
"valoracio.suma_puntuacions": 4,
"valoracio.distribucio.4": 1 } },
{ session: sessio }
)
sessio.commitTransaction()
print("Transacció confirmada: ressenya i comptadors coherents")
} catch (e) {
sessio.abortTransaction()
print("Transacció avortada: " + e.message)
} finally {
sessio.endSession()
}Això resol el problema de coherència que vam deixar obert a la lliçó 03-03: la ressenya i el comptador del catàleg canvien d'una peça. O totes dues o cap.
Però hi ha lletra petita, i és important:
| Aspecte | Realitat |
|---|---|
| Cost | Notablement més car que una escriptura simple |
| Durada màxima | 60 segons per defecte; més enllà, s'avorta |
| Requisit | Conjunt de rèpliques o clúster particionat; no funciona en un node solt |
| Filosofia | Són una xarxa de seguretat, no l'eina habitual |
La postura del mateix fabricant ho diu bé: si la teva aplicació necessita transaccions constantment, el disseny de documents no és l'adequat —o el domini volia una relacional—. Un bon disseny documental fa que la majoria de les operacions siguin atòmiques per naturalesa, perquè tot el que ha de canviar junt és al mateix document. Just el que buscàvem amb el concepte d'agregat.
- La mateixa consulta als dos mons
Comparem el mateix requisit real: "materials en castellà amb l'etiqueta novella historica, amb la seva puntuació mitjana, ordenats de millor a pitjor, els cinc primers".
-- PostgreSQL amb jsonb
SELECT c.material_id,
c.titol,
(c.metadades ->> 'editorial') AS editorial,
ROUND(AVG(r.puntuacio), 2) AS mitjana,
COUNT(r.ressenya_id) AS total_ressenyes
FROM cataleg c
LEFT JOIN ressenyes r ON r.material_id = c.material_id AND r.estat = 'publicada'
WHERE c.idioma = 'es'
AND c.actiu
AND c.metadades @> '{"etiquetes":["novella historica"]}'
GROUP BY c.material_id, c.titol, c.metadades
HAVING COUNT(r.ressenya_id) >= 3
ORDER BY mitjana DESC NULLS LAST
LIMIT 5; material_id | titol | editorial | mitjana | total_ressenyes
-------------+--------------------------+--------------------+---------+-----------------
MAT-0412 | Els pilars de la Terra | Edicions Vallmar | 4.61 | 1240
MAT-0331 | El mapa del temps | Edicions Vallmar | 4.30 | 812
MAT-0508 | L'ombra del far | Far Editorial | 4.12 | 97
(3 rows)// MongoDB, amb el disseny de la lliçó 03-03
db.cataleg.find(
{
idioma: "es",
actiu: true,
etiquetes: "novella historica",
"valoracio.total_ressenyes": { $gte: 3 }
},
{ titol: 1, "metadades.editorial": 1, "valoracio.mitjana": 1, "valoracio.total_ressenyes": 1 }
).sort({ "valoracio.mitjana": -1 }).limit(5)[
{ _id: 'MAT-0412', titol: 'Els pilars de la Terra',
metadades: { editorial: 'Edicions Vallmar' },
valoracio: { mitjana: 4.61, total_ressenyes: 1240 } },
{ _id: 'MAT-0331', titol: 'El mapa del temps',
metadades: { editorial: 'Edicions Vallmar' },
valoracio: { mitjana: 4.3, total_ressenyes: 812 } },
{ _id: 'MAT-0508', titol: "L'ombra del far",
metadades: { editorial: 'Far Editorial' },
valoracio: { mitjana: 4.12, total_ressenyes: 97 } }
]La comparació honesta, punt per punt:
| Aspecte | PostgreSQL amb jsonb |
MongoDB |
|---|---|---|
| Dades llegides | Creua dues taules i agrega al moment | Un sol document per material |
| Cost amb 2.150 ressenyes per material | Agrega a cada execució | Llegeix un número ja calculat |
| Precisió de la mitjana | Sempre exacta | Depèn que el camp calculat estigui al dia |
| Cost d'escriptura | Cap d'addicional | Cada ressenya actualitza el catàleg |
| Consulta imprevista nova | S'escriu i funciona | Pot exigir redisseny o $lookup |
| Complexitat del codi | Una consulta declarativa | Una consulta simple + manteniment del camp calculat |
L'important no és quina guanya. És que totes dues són bones solucions amb equilibris diferents: PostgreSQL calcula en llegir i garanteix exactitud; MongoDB calcula en escriure i garanteix velocitat de lectura. L'elecció depèn de la proporció entre lectures i escriptures i de quanta exactitud instantània exigeixi el negoci.
I fixa't en una cosa reveladora: el camp calculat també es pot fer a PostgreSQL —amb una vista materialitzada o una columna mantinguda per disparador—, i el càlcul al vol també es pot fer a MongoDB amb una canalització d'agregació. Les tècniques de modelatge són transversals; el que canvia és què et dóna el motor de sèrie.
- Guia de decisió
Les vuit preguntes, en ordre
- Com són les meves dades? Homogènies i molt relacionades → relacional. Heterogènies, imbricades, amb forma variable → documental (o
jsonb). - Conec les meves consultes? Si no les puc enumerar, o si canviaran de manera impredictible → relacional. El modelatge documental exigeix conèixer-les.
- Necessito transaccions entre entitats com a norma? Sí → relacional. Ocasionalment → qualsevol de les dues.
- Quanta integritat exigeix el negoci? Si una dada incoherent té conseqüències legals o econòmiques → relacional, amb les garanties al servidor.
- Quin volum i quin creixement tinc? Menys d'uns centenars de gigabytes i creixement previsible → cap en un node, i això descarta l'argument de l'escalat.
- Amb quina freqüència canvia l'esquema? Cada setmana → documental o
jsonb. Cada sis mesos →ALTER TABLEno és un problema. - Què sap operar el meu equip? És una pregunta tècnica de primer ordre, no un detall de recursos humans.
- Necessito informes i anàlisi? Si l'ús principal són quadres de comandament i analistes → relacional, perquè les eines parlen SQL.
El consell per defecte
Comença per la base relacional llevat que tinguis un motiu clar per no fer-ho, i sigues capaç d'escriure aquest motiu en una frase.
No és conservadorisme. És que la relacional és el model més general: serveix raonablement bé per a gairebé tot, té l'ecosistema més gran, el talent més disponible, i —amb jsonb— ha absorbit bona part de la flexibilitat que va motivar el moviment NoSQL. Començar allà i migrar el que no encaixi és molt més barat que el camí invers.
El motiu de BiblioRed, escrit en una frase, compleix aquest llistó: "el catàleg té metadades diferents per tipus de material, el servei de ressenyes canvia de forma cada mes i l'activitat és un volum alt d'escriptures que no necessita integritat referencial".
flowchart TD
A["Nou sistema o subsistema"] --> B{"Transaccions entre entitats<br/>o integritat crítica?"}
B -->|Sí| REL["RELACIONAL<br/>PostgreSQL"]
B -->|No| C{"Conec bé<br/>les meves consultes?"}
C -->|No| REL
C -->|Sí| D{"Dades heterogènies<br/>o esquema molt canviant?"}
D -->|No| REL
D -->|Sí| E{"Cap en un node<br/>i la resta del sistema<br/>ja és relacional?"}
E -->|Sí| JB["RELACIONAL + jsonb<br/>una base menys per operar"]
E -->|No| DOC["DOCUMENTAL<br/>MongoDB"]
- Mites desmuntats
Mite 1: "NoSQL és més ràpid".
Més ràpid en què? Una lectura d'un agregat complet per la seva clau és més ràpida a MongoDB que reconstruir-lo amb cinc JOIN. Una agregació complexa sobre dades normalitzades sol ser més ràpida a PostgreSQL. I la majoria dels problemes de rendiment reals es resolen amb un índex, no amb un canvi de motor. La velocitat depèn de l'encaix entre el disseny i la consulta, no del logotip.
Mite 2: "NoSQL no té esquema".
Té esquema. És al codi de l'aplicació en lloc de al servidor. La diferència és qui el comprova i quan falla: a l'INSERT i de manera automàtica, o a la lectura i sis mesos després. Un esquema no vigilat no és absència d'esquema: és esquema sense garanties.
Mite 3: "Les relacionals no escalen". Escalen extraordinàriament bé en vertical, i una sola instància de PostgreSQL ben afinada gestiona centenars de gigabytes i milers de transaccions per segon. El que no escala amb facilitat és l'escriptura distribuïda en diversos nodes, que és una necessitat molt menys freqüent del que es creu. La majoria dels sistemes que "necessiten escalar" necessiten en realitat un índex i una consulta més ben escrita.
Mite 4: "MongoDB perd dades".
Va ser una crítica legítima cap al 2011, quan la configuració per defecte no esperava confirmació d'escriptura. Des de fa anys el valor per defecte és w: 1, i amb w: "majority" i j: true les garanties són comparables a les d'una relacional amb rèpliques síncrones. Les dades es perden per configuracions inadequades, no pel producte.
Mite 5: "Amb NoSQL no cal dissenyar". És el mite més car. La lliçó 03-03 sencera existeix perquè cal dissenyar més, no menys: sense normalització ni claus foranes que corregeixin els errors, un mal model documental no té xarxa de seguretat.
Mite 6: "Cal triar-ne una i fer-la servir per a tot". Fals des de fa temps, i és precisament el tema de l'apartat següent.
- Persistència poliglota: l'arquitectura de BiblioRed
El terme el va encunyar Martin Fowler el 2011 per analogia amb la programació poliglota, i descriu una idea senzilla:
Persistència poliglota: fer servir deliberadament diversos motors d'emmagatzematge dins d'un mateix sistema, triant per a cada tipus de dada el que la gestiona millor, en lloc de forçar-ho tot en un únic motor.
És exactament on ha acabat BiblioRed:
flowchart TD
APP["Portal i aplicació de gestió<br/>de BiblioRed"]
APP --> PG["PostgreSQL — biblioredb<br/>sucursals · socis · autors · llibres<br/>exemplars · prestecs · reserves<br/>ACID, integritat, informes"]
APP --> MG["MongoDB — bibliored<br/>cataleg · ressenyes · activitat<br/>esquema flexible, agregats"]
PG -->|sincronització horària<br/>de disponibilitat| MG
MG -.->|procés nocturn:<br/>recomanacions precalculades| MG
| Necessitat | Motor | Per què |
|---|---|---|
| Préstecs, socis, exemplars, reserves | PostgreSQL | Transaccions, integritat referencial, consultes impredictibles, informes de gestió |
| Catàleg enriquit | MongoDB | Metadades heterogènies per tipus de material |
| Ressenyes | MongoDB | Agregat autocontingut, esquema que canvia cada mes |
| Registre d'activitat | MongoDB | Volum alt d'escriptures sense necessitat d'integritat |
| Recomanacions | Precàlcul → MongoDB | 12.000 socis no justifiquen desplegar Neo4j |
| Memòria cau i sessions | Ajornat | Encara no hi ha un problema de latència per resoldre |
El preu de la persistència poliglota
Convé dir-ho amb claredat, perquè les presentacions d'arquitectura gairebé mai no ho diuen:
- Coherència entre motors: la disponibilitat viu a PostgreSQL i es copia a MongoDB. Aquesta còpia es pot desfasar, i algú ho ha de vigilar.
- Sense transaccions entre motors: no hi ha manera que una escriptura a PostgreSQL i una altra a MongoDB siguin atòmiques entre si. Hi ha patrons per mitigar-ho, però cap no és gratis.
- Doble operació: dos productes per actualitzar, dos esquemes de còpies de seguretat i restauració, dos sistemes de permisos, dos conjunts de mètriques.
- Doble competència a l'equip: cal saber dissenyar i afinar tots dos.
- Informes que creuen motors: qualsevol anàlisi que combini préstecs i ressenyes exigeix una capa d'integració, un magatzem analític o un procés d'extracció.
La regla que resumeix el criteri professional:
Cada motor que afegeixes ha de justificar el seu cost d'operació amb un avantatge que no puguis aconseguir d'una altra manera. Dos motors ben triats i ben operats són una bona arquitectura. Cinc motors triats per curiositat són un deute tècnic amb nòmina pròpia.
I per això BiblioRed s'ha quedat en dos: Redis, Cassandra i Neo4j van quedar ajornats no perquè no encaixessin —encaixen molt bé, com vam veure a 03-02— sinó perquè el seu avantatge no compensa encara el cost d'operar-los.
Aquesta arquitectura es desenvoluparà per complet a la lliçó 08-03, Cas d'Estudi: Persistència Poliglota, amb el flux de dades entre motors, els processos de sincronització, la gestió de fallades i l'evolució prevista del sistema. Abans, les lliçons 08-01 i 08-02 analitzaran per separat un cas relacional i un cas documental.
Errors Habituals i Consells
Error 1: repetir "tria'n dues de tres" en parlar de CAP. És la formulació incorrecta i qui l'escolta se'n va amb una idea equivocada. La partició no es tria: passa. L'elecció és entre C i A quan hi ha partició, i la resta del temps es tenen totes dues.
Error 2: confondre la C de CAP amb la C d'ACID. La C de CAP és "tots els nodes veuen el mateix". La C d'ACID és "les regles de l'esquema es compleixen en acabar la transacció". Mateixa lletra, conceptes diferents.
Error 3: llegir d'un secundari just després d'escriure. És la causa número u d'incidències del tipus "el sistema ha perdut el meu canvi". Solució: llegir del primari després d'escriure, o fer servir sessions causalment consistents.
Error 4: fer servir w: 0 per a dades que importen.
acknowledged: false significa literalment que ningú no ha confirmat res. Només és acceptable per a dades que et pots permetre perdre.
Error 5: decidir amb comparatives del 2013.
jsonb i les transaccions multidocument van canviar el terreny. Un argument del tipus "MongoDB no té transaccions" o "PostgreSQL no pot amb dades flexibles" està deu anys desactualitzat.
Error 6: adoptar persistència poliglota per elegància. Cada motor addicional multiplica el cost d'operació. Si dubtes, no l'afegeixis: sempre el pots afegir després amb dades que ho justifiquin.
Consell 1: escriu la decisió, no només la prenguis. Un document de mitja pàgina —context, opcions considerades, decisió, motiu, conseqüències acceptades— per cada elecció de motor. D'aquí a dos anys, quan algú pregunti "per què MongoDB?", aquesta mitja pàgina val el seu pes en or.
Consell 2: ajusta la consistència per operació, no per sistema.
És la lliçó de PACELC portada al codi: w: "majority" per publicar una ressenya, w: 0 per registrar un esdeveniment de navegació. La mateixa base de dades, dues garanties diferents, cadascuna justificada.
Consell 3: prova jsonb abans d'afegir MongoDB.
Si ja tens PostgreSQL i el teu problema és només la flexibilitat d'esquema, una tarda de proves amb jsonb i un índex GIN pot estalviar-te un motor sencer.
Consell 4: mesura la latència de replicació en producció. "Eventualment consistent" no diu res útil fins que saps que al teu sistema són 40 ms. Amb aquest número pots decidir quines lectures van al primari; sense ell, només pots suposar.
Exercicis
Exercici 1
Per a cadascuna d'aquestes quatre operacions de BiblioRed, indica: (a) quin writeConcern o readConcern faries servir, (b) si la lectura hauria d'anar al primari o pot anar a un secundari, i (c) si és un escenari on prioritzaries consistència o disponibilitat davant una partició de xarxa. Justifica cada resposta.
- Registrar la devolució d'un exemplar amb un recàrrec de 3,50 €.
- Mostrar la llista de ressenyes d'"Els pilars de la Terra" a un visitant anònim.
- Registrar que un soci ha obert la fitxa d'un material.
- Publicar una ressenya i, tot seguit, redirigir el lector a la pàgina del material on l'ha de veure.
Exercici 2
Un company afirma: "Migrarem tot BiblioRed a MongoDB perquè NoSQL escala millor i és més ràpid, i així tenim una sola base de dades." Redacta una resposta tècnica i respectuosa que abordi els tres arguments —escalat, velocitat i unificació— amb els conceptes d'aquesta lliçó, i proposa una alternativa concreta.
Exercici 3
BiblioRed vol afegir llistes de lectura personals: cada soci pot crear llistes amb nom, descripció, visibilitat (privada/pública) i entre 5 i 200 materials, i pot seguir llistes públiques d'altres socis. Consultes previstes: veure les meves llistes; veure una llista amb les dades dels seus materials; cercar llistes públiques per etiqueta; comptar seguidors d'una llista.
Decideix on viu aquesta funcionalitat —PostgreSQL, PostgreSQL amb jsonb, o MongoDB— recorrent les vuit preguntes de l'apartat 10, i escriu l'esquema o el document resultant.
Solucions
Solució 1
1. Devolució amb recàrrec de 3,50 €.
(a) writeConcern: { w: "majority", j: true }. Hi ha diners pel mig: l'escriptura ha d'estar confirmada per la majoria i al disc, perquè sobrevisqui a una caiguda del primari i no pugui revertir-se. (b) Lectura del primari, amb readConcern: "majority". (c) Consistència. Davant partició, és preferible que el taulell mostri "servei no disponible, torneu-ho a provar d'aquí a un minut" a que registri un recàrrec que després desapareix o es duplica. A més, aquesta operació en realitat viu a PostgreSQL: toca prestecs i exemplars de manera atòmica i és un cas ACID de manual.
2. Llista de ressenyes per a un visitant anònim.
(a) readConcern: "local". (b) Secundari, sense dubtar-ho: és la ruta de lectura més freqüent del portal i descarregar el primari és exactament per al que existeixen els secundaris. (c) Disponibilitat. Que un visitant vegi 811 ressenyes en lloc de 812 durant uns mil·lisegons no té cap conseqüència; que la pàgina del catàleg retorni un error, sí.
3. Registrar l'obertura d'una fitxa.
(a) writeConcern: { w: 0 }. És un esdeveniment de telemetria entre 17 milions anuals; perdre'n algun no altera cap informe i esperar confirmació penalitzaria la latència de la navegació (requisit C6: menys de 10 ms). (b) No s'hi aplica: és escriptura pura. (c) Disponibilitat, absolutament. Davant partició, el correcte és descartar l'esdeveniment en silenci abans que degradar l'experiència de navegació o encuar dades indefinidament.
4. Publicar una ressenya i redirigir a la pàgina del material.
(a) Escriptura amb writeConcern: { w: "majority" } —l'usuari es pensa que l'ha publicada i no pot desaparèixer— i lectura posterior amb readConcern: "majority". (b) Primari, o dins d'una sessió causalment consistent. És el cas canònic de llegeix les teves pròpies escriptures: si la lectura posterior cau en un secundari endarrerit, la Marta no veu la seva pròpia ressenya, es pensa que s'ha perdut i probablement la publica una altra vegada. (c) Consistència, però per una raó de percepció més que de correcció: el cost d'un usuari que duplica el seu contingut és més gran que el d'un missatge d'error momentani.
El que il·lustra el conjunt: quatre operacions del mateix sistema amb quatre configuracions diferents. És l'aplicació pràctica de PACELC.
Solució 2
Gràcies per plantejar-ho; val la pena analitzar els tres arguments per separat, perquè crec que un d'ells apunta a alguna cosa real i els altres dos no.
Sobre l'escalat. És cert que MongoDB escala millor en horitzontal. La pregunta és si ho necessitem: BiblioRed té 12.000 socis, 40.000 exemplars i uns 17 milions d'esdeveniments d'activitat l'any. Això cap folgadament en una sola instància de PostgreSQL amb maquinari corrent, i de fet el nostre coll d'ampolla actual no és el volum. Migrar per un escalat que no farem servir ens deixa el cost de la migració sense l'avantatge.
Sobre la velocitat. "Més ràpid" depèn de l'operació. MongoDB és més ràpid llegint un agregat complet per la seva clau; PostgreSQL és més ràpid agregant dades normalitzades, que és exactament el que fan els nostres informes mensuals de préstecs per sucursal. A més, la majoria dels problemes de rendiment que hem tingut es van resoldre amb un índex. Abans de canviar de motor, proposo mesurar amb
EXPLAINles tres consultes més lentes: si el problema és d'índexs, la migració no ho arregla.Sobre unificar en una sola base. Aquest és l'argument amb més força: dos motors costen el doble de còpies de seguretat, supervisió i competència a l'equip. Però unificar a MongoDB ens costaria alguna cosa més cara: perdríem la integritat referencial dels préstecs i les transaccions ACID que garanteixen que un préstec i l'estat del seu exemplar canviïn d'una peça. Això avui ens ho dóna el servidor de franc; a MongoDB ho hauríem d'escriure i mantenir nosaltres, i cada error que se'ns escapi acaba al taulell.
Proposta alternativa. Si l'objectiu real és simplificar, explorem el camí invers: moure el catàleg i les ressenyes a PostgreSQL fent servir columnes
jsonbamb índexs GIN. Ens donaria la flexibilitat d'esquema que buscàvem —metadades diferents per tipus de material, camps nous senseALTER TABLE— sense renunciar aJOIN, transaccions ni integritat, i amb una sola base de dades, que és el que volies. Proposo una prova de concepte d'una setmana amb les dades reals del catàleg i una comparació mesurada de les cinc consultes més freqüents. Sijsonbno dóna la talla, tindrem un argument sòlid per mantenir MongoDB, escrit i amb números.
Solució 3
Recorregut de les vuit preguntes
| # | Pregunta | Resposta per a les llistes de lectura |
|---|---|---|
| 1 | Com són les dades? | Homogènies: totes les llistes tenen la mateixa estructura. No és un cas d'heterogeneïtat |
| 2 | Conec les meves consultes? | Sí, les quatre estan enumerades |
| 3 | Transaccions entre entitats? | Molt poc: afegir un material a una llista afecta la llista i un comptador |
| 4 | Integritat? | Mitjana-alta: una llista que apunta a materials inexistents es veu fatal al portal |
| 5 | Volum? | 12.000 socis × poques llistes × fins a 200 materials. Petit |
| 6 | Canvia l'esquema? | Poc: és una funcionalitat de forma estable |
| 7 | Què opera l'equip? | Els dos motors; no desempata |
| 8 | Informes? | Algun: llistes més seguides, materials més llistats |
Decisió: MongoDB. I no per les preguntes 1 i 6, que apuntaven a relacional, sinó per l'agregat. Una llista de lectura compleix les tres condicions de la lliçó 03-03: es llegeix sencera (la consulta principal és "veure una llista amb els seus materials"), s'escriu junta, i té una arrel clara. Amb un topall de 200 materials és un cas d'un a pocs de manual, i amb referència estesa (títol, tipus i portada) la consulta principal es resol en un sol accés, sense JOIN amb cataleg.
L'argument decisiu és la pregunta 4 combinada amb el patró: la integritat importa, però és integritat de visualització, no de negoci. Si un material es retira del catàleg i queda en una llista, la fitxa mostra "material no disponible" i no passa res greu. Això no exigeix una FOREIGN KEY.
Els seguidors van fora: creixen sense topall conegut (una llista popular pot tenir-ne milers) i es consulten per separat ("llistes que segueixo"). És un a molts, així que col·lecció pròpia amb un comptador calculat a la llista.
{
"_id": "LST-00471",
"esquema_v": 1,
"propietari": {
"soci_id": 14,
"nom_mostrat": "Marta Alsina",
"sucursal_id": 1
},
"nom": "Novel·la històrica per a l'estiu",
"descripcio": "El que m'enduc a la platja aquest any. Res per sota de les 400 pàgines.",
"visibilitat": "publica",
"etiquetes": ["novella historica", "estiu", "recomanacions"],
"materials": [
{ "material_id": "MAT-0331", "titol": "El mapa del temps",
"tipus": "llibre", "portada": "/img/catalogo/0331-s.webp",
"afegit": "2026-06-02T18:20:00Z", "nota": "Comença per aquest." },
{ "material_id": "MAT-0412", "titol": "Els pilars de la Terra",
"tipus": "llibre", "portada": "/img/catalogo/0412-s.webp",
"afegit": "2026-06-02T18:22:00Z", "nota": null },
{ "material_id": "MAT-0801", "titol": "El mapa del temps",
"tipus": "audiollibre", "portada": "/img/catalogo/0801-s.webp",
"afegit": "2026-06-14T09:05:00Z", "nota": "Versió narrada, per al cotxe." }
],
"num_materials": 3,
"num_seguidors": 47,
"creada": "2026-06-02T18:18:00Z",
"actualitzada": "2026-06-14T09:05:00Z"
}{
"_id": "SEG-LST-00471-16",
"llista_id": "LST-00471",
"soci_id": 16,
"nom_mostrat": "Núria Bastos",
"des_de": "2026-06-20T11:40:00Z",
"avisos": true
}Índexs: { "propietari.soci_id": 1, actualitzada: -1 } per a "les meves llistes"; { visibilitat: 1, etiquetes: 1, num_seguidors: -1 } per a la cerca de llistes públiques; i a seguidors_llista, { soci_id: 1 } i { llista_id: 1 }.
Validació ($jsonSchema): maxItems: 200 a materials —converteix el topall de disseny en regla comprovada pel servidor— i enum: ["privada","publica"] a visibilitat.
Nota final d'honestedat. Aquesta funcionalitat també es resoldria perfectament a PostgreSQL amb dues taules i una clau forana, amb l'avantatge afegit que la integritat la garantiria el servidor. La decisió s'inclina cap a MongoDB perquè el catàleg ja hi és —la referència estesa es resol dins del mateix motor— i perquè el patró d'agregat encaixa de manera natural. Si el catàleg visqués a PostgreSQL, la resposta correcta seria la contrària. La decisió d'un subsistema depèn d'on viu la resta del sistema, i aquest és un criteri que cap taula comparativa no recull.
Conclusió
Aquesta lliçó ha posat les dues meitats del curs cara a cara i ha tancat la teoria pendent:
- La comparació dimensió per dimensió mostra que no hi ha guanyador: hi ha equilibris diferents. Les dues files més infravalorades són el talent disponible i la capacitat de respondre a consultes imprevistes, totes dues a favor del model relacional.
- El teorema CAP no és "tria'n dues de tres". La partició no es tria, passa; l'elecció real és entre consistència i disponibilitat quan hi ha partició, i mentre no n'hi ha es tenen totes dues. MongoDB és CP, Cassandra és AP per defecte i ajustable.
- PACELC afegeix el que falta: fins i tot sense partició cal triar entre latència i consistència, perquè sincronitzar rèpliques costa temps. La consistència no és gratis ni quan tot funciona.
- ACID garanteix correcció immediata; BASE —bàsicament disponible, estat tou, eventualment consistent— garanteix disponibilitat i convergència. No són etiquetes de producte sinó d'operació: la mateixa base pot oferir totes dues.
- La consistència eventual significa, en concret, que durant unes desenes de mil·lisegons dos usuaris poden veure realitats diferents. Inofensiu gairebé sempre, tret de llegeix les teves pròpies escriptures, que es resol llegint del primari o amb sessions causalment consistents.
- La consistència és ajustable per operació: la regla del quòrum
W + R > N,writeConcern(w: 0,w: 1,w: "majority",j: true,wtimeout) ireadConcern(local,majority,linearizable,snapshot). - La frontera s'ha difuminat. PostgreSQL guarda documents amb
jsonb, els indexa amb GIN i els consulta amb@>,->>ijsonb_set, sense renunciar aJOIN, transaccions ni integritat. MongoDB té transaccions multidocument des del 2018, amb l'advertiment que són una xarxa de seguretat i no l'eina habitual. Decidir amb arguments del 2013 és la manera més comuna d'equivocar-se. - La guia de decisió són vuit preguntes —forma de les dades, previsibilitat de les consultes, transaccions, integritat, volum, velocitat de canvi de l'esquema, competència de l'equip i necessitats analítiques— i un consell per defecte: comença per la relacional i sigues capaç d'escriure en una frase el motiu de no fer-ho.
- Sis mites desmuntats: NoSQL no és "més ràpid" en abstracte, sí que té esquema (sense vigilància), les relacionals sí que escalen (en vertical), MongoDB no perd dades amb la configuració adequada, amb NoSQL cal dissenyar més, i no cal triar un sol motor.
- La persistència poliglota és fer servir deliberadament diversos motors, cadascun per al que gestiona millor. Té un preu real —coherència entre motors, absència de transaccions creuades, doble operació, doble competència, informes que creuen sistemes— i per això cada motor afegit ha de justificar el seu cost amb un avantatge que no es pugui aconseguir d'una altra manera.
Amb això acaba el mòdul 3. Hem entès per què va néixer NoSQL i a què renuncia a canvi de què, hem recorregut les seves quatre famílies amb operació real, hem après a dissenyar documents a partir de les consultes —amb els seus patrons, els seus antipatrons i les seves garanties recuperables— i hem comparat els dos mons amb els conceptes que governen qualsevol sistema distribuït. BiblioRed n'ha sortit amb una arquitectura completa i, sobretot, justificada: PostgreSQL per al que exigeix integritat i transaccions, MongoDB per al que exigeix flexibilitat i volum, i tres motors més conscientment ajornats amb un criteri escrit de quan deixarien d'estar-ho. Al mòdul 4, Disseny d'Esquemes, tornem al principi de tot amb una mirada més exigent: abans d'escriure un CREATE TABLE o un document, cal dissenyar. Veurem els principis del bon disseny d'esquemes, els diagrames entitat-relació com a llenguatge per pensar un domini abans de tocar el teclat, com es transformen aquests diagrames en esquemes relacionals, i com es trien els tipus de dades i les restriccions que faran que l'esquema resisteixi el pas del temps.
Fonaments de Bases de Dades
Mòdul 1: Introducció a les Bases de Dades
- Conceptes Bàsics de Bases de Dades
- Tipus de Bases de Dades
- Història i Evolució de les Bases de Dades
- Sistemes Gestors de Bases de Dades i Arquitectura
Mòdul 2: Bases de Dades Relacionals
- Model Relacional
- Llenguatge SQL
- Operacions Bàsiques en SQL
- Consultes Multitaula: JOIN i Subconsultes
- Agregació i Agrupació de Dades
- Integritat Referencial
Mòdul 3: Bases de Dades No Relacionals
- Introducció a NoSQL
- Tipus de Bases de Dades NoSQL
- Modelatge de Dades en NoSQL
- Comparació entre Bases de Dades Relacionals i No Relacionals
Mòdul 4: Disseny d'Esquemes
- Principis de Disseny d'Esquemes
- Diagrames Entitat-Relació (ER)
- Transformació de Diagrames ER a Esquemes Relacionals
- Tipus de Dades i Restriccions
Mòdul 5: Normalització
Mòdul 6: Transaccions, Rendiment i Seguretat
- Transaccions i Propietats ACID
- Concurrència i Nivells d'Aïllament
- Índexs i Optimització de Consultes
- Seguretat, Permisos i Còpies de Seguretat
Mòdul 7: Exercicis Pràctics
- Exercicis de SQL
- Exercicis de Disseny d'Esquemes
- Exercicis de Normalització
- Exercicis de Consultes Avançades i Transaccions
Mòdul 8: Casos d'Estudi
- Cas d'Estudi: Base de Dades Relacional
- Cas d'Estudi: Base de Dades No Relacional
- Cas d'Estudi: Persistència Poliglota
