When we talk about "a distributed system" we may be referring to very different things: a browser talking to a web server, a file-sharing network with no central server at all, or a platform with dozens of services sending events to one another. To reason precisely we need models: simplified descriptions that pin down how the nodes are organised, how they interact and what we can assume about their behaviour when something goes wrong.

In this lesson we introduce three families of models that will keep appearing throughout the rest of the course: architecture models (how roles are divided among the nodes), interaction and failure models (what assumptions we make about communication and errors) and, just as a preview, consistency models (what guarantees the system gives about its data). We will apply them to Kilometre Zero to see how well each model fits its needs, or does not.

Contents

  1. What models are for
  2. Architecture models
  3. Interaction models: synchronous and asynchronous
  4. Failure models
  5. Consistency models: a first look
  6. Code example: a client-server catalog service
  7. Code example: shared catalogs in a simulated P2P network
  8. Common mistakes and tips
  9. Exercises
  10. Conclusion

  1. What models are for

A model is a set of explicit assumptions. When we say "this algorithm works in an asynchronous system with crash-stop failures", we are stating exactly the conditions under which the algorithm is correct and, above all, those under which it is not. Without models, architecture discussions turn into exchanges of opinion; with models, they become reasoning that can be checked.

The three families answer three different questions:

Family Question it answers Example answer
Architecture What role does each node play and how do they relate to one another? "Three-tier client-server"
Interaction and failure What can we assume about timing and about errors? "Asynchronous, with crash-stop failures"
Consistency What guarantees does the system offer about the data it returns? "Eventual consistency"

  1. Architecture models

2.1 Client-server

This is the oldest and most widespread model. Nodes are split into two roles: servers offer a service (they wait for requests and answer them) and clients consume it (they send requests and wait for responses). The roles are asymmetric: the server does not start conversations.

Kilometre Zero's initial monolith is a pure example:

flowchart LR
    A[Anna - browser] -->|"GET /catalog?q=cheese"| S
    M[Mark - mobile app] -->|"POST /orders"| S
    L[Lucy - browser] -->|"GET /orders/1234"| S
    S["Kilometre Zero server<br/>(Python app + PostgreSQL)"]
    S -->|HTML/JSON response| A
    S -->|JSON response| M
    S -->|HTML response| L

Characteristics:

  • Advantages: easy to understand, the state is centralised (easy to keep consistent), security is concentrated in one place.
  • Drawbacks: the server is a bottleneck and a single point of failure; scalability depends on how far that server can grow (or on replicating it, which brings new problems).

It is worth making clear that "client" and "server" are roles, not machines: at Kilometre Zero, the orders module is a server for the mobile app and, at the same time, a client of the inventory module when it asks it to check the stock.

2.2 Multi-tier (n-tier)

A natural evolution of the client-server model is to split the server into tiers with different responsibilities, each of them potentially on different machines. The most common version is the three-tier one:

  1. Presentation (web): receives HTTP requests, serves static content, handles sessions and TLS.
  2. Business logic (application): runs the domain rules (calculating prices, validating orders, applying campaign discounts).
  3. Data: stores and retrieves information persistently.

Applied to Kilometre Zero after the Grape Harvest Week incident, a reasonable first step would be this:

flowchart TB
    subgraph Tier1["Presentation tier"]
        W1[Web server 1]
        W2[Web server 2]
    end
    subgraph Tier2["Application tier"]
        A1[Python app 1]
        A2[Python app 2]
        A3[Python app 3]
    end
    subgraph Tier3["Data tier"]
        DB[(PostgreSQL)]
    end
    Customers[Anna, Mark, Lucy] --> LB[Load balancer]
    LB --> W1
    LB --> W2
    W1 --> A1
    W1 --> A2
    W2 --> A2
    W2 --> A3
    A1 --> DB
    A2 --> DB
    A3 --> DB

The important thing about this model is that each tier scales independently: if the problem is CPU in the business logic, you add application instances; if it is web traffic, you add web servers. Besides, each tier only talks to the adjacent ones, which simplifies both reasoning and security.

Notice, however, that the data tier is still a single node: going multi-tier eases the Grape Harvest Week problem (the application tier no longer chokes), but it does not solve it, because PostgreSQL will still run out of connections. We will come back to this in lesson 01-06 and in Modules 3 and 4.

2.3 Peer-to-peer (P2P)

At the opposite end from client-server is the peer-to-peer model: all nodes (peers) have the same role, act as clients and servers at the same time, and there is no central authority. Classic examples: BitTorrent, blockchain networks, or the discovery protocol of many storage systems (Cassandra uses gossip between peers, as we will see in 04-04).

At Kilometre Zero we could imagine a P2P scenario for the physical markets: each local market has a small computer holding its producers' catalog, and the markets exchange their catalogs directly with one another, without depending on the central server (which may be down, or out of coverage in a rural area).

flowchart LR
    M1["Girona market<br/>(Montblanc Dairy)"]
    M2["Lleida market<br/>(La Vega Farm)"]
    M3["Tarragona market<br/>(Roble Alto Winery)"]
    M4["Valencia market"]
    M1 <-->|catalog exchange| M2
    M1 <--> M3
    M2 <--> M3
    M3 <--> M4
    M2 <--> M4
Aspect Client-server Peer-to-peer
Roles Asymmetric Symmetric
Single point of failure Yes (the server) No
Scalability Limited by the server Grows with the number of peers
Data consistency Easy (centralised state) Hard (scattered state, no authority)
Security and control Centralised Hard (whom do you trust?)
Discovery Trivial (well-known address) Complex (how do I find the others?)

2.4 Service-oriented architectures and microservices

The fourth family is the one that will dominate the second half of the course, so here we only introduce it as a model. The idea is to break the application down into independent services, each responsible for one functional area, with its own deployment and (usually) its own data. Services communicate with one another through requests (RPC, HTTP) or through events (message queues).

For Kilometre Zero, the services correspond to its functional domains: catalog, orders, inventory, payments, delivery and analytics.

flowchart LR
    GW[Gateway] --> C[catalog]
    GW --> P[orders]
    GW --> R[delivery]
    P --> I[inventory]
    P --> PA[payments]
    P -.->|OrderCreated event| AN[analytics]
    R -.->|PositionUpdated event| AN

This model combines features of the previous ones: each service is client-server with respect to the others, and is usually deployed in tiers internally. Its great advantage is independence (of deployment, scaling, failure and team); its great cost, the complexity of having dozens of parts communicating over the network. We will develop it in lesson 08-01, and the whole evolution of Kilometre Zero (lesson 01-06) heads towards this model.

  1. Interaction models: synchronous and asynchronous

The word "synchronous" is used with two different meanings in distributed systems, and mixing them up is the source of many misunderstandings. Let's separate them carefully.

3.1 Synchronous versus asynchronous communication (interaction style)

This refers to how the sender behaves after sending a message:

  • Synchronous communication: the sender sends the request and blocks waiting for the response. This is the "request-response" style: calling a remote function, making an HTTP request. Example at Kilometre Zero: orders asks inventory whether there is stock and does not carry on until it gets the answer.
  • Asynchronous communication: the sender sends the message and carries on with its work. The response, if there is one, will arrive later through another channel (a queue, a callback). Example: when an order is confirmed, orders publishes the "OrderConfirmed" event and does not wait for analytics to process it.
Synchronous Asynchronous
Temporal coupling High: both must be available at the same time Low: the receiver can process later
Code simplicity High (it reads like a normal call) Lower (deferred responses have to be handled)
Behaviour when the receiver fails The sender fails or waits The message waits in the queue
Typical example HTTP, RPC, gRPC (Module 2, lessons 02-02 and 02-03) Message queues, events (lessons 02-04 and 02-05)

3.2 Synchronous versus asynchronous systems (timing assumptions)

This second meaning is more theoretical and runs deeper. It refers to what we can assume about timing in the system:

  • Synchronous system: there are known bounds on (a) how long a message takes to arrive, (b) how long a node takes to execute a step and (c) clock drift. Under these assumptions, if a node does not respond within the maximum time, we know for certain that it has failed.
  • Asynchronous system: there are no bounds at all: a message can take an arbitrary (but finite) time to arrive, and a node can take an arbitrary time to take a step. Under these assumptions, it is impossible to tell a slow node from a crashed one.
  • Partially synchronous: the most realistic model. The system behaves asynchronously for periods of time (congestion, garbage collection pauses, overload), but "most of the time" it respects certain bounds. Almost all real systems (and almost all practical consensus algorithms, which we will see in 03-03) assume this model.

Why does it matter? Because there is a famous theoretical result (the FLP impossibility result, by Fischer, Lynch and Paterson, 1985) which proves that in a purely asynchronous system in which even a single node may fail, no deterministic algorithm can guarantee reaching consensus. It is not an engineering limitation: it is a mathematical impossibility. Real systems get around it by assuming partial synchrony (timeouts) or by accepting probabilistic guarantees. That is why, when the orders service at Kilometre Zero waits for a response from inventory, the only tool it has is a timeout: a bet on how long is "too long", never a certainty. Lesson 01-04 explores this idea with code.

  1. Failure models

A failure model describes the ways in which a component can misbehave. The broader the model, the harder (and more expensive) it is to tolerate. From least to most severe:

Model Description Example at Kilometre Zero Cost of tolerating it
Crash-stop The node works correctly until it stops, and it never comes back (or comes back as a new node, remembering nothing) The payments server shuts down because of a power failure Low: replication is enough
Crash-recovery The node stops but can restart, keeping its persistent state (disk) but not its volatile state (memory) orders restarts after running out of memory; it remembers the orders saved in the database, but not the ones it held in memory Medium: recovery has to be managed
Omission The node fails to receive or send some messages (but the ones it does send are correct) The network loses the request from orders to inventory; or inventory processes it but the response is lost Medium: retries, acknowledgements
Timing The node responds correctly but outside the expected time inventory takes 8 seconds to respond because of a slow database query Medium: timeouts (and ambiguity)
Arbitrary or Byzantine The node can do anything: send incorrect data, lie, behave inconsistently towards different nodes, be malicious A compromised inventory node answers "in stock" to orders and "out of stock" to analytics Very high: 3f+1 nodes are needed to tolerate f Byzantine nodes (lesson 03-03)

A few practical observations:

  • Omission and timing failures, from the sender's point of view, are indistinguishable from a crash: in all three cases, the only thing it observes is that no response arrives in time.
  • The vast majority of business systems (Kilometre Zero included) assume the crash-recovery model with omission failures, and do not tolerate Byzantine failures: you trust that your own nodes do not lie. Byzantine tolerance is reserved for systems involving parties that do not trust one another (blockchains, safety-critical aviation systems).
  • A failure model also states how many simultaneous failures we tolerate: "the system keeps working if up to f of the n nodes fail".

  1. Consistency models: a first look

The third family answers the question of what guarantees the system offers when there are several copies of the data (which, as we will see, is almost inevitable as soon as we distribute). This is the subject of the whole of Module 3, so here it is enough to have an intuition of the two extremes:

Model Guarantee Price Example at Kilometre Zero
Strong consistency Every read returns the latest value written, on any copy, as if only one existed Latency and availability: the copies have to be coordinated before responding The stock of the last unit of cheese from Montblanc Dairy: Anna and Mark cannot both buy it
Eventual consistency If writes stop, all the copies will eventually converge on the same value; in the meantime, a read may return an old value Stale reads for a while The number of "likes" on a product, or the photo catalog: it does not matter if Lucy sees the old photo for a few seconds

Between the two extremes lies a range of intermediate models (read-your-writes, causal consistency, etc.) which are covered in lesson 03-01, and the famous CAP theorem (03-02) explains why you cannot have everything at once. For now, hold on to this idea: choosing the consistency model is a business decision made per piece of data, not a global property of the system.

  1. Code example: a client-server catalog service

Let's implement the client-server model in its most elementary form, with asyncio and no external dependencies. The server will be a first (very simplified) version of Kilometre Zero's catalog service: it keeps a small catalog in memory and answers text queries. The protocol will be deliberately primitive (one line of text per request and one JSON response per line) because "proper" protocols are the subject of lesson 02-01.

Save this code as catalog_server.py:

import asyncio
import json

CATALOG = [
    {"id": 1, "name": "Pink tomato", "producer": "La Vega Farm", "price": 3.20},
    {"id": 2, "name": "Aged sheep's cheese", "producer": "Montblanc Dairy", "price": 14.50},
    {"id": 3, "name": "Fresh cheese", "producer": "Montblanc Dairy", "price": 6.80},
    {"id": 4, "name": "Crianza red wine", "producer": "Roble Alto Winery", "price": 9.90},
    {"id": 5, "name": "Zucchini", "producer": "La Vega Farm", "price": 2.10},
]


