Chapter 2  ·  Distributed Databases Lab

MediAI Distributed
Database Platform

A hands-on lab where you deploy a real distributed database cluster using Citus + PostgreSQL, implement fragmentation, run distributed queries, and simulate 2-phase commit transactions across 4 geographic sites.

4
Geographic Sites
Citus
Distribution Engine
3
Fragmentation Types
2PC
Distributed Transactions
Before you start

Setup & Execution Guide

Follow these 6 steps in order before writing any SQL. Steps 1–3 are required for everyone. Docker (Steps 4–6) is the core environment for this lab.

1
Accept the GitHub Classroom Assignment
Creates your personal private repository with all lab files
Click the invitation link sent by your professor
Format: https://classroom.github.com/a/xxxxxxxx — opens GitHub Classroom.
Sign in with your GitHub account
No account? Create one free at github.com before clicking the link.
Click "Accept this assignment"
Wait 30 seconds, then refresh. GitHub creates your private repo automatically.
✓ Your repo is ready
github.com/[org]/tp-distributed-[your-username] — only you and the professor can see it.

💡 Isolated workspace

Your pushes go into YOUR repo inside the Classroom organisation — never into the professor's template. 74 students = 74 separate repos, all isolated.

2
Clone your repository to your PC
Download the lab files — SQL scripts, schema, exercises
PowerShell / Terminal
# Open PowerShell (Windows) or Terminal (Mac/Linux)

# Go to Desktop
cd Desktop

# Clone — replace YOUR-USERNAME with your GitHub username
git clone https://github.com/[org]/tp-distributed-YOUR-USERNAME.git

# Enter the folder
cd tp-distributed-YOUR-USERNAME

# Verify the files are there
dir   # Windows
ls    # Mac / Linux

# Expected output :
README.md  data/  docker-compose.yml  exercises/  solutions/

⚠️ Git not installed?

Download at git-scm.com/downloads → install → reopen your terminal → retry the commands above.

3
Install Docker Desktop
Runs the entire 4-node Citus cluster with one command

🐳 What is Docker?

Docker packages software into isolated "containers". Instead of manually installing PostgreSQL + Citus on every PC, one command docker-compose up -d launches the full 4-node cluster — identical on every machine in the class.

Install Docker Desktop
Go to docker.com/products/docker-desktop
Download for Windows / Mac / Linux
Run installer → restart PC
On Windows: accept WSL 2 activation when asked — it's mandatory
Open Docker Desktop
Wait until the 🐳 whale icon in the taskbar turns green
Verify it works
Terminal
# Check version
docker --version
# Docker version 24.x.x ✓

# Quick test
docker run hello-world
# "Hello from Docker!" ✓
Error Cannot connect to daemon

Docker Desktop is not open. Start it from Start Menu / Applications and wait for the green whale.

Error WSL 2 not installed
Fix — PowerShell as Admin
wsl --install
# Restart PC, then reopen Docker Desktop
4
Launch the Citus Cluster
1 coordinator + 3 worker nodes — all in one command
Launch — inside your lab folder
# Make sure you are in the right folder
cd Desktop/tp-distributed-YOUR-USERNAME

# Start the 4-node cluster (first time = ~4 min download)
docker-compose up -d

# Verify all 4 containers are running
docker ps

# Expected — you must see 4 containers:
CONTAINER ID  IMAGE                  STATUS
aaa111        citusdata/citus:12.1   Up (citus_master)
bbb222        citusdata/citus:12.1   Up (citus_worker1)
ccc333        citusdata/citus:12.1   Up (citus_worker2)
ddd444        citusdata/citus:12.1   Up (citus_worker3)
Error Download fails / connection timeout

University network may block Docker Hub. Solutions:

Fix
# Option A: use your phone hotspot, then:
docker-compose up -d

# Option B: pre-download at home before the lab:
docker pull citusdata/citus:12.1

💡 What just happened?

Docker launched 4 PostgreSQL+Citus containers that talk to each other on an internal network. citus_master is the coordinator — all your SQL goes there. The 3 workers store the actual shards.

5
Load the Schema and Seed Data
Create tables, distribute them, insert test data
Load all scripts in order
# 1. Connect to the coordinator and create tables
docker exec -it citus_master psql -U postgres -d mediAI \
  -f /data/schema-mediAI.sql

