Ein absurd erfolgreicher Umbau
(marginalia.nu)- Marginalia Search hat durch jüngste Änderungen den RAM-Bedarf halbiert und den operativen Aufwand stark reduziert, bei Upgrades die Suchmaschine herunterfahren zu müssen
- Die Störung trat nach dem neuesten Release beim Umschalten des Indexes zutage, und die Struktur, bei Upgrades die URL-Datenbank zu löschen, führte zu lang andauernden Offline-Zeiten
- Der URL-Speicher wechselte von riesigen MariaDB-Tabellen und -Indizes zu einer einzelnen SQLite-Datenbank und einer vom Prozess erzeugten 64-Bit-URL-ID-Struktur
- Die Erzeugung des inversen Indexes verzichtet auf ein Lexikon für dichte Term-IDs und auf groß angelegte zufällige Schreibvorgänge; stattdessen werden kleine Preindizes erstellt und anschließend zusammengeführt
- Die neue Struktur ermöglicht Dateisicherungen, mit denen sich selbst nach schlechten Deployments innerhalb weniger Stunden wiederherstellen lässt, und macht es einfacher, Verarbeitungsergebnisse großer, selten veränderter Daten wie Wikipedia wiederzuverwenden
In einer Woche veränderte Betriebsbedingungen für die Suchmaschine
- Marginalia Search hat mit einer Woche Arbeit an Code mehrere langfristige Probleme gleichzeitig reduziert
- Der RAM-Bedarf der Suchmaschine wurde halbiert
- Die Notwendigkeit entfällt, das System während Upgrades offline zu nehmen
- Die harte Obergrenze für die Anzahl indexierbarer Dokumente wurde entfernt
- Die weiche Obergrenze für die Anzahl der im Korpus enthaltenen Keywords wurde vervierfacht
- Ein langfristiges Ziel war es, das System auch auf leistungsschwacher Hardware betreiben zu können, und diese Änderung ist ein noch größerer Fortschritt als frühere Verbesserungen, bei denen sich selbst eine Entwicklungsmaschine mit 32 GB RAM großzügig anfühlte
Die bisherige Upgrade-Struktur verschärfte die Störung
- Marginalia Search war wegen Problemen im neuesten Release fast eine Woche offline
- Die unmittelbare Ursache war eine Kette vergleichsweise kleiner Skalierungsprobleme, aber ein Prozess, der normalerweise 1 Tag und bei Problemen 2 bis 3 Tage dauert, musste mehrfach neu gestartet werden
- Die meisten Neustarts wurden durch RAM-Mangel verursacht
- Für Nutzer fiel die Störung besonders stark auf, weil das System während der Index-Umschaltung offline genommen werden musste
- In der alten Struktur wurde die URL-Datenbank bei Upgrades gelöscht, und das schnelle Einfügen und Aktualisieren von Zeilen in einer Tabelle mit fast 1 Milliarde Zeilen wurde zum Engpass
- Der Prozess zum Laden neuer Daten verbrauchte viel RAM
- Er ließ sich schwer gleichzeitig mit dem Index-Service betreiben, der auf ähnliche Weise RAM nutzte
- Der Primärschlüssel war eine 32-Bit-Ganzzahl, was eine Grenze knapp unter 2 Milliarden bedeutete
Neuentwurf der URL-Datenbank
- Die URL-Datenbank war eine alte Komponente der Suchmaschine, und frühe Designentscheidungen passten nicht mehr zur heutigen Systemstruktur
- Im Kern standen zwei Tabellen, die nur beim Laden eines neuen Indexes beschrieben und nur über Primärschlüssel-Lookups gelesen wurden
- Die URL-Tabelle weist jeder URL eine eindeutige numerische ID zu
- Die Tabelle PAGE_DATA enthält Informationen wie Titel und Beschreibungen indexierter Links
- Es gab einen zusätzlichen Index, um die Eindeutigkeit von Pfaden innerhalb einer Domain zu gewährleisten, doch dafür war er sehr groß
- Die neue Struktur führt beide Tabellen in einer einzelnen SQLite-Datenbank zusammen, überträgt die Erzeugung eindeutiger IDs an den Loader-Prozess und behandelt Listen nicht zu indexierender URLs in einer separaten Tabelle
- Die Kombination aus MariaDB und SQLite ist keine konventionelle Wahl, aber die beiden Speicher erfüllen unterschiedliche Anforderungen
- Das System benötigt ein relativ kleines dauerhaftes Weltmodell
- Gleichzeitig muss sich ein Zustand im Terabyte-Bereich wie Plug-and-Play austauschen lassen
- Die Tabellen DOCUMENT und DOMAIN sind datenbankübergreifend lose miteinander verknüpft, echte Joins sind aber nicht nötig
- Im Katastrophenfall lassen sich wichtige Teile der DOMAIN-Tabelle aus der DOCUMENT-Tabelle rekonstruieren
64-Bit-URL-IDs ohne Index erzeugen
- Die bisherige URL-Tabelle verwendete 32-Bit-IDs, und die Nähe zum Integer-Overflow war ein bekanntes Skalierungsproblem
- Ein Grund für das Löschen der URL-Datenbank war auch, ein Überlaufen der ID-Spalte zu verhindern
- Ein einfacher Zähler kann wiederholte URLs nicht sauber behandeln, und auch ein Ansatz, alle bereits gesehenen URLs im Speicher zu halten, sollte vermieden werden
- Im neuen Verfahren vergibt nicht mehr die Datenbank die IDs; stattdessen werden 64-Bit-IDs direkt aufgebaut
- Die unteren 26 Bits dienen als Sequenznummer
- Die Bits 26 bis 56 dienen als Dokument-ID
- Die obersten Bits sind für Tricks bei der Index-Sortierung reserviert
- Dieses Layout erlaubt etwa 67 Millionen Dokumente pro Domain
- Das entspricht ungefähr dem Zehnfachen der englischen Wikipedia
- Es erlaubt bis zu 2 Milliarden Domains
- Das ist fast das 100-Fache der Zahl an Domains, auf die Marginalia irgendwo im Web je Verweise gesehen hat
- Die Verantwortung für Datenkonsistenz wandert damit von der Datenbank in den Datenerzeugungsprozess, wo ihre Durchsetzung deutlich weniger kostet
Wie der URL-Umbau die Speicherlast senkte
- Durch das Entfernen riesiger Indizes und großer Tabellen mit gemischten heißen und kalten URL-Daten benötigt der MariaDB-Server keine 36 GB RAM mehr
- Die in MariaDB verbleibenden heißen Daten liegen höchstens im Bereich einiger hundert MB, und es dürfte nicht nötig sein, dem Server mehr als 2 GB RAM zuzuweisen
- Auch die SQLite-Daten brauchen zwar einen Index für den Primärschlüssel, aber der tatsächlich heiße Teil dieses Indexes dürfte unter 1 GB bleiben
- Das Volumen der zu indexierenden Daten sinkt um eine Größenordnung
- Indiziert wird ein einzelner 64-Bit-long
- Es gibt keine zusätzlichen Unique-Constraints
- Da die Loader-Ausgabe nun aus Dateibündeln besteht, lassen sich automatische Backups leicht erstellen
- Selbst bei einem schlechten Deployment ist eine Wiederherstellung in wenigen Stunden möglich statt wie zuvor in fast einer Woche
Engpass bei der Erzeugung des inversen Indexes
- Der Loader erzeugt die URL-Datenbank und ein dokumentzentriertes Journal vom Typ
(document, words[]), für die Suche wird aber ein in die Form(word, documents[])transponierter Index benötigt - Das bisherige Verfahren nutzte ein Lexikon, das Keyword-Strings auf dichte Term-IDs abbildet
- Das erste Wort erhält ID 0, das nächste Wort ID 1
- Strings werden per 64-Bit-Hash auf
longabgebildet - Die Struktur ist praktisch eine Open-Hashmap von long nach int
- Diese Struktur läuft bei wachsender Größe schnell voll
- Java erlaubt keine Arrays mit mehr als 2 Milliarden Einträgen
- Die verwendete Implementierung stößt schon bei etwa 1 Milliarde an ihre Grenze
- Bei 1 Milliarde Einträgen werden 12 GB RAM benötigt
- Der produktive Index-Service nutzt 60 GB RAM, größtenteils als Off-Heap-Speicher
- Problematisch war auch, dass sich bei mehr als 32 GB On-Heap-Größe CompressedOOPs schwer nutzen lassen
Schreibverstärkung im bisherigen Algorithmus für den inversen Index
- Der bisherige Algorithmus ermittelt die größte Wort-ID, erzeugt ein
counts-Array dieser Größe, berechnet daraus die Dokumentpositionen je Wort und schreibt die Dokumentdaten in speicherabgebildete Dateien - Dieses Verfahren benötigte das Lexikon, weil es voraussetzt, dass der Wertebereich der Wort-IDs dicht gefüllt ist
- Der größere Engpass war jedoch, fast 1 TB Daten in nahezu vollständig zufälliger Reihenfolge in speicherabgebildete Dateien zu schreiben
- SSDs kommen mit Zufallszugriffen beim Lesen gut zurecht, bei kleinen Schreibvorgängen entsteht jedoch das Problem der Schreibverstärkung (write amplification)
- Um ein einzelnes Byte auf der SSD zu aktualisieren, muss die gesamte Seite gelöscht und neu geschrieben werden
- Es gibt Caches, die benachbarte Schreibvorgänge zusammenfassen, aber bei zufälligen Schreibvorgängen im Terabyte-Bereich hilft das kaum
- Beim Schreiben von 1 TB schreibt das Laufwerk effektiv etwa 0,5 PB
- Als vorläufige Abmilderung gab es
RandomWriteFunnel- Schreibvorgänge werden zunächst auf kleine Datei-Buckets verteilt
- Anschließend werden die großen Dateien sequentiell geschrieben
- Das ist besser als die ursprüngliche Methode, aber immer noch sehr langsam
Kleine Preindizes erzeugen und zusammenführen
- Die neue Erzeugung des inversen Indexes erstellt mehrere kleine Indizes, deren Daten vollständig in den Speicher passen, und führt sie anschließend zusammen
- Das Zusammenführen sortierter Listen ist schnell und eignet sich auch für mechanische Festplatten
- In diesem Verfahren ist kein Lexikon mehr nötig
- Als Term-ID kann direkt der 64-Bit-String-Hash verwendet werden
- Anfangs ergaben sich Probleme durch komprimierte Eingabedaten und die Art der Index-Zusammenführung
- Statt schnell nur Teile komprimierter Eingaben zu lesen, werden mehrere kleine Dateien erzeugt und einzeln gelesen
- Statt die Indizes nach ihrer Erstellung zu mergen, werden die Daten vor der Index-Erstellung zusammengeführt
- Die neue Abstraktion
preindexbesteht aus einem Wort-ID-Array, einem Count-Array und einem Dokumentdaten-Array - Weil die Eingaben klein sind, können die meisten Preindizes im RAM aufgebaut, während der Erstellung auf die Platte geschrieben und in einem späteren Merge-Schritt zu einem finalen Preindex zusammengeführt werden
- Der finale Preindex wird anschließend wie bisher in einen inversen Index umgewandelt, indem eine
words-Tabelle und statische B-Baum-Indizes für jeden Dokumentblock hinzugefügt werden; dieser Schritt ist vergleichsweise schnell
Veränderungen bei Betrieb und Wiederverwendung von Daten
- Durch den Wegfall des Lexikons sank der RAM-Bedarf, und die neue Erzeugung des inversen Indexes ist schneller als der bisherige Algorithmus
- Das bisherige Lexikon erzeugte für jedes verarbeitete Datenpaket einen eigenen Dialekt
- Dasselbe Wort konnte in einem Lauf eine bestimmte ID und in einem anderen Lauf eine andere ID erhalten
- Deshalb mussten bisher alle Daten gleichzeitig verarbeitet werden
- Mit dem Verzicht auf das Lexikon lassen sich unterschiedliche Datenpakete zusammenführen
- Verarbeitungsergebnisse großer und selten veränderter Daten wie Wikipedia lassen sich wiederverwenden und mit Indexdaten kombinieren, die sich häufiger ändern
- Diese Änderungen haben fast alle bekannten Skalierungsprobleme und operativen Unannehmlichkeiten reduziert und schaffen Raum, weitere Nebeneffekte zu untersuchen
2 Kommentare
Ein absurd großer Erfolg
Marginalia - unabhängig betriebene DIY-Suchmaschine
Hacker-News-Kommentare
Es fühlt sich wirklich gut an, solche Erfolgsgeschichten von Projekten zu sehen, die in die genau entgegengesetzte Richtung wie der Rest der Welt gehen.
Ich würde das gern Engildification nennen; davon sollte es mehr geben.
Mir gefiel auch der Beitrag „Sleeping At Night“, und zusammen mit dem jüngsten Beitrag „Lie Still in Bed“ wirkt das wie eine sehr einfache Option, um Probleme mit Schlaftraining anzugehen.
Die Suchmaschine Marginalia wird vom Autor seit Kurzem in Vollzeit betrieben, und soweit ich weiß besteht das Team aus einer Person; damit ist sie vom Enshittification-Risiko am weitesten entfernt. Gerade in dieser Größenordnung entstehen vielmehr Juwelen, und Kreativität, Einfallsreichtum und Vision können glänzen.
Dieser Kommentar wurde gesponsert von der Gruppe „Kündige deinen Schreibtischjob und mach dich selbstständig“.
Der Ressourcenverbrauch wurde halbiert, auf einem kleineren Bildschirm ist man produktiver als zuvor, und nachts schläft man wie ein Stein.
Link für Interessierte: https://www.marginalia.nu/log/86-sleep/
Zur Info: Die kostenpflichtige Suchmaschine Kagi, die man vielleicht auch schon auf Hacker News gesehen hat, nutzt Marginalia als eine ihrer Datenquellen.
https://help.kagi.com/kagi/search-details/search-sources.htm...
Wenn man die Linse „non-commercial“ verwendet, werden solche Ergebnisse zusammen mit Treffern aus Kagis eigenem Index und einigen unabhängigen Quellen bevorzugt.
Beim Lesen dieses Beitrags frage ich mich, ob Menschen ohne künstliche Beschränkungen vielleicht kaum in der Lage sind, Herausragendes zu schaffen.
Der Grund, warum Marginalia absurd effizient ist, liegt darin, dass Victor die Ausführungshardware und die Speicherkapazität bewusst begrenzt.
Wenn man einfach 32 GiB dazusteckt, läuft es zwar eine Weile weiter, aber das ineffiziente Design bleibt; später kann dasselbe Problem in einem noch komplexeren Zustand wieder auftreten und dann schwerer zu beheben sein.
Falls diese Annahme stimmt, erklärt das auch, warum heutige Software aufgebläht, langsam und voller Bugs ist. Da einzelne Software für sich genommen kaum je an Grenzen stößt, können die einzelnen Teile trotz Ineffizienz mit aktuellen M2 Pro und Verbindungen im GiB-Bereich die Problemstellen immer weiter vor sich herschieben.
Das Fazit könnte sein, dass es langfristig für einen selbst und für alle besser ist, sich selbst zu beschränken.
In vielen Anwendungen ergibt es keinen Sinn, so viel Zeit in kleine Optimierungen zu stecken. Wenn man einem Server für weniger als 50 Dollar 32 GiB RAM hinzufügen kann, ist die Entscheidung aus Business-Sicht offensichtlich, verglichen mit der Alternative, mehr als 40 Entwicklerstunden zu mindestens 20 Dollar pro Stunde aufzuwenden. Außerdem war die Website eine ganze Woche lang offline, und allein das kann die meisten Unternehmen töten.
Techniker lieben solche Tiefenbohrungen und würden gern selbst winzige Codeabschnitte über Jahre hinweg mikrooptimieren, aber mit endlosem Yak Shaving verdient man kein Geld. Wenn Code nur auf einer sehr kleinen Zahl von Maschinen läuft, lohnt es sich normalerweise nicht. Solche Optimierungen enden oft auch in Code, der schwerer zu warten ist.
Die Software-Bloat bei Apps, die heute auf Nutzergeräten laufen, kommt meiner Ansicht nach oft aus der Diskrepanz zwischen Entwicklergeräten und Nutzergeräten. Entwickler brauchen für ihre Arbeit leistungsstarke Workstations und testen darauf auch grundsätzlich, während Nutzer die App auf Geräten ausführen, die vor fünf Jahren untere bis mittlere Klasse waren.
Einem Manager zu verkaufen: „Wir können 150 MB Speicher sparen“, ist schwer; „Wenn wir 150 MB Speicher sparen, steigt die App-Performance für 10 % der Nutzer von furchtbar auf gerade noch brauchbar“, lässt sich verkaufen.
Früher hat man Software tatsächlich so gebaut. Deshalb konnten Betriebssysteme mit fast allem, was man heute erwartet, selbst auf Maschinen wie einem Pentium 1 gut laufen. Heute dagegen ruckelt das Scrollen von Webseiten sogar auf Smartphones, die auf jeder Achse buchstäblich etwa tausendmal mehr Ressourcen haben. Das Word-95-Team stieß ständig auf Grenzen und Performance-Kompromisse, und das Ergebnis funktionierte eindeutig – oder eben nicht.
Hätte man einfach mehr RAM hinzugefügt, wäre man weiterhin in einem schlechteren Design gefangen gewesen und hätte bald wieder noch mehr RAM kaufen müssen. Das Erstaunliche an dieser Änderung ist nicht nur, dass der Ressourcenverbrauch sank, sondern dass das System durch den freien RAM, der den Disk-Cache vergrößerte, leistungsfähiger und schneller wurde.
[1] Zum Beispiel läuft das hier auf einem einzelnen Pi und ist, weil es keine Updates erlaubt, viel schneller als die laufende Wikipedia: https://encyclopedia.marginalia.nu/article/Hacker_News
Deshalb fühlten sich alte Computer vielleicht gut an, und alte Spiele konnten deshalb so großartig sein.
Es könnte mit der Komplexität der Systeme zusammenhängen, mit denen wir umgehen. Wenn Ressourcen wie RAM, physischer Raum, Nahrung, Materialien, Zeit und Geld begrenzt sind, muss man planen, wie man sie nutzt, und man ist gezwungen, klug zu werden.
Hat man praktisch unbegrenzte Ressourcen, kann man bauen, wie man will, muss sich aber weniger um den Endzustand kümmern. Man fängt einfach an und schaut, sobald es läuft.
Ich bin kein echter Gamer, aber die menschliche Fähigkeit, so viele Emotionen, Abenteuer und Spielstunden in ein paar KB/MB große ROM-Cartridges zu packen, erstaunt mich immer wieder. Das Ocarina Of Time ROM ist nur ungefähr so groß wie acht aktuelle Fotos, die ich mit dem iPhone aufgenommen habe.
Ein Raspberry Pi 2 schafft mehr als 4 Milliarden Dhrystone-Instruktionen pro Sekunde, ein Pi 4 mehr als 10 Milliarden.
Natürlich war SABRE aus der Mitte der 1970er nach heutigen Maßstäben für ein Kernsystem einer Airline ziemlich grundlegend, aber theoretisch könnte man auf einem einzelnen Pi 2 gleichzeitig vereinfachte Systeme von über 100 Airlines betreiben.
Moderne Programme sind von Optimierung sehr weit entfernt. Abgesehen von Fällen mit vielen mathematischen Operationen sind Verbesserungen um den Faktor 1000 oder 10000 möglich.
Ich glaube, Microsoft hat mit diesem Problem massiv zu kämpfen. Selbst ein 3000-Dollar-Laptop von vor fünf Jahren wird inakzeptabel langsam, wenn gleichzeitig ein Teams-Call, ein paar Office-Anwendungen und ein Browser mit 30 Tabs laufen.
Wenn man sie jeweils separat testet, ist alles in Ordnung, solange nur eines läuft, aber echte Menschen arbeiten nicht so.
Künstliche Grenzen, bei denen man die Laufzeit auf klar definierter Hardware begrenzt und diese Grenze nur nach einer ausdrücklichen Entscheidung anhebt, könnten eine Lösung sein.
Allerdings schreibe ich nur Business-Software, bei der der Performance-Aspekt letztlich auf „Mach keine dummen Dinge mit der Datenbank, und um den Rest kümmere dich nicht, weil Kunden dafür kein Geld bezahlen“ hinausläuft, also liege ich vielleicht völlig daneben.
Ich freue mich immer, wenn es Updates zu marginalia.nu gibt. Es ist ein wertvoller Nutzer auf dieser Site, und ich hoffe, es kommen weiterhin Beiträge.
Danke. Ich arbeite an einem Suchmaschinen-Hobbyprojekt und habe aus irgendeinem Grund immer nur nach Varianten von „Magnolia“ gesucht. Der Name Marginalia bleibt bei mir schlecht hängen. Im Moment versuche ich, Searx zu verstehen.
Ich frage mich, ob Marginalia Such-Zeitfilter wie „letzter Tag“ oder „letzte Woche“ unterstützt. Wenn man sich die speziellen Keywords ansieht, scheinen die Suchparameter nur jahresbasiert zu sein.
year>2005 (beta) Das Dokument wurde dem Anschein nach nach 2005 veröffentlicht.
year=2005 (beta) Das Dokument wurde dem Anschein nach im Jahr 2005 veröffentlicht.
year<2005 (beta) Das Dokument wurde dem Anschein nach vor 2005 veröffentlicht.
Auch der Jahresfilter ist ziemlich grob, und es ist sehr schwierig, die Datumsangaben der meisten Webseiten korrekt zu erfassen.
Ich frage mich, ob die Aussage „jedes Mal, wenn eine SSD ein einzelnes Byte irgendwo auf der Platte aktualisiert, muss sie eine ganze Page löschen und neu schreiben“ tatsächlich auf SSDs zutrifft.
Bei rohem Flash ist das nicht so. Wenn man einen „leeren“ All-ones-Wert überschreibt oder nur 1 in 0 ändert, geht das. Schreiben ist um Größenordnungen langsamer als Lesen, aber wiederum um Größenordnungen schneller als Löschen, und im Verschleißbudget zählen nur Löschvorgänge.
Wenn SSD-Controller das nicht ausnutzen, klingt das nach einem Eigentor. Allerdings kann ich mir vorstellen, dass es nicht geht, wenn das Innere tatsächlich log-strukturiert ist.
Früher habe ich Treiber für mehrere populäre Flash-Chips geschrieben, und alle Chips, mit denen ich damals zu tun hatte, nutzten sowohl fürs Lesen als auch fürs Schreiben ausschließlich I/O in Pages fester Größe.
Eine SSD ist ein Verbund aus Chips, daher würde ich erwarten, dass auch jeder Chip in der SSD nur Page-I/O fester Größe unterstützt.
Normalerweise verwenden Controller verschiedene Techniken wie Overprovisioning, Buffering und Neuordnung von Schreibvorgängen, um solche Worst-Case-Muster zu vermeiden, aber es gibt Grenzen.
Die kleinste Einheit, die man auf einer SSD schreiben kann, ist eine Page, und die kleinste Einheit, die man löschen kann, ist ein Block, der aus mehreren Pages besteht.
Wenn ein Schreibvorgang also nur 1 Byte innerhalb einer Page betrifft, kann die SSD nicht nur dieses Byte löschen. Das heißt aber nicht unbedingt, dass sie den ganzen Block löschen muss.
Eine SSD kann eine Art „Read-Modify-Write“-Operation ausführen: Sie liest die gesamte Page mit dem zu ändernden Byte in den Cache-Buffer der SSD, ändert nur dieses Byte im Page-Cache, löscht einen neuen freien Block, schreibt die geänderte Page aus dem Cache in den neuen Block und aktualisiert die FTL-Mapping-Tabelle, sodass sie auf die aktualisierte Page im neuen Block zeigt.
Daher muss die Page neu geschrieben werden, selbst wenn sich nur 1 Byte ändert. Das Löschen des gesamten Blocks lässt sich jedoch vermeiden, bis viele Pages darin geändert wurden.
Bei Schreibvorgängen wandern Pages innerhalb des physischen Geräts. Das Laufwerk selbst pflegt eine Karte darüber, welche Adresse wofür verwendet wird, wie ihr Gesundheitszustand ist usw. Es ist eine Art Sparse-Speicherverfahren.
Es gibt auch Befehle wie TRIM sowie regelmäßige Wartung und Garbage Collection.
Wenn man auf ein Laufwerk schreibt, das in der Praxis nicht voll ist, wird eine Page gesucht, in die die Daten kommen, geprüft, ob dort Daten liegen, bei Bedarf Read/Modify/Write ausgeführt, entschieden, wohin die Daten geschrieben werden, und dann geschrieben. Wegen Wear Leveling ist es sehr wahrscheinlich, dass sie nicht an ihren ursprünglichen Ort zurückkehren.
Es stimmt, dass der Controller der Performance wegen deutlich komplexere Schritte ausführt. Deshalb funktioniert ein neues, leeres Laufwerk eine Zeit lang besser und kann tatsächlich langsamer werden als ein altes, volles Laufwerk ohne Reserve-Pages.
Als Kontext: Ich war Lead Engineer für einen speichergemappten Flash-Beschleuniger mit Cache-Kohärenz. Er ermöglichte es, Laufwerke im Linux-User-Space sehr effizient zu mappen, aber am Ende haben wir uns eine Zeit lang dem „einfachen“ Programmiermodell gebeugt, bei dem es wie einfach eine weitere Festplatte aussieht.
Das erinnert mich daran, dass mein Chef bei Mojeek vermutlich einen sehr ähnlichen Weg gegangen ist. Dieser Artikel deckt sich stark mit früheren Gesprächen.
Mojeek startete 2004, und das Grundgerüst wurde größtenteils von einem einzelnen Entwickler gebaut, der fast die gesamte Information-Retrieval- und Infrastruktur-Schicht aufgebaut hat.
Finanzielle und Hardware-Beschränkungen, Entscheidungen zu 32-Bit- gegenüber 64-Bit-IDs, Sharding und Update-Geschwindigkeit klingen alle sehr vertraut.
Es erinnert mich auch an Googles frühere „Google dance“. Damals wurden Suchergebnisse einmal im Monat aktualisiert, heute schwanken sie täglich. Das alles ist Evolution, und es ist schön zu sehen, dass Marginalia eine weitere Perspektive auf das Web jenseits von Big Tech bietet.
War gut zu lesen.
Viele halten Optimierung für tiefschwarze Magie, aber meistens ist sie eher einfacher als gewöhnliches Bugfixing. Man muss übermäßigen Ressourcenverbrauch einfach genauso wie einen Bug behandeln.
Ich glaube, dass die meisten leicht reproduzierbaren Bugs keine Magie brauchen, um behoben zu werden. Wenn man einen Bug anstochern kann, kann man ihn meist einordnen, und auch seltene Designfehler werden in der Regel schnell sichtbar, sobald sie reproduzierbar sind.
Software, deren Performance niemand kritisch betrachtet hat, ist wie Software mit Hunderten leicht reproduzierbarer Bugs, die niemand debuggt hat. Man kann ziemlich lange einen nach dem anderen abtragen, bevor man auf etwas wirklich Schwieriges stößt.
Diese Haltung ist meiner Ansicht nach eher ein Überbleibsel aus der Zeit, als Leute Sprungzieladressen so abstimmten, dass sie genau in dem Moment am Trommelkopf ankamen, in dem die CPU den Befehl brauchte. Als Ressourcen extrem knapp waren, alles per Hand in Assembly geschrieben wurde und globale Speicherstellen je nach Programmschritt unterschiedliche Bedeutungen hatten, hatten bereits sehr kluge Leute die Performance kritisch geprüft, sodass man finden musste, was sie übersehen hatten. In modernem Code ist das selten.
Wenn sich der Bug reproduzieren lässt, indem man Anfrage X an Service Y schickt, verkleinert man den Testfall immer weiter, bis man den Schuldigen findet.
Optimierung ist oft ein Architekturproblem. Es gibt Fälle, in denen man einen Buffer wiederverwenden könnte, aber kopiert; solche Dinge sind jedoch schnell ausgeschöpft, und der Profiler liefert die nötigen Informationen.
Große Performance-Gewinne entstehen oft dadurch, dass man den gesamten Datenfluss verändert und erhebliche Teile entfernt, sodass der Code die notwendige Arbeit in möglichst wenigen Schritten erledigt.
Ich frage mich, warum SQLite gewählt wurde und nicht ein Key-Value-Store. Wenn man nur per ID lesen will und andere Spalten offenbar nicht benötigt, scheint eine relationale Datenbank unnötig.
SQLite hat den Vorteil, eine einzelne Datei zu sein. Dadurch kann man schöne Dinge tun, etwa sie kopieren oder teilen.
Schön, weil es ein weiteres Beispiel dafür ist, dass Beschränkungen Innovation hervorbringen. Innovation findet man häufiger in Grenzen als im Überfluss.