def search(text: str) -> list[dict]:
    """Returns the products whose name or producer contains the text."""
    text = text.lower()
    return [
        p for p in CATALOG
        if text in p["name"].lower() or text in p["producer"].lower()
    ]


async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
    """Runs once for every client that connects."""
    address = writer.get_extra_info("peername")
    print(f"[server] connection from {address}")
    try:
        while True:
            line = await reader.readline()       # wait for a request
            if not line:                         # the client closed the connection
                break
            query = line.decode().strip()
            result = search(query)
            response = json.dumps(result, ensure_ascii=False) + "\n"
            writer.write(response.encode())      # send the response
            await writer.drain()                 # wait for it to go out over the network
            print(f"[server] '{query}' -> {len(result)} results")
    finally:
        writer.close()
        await writer.wait_closed()
        print(f"[server] connection closed with {address}")


async def main():
    server = await asyncio.start_server(handle_client, "127.0.0.1", 8765)
    print("[server] catalog listening on 127.0.0.1:8765")
    async with server:
        await server.serve_forever()


if __name__ == "__main__":
    asyncio.run(main())

And this one as catalog_client.py:

import asyncio
import json


async def query_catalog(query: str) -> list[dict]:
    """Opens a connection, sends a query and returns the response."""
    reader, writer = await asyncio.open_connection("127.0.0.1", 8765)
    writer.write((query + "\n").encode())
    await writer.drain()
    line = await reader.readline()           # blocks until the response arrives
    writer.close()
    await writer.wait_closed()
    return json.loads(line.decode())


async def main():
    for query in ["cheese", "vega", "wine"]:
        products = await query_catalog(query)
        print(f"Query '{query}':")
        for p in products:
            print(f"  - {p['name']} ({p['producer']}): {p['price']:.2f} €")


if __name__ == "__main__":
    asyncio.run(main())

Run the server first in one terminal and then the client in another. Let's go through the pieces:

  1. asyncio.start_server creates a server listening on port 8765 of the local machine. For every client that connects, asyncio calls handle_client with a pair of objects: reader (to read what the client sends) and writer (to reply to it).
  2. The while True loop inside handle_client lets the same client send several queries over the same connection. When readline() returns an empty string, it means the client has closed the connection.
  3. await writer.drain() is the "distributed" part of the code: the server has no control over when the bytes will go out over the network; drain() waits until the operating system has accepted them. It is the first place where the code touches the physical reality of the network.
  4. On the client, query_catalog is a perfect example of synchronous communication (request-response style): it sends the query, and await reader.readline() does not carry on until the response arrives. If the server never responded, the client would be left waiting forever: we have not set any timeout (we will fix that in 01-04).
  5. The roles are clearly separated: the server waits and the client initiates. That is the client-server model.

Try launching two or three clients at once: thanks to asyncio, the server handles them concurrently with no need for threads. And also try killing the server while a client is connected: the client will get a connection error, which is how the crash-stop failure model shows up in code.

  1. Code example: shared catalogs in a simulated P2P network

For the P2P model we are not going to use sockets, but a simulation in which each market is an object and the "network" is a list of neighbours. The idea is to implement a catalog exchange based on gossip: periodically, each peer picks a neighbour at random and tells it what it knows; both end up with the union of what they knew.

import random


class MarketPeer:
    """A local market that knows its own catalog and learns other markets' catalogs."""

    def __init__(self, name: str, own_products: dict[str, str]):
        self.name = name
        # catalog: product name -> producer. It starts with its own products only.
        self.catalog = dict(own_products)
        self.neighbours: list["MarketPeer"] = []

    def connect(self, other: "MarketPeer") -> None:
        """Bidirectional link: both peers consider each other neighbours."""
        self.neighbours.append(other)
        other.neighbours.append(self)

    def gossip_round(self) -> None:
        """Picks a neighbour at random and exchanges catalogs with it."""
        if not self.neighbours:
            return
        neighbour = random.choice(self.neighbours)
        # Each one learns what it did not know from the other. There is no
        # central server: both are sender and receiver at the same time.
        new_for_me = set(neighbour.catalog) - set(self.catalog)
        new_for_them = set(self.catalog) - set(neighbour.catalog)
        self.catalog.update({k: neighbour.catalog[k] for k in new_for_me})
        neighbour.catalog.update({k: self.catalog[k] for k in new_for_them})
        if new_for_me or new_for_them:
            print(f"  {self.name} <-> {neighbour.name}: "
                  f"learned {sorted(new_for_me)}, taught {sorted(new_for_them)}")