# 2. Register workers + distribute tables with Citus
docker exec -it citus_master psql -U postgres -d mediAI \
  -f /data/init-cluster.sql

# 3. Insert 20 patients + medical records + transactions
docker exec -it citus_master psql -U postgres -d mediAI \
  -f /data/seed-mediAI.sql

# 4. Verify everything loaded correctly
docker exec -it citus_master psql -U postgres -d mediAI -c \
  "SELECT 'Patients' AS t, COUNT(*) FROM Patients
   UNION ALL
   SELECT 'MedicalRecords', COUNT(*) FROM MedicalRecords
   UNION ALL
   SELECT 'TrainingData', COUNT(*) FROM TrainingData
   UNION ALL
   SELECT 'Transactions', COUNT(*) FROM Transactions;"

# Expected:
 t              | count
----------------+-------
 Patients       |    20
 MedicalRecords |    14
 TrainingData   |    13
 Transactions   |    18

✅ If you see those numbers — you are ready

All 4 tables are created and distributed across the 3 worker nodes by Citus. You can now start the lab exercises.

6
Connect and Run Your First Query
Two ways to interact with the cluster
Connect via psql
# Connect to the coordinator (master)
docker exec -it citus_master psql -U postgres -d mediAI

# You are now inside psql. Try:
SELECT name, country, siteOrigin
FROM Patients
WHERE country = 'France'
LIMIT 5;

# Exit psql:
\q

DBeaver — recommended for beginners

Download free at dbeaver.io. Then create a new connection with these settings:

DBeaver connection settings
Type     : PostgreSQL
Host     : localhost
Port     : 5432
Database : mediAI
Username : postgres
Password : postgres

🚀 You are fully set up!

Citus cluster running, tables distributed, data loaded. Start with Part 1 — Fragmentation and work through each section in order.

System Design

MediAI Cluster Architecture

MediAI runs on a Citus cluster: 1 coordinator (Paris HQ) + 3 worker nodes (Tunis, Montréal, Tokyo). The coordinator receives all queries and routes them to the right workers transparently.

Your SQL queries psql / DBeaver COORDINATOR citus_master · Paris HQ · port 5432 Query planner · Shard router · Result merger 🇫🇷 Worker 1 Tunis · EU-South citus_worker1 Tunisia shards 🇨🇦 Worker 2 Montréal · NA citus_worker2 Canada shards 🇫🇷 Worker 3 Tokyo · APAC citus_worker3 Japan shards 🇫🇷 Coordinator Paris · EU-West citus_master France shards 32 shards distributed across 3 workers Patients · MedicalRecords · TrainingData · Transactions — each distributed by country / siteOrigin

Table Distribution Strategy

TableDistribution KeyType🇫🇷 Paris🇹🇳 Tunis🇨🇦 Montréal🇯🇵 Tokyo
PatientscountryHorizontalFranceTunisiaCanadaJapan
MedicalRecordscountryHorizontalco-locco-locco-locco-loc
TrainingDatasiteOriginHorizontalParisTunisMontrealTokyo
TransactionscountryHybridFranceTunisiaCanadaJapan
Part 1

Fragmentation

Fragmentation divides a table across multiple sites. You will implement all three types: horizontal (rows), vertical (columns), and hybrid (both combined).

What is horizontal fragmentation?

Split a table into subsets of rows based on a predicate. Each site stores only the rows that belong to it. The union of all fragments reconstructs the full table.

Formal notation:
F_Paris = σ(siteOrigin = 'Paris') (TrainingData)
F_Tunis = σ(siteOrigin = 'Tunis') (TrainingData)
F_Montreal = σ(siteOrigin = 'Montreal') (TrainingData)
F_Tokyo = σ(siteOrigin = 'Tokyo') (TrainingData)

Key properties to verify

Completeness: every row belongs to at least one fragment.
Disjointness: no row appears in two fragments (for H fragmentation).
Reconstructibility: UNION of all fragments = original table.

EXERCISE 1.1a
Create horizontal fragments of TrainingData
Write 4 SQL views — one per site — that implement horizontal fragmentation of TrainingData by siteOrigin.
SQL — complete the blanks
-- Fragment Paris
CREATE OR REPLACE VIEW TrainingData_Paris AS
  SELECT * FROM TrainingData
  WHERE siteOrigin = ___;   ← complete

