Gairebé tot el que fem servir cada dia a Internet —cercar una web, pagar amb targeta, mirar una sèrie— funciona gràcies a desenes o milers d'ordinadors que cooperen sense que ens n'adonem. Aquesta lliçó estableix el vocabulari i les idees fonamentals sobre les quals es construeix la resta del curs: què és exactament un sistema distribuït, per què se'n construeixen, quines característiques el distingeixen d'un programa "normal" i de quines peces està fet. A més, presentarem el cas pràctic que ens acompanyarà durant tot el curs, Quilòmetre Zero, un marketplace de productors locals que comença com una aplicació senzilla en un únic servidor i que, per culpa del seu propi èxit, haurà de convertir-se en una plataforma distribuïda.

Entendre bé aquests fonaments és important perquè els sistemes distribuïts no són simplement "diversos ordinadors": introdueixen una manera de pensar diferent, en què la fallada parcial, la concurrència i l'absència d'una veritat única són la norma i no l'excepció.

Contingut

  1. Què és un sistema distribuït?
  2. Per què existeixen els sistemes distribuïts?
  3. Característiques clau
  4. Transparència: amagar la distribució
  5. Components d'un sistema distribuït
  6. Exemples quotidians
  7. Presentació de Quilòmetre Zero: el monòlit inicial
  8. Exemple de codi: simulant nodes que fallen de manera independent
  9. Errors comuns i consells
  10. Exercicis
  11. Conclusió

  1. Què és un sistema distribuït?

Hi ha moltes definicions, però dues són especialment útils perquè capturen aspectes complementaris:

  • Definició formal (Andrew Tanenbaum): "Un sistema distribuït és una col·lecció d'ordinadors independents que apareix davant dels seus usuaris com un únic sistema coherent." Aquesta definició subratlla l'objectiu: cooperació amb aparença d'unitat. L'usuari d'un cercador no sap (ni li importa) quantes màquines han participat en la seva consulta.
  • Definició irònica (Leslie Lamport): "Un sistema distribuït és aquell en què la fallada d'una màquina que ni tan sols sabies que existia pot deixar el teu propi ordinador inutilitzable." Aquesta definició subratlla el problema: la dependència oculta i la fallada parcial. En un sistema distribuït, el teu programa pot deixar de funcionar per causes completament alienes a ell.

Totes dues definicions són certes alhora, i la tensió entre elles és l'essència d'aquesta disciplina: volem que moltes màquines semblin una de sola, però cada màquina pot fallar per separat.

Convé fixar una distinció senzilla:

Sistema On s'executa Com es comunica Mode de fallada habitual
Centralitzat (un procés) Una màquina Crides a funcions en memòria Tot o res: el procés funciona o cau
Concurrent en una màquina Una màquina, diversos fils o processos Memòria compartida, canonades Tot o res (amb condicions de carrera)
Distribuït Diverses màquines Missatges per xarxa Parcial: unes parts fallen i d'altres no

La diferència essencial no és la quantitat d'ordinadors, sinó que l'única manera de comunicar-se és enviant missatges per una xarxa que pot retardar-los o perdre'ls, i que cada node pot fallar mentre els altres continuen funcionant.

  1. Per què existeixen els sistemes distribuïts?

Ningú no construeix un sistema distribuït per gust: són més difícils de dissenyar, provar i operar que un programa en una sola màquina. Es construeixen perquè hi ha motius que ho justifiquen:

  1. Escala. Una sola màquina té un límit de CPU, memòria, disc i connexions de xarxa. Quan la demanda supera aquest límit, l'única sortida és repartir la feina entre diverses màquines.
  2. Geografia. Els usuaris estan repartits pel món, i la velocitat de la llum imposa una latència mínima. Servir cada usuari des d'un centre de dades proper exigeix tenir màquines en diversos llocs. A més, algunes fonts de dades estan inherentment distribuïdes (sensors, dispositius mòbils, sucursals).
  3. Disponibilitat i tolerància a fallades. Si el servei corre en una sola màquina, aquesta màquina és un punt únic de fallada. Amb diverses màquines, el sistema pot continuar funcionant encara que alguna caigui.
  4. Cost. A partir d'una certa mida, moltes màquines modestes són molt més barates que una única màquina enorme (i una màquina enorme, al final, també té un límit). A més, permet pagar només per la capacitat que es fa servir.
  5. Organització. Les empreses grans tenen molts equips; que cada equip sigui propietari de la seva part del sistema, amb el seu propi cicle de desplegament, requereix que aquestes parts estiguin separades.