def all_converged(peers: list[MarketPeer]) -> bool:
    """True if all the peers have exactly the same catalog."""
    reference = peers[0].catalog
    return all(p.catalog == reference for p in peers)


if __name__ == "__main__":
    random.seed(3)

    girona = MarketPeer("Girona", {"Aged sheep's cheese": "Montblanc Dairy"})
    lleida = MarketPeer("Lleida", {"Pink tomato": "La Vega Farm"})
    tarragona = MarketPeer("Tarragona", {"Crianza red wine": "Roble Alto Winery"})
    valencia = MarketPeer("Valencia", {"Oranges": "Turia Farm"})

    # Topology: not everyone knows everyone (as in a real P2P network)
    girona.connect(lleida)
    girona.connect(tarragona)
    lleida.connect(tarragona)
    tarragona.connect(valencia)
    lleida.connect(valencia)

    peers = [girona, lleida, tarragona, valencia]
    round_number = 0
    while not all_converged(peers):
        round_number += 1
        print(f"Round {round_number}:")
        for peer in peers:
            peer.gossip_round()

    print(f"\nAll markets converged in {round_number} rounds.")
    print("Full catalog in Valencia:")
    for product, producer in sorted(valencia.catalog.items()):
        print(f"  - {product} ({producer})")

Points worth highlighting:

  1. There is no special node. Every MarketPeer has the same code and the same responsibilities. If Girona disappeared, the others would carry on exchanging catalogs.
  2. Knowledge spreads like a contagion. Valencia is not connected to Girona, but it ends up knowing about Montblanc Dairy's cheese through Tarragona or Lleida. This is exactly the gossip mechanism that systems such as Cassandra use to spread cluster state (lesson 04-04).
  3. Convergence is eventual. For several rounds, different markets have different catalogs: it is a tangible example of eventual consistency (section 5). In the end they all agree, but we do not know in advance how many rounds it will take.
  4. Important simplifications. We only add products; we never modify or delete them. If Lleida and Girona had different versions of the price of the same product, which one wins? That problem (conflict resolution) requires the logical clocks of lesson 01-05 and the replication techniques of 03-04.

Common Mistakes and Tips

  • Confusing the two meanings of "synchronous". "Synchronous call" is about programming style (blocking while waiting for a response); "synchronous system" is about assumptions on time bounds. You can make a synchronous call in an asynchronous system (and that is the normal case).
  • Assuming that a timeout detects failures. A timeout only detects that no response has arrived in time. In an asynchronous (or partially synchronous) system, that does not let you conclude that the other node is down. Any design that treats "timeout" as "definite failure" will end up with duplicates or contradictory decisions.
  • Designing for Byzantine failures when there is no need. Tolerating malicious nodes multiplies cost and complexity. If all the nodes are yours and run on your infrastructure, the crash-recovery model is almost always enough.
  • Treating the architecture model as a single choice. Real systems combine models: microservices that are multi-tier internally, with P2P components for discovery and centralised coordination for certain decisions. Kilometre Zero will end up being a mixture.
  • Forgetting that tiers do not fix the database. A common mistake when moving from a monolith to a multi-tier design is to replicate the application and leave a single database: the application scales, but the bottleneck moves to the data.
  • Tip: when you document a design, write down explicitly the failure model you are assuming ("we tolerate the loss of up to one node per service; we do not tolerate malicious nodes"). That sentence prevents many arguments and many surprises.

Exercises

Exercise 1: Choosing an architecture

For each of these Kilometre Zero needs, say which architecture model (simple client-server, multi-tier, P2P or services) fits best and why:

  1. An internal dashboard for the admin team to look up the day's orders.
  2. Letting the producers' tablets in a market with no coverage share stock updates among themselves until the connection comes back.
  3. Letting the payments module be deployed and scaled without affecting the rest of the platform.

Exercise 2: Classifying failures

State the failure model (crash-stop, crash-recovery, omission, timing, Byzantine) that best describes each situation, and explain what the orders service sees in each case:

  1. The inventory process restarts automatically after a failure, recovering the stock from disk.
  2. A faulty network cable drops 5% of the packets between orders and inventory.
  3. A slow query makes inventory take 15 seconds to respond.
  4. A programming bug makes inventory return negative stock to some clients and positive stock to others for the same product.