-- Fragment Tunis
CREATE OR REPLACE VIEW TrainingData_Tunis AS
  SELECT * FROM TrainingData
  WHERE ___ = 'Tunis';      ← complete

-- TODO: write views for Montreal and Tokyo
___
EXERCISE 1.1b
Verify completeness
Write a query that counts rows per fragment and checks the total matches the full table.

What is vertical fragmentation?

Split a table into subsets of columns based on access patterns. Each fragment contains the primary key + a subset of attributes. Used when different user groups need different columns.

MedicalRecords split:
FA = Π(idRecord, idPatient, country, date, examType, result)
→ Clinical data — used by doctors
FB = Π(idRecord, idPatient, country, aiModelUsed, aiScore, aiVersion)
→ AI data — used by data scientists

⚠️ idRecord must appear in BOTH fragments

This is the reconstructibility condition. The JOIN on idRecord must rebuild the original table perfectly.

EXERCISE 1.2a
Why split clinical from AI data?
Give 2 reasons why MedicalRecords should be vertically fragmented into a clinical fragment and an AI fragment.
EXERCISE 1.2b
Create physical vertical fragments
Create two separate tables implementing the vertical fragments. Then write the INSERT to populate them from MedicalRecords. Finally, write the JOIN that reconstructs the full table.

What is hybrid fragmentation?

Apply horizontal fragmentation first, then vertical fragmentation on each horizontal fragment — or vice versa. The result is a matrix of fragments. Applied here to the Transactions table.

Transactions(idTrans, idPatient, country, date, type, amount, currency, status) Step 1 — Horizontal split by country H_France country='France' H_Tunisia country='Tunisia' H_Canada country='Canada' H_Japan country='Japan' Step 2 — Vertical split on each fragment FR_FIN amount currency FR_MGT type status TN_FIN amount currency TN_MGT type status CA_FIN amount currency CA_MGT type status JP_FIN amount currency JP_MGT type status Result: 8 hybrid fragments (4 countries × 2 column sets) All contain: idTrans, idPatient, date (JOIN keys)
EXERCISE 1.3
Implement all 8 hybrid fragments as SQL views
The France fragments are done as examples. Write the remaining 6 views (Tunisia, Canada, Japan × Financial + Management). Then write the JOIN that reconstructs France's full Transactions.
SQL — France example given, complete the rest
-- France Financial
CREATE OR REPLACE VIEW Trans_FR_Financial AS
  SELECT idTrans, idPatient, date, amount, currency
  FROM Transactions WHERE country = 'France';

-- France Management
CREATE OR REPLACE VIEW Trans_FR_Management AS
  SELECT idTrans, idPatient, type, status
  FROM Transactions WHERE country = 'France';

-- TODO: Tunisia, Canada, Japan (6 more views)
___

-- TODO: Reconstruction JOIN for France
SELECT ___
FROM Trans_FR_Financial fin
JOIN Trans_FR_Management mgt ON ___ = ___;

Citus — How distribution works in practice

Citus implements horizontal fragmentation automatically. You declare the distribution key — Citus creates shards and places them on workers.

init-cluster.sql — register workers + distribute
-- Step 1: Register the 3 workers in the coordinator
SELECT citus_add_node('citus_worker1', 5432);
SELECT citus_add_node('citus_worker2', 5432);
SELECT citus_add_node('citus_worker3', 5432);

-- Verify all 3 workers are active
SELECT nodeid, nodename, nodeport, isactive
FROM pg_dist_node ORDER BY nodeid;

-- Step 2: Distribute tables
SELECT create_distributed_table('Patients',      'country');
SELECT create_distributed_table('TrainingData',  'siteOrigin');
SELECT create_distributed_table('MedicalRecords','country');
SELECT create_distributed_table('Transactions',  'country');

-- Step 3: Inspect shards
SELECT s.logicalrelid, s.shardid, p.nodename
FROM pg_dist_shard s
JOIN pg_dist_shard_placement p ON s.shardid = p.shardid
ORDER BY s.logicalrelid, s.shardid;
EXERCISE 1.4
Inspect the shard distribution
After running init-cluster.sql, query pg_dist_shard to find: how many shards does each table have? Which worker stores TrainingData rows from Tokyo?
Part 2