A la lliçó 01-03 analitzarem en detall els avantatges i els costos de distribuir. De moment n'hi ha prou amb tenir clar que la distribució sempre és una resposta a un problema concret, mai una finalitat en si mateixa.

  1. Característiques clau

Tot sistema distribuït, independentment de la seva mida, comparteix tres característiques fonamentals. Són conseqüències directes de "diverses màquines que es comuniquen per missatges".

3.1 Concurrència

Els nodes s'executen simultàniament i de manera independent. A Quilòmetre Zero, mentre l'Anna afegeix un formatge al cistell des de Barcelona, en Marc paga una comanda des de València i un repartidor envia la seva posició des de Saragossa. No hi ha cap "torn": tot passa alhora, i els recursos compartits (per exemple, l'estoc d'un producte) s'han de coordinar explícitament.

3.2 Absència de rellotge global

Cada màquina té el seu propi rellotge, i aquests rellotges mai no estan perfectament sincronitzats. Si el node A registra un esdeveniment a les 10:00:00.120 i el node B un altre a les 10:00:00.118, no podem afirmar amb seguretat quin va passar abans: la diferència pot ser més petita que l'error de sincronització. Això té conseqüències profundes per a qualsevol cosa que depengui de "qui va ser primer". Ho estudiarem a fons a la lliçó 01-05.

3.3 Fallades independents

En un programa monolític, si alguna cosa falla, falla tot, i normalment ho detectes de seguida (una excepció, un procés mort). En un sistema distribuït:

  • Un node pot caure mentre la resta continua en marxa (fallada parcial).
  • La xarxa pot perdre missatges, duplicar-los o retardar-los.
  • Un node pot anar tan lent que sigui indistingible d'un de caigut.
  • I el pitjor: no sempre hi ha manera de saber què ha passat. Si envies una petició i no reps resposta, la petició no va arribar? Va arribar, es va processar i es va perdre la resposta? El node està caigut o simplement sobrecarregat?

Aquesta incertesa és l'arrel de la majoria de dificultats del curs. Dissenyar sistemes distribuïts consisteix, en bona part, a assumir que la fallada parcial és inevitable i decidir com comportar-se quan passa.

  1. Transparència: amagar la distribució

Recordem la definició de Tanenbaum: el sistema ha d'aparèixer com un de sol. Cadascun dels aspectes de la distribució que s'amaguen a l'usuari (o al programador) rep el nom de transparència. Les més importants:

Transparència Què amaga Exemple a Quilòmetre Zero
D'accés Les diferències en la representació de dades i en com s'accedeix a un recurs local o remot El codi que consulta el catàleg s'escriu igual tant si el catàleg és al mateix procés com si és en una altra màquina
D'ubicació On és físicament un recurs L'app crida cataleg.cercar("formatge") sense saber en quin servidor ni en quina ciutat s'executa
De replicació Que existeixen diverses còpies del mateix recurs El client veu "el catàleg", encara que hi hagi cinc còpies servint-lo
De fallada Que un component ha fallat i s'ha recuperat (o s'ha substituït) Si una còpia del catàleg cau, les peticions van a una altra sense que l'Anna se n'adoni
D'escalat Que el sistema creix o decreix en mida Afegir servidors durant una campanya no canvia la manera de fer servir la plataforma
De concurrència Que diversos usuaris comparteixen el mateix recurs L'Anna i en Marc consulten el mateix estoc sense interferir-se
De migració Que un recurs es mou de lloc mentre es fa servir Una sessió passa d'un servidor a un altre sense tallar la connexió

És important entendre que la transparència total és un ideal, no una realitat. Amagar completament la distribució té costos (latència, complexitat) i, en molts casos, és contraproduent: si el programador no sap que una crida viatja per la xarxa, escriurà codi que assumeix que mai no falla i que sempre respon ràpid. Veurem exactament aquest problema a la lliçó 01-04, dedicada a les fal·làcies de la computació distribuïda.

  1. Components d'un sistema distribuït

Podem descriure qualsevol sistema distribuït en funció de quatre tipus de peces:

flowchart LR
    subgraph Node_A["Node A"]
        A1[Aplicació]
        A2[Middleware]
        A3[Sistema operatiu]
    end
    subgraph Node_B["Node B"]
        B1[Aplicació]
        B2[Middleware]
        B3[Sistema operatiu]
    end
    A2 <-- "Protocols (missatges)" --> B2
    A3 <-- "Xarxa" --> B3
  • Nodes. Cadascuna de les màquines (físiques o virtuals, contenidors inclosos) que executen una part del sistema. Un node té la seva pròpia CPU, memòria, disc i rellotge. Entre els nodes no hi ha memòria compartida: només missatges.
  • Xarxa. El mitjà pel qual viatgen els missatges: cables, commutadors, encaminadors, enllaços sense fil, Internet. La xarxa és un component més del sistema, amb les seves pròpies fallades i limitacions, encara que sovint s'oblida.
  • Protocols. Les regles que defineixen com s'estructuren i s'intercanvien els missatges: format, ordre, què fer davant d'un error. Des dels protocols de transport (TCP, UDP) fins als d'aplicació (HTTP, gRPC, MQTT), s'estudien al Mòdul 2.
  • Middleware. La capa de programari que se situa entre les aplicacions i el sistema operatiu/xarxa i que proporciona les transparències: localització de serveis, crides remotes, cues de missatges, replicació... És el que permet que el programador d'aplicacions no hagi de treballar directament amb sockets i bytes. Bona part del curs (RPC, cues, bases de dades distribuïdes, orquestradors) tracta precisament de middleware.

  1. Exemples quotidians

Per convèncer-se que els sistemes distribuïts són a tot arreu, n'hi ha prou amb mirar quatre coses que fem cada dia:

Sistema Què distribueix Transparències que ofereix Què passaria en una fallada parcial
DNS Una base de dades jeràrquica de noms, replicada en milers de servidors d'arreu del món Ubicació, replicació, fallada El teu resolutor pregunta a un altre servidor; gairebé mai no ho notes
La Web Contingut servit per milions de servidors, amb memòries cau (CDN) a prop de cada usuari Ubicació, replicació, escalat Una web concreta no carrega, però la resta d'Internet continua funcionant
Banca electrònica Comptes, transaccions i caixers repartits en molts sistemes que han de mantenir la coherència Accés, concurrència, replicació Pots consultar el saldo però no fer transferències (degradació parcial)
Streaming de vídeo Còpies de cada pel·lícula en servidors repartits geogràficament, més els sistemes de recomanació i facturació Ubicació, replicació, escalat Cau la recomanació, però pots continuar mirant la teva sèrie

Fixa't en l'última columna: en tots els casos, el sistema està dissenyat per degradar-se parcialment en lloc de caure del tot. Aquesta és una de les promeses dels sistemes distribuïts... i també una de les coses més difícils d'aconseguir.

  1. Presentació de Quilòmetre Zero: el monòlit inicial

Al llarg del curs treballarem amb Quilòmetre Zero, un marketplace en línia fictici que connecta productors locals amb consumidors de diverses ciutats. Entre els seus productors hi ha Horta La Vega (verdures de temporada), Formatgeria Montblanc (formatges artesans) i Celler Roure Alt (vins). Entre els seus clients, l'Anna, en Marc i la Llúcia.

7.1 L'arquitectura de partida

Quilòmetre Zero va néixer com qualsevol projecte assenyat: un monòlit. Una única aplicació web en Python, amb tots els mòduls al mateix procés, parlant amb una única base de dades PostgreSQL, tot en un únic servidor.

flowchart TB
    U1[Anna - navegador] --> S
    U2[Marc - app mobil] --> S
    U3[Llucia - navegador] --> S
    subgraph S["Servidor únic (8 vCPU, 32 GB RAM)"]
        direction TB
        subgraph APP["Aplicació Python (un procés)"]
            C[cataleg]
            P[comandes]
            I[inventari]
            PA[pagaments]
            R[repartiment]
            AN[analitica]
        end
        DB[(PostgreSQL)]
        APP --> DB
    end

Aquesta arquitectura té virtuts enormes, i convé dir-ho clarament per no caure en el prejudici que "monòlit" és sinònim de "mal disseny":

  • Simplicitat. Una crida del mòdul comandes al mòdul inventari és una simple crida a una funció Python: no hi ha xarxa, ni serialització, ni timeouts.
  • Transaccions senzilles. Descomptar estoc, crear la comanda i registrar el pagament passen en una única transacció de PostgreSQL: o es fa tot o no es fa res.
  • Desplegament trivial. Un únic artefacte que s'instal·la en un únic servidor.
  • Depuració directa. Un únic registre de logs, una única traça de pila.

En una màquina amb 8 vCPU i 32 GB de RAM, Quilòmetre Zero atén sense problemes les seves dues primeres ciutats: unes 300 comandes al dia i pics de 40 peticions per segon.

7.2 El primer símptoma: la "Setmana de la Verema"

Al setembre, Quilòmetre Zero llança la seva primera gran campanya de temporada: la Setmana de la Verema, amb ofertes del Celler Roure Alt i d'altres cellers, promocionada a les xarxes socials en cinc ciutats. El dilluns a les 10:00 s'obre la campanya i passa el següent:

Hora Peticions/s Símptoma
09:55 45 Normal
10:02 380 Les pàgines del catàleg triguen 3-4 s a carregar
10:06 610 PostgreSQL esgota el seu límit de connexions; els pagaments comencen a fallar
10:09 700+ El procés Python es queda sense memòria i el servidor deixa de respondre
10:10 0 Caiguda total. Ni catàleg, ni comandes, ni el seguiment dels repartidors

Observa un detall revelador: el pic de trànsit el provoca la gent mirant el catàleg de vins, però el que cau és tot, inclòs el seguiment dels repartidors, que no té res a veure amb la campanya. En un monòlit, tots els mòduls comparteixen el mateix destí: la mateixa CPU, la mateixa memòria, el mateix pool de connexions a la base de dades.

Aquest incident serà el punt de partida de l'evolució de Quilòmetre Zero que anirem seguint durant el curs. Convé resistir la temptació de saltar ja a la solució: primer (lliçons 01-02 a 01-05) necessitem entendre què vol dir distribuir i quins nous problemes apareixen; a la lliçó 01-06 dibuixarem l'arquitectura objectiu.

  1. Exemple de codi: simulant nodes que fallen de manera independent

Construirem una petita simulació en Python per sentir la diferència entre una fallada total i una fallada parcial. No necessitem xarxes reals: representarem cada node amb una classe, i la "xarxa" amb crides a mètodes. És una simplificació enorme, però suficient per il·lustrar la idea.

L'escenari: Quilòmetre Zero té tres nodes que atenen comandes (comandes-1, comandes-2 i comandes-3) darrere d'un repartidor de càrrega molt senzill. Un dels nodes fallarà a mitja campanya.

import random
from dataclasses import dataclass, field


@dataclass
class Comanda:
    """Una comanda d'un client de Quilòmetre Zero."""
    id: int
    client: str
    producte: str
    productor: str


class NodeCaigutError(Exception):
    """Es llança quan intentem parlar amb un node que ha fallat."""


@dataclass
class NodeComandes:
    """Simula una màquina que executa el servei 'comandes'.

    Cada node té el seu propi estat (les comandes que ha atès) i la seva
    pròpia 'salut'. No comparteix memòria amb els altres nodes: si volem
    saber-ne alguna cosa, li hem d'enviar 'un missatge' (cridar un mètode).
    """
    nom: str
    actiu: bool = True
    atesos: list = field(default_factory=list)

    def processar(self, comanda: Comanda) -> str:
        if not self.actiu:
            # A la realitat no hi hauria excepció: simplement no arribaria
            # cap resposta. Aquí ho modelem com una excepció per simplificar.
            raise NodeCaigutError(f"{self.nom} no respon")
        self.atesos.append(comanda.id)
        return f"{self.nom} va confirmar la comanda {comanda.id} de {comanda.client}"

    def fallar(self) -> None:
        """Simula que la màquina s'apaga de cop."""
        self.actiu = False


class RepartidorDeCarrega:
    """Envia cada comanda a un node diferent, per torns (round-robin)."""

    def __init__(self, nodes: list[NodeComandes]):
        self.nodes = nodes
        self._seguent = 0

    def enviar(self, comanda: Comanda) -> str:
        node = self.nodes[self._seguent % len(self.nodes)]
        self._seguent += 1
        return node.processar(comanda)


