Caching, CDNs und Load Balancing
Fügen Sie Redis-Caching-Schichten hinzu, übertragen Sie statische Assets an ein CDN und verteilen Sie den Datenverkehr mit Round-Robin- und Consistent-Hashing-Load-Balancern auf Replikate.
Caching, CDNs und Load Balancing ist eine kostenlose Coding Interview Prep-Lektion auf CoddyKit. Dies ist Lektion 3 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Coding Interview Prep-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Coding Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Warum Caching bei großer Skalierung unverzichtbar ist
Caching speichert Kopien häufig abgerufener Daten in einer schnelleren Speicherschicht, sodass zukünftige Anfragen bedient werden können, ohne auf den langsameren zugrunde liegenden Speicher (Datenbank, externe API) zuzugreifen. Bei großer Skalierung erhält eine kleine Anzahl beliebter Elemente den überwiegenden Teil aller Anfragen – häufig gilt die 80/20-Regel (Pareto-Prinzip): 20 % der Elemente verursachen 80 % des Datenverkehrs.
Ein Cache, der die am häufigsten abgerufenen 20 % im Arbeitsspeicher aufnehmen kann, bewältigt 80 % der Datenbanklast. Deshalb senkt das Hinzufügen eines Redis-Caches die CPU-Auslastung der Datenbank häufig um 70–90 % und reduziert die p99-Latenz bei Cache-Treffern von 10 ms auf unter 1 ms – ohne Datenbank oder Anwendungslogik wesentlich zu verändern.
# Demonstrating the 80/20 caching benefit
import random
# Simulate 1000 requests to 100 items with Zipf-like distribution
def zipf_sample(n_items, n_requests):
access_counts = {}
weights = [1.0 / (i + 1) for i in range(n_items)] # Zipf: item 0 most popular
total = sum(weights)
probs = [w / total for w in weights]
for _ in range(n_requests):
item = random.choices(range(n_items), weights=probs)[0]
access_counts[item] = access_counts.get(item, 0) + 1
return access_counts
random.seed(42)
counts = zipf_sample(100, 10000)
top_20_items = sorted(counts, key=counts.get, reverse=True)[:20]
top_20_requests = sum(counts[i] for i in top_20_items)
print(f'Top 20% of items ({20} of 100) handle {top_20_requests/100:.1f}% of requests')Cache-Aside-Muster (Lazy Loading)
Das cache-aside-Muster (auch Lazy Loading genannt) ist die am häufigsten verwendete Caching-Strategie. Der Anwendungscode ist für die Verwaltung des Caches zuständig: Bei einem Lesevorgang wird zuerst der Cache geprüft. Bei einem Cache-Treffer wird das Ergebnis sofort zurückgegeben. Bei einem Cache-Miss werden die Daten aus der Datenbank abgerufen, in den Cache geschrieben und anschließend zurückgegeben. Bei einem Schreibvorgang wird die Datenbank aktualisiert und der Cache-Eintrag invalidiert (gelöscht), damit der nächste Lesevorgang ihn aktualisiert.
Dieses Muster stellt sicher, dass der Cache nur tatsächlich angeforderte Daten enthält (kein unnötiges Vorabladen) und durch die Invalidierung konsistent mit der Datenbank bleibt. Die Abwägung: Beim ersten Zugriff nach einem Cache-Miss fällt die vollständige Datenbanklast an (Kaltstart).
# Cache-aside pattern in Python
class CacheAsideService:
def __init__(self, db, cache):
self.db = db
self.cache = cache # e.g., Redis client
def get_user(self, user_id):
cache_key = f'user:{user_id}'
# 1. Check cache
cached = self.cache.get(cache_key)
if cached:
return cached # cache hit
# 2. Cache miss: fetch from DB
user = self.db.query('SELECT * FROM users WHERE id=%s', user_id)
# 3. Write to cache with TTL
self.cache.set(cache_key, user, ttl=3600) # 1 hour TTL
return user
def update_user(self, user_id, data):
# 1. Write to DB
self.db.execute('UPDATE users SET ... WHERE id=%s', user_id, data)
# 2. Invalidate cache (delete, not update)
self.cache.delete(f'user:{user_id}')
# Next read will re-populate cache from DB
print('Cache-aside: READ from cache, miss? load from DB + write cache')
print(' WRITE to DB, then DELETE from cache (invalidate)')Write-Through- und Write-Behind-Caching
Write-through: Bei jedem Schreibvorgang werden sowohl die Datenbank als auch der Cache synchron aktualisiert. Der Cache enthält stets aktuelle Daten. Abwägung: Schreibvorgänge sind langsamer (zwei Operationen), und der Cache füllt sich mit Daten, die möglicherweise nie wieder gelesen werden.
Write-behind (Write-back): Bei einem Schreibvorgang wird nur der Cache aktualisiert; die Daten werden später asynchron in die Datenbank geschrieben. Dadurch werden Schreibvorgänge extrem schnell, allerdings besteht das Risiko von Datenverlust, wenn der Cache ausfällt, bevor die Daten geschrieben wurden. Dieses Verfahren wird bei schreibintensiven Arbeitslasten eingesetzt, bei denen ein gewisser Datenverlust akzeptabel ist (z. B. bei Aufrufzählern und Analysen).
# Write-through vs Write-behind comparison
strategies = {
'Cache-aside (Lazy)': {
'read': 'Check cache; miss => DB + populate cache',
'write': 'Write DB; delete from cache (invalidate)',
'consistency': 'Strong (invalidation ensures freshness)',
'write_latency': 'Fast (one DB write)',
'risk': 'Cache stampede on popular key expiry',
},
'Write-through': {
'read': 'Always check cache; miss => DB',
'write': 'Write DB AND cache atomically',
'consistency': 'Strong (cache always has latest)',
'write_latency': 'Slower (two writes per operation)',
'risk': 'Cache polluted with rarely-read data',
},
'Write-behind': {
'read': 'Check cache; miss => DB',
'write': 'Write cache only; async flush to DB',
'consistency': 'Eventual (flush may be delayed)',
'write_latency': 'Very fast (cache write only)',
'risk': 'Data loss if cache crashes before flush',
},
}
for name, info in strategies.items():
print(f'\n{name}:')
for k, v in info.items(): print(f' {k}: {v}')Richtlinien zur Cache-Verdrängung
Wenn der Cache voll ist, entscheidet die Verdrängungsrichtlinie, welcher Eintrag entfernt wird. Die häufigsten Richtlinien:
- LRU (Least Recently Used): Entfernt den Eintrag, auf den am längsten nicht zugegriffen wurde. Eignet sich gut für Arbeitslasten mit zeitlicher Lokalität. Wird von Redis standardmäßig verwendet.
- LFU (Least Frequently Used): Entfernt den Eintrag, auf den am seltensten zugegriffen wurde. Eignet sich besser für Arbeitslasten, bei denen einige Elemente dauerhaft beliebt sind, LRU dies aber nicht erfassen würde.
- FIFO: Entfernt den ältesten eingefügten Eintrag. Einfach, aber für typische Webarbeitslasten wenig leistungsfähig.
- Random: Entfernt einen zufälligen Eintrag. Bei sehr großen Caches in der Praxis überraschend konkurrenzfähig gegenüber LRU.
# Implementing LRU cache
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.cache = OrderedDict() # maintains insertion/access order
def get(self, key):
if key not in self.cache:
return -1
self.cache.move_to_end(key) # mark as recently used
return self.cache[key]
def put(self, key, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False) # evict LRU (oldest)
cache = LRUCache(3)
for k, v in [('a',1),('b',2),('c',3)]:
cache.put(k, v)
print('Get a:', cache.get('a')) # 1 (a now most recently used)
cache.put('d', 4) # evicts 'b' (LRU)
print('Get b:', cache.get('b')) # -1 (evicted)
print('Get c:', cache.get('c')) # 3Content Delivery Networks (CDNs)
Ein CDN ist ein geografisch verteiltes Netzwerk aus Edge-Servern (Points of Presence, PoPs), die statische und dynamische Inhalte in der Nähe der Endbenutzer zwischenspeichern. Statt dass die Anfrage jedes Benutzers zu einem Origin-Server in einem einzigen Rechenzentrum geleitet wird, liefern CDN-Edge-Knoten die Inhalte vom nächstgelegenen PoP aus – dadurch sinkt die Latenz von etwa 200 ms (über Kontinente hinweg) auf etwa 5 ms (bei einem nahegelegenen PoP).
CDNs sind unverzichtbar für statische Ressourcen (Bilder, CSS, JS), Video-Streaming (HLS-Segmente) und zunehmend auch für API-Antworten und serverseitig gerendertes HTML. Das CDN prüft seinen Edge-Cache. Bei einem Cache-Miss ruft es den Inhalt vom Origin ab und speichert ihn für zukünftige Anfragen im Cache.
# CDN architecture flow
cdn_flow = [
'User requests https://example.com/image.jpg',
'DNS resolves to the nearest CDN PoP (e.g., Frankfurt for EU users)',
'CDN edge checks its local cache:',
' HIT: Return cached image directly (5ms latency)',
' MISS: Fetch from origin server (e.g., AWS S3 in us-east-1)',
' Cache image at edge with Cache-Control: max-age=86400',
' Future requests for this image served from edge (HIT)',
'Cache-Control headers control CDN behaviour:',
' max-age=31536000 s-maxage=31536000 -- cache 1 year',
' no-cache -- always revalidate',
' private -- CDN must not cache (user-specific)',
]
for step in cdn_flow:
print(step)
print('\nCDN providers: Cloudflare, AWS CloudFront, Fastly, Akamai')Load-Balancing: Datenverkehr verteilen
Ein Load-Balancer verteilt eingehende Anfragen auf mehrere Backend-Server und verhindert so, dass ein einzelner Server zum Engpass wird. Außerdem sorgt er für hohe Verfügbarkeit: Wenn ein Server ausfällt, leitet der Load-Balancer den Datenverkehr automatisch an gesunde Server weiter (Health-Checks alle 5–30 Sekunden).
Load-Balancer arbeiten auf verschiedenen OSI-Schichten: Schicht 4 (Transport – Weiterleitung anhand von IP-Adresse und Port, sehr schnell) und Schicht 7 (Anwendung – Weiterleitung anhand von URL-Pfad, Headern und Cookies, wodurch eine intelligentere Weiterleitung möglich ist). AWS ALB, Nginx und HAProxy sind verbreitete Load-Balancer der Schicht 7. AWS NLB ist ein Load-Balancer der Schicht 4.
# Load balancing algorithms
algorithms = {
'Round Robin': {
'how': 'Rotate through servers in sequence',
'best_for': 'Stateless servers with similar capacity',
'weakness': 'Does not account for server load or response time',
},
'Weighted Round Robin': {
'how': 'Round robin but servers with more capacity get more requests',
'best_for': 'Heterogeneous server fleet',
'weakness': 'Static weights; does not adapt to runtime load',
},
'Least Connections': {
'how': 'Send to server with fewest active connections',
'best_for': 'Long-lived connections (WebSocket, streaming)',
'weakness': 'More complex tracking of connection state',
},
'Consistent Hashing': {
'how': 'Hash request key (user_id, session) to server',
'best_for': 'Sticky sessions, cache locality per server',
'weakness': 'Uneven distribution if hash space is not balanced',
},
'Random': {
'how': 'Choose server at random',
'best_for': 'Simple stateless workloads',
'weakness': 'No guarantee of load balance in short windows',
},
}
for alg, info in algorithms.items():
print(f'{alg}: {info["how"]}')Consistent Hashing: Knoten hinzufügen und entfernen
Consistent Hashing löst das Problem der Umverteilung von Cache-Schlüsseln, wenn Server hinzugefügt oder entfernt werden. Beim naiven Hashing mit Modulo (server = hash(key) % n) werden durch eine Änderung von n fast alle Schlüssel neu zugeordnet – dadurch kann eine Cache Stampede entstehen. Consistent Hashing ordnet sowohl Schlüssel als auch Server auf einem Ring an. Jeder Schlüssel wird vom nächstgelegenen Server im Uhrzeigersinn bedient. Beim Hinzufügen eines Servers werden nur die Schlüssel zwischen dem neuen Server und seinem Vorgänger neu zugeordnet – etwa 1/n aller Schlüssel.
Virtuelle Knoten (vnodes) verbessern die Lastverteilung: Jedem physischen Server werden mehrere Positionen auf dem Ring zugewiesen. Dadurch werden die Schlüssel auch bei einer kleinen Anzahl von Servern gleichmäßiger verteilt.
import hashlib
import bisect
class ConsistentHashRing:
def __init__(self, replicas=100):
self.replicas = replicas # virtual nodes per server
self.ring = {}
self.sorted_keys = []
def add_server(self, server):
for i in range(self.replicas):
key = int(hashlib.md5(f'{server}:{i}'.encode()).hexdigest(), 16)
self.ring[key] = server
bisect.insort(self.sorted_keys, key)
def remove_server(self, server):
for i in range(self.replicas):
key = int(hashlib.md5(f'{server}:{i}'.encode()).hexdigest(), 16)
del self.ring[key]
self.sorted_keys.remove(key)
def get_server(self, item):
key = int(hashlib.md5(item.encode()).hexdigest(), 16)
idx = bisect.bisect(self.sorted_keys, key) % len(self.sorted_keys)
return self.ring[self.sorted_keys[idx]]
ring = ConsistentHashRing()
for s in ['server-1', 'server-2', 'server-3']:
ring.add_server(s)
for item in ['user:1', 'user:2', 'product:abc', 'session:xyz']:
print(f'{item} => {ring.get_server(item)}')Cache Stampede und Lösungen
Eine Cache Stampede (auch Thundering Herd genannt) tritt auf, wenn ein beliebter Cache-Eintrag abläuft und viele gleichzeitige Anfragen den Cache gleichzeitig verfehlen, wodurch die Datenbank mit derselben Abfrage überflutet wird. Lösungen:
- Mutex/Sperre: Nur eine Anfrage berechnet den Wert; die anderen warten
- Probabilistisches vorzeitiges Ablaufen: Kurz vor Ablauf der TTL entscheidet eine Anfrage zufällig, den Cache zu aktualisieren, und verhindert so ein gleichzeitiges Ablaufen
- Stale-while-revalidate: Veraltete Inhalte werden sofort ausgeliefert, während der Cache asynchron aktualisiert wird
- Aktualisierung im Hintergrund: Ein separater Prozess aktualisiert beliebte Schlüssel, bevor sie ablaufen
import time, threading, random
# Probabilistic early expiry (XFetch algorithm)
class ProbabilisticCache:
def __init__(self):
self._cache = {}
def get(self, key, ttl, recompute_fn, beta=1.0):
if key in self._cache:
value, expiry, delta = self._cache[key]
# XFetch: decide to refresh early with probability proportional to delta/TTL
remaining = expiry - time.time()
if remaining > 0:
early_refresh_score = delta * beta * (-1) * (remaining / ttl)
if random.random() > (1 - early_refresh_score): # simplified
pass # could trigger async refresh here
return value
# Cache miss or expired
start = time.time()
value = recompute_fn()
delta = time.time() - start # computation time
expiry = time.time() + ttl
self._cache[key] = (value, expiry, delta)
return value
print('XFetch: refresh probabilistically before expiry based on computation cost')
print('High-cost computations => refresh earlier to avoid stampede')
print('Low-cost computations => refresh closer to TTL')CDN-Cache-Invalidierung
Cache-Invalidierung gilt als besonders schwierig: „Es gibt nur zwei schwierige Probleme in der Informatik: Cache-Invalidierung und das Benennen von Dingen.“ Wenn sich Inhalte am Origin ändern, müssen die CDN-Edge-Knoten die neue Version ausliefern. Strategien:
- Ablauf auf TTL-Basis: Inhalte auf natürliche Weise ablaufen lassen (einfach, aber mit einem Zeitraum veralteter Inhalte)
- URL-Versionierung: Den Inhaltshash in die URL aufnehmen (z. B.
main.a3f2b.js); neuer Inhalt = neue URL, daher ist keine Invalidierung erforderlich - CDN-API-Purge: URLs nach dem Deployment explizit über einen API-Aufruf löschen (schnell, erfordert aber die Integration der CDN-API)
# Cache invalidation strategies for CDN/browser
strategies = [
{
'name': 'Long TTL + URL versioning (best for static assets)',
'example': '<script src="/app.a3f2b1c.js"></script>',
'ttl': 'Cache-Control: max-age=31536000 (1 year)',
'how': 'Content hash in filename; new deploy = new URL; old URL cached forever (OK)',
},
{
'name': 'Short TTL (for frequently changing content)',
'example': '/api/v1/config',
'ttl': 'Cache-Control: max-age=60 (1 minute)',
'how': 'Simple; content is at most 60s stale; no invalidation needed',
},
{
'name': 'CDN API purge (for news / social media)',
'example': '/news/breaking-story.html',
'ttl': 'Cache-Control: s-maxage=3600',
'how': 'On publish, call CDN.purge(url); edge serves new version immediately',
},
]
for s in strategies:
print(f'{s["name"]}:')
print(f' Example: {s["example"]}')
print(f' TTL: {s["ttl"]}')
print(f' Strategy: {s["how"]}\n')Architektur: Alles zusammenführen
Eine vollständig skalierte Webanwendungsschicht verwendet alle drei Techniken gemeinsam: Load-Balancing verteilt den Datenverkehr, das CDN übernimmt statische und cachebare API-Anfragen, und Redis speichert dynamische Daten zwischen. Die Datenbank sieht nur Cache-Misses – typischerweise 5–20 % aller Anfragen.
Ein typischer Anfrageablauf für eine leseintensive API: Benutzer → DNS → CDN-Edge (Cache-Hit: sofort ausgeliefert) → CDN-Miss → Load-Balancer → App-Server-Pool → Redis-Cache (Hit: Antwort in 1 ms) → Redis-Miss → Datenbank (10–50 ms) → Antwort wird in Redis und optional im CDN zwischengespeichert → Benutzer. Jede Schicht reduziert die Datenbanklast erheblich.
# Request flow with cache hit rates
request_flow = [
('Browser Cache', '10%', '0ms', 'Browser caches GET responses per Cache-Control'),
('CDN Edge Cache', '60%', '5ms', 'CloudFront/Fastly caches cacheable API responses'),
('Load Balancer', None, '1ms', 'Routes to healthy app server replica'),
('App Server', None, '2ms', 'Business logic, auth check'),
('Redis Cache', '25%', '1ms', 'Caches computed data, hot DB rows'),
('Database Read Replica','5%', '10ms', 'Cache miss: query read replica'),
('Database Primary', '0.1%', '15ms', 'Cache+replica miss: query primary (rare for reads)'),
]
print(f'{'Layer':30s} {'Hit Rate':10s} {'Latency':10s} {'Notes'}')
print('-'*80)
for layer, hit_rate, latency, note in request_flow:
hr = hit_rate if hit_rate else '-'
print(f'{layer:30s} {hr:10s} {latency:10s} {note}')
print('\nResult: DB sees ~5% of requests; Redis sees ~25%; CDN absorbs 60%; browser 10%')Interviewtipps: Caching und Load-Balancing
Wenn Sie in einem Systemdesign-Interview über Caching sprechen, sollten Sie immer auf folgende Punkte eingehen: was zwischengespeichert werden soll (häufig verwendete Daten, aufwendige Berechnungen), wo zwischengespeichert werden soll (Browser, CDN, Anwendung, Datenbankabfrage-Cache), wann der Cache invalidiert werden soll (beim Schreiben, nach Ablauf der TTL oder durch eine Aktualisierung im Hintergrund) und welche Konsistenzgarantien akzeptabel sind. Ein Cache führt zu einem Konsistenzfenster – benennen Sie dieses ausdrücklich.
Beim Load-Balancing sollten Sie die Wahl des Algorithmus, Health-Checks, Sticky Sessions (falls erforderlich) und die Möglichkeit der horizontalen Skalierung zustandsloser App-Server erwähnen. Wenn die Anwendung Zustände verwaltet (WebSocket-Verbindungen, Sitzungen), sollten Sie erläutern, wie diese Zustände über mehrere Replikate hinweg verwaltet werden.
# Caching design questions checklist
cache_checklist = [
'What data to cache? (read-heavy, expensive to compute, rarely updated)',
'Cache layer: client-side / CDN / app-level / DB query cache?',
'Cache invalidation strategy: TTL / event-driven / write-through?',
'Eviction policy: LRU / LFU?',
'Cache key design: ensure uniqueness, avoid hotspots',
'Consistency window: acceptable staleness in seconds?',
'Cache stampede prevention: mutex / stale-while-revalidate?',
'Cache capacity: how much RAM needed for hot set?',
]
lb_checklist = [
'Layer 4 vs Layer 7: routing by IP or by URL/headers?',
'Algorithm: round robin / least-connections / consistent hashing?',
'Health checks: interval, failure threshold, recovery',
'Session stickiness: needed? Use cookie-based affinity or external session store',
'Auto-scaling: scale out when CPU > 70%; scale in when < 30%',
]
print('Cache checklist:')
for item in cache_checklist: print(f' [ ] {item}')
print('\nLoad balancer checklist:')
for item in lb_checklist: print(f' [ ] {item}')Schnelltest
Testen Sie Ihr Verständnis der Konzepte aus Data Structures & Algorithms — Coding Interview Prep in dieser Lektion.
Zusammenfassung der Lektion
In dieser Lektion haben Sie gelernt: Caching speichert häufig benötigte Daten in schnellen Speicherschichten (Redis, CDN), um den Großteil der Lesezugriffe abzufangen und die Datenbanklast zu reduzieren, Cache-Aside ist das gängigste Muster – bei einem Miss werden die Daten aus der Datenbank geladen, bei einem Hit sofort zurückgegeben und beim Schreiben aus dem Cache gelöscht und Consistent Hashing verteilt Cache-Schlüssel auf Knoten, sodass beim Hinzufügen oder Entfernen von Knoten nur etwa 1/n der Schlüssel statt aller Schlüssel neu zugeordnet wird. Als Nächstes entwerfen wir einen Rate Limiter und einen Twitter-Feed, um alle Konzepte des Systemdesigns in durchgängigen Aufgaben anzuwenden.
Häufig gestellte Fragen
Ist die Lektion „Caching, CDNs und Load Balancing“ kostenlos?
Ja — der vollständige Text von „Caching, CDNs und Load Balancing“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Coding Interview Prep-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Coding Interview Prep-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Caching, CDNs und Load Balancing“?
Fügen Sie Redis-Caching-Schichten hinzu, übertragen Sie statische Assets an ein CDN und verteilen Sie den Datenverkehr mit Round-Robin- und Consistent-Hashing-Load-Balancern auf Replikate. Du übst Coding Interview Prep mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um Coding Interview Prep zu starten?
Keine Vorkenntnisse erforderlich. Coding Interview Prep auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 3 von 4.
Wie lange dauert die Lektion „Caching, CDNs und Load Balancing“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser Coding Interview Prep-Lektion Code schreiben und ausführen?
Ja. Jede Coding Interview Prep-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Das Framework für System-Design-Interviews
- Skalierbare Datenspeicherung: SQL vs. NoSQL
- Caching, CDNs und Load Balancing
- Rate Limiter und Twitter-Feed entwerfen