Replication & Allocation

Replication keeps copies of data on multiple nodes to improve availability and read performance. Allocation decides which fragments go on which sites.

Replication Factor

Citus replicates each shard across multiple workers. A replication factor of 2 means each shard exists on 2 different workers — if one fails, the other still serves the data.

SQL
-- Set replication factor to 2
SET citus.shard_replication_factor = 2;

-- Check current factor
SHOW citus.shard_replication_factor;

-- See all shard placements
SELECT shardid, nodename, nodeport, shardstate
FROM pg_dist_shard_placement
ORDER BY shardid;
EXERCISE 2.1
Check shard placements
Run the query above. How many placements does each shard have? What does shardstate = 1 mean?
Allocation Strategies
StrategyDescriptionUsed in MediAI
PartitionedEach fragment on exactly 1 siteTrainingData
ReplicatedEach fragment on all sitesSchema (DDL)
MixedHot data replicated, cold partitionedPossible
EXERCISE 2.2
Allocation decision
For each table below, justify which allocation strategy is most appropriate for MediAI and why.
Part 3

Distributed Queries

Write SQL queries that Citus transparently distributes across worker nodes. Use EXPLAIN to understand where each query runs and how results are merged.

Q1 — Full patient profile with medical records
-- Citus routes this to a single worker (co-located JOIN on country)
SELECT
  p.name, p.age, p.city, p.country,
  mr.date, mr.examType, mr.result,
  mr.aiModelUsed, mr.aiScore
FROM Patients p
JOIN MedicalRecords mr
  ON p.idPatient = mr.idPatient
  AND p.country  = mr.country   ← distribution key must match
WHERE p.name = 'Mohamed Benali'
ORDER BY mr.date DESC;
EXERCISE 3.1
Analyse the execution plan
Run EXPLAIN (VERBOSE) on the query above. Answer: which worker(s) execute it? Why does co-locating Patients and MedicalRecords on the same country key avoid a cross-node JOIN?
EXERCISE 3.2
AI model performance by site
Write a query that returns, for each site and AI model, the number of exams and the average AI score — sorted by best average score first.
EXERCISE 3.3
Write your own distributed query
Write an original SQL query that joins at least 2 tables, uses an aggregation function (COUNT, AVG, SUM), and includes a filter on a non-distribution-key column. Explain the business question it answers.
Part 4

Distributed Transactions — 2PC

Two-Phase Commit (2PC) guarantees that a transaction spanning multiple nodes is either fully committed on ALL nodes or fully rolled back on ALL nodes — atomicity at scale.

COORDINATOR citus_master · manages 2PC PHASE 1 PREPARE PREPARE PREPARE PREPARE Worker 1 writes WAL log Worker 2 writes WAL log Worker 3 writes WAL log READY ✓ READY ✓ READY ✓ PHASE 2 COMMIT All READY → send COMMIT If any ABORT → send ROLLBACK to all COMMIT COMMIT COMMIT ✓ committed ✓ committed ✓ committed
EXERCISE 4.1
Explain 2PC in your own words
Describe Phase 1 (Prepare) and Phase 2 (Commit) in your own words. What happens if Worker 2 responds ABORT in Phase 1? Why is 2PC called a "blocking protocol"?

Scenario

Patient Yuki Tanaka (Tokyo) has an emergency consultation in Paris. Two operations must be atomic: create a medical record on the Tokyo worker AND charge a transaction on the France worker.

SQL — 2PC with PREPARE TRANSACTION
-- Phase 1: open transaction and prepare it
BEGIN;

INSERT INTO MedicalRecords
  (idPatient, country, date, examType, result, aiModelUsed, aiScore, aiVersion)
VALUES
  (16, 'Japan', NOW()::DATE, 'Emergency consultation',
   'General check — patient travelling', 'DiagNet-3', 0.8934, 'v3.2');

INSERT INTO Transactions
  (idPatient, country, date, type, amount, currency, status)
VALUES
  (16, 'Japan', NOW(), 'consultation', 15000, 'JPY', 'pending');

PREPARE TRANSACTION 'mediAI_emergency_yuki_001';