def generar_comandes(quantitat: int) -> list[Comanda]:
    clients = ["Anna", "Marc", "Llúcia"]
    productes = [
        ("Tomàquets de temporada", "Horta La Vega"),
        ("Formatge curat", "Formatgeria Montblanc"),
        ("Vi negre criança", "Celler Roure Alt"),
    ]
    comandes = []
    for i in range(1, quantitat + 1):
        producte, productor = random.choice(productes)
        comandes.append(Comanda(i, random.choice(clients), producte, productor))
    return comandes


if __name__ == "__main__":
    random.seed(7)  # perquè l'execució sigui reproduïble

    nodes = [NodeComandes("comandes-1"), NodeComandes("comandes-2"), NodeComandes("comandes-3")]
    balancejador = RepartidorDeCarrega(nodes)

    correctes, fallits = 0, 0
    for comanda in generar_comandes(12):
        if comanda.id == 5:
            print(">>> El node comandes-2 ha caigut! <<<")
            nodes[1].fallar()
        try:
            print(balancejador.enviar(comanda))
            correctes += 1
        except NodeCaigutError as e:
            print(f"ERROR en processar la comanda {comanda.id}: {e}")
            fallits += 1

    print(f"\nResultat: {correctes} comandes correctes, {fallits} fallides")
    for node in nodes:
        estat = "actiu" if node.actiu else "CAIGUT"
        print(f"  {node.nom} ({estat}): va atendre {node.atesos}")

Analitzem el programa pas a pas:

  1. Comanda és un simple contenidor de dades: identificador, client, producte i productor. Fem servir @dataclass per no haver d'escriure el constructor a mà.
  2. NodeComandes representa una màquina. L'essencial és que té estat propi (atesos) i salut pròpia (actiu). Quan actiu és False, qualsevol intent de fer-lo servir llança NodeCaigutError. Això modela la fallada independent: un node pot caure sense que els altres se n'assabentin.
  3. RepartidorDeCarrega reparteix les comandes entre els nodes per torns. És la peça que, en un sistema real, seria un balancejador de càrrega; aquí és un simple comptador.
  4. Al bucle principal, quan arribem a la comanda 5 apaguem comandes-2. A partir d'aquí, una de cada tres comandes falla, però els altres dos terços es continuen processant amb normalitat.

La sortida (abreujada) és una cosa així:

comandes-1 va confirmar la comanda 1 de Anna
comandes-2 va confirmar la comanda 2 de Llúcia
comandes-3 va confirmar la comanda 3 de Anna
comandes-1 va confirmar la comanda 4 de Anna
>>> El node comandes-2 ha caigut! <<<
ERROR en processar la comanda 5: comandes-2 no respon
comandes-3 va confirmar la comanda 6 de Llúcia
comandes-1 va confirmar la comanda 7 de Anna
ERROR en processar la comanda 8: comandes-2 no respon
...
Resultat: 9 comandes correctes, 3 fallides
  comandes-1 (actiu): va atendre [1, 4, 7, 10]
  comandes-2 (CAIGUT): va atendre [2]
  comandes-3 (actiu): va atendre [3, 6, 9, 12]

Comparem-ho amb el monòlit de la Setmana de la Verema: allà, una única fallada deixava 0 comandes correctes. Aquí, el sistema continua atenent el 67 % de les comandes. Això és una fallada parcial: pitjor que "tot funciona", molt millor que "res no funciona".

Però fixa't també en el que la simulació no resol, i que anticipa temes de lliçons futures:

  • El balancejador continua enviant comandes a comandes-2 encara que estigui caigut. Caldria detectar la fallada i deixar de fer-lo servir (lliçó 07-03).
  • Les comandes 5, 8 i 11 s'han perdut. Les hauríem de reintentar en un altre node? I si comandes-2 sí que va arribar a processar la comanda abans de caure? (lliçons 02-05 i 07-04).
  • L'estat de comandes-2 (la comanda 2) s'ha quedat atrapat en un node caigut. Com el recuperem? (Mòdul 3, replicació).
  • A la simulació sabem amb certesa que el node ha caigut perquè llança una excepció. A la realitat, l'únic que veuríem és que no respon, i hauríem de decidir quant esperar (lliçó 01-04).

