5.3 Chord Arbeit: Stoica u. a. (2001). Konstruktion: Zwei unabhängige Hashfunktionen: • Knoten: hV : {1, . . . , n} → {0, . . . , 2m − 1}; • Schlüssel: hK : {1, . . . , k } → {0, . . . , 2m − 1}. Hashwerte im Folgenden IDs. IDs auf Kreis angeordnet, rechne modulo 2m . 506 Beispiel: n = 3, k = 4, m = 3. Schlüssel: 5, 6 Knoten 0 7 Schlüssel 1 6 2 Schlüssel: 1 3 5 4 Schlüssel: 3 Speichere Schlüssel in im Uhrzeigersinn nächsten Knoten. 507 Im Folgenden der Einfachheit halber Analysen für vollständig zufällige Hashfunktionen. Dann: X1 := hV (1), . . . , Xn := hV (n) und Y1 := hK (1), . . . , Yk := hK (k ) sind unabhängige, in {0, . . . , 2m − 1} gleichverteilte ZVs. Tatsächlich: • Für Knoten- und Schlüssel-Hashfunktionen jeweils beschränkte Unabhängigkeit reicht. • In der Praxis feste Hashfunktion, z. B. SHA-1 (und Daumen drücken). Annahme: X1 , . . . , Xn paarweise verschieden (m. h. W. erfüllt, falls m hinreichend groß). 508 Abstand: Miss Entfernung von Start zu Ziel auf Kreis im Uhrzeigersinn. Beispiel: Für {0, . . . , 7}: Von 1 nach 7: Abstand 6. Von 7 nach 1: Abstand 2. (Keine Metrik, da nicht symmetrisch.) Definition 5.1: Für Knoten u, v sei von Knoten-ID Xu aus im Uhrzeigersinn auf dem Kreis Xv erste andere Knoten-ID. Dann v Nachfolger von u und u Vorgänger von v . 509 Knotenintervalle: Für Knoten v = 1, . . . , n: I(v ) := (Xu , Xv ] = {Xu + 1, Xu + 2, . . . , Xv }, u Vorgänger von v . (Rechne dabei modulo 2m .) Sonderfall: Falls n = 1, dann I(v ) = {0, . . . , 2m − 1}. Knoten v genau für die Speicherung der in I(v ) ggf. vorkommenden Schlüssel zuständig. 510 Lastverteilung (Balance): Alle Knoten bekommen ungefähr gleich viele“ Schlüssel ab. ” Ideal wären k /n pro Knoten, zeige O(log n · k /n). Lemma 5.2: Sei c > 0. Mit Wahrscheinlichkeit mindestens 1 − n−c gilt: Für alle Knoten v = 1, . . . , n ist 4 2m ′ , c′ = (c + 1). |I(v )| ≤ c log n n log e 511 Beweis: Sei Xv = x fest und ℓ = c ′ log n 2m . n Falls |I(v )| > ℓ, dann enthält insbesondere das Intervall I := (x − ℓ, x] = {x − ℓ + 1, x − ℓ + 2, . . . , x} keine anderen Knoten-IDs außer x. Z := Anzahl Knoten w 6 = v mit Xw ∈ I. Wegen Gleichverteilung der IDs über {0, . . . , 2m − 1}: n≥2 n/2 n−1 2m 1 EZ = m · |I| ≥ m · c ′ log n = c ′ log n. 2 2 n 2 Chernoff: Pr{Z = 0} ≤ Pr{Z − EZ ≤ −EZ } ≤ e−EZ /2 ≤e − 21 · log4 e (c+1) log n /2 = n−(c+1) . Die Behauptung folgt nun mit der Vereinigungsschranke. 512 Folgerung 5.3: Sei c > 0. Mit Wahrscheinlichkeit mindestens 1 − n−c über die Wahl von Knoten- und Schlüssel-IDs gilt: Jeder Knoten speichert höchstens O(log n · k /n) Schlüssel. Beweis wieder mit Chernoff-Argument, nur jetzt über Schlüssel-IDs (selbst überlegen). Schärfere Schranke O((1 + γ ) · k /n), γ > 0, mit 2(log n) IDs für jeden Knoten, Schlüssel einsammeln für alle IDs. (Feinere Streuung der IDs.) 513 Operationen: Datenstruktur: Für jeden aktiven Knoten v : v .succ = Nachfolger von v , v .pred = Vorgänger von v . L OOKUP(v, x), v irgendein aktiver Knoten, x Schlüssel: Rufe F IND S UCCESSOR(v , Yx ) auf, Yx Schlüssel-ID von x. F IND S UCCESSOR(v, y), y ∈ 0, . . . , 2m − 1 : if y ∈ (Xv .pred , Xv ] then return v ; while y ∈ / (Xv , Xv .succ ] do v := v .succ; / / Xv .succ näher an Yx als Xv od; # Botschaften: O(n). 514 J OIN(x), x aktiver Knoten: • Bestimme ID Xv für neuen Knoten v . • Bestimme Vorgänger und Nachfolger von v : v .succ := w := F IND S UCCESSOR (x, Xv ); v .pred := u := w .pred. • Korrigiere Vorgänger-/Nachfolger-Info für andere Knoten: u.succ := v ; w .pred := v . • Verlagere alle Schlüssel in (Xu , Xv ], die w hat, nach v . u w Xu Xw 515 J OIN(x), x aktiver Knoten: • Bestimme ID Xv für neuen Knoten v . • Bestimme Vorgänger und Nachfolger von v : v .succ := w := F IND S UCCESSOR (x, Xv ); v .pred := u := w .pred. # Botschaften: O(n). • Korrigiere Vorgänger-/Nachfolger-Info für andere Knoten: u.succ := v ; w .pred := v . • Verlagere alle Schlüssel in (Xu , Xv ], die w hat, nach v . Verlagerung von O(log n · k /n) Schlüsseln. u v w Xu Xv Xw 515 L EAVE(v), v zu löschender Knoten: • Verlagere Schlüssel von v zum Nachfolger v .succ. Wieder Verlagerung von O(log n · k /n) Schlüsseln. • Aktualisiere Vorgänger- bzw. Nachfolger-Infos anderer Knoten: (v .pred).succ := v .succ; (v .succ).pred := v .pred. Fazit: Balance okay, aber L OOKUP nicht schnell genug. Abhilfe: Knoten brauchen mehr lokale Routing-Info. → Zwischenknoten in exponentiell wachsenden Abständen. 516 Finger-Informationen: Für jeden Knoten v und i = 0, . . . , m − 1: v .finger[i]: Knoten, der zur im Uhrzeigersinn von Xv + 2i aus nächsten ID gehört. u 0 7 1 6 2 5 3 4 w v Knoten u: i: Xu + 2i : finger[i]: 0 1 v 1 2 v 2 4 u Knoten v : i: Xv + 2i : finger[i]: 0 3 w 1 4 u 2 6 u Knoten w: i: Xw +2i : finger[i]: 0 4 u 1 5 u 2 7 u 517 L OOKUP mit Finger-Infos: Neue Datenstruktur für Knoten v : Finger-Tabelle, v .succ = v .finger[0], v .pred. F IND S UCCESSOR(s, y): Sei s Startknoten und t Vorgängerknoten von y . Finde Finger von s, der Ziel t am nächsten (Vereinfachung: Identifiziere IDs ↔ Knoten): s.finger[i] s ··· t s.finger[m−2] s.finger[m−1] y Rekursiv weiter mit s := s.finger[i]. Knoten t wird irgendwann gefunden, da auch direkte Nachfolger in Finger-Tabelle. 518 Lemma 5.4: Sei c > 0. Für beliebige s und y gilt mit Wahrscheinlichkeit mindestens 1 − n−c über die Wahl der Knoten-IDs: Die Anzahl von Knoten, die F IND S UCCESSOR(s, y ) kontaktieren muss, ist O(log n) (bez. n → ∞). Beweis: Notation wie auf der letzten Folie. Sei s 6 = t (sonst fertig). Ersetzen von s durch f := s.finger[i], zum Ziel t nächster Finger von s, halbiert mindestens die Entfernung: f = s.finger[i] s s + 2i t s + 2i+1 d(s, t) = d(s, f ) + d(f , t), d(s, f ) ≥ 2i , d(f , t) ≤ 2i ⇒ d(s, t)/d(f , t) = d(s, f )/d(f , t) + 1 ≥ 2. 519 Abstands-Halbierung ⇒ Nach log n Schritten ist Abstand höchstens 2m /n. Z := Anzahl Knoten-IDs in diesem Intervall, dann ist n 2m EZ = m · = 1. 2 n Chernoff ⇒ Für geeignete Konstante c ′ = c ′ (c) > 0 gilt: Pr{Z ≥ c ′ log n} ≤ Pr{Z − EZ ≥ c ′ log n − 1} ≤ n−c . Also auf dem letzten Wegabschnitt“ noch O(log n) Knoten, ” diese schlimmstenfalls trivial in Einzelschritten überbrücken. Damit auch insgesamt O(log n) Schritte und ebensoviele Anfragen an Knoten. 520 Folgerung 5.5: Sei c > 0. Für jede L OOKUP-Operation ist mit Wskt. mindestens 1 − n−c die benötigte Anzahl Botschaften O(log n). 521 J OIN mit Finger-Infos: J OIN(x), x bereits aktiver Knoten: Sei v neuer Knoten mit ID Xv . • Initialisiere Finger-Tabelle von v . • Aktualisiere Finger-Tabelle von allen Knoten, die v als Finger haben könnten. • Verlagere Schlüssel aus (Xv .pred , Xv ] nach v (wie gehabt). 522 I NIT F INGER TABLE(x, v), x aktiver Knoten, v neu: for i = 0, . . . , m − 1: v .finger[i] := F IND S UCCESSOR x, Xv + 2i . od. Zusätzlicher Trick: Berechne und speichere im ersten Schritt nur verschiedene Finger: Falls Xv + 2i+1 ≤ v .finger[i], dann v .finger[i + 1] = v .finger[i]. 523 U PDATE F INGER TABLES(v ), v neuer Knoten: for i = 0, . . . , m − 1: p := F IND S UCCESSOR v , Xv − 2i .pred; while Xv ∈ (Xp , Xp.finger[i] ) do p.finger[i] := v ; p := p.pred; od od. Korrektheit: • Alle Knoten u, die v als i-ten Finger haben können, haben ID vor/inklusive Xv − 2i . • Knoten p ist letzter mit ID vor/inklusive Xv − 2i . • Alle weitere Knoten, die selben i-ten Finger wie p haben, werden in While-Schleife angepasst. 524 Im Folgenden: Nur O log2 n andere Finger-Tabellen müssen angepasst werden. → # Botschaften O log2 n (Letzteres ohne Beweis). Lemma 5.6: Sei c > 0. Dann hat ein Knoten v mit Wskt. mindestens 1 − n−c höchstens (c + 1) log n verschiedene Finger. Beweis: Zeige, dass m. h. W. die Finger j = 0, 1, . . . , i := m − (c + 1) log n alle gleich sind. Es gilt: v .finger[0] = · · · = v .finger[i] ⇔ Intervall [Xv + 20 , Xv + 2i enthält keine Knoten-ID. 525 Erinnerung: i = m − (c + 1) log n. Z := Anzahl Knoten-IDs in Intervall [Xv + 20 , Xv + 2i , dann: EZ ≤ n · 2i = n−c . m 2 Markoff: Pr{Z ≥ 1} ≤ Pr{Z ≥ nc · EZ } ≤ n−c . Also hat Knoten mit Wskt. mindestens 1 − n−c höchstens (c + 1) log n Finger. 526 Lemma 5.7: Sei c > 0. Ein Knoten v ist mit Wskt. mindestens 1 − 2n−c Finger für höchstens O log2 n andere Knoten. Beweis: Sei p der Vorgänger von v (Funktion der ZVs X1 , . . . , Xn ). Es ist v i-ter Finger für Knoten u genau dann, wenn Xu + 2i ∈ (Xp , Xv ]. Behauptung 1: Pr Xu + 2i ∈ (Xp , Xv ] ≤ (5 log n)/n. Behauptung 2: Mit Wskt. mindestens 1 − n−c gibt es nur O(log n) Knoten, die v als i-ten Finger haben. Lemma 5.6, c ← c + 1: Mit Wskt. mindestens 1 − n−c hat jeder Knoten höchstens O(log n) verschiedene Finger. Damit insgesamt die Behauptung. 527 Beweis von Behauptung 1: Zeige für festes Xu = x: Pr x ∈ (Xp , Xv ] ≤ (5 log n)/n. Betrachte die Situation für festes Xv = y : Es ist x ∈ (Xp , y ] genau dann, wenn es kein w ∈ / {u, v } gibt, sodass Xw ∈ [x, y ]. Sei |y − x| ≥ ℓ := 4 log n 2m (rechne dabei modulo 2m ) und n Z := Anzahl alle Xw , w ∈ / {u, v }, in [x, y ]. Dann ist 2m n≥4 n−2 ≥ 2 log n · 4 log n EZ = 2m n und Pr{x ∈ (Xp , y ]} = Pr{Z = 0} ≤ e−EZ /2 ≤ 1 . n 528 Es gilt: Pr{x ∈ (Xp , y ]} = X y = X y : y−x≥ℓ ≤ Pr{Xv = y } · Pr{x ∈ (Xp , y ]} | {z } =1/2m X 1 · Pr{x ∈ (X , y ]} + p {z } 2m | ≤1/n 1 1 4 log n 1 +ℓ· m = + n 2 n n y : y−x<ℓ n≥2 ≤ 1 · Pr{x ∈ (Xp , y ]} {z } 2m | ≤1 5 log n . n (Behauptung 1) 529 Beweis von Behauptung 2: Zu zeigen: Mit Wskt. mindestens 1 − n−c gibt es nur O(log n) Knoten, die v als i-ten Finger haben. Nach Behauptung 1 gilt: 5 log n Pr Xu + 2i ∈ (Xp , Xv ] ≤ . n Erwartungswert für Anzahl Knoten u, die v als i-ten Finger haben, ist höchstens 5 log n. Mit Chernoff-Argument O log n solche Knoten mit Wahrscheinlichkeit mindestens 1 − n−c . (Behauptung 2) 530 L EAVE analog zu J OIN (nur keine Initialisierung der Finger-Tabelle). Insgesamt: Anzahl Schlüssel pro Knoten: Lokaler Platz für Routing-Infos: J OIN/L EAVE: L OOKUP: O(log n · k /n) O(log n) O log2 n Botschaften O(log n) Botschaften Kritik an der Analyse: Behandelt Operationen einzeln, will aber kleine Fehlschlagswskt. für Folgen von Operationen. Idee für aufgebohrte Version: Benötigte Eigenschaften der IDs vorab sicherstellen, z. B. kleine Abstände, nur damit arbeiten. Haarigere Beweise. 531 Was noch fehlt (siehe Originalarbeit): • Gleichzeitig mehrere J OINs/L EAVEs: Neue Algorithmen nötigt, während Stabilisierungsphase evtl. nur direkte Vorgänger und Nachfolger der Knoten. • Absicherung gegen Datenverlust: Speichere jeden Schlüssel r -fach, zusätzlich in den (r − 1)-nächsten Knoten nach ursprünglichem. Kann bei Ausfall zu Nachfolger weitergehen. • Absicherung gegen Verlust der Netzstruktur: Speichere für jeden Knoten zusätzlich die ersten s Nachfolger. Für beide letzte Fällen können Ausfälle von benachbarten Knoten wegen Hashing als unabhängig angesehen werden. 532