Exercise 3: Timeout on the client

Modify catalog_client.py so that waiting for the response is limited to 2 seconds using asyncio.wait_for. If the limit is exceeded, the client must print an error message and move on to the next query. Then modify the server so that it "sleeps" for 5 seconds (await asyncio.sleep(5)) before answering the "wine" query, and check the behaviour. Which failure model are you simulating?

Solutions

Solution 1:

  1. Simple client-server (or a lightweight multi-tier design). It is an internal tool with few users and no scale or high-availability requirements. Adding complexity brings nothing.
  2. Peer-to-peer. The tablets must work without a central server and share information directly with one another; a gossip mechanism like the one in section 7 fits perfectly. When the connection comes back, the changes will be synchronised with the server (which will raise conflicts: Module 3).
  3. Services (microservices). This is exactly the motivation for this model: independent deployment and scaling per functional area. It is the path Kilometre Zero will take.

Solution 2:

  1. Crash-recovery. orders sees that, for a few seconds, inventory does not respond (connection error), and then it responds again with the saved state. Any changes that inventory held in memory and had not persisted are lost.
  2. Omission. Some requests (or responses) do not arrive. orders sees that, seemingly at random, one call in twenty gets no response, even though inventory is perfectly healthy.
  3. Timing. The response is correct, but it arrives late. If orders has a 3-second timeout, it will see exactly the same as in case 2 (no response), even though the cause is completely different.
  4. Byzantine (even though it is not malicious). The node returns results that are incorrect and inconsistent depending on who it is answering. orders sees no error at all: it receives responses that look valid but whose content is false. It is the most dangerous kind of failure precisely because it cannot be detected with timeouts or retries.

Solution 3:

async def query_catalog(query: str, timeout: float = 2.0) -> list[dict]:
    reader, writer = await asyncio.open_connection("127.0.0.1", 8765)
    try:
        writer.write((query + "\n").encode())
        await writer.drain()
        # wait_for cancels the wait if the limit is exceeded and raises TimeoutError
        line = await asyncio.wait_for(reader.readline(), timeout=timeout)
        return json.loads(line.decode())
    finally:
        writer.close()
        await writer.wait_closed()


async def main():
    for query in ["cheese", "vega", "wine"]:
        try:
            products = await query_catalog(query)
        except asyncio.TimeoutError:
            print(f"Query '{query}': the server did not respond in time")
            continue
        print(f"Query '{query}':")
        for p in products:
            print(f"  - {p['name']} ({p['producer']}): {p['price']:.2f} €")

On the server, inside handle_client, add the following before computing the response:

            if query == "wine":
                await asyncio.sleep(5)   # simulates a very slow query

When you run it, the "cheese" and "vega" queries work and "wine" produces the timeout message. You are simulating a timing failure: the server is healthy and will respond correctly (even though nobody is reading the response any more). Notice that the client cannot know whether the server is slow or down; this is the ambiguity of the asynchronous model in action. If the query had been an operation with side effects (for example, "create order"), the client would not know whether the order had been created or not, and retrying could duplicate it.

Conclusion

Models give us a precise language for talking about distributed systems. We have looked at architecture models (client-server, multi-tier, peer-to-peer and services) and how each one divides up roles and risks differently; at interaction models, carefully distinguishing synchronous/asynchronous communication (call style) from synchronous/asynchronous systems (timing assumptions), with the fundamental consequence that in an asynchronous system you cannot tell a slow node from a crashed one; at failure models, from the simple crash-stop to the Byzantine, with their growing cost of tolerance; and we have taken a first look at consistency models, which Module 3 will develop.

In the code we have built a client-server catalog service with asyncio, and a simulated P2P network of markets that spread their catalogs by gossip, seeing eventual consistency in action.

With this vocabulary we can now assess rigorously what Kilometre Zero gains and loses by becoming distributed. That is exactly what we will do in the next lesson, Advantages and Challenges of Distributed Systems, where we will quantify scalability, availability and the hidden costs of distribution.

Distributed Architectures Course

Module 1: Introduction to Distributed Systems

Module 2: Communication in Distributed Systems

Module 3: Consistency and Replication

Module 4: Distributed Storage

Module 5: Distributed Computing

Module 6: Security in Distributed Systems

Module 7: Monitoring and Maintenance

Module 8: Case Studies and Applications

© Copyright 2026. All rights reserved