Errors Comuns i Consells

  • Confondre "distribuït" amb "molts servidors". El que defineix un sistema distribuït és que les parts només es comuniquen per missatges i poden fallar per separat. Dos processos a la mateixa màquina que parlen per sockets ja presenten molts dels problemes d'un sistema distribuït (encara que no tots).
  • Pensar que la transparència total és desitjable. Amagar completament que una crida és remota porta a escriure codi que ignora la latència i les fallades. Els bons dissenys fan la distribució còmoda, no invisible.
  • Distribuir massa aviat. Quilòmetre Zero va fer bé de començar com a monòlit: li va permetre llançar ràpid i amb pocs costos. La distribució arriba quan hi ha un problema concret (escala, disponibilitat, organització) que la justifica. Distribuir "per si de cas" afegeix complexitat sense benefici.
  • Oblidar que la xarxa és un component. En dibuixar arquitectures és habitual posar caixes (els nodes) i fletxes (la comunicació) i tractar les fletxes com si fossin gratuïtes i infal·libles. Les fletxes fallen, triguen i costen diners.
  • Assumir que un node que no respon està caigut. Pot estar sobrecarregat, pot estar aïllat per un problema de xarxa, o pot haver processat la petició i haver-se perdut només la resposta. Aquesta ambigüitat reapareixerà una vegada i una altra al llarg del curs.
  • Consell: quan dissenyis qualsevol interacció entre dos components, fes-te sempre tres preguntes: què passa si l'altre no respon?, què passa si respon molt tard? i què passa si el meu missatge va arribar però la resposta es va perdre?. Si no tens resposta per a les tres, el disseny no està acabat.

Exercicis

Exercici 1: Classificar sistemes

Per a cadascun dels casos següents, indica si es tracta d'un sistema distribuït segons la definició d'aquesta lliçó i justifica en una o dues frases quina de les característiques clau (concurrència, absència de rellotge global, fallades independents) s'hi aplica i quina no:

  1. Un programa de full de càlcul que fa servir diversos fils per recalcular fórmules en paral·lel.
  2. Una aplicació mòbil de Quilòmetre Zero que desa el cistell al telèfon i el sincronitza amb el servidor quan hi ha cobertura.
  3. Un script de còpia de seguretat que copia fitxers d'un disc a un altre al mateix ordinador.
  4. Tres repartidors que envien la seva posició cada 5 segons al servei repartiment.

Exercici 2: Transparències a la Setmana de la Verema

Imagina que, després de l'incident, Quilòmetre Zero afegeix dos servidors més per al catàleg i un balancejador de càrrega al davant. Indica quines transparències (de la taula de l'apartat 4) s'estan proporcionant als clients (l'Anna, en Marc, la Llúcia) i quines no es proporcionen als desenvolupadors del mòdul comandes, que ara ha de cridar el catàleg per xarxa en lloc de fer-ho amb una crida de funció.

Exercici 3: Millorar la simulació

Modifica la simulació de l'apartat 8 perquè el RepartidorDeCarrega salti els nodes caiguts: quan un node llanci NodeCaigutError, ha de reintentar la mateixa comanda al següent node actiu. Si cap node no està actiu, ha de llançar una excepció SenseNodesDisponiblesError. Comprova que, amb comandes-2 caigut, les 12 comandes es processen correctament.

Solucions

Solució 1:

  1. No és distribuït. Hi ha concurrència (diversos fils), però tots comparteixen memòria i el mateix rellotge, i si el procés falla, fallen tots els fils alhora. És un sistema concurrent centralitzat.
  2. Sí que és distribuït. El telèfon i el servidor són nodes independents que només es comuniquen per missatges, amb rellotges diferents, i el telèfon pot quedar-se sense cobertura (fallada de xarxa) mentre el servidor continua funcionant. Fixa't que apareixeran problemes de coherència (quin cistell és el "bo" si s'ha modificat a tots dos llocs?), que tractarem al Mòdul 3.
  3. No és distribuït. Un únic procés en una única màquina. Si el disc de destinació falla, és una fallada de maquinari local, però no hi ha nodes independents comunicant-se per missatges.
  4. Sí que és distribuït. Cada repartidor és un node amb el seu rellotge (el del mòbil), envia missatges per una xarxa poc fiable (4G) i pot desconnectar-se de manera independent. El servei repartiment ha d'assumir que les posicions poden arribar tard, desordenades o no arribar.

Solució 2:

Als clients se'ls proporciona transparència de replicació (veuen "el catàleg", encara que n'hi hagi tres còpies), d'ubicació (no saben a quin servidor els envia el balancejador), de fallada (si un servidor de catàleg cau, el balancejador els envia a un altre) i d'escalat (afegir el tercer servidor no va canviar res per a ells).

Als desenvolupadors de comandes no se'ls proporciona plenament transparència d'accés: abans cridaven una funció local i ara han de fer una petició per xarxa, gestionar timeouts i errors de connexió, i serialitzar dades. Tampoc no tenen transparència de fallada completa: si el balancejador o els tres servidors de catàleg cauen, comandes rep un error i ha de decidir què fer. Aquesta és precisament la lliçó: la distribució es pot amagar a l'usuari final, però rarament es pot amagar del tot al programador.

Solució 3:

class SenseNodesDisponiblesError(Exception):
    """Cap node del clúster no està actiu."""


class RepartidorDeCarregaResilient(RepartidorDeCarrega):
    """Igual que RepartidorDeCarrega, però salta els nodes caiguts."""

    def enviar(self, comanda: Comanda) -> str:
        intents = 0
        while intents < len(self.nodes):
            node = self.nodes[self._seguent % len(self.nodes)]
            self._seguent += 1
            intents += 1
            try:
                return node.processar(comanda)
            except NodeCaigutError:
                # Aquest node no respon: provem amb el següent.
                continue
        raise SenseNodesDisponiblesError(
            f"No hi ha nodes actius per a la comanda {comanda.id}"
        )

N'hi ha prou amb substituir RepartidorDeCarrega(nodes) per RepartidorDeCarregaResilient(nodes) al programa principal. Amb comandes-2 caigut, la sortida final mostrarà 12 comandes correctes, 0 fallides, amb les comandes repartides entre comandes-1 i comandes-3.

Reflexió addicional: aquesta solució assumeix que si un node llança NodeCaigutError és segur reintentar en un altre. A la simulació és cert, perquè el node caigut no ha fet res. En un sistema real, la petició podria haver arribat i haver-se processat abans que es perdés la resposta; reintentar a cegues podria crear la comanda dues vegades. La solució a això (idempotència) s'estudia a la lliçó 02-05.

Conclusió

En aquesta lliçó hem fixat els fonaments del curs. Un sistema distribuït és un conjunt de nodes independents que cooperen intercanviant missatges per una xarxa i que es presenten davant de l'usuari com un únic sistema. Es construeixen per necessitat —escala, geografia, disponibilitat, cost, organització— i no per gust, perquè introdueixen tres característiques que canvien per complet la manera de raonar: concurrència real, absència d'un rellotge global i fallades parcials i independents. Per fer la distribució suportable, el middleware ofereix diferents transparències (accés, ubicació, replicació, fallada, escalat), que sempre són parcials i tenen un cost.

Hem conegut Quilòmetre Zero en el seu estat inicial —un monòlit Python amb PostgreSQL en un únic servidor— i hem vist com la Setmana de la Verema el tomba per complet, inclosos mòduls que no tenien res a veure amb la campanya. La simulació de nodes ha mostrat el costat positiu (el sistema continua atenent comandes quan un node cau) i també ha deixat a la vista els problemes que encara no sabem resoldre.

A la lliçó següent, Models de Sistemes Distribuïts, veurem les diferents maneres d'organitzar els nodes (client-servidor, multicapa, peer-to-peer, serveis), els supòsits que podem fer sobre la seva comunicació i les seves fallades, i començarem a dibuixar com podria reorganitzar-se Quilòmetre Zero.

Curs d'Arquitectures Distribuïdes

Mòdul 1: Introducció als Sistemes Distribuïts

Mòdul 2: Comunicació en Sistemes Distribuïts

Mòdul 3: Consistència i Replicació

Mòdul 4: Emmagatzematge Distribuït

Mòdul 5: Computació Distribuïda

Mòdul 6: Seguretat en Sistemes Distribuïts

Mòdul 7: Monitoratge i Manteniment

Mòdul 8: Casos d'Estudi i Aplicacions

© Copyright 2026. Tots els drets reservats