-- Check the prepared transaction is waiting
SELECT gid, prepared, owner, database
FROM pg_prepared_xacts;

-- Phase 2a: all workers ready → COMMIT
COMMIT PREPARED 'mediAI_emergency_yuki_001';

-- Phase 2b: simulate failure → ROLLBACK
-- ROLLBACK PREPARED 'mediAI_emergency_yuki_001';
EXERCISE 4.2
Run and observe the 2PC
Execute the PREPARE TRANSACTION block. Then run the pg_prepared_xacts query. What does the gid field contain? Then COMMIT the transaction and verify the data was inserted correctly.
EXERCISE 4.3a
Simulate a worker failure
Start a transaction, prepare it, then stop a worker. What happens when you try to COMMIT? How does 2PC protect data integrity in this case?
Simulate failure
# In a SEPARATE terminal:
docker stop citus_worker3

# Back in psql — try to commit:
COMMIT PREPARED 'your_txn_id';
# Observe the error message

# Restart the worker:
docker start citus_worker3
EXERCISE 4.3b
2PC limitations
Answer these 3 questions in your own words:
1. What happens if the coordinator itself crashes between Phase 1 and Phase 2?
2. Why is 2PC called "blocking"?
3. Name one alternative to 2PC used in high-availability systems.
Submission

How to Submit Your Work

No email. No PDF. Your GitHub repository is your submission. Every commit you push is timestamped and visible to the professor.

📁 What to submit
exercises/01-TP-Pratique-...MediAI.md
The main exercise file — with all your SQL queries written in the answer blocks and results copy-pasted from psql as plain text.
Any additional .sql files you created
If you wrote extra scripts for fragmentation views or your own queries — include them in the data/ folder.
❌ No screenshots
Copy-paste your psql output as text into the .md file. Screenshots cannot be verified and are not accepted.
✍️ How to write answers
Example
### Exercise 3.2 — AI model performance

**My query:**
```sql
SELECT p.siteOrigin, mr.aiModelUsed,
  COUNT(*) AS nb_exams,
  ROUND(AVG(mr.aiScore)::numeric,4)
FROM MedicalRecords mr
JOIN Patients p ON ...
GROUP BY ...
ORDER BY avg_score DESC;
```

**Result from psql:**
```
 siteOrigin | aiModelUsed  | nb_exams | avg_score
------------+--------------+----------+-----------
 Tunis      | NephroAI-1   |        2 |    0.9812
 Paris      | DiagNet-3    |        1 |    0.9812
 ...
```
Git workflow — commit after each part
# After Part 1 — Fragmentation
git add .
git commit -m "Part 1 done – horizontal, vertical, hybrid fragmentation"
git push

# After Part 2 — Replication
git add .
git commit -m "Part 2 done – replication and allocation analysis"
git push

# After Part 3 — Distributed Queries
git add .
git commit -m "Part 3 done – distributed SQL queries with EXPLAIN"
git push

# After Part 4 — 2PC
git add .
git commit -m "Part 4 done – 2PC simulation and failure scenarios"
git push

# FINAL submission
git add .
git commit -m "FINAL SUBMISSION – all parts completed"
git push

# ✓ Your last push before the deadline = your submission
🧠
Mandatory Reflection Questions
Answer at the end of your .md file — in your own words. Cannot be copy-pasted.
Q1 — Difficulty

What was the hardest part of this lab? Describe one specific error you encountered and how you solved it. (min 3 sentences)

Q2 — Fragmentation

In your own words — without the course slides — explain the difference between horizontal and vertical fragmentation. Give a concrete MediAI example for each.

Q3 — Co-location

Why does Citus distribute Patients and MedicalRecords using the same key (country)? What would happen to JOIN performance if they used different keys?

Q4 — 2PC in practice

In a real hospital, a patient record creation and its billing must be atomic. Is 2PC the right choice? What are the trade-offs in terms of availability vs consistency?

📌 Submission Rules

✅  Your last git push before the deadline is your submission.
✅  Push after each part — progressive commits show real work.
✅  Paste psql results as plain text — no screenshots.
✅  Answer all 4 reflection questions in your own words.
❌  Do not modify the solutions/ folder — modifications are tracked.
❌  One commit at 23:58 with everything done = suspicious — commit progressively.