diff --git a/q4-scalability/architecture-diagram.png b/q4-scalability/architecture-diagram.png new file mode 100755 index 0000000..a917d48 Binary files /dev/null and b/q4-scalability/architecture-diagram.png differ diff --git a/q4-scalability/architecture.md b/q4-scalability/architecture.md new file mode 100644 index 0000000..7a00538 --- /dev/null +++ b/q4-scalability/architecture.md @@ -0,0 +1,49 @@ +# Scalable Architecture Design + +## Overview + +The application is designed to handle up to **15,000 requests per second (req/s)** on Ghaymah using multiple application containers behind a Load Balancer. + +The architecture distributes incoming traffic evenly across multiple container instances to ensure high availability, scalability, and fault tolerance. + +--- + +## Architecture Components + +- Internet Users +- Ghaymah Load Balancer +- Multiple Application Containers +- Ghaymah Block Storage +- Database + +--- + +## Traffic Flow + +1. Users send requests to the application. +2. The Load Balancer receives all incoming traffic. +3. Requests are distributed evenly across the application containers. +4. Each container processes the request. +5. Stateful data is stored in the database using Ghaymah Block Storage. + +--- + +## High Availability + +If one container becomes unavailable, the Load Balancer automatically routes traffic to healthy containers without affecting users. + + Internet Users + │ + ▼ + Ghaymah Load Balancer + │ + ┌───────────────┼───────────────┐ + ▼ ▼ ▼ + App Container App Container App Container + │ │ │ + └───────────────┼───────────────┘ + │ + Database + │ + ▼ + Ghaymah Block Storage \ No newline at end of file diff --git a/q4-scalability/block-storage.md b/q4-scalability/block-storage.md new file mode 100644 index 0000000..2b1223a --- /dev/null +++ b/q4-scalability/block-storage.md @@ -0,0 +1,49 @@ +# Ghaymah Block Storage for Stateful Workloads + +## Overview + +Stateful applications store persistent data that must remain available even if a container is restarted, replaced, or moved to another node. + +Ghaymah Block Storage provides persistent storage that can be attached to stateful workloads such as databases. + +--- + +## Common Stateful Workloads + +- MySQL +- PostgreSQL +- MongoDB +- Redis (Persistent Mode) + +--- + +## How It Works + +1. The database container stores its data on a Ghaymah Block Storage volume. +2. If the container fails, a new container can be started. +3. The same Block Storage volume is reattached. +4. The application continues using the existing data. + +--- + +## Benefits + +- Persistent data storage +- High durability +- Faster recovery after failures +- Data remains available after container restarts +- Suitable for production database workloads + +--- + +## Example Architecture + +```text +Application Containers + │ + ▼ + Database Container + │ + ▼ + Ghaymah Block Storage +``` \ No newline at end of file diff --git a/q4-scalability/cold-start-strategy.md b/q4-scalability/cold-start-strategy.md new file mode 100644 index 0000000..028302f --- /dev/null +++ b/q4-scalability/cold-start-strategy.md @@ -0,0 +1,38 @@ +# Cold Start Strategy + +## Overview + +A cold start occurs when a new application container is created to handle increased traffic. During startup, the container needs time to initialize before it can receive requests. + +--- + +## Strategy + +### 1. Keep Warm Containers + +Maintain a small number of standby containers that are already running and ready to receive traffic immediately. + +### 2. Auto Scaling + +Automatically create additional containers when CPU usage, memory usage, or request rate exceeds predefined thresholds. + +### 3. Lightweight Container Images + +Use small Docker images to reduce image download and startup time. + +### 4. Health Checks + +Only send traffic to containers after they successfully pass health checks. + +### 5. Container Image Caching + +Cache frequently used container images on the compute nodes to avoid downloading them repeatedly. + +--- + +## Benefits + +- Faster application startup +- Lower request latency +- Better user experience during traffic spikes +- Improved application availability \ No newline at end of file diff --git a/q4-scalability/container-calculation.md b/q4-scalability/container-calculation.md new file mode 100644 index 0000000..3e55785 --- /dev/null +++ b/q4-scalability/container-calculation.md @@ -0,0 +1,41 @@ +# Container Capacity Calculation + +## Given + +- Expected traffic: **15,000 requests/second** +- Capacity per container: **500 requests/second** +- Safety margin: **30%** + +--- + +## Step 1: Calculate Minimum Containers + +Minimum Containers = Total Requests ÷ Capacity per Container + +``` +15000 ÷ 500 = 30 Containers +``` + +--- + +## Step 2: Add Safety Margin + +``` +30 × 30% = 9 Containers +``` + +--- + +## Total Required Containers + +``` +30 + 9 = 39 Containers +``` + +--- + +## Final Result + +To safely handle **15,000 requests per second**, the application should run **39 application containers**. + +The additional containers provide extra capacity during traffic spikes and improve overall reliability. \ No newline at end of file diff --git a/q5-mithal-monitor/Dockerfile b/q5-mithal-monitor/Dockerfile new file mode 100644 index 0000000..025be5f --- /dev/null +++ b/q5-mithal-monitor/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.12-slim + +WORKDIR /app + +COPY . . + +RUN pip install --no-cache-dir -r requirements.txt + +RUN chmod +x start.sh + +EXPOSE 8000 + +CMD ["./start.sh"] \ No newline at end of file diff --git a/q5-mithal-monitor/README.md b/q5-mithal-monitor/README.md new file mode 100644 index 0000000..e69de29 diff --git a/q5-mithal-monitor/dashboard/app.js b/q5-mithal-monitor/dashboard/app.js new file mode 100644 index 0000000..568e63e --- /dev/null +++ b/q5-mithal-monitor/dashboard/app.js @@ -0,0 +1,120 @@ +async function loadData() { + + try { + + const response = await fetch("data.json?t=" + Date.now()); + const data = await response.json(); + + if (data.length === 0) { + return; + } + + const latest = data[data.length - 1]; + + const upCount = data.filter(item => item.uptime === "UP").length; + const uptimePercentage = ((upCount / data.length) * 100).toFixed(1); + + document.getElementById("uptime").innerText = + uptimePercentage + "%"; + + document.getElementById("latency").innerText = + (latest.latency_ms ?? "-") + " ms"; + + document.getElementById("dns").innerText = + (latest.dns_ms ?? "-") + " ms"; + + if (latest.ssl && latest.ssl.valid) { + + document.getElementById("ssl").innerHTML = ` + Valid
+ ${latest.ssl.expires} + `; + + } else { + + document.getElementById("ssl").innerHTML = ` + Invalid + `; + + } + + const table = document.getElementById("historyTable"); + table.innerHTML = ""; + + data + .slice() + .reverse() + .forEach(item => { + + table.innerHTML += ` + + ${item.timestamp} + ${item.uptime} + ${item.latency_ms ?? "-"} + ${item.dns_ms ?? "-"} + ${item.ssl && item.ssl.valid ? "Valid" : "Invalid"} + + `; + + }); + + + const labels = data.map(item => item.timestamp); + const latency = data.map(item => item.latency_ms); + + if (!window.latencyChart) { + + window.latencyChart = new Chart( + document.getElementById("latencyChart"), + { + type: "line", + + data: { + labels: labels, + datasets: [{ + label: "Latency (ms)", + data: latency, + borderColor: "#0b7dda", + backgroundColor: "rgba(11,125,218,0.15)", + fill: true, + tension: 0.3 + }] + }, + + options: { + responsive: true, + plugins: { + legend: { + display: true + } + }, + scales: { + y: { + beginAtZero: false + } + } + } + } + ); + + } else { + + window.latencyChart.data.labels = labels; + window.latencyChart.data.datasets[0].data = latency; + window.latencyChart.update(); + + } + + } + + catch (error) { + + console.error(error); + + } + +} + +loadData(); + +setInterval(loadData, 60000); diff --git a/q5-mithal-monitor/dashboard/data.json b/q5-mithal-monitor/dashboard/data.json new file mode 100644 index 0000000..c598310 --- /dev/null +++ b/q5-mithal-monitor/dashboard/data.json @@ -0,0 +1,62 @@ +[ + { + "timestamp": "2026-07-28 17:50:54", + "status_code": 200, + "uptime": "UP", + "latency_ms": 994.55, + "dns_ms": 39.1, + "search_response_ms": 1170.81, + "ssl": { + "valid": true, + "expires": "Sep 15 13:10:47 2026 GMT" + } + }, + { + "timestamp": "2026-07-28 17:50:58", + "status_code": 200, + "uptime": "UP", + "latency_ms": 986.42, + "dns_ms": 40.91, + "search_response_ms": 1130.27, + "ssl": { + "valid": true, + "expires": "Sep 15 13:10:47 2026 GMT" + } + }, + { + "timestamp": "2026-07-28 17:51:01", + "status_code": 200, + "uptime": "UP", + "latency_ms": 980.57, + "dns_ms": 51.6, + "search_response_ms": 1168.75, + "ssl": { + "valid": true, + "expires": "Sep 15 13:10:47 2026 GMT" + } + }, + { + "timestamp": "2026-07-28 17:51:04", + "status_code": 200, + "uptime": "UP", + "latency_ms": 969.79, + "dns_ms": 40.75, + "search_response_ms": 1209.84, + "ssl": { + "valid": true, + "expires": "Sep 15 13:10:47 2026 GMT" + } + }, + { + "timestamp": "2026-07-28 17:51:07", + "status_code": 200, + "uptime": "UP", + "latency_ms": 976.47, + "dns_ms": 42.13, + "search_response_ms": 1147.52, + "ssl": { + "valid": true, + "expires": "Sep 15 13:10:47 2026 GMT" + } + } +] \ No newline at end of file diff --git a/q5-mithal-monitor/dashboard/index.html b/q5-mithal-monitor/dashboard/index.html new file mode 100644 index 0000000..7af797d --- /dev/null +++ b/q5-mithal-monitor/dashboard/index.html @@ -0,0 +1,94 @@ + + + + + + + Mithal Monitoring Dashboard + + + + + + + + +
+ +

Mithal Monitoring Dashboard

+ +

+ Website Monitoring Dashboard for mithal.space +

+ +
+ +
+

Uptime

+

--

+
+ +
+

Latency

+

--

+
+ +
+

DNS Time

+

--

+
+ +
+

SSL

+

--

+
+ +
+ +
+ +

Latency History

+ + + +
+ +
+ +

Last 10 Checks

+ + + + + + + + + + + + + + + + + + + + + + + + + +
TimeStatusLatency (ms)DNS (ms)SSL
+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/q5-mithal-monitor/dashboard/style.css b/q5-mithal-monitor/dashboard/style.css new file mode 100644 index 0000000..72fd044 --- /dev/null +++ b/q5-mithal-monitor/dashboard/style.css @@ -0,0 +1,122 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: Arial, Helvetica, sans-serif; + background: #f4f7fb; + color: #333; +} + +.container { + width: 95%; + max-width: 1300px; + margin: 40px auto; +} + +h1 { + text-align: center; + margin-bottom: 10px; + color: #2c3e50; +} + +.subtitle { + text-align: center; + margin-bottom: 35px; + color: gray; +} + +.cards { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 20px; + margin-bottom: 35px; +} + +.card { + background: white; + border-radius: 10px; + padding: 25px; + text-align: center; + box-shadow: 0 2px 8px rgba(0,0,0,.08); +} + +.card h2 { + margin-bottom: 15px; + color: #555; + font-size: 20px; +} + +.card p { + font-size: 30px; + font-weight: bold; + color: #0b7dda; +} + +.chart-card, +.table-card { + background: white; + border-radius: 10px; + padding: 25px; + margin-top: 30px; + box-shadow: 0 2px 8px rgba(0,0,0,.08); +} + +.chart-card h2, +.table-card h2 { + margin-bottom: 20px; +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background: #0b7dda; + color: white; +} + +th, +td { + padding: 12px; + text-align: center; + border-bottom: 1px solid #ddd; +} + +tbody tr:hover { + background: #f5f5f5; +} + +canvas { + width: 100% !important; + max-height: 350px; +} + +@media (max-width:900px){ + +.cards{ + +grid-template-columns:repeat(2,1fr); + +} + +} + +@media (max-width:600px){ + +.cards{ + +grid-template-columns:1fr; + +} + +table{ + +font-size:12px; + +} + +} \ No newline at end of file diff --git a/q5-mithal-monitor/data.json b/q5-mithal-monitor/data.json new file mode 100644 index 0000000..c598310 --- /dev/null +++ b/q5-mithal-monitor/data.json @@ -0,0 +1,62 @@ +[ + { + "timestamp": "2026-07-28 17:50:54", + "status_code": 200, + "uptime": "UP", + "latency_ms": 994.55, + "dns_ms": 39.1, + "search_response_ms": 1170.81, + "ssl": { + "valid": true, + "expires": "Sep 15 13:10:47 2026 GMT" + } + }, + { + "timestamp": "2026-07-28 17:50:58", + "status_code": 200, + "uptime": "UP", + "latency_ms": 986.42, + "dns_ms": 40.91, + "search_response_ms": 1130.27, + "ssl": { + "valid": true, + "expires": "Sep 15 13:10:47 2026 GMT" + } + }, + { + "timestamp": "2026-07-28 17:51:01", + "status_code": 200, + "uptime": "UP", + "latency_ms": 980.57, + "dns_ms": 51.6, + "search_response_ms": 1168.75, + "ssl": { + "valid": true, + "expires": "Sep 15 13:10:47 2026 GMT" + } + }, + { + "timestamp": "2026-07-28 17:51:04", + "status_code": 200, + "uptime": "UP", + "latency_ms": 969.79, + "dns_ms": 40.75, + "search_response_ms": 1209.84, + "ssl": { + "valid": true, + "expires": "Sep 15 13:10:47 2026 GMT" + } + }, + { + "timestamp": "2026-07-28 17:51:07", + "status_code": 200, + "uptime": "UP", + "latency_ms": 976.47, + "dns_ms": 42.13, + "search_response_ms": 1147.52, + "ssl": { + "valid": true, + "expires": "Sep 15 13:10:47 2026 GMT" + } + } +] \ No newline at end of file diff --git a/q5-mithal-monitor/monitor.py b/q5-mithal-monitor/monitor.py new file mode 100644 index 0000000..71966ee --- /dev/null +++ b/q5-mithal-monitor/monitor.py @@ -0,0 +1,135 @@ +import json +import os +import socket +import ssl +import time +from datetime import datetime + +import dns.resolver +import requests + +URL = "https://mithal.space" +DATA_FILE = "data.json" + + +def get_dns_time(): + try: + start = time.time() + + dns.resolver.resolve("mithal.space", "A") + + end = time.time() + + return round((end - start) * 1000, 2) + + except Exception: + return None + + +def get_ssl_info(): + try: + hostname = "mithal.space" + + context = ssl.create_default_context() + + with context.wrap_socket( + socket.socket(), + server_hostname=hostname + ) as sock: + + sock.settimeout(10) + sock.connect((hostname, 443)) + + cert = sock.getpeercert() + + return { + "valid": True, + "expires": cert["notAfter"] + } + + except Exception: + + return { + "valid": False, + "expires": None + } + + +def get_search_response(): + try: + start = time.time() + + requests.get(URL, timeout=10) + + end = time.time() + + return round((end - start) * 1000, 2) + + except Exception: + return None + + +def check_website(): + try: + response = requests.get(URL, timeout=10) + + result = { + "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "status_code": response.status_code, + "uptime": "UP" if response.status_code == 200 else "DOWN", + "latency_ms": round(response.elapsed.total_seconds() * 1000, 2), + "dns_ms": get_dns_time(), + "search_response_ms": get_search_response(), + "ssl": get_ssl_info() + } + + except Exception as e: + + result = { + "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "status_code": None, + "uptime": "DOWN", + "latency_ms": None, + "dns_ms": None, + "search_response_ms": None, + "ssl": { + "valid": False, + "expires": None + }, + "error": str(e) + } + + return result + + +def load_data(): + if not os.path.exists(DATA_FILE): + return [] + + with open(DATA_FILE, "r") as file: + try: + return json.load(file) + except json.JSONDecodeError: + return [] + + +def save_data(data): + with open(DATA_FILE, "w") as file: + json.dump(data, file, indent=4) + + +def main(): + data = load_data() + + result = check_website() + + data.append(result) + data = data[-10:] + + save_data(data) + + print(result) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/q5-mithal-monitor/requirements.txt b/q5-mithal-monitor/requirements.txt new file mode 100644 index 0000000..16ad02c --- /dev/null +++ b/q5-mithal-monitor/requirements.txt @@ -0,0 +1,2 @@ +requests +dnspython \ No newline at end of file diff --git a/q5-mithal-monitor/start.sh b/q5-mithal-monitor/start.sh new file mode 100644 index 0000000..f8a3848 --- /dev/null +++ b/q5-mithal-monitor/start.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +while true +do + python monitor.py + + cp data.json dashboard/data.json + + sleep 60 +done & + +cd dashboard +python -m http.server 8000 \ No newline at end of file