0%

Wenn der Server Neuigkeiten hat

Die Anzeigetafel, die pollt

Am Quizabend entstehen die Neuigkeiten auf dem Server: Der Punktezähler sendet einen neuen Punktestand, und der Server erfährt ihn als Erster. Das Programm eines Zuschauers kann diese Neuigkeit nicht hören, weil ein Client bei nur dann etwas von einem Server hört, wenn er fragt. Der einfachste Plan ist also, nach einem Zeitplan zu fragen. Alle zwei Sekunden sendet der Zuschauer eine Anfrage nach dem aktuellen Punktestand, und der Server antwortet mit dem, was er gerade hat. Das nennt man Polling.

So sieht das in den ersten zehn Sekunden aus, mit einer Änderung des Punktestands bei 7,5 Sekunden:

seconds   0     2     4     6     8     10
          |     |     |     |     |     |
polls     P     P     P     P     P     P
                                 ^
                                 score changes at 7.5

Der Zuschauer fragt bei 0, 2, 4, 6, 8 und 10 Sekunden ab. Die Abfragen bei 0 bis 6 Sekunden finden nichts Neues. Der Punktestand ändert sich bei 7,5 Sekunden, und niemand erfährt davon, bis die Abfrage bei 8 Sekunden fragt. Diese Änderung erreicht den Zuschauer eine halbe Sekunde zu spät, und die Abfrage bei 10 Sekunden ist wieder leer.

Eine Stunde simulieren

Eine Änderung, eine halbe Sekunde zu spät, sagt wenig. Um zu sehen, was Polling über einen ganzen Abend kostet, simuliert das Programm unten eine Stunde mit zwölf Änderungen des Punktestands zu festen Zeiten, angegeben in Sekunden seit dem Start. Es braucht kein Netzwerk und keine Uhr: Jeder Zeitpunkt ist eine Zahl in einer , also gibt es bei jedem Lauf dasselbe Ergebnis aus. Führe es aus:

Try it

Es gibt polls per hour: 1800, polls that carried news: 12, polls with no news: 1788 und average lateness: 1.0 seconds aus.

Zeile für Zeile: Eine Stunde hat 3600 Sekunden, also ergibt eine Abfrage alle interval Sekunden 3600 // interval Abfragen. Die berechnet, wie lange jede Änderung wartet. Abfragen finden bei ganzen Vielfachen des Intervalls statt, also ist change % interval der Abstand, um den die Änderung nach der letzten Abfrage passiert ist, und interval minus diesen Abstand ist die Wartezeit bis zur nächsten. Die erste Änderung, bei 120,25 Sekunden, liegt 0,25 Sekunden nach der Abfrage bei 120, also wartet sie 1,75 Sekunden auf die Abfrage bei 122. Das äußere % interval deckt eine Änderung ab, die genau auf eine Abfrage fällt: Sie wartet 0 Sekunden, nicht ein ganzes Intervall. total_wait addiert alle Wartezeiten, und die letzte Zeile teilt durch die Anzahl der Änderungen.

Jede Änderung fällt zwischen ein anderes Paar von Abfragen, also bringen zwölf Abfragen Neuigkeiten, je eine Änderung, und jede andere Abfrage kommt leer zurück.

Warum ungefähr das halbe Intervall

Eine Änderung des Punktestands kann zu jedem Zeitpunkt zwischen zwei Abfragen passieren. Direkt nach einer Abfrage wartet sie fast ein ganzes Intervall, direkt vor einer fast gar nicht. Gemittelt über viele Änderungen liegt die Wartezeit bei ungefähr dem halben Intervall: ungefähr eine Sekunde bei einem Intervall von 2 Sekunden.

Die zwölf Zeitpunkte in der Liste wurden so gewählt, dass die Simulation genau diesen Durchschnitt zeigt. Sie kommen paarweise und liegen gleich weit auf beiden Seiten eines Vielfachen von zehn Sekunden, zum Beispiel 120,25 und 419,75, sodass sich bei einem Intervall von 2 oder 10 Sekunden die beiden Wartezeiten eines Paars genau zu einem Intervall addieren. Echte Änderungen des Punktestands kommen dem Durchschnitt erst über einen langen Abend nahe.

Die Simulation lässt außerdem etwas weg. Jede Anfrage und jede Antwort braucht Zeit, um das Netzwerk zu durchqueren, wie du es in Networks with Python abgeschätzt hast, und diese Verzögerung kommt zur Wartezeit auf die nächste Abfrage hinzu. Ein echter Zuschauer ist also etwas später dran, als diese Zahlen sagen, nie früher.

Dasselbe Programm mit einer Abfrage alle 10 Sekunden. Was gibt es aus?

score_changes = [120.25, 419.75, 661.5, 908.5, 1332.75, 1707.25, 2043.5, 2296.5, 2614.25, 2955.75, 3220.75, 3489.25]
interval = 10
polls_per_hour = 3600 // interval
total_wait = 0
for change in score_changes:
    wait = (interval - change % interval) % interval
    total_wait += wait
print(f"polls per hour: {polls_per_hour}")
print(f"polls that carried news: {len(score_changes)}")
print(f"polls with no news: {polls_per_hour - len(score_changes)}")
print(f"average lateness: {total_wait / len(score_changes)} seconds")

Jede Abfrage kostet eine Anfrage

Eine Abfrage ist keine Kleinigkeit. Jede ist eine vollständige HTTP-Anfrage und eine vollständige Antwort auf einer , mit einer Anfragezeile, Headern, einer Statuszeile und weiteren Headern, genau die Bytes, die du in HTTP with Python von Hand geschrieben hast. Selbst die billigste leere Abfrage, eine bedingte Anfrage, die wie im Kapitel über Caching jenes Kurses mit einer 304 ohne Body beantwortet wird, sendet immer noch einen ganzen Anfragekopf und empfängt einen ganzen Antwortkopf. Hier ist eine, gekürzt auf die Zeilen, auf die es hier ankommt:

GET /scores HTTP/1.1
Host: quiz.test
If-None-Match: "v12"

HTTP/1.1 304 Not Modified
ETag: "v12"

Der Server beantwortet fast jede dieser Anfragen mit “nichts Neues”. Bei einem Intervall von 2 Sekunden sendet ein Zuschauer 1.800 Anfragen pro Stunde, um von zwölf Änderungen zu erfahren. Tausend Zuschauer senden 1,8 Millionen.

Du halbierst das Polling-Intervall von 2 Sekunden auf 1 Sekunde. Was passiert?

Warum bringen fast alle Abfragen keine Neuigkeiten?

Polling zwingt zu einer Wahl zwischen Verspätung und verschwendeten Anfragen, und kein Intervall gibt dir beides, eine schnelle Anzeigetafel und einen ruhigen Server. Die nächste Lektion sieht sich zwei Wege an, wie HTTP selbst gedehnt wurde, um es besser zu machen.