In the previous two lessons we saw what a distributed system is and which models let us describe it. Now it is time to answer the question any Kilometre Zero architect would ask after the Grape Harvest Week outage: is distributing worth it? The honest answer is "it depends", and this lesson provides the tools to turn that "it depends" into a calculation rather than a hunch.
We are going to quantify the advantages (how much capacity we gain by adding machines, how many minutes of downtime per year each "nine" of availability means, how much parallelism really speeds things up) and put a name to the challenges (complexity, heterogeneity, consistency, partial failures, latency, security, observability and cost). We will finish with a table of situations in which it is better not to distribute, because knowing when not to do it matters as much as knowing how to do it.
Contents
- Advantages of distributing
- Scalability: vertical versus horizontal
- Fault tolerance and availability: the "nines"
- Flexibility, locality and resource sharing
- Amdahl's law: the limit of scaling
- Challenges of distributing
- When NOT to distribute
- Code example: combined availability at Kilometre Zero
- Common mistakes and tips
- Exercises
- Conclusion
- Advantages of distributing
The advantages of a distributed system are the flip side of the reasons they exist (lesson 01-01). We group them into five, and we will quantify the first three:
| Advantage | What it brings | How it is measured |
|---|---|---|
| Scalability | Handling more load by adding machines | Requests per second, concurrent users |
| Fault tolerance and availability | Carrying on working even when components fail | Percentage of time available ("nines") |
| Performance through locality | Serving each user from nearby | Latency (milliseconds) |
| Flexibility and modularity | Evolving each part separately | Deployment frequency, team size |
| Resource sharing | Making the most of common hardware, data and services | Cost per unit of work |
- Scalability: vertical versus horizontal
Scaling means increasing a system's capacity to handle more load. There are two routes:
- Vertical scaling (scale up): replacing the machine with a more powerful one (more CPU, more memory, faster disks).
- Horizontal scaling (scale out): adding more machines and spreading the load across them.
| Criterion | Vertical | Horizontal |
|---|---|---|
| Software changes | None (or almost none) | The application must be able to run spread out (no local state, load balancing) |
| Limit | The biggest machine that exists (or that you can afford) | None in theory; in practice, Amdahl's law (section 5) |
| Cost | Grows faster than linearly: a machine twice as powerful costs a good deal more than twice as much | Roughly linear: two machines cost twice as much as one |
| Availability | No improvement: there is still a single point of failure | Improves: losing one machine does not bring the system down |
| Elasticity | Changing machines means downtime and planning | Machines can be added and removed according to demand (in the cloud, within minutes) |
| Time to apply | Days or weeks (purchasing, migrating) | Minutes (if the system is already prepared for it) |
Capacity calculation for Grape Harvest Week
Recall the figures from the incident (lesson 01-01): the single server started to degrade at around 380 requests/s and went down above 700 requests/s. Marketing expects the next campaign, "Artisan Cheese Week", with Montblanc Dairy in the leading role, to reach a peak of 1,200 requests/s.
Let's do the sums for both options:
Vertical option. We need a machine that can withstand 1,200 req/s with a 30% safety margin: 1,560 req/s, that is, about 4 times the current capacity (380 "comfortable" req/s). A server four times as powerful as the current one (32 vCPU, 128 GB) does not cost 4 times as much, but typically 5-6 times as much, and it will still be a single point of failure. What is more, it is not clear that the application will make linear use of the extra cores (section 5).
Horizontal option. We measure that one instance of the Python application, on a modest machine (4 vCPU, 16 GB), comfortably handles 200 req/s. So:
required capacity = 1,200 req/s × 1.3 (margin) = 1,560 req/s required instances = ceil(1,560 / 200) = 8 instances instances with redundancy (N+1) = 9 instances
Nine small machines during campaign week and two or three for the rest of the year. This elasticity is impossible with vertical scaling.
But beware: the calculation assumes that the application tier is the bottleneck. In the real incident, PostgreSQL ran out of connections at 610 req/s. If we put 9 application instances in front of the same database, we will simply choke it sooner. Scaling the application horizontally forces us to scale the data as well (caching, read replicas, partitioning), which is the subject of Module 4. The lesson: horizontal scaling is not a property of "the system" but of each component, and the system scales only as far as its least scalable component.
- Fault tolerance and availability: the "nines"
The availability of a system is the fraction of time during which it works correctly. It is expressed as a percentage and, colloquially, by its number of nines. It is essential to translate each level into downtime to understand what it really means:
| Availability | Nines | Maximum downtime per year | Maximum downtime per month | Maximum downtime per week |
|---|---|---|---|---|
| 99% | two | 3 days 15 h 36 min (87.6 h) | 7 h 18 min | 1 h 41 min |
| 99.9% | three | 8 h 46 min | 43 min 50 s | 10 min 5 s |
| 99.99% | four | 52 min 34 s | 4 min 23 s | 1 min |
| 99.999% | five | 5 min 15 s | 26 s | 6 s |
The calculation is straightforward: a year has 365 × 24 × 60 = 525,600 minutes; 0.1% of that figure is 525.6 minutes (8 h 46 min); 0.01% is 52.56 minutes.
Two important observations:
- Each extra nine multiplies the cost. Going from 99.9% to 99.99% is not "a bit better": it means that over the whole year you can only afford 52 minutes of downtime, including deployments, operating system upgrades, hardware failures and human error. It demands redundancy at every level, zero-downtime deployments and a 24×7 on-call rota.
- Availability combines. If Anna's request to buy a cheese goes through four components in series (web,
orders,inventory,payments), and each one has 99.9%, the availability of the purchase is not 99.9% but roughly 99.6%: the unavailabilities add up. Conversely, if a component has two independent replicas at 99.9% and it is enough for one of them to work, the combined availability rises to 99.9999%. The code example in section 8 performs these calculations.
With the monolith, Kilometre Zero had an actual availability of 99.3% over the last year (around 61 hours of downtime, between Grape Harvest Week, upgrades and a disk failure). The goal after the restructuring is to reach 99.9% for purchases and 99.95% for catalog browsing.
- Flexibility, locality and resource sharing
Flexibility and modularity
When each service is an independent deployment unit, teams can work without treading on each other's toes. At Kilometre Zero, the delivery team needs to deploy several times a day to tune the routing algorithm, whereas the payments team, which is subject to audits, prefers tightly controlled fortnightly deployments. In the monolith, the two rhythms are incompatible: every deployment is a deployment of everything. Distributing lets each service have its own pace, its own technology (Python for nearly everything, perhaps another language for real-time position processing) and its own owning team.
Performance through locality
The speed of light in optical fibre is about 200,000 km/s. A round trip between Barcelona and a data centre in Frankfurt (about 1,100 km) takes at least 11 ms; with real routing hops, 25-35 ms. Between Barcelona and Virginia (USA), some 90-110 ms. If loading a catalog page requires 10 sequential requests, the difference between serving from nearby and from far away is more than a second. That is why large systems replicate data and services across several regions, and use CDNs for static content (the product photos, in Kilometre Zero's case). Serving each city from a nearby node is an advantage a centralised system cannot offer.
Resource sharing
A distributed system lets many users and services share expensive resources: a database, an analytical processing cluster, a photo store. Kilometre Zero's analytics service will need enormous computing capacity for a few hours a day to process sales; for the rest of the day, that capacity can be put to other uses. Sharing is more efficient than dedicating.
- Amdahl's law: the limit of scaling
It is tempting to think that twice as many machines give you twice the performance. That is almost never the case, and the reason was formulated by Gene Amdahl in 1967: every task has a part that cannot be parallelised, and that part limits the maximum speedup.
If a fraction p of the work is parallelisable (and therefore 1 − p is sequential), the speedup S when using n machines is:
And as n tends to infinity:
Let's apply it to Kilometre Zero. Suppose that, when processing an order, 90% of the work (validating, calculating prices, generating the confirmation, rendering) can be parallelised across instances, but 10% (writing to the single PostgreSQL database, which serialises transactions on the stock) is inherently sequential. Then:
| Machines (n) | Speedup S(n) with p = 0.9 | "Naive" speedup (n) | Efficiency S(n)/n |
|---|---|---|---|
| 1 | 1.00 | 1 | 100% |
| 2 | 1.82 | 2 | 91% |
| 4 | 3.08 | 4 | 77% |
| 8 | 4.71 | 8 | 59% |
| 16 | 6.40 | 16 | 40% |
| 64 | 8.77 | 64 | 14% |
| ∞ | 10.00 | ∞ | 0% |
With 8 machines we get less than 5 times the capacity, and we will never exceed 10 times however many machines we add, as long as that 10% remains sequential. This ties in with section 2: the 9 application instances will not deliver 9 × 200 = 1,800 req/s if the database is the sequential component. The only way to break through the ceiling is to reduce the sequential fraction: caching catalog reads (04-05), partitioning the data (04-01), or making asynchronous those operations that do not need to be immediate (02-04). At heart, a good deal of distributed architecture consists of attacking Amdahl's (1 − p).
- Challenges of distributing
Every advantage comes with a bill. These are the eight challenges that will appear, one by one, in the rest of the course:
| Challenge | What it consists of | How it shows up at Kilometre Zero | Where it is covered |
|---|---|---|---|
| Complexity | More parts, more interactions, more ways to fail. Reasoning moves from "a stack trace" to "a graph of calls between processes" | An order that used to be a function now crosses 5 services and 2 queues | The whole course; especially 08-01 |
| Heterogeneity | Different languages, operating systems, versions, data formats, networks | delivery receives positions from Android and iOS phones over 4G and Wi-Fi; analytics uses tools from a different ecosystem |
02-03 (serialization), 07-05 (orchestration) |
| Data consistency | With several copies, which one is the truth? What happens if two nodes write at the same time? | Anna and Mark buy the last unit of cheese from two different cities | The whole of Module 3 |
| Partial failures | Some parts fail while others carry on; you do not always know what happened | payments charges Lucy but the confirmation to orders is lost |
01-04, 02-05, 07-03, 07-04 |
| Latency | Every network call costs milliseconds; chaining many calls adds up to seconds | Loading the basket makes 12 calls to different services: 12 × 20 ms = 240 ms | 01-04, 04-05 |
| Security | Every network communication can be intercepted or spoofed; a larger attack surface | How does inventory know that whoever is asking it to deduct stock really is orders? |
The whole of Module 6 |
| Debugging and observability | The logs are scattered; reproducing a failure means correlating events from many machines with different clocks | "Mark's order got stuck in 'pending' and nobody knows which service it got stuck in" | Module 7 (07-01, 07-02) |
| Operational cost | More machines, more deployments, more monitoring, more people on call | The team goes from administering one server to administering a cluster with dozens of containers | 07-05, 08-03 |
It is worth dwelling on consistency, because it is the most counter-intuitive one. In the monolith, the PostgreSQL transaction guarantees that if Anna buys the last unit, Mark will see zero stock. With orders and inventory separated, and perhaps with inventory replicated across several cities, that guarantee disappears by default and has to be rebuilt with specific techniques (consensus, distributed transactions, sagas) that carry a cost in latency and complexity. Module 3 is devoted entirely to this problem; here it is enough to know that it exists and that there is no free solution.
- When NOT to distribute
A good architect knows how to say "no". These are situations in which distributing is, almost certainly, a mistake:
| Situation | Why not distribute | Alternative |
|---|---|---|
| The load fits comfortably on one machine and is not expected to grow | You take on all the complexity without getting the main advantage | A well-structured monolith; vertical scaling if needed |
| The team is small (fewer than ~8 people) | Microservices exist largely so that different teams do not get in each other's way; a small team does not have that problem and still pays for the complexity | A modular monolith with clear boundaries between modules |
| The data requires strong transactions across almost all entities | Distributed transactions are slow and complex; if everything is related to everything, there are no natural boundaries along which to split | A single relational database, possibly with read replicas |
| There is no operational capability (monitoring, automated deployment, on-call) | A distributed system without observability is a black box that will fail without anyone knowing why | Invest in operations first (Module 7) and distribute afterwards |
| The availability requirement is modest (99% is enough) | A monolith with backups and a restore procedure already achieves that | Simple high availability: a standby machine and a restore |
| The bottleneck has not been identified | Distributing "blind" tends to move the problem rather than solve it (like the 9 instances against a single database) | Measure first, then distribute the component that is actually the limit |
After Grape Harvest Week, Kilometre Zero meets the criteria for distributing: the load no longer fits on one machine, there are several teams, the domains (catalog, orders, delivery...) have natural boundaries and an availability of 99.3% is unacceptable for the business. But it is worth noting that not every component has to be distributed in the same way or at the same time: lesson 01-06 proposes a gradual path.
- Code example: combined availability at Kilometre Zero
Let's write a small simulator that calculates the availability of a business flow from the availability of the components it goes through. The two rules are:
- Components in series (the operation needs all of them to work): the combined availability is the product of the availabilities. If A has 0.999 and B has 0.999, A and B together have 0.999 × 0.999 = 0.998.
- Components in parallel (it is enough for one to work, for example replicas): the combined unavailability is the product of the unavailabilities. If A and A' have 0.999 each, the probability of both failing at once is 0.001 × 0.001 = 0.000001, so the availability is 1 − 0.000001 = 0.999999.
The second rule assumes that failures are independent, which in reality is not always true (if the two replicas share a power supply or a network, they fail together). We will comment on this after the code.
from dataclasses import dataclass
MINUTES_PER_YEAR = 365 * 24 * 60 # 525,600 minutes
@dataclass
class Component:
"""A service or resource with a known availability (0..1)."""
name: str
availability: float
def yearly_downtime_min(self) -> float:
return (1 - self.availability) * MINUTES_PER_YEAR
def in_series(name: str, *components: Component) -> Component:
"""All of them must work: the availabilities are multiplied."""
total = 1.0
for c in components:
total *= c.availability
return Component(name, total)
def in_parallel(name: str, *replicas: Component) -> Component:
"""One working is enough: the UNavailabilities are multiplied."""
prob_all_down = 1.0
for r in replicas:
prob_all_down *= (1 - r.availability)
return Component(name, 1 - prob_all_down)
def replicate(component: Component, n: int) -> Component:
"""Shortcut: n independent, identical replicas of a component."""
replicas = [Component(f"{component.name}-{i+1}", component.availability)
for i in range(n)]
return in_parallel(f"{component.name} x{n}", *replicas)
def report(c: Component) -> None:
minutes = c.yearly_downtime_min()
hours, rest = divmod(minutes, 60)
print(f"{c.name:<42} {c.availability*100:8.4f} % "
f"downtime/year: {int(hours):3d} h {rest:4.1f} min")
if __name__ == "__main__":
# Components that a product purchase goes through at Kilometre Zero
web = Component("web", 0.9995)
orders = Component("orders", 0.999)
inventory = Component("inventory", 0.999)
payments = Component("payments", 0.995) # depends on an external gateway
postgres = Component("postgresql", 0.9995)
print("=== Scenario 1: one instance of each component, in series ===")
purchase_v1 = in_series("purchase (all in series)", web, orders, inventory, payments, postgres)
for c in (web, orders, inventory, payments, postgres):
report(c)
print("-" * 80)
report(purchase_v1)
print("\n=== Scenario 2: we replicate the services (2 replicas each) ===")
purchase_v2 = in_series(
"purchase (services x2)",
replicate(web, 2), replicate(orders, 2), replicate(inventory, 2),
replicate(payments, 2), postgres,
)
report(purchase_v2)
print("\n=== Scenario 3: PostgreSQL with a standby replica as well ===")
purchase_v3 = in_series(
"purchase (everything x2)",
replicate(web, 2), replicate(orders, 2), replicate(inventory, 2),
replicate(payments, 2), replicate(postgres, 2),
)
report(purchase_v3)
print("\n=== How many 'payments' replicas does 99.99% take? ===")
for n in range(1, 5):
report(replicate(payments, n))The output is as follows:
=== Scenario 1: one instance of each component, in series === web 99.9500 % downtime/year: 4 h 22.8 min orders 99.9000 % downtime/year: 8 h 45.6 min inventory 99.9000 % downtime/year: 8 h 45.6 min payments 99.5000 % downtime/year: 43 h 48.0 min postgresql 99.9500 % downtime/year: 4 h 22.8 min -------------------------------------------------------------------------------- purchase (all in series) 99.2018 % downtime/year: 69 h 55.2 min === Scenario 2: we replicate the services (2 replicas each) === purchase (services x2) 99.9473 % downtime/year: 4 h 37.1 min === Scenario 3: PostgreSQL with a standby replica as well === purchase (everything x2) 99.9973 % downtime/year: 0 h 14.5 min === How many 'payments' replicas does 99.99% take? === payments x1 99.5000 % downtime/year: 43 h 48.0 min payments x2 99.9975 % downtime/year: 0 h 13.1 min payments x3 100.0000 % downtime/year: 0 h 0.1 min payments x4 100.0000 % downtime/year: 0 h 0.0 min
Let's go through the program and its results:
Componentstores a name and an availability between 0 and 1;yearly_downtime_mintranslates it into minutes of downtime per year, which is the measure the business understands.in_seriesmultiplies availabilities. Look at scenario 1: no individual component drops below 99.5%, yet the full purchase ends up at 99.2%, almost 70 hours a year. The more components an operation goes through, the worse it gets. This is the hidden cost of "breaking things down into services": every network boundary added to a synchronous flow takes availability away.in_parallelandreplicateapply the redundancy rule. Scenario 2 shows the effect: replicating the four services takes the purchase from 99.2% to 99.95%, and now the dominant component is the one that has not been replicated, PostgreSQL (4 h 23 min of the 4 h 37 min total).- Scenario 3 replicates the database too and reaches 99.997%. But be careful: replicating a database is not as simple as replicating a stateless service. The copies have to be kept consistent, and that is the whole of Module 3. The figure is an optimistic upper bound.
- The last block answers a very practical question:
payments, which depends on an external gateway with only 99.5%, goes up to 99.9975% with two replicas. That, however, is only true if the two replicas fail independently. If both depend on the same external gateway and that is what goes down, both go down together and the replica is useless. The model in this script does not capture correlated failures; in reality, you always have to ask what the replicas share (network, power, provider, a software version with the same bug).
Common Mistakes and Tips
- Adding availabilities instead of multiplying them. "Each service has 99.9%, so the system has 99.9%" is false. With five components in series at 99.9%, the system has 99.5%. Every network hop in a synchronous flow is a multiplying factor smaller than one.
- Believing that replicas fail independently. Two replicas in the same rack, with the same software version and the same external dependency, have highly correlated failures. Real redundancy requires diversity (different zones, staggered deployments).
- Scaling the wrong component. Adding application instances when the bottleneck is the database achieves nothing (Amdahl). Measure before you scale.
- Confusing "faster" with "more scalable". A distributed system is usually slower for a single request (network latency) and more capable for many requests (total throughput). If what matters is the latency of an isolated operation, distribution can make it worse.
- Chasing nines the business does not need. Each nine costs roughly an order of magnitude more. Always ask how much each hour of downtime costs the business and compare it with what it costs to avoid it.
- Tip: document the target availability of each business operation (not of each service) and use the series rule to work out what availability that demands of each component. That way you quickly discover which components need redundancy.
Exercises
Exercise 1: Capacity planning
The Kilometre Zero team measures that one instance of the catalog service handles 350 queries/s at 60% CPU, and that above 80% CPU latency shoots up. For Artisan Cheese Week, 2,100 queries/s to the catalog are expected.
- How many queries/s can one instance handle without exceeding 80% CPU (assuming a linear relationship between queries and CPU)?
- How many instances are needed for the peak, with N+1 redundancy?
- If half of the queries could be answered from an in-memory cache without reaching the service, how many instances would be needed?
Exercise 2: Downtime budget
The business decides that the "check the status of my order" operation must have 99.95% availability. The operation goes through, in series: the gateway (99.99%), the orders service (X) and its database (99.98%).
- How many minutes of downtime per year does the 99.95% target allow?
- What minimum availability X does
ordersneed for the operation to meet the target? - If one instance of
ordersonly achieves 99.9%, are two independent replicas enough?
Exercise 3: Amdahl in sales processing
The analytics service processes the day's sales in a job that takes 60 minutes on one machine. An analysis shows that 6 minutes are spent reading and consolidating the final result into a single file (sequential) and the remaining 54 on per-city calculations (parallelisable).
- Calculate the speedup with 6 machines and with 18 machines.
- What is the minimum possible time, however many machines are added?
- Modify the script from section 8 (or write a new one) with a function
amdahl(p, n)and use it to print a table of speedups for n = 1, 2, 4, 8, 16, 32.
Solutions
Solution 1:
- If 350 queries/s use 60% of the CPU, 80% corresponds to 350 × (80 / 60) ≈ 467 queries/s per instance.
- 2,100 / 467 = 4.5 → 5 instances are needed for the peak, 6 with N+1 redundancy.
- If the cache absorbs half, 1,050 queries/s reach the service: 1,050 / 467 = 2.25 → 3 instances (4 with N+1). Notice that a cache (lesson 04-05) halves the number of machines: reducing the work is usually more cost-effective than adding machines.
Solution 2:
- 0.05% of 525,600 minutes = 262.8 minutes (about 4 h 23 min) per year.
- The availability in series is 0.9999 × X × 0.9998 ≥ 0.9995. Solving for X: X ≥ 0.9995 / (0.9999 × 0.9998) = 0.9995 / 0.99970002 ≈ 0.99980 (99.98%). In other words,
ordersneeds almost the same availability as the overall target, because the other two components already "spend" part of the budget. - Two independent replicas at 99.9%: 1 − (0.001)² = 0.999999 (99.9999%), well above the 99.98% required. Yes, they are enough, provided that the failure of one does not drag the other down and that the load balancer in front of them does not become the new single point of failure (it would have to be included in the series chain).
Solution 3:
- The parallelisable fraction is p = 54 / 60 = 0.9. With n = 6: S = 1 / (0.1 + 0.9 / 6) = 1 / 0.25 = 4.0 (the job takes 15 minutes). With n = 18: S = 1 / (0.1 + 0.9 / 18) = 1 / 0.15 = 6.67 (9 minutes). Tripling the machines only improves the time from 15 to 9 minutes.
- The minimum time is the sequential part: 6 minutes (maximum speedup 1 / 0.1 = 10). To go below that you have to attack the final consolidation, for example by writing partial per-city results in parallel (the pattern MapReduce follows, lesson 05-02).
def amdahl(p: float, n: int) -> float:
"""Amdahl speedup with parallelisable fraction p and n machines."""
return 1 / ((1 - p) + p / n)
if __name__ == "__main__":
p = 54 / 60
base_time_min = 60
print(f"{'n':>4} {'speedup':>12} {'time (min)':>13} {'efficiency':>11}")
for n in (1, 2, 4, 8, 16, 32):
s = amdahl(p, n)
print(f"{n:>4} {s:>12.2f} {base_time_min / s:>13.1f} {s / n * 100:>10.0f} %")Output:
n speedup time (min) efficiency 1 1.00 60.0 100 % 2 1.82 33.0 91 % 4 3.08 19.5 77 % 8 4.71 12.7 59 % 16 6.40 9.4 40 % 32 7.80 7.7 24 %
The efficiency column (what fraction of each machine is actually put to use) is the one whoever pays the bill should be looking at: with 32 machines, less than a quarter of each one is being used.
Conclusion
Distributing a system brings real, measurable advantages: horizontal scalability (with a capacity calculation that, in the case of Artisan Cheese Week, replaces one enormous machine with nine modest, elastic ones), availability (where we have learnt to translate the "nines" into minutes of downtime and to combine components in series and in parallel), locality (serving each city from nearby), flexibility (each team at its own pace) and resource sharing. But we have also seen that scaling has a ceiling, Amdahl's law, set by the sequential fraction of the work (usually data access), and that every network boundary we add to a synchronous flow multiplies availability by a factor smaller than one.
The challenges (complexity, heterogeneity, consistency, partial failures, latency, security, observability and operational cost) are the table of contents for the rest of the course. And the "when not to distribute" table is a reminder that the right answer to many problems is still a well-built monolith.
Many of these challenges stem from false assumptions that we programmers make about the network without realising it. The next lesson, The Fallacies of Distributed Computing, names them one by one and shows, with code, what happens when we believe them to be true.
Distributed Architectures Course
Module 1: Introduction to Distributed Systems
- Basic Concepts of Distributed Systems
- Distributed System Models
- Advantages and Challenges of Distributed Systems
- The Fallacies of Distributed Computing
- Time, Clocks and Event Ordering
- From Monolith to Distributed Platform: the Kilometre Zero Case
Module 2: Communication in Distributed Systems
- Communication Protocols
- RPC and RMI
- gRPC and Data Serialization
- Messaging and Message Queues
- Asynchronous Communication Patterns
Module 3: Consistency and Replication
- Consistency Models
- The CAP Theorem and PACELC
- Consensus Algorithms
- Data Replication
- Distributed Transactions and Sagas
Module 4: Distributed Storage
- Data Partitioning and Consistent Hashing
- Distributed File Systems
- Object Storage
- Distributed Databases
- Distributed Caches
Module 5: Distributed Computing
- Distributed Computing Models
- MapReduce and Hadoop
- Spark and In-Memory Computing
- Stream Processing
- Job Scheduling and Data Pipelines
Module 6: Security in Distributed Systems
- Authentication and Authorization
- Encryption and Data Protection
- Identity Management
- Service-to-Service Security: mTLS and Secrets Management
- API Gateways, Rate Limiting and Auditing
Module 7: Monitoring and Maintenance
- Monitoring Distributed Systems
- Centralized Logs and Distributed Tracing
- Failure Management and Recovery
- Resilience Patterns: Timeouts, Retries and Circuit Breakers
- Automation and Orchestration
- Testing Distributed Systems and Chaos Engineering
