- One Million Checkboxes, veröffentlicht am 26. Juni 2024, war eine Website, auf der alle dieselben 1 Million Checkboxen in Echtzeit bedienen konnten, und verarbeitete bis zur Abschaltung zwei Wochen später mehr als 650 Millionen Klicks
- Der Zustand selbst umfasste nur 1 Million Bits, also 125 KB, doch die anfängliche Architektur aus nginx, Flask/gunicorn und Redis pubsub stieß unter dem unerwarteten Traffic schnell an ihre Grenzen
- Als durch Hacker News, Reddit, Mastodon und Twitter Zehntausende Nutzer gleichzeitig kamen, traten nacheinander Redis-Verbindungsengpässe, explodierende Bandbreitennutzung, fehlende Eingabevalidierung und das Anwenden veralteter Updates zutage
- Die Reaktion konzentrierte sich auf schnell umsetzbare Maßnahmen wie das Skalieren von Servern und Redis, das Batchen von Updates, die Verkleinerung des Übertragungsformats, ein auf Linux
tcbasierendes 250-Mbit/s-Bandbreitenlimit und Skripte zum Neustart von Prozessen - Später wurde das Backend auf Go umgestellt und stabilisiert; am Ende behandelte ein Redis-Lua-Skript die Logik zum Einfrieren der Checkboxen atomar, bevor die Website am 11. Juli 2024 um 4:35 PM Eastern Time beendet wurde
Website und anfängliches Design
- One Million Checkboxes (OMCB) ist eine am 26. Juni 2024 gestartete Website, die 1 Million globale Checkboxen bereitstellt
- Wenn ein Nutzer eine Checkbox aktiviert oder deaktiviert, wird dies sofort auf den Bildschirmen aller Nutzer angezeigt
- Die Entwicklung dauerte 2 Tage, und es wurden höchstens ein paar Hundert Nutzer erwartet
- Die tatsächliche Resonanz war viel größer als erwartet
- Innerhalb weniger Stunden nach dem Start kamen Zehntausende Nutzer und bedienten Millionen von Checkboxen
- Der Traffic kam von Hacker News, /r/InternetIsBeautiful, Mastodon und Twitter
- Einige Tage später wurde die Seite auch in der Washington Post und der New York Times vorgestellt
- Ein Teil der frühen Logs vom ersten Tag ist nicht erhalten geblieben
- Anfangs wurden nur die neuesten 1 Million Log-Einträge pro Tag gespeichert
- Ab dem zweiten Tag begann sich der Betrieb zu stabilisieren, und an diesem Tag wurden mehr als 50 Millionen Checkboxen betätigt
- Vor der Abschaltung überschritt die kumulierte Zahl der Klicks 650 Millionen
Die ursprüngliche Redis-zentrierte Architektur
- Der Zustand der Checkboxen wurde als 1 Million Bits dargestellt
- Aktivierte Felder waren
1, deaktivierte Felder0 - Die Gesamtgröße des Zustands betrug 125 KB
- Der Client speicherte ein Bitset und referenzierte es beim Rendern
- Aktivierte Felder waren
- Der Client war so aufgebaut, dass DOM-Last vermieden wurde
- Es wurden nicht alle 1 Million Elemente in das DOM eingefügt
- Mit react-window wurden nur die aktuell sichtbaren Checkboxen plus ein kleiner Puffer gerendert
- Die Serverkonfiguration war einfach und auf horizontale Skalierung ausgelegt
- nginx lieferte statische Inhalte aus und leitete API-Anfragen sowie WebSocket-Verbindungen an Flask-Server weiter
- Die Flask-Server liefen als zwei gunicorn-Instanzen
- Redis übernahm die Speicherung des Checkbox-Zustands und fungierte als Message Queue
- Auch die Nutzung von Redis war direkt gehalten
- Mit Redis-Bit-Primitive wurden die Zustände einzelner Checkboxen geändert
- Wenn der Client ein Klick-Event sendete, drehte Flask das entsprechende Bit in Redis um und schrieb das Event in pubsub
- Die beiden Flask-Server lasen pubsub und informierten die mit ihnen verbundenen Clients über Änderungen
- Ein vollständiger Snapshot des Gesamtzustands diente dazu, verpasste Updates auszugleichen
- Er war dafür gedacht, Clients zu synchronisieren, die im Hintergrund-Tab Updates verpasst hatten
- Die ursprüngliche Implementierung sendete alle 30 Sekunden den vollständigen Zustand
Prinzipien bei der Skalierung
- Die Kosten mussten eine berechenbare Obergrenze haben
- Ein unbegrenztes Auto-Scaling mit explodierenden Kosten sollte vermieden werden
- Bei Last über die Erwartungen hinaus wurde bewusst in Kauf genommen, dass das System bricht
- Es wurde angenommen, dass die Popularität nur kurz anhalten würde
- Statt ausgereifter Lösungen, die Tage oder Wochen brauchen, wurden Gegenmaßnahmen bevorzugt, die sich innerhalb weniger Stunden umsetzen ließen
- Dabei entstehende technische Schulden wurden akzeptiert
- Bei der Technologiewahl wurde Einfachheit und direkte Betreibbarkeit bevorzugt
- Es wurde eine Konfiguration gewählt, bei der man sich direkt auf die Server einloggen, Befehle ausführen und debuggen konnte
- Verwendet wurden vor allem Abhängigkeiten, die sich selbst betreiben und debuggen ließen
- Das Kernerlebnis der Website war die globale Synchronisierung
- Unabhängig davon, wohin man sich bewegte, sollte man sofort Veränderungen sehen können
- Es wurde nicht in Richtung eines Modells skaliert, das nur die vom Nutzer betrachteten Checkboxen sendet
Erster Tag: zusätzliche Server und Redis als Flaschenhals
- Innerhalb von 30 Minuten nach dem Start schoss die Last nach oben, und die Seite lief zwar noch, war aber nicht für langes Durchhalten gerüstet
- Die offensichtlichste Verbesserung war, weitere Server hinzuzufügen
- nginx konnte problemlos als Reverse Proxy zu Flask-Instanzen auf anderen VMs weiterleiten, und der Zustand lag ohnehin bereits in Redis
- Der zweite Server wurde gegen 12:30 PM hinzugefügt und erreichte kurz darauf ebenfalls 100 % Last
- Zunächst wurde angenommen, dass ein oder zwei zusätzliche Server ausreichen würden
- In der Praxis wuchs der Traffic jedoch im gleichen Maß wie die Skalierung
- Die Seite stand auf Platz 1 bei Hacker News, und die Twitter-Aktivität nahm stark zu
- Die Verbindungen zwischen Flask-Servern und Redis wurden zum Engpass
- Es gab keinen Redis-Connection-Pool, und Redis war nahe daran, keine Verbindungen mehr annehmen zu können
- Danach wurde auf gebündelte Updates umgestellt
- Kompatibilität mit bestehenden Clients wurde nicht berücksichtigt, weil davon ausgegangen wurde, dass Nutzer ohnehin neu laden würden
- Es wurde auch ein Redis-Connection-Pool ergänzt, der in der Kombination aus gunicorn und Flask aber nicht sauber funktionierte
- Dennoch half er offenbar dabei, die Zahl der Redis-Verbindungen zu reduzieren
- Statt das Problem tiefer zu untersuchen, erfolgte später der Wechsel zu Go
- Das Rate Limit für die Session-Erzeugung wurde entfernt
- Der Zustand des Rate Limits wurde in Redis gespeichert, und Redis litt bereits unter erschöpften Verbindungen
- Das Problem war nicht ein Ansturm neuer Sessions, sondern dass einzelne Sessions sehr viele Daten sendeten
- Kurzfristig war das riskant, wurde aber als vertretbare Maßnahme eingeschätzt
- Auch die Redis-Instanz wurde aufgerüstet
- Genutzt wurde Digital Ocean Managed Redis
- Statt einer kleinen Instanz mit 1 shared CPU und 2 GB RAM wurde auf eine Instanz mit 4 dedizierten CPUs und 32 GB RAM skaliert
- Das Resize dauerte etwa 30 Minuten
Bandbreitenprobleme und Reduktion des Datenvolumens
- Anfangs wurde den Bandbreitenkosten nicht genug Aufmerksamkeit geschenkt
- Digital Ocean berechnet nach Überschreiten des freien Volumens $0.01 pro GB
- Aus einem früheren Projekt standen 1 TB freie Bandbreite zur Verfügung, und es wurde erwartet, dass OMCB davon keinen großen Anteil verbrauchen würde
- Vollständige Snapshots des Zustands konnten die Bandbreite schnell aufbrauchen
- 1 Million Bits entsprechen 1 Mbit
- Wenn dies alle 30 Sekunden an 1.000 Nutzer gesendet wird, ergibt das ungefähr 2 GB pro Minute und 120 GB pro Stunde
- In dieser Rechnung sind inkrementelle Updates nicht einmal berücksichtigt
- Die Prüfung der Bandbreite und das Setzen einer Kostenobergrenze erfolgten auf der nginx-Maschine
- Mit
ip -s link show dev eth0wurde die Anzahl gesendeter Bytes geprüft - Da nur ein einzelner nginx-Reverse-Proxy verwendet wurde, ließ sich die Quelle der Bandbreite leicht eingrenzen
- Mit
- Die Reduktion des Datenvolumens verlief auf zwei Wegen
- Die Frequenz vollständiger Snapshots wurde gesenkt
- Das Format der inkrementellen Updates wurde verkleinert
- Das Batch-Update-Format wurde stark komprimiert
- Das ursprüngliche Format war eine Liste von Dicts wie
{ "index": 123, "value": true } - Das endgültige Format war ein Paar aus einem Array wahrer Indizes und einem Array falscher Indizes in der Form
[[123, 125], [124]] - Dieses Verfahren war 5-mal kürzer als die ursprüngliche Implementierung
- Das ursprüngliche Format war eine Liste von Dicts wie
- Mit Linux
tcwurde ein hartes Limit gesetzt, um außer Kontrolle geratene Kosten zu verhindern- Der Traffic auf dem öffentlichen Interface
eth0wurde auf 250 Mbit/s begrenzt - Das entspricht etwa 2 GB pro Minute und knapp unter 3 TB pro Tag
- Bei $0.01 pro GB wurde so verhindert, dass die Kosten über Nacht unkontrollierbar anwuchsen
- Der Traffic auf dem öffentlichen Interface
Zweiter Tag: fehlende Eingabevalidierung und Redis-Replica
- Am nächsten Morgen war die Website down, und die Ursache war fehlende Eingabevalidierung
- Es wurde nicht verhindert, dass Checkboxen mit Indizes über 1 Million angesprochen wurden
- Jemand manipulierte Checkboxen mit Indizes im Hunderte-Millionen-Bereich
- Dadurch sah es so aus, als sei die Zahl aktivierter Felder bereits bei 1 Million angekommen und die Website deshalb beendet
- Auch die Redis-Daten wuchsen unnötig an
- Zwischen dem millionsten Bit und dem hundertmillionsten Bit wurden Millionen von
0-Werten eingefügt - Die an die Clients gesendeten Daten wurden dadurch 100-mal größer
- Zwischen dem millionsten Bit und dem hundertmillionsten Bit wurden Millionen von
- Die Wiederherstellung verlief schnell
- nginx wurde angehalten
- Nur die ersten 1 Million Bits des bestehenden Bitsets wurden in ein neues Bitset kopiert
- Das alte Bitset wurde zur Fehlersuche aufbewahrt
- Danach wurde der Code auf das neue Bitset umgestellt und Eingabevalidierung ergänzt
- Auch das initiale Laden der Seite war langsamer geworden
- Redis stand unter hoher Last, und durch einen Bug im Connection Pool wurden zusätzlich zu viele Verbindungen erzeugt
- Statt den Connection-Pool-Fehler zu debuggen, wurde eine Redis-Replica ergänzt, um Last und Verbindungen vom Primary zu verteilen
- Die private IP der Replica musste manuell gefunden werden
- Wie von Digital Ocean beschrieben, funktionierte der
replica--Präfix bei öffentlichem DNS, aber nicht bei privatem DNS - Die öffentliche IP zu nutzen, wurde wegen der Route über das öffentliche Internet und möglicher Bandbreitenkosten vermieden
- Durch Verbindungsversuche zu Adressen in der Nähe der privaten IPs des Primary und der anderen Server wurde beim dritten oder vierten Versuch die private IP der Replica gefunden
- Diese IP wurde anschließend hart im Code hinterlegt
- Wie von Digital Ocean beschrieben, funktionierte der
Neustart von Prozessen und Korrektur veralteter Updates
- Die Flask-Prozesse stürzten weiterhin ab, offenbar wegen fehlender Redis-Verbindungen
- Statt ausführlichem Debugging wurde ein bash-Skript geschrieben, das die Zahl laufender Flask-Prozesse prüfte
- Wenn weniger als 3 Prozesse liefen, startete es die systemd-Unit neu
- Das Skript wurde in die crontab eingetragen
- Auch die nginx-Konfiguration wurde angepasst
- Ausgefallene Server wurden vorübergehend aus der Rotation genommen
- Nach dieser Änderung stabilisierte sich die Website
- Bei der Synchronisierung des Client-Zustands gab es einen Bug mit veralteten Updates
- Der Client erhielt sowohl inkrementelle Updates als auch vollständige Snapshots des Gesamtzustands
- Da beide keine Timestamps hatten, konnte nach Erhalt eines neuen Snapshots noch ein älteres inkrementelles Update angewendet werden
- Dadurch konnten Nutzer bis zum nächsten vollständigen Snapshot einen komplett falschen Zustand sehen
- Als Abschwächung wurde eine timestamp-basierte Lösung ergänzt
- Vollständige Snapshots erhielten einen Timestamp
- Jedes in Redis pubsub geschriebene Update bekam ebenfalls einen Timestamp
- An die Clients gesendete Batches enthielten den höchsten Timestamp der darin enthaltenen inkrementellen Updates
- Der Client wurde so geändert, dass er Batches verwirft, die älter sind als der letzte vollständige Snapshot
- Diese Lösung war nicht perfekt
- Wenn sich in einem Batch auch nur ein neues Update befand, konnte er angewendet werden, selbst wenn die meisten enthaltenen Updates veraltet waren
- Trotzdem war die Situation deutlich besser als zuvor
Neuschreiben in Go und Stabilisierung
- Am nächsten Morgen lief die Website noch, und der Fokus verlagerte sich anschließend auf ein Rewrite des Backends
- Es war bereits eine E-Mail von der Washington Post eingetroffen
- Parallel wurde auch darüber nachgedacht, wie die Website beendet werden sollte
- Der Plan für die Beendigung bestand darin, dass aktivierte Felder einfrieren, wenn sie nicht schnell genug wieder deaktiviert werden
- Diese Änderung hätte einen Aktivitätsschub und zusätzliche Serverarbeit auslösen können
- Es war unklar, ob die bestehende Flask-basierte Struktur das tragen könnte
- Zusammen mit dem Freund Eliot wurde das Backend in Go neu geschrieben
- Von Sonntag 2 PM bis 2 AM wurden Implementierung und der vollständige Port des Backends diskutiert und umgesetzt
- Die Struktur wurde dabei weitgehend unverändert übernommen
- Hindernisse waren etwa die Suche nach einer Go-
socketio-Bibliothek, die das neueste Protokoll unterstützt
- Die Leistungsverbesserung war enorm
- Das System skalierte so gut, dass Bots übermäßig viel Traffic hineindrücken konnten
- Dadurch wurde ein besseres Rate Limit notwendig
- In der Nacht zum Sonntag gab es auch einen DDoS-Angriff
- Darauf wurde reagiert, indem die Website hinter Cloudflare gestellt und die nginx-Konfiguration leicht angepasst wurde
Logik zum Beenden der Website
- Nach dem Go-Rewrite lief die Website stabil
- In der folgenden Woche wurden Interviews und das öffentliche Interesse bewältigt
- Danach begann die Arbeit an der Abschaltlogik
- Die Methode zur Beendigung bestand im Einfrieren der Checkboxen
- Wenn aktivierte Felder nicht schnell wieder deaktiviert wurden, gingen sie in einen eingefrorenen Zustand über
- Mit der Zeit fror die gesamte Website vollständig ein
- In Redis wurde zusätzlicher Zustand gespeichert
- Es wurde eine Hashtable hinzugefügt, die für jede Checkbox den Zeitpunkt der letzten Aktivierung speicherte
- Für die Übertragung an Clients wäre das ein zu großer Zustand gewesen, für die Speicherung in Redis war es jedoch unproblematisch
- Auch ein
time_to_freeze-Wert wurde gespeichert
- Ob eine Deaktivierung erlaubt ist, wurde beim Uncheck entschieden
- Wenn
now - last_checked > time_to_freeze, wurde nicht deaktiviert - Stattdessen wurde
frozen_bitsetaktualisiert, um zu markieren, dass diese Checkbox eingefroren ist - Das
frozen_bitsetwurde auf dieselbe Weise wie der Checked-Zustand an die Clients verteilt - Der Client deaktivierte Checkboxen, deren Frozen-Bit gesetzt war
- Wenn
- Es wurde zusätzlich ein separater Job eingeführt, damit Checkboxen auch ohne Uncheck einfrieren
- Dieser suchte periodisch nach Bits, die eigentlich eingefroren sein sollten, aber noch nicht als solche markiert waren, und fror sie ein
- Die zugehörige Logik wurde in ein Redis-Lua-Skript gelegt und atomar ausgeführt
- Dadurch ließen sich Race Conditions leicht vermeiden
- Die Änderung zur Beendigung wurde 2 Wochen und 1 Tag nach dem Start ausgerollt
- Am 11. Juli 2024 um 4:35 PM Eastern Time wurde Box 491915 aktiviert, womit die Website abgeschlossen war
Kosten und Erkenntnisse
- Der Betrieb der Website kostete etwa $850
- Die Spenden kamen diesem Betrag ziemlich nahe
- Insgesamt wurde das nicht als großer Verlust bewertet
- Mit der Wahl von Redis und nginx war man zufrieden
- Redis und nginx wurden als sehr nützliche Technologien bewertet
- Durch den Eigenbetrieb waren Debugging und Anpassungen einfach
- Etwas unpraktisch war nur, dass die Managed-Redis-Instanz nicht vollständig kontrolliert werden konnte
- Die Entscheidung, von Anfang an nicht langfristig für massives Scale zu planen, wurde positiv bewertet
- Es gilt als schwer vorherzusagen, was im Internet erfolgreich wird
- Hätte man von Beginn an mehrere Wochen über Skalierung nachgedacht, wäre die Seite womöglich nie gestartet worden
- Dass viele Nutzer kamen, half auch bei der Motivation zur Wartung und beim Setzen von Prioritäten
- Es zeigte sich außerdem Nachfrage nach begrenzter anonymer Interaktion
- Menschen interessieren sich für Websites, auf denen sie mit Fremden auf eingeschränkte Weise interagieren können
- Das stärkte das Vertrauen, weiterhin solche Arten von Websites zu bauen
1 Kommentare
Hacker-News-Kommentare
Das war ein sehr lehrreicher Artikel, auch wegen des historischen Wissens über verteilte Systeme
Abgesehen vom Speicherplatz scheint er fast jede Art von Unterbrechung und Ausfallpunkt erlebt zu haben, und es war gut, den Lösungsweg mitverfolgen zu können
Ich wusste nicht, dass Redis Lua unterstützt, aber nachdem ich das gesehen habe, möchte ich es als alternativen State-Store ausprobieren
Bandbreite ist einer meiner größten Kritikpunkte an Cloud-Diensten. Es gibt kein hartes Limit, das Überschreitungen bei der Abrechnung verhindert
logrotate-Konfiguration war kaputt, sodass die Platte fast voll lief, und weil box-check-Logs nach Redis geschickt wurden, musste ich einen Mechanismus bauen, der alte Logs auf die Festplatte auslagert, damit Redis nicht abstürztBeides war aber kein großes Problem, und ein Projekt zu haben, bei dem Speicherplatz kein nennenswertes Problem war, fand ich ziemlich erstaunlich. Für mich persönlich war das eine neue Erfahrung
Bandbreite war wirklich unerquicklich. Ich war ungefähr zwei Tage lang ständig angespannt, habe die gesendeten Bytes der NIC beobachtet und meine Rechnungen immer wieder neu gemacht, und dass es keinen harten Cap gab, war beängstigend. Dabei ist Digital Ocean preislich noch ziemlich vernünftig
Ich habe beliebte Serverless-Dienste nicht benutzt, aber mein Verständnis ist, dass die Bandbreitenkosten dort ziemlich heftig ausfallen
Und Lua in Redis ist wirklich mächtig: Wenn man einen kleinen Performanceverlust in Kauf nehmen kann, lassen sich viele schwierige, von Race Conditions geprägte Probleme überspringen, und es hat Spaß gemacht, damit zu arbeiten
Großartiger Artikel, und auch die Website ist etwas, zu dem man gratulieren kann
Aber persönlich finde ich, dass genau dieser geschriebene Artikel der Teil ist, auf den man am stolzesten sein sollte
Ich denke, der Kern ist die Stelle „Es war die richtige Entscheidung, die Website in zwei Tagen zu bauen, ohne sich groß um Skalierbarkeit zu kümmern“
Gerade Ingenieure am Anfang ihrer Laufbahn sollten das lernen. Skalierbarkeit ist kein Problem, bis sie eines ist
Und wenn es so weit ist, ist das eher ein gutes Problem und oft nicht so schwer zu beheben, wie man denkt
Ich habe viele Systeme gesehen, in denen Microservices zur „offensichtlichen Wahl“ wurden, nicht wegen Skalierung oder Teamtrennung, sondern einfach, weil die Entwickler Lust darauf hatten
Solche Systeme zu skalieren, ist wirklich unerquicklich
Verwandter aktueller Artikel: One Million Checkboxes - https://news.ycombinator.com/item?id=40800869 - Juni 2024, 305 Kommentare
Solche Projekte machen Spaß
Ich habe vor etwa sechs Jahren Pixmap auf Android veröffentlicht, eine kleine kollaborative Pixel-Editor-App, die größere Raster wie 1024x1024 unterstützt hat
Es gab eine Queue, die jedes Ereignis auf ein PNG-Bild angewendet hat, und beim Verbinden lud der Client zuerst das initiale PNG, danach erhielt jedes Pixel-Zeichnen-Ereignis nur noch ein kleines einzelnes Objekt
So konnte man beim initialen Laden Bildkompression nutzen, und die nachfolgenden Änderungsmengen waren sehr klein. Außerdem wurden alle Ereignisse im Log gespeichert, sodass man das Bild auch „zurückspulen“ konnte [0]
[0] 22mb: https://blog.winricklabs.com/images/pixmap-rewind-demo.gif
Deshalb experimentiere ich gerade mit einer Canvas, die sich über API-Aufrufe adressieren lässt
https://x.com/RussTheMagic/status/1816749136487588311
Guter Artikel. Mich würde interessieren, wie viel es am Ende gekostet hat
Die Gesamtkosten lagen bei ungefähr 850 Dollar, und wurden durch Spenden fast ausgeglichen
Nach dem Umzug auf Go habe ich den Fehler gemacht, die Infrastruktur nicht sauber herunterzufahren, und die zusätzlich gestartete zweite Redis-Replik hätte ich auch wieder entfernen können. Wenn ich mich auf die Kosten konzentriert hätte, hätte ich sie wahrscheinlich halbieren können
Aber die Spenden deckten die Kosten fast, und ich hatte zu viele andere Dinge zu tun, um mich stark darauf zu konzentrieren
Auch nachdem die Seite beendet war, habe ich die Infrastruktur noch eine Weile für die Aufbereitung von Grafiken und Ähnlichem weiterlaufen lassen, wodurch noch etwas mehr Geld angefallen ist; im Moment bin ich leicht im Minus, aber nicht in großem Ausmaß
Als jemand, der gerade erst Backend lernt, frage ich mich, ob es für dieses Projekt eine einfachere Alternativarchitektur gibt
Es wäre schön, wenn es einen einfacheren Weg gäbe, den Status von 1 Million Bits zu hosten und mit Clients zu synchronisieren. Einige Lösungen im Artikel waren schwer zu verstehen
Die Projekte des Autors sind großartig
Ich hätte die verwendeten Techniken gern ausführlicher erklärt, aber der Artikel war schon so lang, dass ich das Gefühl hatte, nicht noch mehr unterbringen zu können
Wenn du Fragen hast, beantworte ich sie gern
Ehrlich gesagt weiß ich nicht, wie man die Architektur wesentlich einfacher machen könnte. Es gibt sicher Dienste, die man für so etwas verwenden könnte, aber das wäre eher ein Verschieben der Komplexität auf jemand anderen
Am Ende braucht man eine Datenbank, die die angehakten Kästchen verfolgt, eine Möglichkeit, Daten in die Datenbank zu schreiben, eine Möglichkeit, den Clients den aktuellen Zustand mitzuteilen, eine Möglichkeit, den Server zu informieren und den Zustand zu aktualisieren, wenn ein Client ein Kästchen anhakt, eine Möglichkeit, Clients zu benachrichtigen, wenn ein Kästchen an- oder abgehakt wurde, und eine Möglichkeit, nicht ständig 1 Million DOM-Elemente zu rendern
Hier wurde Redis verwendet, um den Check-Status zu speichern, und der Einfachheit halber wurden einfach alle 1 Million Bits direkt gespeichert; an die Clients gingen ebenfalls die gesamten 1 Million Bits. Das war gut, weil die Datenmenge nicht besonders groß war
Flask und WebSocket wurden für Check-Ereignisse und Updates verwendet, wobei sowohl einzelne Box-Updates als auch komplette Updates aller 1 Million Boxen verschickt wurden, und mit
react-windowwurde das Rendering-Problem vermiedenDas übrige nginx-Static-Content- und Reverse-Proxy-Setup diente hauptsächlich dazu, die Skalierung zu erleichtern; auch ohne diese Details kann man es umsetzen und die Seite funktioniert, sie würde dann nur nicht dieselbe Last tragen können
Statt einer Datenbank speichert man die Bitmenge in einer Datei und mappt sie mit
mmap. Statt eines Reverse Proxys könnte die Anwendung HTTP-Anfragen und WebSocket-Verbindungen auch direkt selbst verarbeitenEin paar Webserver mit einem Cache und einer Publish/Subscribe-Queue dahinter
Man hätte auch alles auf einem großen Host komplett im Arbeitsspeicher verarbeiten können, aber wenn der die Nachfrage nicht schafft oder aus irgendeinem Grund ausfällt, steht eben alles still
Außer man nimmt einen nicht skalierbaren Ansatz wie eine globale Liste mit 1 Million Booleschen Werten im selben Prozess wie die Backend-API
(checked, start_x, start_y, end_x, end_y)zusammenfasst. Ist das nicht der offensichtliche WegCool
Ich frage mich, ob der nächste Artikel eine statistische Analyse darüber sein wird, welche Checkboxen am seltensten oder am häufigsten angehakt wurden
Ich erinnere mich, dass ich ziemlich weit nach unten gescrollt habe, eine Checkbox ausgewählt habe und dann etwas traurig war, als sie fast sofort wieder deaktiviert wurde
Davor muss ich aber noch eine weitere Geschichte über die Seite erzählen
Ich frage mich, ob das Spiel noch läuft
Wenn ich auf https://onemillioncheckboxes.com/ gehe, ist nichts angehakt, und in der JS-Konsole sehe ich nur das hier
{"total":0,"totalGold":0,"totalRed":0,"totalGreen":0,"totalPurple":0,"totalOrange":0,"recentlyChecked":false}Als Gegenbeispiel zu einer skalierbaren Implementierung gibt es eine Implementierung von 1 Million Checkboxen in weniger als 1000 Zeichen. Eine Deno-Version
https://gist.github.com/jeff-hykin/4cdebafd8698298d021f103e2...