mirror of
https://github.com/harivansh-afk/system-design.git
synced 2026-04-15 05:02:11 +00:00
claude one shot lol
This commit is contained in:
parent
7964b78a3a
commit
1ab3058cca
27 changed files with 5218 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
node_modules
|
||||
.svelte-kit
|
||||
build
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
32
package.json
Normal file
32
package.json
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "system-design-explorer",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
"@sveltejs/kit": "^2.21.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@types/d3": "^7.4.3",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.5.1",
|
||||
"svelte": "^5.17.4",
|
||||
"svelte-check": "^4.0.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^6.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xyflow/svelte": "^0.1.26",
|
||||
"d3": "^7.9.0",
|
||||
"gsap": "^3.12.7",
|
||||
"lucide-svelte": "^0.469.0"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
111
src/app.css
Normal file
111
src/app.css
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-surface-700;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply antialiased;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
@apply w-2 h-2;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-surface-900;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-surface-700 rounded-full;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
@apply bg-surface-600;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.card {
|
||||
@apply bg-surface-900 border border-surface-800 rounded-xl p-6;
|
||||
}
|
||||
|
||||
.card-hover {
|
||||
@apply card hover:border-surface-600 hover:bg-surface-800/50 transition-all duration-200 cursor-pointer;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply px-4 py-2 rounded-lg font-medium transition-all duration-200;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply btn bg-blue-600 hover:bg-blue-500 text-white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply btn bg-surface-800 hover:bg-surface-700 text-surface-200;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
@apply text-2xl font-bold text-surface-100 mb-4;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
@apply text-surface-400 mb-6;
|
||||
}
|
||||
|
||||
.tag {
|
||||
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium;
|
||||
}
|
||||
|
||||
.tag-aws {
|
||||
@apply tag bg-aws-orange/20 text-aws-orange;
|
||||
}
|
||||
|
||||
.tag-gcp {
|
||||
@apply tag bg-gcp-blue/20 text-gcp-blue;
|
||||
}
|
||||
|
||||
.tag-concept {
|
||||
@apply tag bg-purple-500/20 text-purple-400;
|
||||
}
|
||||
|
||||
/* Animated flow lines for diagrams */
|
||||
.flow-line {
|
||||
stroke-dasharray: 5 5;
|
||||
animation: flowAnimation 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes flowAnimation {
|
||||
to {
|
||||
stroke-dashoffset: -10;
|
||||
}
|
||||
}
|
||||
|
||||
/* Node styles for diagrams */
|
||||
.diagram-node {
|
||||
@apply bg-surface-800 border-2 border-surface-600 rounded-xl p-4 shadow-lg;
|
||||
@apply hover:border-blue-500 hover:shadow-blue-500/20 transition-all duration-200;
|
||||
}
|
||||
|
||||
.diagram-node-aws {
|
||||
@apply diagram-node border-aws-orange/50 hover:border-aws-orange;
|
||||
}
|
||||
|
||||
.diagram-node-gcp {
|
||||
@apply diagram-node border-gcp-blue/50 hover:border-gcp-blue;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.text-gradient {
|
||||
@apply bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-400;
|
||||
}
|
||||
|
||||
.glass {
|
||||
@apply bg-surface-900/80 backdrop-blur-lg;
|
||||
}
|
||||
}
|
||||
15
src/app.html
Normal file
15
src/app.html
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover" class="bg-surface-950 text-surface-100">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
163
src/lib/components/diagrams/ComparisonTable.svelte
Normal file
163
src/lib/components/diagrams/ComparisonTable.svelte
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
<script lang="ts">
|
||||
import * as Icons from 'lucide-svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
|
||||
interface ComparisonItem {
|
||||
feature: string;
|
||||
description?: string;
|
||||
values: Record<string, string | boolean>;
|
||||
}
|
||||
|
||||
interface Option {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
icon: string;
|
||||
whenToUse: string[];
|
||||
whenNotToUse: string[];
|
||||
realWorldExample?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
options: Option[];
|
||||
features: ComparisonItem[];
|
||||
}
|
||||
|
||||
let { title, subtitle = '', options, features }: Props = $props();
|
||||
let selectedOption = $state<string | null>(null);
|
||||
|
||||
function getIcon(iconName: string) {
|
||||
return (Icons as Record<string, typeof Icons.Box>)[iconName] || Icons.Box;
|
||||
}
|
||||
|
||||
function getCellContent(value: string | boolean) {
|
||||
if (typeof value === 'boolean') {
|
||||
return value
|
||||
? { type: 'check', class: 'text-green-400' }
|
||||
: { type: 'x', class: 'text-red-400' };
|
||||
}
|
||||
return { type: 'text', value, class: 'text-surface-300' };
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div>
|
||||
<h2 class="section-title">{title}</h2>
|
||||
{#if subtitle}
|
||||
<p class="section-subtitle">{subtitle}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Comparison Table -->
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left p-4 bg-surface-900 border-b border-surface-700 text-surface-400 font-medium">
|
||||
Feature
|
||||
</th>
|
||||
{#each options as option}
|
||||
{@const Icon = getIcon(option.icon)}
|
||||
<th
|
||||
class="p-4 bg-surface-900 border-b border-surface-700 text-center cursor-pointer hover:bg-surface-800 transition-colors"
|
||||
onclick={() => selectedOption = selectedOption === option.id ? null : option.id}
|
||||
>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div
|
||||
class="w-10 h-10 rounded-lg flex items-center justify-center"
|
||||
style="background-color: {option.color}20; color: {option.color}"
|
||||
>
|
||||
<Icon class="w-6 h-6" />
|
||||
</div>
|
||||
<span class="font-medium text-surface-100">{option.name}</span>
|
||||
</div>
|
||||
</th>
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each features as feature, i}
|
||||
<tr class="border-b border-surface-800 hover:bg-surface-900/50">
|
||||
<td class="p-4">
|
||||
<div class="font-medium text-surface-200">{feature.feature}</div>
|
||||
{#if feature.description}
|
||||
<div class="text-sm text-surface-500 mt-1">{feature.description}</div>
|
||||
{/if}
|
||||
</td>
|
||||
{#each options as option}
|
||||
{@const cell = getCellContent(feature.values[option.id])}
|
||||
<td class="p-4 text-center {cell.class}">
|
||||
{#if cell.type === 'check'}
|
||||
<Icons.Check class="w-5 h-5 mx-auto" />
|
||||
{:else if cell.type === 'x'}
|
||||
<Icons.X class="w-5 h-5 mx-auto" />
|
||||
{:else}
|
||||
<span class="text-sm">{cell.value}</span>
|
||||
{/if}
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- When to Use / When Not to Use Panels -->
|
||||
{#if selectedOption}
|
||||
{@const selected = options.find(o => o.id === selectedOption)}
|
||||
{#if selected}
|
||||
<div transition:slide={{ duration: 200 }} class="grid md:grid-cols-2 gap-4">
|
||||
<!-- When to Use -->
|
||||
<div class="card border-green-500/30">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<Icons.CheckCircle class="w-5 h-5 text-green-400" />
|
||||
<h3 class="font-semibold text-green-400">When to Use {selected.name}</h3>
|
||||
</div>
|
||||
<ul class="space-y-2">
|
||||
{#each selected.whenToUse as reason}
|
||||
<li class="flex items-start gap-2 text-sm text-surface-300">
|
||||
<Icons.Check class="w-4 h-4 text-green-400 mt-0.5 flex-shrink-0" />
|
||||
<span>{reason}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- When NOT to Use -->
|
||||
<div class="card border-red-500/30">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<Icons.XCircle class="w-5 h-5 text-red-400" />
|
||||
<h3 class="font-semibold text-red-400">When NOT to Use {selected.name}</h3>
|
||||
</div>
|
||||
<ul class="space-y-2">
|
||||
{#each selected.whenNotToUse as reason}
|
||||
<li class="flex items-start gap-2 text-sm text-surface-300">
|
||||
<Icons.X class="w-4 h-4 text-red-400 mt-0.5 flex-shrink-0" />
|
||||
<span>{reason}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Real World Example -->
|
||||
{#if selected.realWorldExample}
|
||||
<div class="card md:col-span-2 border-blue-500/30">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<Icons.Lightbulb class="w-5 h-5 text-blue-400" />
|
||||
<h3 class="font-semibold text-blue-400">Real World Example</h3>
|
||||
</div>
|
||||
<p class="text-sm text-surface-300">{selected.realWorldExample}</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="text-center py-6 text-surface-500">
|
||||
<Icons.MousePointerClick class="w-8 h-8 mx-auto mb-2 opacity-50" />
|
||||
<p class="text-sm">Click on a column header to see when to use each option</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
207
src/lib/components/diagrams/DecisionTree.svelte
Normal file
207
src/lib/components/diagrams/DecisionTree.svelte
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
<script lang="ts">
|
||||
import * as Icons from 'lucide-svelte';
|
||||
import { fly, fade } from 'svelte/transition';
|
||||
|
||||
interface DecisionNode {
|
||||
id: string;
|
||||
type: 'question' | 'answer';
|
||||
text: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
yes?: string; // id of next node if yes
|
||||
no?: string; // id of next node if no
|
||||
recommendation?: string;
|
||||
reasoning?: string;
|
||||
alternatives?: string[];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
nodes: DecisionNode[];
|
||||
startNode: string;
|
||||
}
|
||||
|
||||
let { title, subtitle = '', nodes, startNode }: Props = $props();
|
||||
|
||||
let path = $state<string[]>([startNode]);
|
||||
let answers = $state<Record<string, boolean>>({});
|
||||
|
||||
function getNode(id: string): DecisionNode | undefined {
|
||||
return nodes.find(n => n.id === id);
|
||||
}
|
||||
|
||||
function getIcon(iconName?: string) {
|
||||
if (!iconName) return Icons.HelpCircle;
|
||||
return (Icons as Record<string, typeof Icons.Box>)[iconName] || Icons.HelpCircle;
|
||||
}
|
||||
|
||||
function answer(nodeId: string, isYes: boolean) {
|
||||
const node = getNode(nodeId);
|
||||
if (!node) return;
|
||||
|
||||
answers[nodeId] = isYes;
|
||||
const nextId = isYes ? node.yes : node.no;
|
||||
if (nextId) {
|
||||
path = [...path, nextId];
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
if (path.length > 1) {
|
||||
const lastNodeId = path[path.length - 1];
|
||||
delete answers[path[path.length - 2]];
|
||||
path = path.slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
path = [startNode];
|
||||
answers = {};
|
||||
}
|
||||
|
||||
const currentNode = $derived(getNode(path[path.length - 1]));
|
||||
const isAtEnd = $derived(currentNode?.type === 'answer');
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div>
|
||||
<h2 class="section-title">{title}</h2>
|
||||
{#if subtitle}
|
||||
<p class="section-subtitle">{subtitle}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Progress Trail -->
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
{#each path as nodeId, i}
|
||||
{@const node = getNode(nodeId)}
|
||||
{@const isLast = i === path.length - 1}
|
||||
{#if node}
|
||||
<div class="flex items-center gap-2">
|
||||
{#if i > 0}
|
||||
<Icons.ChevronRight class="w-4 h-4 text-surface-600" />
|
||||
{/if}
|
||||
<span
|
||||
class="px-3 py-1 rounded-full text-sm transition-colors"
|
||||
class:bg-blue-600={isLast}
|
||||
class:text-white={isLast}
|
||||
class:bg-surface-800={!isLast}
|
||||
class:text-surface-400={!isLast}
|
||||
>
|
||||
{#if node.type === 'question'}
|
||||
{answers[nodeId] !== undefined ? (answers[nodeId] ? 'Yes' : 'No') : '?'}
|
||||
{:else}
|
||||
{node.text.substring(0, 20)}...
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Current Decision -->
|
||||
{#if currentNode}
|
||||
<div
|
||||
class="card max-w-2xl mx-auto"
|
||||
in:fly={{ y: 20, duration: 300 }}
|
||||
>
|
||||
{#if currentNode.type === 'question'}
|
||||
<!-- Question Node -->
|
||||
<div class="text-center space-y-6">
|
||||
<div class="w-16 h-16 mx-auto rounded-full bg-blue-500/20 flex items-center justify-center">
|
||||
<Icons.HelpCircle class="w-8 h-8 text-blue-400" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-surface-100 mb-2">{currentNode.text}</h3>
|
||||
{#if currentNode.description}
|
||||
<p class="text-surface-400">{currentNode.description}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center gap-4">
|
||||
<button
|
||||
onclick={() => answer(currentNode.id, true)}
|
||||
class="flex items-center gap-2 px-6 py-3 rounded-lg bg-green-600 hover:bg-green-500 text-white font-medium transition-colors"
|
||||
>
|
||||
<Icons.Check class="w-5 h-5" />
|
||||
Yes
|
||||
</button>
|
||||
<button
|
||||
onclick={() => answer(currentNode.id, false)}
|
||||
class="flex items-center gap-2 px-6 py-3 rounded-lg bg-red-600 hover:bg-red-500 text-white font-medium transition-colors"
|
||||
>
|
||||
<Icons.X class="w-5 h-5" />
|
||||
No
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{:else}
|
||||
<!-- Answer Node -->
|
||||
{@const Icon = getIcon(currentNode.icon)}
|
||||
<div class="text-center space-y-6">
|
||||
<div class="w-20 h-20 mx-auto rounded-full bg-green-500/20 flex items-center justify-center">
|
||||
<Icon class="w-10 h-10 text-green-400" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="tag tag-concept mb-2">Recommendation</span>
|
||||
<h3 class="text-2xl font-bold text-surface-100 mt-2">{currentNode.text}</h3>
|
||||
</div>
|
||||
|
||||
{#if currentNode.reasoning}
|
||||
<div class="text-left bg-surface-800 rounded-lg p-4 border border-surface-700">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<Icons.Lightbulb class="w-5 h-5 text-yellow-400" />
|
||||
<span class="font-medium text-yellow-400">Why this choice?</span>
|
||||
</div>
|
||||
<p class="text-surface-300 text-sm">{currentNode.reasoning}</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if currentNode.alternatives && currentNode.alternatives.length > 0}
|
||||
<div class="text-left bg-surface-800 rounded-lg p-4 border border-surface-700">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<Icons.GitBranch class="w-5 h-5 text-purple-400" />
|
||||
<span class="font-medium text-purple-400">Alternatives to consider</span>
|
||||
</div>
|
||||
<ul class="space-y-1">
|
||||
{#each currentNode.alternatives as alt}
|
||||
<li class="text-surface-400 text-sm flex items-start gap-2">
|
||||
<Icons.ChevronRight class="w-4 h-4 mt-0.5 flex-shrink-0" />
|
||||
<span>{alt}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Controls -->
|
||||
<div class="flex justify-center gap-4">
|
||||
{#if path.length > 1}
|
||||
<button
|
||||
onclick={goBack}
|
||||
class="btn-secondary flex items-center gap-2"
|
||||
>
|
||||
<Icons.ArrowLeft class="w-4 h-4" />
|
||||
Go Back
|
||||
</button>
|
||||
{/if}
|
||||
{#if isAtEnd}
|
||||
<button
|
||||
onclick={reset}
|
||||
class="btn-primary flex items-center gap-2"
|
||||
>
|
||||
<Icons.RotateCcw class="w-4 h-4" />
|
||||
Start Over
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
223
src/lib/components/diagrams/GuidedWalkthrough.svelte
Normal file
223
src/lib/components/diagrams/GuidedWalkthrough.svelte
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
<script lang="ts">
|
||||
import * as Icons from 'lucide-svelte';
|
||||
import { fly, fade } from 'svelte/transition';
|
||||
|
||||
interface WalkthroughStep {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon?: string;
|
||||
highlight?: string; // CSS selector or id to highlight
|
||||
details?: string;
|
||||
tip?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
steps: WalkthroughStep[];
|
||||
autoPlay?: boolean;
|
||||
autoPlayInterval?: number;
|
||||
}
|
||||
|
||||
let {
|
||||
title,
|
||||
subtitle = '',
|
||||
steps,
|
||||
autoPlay = false,
|
||||
autoPlayInterval = 3000
|
||||
}: Props = $props();
|
||||
|
||||
let currentStep = $state(0);
|
||||
let isPlaying = $state(autoPlay);
|
||||
let intervalId: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
function getIcon(iconName?: string) {
|
||||
if (!iconName) return Icons.Circle;
|
||||
return (Icons as Record<string, typeof Icons.Box>)[iconName] || Icons.Circle;
|
||||
}
|
||||
|
||||
function nextStep() {
|
||||
if (currentStep < steps.length - 1) {
|
||||
currentStep++;
|
||||
} else {
|
||||
currentStep = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function prevStep() {
|
||||
if (currentStep > 0) {
|
||||
currentStep--;
|
||||
}
|
||||
}
|
||||
|
||||
function goToStep(index: number) {
|
||||
currentStep = index;
|
||||
}
|
||||
|
||||
function togglePlay() {
|
||||
isPlaying = !isPlaying;
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (isPlaying) {
|
||||
intervalId = setInterval(nextStep, autoPlayInterval);
|
||||
} else if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (intervalId) clearInterval(intervalId);
|
||||
};
|
||||
});
|
||||
|
||||
const step = $derived(steps[currentStep]);
|
||||
const StepIcon = $derived(getIcon(step?.icon));
|
||||
const progress = $derived(((currentStep + 1) / steps.length) * 100);
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<h2 class="section-title">{title}</h2>
|
||||
{#if subtitle}
|
||||
<p class="section-subtitle">{subtitle}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
onclick={togglePlay}
|
||||
class="btn-secondary flex items-center gap-2"
|
||||
>
|
||||
{#if isPlaying}
|
||||
<Icons.Pause class="w-4 h-4" />
|
||||
Pause
|
||||
{:else}
|
||||
<Icons.Play class="w-4 h-4" />
|
||||
Auto Play
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="relative">
|
||||
<div class="h-1 bg-surface-800 rounded-full overflow-hidden">
|
||||
<div
|
||||
class="h-full bg-gradient-to-r from-blue-500 to-purple-500 transition-all duration-300"
|
||||
style="width: {progress}%"
|
||||
></div>
|
||||
</div>
|
||||
<div class="flex justify-between mt-2">
|
||||
<span class="text-xs text-surface-500">Step {currentStep + 1} of {steps.length}</span>
|
||||
<span class="text-xs text-surface-500">{Math.round(progress)}% complete</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step Indicators -->
|
||||
<div class="flex justify-center gap-2 flex-wrap">
|
||||
{#each steps as s, i}
|
||||
{@const Icon = getIcon(s.icon)}
|
||||
<button
|
||||
onclick={() => goToStep(i)}
|
||||
class="flex items-center gap-2 px-3 py-2 rounded-lg transition-all duration-200"
|
||||
class:bg-blue-600={i === currentStep}
|
||||
class:text-white={i === currentStep}
|
||||
class:bg-surface-800={i !== currentStep && i <= currentStep}
|
||||
class:text-surface-300={i !== currentStep && i <= currentStep}
|
||||
class:bg-surface-900={i > currentStep}
|
||||
class:text-surface-600={i > currentStep}
|
||||
>
|
||||
<Icon class="w-4 h-4" />
|
||||
<span class="text-sm font-medium hidden md:inline">{i + 1}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Current Step Content -->
|
||||
{#key currentStep}
|
||||
<div
|
||||
class="card"
|
||||
in:fly={{ x: 50, duration: 300 }}
|
||||
out:fade={{ duration: 150 }}
|
||||
>
|
||||
<div class="flex items-start gap-6">
|
||||
<!-- Icon -->
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-16 h-16 rounded-xl bg-gradient-to-br from-blue-500/20 to-purple-500/20 flex items-center justify-center">
|
||||
<StepIcon class="w-8 h-8 text-blue-400" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-1 space-y-4">
|
||||
<div>
|
||||
<span class="tag tag-concept mb-2">Step {currentStep + 1}</span>
|
||||
<h3 class="text-xl font-semibold text-surface-100 mt-2">{step.title}</h3>
|
||||
</div>
|
||||
|
||||
<p class="text-surface-300">{step.description}</p>
|
||||
|
||||
{#if step.details}
|
||||
<div class="bg-surface-800 rounded-lg p-4 border border-surface-700">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<Icons.Info class="w-4 h-4 text-blue-400" />
|
||||
<span class="text-sm font-medium text-blue-400">Technical Details</span>
|
||||
</div>
|
||||
<p class="text-sm text-surface-400">{step.details}</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if step.tip}
|
||||
<div class="bg-yellow-500/10 rounded-lg p-4 border border-yellow-500/30">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<Icons.Lightbulb class="w-4 h-4 text-yellow-400" />
|
||||
<span class="text-sm font-medium text-yellow-400">Pro Tip</span>
|
||||
</div>
|
||||
<p class="text-sm text-surface-300">{step.tip}</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/key}
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="flex justify-between items-center">
|
||||
<button
|
||||
onclick={prevStep}
|
||||
disabled={currentStep === 0}
|
||||
class="btn-secondary flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<Icons.ArrowLeft class="w-4 h-4" />
|
||||
Previous
|
||||
</button>
|
||||
|
||||
<div class="flex gap-1">
|
||||
{#each steps as _, i}
|
||||
<button
|
||||
onclick={() => goToStep(i)}
|
||||
class="w-2 h-2 rounded-full transition-all duration-200"
|
||||
class:bg-blue-500={i === currentStep}
|
||||
class:w-4={i === currentStep}
|
||||
class:bg-surface-600={i !== currentStep}
|
||||
></button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onclick={nextStep}
|
||||
class="btn-primary flex items-center gap-2"
|
||||
>
|
||||
{#if currentStep === steps.length - 1}
|
||||
Start Over
|
||||
<Icons.RotateCcw class="w-4 h-4" />
|
||||
{:else}
|
||||
Next
|
||||
<Icons.ArrowRight class="w-4 h-4" />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
90
src/lib/components/diagrams/ServiceNode.svelte
Normal file
90
src/lib/components/diagrams/ServiceNode.svelte
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<script lang="ts">
|
||||
import * as Icons from 'lucide-svelte';
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
type?: 'aws' | 'gcp' | 'concept' | 'default';
|
||||
selected?: boolean;
|
||||
onclick?: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
id,
|
||||
label,
|
||||
description = '',
|
||||
icon = 'Box',
|
||||
type = 'default',
|
||||
selected = false,
|
||||
onclick
|
||||
}: Props = $props();
|
||||
|
||||
function getIcon(iconName: string) {
|
||||
return (Icons as Record<string, typeof Icons.Box>)[iconName] || Icons.Box;
|
||||
}
|
||||
|
||||
const Icon = $derived(getIcon(icon));
|
||||
|
||||
const borderColor = $derived({
|
||||
aws: 'border-aws-orange',
|
||||
gcp: 'border-gcp-blue',
|
||||
concept: 'border-purple-500',
|
||||
default: 'border-surface-600'
|
||||
}[type]);
|
||||
|
||||
const bgColor = $derived({
|
||||
aws: 'bg-aws-orange/10',
|
||||
gcp: 'bg-gcp-blue/10',
|
||||
concept: 'bg-purple-500/10',
|
||||
default: 'bg-surface-800'
|
||||
}[type]);
|
||||
|
||||
const iconColor = $derived({
|
||||
aws: 'text-aws-orange',
|
||||
gcp: 'text-gcp-blue',
|
||||
concept: 'text-purple-400',
|
||||
default: 'text-surface-300'
|
||||
}[type]);
|
||||
</script>
|
||||
|
||||
<button
|
||||
{onclick}
|
||||
class="relative group cursor-pointer transition-all duration-200 transform hover:scale-105"
|
||||
class:ring-2={selected}
|
||||
class:ring-blue-500={selected}
|
||||
class:ring-offset-2={selected}
|
||||
class:ring-offset-surface-950={selected}
|
||||
>
|
||||
<div
|
||||
class="flex flex-col items-center p-4 rounded-xl border-2 min-w-[120px] {borderColor} {bgColor}"
|
||||
>
|
||||
<!-- Icon -->
|
||||
<div class="w-12 h-12 rounded-lg flex items-center justify-center mb-2 {iconColor}">
|
||||
<Icon class="w-8 h-8" />
|
||||
</div>
|
||||
|
||||
<!-- Label -->
|
||||
<span class="text-sm font-medium text-surface-100 text-center">{label}</span>
|
||||
|
||||
<!-- Description (on hover) -->
|
||||
{#if description}
|
||||
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-3 py-2 bg-surface-800 rounded-lg text-xs text-surface-300 whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-10 border border-surface-700 shadow-lg">
|
||||
{description}
|
||||
<div class="absolute top-full left-1/2 -translate-x-1/2 -mt-1 w-2 h-2 bg-surface-800 border-r border-b border-surface-700 transform rotate-45"></div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Type badge -->
|
||||
{#if type !== 'default'}
|
||||
<span class="absolute -top-2 -right-2 px-1.5 py-0.5 text-[10px] font-medium rounded-full uppercase
|
||||
{type === 'aws' ? 'bg-aws-orange text-white' : ''}
|
||||
{type === 'gcp' ? 'bg-gcp-blue text-white' : ''}
|
||||
{type === 'concept' ? 'bg-purple-500 text-white' : ''}
|
||||
">
|
||||
{type}
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
126
src/lib/components/ui/Sidebar.svelte
Normal file
126
src/lib/components/ui/Sidebar.svelte
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { navigationItems, sidebarOpen, expandedSections, type NavItem } from '$lib/stores/navigation';
|
||||
import * as Icons from 'lucide-svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
|
||||
function getIcon(iconName: string) {
|
||||
return (Icons as Record<string, typeof Icons.Home>)[iconName] || Icons.Circle;
|
||||
}
|
||||
|
||||
function toggleSection(id: string) {
|
||||
expandedSections.update(set => {
|
||||
const newSet = new Set(set);
|
||||
if (newSet.has(id)) {
|
||||
newSet.delete(id);
|
||||
} else {
|
||||
newSet.add(id);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
}
|
||||
|
||||
function isActive(href: string): boolean {
|
||||
return $page.url.pathname === href || $page.url.pathname.startsWith(href + '/');
|
||||
}
|
||||
</script>
|
||||
|
||||
<aside
|
||||
class="fixed left-0 top-0 h-screen bg-surface-900 border-r border-surface-800 flex flex-col z-40 transition-all duration-300"
|
||||
class:w-64={$sidebarOpen}
|
||||
class:w-16={!$sidebarOpen}
|
||||
>
|
||||
<!-- Logo -->
|
||||
<div class="h-16 flex items-center px-4 border-b border-surface-800">
|
||||
{#if $sidebarOpen}
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-purple-500 flex items-center justify-center">
|
||||
<Icons.Layers class="w-5 h-5 text-white" />
|
||||
</div>
|
||||
<span class="font-bold text-lg text-surface-100">System Design</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-purple-500 flex items-center justify-center mx-auto">
|
||||
<Icons.Layers class="w-5 h-5 text-white" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="flex-1 overflow-y-auto py-4 px-2">
|
||||
{#each navigationItems as item}
|
||||
{@const Icon = getIcon(item.icon)}
|
||||
{@const hasChildren = item.children && item.children.length > 0}
|
||||
{@const isExpanded = $expandedSections.has(item.id)}
|
||||
{@const active = isActive(item.href)}
|
||||
|
||||
<div class="mb-1">
|
||||
{#if hasChildren}
|
||||
<button
|
||||
onclick={() => toggleSection(item.id)}
|
||||
class="w-full flex items-center gap-3 px-3 py-2 rounded-lg text-left transition-colors duration-150"
|
||||
class:bg-surface-800={active}
|
||||
class:text-surface-100={active}
|
||||
class:text-surface-400={!active}
|
||||
class:hover:bg-surface-800={!active}
|
||||
class:hover:text-surface-200={!active}
|
||||
>
|
||||
<Icon class="w-5 h-5 flex-shrink-0" />
|
||||
{#if $sidebarOpen}
|
||||
<span class="flex-1 text-sm font-medium">{item.label}</span>
|
||||
<Icons.ChevronDown
|
||||
class="w-4 h-4 transition-transform duration-200 {isExpanded ? 'rotate-180' : ''}"
|
||||
/>
|
||||
{/if}
|
||||
</button>
|
||||
{:else}
|
||||
<a
|
||||
href={item.href}
|
||||
class="w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors duration-150"
|
||||
class:bg-blue-600={active}
|
||||
class:text-white={active}
|
||||
class:text-surface-400={!active}
|
||||
class:hover:bg-surface-800={!active}
|
||||
class:hover:text-surface-200={!active}
|
||||
>
|
||||
<Icon class="w-5 h-5 flex-shrink-0" />
|
||||
{#if $sidebarOpen}
|
||||
<span class="text-sm font-medium">{item.label}</span>
|
||||
{/if}
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
{#if hasChildren && isExpanded && $sidebarOpen}
|
||||
<div transition:slide={{ duration: 200 }} class="ml-4 mt-1 space-y-1">
|
||||
{#each item.children as child}
|
||||
{@const ChildIcon = getIcon(child.icon)}
|
||||
{@const childActive = isActive(child.href)}
|
||||
<a
|
||||
href={child.href}
|
||||
class="flex items-center gap-3 px-3 py-1.5 rounded-lg text-sm transition-colors duration-150 {childActive ? 'bg-blue-600/20 text-blue-400' : 'text-surface-500 hover:bg-surface-800 hover:text-surface-300'}"
|
||||
>
|
||||
<ChildIcon class="w-4 h-4 flex-shrink-0" />
|
||||
<span>{child.label}</span>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</nav>
|
||||
|
||||
<!-- Toggle Button -->
|
||||
<div class="p-4 border-t border-surface-800">
|
||||
<button
|
||||
onclick={() => sidebarOpen.update(v => !v)}
|
||||
class="w-full flex items-center justify-center gap-2 px-3 py-2 rounded-lg bg-surface-800 hover:bg-surface-700 text-surface-400 hover:text-surface-200 transition-colors"
|
||||
>
|
||||
{#if $sidebarOpen}
|
||||
<Icons.PanelLeftClose class="w-5 h-5" />
|
||||
<span class="text-sm">Collapse</span>
|
||||
{:else}
|
||||
<Icons.PanelLeftOpen class="w-5 h-5" />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
125
src/lib/stores/navigation.ts
Normal file
125
src/lib/stores/navigation.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import { writable } from 'svelte/store';
|
||||
|
||||
export interface NavItem {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
href: string;
|
||||
children?: NavItem[];
|
||||
}
|
||||
|
||||
export const navigationItems: NavItem[] = [
|
||||
{
|
||||
id: 'home',
|
||||
label: 'Overview',
|
||||
icon: 'Home',
|
||||
href: '/'
|
||||
},
|
||||
{
|
||||
id: 'fundamentals',
|
||||
label: 'Fundamentals',
|
||||
icon: 'BookOpen',
|
||||
href: '/fundamentals',
|
||||
children: [
|
||||
{ id: 'cap-theorem', label: 'CAP Theorem', icon: 'Triangle', href: '/fundamentals/cap-theorem' },
|
||||
{ id: 'consistency', label: 'Consistency Models', icon: 'RefreshCw', href: '/fundamentals/consistency' },
|
||||
{ id: 'scaling', label: 'Scaling', icon: 'TrendingUp', href: '/fundamentals/scaling' },
|
||||
{ id: 'latency', label: 'Latency vs Throughput', icon: 'Gauge', href: '/fundamentals/latency' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'compute',
|
||||
label: 'Compute',
|
||||
icon: 'Cpu',
|
||||
href: '/compute',
|
||||
children: [
|
||||
{ id: 'vms', label: 'Virtual Machines', icon: 'Server', href: '/compute/virtual-machines' },
|
||||
{ id: 'serverless', label: 'Serverless', icon: 'Zap', href: '/compute/serverless' },
|
||||
{ id: 'containers', label: 'Containers', icon: 'Box', href: '/compute/containers' },
|
||||
{ id: 'kubernetes', label: 'Kubernetes', icon: 'Boxes', href: '/compute/kubernetes' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'storage',
|
||||
label: 'Storage',
|
||||
icon: 'HardDrive',
|
||||
href: '/storage',
|
||||
children: [
|
||||
{ id: 'object', label: 'Object Storage', icon: 'File', href: '/storage/object' },
|
||||
{ id: 'block', label: 'Block Storage', icon: 'Database', href: '/storage/block' },
|
||||
{ id: 'file', label: 'File Storage', icon: 'Folder', href: '/storage/file' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'databases',
|
||||
label: 'Databases',
|
||||
icon: 'Database',
|
||||
href: '/databases',
|
||||
children: [
|
||||
{ id: 'sql', label: 'SQL Databases', icon: 'Table', href: '/databases/sql' },
|
||||
{ id: 'nosql', label: 'NoSQL Databases', icon: 'FileJson', href: '/databases/nosql' },
|
||||
{ id: 'cache', label: 'Caching', icon: 'Layers', href: '/databases/caching' },
|
||||
{ id: 'replication', label: 'Replication', icon: 'Copy', href: '/databases/replication' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'networking',
|
||||
label: 'Networking',
|
||||
icon: 'Network',
|
||||
href: '/networking',
|
||||
children: [
|
||||
{ id: 'vpc', label: 'VPC & Subnets', icon: 'Globe', href: '/networking/vpc' },
|
||||
{ id: 'dns', label: 'DNS', icon: 'Link', href: '/networking/dns' },
|
||||
{ id: 'load-balancing', label: 'Load Balancing', icon: 'Scale', href: '/networking/load-balancing' },
|
||||
{ id: 'cdn', label: 'CDN', icon: 'Radio', href: '/networking/cdn' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'messaging',
|
||||
label: 'Messaging',
|
||||
icon: 'MessageSquare',
|
||||
href: '/messaging',
|
||||
children: [
|
||||
{ id: 'queues', label: 'Message Queues', icon: 'ListOrdered', href: '/messaging/queues' },
|
||||
{ id: 'pubsub', label: 'Pub/Sub', icon: 'Bell', href: '/messaging/pubsub' },
|
||||
{ id: 'event-driven', label: 'Event-Driven', icon: 'Activity', href: '/messaging/event-driven' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'security',
|
||||
label: 'Security',
|
||||
icon: 'Shield',
|
||||
href: '/security',
|
||||
children: [
|
||||
{ id: 'iam', label: 'IAM', icon: 'Key', href: '/security/iam' },
|
||||
{ id: 'encryption', label: 'Encryption', icon: 'Lock', href: '/security/encryption' },
|
||||
{ id: 'secrets', label: 'Secrets Management', icon: 'KeyRound', href: '/security/secrets' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'compare',
|
||||
label: 'Compare',
|
||||
icon: 'GitCompare',
|
||||
href: '/compare',
|
||||
children: [
|
||||
{ id: 'sql-vs-nosql', label: 'SQL vs NoSQL', icon: 'GitCompare', href: '/compare/sql-vs-nosql' },
|
||||
{ id: 'kafka-vs-rabbitmq', label: 'Kafka vs RabbitMQ', icon: 'GitCompare', href: '/compare/kafka-vs-rabbitmq' },
|
||||
{ id: 'rest-vs-graphql', label: 'REST vs GraphQL vs gRPC', icon: 'GitCompare', href: '/compare/api-design' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'decisions',
|
||||
label: 'Decision Trees',
|
||||
icon: 'GitBranch',
|
||||
href: '/decisions',
|
||||
children: [
|
||||
{ id: 'which-database', label: 'Which Database?', icon: 'HelpCircle', href: '/decisions/which-database' },
|
||||
{ id: 'serverless-or-not', label: 'Serverless or Not?', icon: 'HelpCircle', href: '/decisions/serverless' },
|
||||
{ id: 'which-queue', label: 'Which Message Queue?', icon: 'HelpCircle', href: '/decisions/which-queue' }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const sidebarOpen = writable(true);
|
||||
export const activeSection = writable('home');
|
||||
export const expandedSections = writable<Set<string>>(new Set(['fundamentals']));
|
||||
21
src/routes/+layout.svelte
Normal file
21
src/routes/+layout.svelte
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import Sidebar from '$lib/components/ui/Sidebar.svelte';
|
||||
import { sidebarOpen } from '$lib/stores/navigation';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen bg-surface-950">
|
||||
<Sidebar />
|
||||
|
||||
<main
|
||||
class="transition-all duration-300 min-h-screen"
|
||||
class:ml-64={$sidebarOpen}
|
||||
class:ml-16={!$sidebarOpen}
|
||||
>
|
||||
<div class="p-8">
|
||||
{@render children()}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
225
src/routes/+page.svelte
Normal file
225
src/routes/+page.svelte
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
<script lang="ts">
|
||||
import * as Icons from 'lucide-svelte';
|
||||
import { navigationItems } from '$lib/stores/navigation';
|
||||
|
||||
const categories = [
|
||||
{
|
||||
id: 'fundamentals',
|
||||
title: 'Fundamentals',
|
||||
description: 'Core distributed systems concepts: CAP theorem, consistency, scaling',
|
||||
icon: 'BookOpen',
|
||||
color: 'from-blue-500 to-cyan-500',
|
||||
href: '/fundamentals/cap-theorem'
|
||||
},
|
||||
{
|
||||
id: 'compute',
|
||||
title: 'Compute',
|
||||
description: 'VMs, serverless, containers, and Kubernetes',
|
||||
icon: 'Cpu',
|
||||
color: 'from-purple-500 to-pink-500',
|
||||
href: '/compute/virtual-machines'
|
||||
},
|
||||
{
|
||||
id: 'databases',
|
||||
title: 'Databases',
|
||||
description: 'SQL, NoSQL, caching, replication patterns',
|
||||
icon: 'Database',
|
||||
color: 'from-green-500 to-emerald-500',
|
||||
href: '/databases/sql'
|
||||
},
|
||||
{
|
||||
id: 'networking',
|
||||
title: 'Networking',
|
||||
description: 'VPC, DNS, load balancing, CDN',
|
||||
icon: 'Network',
|
||||
color: 'from-orange-500 to-red-500',
|
||||
href: '/networking/vpc'
|
||||
},
|
||||
{
|
||||
id: 'messaging',
|
||||
title: 'Messaging',
|
||||
description: 'Message queues, pub/sub, event-driven architecture',
|
||||
icon: 'MessageSquare',
|
||||
color: 'from-yellow-500 to-orange-500',
|
||||
href: '/messaging/queues'
|
||||
},
|
||||
{
|
||||
id: 'decisions',
|
||||
title: 'Decision Trees',
|
||||
description: 'Interactive guides: which database? serverless or not?',
|
||||
icon: 'GitBranch',
|
||||
color: 'from-indigo-500 to-purple-500',
|
||||
href: '/decisions/which-database'
|
||||
}
|
||||
];
|
||||
|
||||
const quickLinks = [
|
||||
{ label: 'SQL vs NoSQL', href: '/compare/sql-vs-nosql', icon: 'GitCompare' },
|
||||
{ label: 'Kafka vs RabbitMQ', href: '/compare/kafka-vs-rabbitmq', icon: 'GitCompare' },
|
||||
{ label: 'REST vs GraphQL vs gRPC', href: '/compare/api-design', icon: 'GitCompare' },
|
||||
{ label: 'Which Database?', href: '/decisions/which-database', icon: 'HelpCircle' }
|
||||
];
|
||||
|
||||
function getIcon(iconName: string) {
|
||||
return (Icons as Record<string, typeof Icons.Box>)[iconName] || Icons.Box;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>System Design Explorer</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-6xl mx-auto space-y-12">
|
||||
<!-- Hero Section -->
|
||||
<div class="text-center space-y-6 py-8">
|
||||
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-blue-500/10 border border-blue-500/20 text-blue-400 text-sm">
|
||||
<Icons.Sparkles class="w-4 h-4" />
|
||||
Interactive Visual Learning
|
||||
</div>
|
||||
|
||||
<h1 class="text-4xl md:text-5xl font-bold text-surface-100">
|
||||
Master <span class="text-gradient">System Design</span>
|
||||
</h1>
|
||||
|
||||
<p class="text-lg text-surface-400 max-w-2xl mx-auto">
|
||||
Learn cloud infrastructure, distributed systems, and architecture patterns through
|
||||
interactive diagrams, visual comparisons, and decision-guided walkthroughs.
|
||||
</p>
|
||||
|
||||
<div class="flex justify-center gap-4">
|
||||
<a href="/fundamentals/cap-theorem" class="btn-primary flex items-center gap-2">
|
||||
<Icons.Play class="w-4 h-4" />
|
||||
Start Learning
|
||||
</a>
|
||||
<a href="/decisions/which-database" class="btn-secondary flex items-center gap-2">
|
||||
<Icons.GitBranch class="w-4 h-4" />
|
||||
Try Decision Tree
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Category Cards -->
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{#each categories as category}
|
||||
{@const Icon = getIcon(category.icon)}
|
||||
<a
|
||||
href={category.href}
|
||||
class="card-hover group"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-12 h-12 rounded-xl bg-gradient-to-br {category.color} flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-200">
|
||||
<Icon class="w-6 h-6 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-surface-100 group-hover:text-white transition-colors">
|
||||
{category.title}
|
||||
</h3>
|
||||
<p class="text-sm text-surface-400 mt-1">{category.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex items-center text-sm text-blue-400 group-hover:text-blue-300">
|
||||
<span>Explore</span>
|
||||
<Icons.ArrowRight class="w-4 h-4 ml-1 group-hover:translate-x-1 transition-transform" />
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Quick Comparisons -->
|
||||
<div class="card">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="w-10 h-10 rounded-lg bg-purple-500/20 flex items-center justify-center">
|
||||
<Icons.Scale class="w-5 h-5 text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="font-semibold text-surface-100">Quick Comparisons</h2>
|
||||
<p class="text-sm text-surface-400">Side-by-side analysis with "when to use" guidance</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||
{#each quickLinks as link}
|
||||
{@const Icon = getIcon(link.icon)}
|
||||
<a
|
||||
href={link.href}
|
||||
class="flex items-center gap-3 p-3 rounded-lg bg-surface-800 hover:bg-surface-700 transition-colors"
|
||||
>
|
||||
<Icon class="w-5 h-5 text-surface-400" />
|
||||
<span class="text-sm text-surface-200">{link.label}</span>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cloud Providers Section -->
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<!-- AWS -->
|
||||
<div class="card border-aws-orange/30">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-10 h-10 rounded-lg bg-aws-orange/20 flex items-center justify-center">
|
||||
<Icons.Cloud class="w-5 h-5 text-aws-orange" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-surface-100">Amazon Web Services</h3>
|
||||
<p class="text-xs text-surface-500">EC2, S3, Lambda, RDS, EKS, and more</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
{#each ['EC2', 'S3', 'Lambda', 'RDS', 'EKS', 'SQS'] as service}
|
||||
<div class="px-2 py-1 rounded bg-aws-orange/10 text-aws-orange text-xs text-center">
|
||||
{service}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GCP -->
|
||||
<div class="card border-gcp-blue/30">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-10 h-10 rounded-lg bg-gcp-blue/20 flex items-center justify-center">
|
||||
<Icons.Cloud class="w-5 h-5 text-gcp-blue" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-surface-100">Google Cloud Platform</h3>
|
||||
<p class="text-xs text-surface-500">Compute Engine, Cloud Storage, GKE, and more</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
{#each ['Compute', 'Storage', 'GKE', 'Cloud SQL', 'Pub/Sub', 'BigQuery'] as service}
|
||||
<div class="px-2 py-1 rounded bg-gcp-blue/10 text-gcp-blue text-xs text-center">
|
||||
{service}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Key Concepts Preview -->
|
||||
<div class="card">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="w-10 h-10 rounded-lg bg-green-500/20 flex items-center justify-center">
|
||||
<Icons.Lightbulb class="w-5 h-5 text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="font-semibold text-surface-100">Key Concepts You'll Learn</h2>
|
||||
<p class="text-sm text-surface-400">Each with interactive diagrams and real-world examples</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{#each [
|
||||
{ name: 'CAP Theorem', desc: 'Consistency, Availability, Partition Tolerance trade-offs' },
|
||||
{ name: 'Load Balancing', desc: 'L4 vs L7, algorithms, health checks' },
|
||||
{ name: 'Database Sharding', desc: 'Horizontal scaling, partition strategies' },
|
||||
{ name: 'Caching Patterns', desc: 'Cache-aside, write-through, invalidation' },
|
||||
{ name: 'Message Queues', desc: 'When to use Kafka vs RabbitMQ vs SQS' },
|
||||
{ name: 'Kubernetes', desc: 'Pods, services, deployments, scaling' }
|
||||
] as concept}
|
||||
<div class="p-4 rounded-lg bg-surface-800/50 border border-surface-700">
|
||||
<h4 class="font-medium text-surface-200">{concept.name}</h4>
|
||||
<p class="text-xs text-surface-500 mt-1">{concept.desc}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
406
src/routes/compare/kafka-vs-rabbitmq/+page.svelte
Normal file
406
src/routes/compare/kafka-vs-rabbitmq/+page.svelte
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
<script lang="ts">
|
||||
import ComparisonTable from '$lib/components/diagrams/ComparisonTable.svelte';
|
||||
|
||||
let selectedSystem: 'kafka' | 'rabbitmq' | null = null;
|
||||
|
||||
const comparisonOptions = [
|
||||
{
|
||||
id: 'kafka',
|
||||
name: 'Apache Kafka',
|
||||
description: 'Distributed event streaming platform for high-throughput data pipelines',
|
||||
whenToUse: [
|
||||
'High-throughput event streaming (millions of messages/sec)',
|
||||
'Real-time analytics and data pipelines',
|
||||
'Event sourcing and audit trails',
|
||||
'Log aggregation from multiple sources',
|
||||
'Need to replay/reprocess historical messages',
|
||||
'Building data lakes and ML pipelines',
|
||||
'Long-term message retention (days/weeks)'
|
||||
],
|
||||
whenNotToUse: [
|
||||
'Simple task queues with low volume',
|
||||
'Need complex routing patterns',
|
||||
'Request/reply messaging patterns',
|
||||
'Low-latency at low throughput more important',
|
||||
'Need message priorities',
|
||||
'Want simpler operations and setup'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'rabbitmq',
|
||||
name: 'RabbitMQ',
|
||||
description: 'Traditional message broker for reliable delivery and flexible routing',
|
||||
whenToUse: [
|
||||
'Complex routing patterns (topic, fanout, headers)',
|
||||
'Traditional request/reply messaging',
|
||||
'Task queues with acknowledgments',
|
||||
'Need message priorities',
|
||||
'Multiple protocol support (AMQP, MQTT, STOMP)',
|
||||
'Microservices communication',
|
||||
'Lower volume with guaranteed delivery'
|
||||
],
|
||||
whenNotToUse: [
|
||||
'Need millions of messages per second',
|
||||
'Want to replay old messages',
|
||||
'Building real-time analytics pipelines',
|
||||
'Long-term message storage required',
|
||||
'Event sourcing architecture',
|
||||
'Horizontal scaling is critical'
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const features = [
|
||||
{ name: 'Architecture', kafka: 'Distributed log (append-only)', rabbitmq: 'Message broker (queue-based)' },
|
||||
{ name: 'Delivery Model', kafka: 'Pull (consumers poll)', rabbitmq: 'Push (broker pushes)' },
|
||||
{ name: 'Message Retention', kafka: 'Time/size based (keeps messages)', rabbitmq: 'Until acknowledged (deletes)' },
|
||||
{ name: 'Throughput', kafka: 'Millions/sec per broker', rabbitmq: '10K-50K/sec' },
|
||||
{ name: 'Latency', kafka: '~5ms at scale', rabbitmq: 'Sub-ms at low volume' },
|
||||
{ name: 'Ordering', kafka: 'Per partition only', rabbitmq: 'Per queue' },
|
||||
{ name: 'Replay', kafka: 'Yes (offset-based)', rabbitmq: 'No (messages deleted)' },
|
||||
{ name: 'Routing', kafka: 'Topic-based only', rabbitmq: 'Flexible (exchanges, bindings)' },
|
||||
{ name: 'Protocol', kafka: 'Kafka protocol (binary)', rabbitmq: 'AMQP, MQTT, STOMP, HTTP' },
|
||||
{ name: 'Scaling', kafka: 'Horizontal (partitions)', rabbitmq: 'Vertical + clustering' },
|
||||
{ name: 'Consumer Groups', kafka: 'Built-in (partition-based)', rabbitmq: 'Manual (competing consumers)' }
|
||||
];
|
||||
|
||||
const architectureDiff = {
|
||||
kafka: {
|
||||
title: 'Distributed Log',
|
||||
description: 'Messages are appended to partitioned logs and retained based on time/size policies',
|
||||
flow: [
|
||||
{ label: 'Producer', desc: 'Writes to topic partition' },
|
||||
{ label: 'Broker', desc: 'Stores in append-only log' },
|
||||
{ label: 'Consumer', desc: 'Pulls at own pace via offset' }
|
||||
],
|
||||
keyPoint: 'Messages persist regardless of consumption - consumers track their own position (offset)'
|
||||
},
|
||||
rabbitmq: {
|
||||
title: 'Message Broker',
|
||||
description: 'Messages routed through exchanges to queues and removed once acknowledged',
|
||||
flow: [
|
||||
{ label: 'Producer', desc: 'Sends to exchange' },
|
||||
{ label: 'Exchange', desc: 'Routes to queue(s)' },
|
||||
{ label: 'Consumer', desc: 'Receives push, sends ack' }
|
||||
],
|
||||
keyPoint: 'Messages deleted after acknowledgment - broker tracks delivery state'
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Kafka vs RabbitMQ</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<h1 class="text-3xl font-bold text-white mb-2">Kafka vs RabbitMQ</h1>
|
||||
<p class="text-gray-400 mb-8">Event streaming platform vs traditional message broker - understanding the fundamental differences</p>
|
||||
|
||||
<!-- TL;DR -->
|
||||
<section class="mb-12">
|
||||
<div class="bg-gradient-to-r from-orange-500/10 to-green-500/10 rounded-xl p-6 border border-gray-700">
|
||||
<h2 class="text-lg font-semibold text-white mb-4">TL;DR</h2>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-3 h-3 rounded-full bg-orange-500 mt-1.5"></div>
|
||||
<div>
|
||||
<span class="text-orange-400 font-medium">Kafka</span>
|
||||
<span class="text-gray-300"> = Event streaming. High throughput, message replay, data pipelines.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-3 h-3 rounded-full bg-green-500 mt-1.5"></div>
|
||||
<div>
|
||||
<span class="text-green-400 font-medium">RabbitMQ</span>
|
||||
<span class="text-gray-300"> = Message broker. Complex routing, guaranteed delivery, task queues.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Architecture Comparison -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Architecture Difference</h2>
|
||||
<p class="text-gray-400 mb-6">The fundamental architectural difference explains most of their behavior differences.</p>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<!-- Kafka -->
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-orange-500/30">
|
||||
<h3 class="text-lg font-semibold text-orange-400 mb-2">{architectureDiff.kafka.title}</h3>
|
||||
<p class="text-gray-400 text-sm mb-4">{architectureDiff.kafka.description}</p>
|
||||
|
||||
<div class="space-y-3 mb-4">
|
||||
{#each architectureDiff.kafka.flow as step, i}
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-lg bg-orange-500/20 flex items-center justify-center text-orange-400 font-bold text-sm">
|
||||
{i + 1}
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-white font-medium text-sm">{step.label}</div>
|
||||
<div class="text-gray-500 text-xs">{step.desc}</div>
|
||||
</div>
|
||||
{#if i < architectureDiff.kafka.flow.length - 1}
|
||||
<div class="flex-1 border-t border-dashed border-orange-500/30"></div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="p-3 bg-orange-500/10 rounded-lg border border-orange-500/20">
|
||||
<div class="text-orange-300 text-sm">{architectureDiff.kafka.keyPoint}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RabbitMQ -->
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-green-500/30">
|
||||
<h3 class="text-lg font-semibold text-green-400 mb-2">{architectureDiff.rabbitmq.title}</h3>
|
||||
<p class="text-gray-400 text-sm mb-4">{architectureDiff.rabbitmq.description}</p>
|
||||
|
||||
<div class="space-y-3 mb-4">
|
||||
{#each architectureDiff.rabbitmq.flow as step, i}
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-lg bg-green-500/20 flex items-center justify-center text-green-400 font-bold text-sm">
|
||||
{i + 1}
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-white font-medium text-sm">{step.label}</div>
|
||||
<div class="text-gray-500 text-xs">{step.desc}</div>
|
||||
</div>
|
||||
{#if i < architectureDiff.rabbitmq.flow.length - 1}
|
||||
<div class="flex-1 border-t border-dashed border-green-500/30"></div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="p-3 bg-green-500/10 rounded-lg border border-green-500/20">
|
||||
<div class="text-green-300 text-sm">{architectureDiff.rabbitmq.keyPoint}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Message Lifecycle -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Message Lifecycle</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<h3 class="text-lg font-semibold text-orange-400 mb-4">Kafka: Log-Based</h3>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center gap-3 p-3 bg-orange-500/10 rounded-lg">
|
||||
<span class="text-2xl">1.</span>
|
||||
<div>
|
||||
<div class="text-white">Message written to partition</div>
|
||||
<div class="text-gray-500 text-sm">Appended to end of log with offset</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 p-3 bg-orange-500/10 rounded-lg">
|
||||
<span class="text-2xl">2.</span>
|
||||
<div>
|
||||
<div class="text-white">Consumers read at their offset</div>
|
||||
<div class="text-gray-500 text-sm">Multiple consumers can read same message</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 p-3 bg-orange-500/10 rounded-lg">
|
||||
<span class="text-2xl">3.</span>
|
||||
<div>
|
||||
<div class="text-white">Retention policy deletes old messages</div>
|
||||
<div class="text-gray-500 text-sm">After 7 days or 1GB (configurable)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<h3 class="text-lg font-semibold text-green-400 mb-4">RabbitMQ: Queue-Based</h3>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center gap-3 p-3 bg-green-500/10 rounded-lg">
|
||||
<span class="text-2xl">1.</span>
|
||||
<div>
|
||||
<div class="text-white">Message routed to queue(s)</div>
|
||||
<div class="text-gray-500 text-sm">Exchange determines destination</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 p-3 bg-green-500/10 rounded-lg">
|
||||
<span class="text-2xl">2.</span>
|
||||
<div>
|
||||
<div class="text-white">Broker pushes to consumer</div>
|
||||
<div class="text-gray-500 text-sm">One consumer receives each message</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 p-3 bg-green-500/10 rounded-lg">
|
||||
<span class="text-2xl">3.</span>
|
||||
<div>
|
||||
<div class="text-white">Consumer acknowledges</div>
|
||||
<div class="text-gray-500 text-sm">Message deleted from queue</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Feature Comparison -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Feature Comparison</h2>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl border border-gray-700 overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-700">
|
||||
<th class="text-left p-4 text-gray-400 font-medium">Feature</th>
|
||||
<th class="text-left p-4 text-orange-400 font-medium">Kafka</th>
|
||||
<th class="text-left p-4 text-green-400 font-medium">RabbitMQ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each features as feature, i}
|
||||
<tr class="border-b border-gray-700/50 {i % 2 === 0 ? 'bg-gray-800/30' : ''}">
|
||||
<td class="p-4 text-white font-medium">{feature.name}</td>
|
||||
<td class="p-4 text-gray-300">{feature.kafka}</td>
|
||||
<td class="p-4 text-gray-300">{feature.rabbitmq}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- When to Use -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">When to Use Each</h2>
|
||||
<ComparisonTable options={comparisonOptions} />
|
||||
</section>
|
||||
|
||||
<!-- Exchange Types (RabbitMQ) -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">RabbitMQ Routing Patterns</h2>
|
||||
<p class="text-gray-400 mb-4">RabbitMQ's flexible exchange types enable complex routing scenarios that Kafka cannot do natively.</p>
|
||||
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-green-400 font-semibold mb-2">Direct</div>
|
||||
<div class="text-gray-400 text-sm">Route by exact routing key match</div>
|
||||
<div class="mt-2 text-xs text-gray-500">order.created to orders queue</div>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-green-400 font-semibold mb-2">Topic</div>
|
||||
<div class="text-gray-400 text-sm">Route by pattern matching (*.error)</div>
|
||||
<div class="mt-2 text-xs text-gray-500">app.*.error to error-handler</div>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-green-400 font-semibold mb-2">Fanout</div>
|
||||
<div class="text-gray-400 text-sm">Broadcast to all bound queues</div>
|
||||
<div class="mt-2 text-xs text-gray-500">notification to all subscribers</div>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-green-400 font-semibold mb-2">Headers</div>
|
||||
<div class="text-gray-400 text-sm">Route by message headers</div>
|
||||
<div class="mt-2 text-xs text-gray-500">x-priority: high to fast queue</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Kafka Partitions -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Kafka Partitioning</h2>
|
||||
<p class="text-gray-400 mb-4">Kafka achieves scalability through partitions - each partition is an ordered, immutable log.</p>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<div class="grid md:grid-cols-3 gap-4 mb-6">
|
||||
<div class="p-4 bg-orange-500/10 rounded-lg border border-orange-500/30">
|
||||
<div class="text-orange-400 font-semibold mb-1">Partition 0</div>
|
||||
<div class="flex gap-1">
|
||||
{#each [0, 1, 2, 3, 4] as offset}
|
||||
<div class="w-8 h-8 bg-orange-500/30 rounded flex items-center justify-center text-xs text-orange-300">{offset}</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 bg-orange-500/10 rounded-lg border border-orange-500/30">
|
||||
<div class="text-orange-400 font-semibold mb-1">Partition 1</div>
|
||||
<div class="flex gap-1">
|
||||
{#each [0, 1, 2, 3] as offset}
|
||||
<div class="w-8 h-8 bg-orange-500/30 rounded flex items-center justify-center text-xs text-orange-300">{offset}</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 bg-orange-500/10 rounded-lg border border-orange-500/30">
|
||||
<div class="text-orange-400 font-semibold mb-1">Partition 2</div>
|
||||
<div class="flex gap-1">
|
||||
{#each [0, 1, 2, 3, 4, 5] as offset}
|
||||
<div class="w-8 h-8 bg-orange-500/30 rounded flex items-center justify-center text-xs text-orange-300">{offset}</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-3 gap-4 text-sm">
|
||||
<div>
|
||||
<div class="text-white font-medium">Ordering</div>
|
||||
<div class="text-gray-400">Guaranteed within partition only</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-white font-medium">Parallelism</div>
|
||||
<div class="text-gray-400">Max consumers = number of partitions</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-white font-medium">Key-based routing</div>
|
||||
<div class="text-gray-400">Same key always goes to same partition</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Use Case Examples -->
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Real World Examples</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-orange-500/30">
|
||||
<h3 class="text-lg font-semibold text-orange-400 mb-4">Kafka Use Cases</h3>
|
||||
<div class="space-y-3">
|
||||
<div class="p-3 bg-gray-700/50 rounded-lg">
|
||||
<div class="text-white font-medium">Real-time Analytics</div>
|
||||
<div class="text-gray-400 text-sm">Stream clickstream data to analytics pipeline</div>
|
||||
</div>
|
||||
<div class="p-3 bg-gray-700/50 rounded-lg">
|
||||
<div class="text-white font-medium">Event Sourcing</div>
|
||||
<div class="text-gray-400 text-sm">Store all state changes as immutable events</div>
|
||||
</div>
|
||||
<div class="p-3 bg-gray-700/50 rounded-lg">
|
||||
<div class="text-white font-medium">Log Aggregation</div>
|
||||
<div class="text-gray-400 text-sm">Centralize logs from thousands of services</div>
|
||||
</div>
|
||||
<div class="p-3 bg-gray-700/50 rounded-lg">
|
||||
<div class="text-white font-medium">CDC Pipelines</div>
|
||||
<div class="text-gray-400 text-sm">Stream database changes to data warehouse</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-green-500/30">
|
||||
<h3 class="text-lg font-semibold text-green-400 mb-4">RabbitMQ Use Cases</h3>
|
||||
<div class="space-y-3">
|
||||
<div class="p-3 bg-gray-700/50 rounded-lg">
|
||||
<div class="text-white font-medium">Task Queues</div>
|
||||
<div class="text-gray-400 text-sm">Background job processing with retries</div>
|
||||
</div>
|
||||
<div class="p-3 bg-gray-700/50 rounded-lg">
|
||||
<div class="text-white font-medium">Microservices</div>
|
||||
<div class="text-gray-400 text-sm">Async communication between services</div>
|
||||
</div>
|
||||
<div class="p-3 bg-gray-700/50 rounded-lg">
|
||||
<div class="text-white font-medium">IoT Messaging</div>
|
||||
<div class="text-gray-400 text-sm">MQTT protocol for device communication</div>
|
||||
</div>
|
||||
<div class="p-3 bg-gray-700/50 rounded-lg">
|
||||
<div class="text-white font-medium">Request/Reply</div>
|
||||
<div class="text-gray-400 text-sm">RPC-style communication patterns</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
343
src/routes/compare/sql-vs-nosql/+page.svelte
Normal file
343
src/routes/compare/sql-vs-nosql/+page.svelte
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
<script lang="ts">
|
||||
import * as Icons from 'lucide-svelte';
|
||||
import ComparisonTable from '$lib/components/diagrams/ComparisonTable.svelte';
|
||||
|
||||
const options = [
|
||||
{
|
||||
id: 'sql',
|
||||
name: 'SQL (Relational)',
|
||||
color: '#3B82F6',
|
||||
icon: 'Table',
|
||||
whenToUse: [
|
||||
'Complex queries with JOINs across multiple tables',
|
||||
'ACID compliance is required (financial transactions)',
|
||||
'Data has clear relationships and fixed schema',
|
||||
'Strong data integrity and consistency needed',
|
||||
'Reporting and analytics with complex aggregations'
|
||||
],
|
||||
whenNotToUse: [
|
||||
'Rapidly changing or unknown schema',
|
||||
'Massive horizontal scaling requirements',
|
||||
'Simple key-value lookups at extreme scale',
|
||||
'Hierarchical or document-like data',
|
||||
'Write-heavy workloads at massive scale'
|
||||
],
|
||||
realWorldExample: 'E-commerce order management: Orders, customers, products, and inventory have clear relationships. You need to JOIN tables to show "all orders for customer X with product details" and ensure inventory counts are accurate with ACID transactions.'
|
||||
},
|
||||
{
|
||||
id: 'nosql-document',
|
||||
name: 'NoSQL (Document)',
|
||||
color: '#22C55E',
|
||||
icon: 'FileJson',
|
||||
whenToUse: [
|
||||
'Flexible, evolving schema',
|
||||
'Hierarchical or nested data (JSON-like)',
|
||||
'Rapid development with changing requirements',
|
||||
'Content management and user profiles',
|
||||
'Each document is largely self-contained'
|
||||
],
|
||||
whenNotToUse: [
|
||||
'Heavy relational queries (many JOINs)',
|
||||
'Strong consistency is critical',
|
||||
'Complex transactions across documents',
|
||||
'Data has clear tabular structure',
|
||||
'Strict schema enforcement needed'
|
||||
],
|
||||
realWorldExample: 'Product catalog: Each product can have different attributes (electronics vs clothing). Schema evolves as you add new product types. MongoDB stores each product as a document with flexible fields.'
|
||||
},
|
||||
{
|
||||
id: 'nosql-keyvalue',
|
||||
name: 'NoSQL (Key-Value)',
|
||||
color: '#EAB308',
|
||||
icon: 'Key',
|
||||
whenToUse: [
|
||||
'Session storage and caching',
|
||||
'Simple lookups by key at extreme speed',
|
||||
'Leaderboards and counters',
|
||||
'Shopping carts and temporary data',
|
||||
'Real-time data with TTL (expiration)'
|
||||
],
|
||||
whenNotToUse: [
|
||||
'Complex queries beyond key lookup',
|
||||
'Relational data with JOINs',
|
||||
'Full-text search requirements',
|
||||
'Data needs to be queried by multiple fields',
|
||||
'Complex data structures'
|
||||
],
|
||||
realWorldExample: 'Session storage in Redis: User logs in, you store session data with key "session:user123". Lightning-fast lookups. Automatic expiration after 30 minutes. Perfect for caching database query results.'
|
||||
},
|
||||
{
|
||||
id: 'nosql-widecolumn',
|
||||
name: 'NoSQL (Wide-Column)',
|
||||
color: '#EC4899',
|
||||
icon: 'LayoutGrid',
|
||||
whenToUse: [
|
||||
'Time-series data (IoT, metrics, logs)',
|
||||
'Massive write throughput needed',
|
||||
'Data partitioned by time or ID',
|
||||
'Distributed across many nodes',
|
||||
'Write-heavy with sequential reads'
|
||||
],
|
||||
whenNotToUse: [
|
||||
'Ad-hoc queries on any column',
|
||||
'Complex JOINs or aggregations',
|
||||
'Small datasets (overkill)',
|
||||
'Strong consistency requirements',
|
||||
'Frequent schema changes'
|
||||
],
|
||||
realWorldExample: 'IoT sensor data in Cassandra: Millions of sensors writing data every second. Partition by sensor_id and cluster by timestamp. Query pattern: "get all readings for sensor X in the last hour."'
|
||||
}
|
||||
];
|
||||
|
||||
const features = [
|
||||
{
|
||||
feature: 'Schema',
|
||||
description: 'How data structure is defined',
|
||||
values: {
|
||||
sql: 'Fixed, predefined schema',
|
||||
'nosql-document': 'Flexible, dynamic schema',
|
||||
'nosql-keyvalue': 'Schema-less (key-value pairs)',
|
||||
'nosql-widecolumn': 'Flexible columns per row'
|
||||
}
|
||||
},
|
||||
{
|
||||
feature: 'Data Model',
|
||||
description: 'How data is organized',
|
||||
values: {
|
||||
sql: 'Tables with rows and columns',
|
||||
'nosql-document': 'JSON-like documents',
|
||||
'nosql-keyvalue': 'Simple key-value pairs',
|
||||
'nosql-widecolumn': 'Column families'
|
||||
}
|
||||
},
|
||||
{
|
||||
feature: 'Scaling',
|
||||
description: 'How to handle growth',
|
||||
values: {
|
||||
sql: 'Vertical (bigger server)',
|
||||
'nosql-document': 'Horizontal (more servers)',
|
||||
'nosql-keyvalue': 'Horizontal (more servers)',
|
||||
'nosql-widecolumn': 'Horizontal (more servers)'
|
||||
}
|
||||
},
|
||||
{
|
||||
feature: 'ACID Transactions',
|
||||
description: 'Atomicity, Consistency, Isolation, Durability',
|
||||
values: {
|
||||
sql: true,
|
||||
'nosql-document': 'Partial (single document)',
|
||||
'nosql-keyvalue': false,
|
||||
'nosql-widecolumn': 'Partial (row-level)'
|
||||
}
|
||||
},
|
||||
{
|
||||
feature: 'JOINs',
|
||||
description: 'Combining data from multiple sources',
|
||||
values: {
|
||||
sql: true,
|
||||
'nosql-document': false,
|
||||
'nosql-keyvalue': false,
|
||||
'nosql-widecolumn': false
|
||||
}
|
||||
},
|
||||
{
|
||||
feature: 'Query Language',
|
||||
description: 'How you retrieve data',
|
||||
values: {
|
||||
sql: 'SQL (standardized)',
|
||||
'nosql-document': 'Proprietary (varies)',
|
||||
'nosql-keyvalue': 'GET/SET operations',
|
||||
'nosql-widecolumn': 'CQL (SQL-like)'
|
||||
}
|
||||
},
|
||||
{
|
||||
feature: 'Consistency',
|
||||
description: 'Data accuracy guarantees',
|
||||
values: {
|
||||
sql: 'Strong consistency',
|
||||
'nosql-document': 'Tunable (eventual to strong)',
|
||||
'nosql-keyvalue': 'Eventual consistency',
|
||||
'nosql-widecolumn': 'Tunable consistency'
|
||||
}
|
||||
},
|
||||
{
|
||||
feature: 'Read Performance',
|
||||
values: {
|
||||
sql: 'Good (with indexes)',
|
||||
'nosql-document': 'Excellent (by _id)',
|
||||
'nosql-keyvalue': 'Excellent (by key)',
|
||||
'nosql-widecolumn': 'Good (by partition key)'
|
||||
}
|
||||
},
|
||||
{
|
||||
feature: 'Write Performance',
|
||||
values: {
|
||||
sql: 'Good',
|
||||
'nosql-document': 'Excellent',
|
||||
'nosql-keyvalue': 'Excellent',
|
||||
'nosql-widecolumn': 'Excellent'
|
||||
}
|
||||
},
|
||||
{
|
||||
feature: 'Examples',
|
||||
values: {
|
||||
sql: 'PostgreSQL, MySQL, SQL Server',
|
||||
'nosql-document': 'MongoDB, Firestore, CouchDB',
|
||||
'nosql-keyvalue': 'Redis, DynamoDB, Memcached',
|
||||
'nosql-widecolumn': 'Cassandra, Bigtable, ScyllaDB'
|
||||
}
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>SQL vs NoSQL - System Design Explorer</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-6xl mx-auto space-y-8">
|
||||
<!-- Header -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 text-surface-500 text-sm mb-2">
|
||||
<a href="/compare" class="hover:text-surface-300">Compare</a>
|
||||
<Icons.ChevronRight class="w-4 h-4" />
|
||||
<span class="text-surface-300">SQL vs NoSQL</span>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-surface-100">SQL vs NoSQL Databases</h1>
|
||||
<p class="text-surface-400 mt-2">
|
||||
Understanding when to use relational vs non-relational databases for your use case.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Quick Summary Cards -->
|
||||
<div class="grid md:grid-cols-2 gap-4">
|
||||
<div class="card border-blue-500/30">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-blue-500/20 flex items-center justify-center">
|
||||
<Icons.Table class="w-5 h-5 text-blue-400" />
|
||||
</div>
|
||||
<span class="font-semibold text-surface-100">SQL in a Nutshell</span>
|
||||
</div>
|
||||
<p class="text-sm text-surface-400">
|
||||
Structured data in tables with relationships. Strong consistency, ACID transactions,
|
||||
complex queries with JOINs. Scale vertically. Best when data structure is known and
|
||||
relationships matter.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card border-green-500/30">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-green-500/20 flex items-center justify-center">
|
||||
<Icons.FileJson class="w-5 h-5 text-green-400" />
|
||||
</div>
|
||||
<span class="font-semibold text-surface-100">NoSQL in a Nutshell</span>
|
||||
</div>
|
||||
<p class="text-sm text-surface-400">
|
||||
Flexible schemas, horizontal scaling, eventual consistency. Different types for
|
||||
different needs: documents, key-value, wide-column, graph. Best for scale,
|
||||
flexibility, and specific access patterns.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Comparison Table -->
|
||||
<ComparisonTable
|
||||
title="Feature Comparison"
|
||||
subtitle="Click on a column header to see detailed guidance"
|
||||
{options}
|
||||
{features}
|
||||
/>
|
||||
|
||||
<!-- The Real Question -->
|
||||
<div class="card bg-gradient-to-br from-purple-500/10 to-pink-500/10 border-purple-500/30">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-10 h-10 rounded-lg bg-purple-500/20 flex items-center justify-center flex-shrink-0">
|
||||
<Icons.Lightbulb class="w-5 h-5 text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-surface-100 mb-2">The Real Question to Ask</h3>
|
||||
<p class="text-surface-300 text-sm mb-4">
|
||||
Instead of "SQL or NoSQL?", ask yourself these questions:
|
||||
</p>
|
||||
<div class="grid md:grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-start gap-2">
|
||||
<Icons.HelpCircle class="w-4 h-4 text-purple-400 mt-0.5 flex-shrink-0" />
|
||||
<span class="text-sm text-surface-300">What are my query patterns? (JOINs? Key lookups?)</span>
|
||||
</div>
|
||||
<div class="flex items-start gap-2">
|
||||
<Icons.HelpCircle class="w-4 h-4 text-purple-400 mt-0.5 flex-shrink-0" />
|
||||
<span class="text-sm text-surface-300">How important is consistency vs availability?</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-start gap-2">
|
||||
<Icons.HelpCircle class="w-4 h-4 text-purple-400 mt-0.5 flex-shrink-0" />
|
||||
<span class="text-sm text-surface-300">Will my schema evolve frequently?</span>
|
||||
</div>
|
||||
<div class="flex items-start gap-2">
|
||||
<Icons.HelpCircle class="w-4 h-4 text-purple-400 mt-0.5 flex-shrink-0" />
|
||||
<span class="text-sm text-surface-300">What scale do I need to handle?</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Common Patterns -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-semibold text-surface-100 mb-4">Common Patterns in Production</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start gap-4 p-4 bg-surface-800 rounded-lg">
|
||||
<div class="w-8 h-8 rounded-lg bg-blue-500/20 flex items-center justify-center flex-shrink-0">
|
||||
<span class="text-sm font-bold text-blue-400">1</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium text-surface-200">Polyglot Persistence</h4>
|
||||
<p class="text-sm text-surface-400 mt-1">
|
||||
Use multiple databases for different needs. PostgreSQL for transactions, Redis for caching,
|
||||
Elasticsearch for search, MongoDB for flexible data. Match the database to the use case.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start gap-4 p-4 bg-surface-800 rounded-lg">
|
||||
<div class="w-8 h-8 rounded-lg bg-green-500/20 flex items-center justify-center flex-shrink-0">
|
||||
<span class="text-sm font-bold text-green-400">2</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium text-surface-200">Start with SQL, Add NoSQL Later</h4>
|
||||
<p class="text-sm text-surface-400 mt-1">
|
||||
Begin with PostgreSQL - it's versatile, well-understood, and handles most use cases.
|
||||
Add specialized databases when you hit specific scaling or performance needs.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start gap-4 p-4 bg-surface-800 rounded-lg">
|
||||
<div class="w-8 h-8 rounded-lg bg-yellow-500/20 flex items-center justify-center flex-shrink-0">
|
||||
<span class="text-sm font-bold text-yellow-400">3</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium text-surface-200">Cache Hot Data</h4>
|
||||
<p class="text-sm text-surface-400 mt-1">
|
||||
Use Redis in front of your primary database. Most apps have hot data (frequently accessed)
|
||||
and cold data. Cache the hot data for massive performance gains without changing your primary DB.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="flex justify-between items-center pt-4 border-t border-surface-800">
|
||||
<a href="/compare" class="btn-secondary flex items-center gap-2">
|
||||
<Icons.ArrowLeft class="w-4 h-4" />
|
||||
All Comparisons
|
||||
</a>
|
||||
<a href="/decisions/which-database" class="btn-primary flex items-center gap-2">
|
||||
Try Decision Tree
|
||||
<Icons.GitBranch class="w-4 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
397
src/routes/compute/kubernetes/+page.svelte
Normal file
397
src/routes/compute/kubernetes/+page.svelte
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
<script lang="ts">
|
||||
let selectedComponent: string | null = null;
|
||||
|
||||
const controlPlaneComponents = [
|
||||
{
|
||||
id: 'api-server',
|
||||
name: 'kube-apiserver',
|
||||
description: 'Front door to the cluster - all communication goes through here',
|
||||
details: [
|
||||
'Exposes the Kubernetes API (REST)',
|
||||
'Validates and processes API requests',
|
||||
'Only component that talks to etcd',
|
||||
'Handles authentication and authorization'
|
||||
],
|
||||
color: 'blue'
|
||||
},
|
||||
{
|
||||
id: 'etcd',
|
||||
name: 'etcd',
|
||||
description: 'Distributed key-value store - the source of truth for cluster state',
|
||||
details: [
|
||||
'Stores all cluster configuration',
|
||||
'Highly available (Raft consensus)',
|
||||
'Only accessed by API server',
|
||||
'Critical - backup regularly!'
|
||||
],
|
||||
color: 'purple'
|
||||
},
|
||||
{
|
||||
id: 'scheduler',
|
||||
name: 'kube-scheduler',
|
||||
description: 'Decides which node runs each pod',
|
||||
details: [
|
||||
'Watches for unscheduled pods',
|
||||
'Evaluates node resources and constraints',
|
||||
'Considers affinity/anti-affinity rules',
|
||||
'Assigns pod to best-fit node'
|
||||
],
|
||||
color: 'green'
|
||||
},
|
||||
{
|
||||
id: 'controller-manager',
|
||||
name: 'kube-controller-manager',
|
||||
description: 'Runs control loops that maintain desired state',
|
||||
details: [
|
||||
'Node Controller - monitors node health',
|
||||
'Deployment Controller - manages rollouts',
|
||||
'ReplicaSet Controller - ensures pod count',
|
||||
'Job Controller - runs one-off tasks'
|
||||
],
|
||||
color: 'orange'
|
||||
},
|
||||
{
|
||||
id: 'cloud-controller',
|
||||
name: 'cloud-controller-manager',
|
||||
description: 'Integrates with cloud provider APIs',
|
||||
details: [
|
||||
'Creates load balancers for Services',
|
||||
'Provisions cloud storage volumes',
|
||||
'Manages node lifecycle in cloud',
|
||||
'Only present in cloud deployments'
|
||||
],
|
||||
color: 'cyan'
|
||||
}
|
||||
];
|
||||
|
||||
const nodeComponents = [
|
||||
{
|
||||
id: 'kubelet',
|
||||
name: 'kubelet',
|
||||
description: 'Agent on each node - ensures containers are running',
|
||||
details: [
|
||||
'Receives pod specs from API server',
|
||||
'Manages container lifecycle via CRI',
|
||||
'Reports node and pod status',
|
||||
'Handles liveness/readiness probes'
|
||||
],
|
||||
color: 'yellow'
|
||||
},
|
||||
{
|
||||
id: 'kube-proxy',
|
||||
name: 'kube-proxy',
|
||||
description: 'Network proxy - implements Service networking',
|
||||
details: [
|
||||
'Maintains iptables/IPVS rules',
|
||||
'Routes traffic to correct pods',
|
||||
'Handles ClusterIP, NodePort, LoadBalancer',
|
||||
'Can be replaced by CNI plugins'
|
||||
],
|
||||
color: 'pink'
|
||||
},
|
||||
{
|
||||
id: 'container-runtime',
|
||||
name: 'Container Runtime',
|
||||
description: 'Actually runs containers (containerd, CRI-O)',
|
||||
details: [
|
||||
'Pulls container images',
|
||||
'Creates and runs containers',
|
||||
'Manages container lifecycle',
|
||||
'Implements Container Runtime Interface (CRI)'
|
||||
],
|
||||
color: 'gray'
|
||||
}
|
||||
];
|
||||
|
||||
const podLifecycle = [
|
||||
{ phase: 'Pending', desc: 'Pod accepted but not yet scheduled', color: 'yellow' },
|
||||
{ phase: 'Scheduled', desc: 'Scheduler assigned pod to a node', color: 'blue' },
|
||||
{ phase: 'ContainerCreating', desc: 'Pulling images, creating containers', color: 'purple' },
|
||||
{ phase: 'Running', desc: 'All containers started', color: 'green' },
|
||||
{ phase: 'Succeeded/Failed', desc: 'All containers terminated', color: 'gray' }
|
||||
];
|
||||
|
||||
const requestFlow = [
|
||||
{ step: 1, title: 'kubectl apply', desc: 'User submits manifest to API server' },
|
||||
{ step: 2, title: 'Validation', desc: 'API server validates and stores in etcd' },
|
||||
{ step: 3, title: 'Scheduling', desc: 'Scheduler picks optimal node for pod' },
|
||||
{ step: 4, title: 'Kubelet notified', desc: 'Node kubelet sees new pod assignment' },
|
||||
{ step: 5, title: 'Container creation', desc: 'Kubelet tells runtime to start containers' },
|
||||
{ step: 6, title: 'Running', desc: 'Pod running, kube-proxy sets up networking' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Kubernetes Architecture</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<h1 class="text-3xl font-bold text-white mb-2">Kubernetes Architecture</h1>
|
||||
<p class="text-gray-400 mb-8">Understanding the control plane, worker nodes, and how they work together</p>
|
||||
|
||||
<!-- Overview Diagram -->
|
||||
<section class="mb-12">
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<div class="grid md:grid-cols-2 gap-8">
|
||||
<!-- Control Plane -->
|
||||
<div class="p-4 rounded-xl bg-blue-500/10 border border-blue-500/30">
|
||||
<h3 class="text-lg font-semibold text-blue-400 mb-4 text-center">Control Plane</h3>
|
||||
<p class="text-gray-400 text-sm text-center mb-4">The brain - makes decisions about the cluster</p>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
{#each controlPlaneComponents.slice(0, 4) as component}
|
||||
<button
|
||||
class="p-3 rounded-lg bg-gray-800/50 border border-gray-600 hover:border-gray-400 transition-all text-left"
|
||||
class:border-blue-500={selectedComponent === component.id}
|
||||
on:click={() => selectedComponent = selectedComponent === component.id ? null : component.id}
|
||||
>
|
||||
<div class="text-white font-medium text-sm">{component.name}</div>
|
||||
<div class="text-gray-500 text-xs mt-1 line-clamp-2">{component.description}</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Worker Nodes -->
|
||||
<div class="p-4 rounded-xl bg-green-500/10 border border-green-500/30">
|
||||
<h3 class="text-lg font-semibold text-green-400 mb-4 text-center">Worker Nodes</h3>
|
||||
<p class="text-gray-400 text-sm text-center mb-4">The muscle - runs your actual workloads</p>
|
||||
|
||||
<div class="space-y-3">
|
||||
{#each nodeComponents as component}
|
||||
<button
|
||||
class="w-full p-3 rounded-lg bg-gray-800/50 border border-gray-600 hover:border-gray-400 transition-all text-left"
|
||||
class:border-green-500={selectedComponent === component.id}
|
||||
on:click={() => selectedComponent = selectedComponent === component.id ? null : component.id}
|
||||
>
|
||||
<div class="text-white font-medium text-sm">{component.name}</div>
|
||||
<div class="text-gray-500 text-xs mt-1">{component.description}</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Selected Component Details -->
|
||||
{#if selectedComponent}
|
||||
{@const allComponents = [...controlPlaneComponents, ...nodeComponents]}
|
||||
{@const component = allComponents.find(c => c.id === selectedComponent)}
|
||||
{#if component}
|
||||
<div class="mt-6 p-4 bg-gray-700/50 rounded-lg border border-gray-600">
|
||||
<h4 class="text-white font-semibold mb-3">{component.name}</h4>
|
||||
<ul class="grid md:grid-cols-2 gap-2">
|
||||
{#each component.details as detail}
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-blue-400 mt-0.5">-</span>
|
||||
{detail}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Control Plane Deep Dive -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Control Plane Components</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
{#each controlPlaneComponents as component}
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-white">{component.name}</h3>
|
||||
<p class="text-gray-400 text-sm">{component.description}</p>
|
||||
</div>
|
||||
<span class="px-3 py-1 rounded-full text-xs font-medium bg-{component.color}-500/20 text-{component.color}-400">
|
||||
Control Plane
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid md:grid-cols-2 gap-3">
|
||||
{#each component.details as detail}
|
||||
<div class="flex items-center gap-2 p-2 bg-gray-700/30 rounded-lg text-sm text-gray-300">
|
||||
<span class="w-2 h-2 rounded-full bg-{component.color}-500"></span>
|
||||
{detail}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Request Flow -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">How a Pod Gets Scheduled</h2>
|
||||
<p class="text-gray-400 mb-6">Follow the journey from kubectl apply to running pod</p>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<div class="relative">
|
||||
<div class="absolute left-6 top-0 bottom-0 w-0.5 bg-blue-500/30"></div>
|
||||
<div class="space-y-6">
|
||||
{#each requestFlow as step}
|
||||
<div class="relative pl-16">
|
||||
<div class="absolute left-0 w-12 h-12 rounded-full bg-blue-500/20 border-2 border-blue-500 flex items-center justify-center text-blue-400 font-bold">
|
||||
{step.step}
|
||||
</div>
|
||||
<div class="bg-gray-700/50 rounded-lg p-4">
|
||||
<div class="text-white font-medium">{step.title}</div>
|
||||
<div class="text-gray-400 text-sm mt-1">{step.desc}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Pod Lifecycle -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Pod Lifecycle</h2>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<div class="flex flex-wrap items-center justify-center gap-4">
|
||||
{#each podLifecycle as phase, i}
|
||||
<div class="flex items-center">
|
||||
<div class="p-3 rounded-lg bg-{phase.color}-500/20 border border-{phase.color}-500/30 text-center min-w-[120px]">
|
||||
<div class="text-{phase.color}-400 font-semibold text-sm">{phase.phase}</div>
|
||||
<div class="text-gray-500 text-xs mt-1">{phase.desc}</div>
|
||||
</div>
|
||||
{#if i < podLifecycle.length - 1}
|
||||
<div class="mx-2 text-gray-500">-></div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Key Concepts -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Key Kubernetes Objects</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-blue-400 font-semibold mb-2">Pod</div>
|
||||
<p class="text-gray-400 text-sm">Smallest deployable unit. One or more containers that share network/storage.</p>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-green-400 font-semibold mb-2">Deployment</div>
|
||||
<p class="text-gray-400 text-sm">Manages ReplicaSets. Handles rolling updates and rollbacks.</p>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-purple-400 font-semibold mb-2">Service</div>
|
||||
<p class="text-gray-400 text-sm">Stable network endpoint for pods. ClusterIP, NodePort, LoadBalancer.</p>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-orange-400 font-semibold mb-2">ConfigMap</div>
|
||||
<p class="text-gray-400 text-sm">Non-sensitive configuration data. Injected as env vars or files.</p>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-red-400 font-semibold mb-2">Secret</div>
|
||||
<p class="text-gray-400 text-sm">Sensitive data like passwords, tokens. Base64 encoded (not encrypted!).</p>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-yellow-400 font-semibold mb-2">Ingress</div>
|
||||
<p class="text-gray-400 text-sm">HTTP routing rules. Path-based routing to Services.</p>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-cyan-400 font-semibold mb-2">StatefulSet</div>
|
||||
<p class="text-gray-400 text-sm">For stateful apps. Stable network identity and persistent storage.</p>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-pink-400 font-semibold mb-2">DaemonSet</div>
|
||||
<p class="text-gray-400 text-sm">Run pod on every node. Used for logging, monitoring agents.</p>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-indigo-400 font-semibold mb-2">HPA</div>
|
||||
<p class="text-gray-400 text-sm">Horizontal Pod Autoscaler. Scale pods based on CPU/memory/custom metrics.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Networking -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Kubernetes Networking Model</h2>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<div class="grid md:grid-cols-3 gap-6">
|
||||
<div class="p-4 bg-blue-500/10 rounded-lg border border-blue-500/30">
|
||||
<h4 class="text-blue-400 font-semibold mb-2">Pod-to-Pod</h4>
|
||||
<p class="text-gray-400 text-sm mb-2">Every pod gets a unique IP. Pods can communicate directly without NAT.</p>
|
||||
<div class="text-xs text-gray-500">Implemented by CNI plugin (Calico, Cilium, etc.)</div>
|
||||
</div>
|
||||
<div class="p-4 bg-green-500/10 rounded-lg border border-green-500/30">
|
||||
<h4 class="text-green-400 font-semibold mb-2">Pod-to-Service</h4>
|
||||
<p class="text-gray-400 text-sm mb-2">Services provide stable virtual IPs (ClusterIP). kube-proxy routes to pods.</p>
|
||||
<div class="text-xs text-gray-500">Uses iptables or IPVS rules</div>
|
||||
</div>
|
||||
<div class="p-4 bg-purple-500/10 rounded-lg border border-purple-500/30">
|
||||
<h4 class="text-purple-400 font-semibold mb-2">External-to-Service</h4>
|
||||
<p class="text-gray-400 text-sm mb-2">NodePort, LoadBalancer, or Ingress expose services externally.</p>
|
||||
<div class="text-xs text-gray-500">Ingress for HTTP, LoadBalancer for TCP/UDP</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Common Patterns -->
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Production Best Practices</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<h4 class="text-green-400 font-medium mb-3">DO</h4>
|
||||
<ul class="space-y-2">
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-green-400">+</span>
|
||||
Set resource requests and limits on all pods
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-green-400">+</span>
|
||||
Use liveness and readiness probes
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-green-400">+</span>
|
||||
Run multiple replicas with anti-affinity
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-green-400">+</span>
|
||||
Use namespaces for isolation
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-green-400">+</span>
|
||||
Backup etcd regularly
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<h4 class="text-red-400 font-medium mb-3">DON'T</h4>
|
||||
<ul class="space-y-2">
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-red-400">-</span>
|
||||
Run workloads on control plane nodes
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-red-400">-</span>
|
||||
Use :latest image tags in production
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-red-400">-</span>
|
||||
Store secrets in ConfigMaps
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-red-400">-</span>
|
||||
Skip pod disruption budgets
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-red-400">-</span>
|
||||
Ignore resource quotas in multi-tenant clusters
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
390
src/routes/compute/serverless/+page.svelte
Normal file
390
src/routes/compute/serverless/+page.svelte
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
<script lang="ts">
|
||||
import GuidedWalkthrough from '$lib/components/diagrams/GuidedWalkthrough.svelte';
|
||||
|
||||
let showColdStart = true;
|
||||
|
||||
const coldStartPhases = [
|
||||
{ name: 'Container Provisioning', duration: '~100ms', desc: 'Allocate compute resources based on memory config' },
|
||||
{ name: 'Runtime Init', duration: '~50-200ms', desc: 'Load language runtime (Node.js, Python, Java)' },
|
||||
{ name: 'Code Loading', duration: 'Variable', desc: 'Download and unpack function code' },
|
||||
{ name: 'Dependency Init', duration: 'Variable', desc: 'Load libraries and initialize connections' }
|
||||
];
|
||||
|
||||
const optimizations = [
|
||||
{
|
||||
category: 'Code Optimization',
|
||||
items: [
|
||||
{ name: 'Minimize package size', desc: 'Remove unused dependencies, use tree shaking', impact: 'High' },
|
||||
{ name: 'Use lightweight libraries', desc: 'Choose smaller alternatives (e.g., date-fns over moment)', impact: 'Medium' },
|
||||
{ name: 'Lazy load dependencies', desc: 'Only import modules when needed', impact: 'Medium' },
|
||||
{ name: 'Use compiled languages', desc: 'Rust, Go have faster cold starts than Java', impact: 'High' }
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'AWS Features',
|
||||
items: [
|
||||
{ name: 'Provisioned Concurrency', desc: 'Pre-warm instances that are always ready', impact: 'Eliminates' },
|
||||
{ name: 'SnapStart (Java/Python/.NET)', desc: 'Snapshot initialized state for faster restore', impact: 'High' },
|
||||
{ name: 'Increase memory', desc: 'More memory = more CPU = faster init', impact: 'Medium' },
|
||||
{ name: 'Use ARM (Graviton)', desc: 'Better price/performance ratio', impact: 'Low' }
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'Architecture',
|
||||
items: [
|
||||
{ name: 'Keep functions warm', desc: 'Schedule periodic invocations', impact: 'Medium' },
|
||||
{ name: 'Avoid VPC when possible', desc: 'VPC adds ENI setup time', impact: 'Medium' },
|
||||
{ name: 'Connection pooling', desc: 'Reuse DB connections across invocations', impact: 'Medium' },
|
||||
{ name: 'Smaller functions', desc: 'Split large functions into focused units', impact: 'Medium' }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const runtimeComparison = [
|
||||
{ runtime: 'Python', coldStart: '~200-400ms', warmStart: '~1-5ms', notes: 'Fast init, good for most use cases' },
|
||||
{ runtime: 'Node.js', coldStart: '~200-400ms', warmStart: '~1-5ms', notes: 'Fast init, large ecosystem' },
|
||||
{ runtime: 'Go', coldStart: '~100-200ms', warmStart: '<1ms', notes: 'Compiled, very fast' },
|
||||
{ runtime: 'Rust', coldStart: '~100-200ms', warmStart: '<1ms', notes: 'Compiled, fastest cold starts' },
|
||||
{ runtime: 'Java', coldStart: '~1-3s', warmStart: '~5-20ms', notes: 'JVM warmup, use SnapStart' },
|
||||
{ runtime: '.NET', coldStart: '~500ms-1s', warmStart: '~5-10ms', notes: 'CLR init, improving with AOT' }
|
||||
];
|
||||
|
||||
const coldStartWalkthrough = {
|
||||
title: 'Lambda Cold Start Flow',
|
||||
steps: [
|
||||
{
|
||||
title: 'Invocation Request',
|
||||
content: 'API Gateway triggers Lambda function',
|
||||
details: 'A request arrives but no warm execution environment is available.',
|
||||
tips: ['Cold starts affect ~1% of requests', 'More common during traffic spikes']
|
||||
},
|
||||
{
|
||||
title: 'Container Provisioning',
|
||||
content: 'Lambda allocates compute resources',
|
||||
details: 'AWS provisions a micro-VM with the configured memory. More memory = more CPU.',
|
||||
tips: ['Memory range: 128MB - 10GB', 'CPU scales proportionally with memory']
|
||||
},
|
||||
{
|
||||
title: 'Runtime Initialization',
|
||||
content: 'Load Python/Node.js/Java runtime',
|
||||
details: 'The language runtime is loaded and initialized in the container.',
|
||||
tips: ['Python/Node.js: ~100-200ms', 'Java: ~500ms-1s (JVM startup)']
|
||||
},
|
||||
{
|
||||
title: 'Function Code Loading',
|
||||
content: 'Download and extract deployment package',
|
||||
details: 'Your code is downloaded from S3 and extracted. Larger packages take longer.',
|
||||
tips: ['Keep deployment package small', 'Use Lambda Layers for shared code']
|
||||
},
|
||||
{
|
||||
title: 'Static Initialization',
|
||||
content: 'Run code outside handler (global scope)',
|
||||
details: 'Code at module level runs once per container. Initialize DB connections here.',
|
||||
tips: ['Initialize SDK clients outside handler', 'This code runs once per container lifecycle']
|
||||
},
|
||||
{
|
||||
title: 'Handler Execution',
|
||||
content: 'Your function code runs',
|
||||
details: 'Finally, your handler function executes. Container is now "warm".',
|
||||
tips: ['Subsequent invocations skip steps 1-5', 'Warm container reused for ~15 minutes']
|
||||
}
|
||||
]
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Serverless - Cold & Warm Starts</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<h1 class="text-3xl font-bold text-white mb-2">Serverless Computing</h1>
|
||||
<p class="text-gray-400 mb-8">Understanding cold starts, warm starts, and optimization strategies</p>
|
||||
|
||||
<!-- Cold vs Warm Visual -->
|
||||
<section class="mb-12">
|
||||
<div class="flex gap-4 mb-6">
|
||||
<button
|
||||
class="px-4 py-2 rounded-lg font-medium transition-all
|
||||
{showColdStart ? 'bg-blue-500 text-white' : 'bg-gray-700 text-gray-300 hover:bg-gray-600'}"
|
||||
on:click={() => showColdStart = true}
|
||||
>
|
||||
Cold Start
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-2 rounded-lg font-medium transition-all
|
||||
{!showColdStart ? 'bg-green-500 text-white' : 'bg-gray-700 text-gray-300 hover:bg-gray-600'}"
|
||||
on:click={() => showColdStart = false}
|
||||
>
|
||||
Warm Start
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
{#if showColdStart}
|
||||
<div class="mb-4">
|
||||
<h3 class="text-xl font-semibold text-blue-400 mb-2">Cold Start</h3>
|
||||
<p class="text-gray-400">No existing execution environment - must provision new container</p>
|
||||
</div>
|
||||
|
||||
<!-- Cold Start Timeline -->
|
||||
<div class="relative">
|
||||
<div class="absolute left-0 top-0 bottom-0 w-1 bg-blue-500/30 rounded"></div>
|
||||
<div class="space-y-4 pl-8">
|
||||
{#each coldStartPhases as phase, i}
|
||||
<div class="relative">
|
||||
<div class="absolute -left-8 w-4 h-4 rounded-full bg-blue-500 border-4 border-gray-800"></div>
|
||||
<div class="bg-gray-700/50 rounded-lg p-4">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-white font-medium">{phase.name}</span>
|
||||
<span class="text-blue-400 text-sm font-mono">{phase.duration}</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm">{phase.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="relative">
|
||||
<div class="absolute -left-8 w-4 h-4 rounded-full bg-green-500 border-4 border-gray-800"></div>
|
||||
<div class="bg-green-500/20 rounded-lg p-4 border border-green-500/30">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-white font-medium">Handler Execution</span>
|
||||
<span class="text-green-400 text-sm font-mono">Your code</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm">Finally, your function handler runs</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 p-4 bg-blue-500/10 rounded-lg border border-blue-500/30">
|
||||
<div class="text-blue-300 font-medium">Total Cold Start: 200ms - 3s+</div>
|
||||
<div class="text-gray-400 text-sm mt-1">Depends on runtime, package size, and dependencies</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="mb-4">
|
||||
<h3 class="text-xl font-semibold text-green-400 mb-2">Warm Start</h3>
|
||||
<p class="text-gray-400">Reuses existing execution environment - skips initialization</p>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<div class="absolute left-0 top-0 bottom-0 w-1 bg-gray-600 rounded"></div>
|
||||
<div class="space-y-4 pl-8">
|
||||
{#each coldStartPhases as phase}
|
||||
<div class="relative opacity-40">
|
||||
<div class="absolute -left-8 w-4 h-4 rounded-full bg-gray-600 border-4 border-gray-800"></div>
|
||||
<div class="bg-gray-700/30 rounded-lg p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-500 font-medium line-through">{phase.name}</span>
|
||||
<span class="text-gray-600 text-sm">SKIPPED</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="relative">
|
||||
<div class="absolute -left-8 w-4 h-4 rounded-full bg-green-500 border-4 border-gray-800"></div>
|
||||
<div class="bg-green-500/20 rounded-lg p-4 border border-green-500/30">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-white font-medium">Handler Execution</span>
|
||||
<span class="text-green-400 text-sm font-mono">~1-5ms</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm">Jump straight to your code!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 p-4 bg-green-500/10 rounded-lg border border-green-500/30">
|
||||
<div class="text-green-300 font-medium">Total Warm Start: 1-5ms</div>
|
||||
<div class="text-gray-400 text-sm mt-1">Container reused for ~5-15 minutes of inactivity</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Cold Start Walkthrough -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Cold Start Deep Dive</h2>
|
||||
<GuidedWalkthrough {...coldStartWalkthrough} />
|
||||
</section>
|
||||
|
||||
<!-- Runtime Comparison -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Runtime Comparison</h2>
|
||||
<p class="text-gray-400 mb-6">Cold start times vary significantly by language runtime</p>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl border border-gray-700 overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-700">
|
||||
<th class="text-left p-4 text-gray-400 font-medium">Runtime</th>
|
||||
<th class="text-left p-4 text-blue-400 font-medium">Cold Start</th>
|
||||
<th class="text-left p-4 text-green-400 font-medium">Warm Start</th>
|
||||
<th class="text-left p-4 text-gray-400 font-medium">Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each runtimeComparison as rt, i}
|
||||
<tr class="border-b border-gray-700/50 {i % 2 === 0 ? 'bg-gray-800/30' : ''}">
|
||||
<td class="p-4 text-white font-medium">{rt.runtime}</td>
|
||||
<td class="p-4 text-blue-300 font-mono">{rt.coldStart}</td>
|
||||
<td class="p-4 text-green-300 font-mono">{rt.warmStart}</td>
|
||||
<td class="p-4 text-gray-400 text-sm">{rt.notes}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Optimization Strategies -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Optimization Strategies</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
{#each optimizations as category}
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<h3 class="text-lg font-semibold text-white mb-4">{category.category}</h3>
|
||||
<div class="grid md:grid-cols-2 gap-4">
|
||||
{#each category.items as item}
|
||||
<div class="p-4 bg-gray-700/30 rounded-lg">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-white font-medium">{item.name}</span>
|
||||
<span class="px-2 py-0.5 rounded text-xs font-medium
|
||||
{item.impact === 'Eliminates' ? 'bg-green-500/20 text-green-400' : ''}
|
||||
{item.impact === 'High' ? 'bg-blue-500/20 text-blue-400' : ''}
|
||||
{item.impact === 'Medium' ? 'bg-yellow-500/20 text-yellow-400' : ''}
|
||||
{item.impact === 'Low' ? 'bg-gray-500/20 text-gray-400' : ''}">
|
||||
{item.impact} impact
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm">{item.desc}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Provisioned Concurrency vs SnapStart -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Provisioned Concurrency vs SnapStart</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-purple-500/30">
|
||||
<h3 class="text-lg font-semibold text-purple-400 mb-4">Provisioned Concurrency</h3>
|
||||
<p class="text-gray-400 text-sm mb-4">
|
||||
Pre-initialize a specified number of execution environments that are always ready.
|
||||
</p>
|
||||
<div class="space-y-2 mb-4">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-green-400">+</span>
|
||||
<span class="text-gray-300">Eliminates cold starts completely</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-green-400">+</span>
|
||||
<span class="text-gray-300">Works with any runtime</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-red-400">-</span>
|
||||
<span class="text-gray-300">Costs money even when idle</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-red-400">-</span>
|
||||
<span class="text-gray-300">Must estimate concurrency needs</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-3 bg-purple-500/10 rounded-lg">
|
||||
<div class="text-purple-300 text-sm">Best for: Consistent, predictable traffic patterns</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-orange-500/30">
|
||||
<h3 class="text-lg font-semibold text-orange-400 mb-4">SnapStart</h3>
|
||||
<p class="text-gray-400 text-sm mb-4">
|
||||
Snapshot initialized state and restore from cache instead of re-initializing.
|
||||
</p>
|
||||
<div class="space-y-2 mb-4">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-green-400">+</span>
|
||||
<span class="text-gray-300">Up to 10x faster cold starts</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-green-400">+</span>
|
||||
<span class="text-gray-300">No additional cost</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-red-400">-</span>
|
||||
<span class="text-gray-300">Only Java, Python, .NET</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-red-400">-</span>
|
||||
<span class="text-gray-300">May need code changes for uniqueness</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-3 bg-orange-500/10 rounded-lg">
|
||||
<div class="text-orange-300 text-sm">Best for: Java/.NET with variable traffic</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Best Practices -->
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Best Practices</h2>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h4 class="text-green-400 font-medium mb-3">DO</h4>
|
||||
<ul class="space-y-2">
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-green-400 mt-0.5">+</span>
|
||||
Initialize SDK clients outside the handler (global scope)
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-green-400 mt-0.5">+</span>
|
||||
Reuse database connections across invocations
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-green-400 mt-0.5">+</span>
|
||||
Use Lambda Layers for shared dependencies
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-green-400 mt-0.5">+</span>
|
||||
Monitor INIT duration in CloudWatch
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-green-400 mt-0.5">+</span>
|
||||
Use ARM architecture for cost savings
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-red-400 font-medium mb-3">DON'T</h4>
|
||||
<ul class="space-y-2">
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-red-400 mt-0.5">-</span>
|
||||
Create new connections on every invocation
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-red-400 mt-0.5">-</span>
|
||||
Bundle unnecessary dependencies
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-red-400 mt-0.5">-</span>
|
||||
Use VPC unless you need private resources
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-red-400 mt-0.5">-</span>
|
||||
Ignore cold start metrics in production
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300 text-sm">
|
||||
<span class="text-red-400 mt-0.5">-</span>
|
||||
Use Java without SnapStart for user-facing APIs
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
442
src/routes/databases/caching/+page.svelte
Normal file
442
src/routes/databases/caching/+page.svelte
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
<script lang="ts">
|
||||
import GuidedWalkthrough from '$lib/components/diagrams/GuidedWalkthrough.svelte';
|
||||
|
||||
let selectedPattern: string | null = null;
|
||||
|
||||
const patterns = [
|
||||
{
|
||||
id: 'cache-aside',
|
||||
name: 'Cache-Aside (Lazy Loading)',
|
||||
description: 'Application manages the cache - loads data on cache miss',
|
||||
color: 'blue',
|
||||
steps: [
|
||||
'Application checks cache for data',
|
||||
'If cache hit: return cached data',
|
||||
'If cache miss: query database',
|
||||
'Store result in cache',
|
||||
'Return data to caller'
|
||||
],
|
||||
pros: [
|
||||
'Only caches what is actually used',
|
||||
'Simple to implement',
|
||||
'Cache failure does not break the app',
|
||||
'Works with any database'
|
||||
],
|
||||
cons: [
|
||||
'First request always slow (cache miss)',
|
||||
'Data can become stale',
|
||||
'Application must handle cache logic'
|
||||
],
|
||||
useCase: 'Read-heavy workloads with infrequent updates'
|
||||
},
|
||||
{
|
||||
id: 'write-through',
|
||||
name: 'Write-Through',
|
||||
description: 'Data written to cache and database simultaneously',
|
||||
color: 'green',
|
||||
steps: [
|
||||
'Application writes data',
|
||||
'Write to cache first',
|
||||
'Write to database',
|
||||
'Both writes must succeed',
|
||||
'Return success to caller'
|
||||
],
|
||||
pros: [
|
||||
'Cache always has fresh data',
|
||||
'No stale data issues',
|
||||
'Simple mental model'
|
||||
],
|
||||
cons: [
|
||||
'Write latency increased (two writes)',
|
||||
'Cache may have unused data',
|
||||
'Risk of inconsistency if one write fails'
|
||||
],
|
||||
useCase: 'Data that is read immediately after writing'
|
||||
},
|
||||
{
|
||||
id: 'write-behind',
|
||||
name: 'Write-Behind (Write-Back)',
|
||||
description: 'Write to cache immediately, async write to database later',
|
||||
color: 'purple',
|
||||
steps: [
|
||||
'Application writes data',
|
||||
'Write to cache immediately',
|
||||
'Return success to caller',
|
||||
'Async: batch writes to database',
|
||||
'Confirm persistence'
|
||||
],
|
||||
pros: [
|
||||
'Fastest write performance',
|
||||
'Batches database writes',
|
||||
'Reduces database load'
|
||||
],
|
||||
cons: [
|
||||
'Risk of data loss if cache fails',
|
||||
'Complex to implement correctly',
|
||||
'Eventual consistency'
|
||||
],
|
||||
useCase: 'Write-heavy workloads where some data loss is acceptable'
|
||||
},
|
||||
{
|
||||
id: 'read-through',
|
||||
name: 'Read-Through',
|
||||
description: 'Cache automatically fetches from database on miss',
|
||||
color: 'orange',
|
||||
steps: [
|
||||
'Application requests data from cache',
|
||||
'Cache checks if data exists',
|
||||
'If miss: cache fetches from database',
|
||||
'Cache stores and returns data',
|
||||
'Subsequent reads served from cache'
|
||||
],
|
||||
pros: [
|
||||
'Simpler application code',
|
||||
'Cache handles all logic',
|
||||
'Consistent caching behavior'
|
||||
],
|
||||
cons: [
|
||||
'Cache must understand data source',
|
||||
'First request still slow',
|
||||
'Less flexible than cache-aside'
|
||||
],
|
||||
useCase: 'When you want caching logic abstracted from application'
|
||||
},
|
||||
{
|
||||
id: 'refresh-ahead',
|
||||
name: 'Refresh-Ahead (Pre-fetching)',
|
||||
description: 'Proactively refresh cache before expiration',
|
||||
color: 'pink',
|
||||
steps: [
|
||||
'Cache tracks TTL of entries',
|
||||
'Before TTL expires, trigger refresh',
|
||||
'Fetch fresh data from database',
|
||||
'Update cache in background',
|
||||
'Users always get cached data'
|
||||
],
|
||||
pros: [
|
||||
'No cache miss latency',
|
||||
'Always fresh data',
|
||||
'Predictable performance'
|
||||
],
|
||||
cons: [
|
||||
'Complex to implement',
|
||||
'May refresh unused data',
|
||||
'Higher database load'
|
||||
],
|
||||
useCase: 'Hot data that must always be fast and fresh'
|
||||
}
|
||||
];
|
||||
|
||||
const evictionStrategies = [
|
||||
{
|
||||
name: 'LRU (Least Recently Used)',
|
||||
description: 'Evict the item that has not been accessed for the longest time',
|
||||
best: 'General purpose, most common'
|
||||
},
|
||||
{
|
||||
name: 'LFU (Least Frequently Used)',
|
||||
description: 'Evict the item with the fewest accesses',
|
||||
best: 'When access frequency matters more than recency'
|
||||
},
|
||||
{
|
||||
name: 'FIFO (First In, First Out)',
|
||||
description: 'Evict the oldest item regardless of access pattern',
|
||||
best: 'Simple, predictable behavior'
|
||||
},
|
||||
{
|
||||
name: 'TTL (Time To Live)',
|
||||
description: 'Evict when time expires, regardless of space',
|
||||
best: 'Data that becomes stale over time'
|
||||
},
|
||||
{
|
||||
name: 'Random',
|
||||
description: 'Evict a random item',
|
||||
best: 'When access patterns are unpredictable'
|
||||
}
|
||||
];
|
||||
|
||||
const cacheAsideWalkthrough = {
|
||||
title: 'Cache-Aside Pattern Flow',
|
||||
steps: [
|
||||
{
|
||||
title: 'Application Requests Data',
|
||||
content: 'User requests user profile for user_id=123',
|
||||
details: 'The application receives a request that needs data from the database.',
|
||||
tips: ['Common entry point: API endpoint, service method']
|
||||
},
|
||||
{
|
||||
title: 'Check Cache First',
|
||||
content: 'GET user:123 from Redis',
|
||||
details: 'Application checks if the data exists in cache before hitting the database.',
|
||||
tips: ['Use consistent key naming: entity:id', 'Handle cache connection failures gracefully']
|
||||
},
|
||||
{
|
||||
title: 'Cache Hit - Return Immediately',
|
||||
content: 'Data found! Return cached user profile',
|
||||
details: 'If data is in cache, return it immediately. This is the fast path.',
|
||||
tips: ['Typical cache hit latency: < 1ms', 'Log cache hits for monitoring']
|
||||
},
|
||||
{
|
||||
title: 'Cache Miss - Query Database',
|
||||
content: 'SELECT * FROM users WHERE id = 123',
|
||||
details: 'If cache miss, query the primary database for the data.',
|
||||
tips: ['This is the slow path', 'Consider query optimization']
|
||||
},
|
||||
{
|
||||
title: 'Store in Cache',
|
||||
content: 'SET user:123 with TTL 3600 seconds',
|
||||
details: 'After getting data from DB, store it in cache for future requests.',
|
||||
tips: ['Always set a TTL to prevent stale data', 'Consider cache size limits']
|
||||
},
|
||||
{
|
||||
title: 'Return Data to Caller',
|
||||
content: 'Return user profile to client',
|
||||
details: 'Finally return the data. Next request will hit the cache.',
|
||||
tips: ['Monitor cache hit rate', 'Aim for > 90% hit rate']
|
||||
}
|
||||
]
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Caching Patterns</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<h1 class="text-3xl font-bold text-white mb-2">Caching Patterns</h1>
|
||||
<p class="text-gray-400 mb-8">Understanding different caching strategies and when to use each</p>
|
||||
|
||||
<!-- Overview -->
|
||||
<section class="mb-12">
|
||||
<div class="bg-gradient-to-r from-blue-500/10 to-purple-500/10 rounded-xl p-6 border border-gray-700">
|
||||
<h2 class="text-lg font-semibold text-white mb-4">Why Caching Matters</h2>
|
||||
<div class="grid md:grid-cols-4 gap-4 text-center">
|
||||
<div class="p-4 bg-gray-800/50 rounded-lg">
|
||||
<div class="text-3xl font-bold text-blue-400">10-100x</div>
|
||||
<div class="text-gray-400 text-sm">Faster than database</div>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-800/50 rounded-lg">
|
||||
<div class="text-3xl font-bold text-green-400"><1ms</div>
|
||||
<div class="text-gray-400 text-sm">Cache read latency</div>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-800/50 rounded-lg">
|
||||
<div class="text-3xl font-bold text-purple-400">90%+</div>
|
||||
<div class="text-gray-400 text-sm">Target hit rate</div>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-800/50 rounded-lg">
|
||||
<div class="text-3xl font-bold text-orange-400">$$$</div>
|
||||
<div class="text-gray-400 text-sm">Reduced DB costs</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Pattern Cards -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Caching Patterns</h2>
|
||||
|
||||
<div class="grid gap-6">
|
||||
{#each patterns as pattern}
|
||||
<div
|
||||
class="bg-gray-800/50 rounded-xl border border-gray-700 overflow-hidden cursor-pointer transition-all hover:border-gray-500"
|
||||
class:border-blue-500={selectedPattern === pattern.id && pattern.color === 'blue'}
|
||||
class:border-green-500={selectedPattern === pattern.id && pattern.color === 'green'}
|
||||
class:border-purple-500={selectedPattern === pattern.id && pattern.color === 'purple'}
|
||||
class:border-orange-500={selectedPattern === pattern.id && pattern.color === 'orange'}
|
||||
class:border-pink-500={selectedPattern === pattern.id && pattern.color === 'pink'}
|
||||
on:click={() => selectedPattern = selectedPattern === pattern.id ? null : pattern.id}
|
||||
on:keydown={(e) => e.key === 'Enter' && (selectedPattern = selectedPattern === pattern.id ? null : pattern.id)}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="p-6">
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-white">{pattern.name}</h3>
|
||||
<p class="text-gray-400 text-sm">{pattern.description}</p>
|
||||
</div>
|
||||
<span class="px-3 py-1 rounded-full text-xs font-medium
|
||||
{pattern.color === 'blue' ? 'bg-blue-500/20 text-blue-400' : ''}
|
||||
{pattern.color === 'green' ? 'bg-green-500/20 text-green-400' : ''}
|
||||
{pattern.color === 'purple' ? 'bg-purple-500/20 text-purple-400' : ''}
|
||||
{pattern.color === 'orange' ? 'bg-orange-500/20 text-orange-400' : ''}
|
||||
{pattern.color === 'pink' ? 'bg-pink-500/20 text-pink-400' : ''}">
|
||||
{pattern.useCase}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Flow Steps -->
|
||||
<div class="flex items-center gap-2 mb-4 overflow-x-auto pb-2">
|
||||
{#each pattern.steps as step, i}
|
||||
<div class="flex items-center">
|
||||
<div class="px-3 py-2 bg-gray-700/50 rounded-lg text-sm text-gray-300 whitespace-nowrap">
|
||||
{step}
|
||||
</div>
|
||||
{#if i < pattern.steps.length - 1}
|
||||
<span class="mx-2 text-gray-500">-></span>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if selectedPattern === pattern.id}
|
||||
<div class="grid md:grid-cols-2 gap-4 pt-4 border-t border-gray-700">
|
||||
<div>
|
||||
<h4 class="text-green-400 font-medium mb-2">Pros</h4>
|
||||
<ul class="space-y-1">
|
||||
{#each pattern.pros as pro}
|
||||
<li class="text-gray-300 text-sm flex items-start gap-2">
|
||||
<span class="text-green-400">+</span>
|
||||
{pro}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-red-400 font-medium mb-2">Cons</h4>
|
||||
<ul class="space-y-1">
|
||||
{#each pattern.cons as con}
|
||||
<li class="text-gray-300 text-sm flex items-start gap-2">
|
||||
<span class="text-red-400">-</span>
|
||||
{con}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Cache-Aside Walkthrough -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Cache-Aside in Detail</h2>
|
||||
<p class="text-gray-400 mb-6">The most common caching pattern - walk through each step.</p>
|
||||
<GuidedWalkthrough {...cacheAsideWalkthrough} />
|
||||
</section>
|
||||
|
||||
<!-- Eviction Strategies -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Eviction Strategies</h2>
|
||||
<p class="text-gray-400 mb-6">When cache is full, which items should be removed?</p>
|
||||
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{#each evictionStrategies as strategy}
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<h3 class="text-white font-semibold mb-2">{strategy.name}</h3>
|
||||
<p class="text-gray-400 text-sm mb-3">{strategy.description}</p>
|
||||
<div class="text-xs text-blue-400">Best for: {strategy.best}</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Common Issues -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Common Caching Problems</h2>
|
||||
|
||||
<div class="grid md:grid-cols-3 gap-6">
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-red-500/30">
|
||||
<h3 class="text-red-400 font-semibold mb-3">Cache Stampede</h3>
|
||||
<p class="text-gray-400 text-sm mb-4">
|
||||
Many requests hit an expired cache key simultaneously, all querying the database at once.
|
||||
</p>
|
||||
<div class="p-3 bg-gray-700/50 rounded-lg">
|
||||
<div class="text-green-400 text-sm font-medium mb-1">Solutions:</div>
|
||||
<ul class="text-gray-300 text-sm space-y-1">
|
||||
<li>- Lock/mutex on cache refresh</li>
|
||||
<li>- Staggered TTLs</li>
|
||||
<li>- Background refresh</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-yellow-500/30">
|
||||
<h3 class="text-yellow-400 font-semibold mb-3">Stale Data</h3>
|
||||
<p class="text-gray-400 text-sm mb-4">
|
||||
Cache contains outdated data that does not reflect current database state.
|
||||
</p>
|
||||
<div class="p-3 bg-gray-700/50 rounded-lg">
|
||||
<div class="text-green-400 text-sm font-medium mb-1">Solutions:</div>
|
||||
<ul class="text-gray-300 text-sm space-y-1">
|
||||
<li>- Appropriate TTL values</li>
|
||||
<li>- Cache invalidation on writes</li>
|
||||
<li>- Write-through pattern</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-orange-500/30">
|
||||
<h3 class="text-orange-400 font-semibold mb-3">Cache Penetration</h3>
|
||||
<p class="text-gray-400 text-sm mb-4">
|
||||
Requests for non-existent data bypass cache and always hit the database.
|
||||
</p>
|
||||
<div class="p-3 bg-gray-700/50 rounded-lg">
|
||||
<div class="text-green-400 text-sm font-medium mb-1">Solutions:</div>
|
||||
<ul class="text-gray-300 text-sm space-y-1">
|
||||
<li>- Cache negative results</li>
|
||||
<li>- Bloom filter for existence check</li>
|
||||
<li>- Input validation</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Redis vs Memcached -->
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Redis vs Memcached</h2>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl border border-gray-700 overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-700">
|
||||
<th class="text-left p-4 text-gray-400 font-medium">Feature</th>
|
||||
<th class="text-left p-4 text-red-400 font-medium">Redis</th>
|
||||
<th class="text-left p-4 text-green-400 font-medium">Memcached</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="border-b border-gray-700/50">
|
||||
<td class="p-4 text-white">Data Structures</td>
|
||||
<td class="p-4 text-gray-300">Strings, Lists, Sets, Hashes, Sorted Sets</td>
|
||||
<td class="p-4 text-gray-300">Strings only</td>
|
||||
</tr>
|
||||
<tr class="border-b border-gray-700/50 bg-gray-800/30">
|
||||
<td class="p-4 text-white">Persistence</td>
|
||||
<td class="p-4 text-gray-300">Yes (RDB, AOF)</td>
|
||||
<td class="p-4 text-gray-300">No</td>
|
||||
</tr>
|
||||
<tr class="border-b border-gray-700/50">
|
||||
<td class="p-4 text-white">Replication</td>
|
||||
<td class="p-4 text-gray-300">Built-in</td>
|
||||
<td class="p-4 text-gray-300">No</td>
|
||||
</tr>
|
||||
<tr class="border-b border-gray-700/50 bg-gray-800/30">
|
||||
<td class="p-4 text-white">Pub/Sub</td>
|
||||
<td class="p-4 text-gray-300">Yes</td>
|
||||
<td class="p-4 text-gray-300">No</td>
|
||||
</tr>
|
||||
<tr class="border-b border-gray-700/50">
|
||||
<td class="p-4 text-white">Lua Scripting</td>
|
||||
<td class="p-4 text-gray-300">Yes</td>
|
||||
<td class="p-4 text-gray-300">No</td>
|
||||
</tr>
|
||||
<tr class="border-b border-gray-700/50 bg-gray-800/30">
|
||||
<td class="p-4 text-white">Memory Efficiency</td>
|
||||
<td class="p-4 text-gray-300">Good</td>
|
||||
<td class="p-4 text-gray-300">Better (simpler)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="p-4 text-white">Best For</td>
|
||||
<td class="p-4 text-gray-300">Feature-rich caching, sessions, queues</td>
|
||||
<td class="p-4 text-gray-300">Simple, high-throughput key-value cache</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
396
src/routes/databases/replication/+page.svelte
Normal file
396
src/routes/databases/replication/+page.svelte
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
<script lang="ts">
|
||||
import ComparisonTable from '$lib/components/diagrams/ComparisonTable.svelte';
|
||||
|
||||
let selectedType: 'sync' | 'async' | null = null;
|
||||
|
||||
const replicationTypes = [
|
||||
{
|
||||
id: 'leader-follower',
|
||||
name: 'Leader-Follower (Primary-Replica)',
|
||||
description: 'One leader handles writes, followers replicate and serve reads',
|
||||
pros: [
|
||||
'Simple to understand and implement',
|
||||
'Read scalability (add more followers)',
|
||||
'Automatic failover possible'
|
||||
],
|
||||
cons: [
|
||||
'Single point of failure for writes',
|
||||
'Replication lag can cause stale reads',
|
||||
'Failover can cause data loss'
|
||||
],
|
||||
useCase: 'Most common - PostgreSQL, MySQL, MongoDB'
|
||||
},
|
||||
{
|
||||
id: 'multi-leader',
|
||||
name: 'Multi-Leader (Master-Master)',
|
||||
description: 'Multiple nodes accept writes, replicate to each other',
|
||||
pros: [
|
||||
'Write availability in multiple regions',
|
||||
'Lower write latency (local writes)',
|
||||
'No single point of failure'
|
||||
],
|
||||
cons: [
|
||||
'Conflict resolution is complex',
|
||||
'Harder to reason about consistency',
|
||||
'More network traffic'
|
||||
],
|
||||
useCase: 'Multi-datacenter deployments, CRDTs'
|
||||
},
|
||||
{
|
||||
id: 'leaderless',
|
||||
name: 'Leaderless',
|
||||
description: 'Any node can accept reads and writes (quorum-based)',
|
||||
pros: [
|
||||
'No single point of failure',
|
||||
'High availability',
|
||||
'Tunable consistency'
|
||||
],
|
||||
cons: [
|
||||
'Quorum overhead on every write',
|
||||
'Complex conflict resolution',
|
||||
'Harder to achieve strong consistency'
|
||||
],
|
||||
useCase: 'DynamoDB, Cassandra, Riak'
|
||||
}
|
||||
];
|
||||
|
||||
const comparisonOptions = [
|
||||
{
|
||||
id: 'sync',
|
||||
name: 'Synchronous Replication',
|
||||
description: 'Wait for replica confirmation before acknowledging write',
|
||||
whenToUse: [
|
||||
'Zero data loss is critical (financial systems)',
|
||||
'Strong consistency required',
|
||||
'Regulatory compliance (GDPR, SOC2)',
|
||||
'Hot standby for instant failover',
|
||||
'Replicas in same datacenter (low latency)'
|
||||
],
|
||||
whenNotToUse: [
|
||||
'Replicas are geographically distant',
|
||||
'Write latency is critical',
|
||||
'High write throughput needed',
|
||||
'Some data loss is acceptable'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'async',
|
||||
name: 'Asynchronous Replication',
|
||||
description: 'Acknowledge write immediately, replicate in background',
|
||||
whenToUse: [
|
||||
'Geographic distribution (cross-region)',
|
||||
'Write performance is priority',
|
||||
'Read scaling is the goal',
|
||||
'Some data loss on failover is acceptable',
|
||||
'Analytics/reporting replicas'
|
||||
],
|
||||
whenNotToUse: [
|
||||
'Cannot tolerate any data loss',
|
||||
'Need strong consistency guarantees',
|
||||
'Regulatory requirements for durability',
|
||||
'Reads must always be consistent'
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const lagMetrics = [
|
||||
{ name: 'Write Position', desc: 'Where leader has written (WAL/binlog position)' },
|
||||
{ name: 'Sent Position', desc: 'What has been sent to replica' },
|
||||
{ name: 'Flush Position', desc: 'What replica has written to disk' },
|
||||
{ name: 'Replay Position', desc: 'What replica has applied to database' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Database Replication</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<h1 class="text-3xl font-bold text-white mb-2">Database Replication</h1>
|
||||
<p class="text-gray-400 mb-8">Understanding replication topologies, sync vs async, and consistency trade-offs</p>
|
||||
|
||||
<!-- Why Replication -->
|
||||
<section class="mb-12">
|
||||
<div class="bg-gradient-to-r from-blue-500/10 to-purple-500/10 rounded-xl p-6 border border-gray-700">
|
||||
<h2 class="text-lg font-semibold text-white mb-4">Why Replicate?</h2>
|
||||
<div class="grid md:grid-cols-4 gap-4">
|
||||
<div class="p-4 bg-gray-800/50 rounded-lg text-center">
|
||||
<div class="text-2xl mb-2">HA</div>
|
||||
<div class="text-white font-medium">High Availability</div>
|
||||
<div class="text-gray-400 text-sm mt-1">Survive node failures</div>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-800/50 rounded-lg text-center">
|
||||
<div class="text-2xl mb-2">R</div>
|
||||
<div class="text-white font-medium">Read Scaling</div>
|
||||
<div class="text-gray-400 text-sm mt-1">Distribute read load</div>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-800/50 rounded-lg text-center">
|
||||
<div class="text-2xl mb-2">GEO</div>
|
||||
<div class="text-white font-medium">Geographic</div>
|
||||
<div class="text-gray-400 text-sm mt-1">Data closer to users</div>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-800/50 rounded-lg text-center">
|
||||
<div class="text-2xl mb-2">DR</div>
|
||||
<div class="text-white font-medium">Disaster Recovery</div>
|
||||
<div class="text-gray-400 text-sm mt-1">Backup in another region</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Sync vs Async Toggle -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Synchronous vs Asynchronous</h2>
|
||||
|
||||
<div class="flex gap-4 mb-6">
|
||||
<button
|
||||
class="px-4 py-2 rounded-lg font-medium transition-all
|
||||
{selectedType === 'sync' ? 'bg-blue-500 text-white' : 'bg-gray-700 text-gray-300 hover:bg-gray-600'}"
|
||||
on:click={() => selectedType = selectedType === 'sync' ? null : 'sync'}
|
||||
>
|
||||
Synchronous
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-2 rounded-lg font-medium transition-all
|
||||
{selectedType === 'async' ? 'bg-green-500 text-white' : 'bg-gray-700 text-gray-300 hover:bg-gray-600'}"
|
||||
on:click={() => selectedType = selectedType === 'async' ? null : 'async'}
|
||||
>
|
||||
Asynchronous
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<!-- Sync -->
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border {selectedType === 'sync' ? 'border-blue-500' : 'border-gray-700'}">
|
||||
<h3 class="text-lg font-semibold text-blue-400 mb-4">Synchronous Replication</h3>
|
||||
|
||||
<div class="space-y-3 mb-6">
|
||||
<div class="flex items-center gap-3 p-3 bg-gray-700/50 rounded-lg">
|
||||
<div class="w-10 h-10 rounded-full bg-blue-500/20 flex items-center justify-center text-blue-400 font-bold">1</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-white text-sm">Client writes to leader</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 p-3 bg-gray-700/50 rounded-lg">
|
||||
<div class="w-10 h-10 rounded-full bg-blue-500/20 flex items-center justify-center text-blue-400 font-bold">2</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-white text-sm">Leader sends to replica</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 p-3 bg-blue-500/20 rounded-lg border border-blue-500/30">
|
||||
<div class="w-10 h-10 rounded-full bg-blue-500/30 flex items-center justify-center text-blue-400 font-bold">3</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-white text-sm font-medium">WAIT for replica confirmation</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 p-3 bg-gray-700/50 rounded-lg">
|
||||
<div class="w-10 h-10 rounded-full bg-blue-500/20 flex items-center justify-center text-blue-400 font-bold">4</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-white text-sm">Acknowledge to client</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-3 bg-blue-500/10 rounded-lg border border-blue-500/30">
|
||||
<div class="text-blue-300 text-sm">Guarantee: Data exists on N nodes before success</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Async -->
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border {selectedType === 'async' ? 'border-green-500' : 'border-gray-700'}">
|
||||
<h3 class="text-lg font-semibold text-green-400 mb-4">Asynchronous Replication</h3>
|
||||
|
||||
<div class="space-y-3 mb-6">
|
||||
<div class="flex items-center gap-3 p-3 bg-gray-700/50 rounded-lg">
|
||||
<div class="w-10 h-10 rounded-full bg-green-500/20 flex items-center justify-center text-green-400 font-bold">1</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-white text-sm">Client writes to leader</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 p-3 bg-green-500/20 rounded-lg border border-green-500/30">
|
||||
<div class="w-10 h-10 rounded-full bg-green-500/30 flex items-center justify-center text-green-400 font-bold">2</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-white text-sm font-medium">Acknowledge immediately</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 p-3 bg-gray-700/50 rounded-lg opacity-60">
|
||||
<div class="w-10 h-10 rounded-full bg-green-500/20 flex items-center justify-center text-green-400 font-bold">3</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-white text-sm">Replicate in background</div>
|
||||
<div class="text-gray-500 text-xs">May lag behind leader</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-3 bg-green-500/10 rounded-lg border border-green-500/30">
|
||||
<div class="text-green-300 text-sm">Trade-off: Faster writes, but possible data loss on failover</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- When to Use Each -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">When to Use Each</h2>
|
||||
<ComparisonTable options={comparisonOptions} />
|
||||
</section>
|
||||
|
||||
<!-- Replication Topologies -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Replication Topologies</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
{#each replicationTypes as type}
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-white">{type.name}</h3>
|
||||
<p class="text-gray-400 text-sm">{type.description}</p>
|
||||
</div>
|
||||
<span class="px-3 py-1 rounded-full text-xs font-medium bg-purple-500/20 text-purple-400">
|
||||
{type.useCase}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h4 class="text-green-400 font-medium mb-2 text-sm">Pros</h4>
|
||||
<ul class="space-y-1">
|
||||
{#each type.pros as pro}
|
||||
<li class="text-gray-300 text-sm flex items-start gap-2">
|
||||
<span class="text-green-400">+</span>
|
||||
{pro}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="text-red-400 font-medium mb-2 text-sm">Cons</h4>
|
||||
<ul class="space-y-1">
|
||||
{#each type.cons as con}
|
||||
<li class="text-gray-300 text-sm flex items-start gap-2">
|
||||
<span class="text-red-400">-</span>
|
||||
{con}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Replication Lag -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Understanding Replication Lag</h2>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<p class="text-gray-400 mb-6">
|
||||
Replication lag is the delay between when data is written to the leader and when it appears on replicas.
|
||||
Multiple metrics help pinpoint where the lag is occurring.
|
||||
</p>
|
||||
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
{#each lagMetrics as metric, i}
|
||||
<div class="p-4 bg-gray-700/50 rounded-lg">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<div class="w-6 h-6 rounded-full bg-blue-500/20 flex items-center justify-center text-blue-400 text-xs font-bold">{i + 1}</div>
|
||||
<span class="text-white font-medium text-sm">{metric.name}</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-xs">{metric.desc}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-700/50 rounded-lg p-4">
|
||||
<h4 class="text-white font-medium mb-2">PostgreSQL: Check Replication Lag</h4>
|
||||
<pre class="text-sm text-gray-300 overflow-x-auto"><code>SELECT client_addr, state,
|
||||
pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_bytes,
|
||||
replay_lag
|
||||
FROM pg_stat_replication;</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Failover -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Failover Considerations</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-yellow-500/30">
|
||||
<h3 class="text-yellow-400 font-semibold mb-4">Automatic Failover</h3>
|
||||
<p class="text-gray-400 text-sm mb-4">
|
||||
System automatically promotes replica when leader fails. Fast recovery but risks split-brain.
|
||||
</p>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li class="flex items-start gap-2 text-gray-300">
|
||||
<span class="text-yellow-400">!</span>
|
||||
Requires proper fencing to prevent split-brain
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300">
|
||||
<span class="text-yellow-400">!</span>
|
||||
May lose uncommitted transactions (async)
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300">
|
||||
<span class="text-yellow-400">!</span>
|
||||
Applications may need reconnection logic
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-blue-500/30">
|
||||
<h3 class="text-blue-400 font-semibold mb-4">Manual Failover</h3>
|
||||
<p class="text-gray-400 text-sm mb-4">
|
||||
Operator manually promotes replica after verifying state. Slower but safer.
|
||||
</p>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li class="flex items-start gap-2 text-gray-300">
|
||||
<span class="text-blue-400">-</span>
|
||||
Verify replica is fully caught up
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300">
|
||||
<span class="text-blue-400">-</span>
|
||||
Stop writes to old leader first
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-gray-300">
|
||||
<span class="text-blue-400">-</span>
|
||||
Update DNS/connection strings
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Common Patterns -->
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Common Patterns</h2>
|
||||
|
||||
<div class="grid md:grid-cols-3 gap-4">
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-blue-400 font-semibold mb-2">Read Replicas</div>
|
||||
<p class="text-gray-400 text-sm">Route reads to replicas, writes to leader. Scale read capacity horizontally.</p>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-green-400 font-semibold mb-2">Hot Standby</div>
|
||||
<p class="text-gray-400 text-sm">Sync replica ready for instant failover. Zero data loss guarantee.</p>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-purple-400 font-semibold mb-2">Delayed Replica</div>
|
||||
<p class="text-gray-400 text-sm">Intentionally lag behind. Protection against accidental deletes.</p>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-orange-400 font-semibold mb-2">Cascading Replication</div>
|
||||
<p class="text-gray-400 text-sm">Replicas replicate to other replicas. Reduce load on leader.</p>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-red-400 font-semibold mb-2">Cross-Region Replica</div>
|
||||
<p class="text-gray-400 text-sm">Async replica in another region. Disaster recovery and local reads.</p>
|
||||
</div>
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700">
|
||||
<div class="text-cyan-400 font-semibold mb-2">Logical Replication</div>
|
||||
<p class="text-gray-400 text-sm">Replicate specific tables/databases. Schema changes, version upgrades.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
350
src/routes/decisions/which-database/+page.svelte
Normal file
350
src/routes/decisions/which-database/+page.svelte
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
<script lang="ts">
|
||||
import * as Icons from 'lucide-svelte';
|
||||
import DecisionTree from '$lib/components/diagrams/DecisionTree.svelte';
|
||||
|
||||
const nodes = [
|
||||
// Start
|
||||
{
|
||||
id: 'start',
|
||||
type: 'question' as const,
|
||||
text: 'Do you need complex queries with JOINs across multiple tables?',
|
||||
description: 'Think about your main query patterns. Will you frequently combine data from different sources?',
|
||||
yes: 'acid-needed',
|
||||
no: 'data-structure'
|
||||
},
|
||||
// SQL Path
|
||||
{
|
||||
id: 'acid-needed',
|
||||
type: 'question' as const,
|
||||
text: 'Do you need ACID transactions?',
|
||||
description: 'Atomicity, Consistency, Isolation, Durability - critical for financial data, inventory, bookings',
|
||||
yes: 'sql-answer',
|
||||
no: 'read-heavy'
|
||||
},
|
||||
{
|
||||
id: 'read-heavy',
|
||||
type: 'question' as const,
|
||||
text: 'Is your workload read-heavy with complex analytics?',
|
||||
description: 'Reporting, dashboards, aggregations across large datasets',
|
||||
yes: 'sql-analytics',
|
||||
no: 'sql-general'
|
||||
},
|
||||
{
|
||||
id: 'sql-answer',
|
||||
type: 'answer' as const,
|
||||
text: 'PostgreSQL',
|
||||
icon: 'Database',
|
||||
reasoning: 'You need JOINs AND ACID transactions. PostgreSQL is the gold standard - it handles complex queries, has excellent ACID support, JSON support for flexibility, and scales well with read replicas.',
|
||||
alternatives: [
|
||||
'MySQL - If you need simpler replication',
|
||||
'CockroachDB - If you need global distribution',
|
||||
'Cloud SQL / RDS - For managed hosting'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'sql-analytics',
|
||||
type: 'answer' as const,
|
||||
text: 'PostgreSQL + Analytics DB',
|
||||
icon: 'BarChart',
|
||||
reasoning: 'For heavy analytics, consider PostgreSQL for OLTP (transactions) plus a specialized analytics database like ClickHouse or BigQuery for OLAP (analytics). Replicate data between them.',
|
||||
alternatives: [
|
||||
'TimescaleDB - If time-series analytics',
|
||||
'Redshift/BigQuery - For massive data warehousing',
|
||||
'DuckDB - For embedded analytics'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'sql-general',
|
||||
type: 'answer' as const,
|
||||
text: 'PostgreSQL (or MySQL)',
|
||||
icon: 'Table',
|
||||
reasoning: 'You need JOINs but not strict ACID. PostgreSQL still works great here, or MySQL if your team knows it better. Both are excellent general-purpose relational databases.',
|
||||
alternatives: [
|
||||
'MySQL - Wide adoption, simpler',
|
||||
'MariaDB - MySQL fork with more features',
|
||||
'SQLite - For embedded/local use'
|
||||
]
|
||||
},
|
||||
// NoSQL Path
|
||||
{
|
||||
id: 'data-structure',
|
||||
type: 'question' as const,
|
||||
text: 'Is your data hierarchical or document-like (JSON)?',
|
||||
description: 'Think: user profiles, product catalogs, content with varying attributes',
|
||||
yes: 'schema-flexibility',
|
||||
no: 'access-pattern'
|
||||
},
|
||||
{
|
||||
id: 'schema-flexibility',
|
||||
type: 'question' as const,
|
||||
text: 'Does your schema change frequently?',
|
||||
description: 'Adding new fields often? Different documents have different structures?',
|
||||
yes: 'document-answer',
|
||||
no: 'document-consistency'
|
||||
},
|
||||
{
|
||||
id: 'document-consistency',
|
||||
type: 'question' as const,
|
||||
text: 'Do you need strong consistency?',
|
||||
description: 'Every read must return the latest write, no stale data acceptable',
|
||||
yes: 'document-strong',
|
||||
no: 'document-answer'
|
||||
},
|
||||
{
|
||||
id: 'document-answer',
|
||||
type: 'answer' as const,
|
||||
text: 'MongoDB',
|
||||
icon: 'FileJson',
|
||||
reasoning: 'Flexible document structure, great for evolving schemas. Handles JSON-like data naturally. Good balance of flexibility and features.',
|
||||
alternatives: [
|
||||
'Firestore - If using Firebase/GCP',
|
||||
'CouchDB - If you need offline sync',
|
||||
'DocumentDB - AWS managed MongoDB-compatible'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'document-strong',
|
||||
type: 'answer' as const,
|
||||
text: 'MongoDB or Firestore',
|
||||
icon: 'FileJson',
|
||||
reasoning: 'Both support configurable consistency levels. MongoDB with "majority" write concern and read concern gives you strong consistency. Firestore provides strong consistency by default.',
|
||||
alternatives: [
|
||||
'FaunaDB - Strong consistency by design',
|
||||
'PostgreSQL with JSONB - Best of both worlds'
|
||||
]
|
||||
},
|
||||
// Key-Value/Cache Path
|
||||
{
|
||||
id: 'access-pattern',
|
||||
type: 'question' as const,
|
||||
text: 'Is your primary access pattern key-based lookups?',
|
||||
description: 'GET by ID, SET by ID - simple operations at high speed',
|
||||
yes: 'persistence-needed',
|
||||
no: 'time-series-check'
|
||||
},
|
||||
{
|
||||
id: 'persistence-needed',
|
||||
type: 'question' as const,
|
||||
text: 'Do you need data persistence (survive restarts)?',
|
||||
description: 'Or is this for caching/sessions that can be rebuilt?',
|
||||
yes: 'distributed-keyvalue',
|
||||
no: 'cache-answer'
|
||||
},
|
||||
{
|
||||
id: 'cache-answer',
|
||||
type: 'answer' as const,
|
||||
text: 'Redis',
|
||||
icon: 'Zap',
|
||||
reasoning: 'Lightning-fast in-memory storage. Perfect for caching, sessions, leaderboards, rate limiting. Supports TTL for automatic expiration. Can persist to disk if needed.',
|
||||
alternatives: [
|
||||
'Memcached - Simpler, multi-threaded',
|
||||
'KeyDB - Redis fork, multi-threaded',
|
||||
'Dragonfly - Modern Redis alternative'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'distributed-keyvalue',
|
||||
type: 'question' as const,
|
||||
text: 'Do you need global distribution and auto-scaling?',
|
||||
description: 'Multiple regions, managed infrastructure, serverless scaling',
|
||||
yes: 'dynamo-answer',
|
||||
no: 'redis-persistent'
|
||||
},
|
||||
{
|
||||
id: 'dynamo-answer',
|
||||
type: 'answer' as const,
|
||||
text: 'DynamoDB',
|
||||
icon: 'Cloud',
|
||||
reasoning: 'Fully managed, globally distributed, auto-scaling key-value store. Pay per request. Excellent for serverless architectures. Single-digit millisecond latency at any scale.',
|
||||
alternatives: [
|
||||
'Cosmos DB - If on Azure',
|
||||
'Cloud Spanner - If need SQL with global distribution',
|
||||
'ScyllaDB - Self-hosted Cassandra-compatible'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'redis-persistent',
|
||||
type: 'answer' as const,
|
||||
text: 'Redis with Persistence',
|
||||
icon: 'Database',
|
||||
reasoning: 'Redis with AOF (Append-Only File) or RDB snapshots gives you persistence. Great for primary key-value storage with backup. Consider Redis Cluster for horizontal scaling.',
|
||||
alternatives: [
|
||||
'Valkey - Redis fork (open source)',
|
||||
'KeyDB - Multi-threaded Redis',
|
||||
'Managed: ElastiCache, MemoryDB'
|
||||
]
|
||||
},
|
||||
// Time-Series/Wide-Column Path
|
||||
{
|
||||
id: 'time-series-check',
|
||||
type: 'question' as const,
|
||||
text: 'Is your data time-series or event-based?',
|
||||
description: 'IoT sensors, metrics, logs, events with timestamps',
|
||||
yes: 'time-series-scale',
|
||||
no: 'graph-check'
|
||||
},
|
||||
{
|
||||
id: 'time-series-scale',
|
||||
type: 'question' as const,
|
||||
text: 'Do you need massive write throughput (millions/sec)?',
|
||||
description: 'IoT at scale, real-time analytics, high-frequency data',
|
||||
yes: 'widecolumn-answer',
|
||||
no: 'timeseries-answer'
|
||||
},
|
||||
{
|
||||
id: 'timeseries-answer',
|
||||
type: 'answer' as const,
|
||||
text: 'TimescaleDB or InfluxDB',
|
||||
icon: 'LineChart',
|
||||
reasoning: 'Purpose-built for time-series data. TimescaleDB extends PostgreSQL (familiar SQL). InfluxDB is pure time-series with its own query language. Both handle time-based queries efficiently.',
|
||||
alternatives: [
|
||||
'QuestDB - Faster ingestion',
|
||||
'Prometheus - For metrics specifically',
|
||||
'ClickHouse - For analytics on time-series'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'widecolumn-answer',
|
||||
type: 'answer' as const,
|
||||
text: 'Cassandra or ScyllaDB',
|
||||
icon: 'LayoutGrid',
|
||||
reasoning: 'Designed for massive write throughput across many nodes. Wide-column stores excel at time-series at scale. ScyllaDB is a faster C++ rewrite of Cassandra.',
|
||||
alternatives: [
|
||||
'Bigtable - GCP managed',
|
||||
'HBase - Hadoop ecosystem',
|
||||
'ClickHouse - For analytics focus'
|
||||
]
|
||||
},
|
||||
// Graph Path
|
||||
{
|
||||
id: 'graph-check',
|
||||
type: 'question' as const,
|
||||
text: 'Do you need to query complex relationships?',
|
||||
description: 'Social networks, recommendations, fraud detection, knowledge graphs',
|
||||
yes: 'graph-answer',
|
||||
no: 'search-check'
|
||||
},
|
||||
{
|
||||
id: 'graph-answer',
|
||||
type: 'answer' as const,
|
||||
text: 'Neo4j',
|
||||
icon: 'Share2',
|
||||
reasoning: 'Native graph database - relationships are first-class citizens. Cypher query language makes traversing connections intuitive. Perfect for "friends of friends" or "shortest path" queries.',
|
||||
alternatives: [
|
||||
'Neptune - AWS managed',
|
||||
'ArangoDB - Multi-model (graph + document)',
|
||||
'Dgraph - Distributed graph'
|
||||
]
|
||||
},
|
||||
// Search Path
|
||||
{
|
||||
id: 'search-check',
|
||||
type: 'question' as const,
|
||||
text: 'Do you need full-text search or fuzzy matching?',
|
||||
description: 'Search boxes, autocomplete, finding documents by content',
|
||||
yes: 'search-answer',
|
||||
no: 'fallback-answer'
|
||||
},
|
||||
{
|
||||
id: 'search-answer',
|
||||
type: 'answer' as const,
|
||||
text: 'Elasticsearch or Meilisearch',
|
||||
icon: 'Search',
|
||||
reasoning: 'Elasticsearch is the industry standard for full-text search, logging, and analytics. Meilisearch is simpler and faster for typical search use cases.',
|
||||
alternatives: [
|
||||
'OpenSearch - Open-source Elasticsearch',
|
||||
'Algolia - Managed search service',
|
||||
'Typesense - Simple, fast alternative'
|
||||
]
|
||||
},
|
||||
// Fallback
|
||||
{
|
||||
id: 'fallback-answer',
|
||||
type: 'answer' as const,
|
||||
text: 'Start with PostgreSQL',
|
||||
icon: 'Database',
|
||||
reasoning: 'When in doubt, PostgreSQL is a safe choice. It handles JSON (document-like), has full-text search, and scales well. You can always add specialized databases later when you identify specific needs.',
|
||||
alternatives: [
|
||||
'SQLite - For embedded/local',
|
||||
'Supabase - Managed PostgreSQL + extras',
|
||||
'PlanetScale - Managed MySQL'
|
||||
]
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Which Database? - System Design Explorer</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-4xl mx-auto space-y-8">
|
||||
<!-- Header -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 text-surface-500 text-sm mb-2">
|
||||
<a href="/decisions" class="hover:text-surface-300">Decision Trees</a>
|
||||
<Icons.ChevronRight class="w-4 h-4" />
|
||||
<span class="text-surface-300">Which Database?</span>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-surface-100">Which Database Should I Use?</h1>
|
||||
<p class="text-surface-400 mt-2">
|
||||
Answer a few questions about your use case, and we'll recommend the best database for your needs.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Decision Tree -->
|
||||
<DecisionTree
|
||||
title=""
|
||||
subtitle=""
|
||||
{nodes}
|
||||
startNode="start"
|
||||
/>
|
||||
|
||||
<!-- Quick Reference -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-semibold text-surface-100 mb-4">Quick Reference</h3>
|
||||
<div class="grid md:grid-cols-2 gap-4">
|
||||
<div class="space-y-3">
|
||||
<h4 class="text-sm font-medium text-surface-400 uppercase tracking-wide">By Use Case</h4>
|
||||
{#each [
|
||||
{ case: 'E-commerce orders', db: 'PostgreSQL', color: 'blue' },
|
||||
{ case: 'User profiles', db: 'MongoDB', color: 'green' },
|
||||
{ case: 'Session caching', db: 'Redis', color: 'red' },
|
||||
{ case: 'IoT sensor data', db: 'TimescaleDB', color: 'purple' },
|
||||
{ case: 'Social graph', db: 'Neo4j', color: 'pink' }
|
||||
] as item}
|
||||
<div class="flex items-center justify-between p-2 bg-surface-800 rounded-lg">
|
||||
<span class="text-sm text-surface-300">{item.case}</span>
|
||||
<span class="text-sm font-medium text-{item.color}-400">{item.db}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<h4 class="text-sm font-medium text-surface-400 uppercase tracking-wide">By Data Type</h4>
|
||||
{#each [
|
||||
{ type: 'Relational', db: 'PostgreSQL/MySQL', color: 'blue' },
|
||||
{ type: 'Documents', db: 'MongoDB/Firestore', color: 'green' },
|
||||
{ type: 'Key-Value', db: 'Redis/DynamoDB', color: 'yellow' },
|
||||
{ type: 'Time-Series', db: 'TimescaleDB/InfluxDB', color: 'purple' },
|
||||
{ type: 'Graph', db: 'Neo4j/Neptune', color: 'pink' }
|
||||
] as item}
|
||||
<div class="flex items-center justify-between p-2 bg-surface-800 rounded-lg">
|
||||
<span class="text-sm text-surface-300">{item.type}</span>
|
||||
<span class="text-sm font-medium text-{item.color}-400">{item.db}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="flex justify-between items-center pt-4 border-t border-surface-800">
|
||||
<a href="/decisions" class="btn-secondary flex items-center gap-2">
|
||||
<Icons.ArrowLeft class="w-4 h-4" />
|
||||
All Decision Trees
|
||||
</a>
|
||||
<a href="/compare/sql-vs-nosql" class="btn-primary flex items-center gap-2">
|
||||
Compare SQL vs NoSQL
|
||||
<Icons.GitCompare class="w-4 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
376
src/routes/fundamentals/cap-theorem/+page.svelte
Normal file
376
src/routes/fundamentals/cap-theorem/+page.svelte
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
<script lang="ts">
|
||||
import * as Icons from 'lucide-svelte';
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
|
||||
type CapChoice = 'C' | 'A' | 'P' | 'CA' | 'CP' | 'AP' | null;
|
||||
|
||||
let selectedChoice = $state<CapChoice>(null);
|
||||
let hoveredVertex = $state<'C' | 'A' | 'P' | null>(null);
|
||||
|
||||
const vertices = {
|
||||
C: {
|
||||
x: 200,
|
||||
y: 50,
|
||||
label: 'Consistency',
|
||||
short: 'C',
|
||||
description: 'Every read receives the most recent write or an error',
|
||||
detail: 'All nodes see the same data at the same time. When you write data, all subsequent reads return that data.',
|
||||
color: '#3B82F6' // blue
|
||||
},
|
||||
A: {
|
||||
x: 50,
|
||||
y: 300,
|
||||
label: 'Availability',
|
||||
short: 'A',
|
||||
description: 'Every request receives a response (success or failure)',
|
||||
detail: 'The system remains operational and responsive, even if some nodes fail. Every request gets a response.',
|
||||
color: '#22C55E' // green
|
||||
},
|
||||
P: {
|
||||
x: 350,
|
||||
y: 300,
|
||||
label: 'Partition Tolerance',
|
||||
short: 'P',
|
||||
description: 'System continues to operate despite network partitions',
|
||||
detail: 'The system continues to function even when network communication between nodes fails.',
|
||||
color: '#EAB308' // yellow
|
||||
}
|
||||
};
|
||||
|
||||
const combinations = {
|
||||
CA: {
|
||||
name: 'CA (Consistency + Availability)',
|
||||
description: 'NOT possible in distributed systems',
|
||||
explanation: 'In a distributed system, network partitions are inevitable. When a partition occurs, you must choose between C and A.',
|
||||
examples: ['Single-node databases (PostgreSQL, MySQL on one server)', 'Not achievable in distributed systems'],
|
||||
color: '#94A3B8',
|
||||
viable: false
|
||||
},
|
||||
CP: {
|
||||
name: 'CP (Consistency + Partition Tolerance)',
|
||||
description: 'Sacrifice availability for consistency',
|
||||
explanation: 'During a partition, the system refuses to respond rather than return potentially stale data. Prioritizes data accuracy.',
|
||||
examples: ['MongoDB (default)', 'HBase', 'Redis Cluster', 'Zookeeper', 'etcd'],
|
||||
useCase: 'Financial systems, inventory management, booking systems - where incorrect data causes real problems',
|
||||
color: '#8B5CF6',
|
||||
viable: true
|
||||
},
|
||||
AP: {
|
||||
name: 'AP (Availability + Partition Tolerance)',
|
||||
description: 'Sacrifice consistency for availability',
|
||||
explanation: 'During a partition, the system continues to respond but may return stale data. Prioritizes uptime.',
|
||||
examples: ['Cassandra', 'DynamoDB', 'CouchDB', 'Riak'],
|
||||
useCase: 'Social media feeds, shopping carts, DNS - where temporary inconsistency is acceptable',
|
||||
color: '#EC4899',
|
||||
viable: true
|
||||
}
|
||||
};
|
||||
|
||||
function selectCombination(choice: CapChoice) {
|
||||
selectedChoice = selectedChoice === choice ? null : choice;
|
||||
}
|
||||
|
||||
const activeVertex = $derived(hoveredVertex);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>CAP Theorem - System Design Explorer</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-5xl mx-auto space-y-8">
|
||||
<!-- Header -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 text-surface-500 text-sm mb-2">
|
||||
<a href="/fundamentals" class="hover:text-surface-300">Fundamentals</a>
|
||||
<Icons.ChevronRight class="w-4 h-4" />
|
||||
<span class="text-surface-300">CAP Theorem</span>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-surface-100">CAP Theorem</h1>
|
||||
<p class="text-surface-400 mt-2">
|
||||
The fundamental trade-off in distributed systems: you can only guarantee two of three properties.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Interactive Triangle -->
|
||||
<div class="card">
|
||||
<div class="flex flex-col lg:flex-row gap-8">
|
||||
<!-- SVG Triangle -->
|
||||
<div class="flex-1">
|
||||
<svg viewBox="0 0 400 380" class="w-full max-w-md mx-auto">
|
||||
<!-- Triangle edges -->
|
||||
<line x1={vertices.C.x} y1={vertices.C.y} x2={vertices.A.x} y2={vertices.A.y}
|
||||
stroke="#334155" stroke-width="2" />
|
||||
<line x1={vertices.A.x} y1={vertices.A.y} x2={vertices.P.x} y2={vertices.P.y}
|
||||
stroke="#334155" stroke-width="2" />
|
||||
<line x1={vertices.P.x} y1={vertices.P.y} x2={vertices.C.x} y2={vertices.C.y}
|
||||
stroke="#334155" stroke-width="2" />
|
||||
|
||||
<!-- Edge labels (combinations) -->
|
||||
<!-- CA Edge -->
|
||||
<g
|
||||
class="cursor-pointer"
|
||||
onclick={() => selectCombination('CA')}
|
||||
onmouseenter={() => {}}
|
||||
>
|
||||
<line x1={vertices.C.x} y1={vertices.C.y} x2={vertices.A.x} y2={vertices.A.y}
|
||||
stroke={selectedChoice === 'CA' ? '#94A3B8' : 'transparent'}
|
||||
stroke-width="8"
|
||||
class="transition-all duration-200" />
|
||||
<text x={(vertices.C.x + vertices.A.x) / 2 - 30} y={(vertices.C.y + vertices.A.y) / 2}
|
||||
fill={selectedChoice === 'CA' ? '#94A3B8' : '#64748B'}
|
||||
class="text-sm font-medium transition-colors duration-200">
|
||||
CA
|
||||
</text>
|
||||
<text x={(vertices.C.x + vertices.A.x) / 2 - 30} y={(vertices.C.y + vertices.A.y) / 2 + 16}
|
||||
fill="#EF4444"
|
||||
class="text-[10px]">
|
||||
(Not viable)
|
||||
</text>
|
||||
</g>
|
||||
|
||||
<!-- CP Edge -->
|
||||
<g
|
||||
class="cursor-pointer"
|
||||
onclick={() => selectCombination('CP')}
|
||||
>
|
||||
<line x1={vertices.C.x} y1={vertices.C.y} x2={vertices.P.x} y2={vertices.P.y}
|
||||
stroke={selectedChoice === 'CP' ? '#8B5CF6' : 'transparent'}
|
||||
stroke-width="8"
|
||||
class="transition-all duration-200" />
|
||||
<text x={(vertices.C.x + vertices.P.x) / 2 + 15} y={(vertices.C.y + vertices.P.y) / 2}
|
||||
fill={selectedChoice === 'CP' ? '#8B5CF6' : '#64748B'}
|
||||
class="text-sm font-medium transition-colors duration-200">
|
||||
CP
|
||||
</text>
|
||||
</g>
|
||||
|
||||
<!-- AP Edge -->
|
||||
<g
|
||||
class="cursor-pointer"
|
||||
onclick={() => selectCombination('AP')}
|
||||
>
|
||||
<line x1={vertices.A.x} y1={vertices.A.y} x2={vertices.P.x} y2={vertices.P.y}
|
||||
stroke={selectedChoice === 'AP' ? '#EC4899' : 'transparent'}
|
||||
stroke-width="8"
|
||||
class="transition-all duration-200" />
|
||||
<text x={(vertices.A.x + vertices.P.x) / 2} y={(vertices.A.y + vertices.P.y) / 2 + 30}
|
||||
fill={selectedChoice === 'AP' ? '#EC4899' : '#64748B'}
|
||||
class="text-sm font-medium transition-colors duration-200">
|
||||
AP
|
||||
</text>
|
||||
</g>
|
||||
|
||||
<!-- Vertices -->
|
||||
{#each Object.entries(vertices) as [key, vertex]}
|
||||
<g
|
||||
class="cursor-pointer"
|
||||
onmouseenter={() => hoveredVertex = key as 'C' | 'A' | 'P'}
|
||||
onmouseleave={() => hoveredVertex = null}
|
||||
>
|
||||
<circle
|
||||
cx={vertex.x}
|
||||
cy={vertex.y}
|
||||
r={hoveredVertex === key ? 35 : 30}
|
||||
fill={vertex.color}
|
||||
class="transition-all duration-200"
|
||||
opacity={hoveredVertex === key ? 1 : 0.8}
|
||||
/>
|
||||
<text
|
||||
x={vertex.x}
|
||||
y={vertex.y + 6}
|
||||
text-anchor="middle"
|
||||
fill="white"
|
||||
class="text-lg font-bold pointer-events-none"
|
||||
>
|
||||
{vertex.short}
|
||||
</text>
|
||||
</g>
|
||||
{/each}
|
||||
|
||||
<!-- Center text -->
|
||||
<text x="200" y="200" text-anchor="middle" fill="#64748B" class="text-xs">
|
||||
Click an edge to learn more
|
||||
</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Vertex Info Panel -->
|
||||
<div class="lg:w-80 space-y-4">
|
||||
<h3 class="font-semibold text-surface-200">The Three Properties</h3>
|
||||
{#each Object.entries(vertices) as [key, vertex]}
|
||||
<div
|
||||
class="p-4 rounded-lg border transition-all duration-200"
|
||||
class:bg-surface-800={hoveredVertex !== key}
|
||||
class:border-surface-700={hoveredVertex !== key}
|
||||
style:background-color={hoveredVertex === key ? `${vertex.color}20` : undefined}
|
||||
style:border-color={hoveredVertex === key ? vertex.color : undefined}
|
||||
onmouseenter={() => hoveredVertex = key as 'C' | 'A' | 'P'}
|
||||
onmouseleave={() => hoveredVertex = null}
|
||||
>
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<div
|
||||
class="w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold text-white"
|
||||
style:background-color={vertex.color}
|
||||
>
|
||||
{vertex.short}
|
||||
</div>
|
||||
<span class="font-medium text-surface-100">{vertex.label}</span>
|
||||
</div>
|
||||
<p class="text-sm text-surface-400">{vertex.description}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Selected Combination Details -->
|
||||
{#if selectedChoice && selectedChoice !== 'C' && selectedChoice !== 'A' && selectedChoice !== 'P'}
|
||||
{@const combo = combinations[selectedChoice]}
|
||||
<div
|
||||
class="card border-2 transition-all duration-300"
|
||||
style:border-color={combo.color}
|
||||
in:fly={{ y: 20, duration: 300 }}
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
<div
|
||||
class="w-12 h-12 rounded-xl flex items-center justify-center text-white font-bold flex-shrink-0"
|
||||
style:background-color={combo.color}
|
||||
>
|
||||
{selectedChoice}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<h3 class="text-xl font-semibold text-surface-100">{combo.name}</h3>
|
||||
{#if !combo.viable}
|
||||
<span class="tag bg-red-500/20 text-red-400">Not Achievable</span>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="text-surface-300 mb-4">{combo.explanation}</p>
|
||||
|
||||
{#if combo.viable}
|
||||
<div class="grid md:grid-cols-2 gap-4">
|
||||
<!-- Examples -->
|
||||
<div class="bg-surface-800 rounded-lg p-4">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<Icons.Database class="w-4 h-4 text-blue-400" />
|
||||
<span class="text-sm font-medium text-blue-400">Examples</span>
|
||||
</div>
|
||||
<ul class="space-y-2">
|
||||
{#each combo.examples as example}
|
||||
<li class="text-sm text-surface-300 flex items-start gap-2">
|
||||
<Icons.ChevronRight class="w-4 h-4 mt-0.5 text-surface-500 flex-shrink-0" />
|
||||
{example}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Use Case -->
|
||||
{#if combo.useCase}
|
||||
<div class="bg-surface-800 rounded-lg p-4">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<Icons.Lightbulb class="w-4 h-4 text-yellow-400" />
|
||||
<span class="text-sm font-medium text-yellow-400">Best For</span>
|
||||
</div>
|
||||
<p class="text-sm text-surface-300">{combo.useCase}</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="bg-red-500/10 border border-red-500/30 rounded-lg p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<Icons.AlertTriangle class="w-4 h-4 text-red-400" />
|
||||
<span class="text-sm font-medium text-red-400">Why Not Achievable?</span>
|
||||
</div>
|
||||
<p class="text-sm text-surface-300">
|
||||
In any distributed system, network partitions can and will occur. When they do,
|
||||
you MUST choose between consistency and availability - you cannot have both.
|
||||
CA systems only exist as single-node databases.
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Key Insight -->
|
||||
<div class="card bg-gradient-to-br from-blue-500/10 to-purple-500/10 border-blue-500/30">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-10 h-10 rounded-lg bg-blue-500/20 flex items-center justify-center flex-shrink-0">
|
||||
<Icons.Sparkles class="w-5 h-5 text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-surface-100 mb-2">The Key Insight</h3>
|
||||
<p class="text-surface-300 text-sm">
|
||||
In practice, <strong class="text-yellow-400">Partition Tolerance (P) is mandatory</strong> for any
|
||||
distributed system - network failures are a reality. This means your real choice is between
|
||||
<strong class="text-purple-400">CP</strong> (consistency over availability) and
|
||||
<strong class="text-pink-400">AP</strong> (availability over consistency).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Real-World Decision Guide -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-semibold text-surface-100 mb-4">Choosing Between CP and AP</h3>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-lg bg-purple-500/20 flex items-center justify-center">
|
||||
<span class="text-sm font-bold text-purple-400">CP</span>
|
||||
</div>
|
||||
<span class="font-medium text-surface-200">Choose CP when...</span>
|
||||
</div>
|
||||
<ul class="space-y-2 ml-10">
|
||||
{#each [
|
||||
'Wrong data causes real harm (financial transactions)',
|
||||
'Users expect immediate consistency (booking systems)',
|
||||
'You need strong guarantees (inventory counts)',
|
||||
'Data conflicts are hard to resolve'
|
||||
] as item}
|
||||
<li class="text-sm text-surface-400 flex items-start gap-2">
|
||||
<Icons.Check class="w-4 h-4 text-purple-400 mt-0.5 flex-shrink-0" />
|
||||
{item}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-lg bg-pink-500/20 flex items-center justify-center">
|
||||
<span class="text-sm font-bold text-pink-400">AP</span>
|
||||
</div>
|
||||
<span class="font-medium text-surface-200">Choose AP when...</span>
|
||||
</div>
|
||||
<ul class="space-y-2 ml-10">
|
||||
{#each [
|
||||
'Availability is critical (always-on services)',
|
||||
'Eventual consistency is acceptable (social feeds)',
|
||||
'You can merge conflicts automatically',
|
||||
'Stale data is better than no data'
|
||||
] as item}
|
||||
<li class="text-sm text-surface-400 flex items-start gap-2">
|
||||
<Icons.Check class="w-4 h-4 text-pink-400 mt-0.5 flex-shrink-0" />
|
||||
{item}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="flex justify-between items-center pt-4 border-t border-surface-800">
|
||||
<a href="/fundamentals" class="btn-secondary flex items-center gap-2">
|
||||
<Icons.ArrowLeft class="w-4 h-4" />
|
||||
Back to Fundamentals
|
||||
</a>
|
||||
<a href="/fundamentals/consistency" class="btn-primary flex items-center gap-2">
|
||||
Next: Consistency Models
|
||||
<Icons.ArrowRight class="w-4 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
306
src/routes/networking/dns/+page.svelte
Normal file
306
src/routes/networking/dns/+page.svelte
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
<script lang="ts">
|
||||
import * as Icons from 'lucide-svelte';
|
||||
import GuidedWalkthrough from '$lib/components/diagrams/GuidedWalkthrough.svelte';
|
||||
|
||||
const dnsSteps = [
|
||||
{
|
||||
id: 'step1',
|
||||
title: 'User Types URL',
|
||||
description: 'You type "example.com" into your browser. The browser needs to find the IP address of this domain to connect to it.',
|
||||
icon: 'Globe',
|
||||
details: 'The browser first checks its own cache. If you visited this site recently, it may already know the IP address.',
|
||||
tip: 'Browser caches typically last a few minutes to hours, depending on the TTL (Time To Live) set by the domain.'
|
||||
},
|
||||
{
|
||||
id: 'step2',
|
||||
title: 'Check OS Cache',
|
||||
description: 'If not in browser cache, the OS (Operating System) cache is checked. Your computer maintains its own DNS cache.',
|
||||
icon: 'Monitor',
|
||||
details: 'On macOS: "dscacheutil -q host -a name example.com". On Windows: "ipconfig /displaydns". On Linux: check systemd-resolved.',
|
||||
tip: 'You can flush your OS DNS cache if you need to force a fresh lookup.'
|
||||
},
|
||||
{
|
||||
id: 'step3',
|
||||
title: 'Query Recursive Resolver',
|
||||
description: 'If still not found, your computer asks the Recursive DNS Resolver (usually your ISP or a service like 8.8.8.8).',
|
||||
icon: 'Search',
|
||||
details: 'The recursive resolver is like a librarian - it will do all the work to find the answer for you. Popular resolvers: Google (8.8.8.8), Cloudflare (1.1.1.1), OpenDNS.',
|
||||
tip: 'Using a different DNS resolver can improve speed and privacy. Cloudflare 1.1.1.1 is known for speed, while 9.9.9.9 (Quad9) blocks malicious domains.'
|
||||
},
|
||||
{
|
||||
id: 'step4',
|
||||
title: 'Query Root Nameserver',
|
||||
description: 'The resolver asks a Root Nameserver: "Who knows about .com domains?" There are 13 root server clusters worldwide.',
|
||||
icon: 'Server',
|
||||
details: 'Root servers don\'t know specific domains - they only know which servers handle each TLD (.com, .org, .net, etc.). They respond with the address of the TLD nameserver.',
|
||||
tip: 'The 13 root server clusters (A through M) are actually hundreds of servers using Anycast to appear as one.'
|
||||
},
|
||||
{
|
||||
id: 'step5',
|
||||
title: 'Query TLD Nameserver',
|
||||
description: 'The resolver asks the .com TLD Nameserver: "Who knows about example.com?" It responds with the authoritative nameserver.',
|
||||
icon: 'Building',
|
||||
details: 'TLD (Top Level Domain) servers are managed by registries. Verisign manages .com and .net. Different organizations manage country codes like .uk, .de, etc.',
|
||||
tip: 'When you buy a domain, your registrar tells the TLD servers which nameservers are authoritative for your domain.'
|
||||
},
|
||||
{
|
||||
id: 'step6',
|
||||
title: 'Query Authoritative Nameserver',
|
||||
description: 'The resolver asks the Authoritative Nameserver: "What is the IP address of example.com?" This server has the actual answer.',
|
||||
icon: 'Database',
|
||||
details: 'The authoritative nameserver is typically run by your DNS provider (Cloudflare, Route53, etc.) or hosting provider. It contains the actual DNS records you configured.',
|
||||
tip: 'This is where your A records, CNAME records, MX records, and TXT records live. Changes here propagate based on TTL.'
|
||||
},
|
||||
{
|
||||
id: 'step7',
|
||||
title: 'Return IP Address',
|
||||
description: 'The authoritative nameserver responds with the IP address (e.g., 93.184.216.34). The resolver caches this and returns it to your browser.',
|
||||
icon: 'ArrowDown',
|
||||
details: 'The response includes a TTL (Time To Live) - how long this answer can be cached. Typical TTLs range from 5 minutes to 24 hours.',
|
||||
tip: 'Lower TTL = faster propagation of changes but more DNS queries. Higher TTL = better caching but slower updates.'
|
||||
},
|
||||
{
|
||||
id: 'step8',
|
||||
title: 'Browser Connects',
|
||||
description: 'Your browser now has the IP address and can establish a TCP connection to the web server and load the page.',
|
||||
icon: 'Wifi',
|
||||
details: 'The entire DNS lookup typically takes 20-120ms. After that, the browser opens a TCP connection (another round trip) and sends the HTTP request.',
|
||||
tip: 'This whole process happens every time you visit a new domain. Caching at multiple levels makes repeat visits much faster.'
|
||||
}
|
||||
];
|
||||
|
||||
const recordTypes = [
|
||||
{
|
||||
type: 'A',
|
||||
description: 'Maps domain to IPv4 address',
|
||||
example: 'example.com -> 93.184.216.34',
|
||||
useCase: 'Primary record for websites'
|
||||
},
|
||||
{
|
||||
type: 'AAAA',
|
||||
description: 'Maps domain to IPv6 address',
|
||||
example: 'example.com -> 2606:2800:220:1:248:1893:25c8:1946',
|
||||
useCase: 'IPv6 support'
|
||||
},
|
||||
{
|
||||
type: 'CNAME',
|
||||
description: 'Alias pointing to another domain',
|
||||
example: 'www.example.com -> example.com',
|
||||
useCase: 'Subdomains, CDN integration'
|
||||
},
|
||||
{
|
||||
type: 'MX',
|
||||
description: 'Mail server for the domain',
|
||||
example: 'example.com -> mail.example.com (priority 10)',
|
||||
useCase: 'Email routing'
|
||||
},
|
||||
{
|
||||
type: 'TXT',
|
||||
description: 'Arbitrary text data',
|
||||
example: 'v=spf1 include:_spf.google.com ~all',
|
||||
useCase: 'SPF, DKIM, domain verification'
|
||||
},
|
||||
{
|
||||
type: 'NS',
|
||||
description: 'Nameservers for the domain',
|
||||
example: 'example.com -> ns1.cloudflare.com',
|
||||
useCase: 'Delegate DNS authority'
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>DNS - How it Works - System Design Explorer</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-5xl mx-auto space-y-8">
|
||||
<!-- Header -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 text-surface-500 text-sm mb-2">
|
||||
<a href="/networking" class="hover:text-surface-300">Networking</a>
|
||||
<Icons.ChevronRight class="w-4 h-4" />
|
||||
<span class="text-surface-300">DNS</span>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold text-surface-100">DNS: Domain Name System</h1>
|
||||
<p class="text-surface-400 mt-2">
|
||||
The internet's phonebook - translating human-readable domain names to IP addresses.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Quick Summary -->
|
||||
<div class="grid md:grid-cols-3 gap-4">
|
||||
<div class="card text-center">
|
||||
<div class="w-12 h-12 mx-auto rounded-xl bg-blue-500/20 flex items-center justify-center mb-3">
|
||||
<Icons.Globe class="w-6 h-6 text-blue-400" />
|
||||
</div>
|
||||
<h3 class="font-medium text-surface-200">What</h3>
|
||||
<p class="text-sm text-surface-400 mt-1">Translates domain names to IP addresses</p>
|
||||
</div>
|
||||
<div class="card text-center">
|
||||
<div class="w-12 h-12 mx-auto rounded-xl bg-green-500/20 flex items-center justify-center mb-3">
|
||||
<Icons.Zap class="w-6 h-6 text-green-400" />
|
||||
</div>
|
||||
<h3 class="font-medium text-surface-200">Speed</h3>
|
||||
<p class="text-sm text-surface-400 mt-1">20-120ms for uncached lookups</p>
|
||||
</div>
|
||||
<div class="card text-center">
|
||||
<div class="w-12 h-12 mx-auto rounded-xl bg-purple-500/20 flex items-center justify-center mb-3">
|
||||
<Icons.Layers class="w-6 h-6 text-purple-400" />
|
||||
</div>
|
||||
<h3 class="font-medium text-surface-200">Layers</h3>
|
||||
<p class="text-sm text-surface-400 mt-1">Multiple caching layers for efficiency</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Walkthrough -->
|
||||
<GuidedWalkthrough
|
||||
title="How DNS Resolution Works"
|
||||
subtitle="Follow a DNS query from your browser to the answer"
|
||||
steps={dnsSteps}
|
||||
autoPlay={false}
|
||||
/>
|
||||
|
||||
<!-- DNS Record Types -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-semibold text-surface-100 mb-4">DNS Record Types</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-surface-700">
|
||||
<th class="text-left p-3 text-surface-400 font-medium">Type</th>
|
||||
<th class="text-left p-3 text-surface-400 font-medium">Description</th>
|
||||
<th class="text-left p-3 text-surface-400 font-medium">Example</th>
|
||||
<th class="text-left p-3 text-surface-400 font-medium">Use Case</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each recordTypes as record}
|
||||
<tr class="border-b border-surface-800 hover:bg-surface-800/50">
|
||||
<td class="p-3">
|
||||
<span class="px-2 py-1 rounded bg-blue-500/20 text-blue-400 font-mono text-sm">
|
||||
{record.type}
|
||||
</span>
|
||||
</td>
|
||||
<td class="p-3 text-surface-300 text-sm">{record.description}</td>
|
||||
<td class="p-3 text-surface-400 text-sm font-mono">{record.example}</td>
|
||||
<td class="p-3 text-surface-400 text-sm">{record.useCase}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- A vs CNAME -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-semibold text-surface-100 mb-4">A Record vs CNAME: When to Use Each</h3>
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="px-3 py-1 rounded-lg bg-blue-500/20 text-blue-400 font-bold">A</span>
|
||||
<span class="font-medium text-surface-200">Direct IP Mapping</span>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li class="flex items-start gap-2 text-surface-300">
|
||||
<Icons.Check class="w-4 h-4 text-green-400 mt-0.5 flex-shrink-0" />
|
||||
Use for root domain (example.com)
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-surface-300">
|
||||
<Icons.Check class="w-4 h-4 text-green-400 mt-0.5 flex-shrink-0" />
|
||||
Slightly faster (no extra lookup)
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-surface-300">
|
||||
<Icons.X class="w-4 h-4 text-red-400 mt-0.5 flex-shrink-0" />
|
||||
Must update if IP changes
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-surface-300">
|
||||
<Icons.X class="w-4 h-4 text-red-400 mt-0.5 flex-shrink-0" />
|
||||
Can't point to another domain
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="px-3 py-1 rounded-lg bg-green-500/20 text-green-400 font-bold">CNAME</span>
|
||||
<span class="font-medium text-surface-200">Alias to Another Domain</span>
|
||||
</div>
|
||||
<ul class="space-y-2 text-sm">
|
||||
<li class="flex items-start gap-2 text-surface-300">
|
||||
<Icons.Check class="w-4 h-4 text-green-400 mt-0.5 flex-shrink-0" />
|
||||
Use for subdomains (www.example.com)
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-surface-300">
|
||||
<Icons.Check class="w-4 h-4 text-green-400 mt-0.5 flex-shrink-0" />
|
||||
Great for CDNs and load balancers
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-surface-300">
|
||||
<Icons.Check class="w-4 h-4 text-green-400 mt-0.5 flex-shrink-0" />
|
||||
Target can change IP without updating
|
||||
</li>
|
||||
<li class="flex items-start gap-2 text-surface-300">
|
||||
<Icons.X class="w-4 h-4 text-red-400 mt-0.5 flex-shrink-0" />
|
||||
Cannot use at root domain (usually)
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Popular DNS Providers -->
|
||||
<div class="card">
|
||||
<h3 class="text-lg font-semibold text-surface-100 mb-4">DNS Providers Comparison</h3>
|
||||
<div class="grid md:grid-cols-3 gap-4">
|
||||
{#each [
|
||||
{
|
||||
name: 'Cloudflare',
|
||||
icon: 'Cloud',
|
||||
color: 'orange',
|
||||
pros: ['Free tier', 'Fast propagation', 'DDoS protection'],
|
||||
bestFor: 'Most websites'
|
||||
},
|
||||
{
|
||||
name: 'Route 53',
|
||||
icon: 'Compass',
|
||||
color: 'yellow',
|
||||
pros: ['AWS integration', 'Health checks', 'Geo-routing'],
|
||||
bestFor: 'AWS infrastructure'
|
||||
},
|
||||
{
|
||||
name: 'Cloud DNS',
|
||||
icon: 'Cloud',
|
||||
color: 'blue',
|
||||
pros: ['GCP integration', '100% SLA', 'Global anycast'],
|
||||
bestFor: 'GCP infrastructure'
|
||||
}
|
||||
] as provider}
|
||||
<div class="bg-surface-800 rounded-lg p-4">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<Icons.Cloud class="w-5 h-5 text-{provider.color}-400" />
|
||||
<span class="font-medium text-surface-200">{provider.name}</span>
|
||||
</div>
|
||||
<ul class="space-y-1 mb-3">
|
||||
{#each provider.pros as pro}
|
||||
<li class="text-sm text-surface-400 flex items-center gap-2">
|
||||
<Icons.Check class="w-3 h-3 text-green-400" />
|
||||
{pro}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<div class="text-xs text-surface-500">Best for: {provider.bestFor}</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="flex justify-between items-center pt-4 border-t border-surface-800">
|
||||
<a href="/networking" class="btn-secondary flex items-center gap-2">
|
||||
<Icons.ArrowLeft class="w-4 h-4" />
|
||||
All Networking
|
||||
</a>
|
||||
<a href="/networking/load-balancing" class="btn-primary flex items-center gap-2">
|
||||
Next: Load Balancing
|
||||
<Icons.ArrowRight class="w-4 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
368
src/routes/networking/load-balancing/+page.svelte
Normal file
368
src/routes/networking/load-balancing/+page.svelte
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
<script lang="ts">
|
||||
import ComparisonTable from '$lib/components/diagrams/ComparisonTable.svelte';
|
||||
|
||||
let selectedLayer: 'l4' | 'l7' | null = null;
|
||||
let showOsiModel = true;
|
||||
|
||||
const osiLayers = [
|
||||
{ num: 7, name: 'Application', protocols: 'HTTP, HTTPS, gRPC', lb: 'ALB' },
|
||||
{ num: 6, name: 'Presentation', protocols: 'SSL/TLS', lb: '-' },
|
||||
{ num: 5, name: 'Session', protocols: 'Sockets', lb: '-' },
|
||||
{ num: 4, name: 'Transport', protocols: 'TCP, UDP', lb: 'NLB' },
|
||||
{ num: 3, name: 'Network', protocols: 'IP', lb: 'GLB' },
|
||||
{ num: 2, name: 'Data Link', protocols: 'Ethernet', lb: '-' },
|
||||
{ num: 1, name: 'Physical', protocols: 'Cables', lb: '-' }
|
||||
];
|
||||
|
||||
const comparisonOptions = [
|
||||
{
|
||||
id: 'l4',
|
||||
name: 'Layer 4 (NLB)',
|
||||
description: 'Transport layer load balancing based on IP and port',
|
||||
whenToUse: [
|
||||
'High-performance, low-latency requirements (gaming, trading)',
|
||||
'Non-HTTP protocols (databases, custom TCP/UDP)',
|
||||
'Need for static IP addresses',
|
||||
'Simple round-robin or hash-based distribution',
|
||||
'TLS passthrough without termination',
|
||||
'Millions of connections per second'
|
||||
],
|
||||
whenNotToUse: [
|
||||
'Need content-based routing (URL paths, headers)',
|
||||
'Require sticky sessions based on cookies',
|
||||
'Want to inspect or modify HTTP traffic',
|
||||
'Need Web Application Firewall (WAF) integration',
|
||||
'Microservices with path-based routing'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'l7',
|
||||
name: 'Layer 7 (ALB)',
|
||||
description: 'Application layer load balancing with content inspection',
|
||||
whenToUse: [
|
||||
'HTTP/HTTPS web applications',
|
||||
'Microservices with path-based routing (/api, /web)',
|
||||
'Content-based routing (headers, cookies, query strings)',
|
||||
'SSL termination at the load balancer',
|
||||
'WebSocket support',
|
||||
'Integration with WAF for security'
|
||||
],
|
||||
whenNotToUse: [
|
||||
'Non-HTTP protocols (databases, gaming servers)',
|
||||
'Ultra-low latency requirements',
|
||||
'Need static IP addresses',
|
||||
'Simple TCP/UDP load balancing',
|
||||
'Want to avoid SSL termination overhead'
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const features = [
|
||||
{ name: 'OSI Layer', l4: 'Layer 4 (Transport)', l7: 'Layer 7 (Application)' },
|
||||
{ name: 'Protocols', l4: 'TCP, UDP, TLS', l7: 'HTTP, HTTPS, gRPC, WebSocket' },
|
||||
{ name: 'Routing Decision', l4: 'IP + Port (5-tuple)', l7: 'URL path, headers, cookies' },
|
||||
{ name: 'SSL/TLS', l4: 'Passthrough or terminate', l7: 'Always terminates' },
|
||||
{ name: 'Performance', l4: 'Millions req/sec, microseconds', l7: 'Lower throughput, milliseconds' },
|
||||
{ name: 'Connection', l4: 'Preserves client connection', l7: 'Terminates and creates new' },
|
||||
{ name: 'Algorithm', l4: 'Flow hash', l7: 'Round robin, least connections' },
|
||||
{ name: 'Static IP', l4: 'Yes (per AZ)', l7: 'No (DNS-based)' },
|
||||
{ name: 'Health Checks', l4: 'TCP/UDP port check', l7: 'HTTP status codes' },
|
||||
{ name: 'WAF Support', l4: 'No', l7: 'Yes' }
|
||||
];
|
||||
|
||||
const awsServices = [
|
||||
{ name: 'ALB', layer: 'L7', use: 'Web apps, microservices, APIs' },
|
||||
{ name: 'NLB', layer: 'L4', use: 'High performance, TCP/UDP, static IPs' },
|
||||
{ name: 'GLB', layer: 'L3/L4', use: 'Network appliances, firewalls, inspection' },
|
||||
{ name: 'CLB', layer: 'L4/L7', use: 'Legacy (deprecated)' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Load Balancing - L4 vs L7</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<h1 class="text-3xl font-bold text-white mb-2">Load Balancing</h1>
|
||||
<p class="text-gray-400 mb-8">Understanding Layer 4 vs Layer 7 load balancers and when to use each</p>
|
||||
|
||||
<!-- OSI Model Visual -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">OSI Model & Load Balancer Layers</h2>
|
||||
<p class="text-gray-400 mb-6">Load balancers operate at different OSI layers, determining what information they can see and use for routing decisions.</p>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<div class="grid gap-2">
|
||||
{#each osiLayers as layer}
|
||||
<div
|
||||
class="flex items-center gap-4 p-3 rounded-lg transition-all cursor-pointer
|
||||
{layer.num === 7 ? 'bg-blue-500/20 border border-blue-500/50' : ''}
|
||||
{layer.num === 4 ? 'bg-purple-500/20 border border-purple-500/50' : ''}
|
||||
{layer.num === 3 ? 'bg-orange-500/20 border border-orange-500/50' : ''}
|
||||
{layer.num !== 7 && layer.num !== 4 && layer.num !== 3 ? 'bg-gray-700/30' : ''}"
|
||||
on:click={() => {
|
||||
if (layer.num === 7) selectedLayer = 'l7';
|
||||
else if (layer.num === 4) selectedLayer = 'l4';
|
||||
}}
|
||||
on:keydown={(e) => e.key === 'Enter' && (layer.num === 7 ? selectedLayer = 'l7' : layer.num === 4 ? selectedLayer = 'l4' : null)}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="w-8 h-8 rounded-full flex items-center justify-center font-bold
|
||||
{layer.num === 7 ? 'bg-blue-500 text-white' : ''}
|
||||
{layer.num === 4 ? 'bg-purple-500 text-white' : ''}
|
||||
{layer.num === 3 ? 'bg-orange-500 text-white' : ''}
|
||||
{layer.num !== 7 && layer.num !== 4 && layer.num !== 3 ? 'bg-gray-600 text-gray-300' : ''}">
|
||||
{layer.num}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<span class="text-white font-medium">{layer.name}</span>
|
||||
<span class="text-gray-500 ml-2">- {layer.protocols}</span>
|
||||
</div>
|
||||
{#if layer.lb !== '-'}
|
||||
<span class="px-3 py-1 rounded-full text-sm font-medium
|
||||
{layer.num === 7 ? 'bg-blue-500/30 text-blue-300' : ''}
|
||||
{layer.num === 4 ? 'bg-purple-500/30 text-purple-300' : ''}
|
||||
{layer.num === 3 ? 'bg-orange-500/30 text-orange-300' : ''}">
|
||||
{layer.lb}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid grid-cols-3 gap-4 text-center">
|
||||
<div class="p-3 rounded-lg bg-blue-500/10 border border-blue-500/30">
|
||||
<div class="text-blue-400 font-semibold">Layer 7 (ALB)</div>
|
||||
<div class="text-gray-400 text-sm mt-1">Sees HTTP content</div>
|
||||
</div>
|
||||
<div class="p-3 rounded-lg bg-purple-500/10 border border-purple-500/30">
|
||||
<div class="text-purple-400 font-semibold">Layer 4 (NLB)</div>
|
||||
<div class="text-gray-400 text-sm mt-1">Sees IP + Port</div>
|
||||
</div>
|
||||
<div class="p-3 rounded-lg bg-orange-500/10 border border-orange-500/30">
|
||||
<div class="text-orange-400 font-semibold">Layer 3 (GLB)</div>
|
||||
<div class="text-gray-400 text-sm mt-1">Network gateway</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- How They Work -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">How They Work</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<!-- L4 -->
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-purple-500/30">
|
||||
<h3 class="text-lg font-semibold text-purple-400 mb-4">Layer 4 Load Balancer</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-6 h-6 rounded-full bg-purple-500/20 flex items-center justify-center text-purple-400 text-sm font-bold">1</div>
|
||||
<div>
|
||||
<div class="text-white font-medium">Extract 5-tuple</div>
|
||||
<div class="text-gray-400 text-sm">Source IP, Source Port, Dest IP, Dest Port, Protocol</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-6 h-6 rounded-full bg-purple-500/20 flex items-center justify-center text-purple-400 text-sm font-bold">2</div>
|
||||
<div>
|
||||
<div class="text-white font-medium">Apply hash algorithm</div>
|
||||
<div class="text-gray-400 text-sm">Deterministic routing based on connection info</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-6 h-6 rounded-full bg-purple-500/20 flex items-center justify-center text-purple-400 text-sm font-bold">3</div>
|
||||
<div>
|
||||
<div class="text-white font-medium">Forward entire connection</div>
|
||||
<div class="text-gray-400 text-sm">All packets in session go to same backend</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 p-3 bg-purple-500/10 rounded-lg">
|
||||
<div class="text-purple-300 text-sm font-medium">Key insight:</div>
|
||||
<div class="text-gray-400 text-sm">Cannot see inside the packet payload - only network headers</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- L7 -->
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-blue-500/30">
|
||||
<h3 class="text-lg font-semibold text-blue-400 mb-4">Layer 7 Load Balancer</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-6 h-6 rounded-full bg-blue-500/20 flex items-center justify-center text-blue-400 text-sm font-bold">1</div>
|
||||
<div>
|
||||
<div class="text-white font-medium">Terminate connection</div>
|
||||
<div class="text-gray-400 text-sm">Client connects to LB, LB connects to backend</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-6 h-6 rounded-full bg-blue-500/20 flex items-center justify-center text-blue-400 text-sm font-bold">2</div>
|
||||
<div>
|
||||
<div class="text-white font-medium">Parse HTTP request</div>
|
||||
<div class="text-gray-400 text-sm">Read URL path, headers, cookies, body</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="w-6 h-6 rounded-full bg-blue-500/20 flex items-center justify-center text-blue-400 text-sm font-bold">3</div>
|
||||
<div>
|
||||
<div class="text-white font-medium">Apply routing rules</div>
|
||||
<div class="text-gray-400 text-sm">/api/* to API servers, /static/* to CDN</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 p-3 bg-blue-500/10 rounded-lg">
|
||||
<div class="text-blue-300 text-sm font-medium">Key insight:</div>
|
||||
<div class="text-gray-400 text-sm">Full visibility into application data - can route by content</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Feature Comparison Table -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Feature Comparison</h2>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl border border-gray-700 overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-700">
|
||||
<th class="text-left p-4 text-gray-400 font-medium">Feature</th>
|
||||
<th class="text-left p-4 text-purple-400 font-medium">Layer 4 (NLB)</th>
|
||||
<th class="text-left p-4 text-blue-400 font-medium">Layer 7 (ALB)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each features as feature, i}
|
||||
<tr class="border-b border-gray-700/50 {i % 2 === 0 ? 'bg-gray-800/30' : ''}">
|
||||
<td class="p-4 text-white font-medium">{feature.name}</td>
|
||||
<td class="p-4 text-gray-300">{feature.l4}</td>
|
||||
<td class="p-4 text-gray-300">{feature.l7}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- When to Use -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">When to Use Each</h2>
|
||||
<ComparisonTable options={comparisonOptions} />
|
||||
</section>
|
||||
|
||||
<!-- AWS Services -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">AWS Load Balancer Types</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{#each awsServices as service}
|
||||
<div class="bg-gray-800/50 rounded-xl p-4 border border-gray-700 hover:border-gray-500 transition-colors">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="text-lg font-bold text-white">{service.name}</span>
|
||||
<span class="px-2 py-0.5 rounded text-xs font-medium
|
||||
{service.layer === 'L7' ? 'bg-blue-500/20 text-blue-400' : ''}
|
||||
{service.layer === 'L4' ? 'bg-purple-500/20 text-purple-400' : ''}
|
||||
{service.layer === 'L3/L4' ? 'bg-orange-500/20 text-orange-400' : ''}
|
||||
{service.layer === 'L4/L7' ? 'bg-gray-500/20 text-gray-400' : ''}">
|
||||
{service.layer}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm">{service.use}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Decision Guide -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Quick Decision Guide</h2>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center gap-4 p-4 bg-purple-500/10 rounded-lg border border-purple-500/30">
|
||||
<div class="text-2xl">1M+</div>
|
||||
<div>
|
||||
<div class="text-white font-medium">Need millions of requests/second?</div>
|
||||
<div class="text-gray-400 text-sm">Use NLB (Layer 4) - designed for extreme performance</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4 p-4 bg-blue-500/10 rounded-lg border border-blue-500/30">
|
||||
<div class="text-2xl">/api</div>
|
||||
<div>
|
||||
<div class="text-white font-medium">Need path-based routing?</div>
|
||||
<div class="text-gray-400 text-sm">Use ALB (Layer 7) - can route by URL, headers, cookies</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4 p-4 bg-purple-500/10 rounded-lg border border-purple-500/30">
|
||||
<div class="text-2xl">IP</div>
|
||||
<div>
|
||||
<div class="text-white font-medium">Need static IP addresses?</div>
|
||||
<div class="text-gray-400 text-sm">Use NLB (Layer 4) - provides static IPs per AZ</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4 p-4 bg-blue-500/10 rounded-lg border border-blue-500/30">
|
||||
<div class="text-2xl">WAF</div>
|
||||
<div>
|
||||
<div class="text-white font-medium">Need WAF or advanced security?</div>
|
||||
<div class="text-gray-400 text-sm">Use ALB (Layer 7) - integrates with AWS WAF</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4 p-4 bg-gray-700/50 rounded-lg border border-gray-600">
|
||||
<div class="text-2xl">DB</div>
|
||||
<div>
|
||||
<div class="text-white font-medium">Load balancing databases or non-HTTP?</div>
|
||||
<div class="text-gray-400 text-sm">Use NLB (Layer 4) - protocol agnostic</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Real World Examples -->
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold text-white mb-4">Real World Examples</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<h3 class="text-lg font-semibold text-white mb-3">E-commerce Platform</h3>
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 rounded bg-blue-500/20 text-blue-400">ALB</span>
|
||||
<span class="text-gray-300">/api/* to API servers</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 rounded bg-blue-500/20 text-blue-400">ALB</span>
|
||||
<span class="text-gray-300">/static/* to static content servers</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 rounded bg-blue-500/20 text-blue-400">ALB</span>
|
||||
<span class="text-gray-300">Sticky sessions for shopping cart</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-800/50 rounded-xl p-6 border border-gray-700">
|
||||
<h3 class="text-lg font-semibold text-white mb-3">Gaming Server</h3>
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 rounded bg-purple-500/20 text-purple-400">NLB</span>
|
||||
<span class="text-gray-300">UDP game traffic (low latency)</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 rounded bg-purple-500/20 text-purple-400">NLB</span>
|
||||
<span class="text-gray-300">TCP matchmaking servers</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 rounded bg-purple-500/20 text-purple-400">NLB</span>
|
||||
<span class="text-gray-300">Static IPs for firewall rules</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
12
svelte.config.js
Normal file
12
svelte.config.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import adapter from '@sveltejs/adapter-auto';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
preprocess: vitePreprocess(),
|
||||
kit: {
|
||||
adapter: adapter()
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
58
tailwind.config.js
Normal file
58
tailwind.config.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// AWS colors
|
||||
aws: {
|
||||
orange: '#FF9900',
|
||||
dark: '#232F3E',
|
||||
light: '#37475A'
|
||||
},
|
||||
// GCP colors
|
||||
gcp: {
|
||||
blue: '#4285F4',
|
||||
red: '#EA4335',
|
||||
yellow: '#FBBC04',
|
||||
green: '#34A853'
|
||||
},
|
||||
// Custom theme colors
|
||||
surface: {
|
||||
50: '#f8fafc',
|
||||
100: '#f1f5f9',
|
||||
200: '#e2e8f0',
|
||||
300: '#cbd5e1',
|
||||
400: '#94a3b8',
|
||||
500: '#64748b',
|
||||
600: '#475569',
|
||||
700: '#334155',
|
||||
800: '#1e293b',
|
||||
900: '#0f172a',
|
||||
950: '#020617'
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', 'sans-serif'],
|
||||
mono: ['JetBrains Mono', 'Fira Code', 'monospace']
|
||||
},
|
||||
animation: {
|
||||
'flow': 'flow 2s ease-in-out infinite',
|
||||
'pulse-slow': 'pulse 3s ease-in-out infinite',
|
||||
'fade-in': 'fadeIn 0.5s ease-out'
|
||||
},
|
||||
keyframes: {
|
||||
flow: {
|
||||
'0%, 100%': { strokeDashoffset: '0' },
|
||||
'50%': { strokeDashoffset: '20' }
|
||||
},
|
||||
fadeIn: {
|
||||
'0%': { opacity: '0', transform: 'translateY(10px)' },
|
||||
'100%': { opacity: '1', transform: 'translateY(0)' }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: []
|
||||
};
|
||||
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
}
|
||||
6
vite.config.ts
Normal file
6
vite.config.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue