-
Gebiet der Erfindung
-
Diese
Erfindung betrifft Rechner und Rechnersysteme und insbesondere ein
Dateisystem, das auf mehreren Rechnern läuft, von denen jeder seine
eigene Instanz eines Betriebssystems hat und zur Freigabe von Daten
mit gemeinsam benutzten Platten, die an ein Netzwerk angeschlossen
sind, verbunden ist, d.h., sie betrifft ein Dateisystem mit gemeinsam
benutzten Platten.
-
Begriffsglossar
-
Zwar
treten bestimmte hier verwendete Begriffe auch in ihrer Bedeutung
laut Wörterbuch
auf, doch dürfte
sich das folgende Glossar mit einigen Begriffen, die sich auf unsere
Erfindung beziehen, als nützlich
erweisen:
-
- – Daten/Dateisystem-Daten:
Hierbei handelt es sich um beliebige Bitfolgen, die nur im Kontext
einer bestimmten Anwendung eine Bedeutung haben.
- – Datei:
Eine benannte Folge von Bits, auf die eine Rechneranwendung zugreifen
kann. Eine Datei hat bestimmte Standardattribute wie zum Beispiel
eine Länge,
einen Änderungszeitpunkt
und einen Zeitpunkt des letzten Zugriffs.
- – Metadaten:
Dies sind die Steuerstrukturen, die von der Dateisystem-Software
erzeugt werden, um die Struktur einer Datei und die Verwendung der
Platten, die das Dateisystem enthalten, zu beschreiben. Bestimmte
Arten von Metadaten, die für
Dateisysteme dieser Art gelten, sind:
- – Verzeichnisse:
Hierbei handelt es sich um Steuerstrukturen, die einer Gruppe von
Daten, die von einem Informationsknoten (Inode) dargestellt werden,
einen Namen zuordnen.
- – Ein
Inode enthält
die Attribute der Datei sowie eine Reihe von Zeigern auf Bereiche
der Platte, die die Daten, die diese Datei bilden, enthalten. Ein
Inode kann durch indirekte Blöcke
ergänzt
werden, die den Inode um zusätzliche
Zeiger ergänzen,
wenn die Datei groß ist.
- – Zuordnungslisten:
Hierbei handelt es sich um Steuerstrukturen, die angeben, ob bestimmte
Bereiche der Platte (oder andere Steuerstrukturen wie zum Beispiel
Inodes) belegt oder verfügbar
sind. Dadurch kann die Software verfügbare Blöcke und Inodes wirksam neuen
Dateien zuweisen.
- – Protokolle:
Hierbei handelt es sich um einen Satz von Aufzeichnungen, der dazu
dient, die anderen Arten von Metadaten im Falle eines Ausfalls synchron
zu halten. Ein Protokoll enthält
einzelne Aufzeichnungen, die zusammenhängende Aktualisierungen an
mehreren Strukturen beschreiben.
- – Dateisystem:
Eine Software-Komponente, die einen festgelegten Satz von Platten
verwaltet, der auf verschiedene Arten Zugriff auf Daten ermöglicht,
wobei diese Arten von der auf Dateidaten bezogenen Reihe der Standards
Xopen und POSIX vorgegeben werden. Der Begriff dient auch zur Beschreibung
der Gruppe von Daten und Metadaten, die in einem bestimmten Satz
von Platten enthalten sind.
- – Dateisystem
mit gemeinsam benutzten Platten: Ein Dateisystem, bei dem mehrere
Rechner ein Dateisystem gemeinsam verwalten, ohne einer einzigen
Einheit die gesamte Verwaltung zu übertragen. Alle Rechner sind
insofern gleichgestellt, als jeder Rechner jede Aufgabe wahrnehmen
kann, die zur Verwaltung der Daten notwendig ist. Bestimmte Aufgaben
können
bei Bedarf bestimmten Rechnern zugewiesen werden.
- – Anschluss
von gemeinsam benutzten Platten: Dies ist ein Verfahren, das dazu
dient, Platten mit mehreren Rechnern mittels eines Protokolls zu
verbinden, das die Platten so erscheinen lässt, als wären sie lokal mit jedem Dateisystem
verbunden. Das genaue Protokoll für den Anschluss an jeden Rechner
ist für
diese Arbeit nicht wichtig, beinhaltet aber verschiedene Formen
des Anschlusses von Platten an ein Netzwerk, des Anschlusses von
umschaltbaren Platten oder des Anschlusses für das Speicher- und Weiterleitungsverfahren
(store and forward). Die wichtigsten Punkte sind, dass es dem Dateisystem
lokal und allen Instanzen des Dateisystems gleich erscheint.
- – Quota
(Quote): Mittels dieser Funktion beschränkt ein Dateisystem die Nutzung
durch einen bestimmten Benutzer oder durch eine benannte Gruppe
von Benutzern in dem Dateisystem. Zum Beispiel kann der Administrator
den Benutzer "john" auf eine Datenmenge
von 100 Megabyte in dem Dateisystem beschränken. Quota ist der Funktionsname,
der in der Unix-Umgebung (Warenzeichen von S.C.O.) verwendet wird.
- – Zugriffssteuerungsliste
(Access Control List): Dies ist ein Dateisystem-Verfahren, mittels
dessen ein Benutzer den Zugriff auf Daten auf Benutzer beschränken kann,
die in einer bestimmten Liste genannt sind.
-
Der Erfindung zugrunde liegender
allgemeiner Stand der Technik
-
Es
besteht Bedarf an der Bereitstellung von Dateidiensten für Rechner
wie zum Beispiel einen MPP-Rechner oder für andere Gruppen von Rechnern,
die Teil eines aus angeschlossenen Rechnern bestehenden Netzwerks
bilden, welche als eine gemeinsame Datenverarbeitungsressource dienen.
-
Wir
haben nun bestimmte "offene" (z.B. Xopen- und
POSIX-)Standards in Bezug auf Dateidaten in einem Dateisystem mit
gemeinsam benutzten Platten, in dem Datenverarbeitungsaufgaben,
die auf verschiedenen Rechnern ausgeführt werden, den Zugriff auf
dieselben Dateidaten in der Weise benötigen, als würden sich
die Daten lokal an dem Rechner befinden, der die Aufgabe ausführt (um
von IBM entwickelte Systeme für verschiedene
Systeme betreiben zu können,
siehe z.B. die
US-Patentschriften
4 274 139 ,
5 202 971 und
5 226 159 ). Wenn mehrere
Rechner Teil eines Netzwerks und mehrere Platten Teil des Netzwerks
sind, besteht ein Bedarf an der Erzeugung eines Dateisystems mit
gemeinsam benutzten Platten, das mit den Standards vereinbar ist
und dennoch keine Änderung
an mehreren Instanzen von Betriebssystemen erforderlich macht, die auf
den Rechnern ausgeführt
werden, ungeachtet dessen, ob es MMP-Rechner oder Rechner in einem
Rechnerverbund sind.
-
Shared
File System (SFS) (siehe die
US-Patentschrift
5 043 876 ) ist ein Begriff, der auf die Systeme S/390 von
IBM angewendet wird, welche unter dem VM von IBM arbeiten, um Daten
unter virtuellen Maschinen freizugeben. Gemeinsam benutzte Dateisysteme
sind auch als Mittel zur Freigabe von Daten bekannt, wie zum Beispiel
die Modelle IMS und GRS von IBM, sie wurden für eine Umgebung mit nur einem
einzigen System entwickelt, und unter MVS wurde GRS in einem Verbund
von Systemen eingesetzt, die den Plattenspeicher gemeinsam benutzen,
und in einem solchen System konnte das GRS kleine Sperrdateien auf
der gemeinsam benutzten Platte zuordnen, um den Zugriff auf Datensätze zu serialisieren.
MVS muss den Zugriff auf das Inhaltsverzeichnis auf Platten oder
auf den Katalog serialisieren, ungeachtet der RESERVES-Operationen,
die das Betriebssystem durchführen
muss. Dies verursacht einen ziemlichen Systemmehraufwand.
-
Die
Datenbank DB2 von IBM ist für
die Freigabe von Daten in einer Multiple-Virtual-Storage-(MVS-)/Enterprise-Systems-Architectures-(ESA-)Umgebung
ausgelegt, indem sie die Kopplungseinrichtung von IBM nutzt, um
die Datenfreigabe zwischen mehreren Systemen zu ermöglichen,
was eine System/390-Parallel-Sysplex-Umgebung erforderlich macht,
da die Kopplungseinrichtung benötigt
wird, um äußerst wirksame und
skalierbare Funktionen zur Datenfreigabe bereitzustellen, wobei
die Kopplungseinrichtung Verbindungen zwischen Prozessoren verwaltet
und ein Nachrichtenpfad-Mechanismus, der in der
US-Patentschrift 5 463 736 kurz dargelegt
ist, im Grunde der einzige Superserver für die gemeinsam benutzten Daten wird.
-
Bisherige
Lösungen,
die durch das möglicherweise
Beste ihrer Art bei Audio/Video-Dateisystemen (dem VideoCharger-Server
von IBM für
AIX) vertreten sind und die sich mit Rechnersystemen befassen, die die
Erfüllung
von Standards ermöglichen
würden,
beruhen darauf, dass Anforderungen auf Dateisystemebene an einen
einzigen Server gesendet werden, der die Daten abruft und sie zurückschickt,
oder darauf, dass Anforderungen für Metadaten von einem Client
an einen einzigen Server gesendet werden, der es dem ursprünglichen
Rechner ermöglicht,
die Daten direkt abzurufen. IBM bietet auch ein Programmprodukt
mit der Bezeichnung Virtual Shared Disk (VSD) an, mit dem ein SP2-Benutzer
Knoten als primäre
und sekundäre IBM-VSD-Serverknoten konfigurieren
kann. Die VSD-Software ermöglicht
es mehreren Knoten, die unabhängige
Abbilder des Betriebssystems ausführen, auf eine Platteneinheit,
die nur mit einem der Knoten physisch verbunden ist, so zuzugreifen,
als wäre
die Platteneinheit mit allen Knoten verbunden, wobei IBM bei dem
Betriebssystem AIX eine transparente Umschaltung zu einem sekundären Serverknoten
vorgesehen hat, wenn der primäre
Serverknoten für
eine Gruppe von virtuellen gemeinsam benutzten Platten ausfällt. In
beiden Fällen
stellt das Vorhandensein des einzigen Servers einen Flaschenhals
und eine mögliche
Fehlerstelle dar, obgleich bei solchen Systemen mit nur einem einzigen
Server wie zum Beispiel dem VideoCharger von IBM beträchtliche
Fortschritte erzielt wurden, wie das Sperrenverwaltungsprogramm
der
US-Patentschrift 5 454 108 und
die Verbundanordnung der
US-Patentschriften
5 490 270 und
5 566
297 zeigen. Wie bei den Systemen der International Business
Machines Corporation gibt es auch Funktionen zur Partitionierung
einer Platte, auf die über
ein Netzwerk zugegriffen wird, so dass ein bestimmter Rechner einen
bestimmten Bereich der gemeinsam benutzten Platte verwaltet und
darauf zugreift, während
er die Bereiche, die einem oder mehreren anderen Rechnern zugewiesen
sind, nicht in Anspruch nimmt.
-
Jedoch
haben diese Systeme in der Vergangenheit keine zufrieden stellende
Lösung
geboten, bei der viele Rechner, die über ein Netzwerk Zugriff auf
mehrere Platten haben, einem beliebigen Rechner den jederzeitigen
Zugriff auf beliebige Daten gestatten dürfen, und insbesondere denjenigen
Rechnern, die keine Änderung
an einem Betriebssystem oder an einem Standard erforderlich machen,
wie sie von uns entwickelt wurde und im Zusammenhang mit unserem
Dateisystem mit gemeinsam benutzten Platten beschrieben wird. Nichtsdestotrotz
wissen wir die Arbeit der Erfinder der
US-Patentschrift 5 454 108 für ihre Fortschritte
zu würdigen, da
es uns möglich
war, eine abgeänderte
Version ihres Sperrenverwaltungsprogramms als unser weiterentwickelten
Token-Verwaltungsprogramm in unserem eigenen Dateisystem mit gemeinsam
benutzten Platten zu verwenden.
-
Shared
Disk, UNIX-based, cluster file system (XP 000455986), IBM Technical
Disclosure Bulletin, IBM CORP., New York, USA, Band 37(6B), Seiten
209 bis 210 betrifft ein Verfahren zum Aufbau eines Dateisystems,
das einen direkten und gleichzeitigen Zugriff auf Dateidaten, die
auf einer Platte gespeichert sind, durch zwei oder mehrere Prozessoren
ermöglicht.
-
Es
ist eine Aufgabe der Erfindung, ein Verfahren bereitzustellen, das
die vorstehend genannten Nachteile nach dem Stand der Technik mildert.
-
Zusammenfassung der Erfindung
-
Gemäß der vorliegenden
Erfindung stellen wir ein Verfahren nach Anspruch 1 bereit.
-
Eine
bevorzugte Ausführungsform
der vorliegenden Erfindung stellt ein Dateisystem mit gemeinsam benutzten
Platten bereit, wobei eine Instanz des Dateisystems auf jedem Rechner
den gleichen Zugriff auf alle Platten hat, die mit dem Dateisystem
verbunden sind und einen Teil des Dateisystems bilden. Dies kann
mit Hilfe eines Gateway-Prozessors, eines Vermittlungsnetzwerks,
einer Hochgeschwindigkeits-Intranetkopplung, die
TCP/IP unterstützt,
und mit Hilfe von Buskopplungen für einen nichteinheitlichen
Speicherzugriff oder anderen ähnlichen
Verbindungen realisiert werden. Das Dateisystem mit gemeinsam benutzten
Platten unterstützt
Plattenlese- und -schreibaufrufe mit zugehörigen Verwaltungsaufrufen.
Die Betriebsinstanz ist eine allgemein verfügbare oder standardmäßige Instanz
und braucht zur Verwendung unseres Dateisystems mit gemeinsam benutzten
Platten nicht verändert
zu werden. Wir haben neue Dienste bereitgestellt, die erforderlich sind,
damit unser Dateisystem mit gemeinsam benutzten Platten nutzbringend
arbeitet.
-
Unser
Dateisystem mit gemeinsam benutzten Platten arbeitet als ein paralleles
Dateisystem in einer Umgebung mit gemeinsam benutzten Platten. Wir
haben einen skalierbaren Verzeichnisdienst für das System mit einem stabilen
Cursor eingerichtet. Wir haben eine segmentierte Zuordnungsliste
bereitgestellt. Bei unserem skalierbaren parallelen Dateisystem
haben wir den dynamischen Vorababruf realisiert. Die Geschwindigkeit
in unserem skalierbaren parallelen Dateisystem wurde verbessert,
indem die Leistungsfähigkeit
des Cachespeichers und die Ausnutzung des Speicherplatzes verbessert
wurden. Darüber
hinaus unterstützen
erweiterte Dateiattribute Zugriffskontrolllisten, die in der UNIX-Welt
als ACLs bekannt sind und die sich erstmalig in einem parallelen
Dateisystem einsetzen lassen, das in einer Umgebung mit gemeinsam
benutzten Platten skalierbar ist.
-
Mit
den von uns vorgenommenen Verbesserungen kann eine wirksame grundlegende
Dateisteuerung in einer Umgebung mit gemeinsam benutzten Platten
für mehrere
Rechner erreicht werden, welche die Platte und die Dateiumgebung
gemeinsam nutzen. Die den Verzeichnisdienst betreffenden Ansprüche ermöglichen ein
wirksames Einfügen
und Löschen
von Dateien in Datenstrukturen, ohne dass die Datenstrukturen stark
beschädigt
werden. Dies ist bei parallelen Systemen, bei denen man die ausschließliche Kontrolle über Bereiche erlangen
muss, die geändert
werden sollen, äußerst wichtig.
-
Durch
die Erzeugung unserer Zuordnungsliste ist es möglich, Speicher von derselben
Gruppe von Platten parallel zuzuordnen, während die Konsistenz der Metadaten
gleichzeitig vollständig
gewahrt bleibt. Dies ist wichtig, da jeder der Rechner, der Zugriff
auf das Dateisystem hat, weitere Daten erzeugen möchte, und
zwar ungeachtet dessen, was in den anderen Rechnern vor sich geht.
Unsere Vorababruf-Algorithmen berechnen die verfügbare E/A-Bandbreite und den
Datenbedarf der Anwendung, um die Datenmenge zu ermitteln, die vorab
abgerufen werden soll. Dies ist bei parallelen Systemen wichtig,
bei denen der E/A-Bedarf die verfügbare Bandbreite übersteigen
kann. Unsere Entwicklungen bei der Leistungsfähigkeit der Cachespeicher sorgen
für Ausgewogenheit
bei Gruppen mit Mehrfachzugriff, und obgleich dies nicht mit der
Parallelverarbeitung zusammenhängt,
stellt es eine allgemeine Verbesserung von Dateisystemen dar. Die
Verwendung von Dateiattributen als ein unterstützender Mechanismus gilt auch
für nichtparallele
Dateisysteme; im Rahmen unserer gesamten parallelen Dateisystem-Mechanismen
ist er jedoch sehr wichtig, da er eine wirksame Realisierung von
Zugriffssteuerungslisten in einem parallelen Dateisystem ermöglicht.
-
Eine
Funktion zur parallelen Aktualisierung derselben Datei oder desselben
Verzeichnisses in einer Umgebung mit gemeinsam benutzten Platten
wird bereitgestellt. Zudem stellen wir einen Metadaten-Knoten zur
Verwaltung von Datei-Metadaten für
parallele Lese- und Schreibvorgänge
bereit. Bei unserem System dienen Token zur Auswahl und Kennzeichnung
von Metadaten-Knoten,
und wir verfügen über erweiterte
Token-Betriebsarten zur Steuerung der Dateigröße sowie zur intelligenten
Zwischenspeicherung von Bytebereich-Token im Cachespeicher, wobei
Dateizugriffsmuster verwendet werden, sowie über einen Bytebereich-Sperralgorithmus,
der eine Bytebereich-Token-Schnittstelle
verwendet.
-
Parallele
Aktualisierungen von Dateien machten weiterführende Entwicklungen notwendig,
die sich um das Problem drehten, wie sich Metadaten wirksam erzeugen
und aktualisieren lassen, während
die gleiche Datei gleichzeitig von mehreren Rechnern aktualisiert
wird. Eine unserer Lösungen
besteht in der Erzeugung eines Metadaten-Knotens, der die konsistente
Zusammenführung
von bestimmten veränderbaren
Metadaten von mehreren Ursprungs-Rechneranwendungen handhabt. Die
zweite Lösung
stellt ein Sperrschema bereit, um allen Rechnern gegenüber denjenigen
Rechner auszuweisen, dessen Dienste sie benötigen. Dadurch entfällt die
Notwendigkeit, eine feste Verwaltungsstelle einzurichten, die sich
als Flaschenhals erweisen könnte.
-
Jetzt
ist die Dateigröße eine
Metadaten-Art, die sich in einer parallelen Aktualisierungssituation
häufig ändert. Wir
haben ein Verfahren bereitgestellt, mit dem man die korrekte Dateigröße zeitoptimal,
d.h., wenn die ausführende
Anwendung sie benötigt,
abrufen kann. Überdies
haben wir Sperrverfahren neu festgelegt, um den Mehraufwand des
Token-Verwaltungsprogramms
in dieser Umgebung zu verringern.
-
Wir
haben eine Funktion zur Wiederherstellung des Dateisystems vorgesehen,
falls ein Rechner, der an der Verwaltung von gemeinsam benutzten
Platten beteiligt ist, nicht mehr verfügbar ist, was aus vielerlei Gründen einschließlich eines
Systemausfalls passieren kann. Wir haben ein paralleles Dateisystem-Wiederherstellungsmodell
sowie eine synchrone und asynchrone Übernahme eines Metadaten-Knotens
vorgesehen.
-
Unser
paralleles Dateisystem mit gemeinsam benutzten Platten ermöglicht es,
die Steuerung von bestimmten Ressourcen vorübergehend einem ganz bestimmten
Rechner zu Änderungszwecken
zu übertragen. Während dies
der Fall ist, können
sich Strukturen auf der Platte, die für andere Rechner sichtbar sind,
in einem nicht übereinstimmenden
Zustand befinden und müssen
im Falle eines Ausfalls korrigiert werden. Um dies zu bewerkstelligen,
haben wir ein Verfahren zur Erweiterung der standardmäßigen Protokollierung
und zur Wiederherstellung von Sperren bereitgestellt, damit diese
Wiederherstellung stattfinden kann, während anderer Rechner weiterhin
auf den größten Teil
der Daten auf dem Dateisystem zugreifen. Ferner haben wir eine Funktion
zur Behandlung des Ausfalls des Metadaten-Knotens vorgesehen. Diese
Entwicklung beinhaltet die Korrektur von Metadaten, die geändert wurden,
und die Festlegung eines neuen Rechners als Metadaten-Knoten für diese
Datei, wie nachstehend beschrieben wird.
-
Nun,
in der UNIX-Welt ist das Quotenkonzept "Quota" unter diesem Namen gut bekannt. Es
ist ein allgemeines Konzept, das zur Verwaltung des anfänglichen
Umfangs eines Speicherbereichs verwendet werden kann, und dieses
Konzept wird mit anderen Betriebssystemen wie zum Beispiel denjenigen
der Systeme S/390 verwendet. Allgemein gesprochen, wenn wir Quoten
berücksichtigen,
müssen
sie aggressiv verwaltet werden, so dass Sperren nicht ständig benötigt werden,
um für
einen Benutzer neue Blöcke
zuzuordnen. Wir haben wiederherstellbare lokale Anteile für die Quotenverwaltung
(Quota Management) vorgesehen, wie nachstehend beschrieben wird.
-
Da
eine Quote eine Begrenzung der Plattenkapazität darstellt, die ein Benutzer
oder eine Gruppe von Benutzern in Anspruch nehmen kann, haben wir,
um das Konzept in unserem parallelen Dateisystem zu verwenden, eine
Möglichkeit
geschaffen, mittels der lokale Anteile über ein Quoten-Verwaltungsprogramm
(das auf die einzelne Quotendatei zugreift) zur parallelen Zuordnung
verteilt werden können.
Dies ist für
diejenigen Fälle
von zentraler Bedeutung, in denen ein Benutzer mehrere Anwendungsinstanzen
auf verschiedenen Rechnern laufen hat, die ein Dateisystem gemeinsam
benutzen. Unsere Entwicklung ermöglicht
die sofortige Wiederherstellung in vielen Situationen, in denen
zum Zeitpunkt des Ausfalls ausreichend Quoten vorhanden sind. In
bestimmten Fällen
ist die Ausführung
eines Dienstprogramms wie zum Beispiel des standardmäßigen UNIX-Dienstprogramms
mit der Bezeichnung "quotacheck" erforderlich, um
die Wiederherstellung durchzuführen.
Darüber
hinaus haben wir ein Verfahren entwickelt, mit dem das Quotenprüfungs-Dienstprogramm "quotacheck" zur selben Zeit
wie Anwendungen ausgeführt
werden kann, die von Quoten Gebrauch machen, wobei die Beeinträchtigung
so gering wie möglich
gehalten wird.
-
Diese
und andere Verbesserungen werden in der folgenden ausführlichen
Beschreibung aufgezeigt. Um die Erfindung mit ihren Vorteilen und
Merkmalen besser verstehen zu können,
sei auf die Beschreibung und die Zeichnung verwiesen.
-
Zeichnung
-
1 zeigt
ein Dateisystem mit gemeinsam benutzten Platten gemäß unserer
Erfindung, das ein Token-Verwaltungsprogramm für Knoten des Rechnersystems
enthält.
-
Ausführliche Beschreibung der Erfindung
-
Ein
Beispiel für
die Ausführungsart
von mehreren wichtigen Komponenten unserer bevorzugten Ausführungsform
unseres Dateisystems mit gemeinsam benutzten Platten ist in
1 gezeigt.
Wie in der Figur dargestellt ist, enthält unser System ein Token-Verwaltungsprogramm
11, das für
die Rechner, die als die Knoten 1, 2 und 3 betrachtet werden und
an der Verwaltung eines Dateisystems beteiligt sind, Sperrfunktionen
bereitstellt. (NB: Für
unser Token-Verwaltungsprogramm mussten wir an dem Sperrenverwaltungsprogramm
der
US-Patentschrift 5 454 108 Änderungen
vornehmen.)
-
Unser
Dateisystem-Code verwaltet Lese- und Schreiboperationen, die von
Anwendungen angefordert werden. Bei dieser Verwaltung werden die
Anwendungsanforderungen und die gemeinsam verwalteten Metadaten
verwendet, um Daten in dem Dateisystem zu erzeugen und darauf zuzugreifen.
Diese Funktion stellt den Großteil
der Verarbeitung dar, und sie ist auf allen Rechnern gleich. Mit
den geeigneten Token greift diese Verarbeitung über die Funktionen zum Lesen,
Beschreiben und Steuern der Platte direkt auf die Platte zu.
-
Die
in 1 gezeigte und vorstehend allgemein beschriebene
Ausführungsart
einer gemeinsam benutzten Platte hat mehrere entscheidende Vorteile
gegenüber
bisherigen parallelen und Verbunddateisystemen. Sie stellt den kürzesten
verfügbaren
Pfad zur Verfügung,
um die Daten von der Platte an die die Daten nutzende Anwendung
und von der die Daten nutzenden Anwendung an die Platte zu übertragen.
In dem Pfad gibt es weder für
Daten noch für
Metadaten einen Dateisystem-Server. Jeder verfügbare Pfad kann verwendet werden,
wodurch ein Server als möglicher
Flaschenhals oder als eine einzelne Fehlerstelle vermieden werden kann.
Da die in dem Sperrenverwaltungsprogramm benötigten zentralen Funktionen
nicht an einen bestimmten Rechner gebunden sind, können sie
von Rechner zu Rechner verlagert werden, um die Anforderungen an die
Leistungsfähigkeit
und die Verfügbarkeit
zu erfüllen.
-
Um
das System, das gerade beschrieben wird, zu erzeugen, mussten wir,
wie bereits erwähnt,
an dem in der
US-Patentschrift 5 454
108 gezeigten Sperrenverwaltungsprogramm Änderungen
vornehmen, um verschiedene Wiederherstellungsparadigmen auszuführen, die
für Dateisysteme
mit gemeinsam benutzten Platten erforderlich sind, und auch, um
eine Erweiterung um zusätzliche
Sperrzustände
vorzunehmen, die für
die Verarbeitung durch den Metaknoten erforderlich sind, welche
wiederum notwendig ist, um dieselbe Datei parallel aktualisieren
zu können.
Diese sowie andere Einzelheiten sind nachstehend in den verschiedenen
Unterabschnitten dieser ausführlichen
Beschreibung näher
ausgeführt.
-
Skalierbarer Verzeichnisdienst mit stabilem
Cursor und erweiterbarem Streuwertverfahren (Hashing)
-
Für die Realisierung
unseres Dateisystems mit gemeinsam benutzten Platten haben wir ein
Verfahren zur Speicherung und Indexierung einer großen Gruppe
von Datensätzen
in einer Weise entwickelt, die sehr schnelle Einfüge-, Lösch- und
Suchoperationen sowie den in Folge stattfindenden Abruf (die in
Folge stattfindende "Abfrage" ("scan")) aller Datensätze in einer
Umgebung unterstützt,
die in jeder beliebigen Instanz eines Betriebssystems, selbst in
nur einer einzigen Instanz, so realisiert werden kann, dass nicht
gegen vorhandene Schnittstellenprogrammierstandards wie zum Beispiel
die Spezifikation Single Unix von X/Open verstoßen wird. Folglich beginnen
wir mit unserer in Folge stattfindenden Abfrage und den grundlegenden
Verfahren zur Speicherung und zur Suche nach Datensätzen. Im
Gegensatz zu bisher bekannten Indexierverfahren erzeugt unsere in
Folge stattfindende Abfrage vorhersagbare Ergebnisse unter Verwendung
von nur einer kleinen begrenzten Menge an Kontextinformationen ("Cursor"), selbst wenn Datensätze eingefügt oder
gelöscht
werden, während
die Abfrage in Ausführung
befindlich ist. Das von uns eingesetzte Verfahren gehört zu einem
Gebiet der Technik, das als erweiterbares Streuwertverfahren (extendible
hashing) bezeichnet wird. In der realisierten Weise kann das erweiterbare
Streuwertverfahren Dateien mit freien Bereichen verwenden, ohne
eine bestimmte Hash-Tabelle zu speichern. Bei Nutzung des erweiterbaren
Streuwertverfahrens können
wir folglich Verzeichnisse in einem Dateisystem realisieren, das
dem Unix-Standard entspricht, obgleich es nicht auf diesen Standard
beschränkt
ist. Im Allgemeinen kann unsere bevorzugte Ausführungsform in einer Umgebung eines
Unix-Betriebssystems realisiert werden, und diese Umgebung sollte
sich als Hintergrund verstehen, wenngleich auch andere Betriebssysteme
in Betracht kommen, die dieselben Funktionen verwenden. Tatsächlich kann
das Basissystem derzeit mit vielen Betriebssystemebenen oberhalb
der Ebene arbeiten, die eigentlich zur Steuerung der Maschine, die
wir als Rechner bezeichnen, verwendet wird.
-
Sowohl
Datenbanksysteme als auch Universaldateisysteme ermöglichen
die Speicherung und den Abruf von Daten, indem ein "Schlüssel" angegeben wird,
der einen Datensatz oder eine Datei kennzeichnet. In einem Universaldateisystem
dient der Dateiname als Schlüssel
zum Zugriff auf die Daten, die in der Datei gespeichert sind; die
Struktur, die eine Gruppe von Dateinamen und die zugehörigen Informationen über den Dateizugriff
speichert, wird allgemein als Verzeichnis bezeichnet. Wenn die Gruppe
der Datensätze
oder Dateinamen groß ist,
wird häufig
eine Hilfsdatenstruktur verwendet, die als Index bezeichnet wird,
um die Suchoperationen zu beschleunigen. Ein Index ermöglicht das
Auffinden eines Datensatzes in einer Datenbanktabelle oder einen
Dateinamen in einem Verzeichnis, ohne dass die gesamte Datenbanktabelle
oder das gesamte Verzeichnis abgefragt werden muss.
-
Es
gibt mehrere bekannte Indexierverfahren, die auf Hash-Tabellen beruhen,
sowie ausgeglichene Suchbäume
wie zum Beispiel AVL-Bäume
und B-Bäume.
Um eine gute Leistung bei Suchoperationen zu erzielen, ist es bei
diesen Verfahren erforderlich, zumindest einen Teil des Indexes
neu zu organisieren, nachdem eine bestimmte Anzahl von Datensätzen eingefügt oder
gelöscht
worden ist. Um einen Datensatz in einen B-Baum einzufügen, kann
es zum Beispiel notwendig sein, einen Knoten eines B-Baums in zwei
neue Knoten aufzuteilen, um Platz für den neuen Datensatz zu schaffen.
In der Folge müssen
vorhandene Datensätze
möglicherweise
an einen anderen physischen Speicherort verschoben werden.
-
Dies
stellt bei Anwendungen ein Problem dar, die eine Datenbanktabelle
oder ein Verzeichnis eines Dateisystems in Folge durchsuchen müssen, zum
Beispiel, um den Inhalt eines Verzeichnisses aufzuführen. Solche
Anwendungen richten wiederholt Aufrufe an die Datenbank oder das
Dateisystem, wobei sie bei jedem Aufruf einen oder mehrere Datensätze abrufen,
bis alle Datensätze
oder Verzeichniseinträge
abgerufen worden sind. Zwischen den Aufrufen muss eine bestimmte
Menge an Kontextinformationen, die häufig als "Cursor" bezeichnet werden, verwaltet werden,
um verfolgen zu können,
wie weit die Abfrage fortgeschritten ist. Dies ist notwendig, damit
der nächste
Aufruf mit dem Abrufen der verbleibenden Datensätze fortfahren kann. Ausführungsarten
von Verzeichnissen von Dateisystemen verwenden üblicherweise den physischen
Speicherort oder den Offset eines Eintrags innerhalb eines Verzeichnisses
als Cursor für
eine in Folge stattfindende Abfrage. Da bei der Aktualisierung eines
Indexes wie beispielsweise der Aufteilung eines B-Baums vorhandene
Einträge
an eine andere Position in dem Verzeichnis verschoben werden können, hat
das Einfügen
oder Löschen von
Verzeichniseinträgen
während
einer in Folge stattfindenden Abfrage unerwünschte Auswirkungen auf das Ergebnis
der Abfrage: Wenn ein vorhandener Eintrag verschoben wird, könnte die
in Folge stattfindende Abfrage den Eintrag übersehen oder denselben Eintrag
zweimal zurückliefern.
-
Um
dieses Problem mit bereits bekannten Indexierverfahren zu lösen, könnte man
entweder den Index von den Datensätzen getrennt aufbewahren oder
während
einer Abfrage mehr Kontextinformationen speichern. Der erstere Ansatz
macht Such-, Einfüge- und
Löschoperationen
aufgrund der zusätzlich
notwendigen Dereferenzierungsebene aufwändiger und wesentlich komplexer
als unseren bevorzugten Ansatz. Das letztere Verfahren, das Speichern
von Kontextinformationen, kann nicht bei einem System angewendet
werden, das mit vorhandenen Programmierschnittstellenstandards kompatibel
sein muss. Die in der Spezifikation von X/Open Single Unix festgelegte
Verzeichnisschnittstelle (die Funktionen readdir, telldir und seekdir)
beispielsweise lässt
nur einen einzigen 32-Bit-Wert als Cursor für eine in Folge stattfindende
Verzeichnisabfrage zu.
-
Mit
unserer bevorzugten Entwicklung, die das erweiterbare Streuwertverfahren
nutzt, können
wir zeigen, wie eine große
Gruppe von Datensätzen
in einer Weise gespeichert und indexiert werden kann, die sehr schnelle
Einfüge-,
Lösch-
und Suchoperationen sowie eine in Folge stattfindende Abfrage unterstützt. Überdies
wird bei unserer bevorzugten Entwicklung als vorteilhaft erkannt
werden, dass ein kleiner, begrenzter Cursor-Wert (üblicherweise
32 Bit) ausreicht, um zu gewährleisten,
dass eine in Folge stattfindende Abfrage keine doppelten Datensätze zurückliefert
und alle vorhandenen Datensätze
abruft, d.h. alle Datensätze,
außer
denjenigen, die eingefügt
oder gelöscht
wurden, während
die Abfrage in Ausführung
befindlich war.
-
Es
ist nunmehr bekannt, dass das Streuwertverfahren ein Verfahren zur
Speicherung von Datensätzen und
zur Suche nach Datensätzen
mittels eines Schlüssels
ist, welches gut funktioniert, wenn ein ungefährer Begrenzungswert der Anzahl
der Datensätze
im Voraus bekannt ist. Beim Streuwertverfahren wird der verfügbare Speicherbereich
in eine feste Anzahl von "Hash-Behältern" ("hash buckets") aufgeteilt. Um
einen Datensatz zu speichern, wird eine als "Hash-Funktion" bekannte Abbildung angewendet, bei
der der Schlüsselwert einer
Hash-Behälternummer
zugeordnet wird; der neue Datensatz wird in dem von dem Hash-Wert
angegebenen Hash-Behälter
gespeichert. Um einen Datensatz mittels eines Schlüssels aufzufinden,
wird sein Hash-Wert berechnet; der angeforderte Datensatz kann dann
aufgefunden werden, indem nur die Datensätze abgefragt werden, die in
dem von dem Hash-Wert angegebenen Behälter gespeichert sind.
-
Im
Allgemeinen ist die Anzahl der zu speichernden Schlüsselwerte
im Voraus nicht bekannt und kann beliebig groß werden. Dies stellt Probleme
bei dem standardmäßigen Hashing-Verfahren dar, bei
dem die Höchstzahl
der Hash-Behälter
von Anfang an bekannt sein muss. Eine erweiterte Form eines Hashing-Algorithmus,
die als "erweiterbares
Streuwertverfahren" bekannt
ist, löst
dieses Problem, indem von dem Wert der Hash-Funktion eine veränderliche
Anzahl von Bits verwendet wird. Wenn sich ein Hash-Behälter füllt, wird
sein Inhalt "aufgeteilt", d.h., ein neuer
Hash-Behälter
wird hinzugefügt,
und ein Teil der Datensätze
wird aus dem vorhandenen Hash-Behälter in den neuen Hash-Behälter verschoben.
Welche Datensätze
verschoben werden, wird durch Neuberechnung der Hash-Funktion und
durch Verwendung eines weiteren Bit zur Festlegung der Nummer des
Hash-Behälters
bestimmt: Datensätze,
bei denen das zusätzliche
Bit den Wert null hat, bleiben in dem vorhandenen Behälter, und
diejenigen Datensätze,
bei denen das zusätzliche
Bit den Wert eins hat, wandern in den neuen Behälter.
-
Bei
Verwendung unserer bevorzugten Ausführungsform, die das erweiterbare
Streuwertverfahren nutzt, beginnt ein Index oder ein Verzeichnis
mit einem einzigen Hash-Behälter,
dem Behälter
Nummer null. Solange sie hineinpassen, wandern alle Datensätze ungeachtet
ihres Hash-Werts in den ersten Behälter, d.h., Null-Bits der Hash-Funktion
dienen zur Festlegung der Nummer des Hash-Behälters. Wenn der erste Behälter voll
ist, wird sein Inhalt aufgeteilt, indem ein neuer Hash-Behälter hinzugefügt wird,
der Behälter
Nummer eins. Nun werden die Bits mit dem Wert "1" der
Hash-Funktion zur Platzierung der Datensätze verwendet: Diejenigen Datensätze, deren
niedrigstwertiges Bit des Hash-Werts den Wert null hat, bleiben
im Behälter
Nummer null, und diejenigen Datensätze, deren niedrigstwertiges
Bit den Wert eins hat, werden in den Hash-Behälter Nummer eins verschoben.
Neue Datensätze
werden dem Behälter
Nummer null oder dem Behälter
Nummer eins in Abhängigkeit
von dem Wert des niedrigstwertigen Bits des Hash-Werts hinzugefügt. Nehmen wir nun beispielsweise
an, dass sich der Hash-Behälter
Nummer eins wieder füllt
und sein Inhalt aufgeteilt werden muss. Mit den beiden letzten Bits
der Hash-Funktion
wird nun festgelegt, wo die Datensätze des Behälters Nummer eins platziert
werden. Diejenigen Datensätze
mit dem Bit-Wert "01" bleiben im Hash-Behälter Nummer
eins, und diejenigen mit dem Bit-Wert "11" wandern
in einen neuen Hash-Behälter mit
der Nummer drei (binär
11 = dezimal 3). Die Datensätze
im Hash-Behälter
Nummer null sind von der Teilung nicht betroffen, d.h., Datensätze, deren
letzten beiden Bits den Wert "00" oder "10" haben, bleiben solange
im Behälter
Nummer null, bis sich dieser füllt
und sein Inhalt ebenfalls aufgeteilt werden muss. Es ist auch möglich, dass
sich der Behälter
Nummer eins ganz füllt
und sein Inhalt noch einmal aufgeteilt werden muss, bevor der Inhalt
des Behälters
Nummer null überhaupt
jemals aufgeteilt wird.
-
Die
Verzeichnisstruktur nach mehreren Aufteilungen des Inhalts der Hash-Behälter kann
durch einen binären
Baum ("Hash-Baum") dargestellt werden,
wie in dem Beispiel in Tabelle 1 gezeigt ist. Ein Datensatz kann
aufgefunden werden, indem der Baum von der Wurzel zu einem Blattknoten
(Hash-Behälter)
durchlaufen wird, wobei die Bits des Hash-Werts zur Entscheidung,
welcher Verzweigung an jedem inneren Knoten gefolgt wird, verwendet werden.
In Abhängigkeit
von der Verteilung der Hash-Werte kann eine Verzweigung des Hash-Baums
länger
als andere werden. Bei einer gut gewählten Hash-Funktion, d.h. einer
Funktion, die gleichmäßig verteilte
Hash-Werte erzeugt, gehen wir davon aus, dass alle Verzweigungen
des Baums ungefähr
dieselbe Tiefe haben. Eine in Folge stattfindende Abfrage des Verzeichnisses
wird mittels eines Tiefendurchlaufs des Baums durchgeführt, bei
dem die Blattknoten (Hash-Behälter)
in der Reihenfolge von links nach rechts besucht werden. TABELLE
1
Tabelle
1: | Beispiel
für einen
Hash-Baum nach 4 Aufteilungen: |
| Der
Behälter
0 wurde in den Behälter
0 und den Behälter
1 aufgeteilt, |
| der
Behälter
0 wurde erneut in den Behälter
0 und den Behälter
2 aufgeteilt, |
| der
Behälter
2 wurde erneut in den Behälter
2 und den Behälter
6 aufgeteilt, |
| der
Behälter
1 wurde erneut in den Behälter
1 und den Behälter
3 aufgeteilt. |
-
Die
Blattknoten des Baums werden mit der Nummer des Hash-Behälters in
Binär-
und Dezimalschreibweise gekennzeichnet.
-
Gemäß unserer
bevorzugten Ausführungsform
wird ein Hash-Baum als eine Datei mit freien Bereichen auf der Platte
dargestellt, und Datensätze
werden verschoben, wenn der Inhalt eines Hash-Behälters aufgeteilt
wird, und eine in Folge stattfindende Verzeichnisabfrage durchläuft den
Hash-Baum so, dass alle vorhandenen Einträge genau einmal zurückgeliefert
werden. Jeder dieser Entwicklungsbereiche hat auf unser System anwendbare
Verbesserungen hervorgebracht.
-
In
unserem System werden bei der Umsetzung des erweiterbaren Streuwertverfahrens
Dateien mit freien Bereichen verwendet. In einem Dateisystem werden
Daten, die in eine gewöhnliche
Datei geschrieben werden, in einem oder mehreren Plattenblöcken auf
der Platte gespeichert. Bei Unix- und Unix ähnlichen Dateisystemschnittstellen
ist es möglich,
neue Daten hinter das aktuelle Ende einer Datei zu schreiben, indem zwischen
Schreibaufrufen "Such"-Aufrufe ausgegeben
werden. Dadurch können
Dateien mit Lücken
oder "Löchern" erstellt werden,
d.h. Bereichen innerhalb einer Datei, in die nie Daten geschrieben
wurden. Solche Dateien werden als "Dateien mit freien Bereichen" ("sparse files") bezeichnet. Leseoperationen
in Dateien mit freien Bereichen liefern an den Stellen, an denen
der Lese-Offset und die Länge
ein Loch kreuzen, Nullen zurück. Ausführungsarten
von Dateisystemen, die Dateien mit freien Bereichen wirksam unterstützen, ordnen
Plattenspeicher nur für
die Bereiche einer Datei zu, in die Daten geschrieben wurden, jedoch
nicht für
Löcher
oder zumindest nicht für
Löcher,
die größer als
die Blockgröße oder
die Einheit der Plattenzuordnung ist, die von dem Dateisystem verwendet
wird.
-
Ein
Index oder ein Verzeichnis, der/das auf dem erweiterbaren Streuwertverfahren
beruht, wird in unserer bevorzugten Ausführungsform mittels einer Datei
mit freien Bereichen realisiert. Jeder Hash-Behälter wird in der Datei an einem
Offset gespeichert, der mit i*s angegeben wird, wobei i die Nummer
des Hash-Behälters
(wobei mit der Nummer null begonnen wird) und s die Größe des Hash-Behälters (alle
Hash-Behälter haben
dieselbe Größe) ist.
Das Verzeichnis beginnt als eine leere Datei. Wenn der erste Datensatz
eingefügt wird,
wird er im Hash-Behälter
Nummer null gespeichert, dessen Inhalt anschließend in die Datei geschrieben wird
und die Größe der Datei
von null auf s erhöht.
Wenn der Inhalt des Hash-Behälters Nummer
null aufgeteilt werden muss, wird der Inhalt des Behälters 1
in die Datei geschrieben und erhöht
die Dateigröße von s
auf 2*s. Bei der nächsten
Aufteilung des Inhalts des Hash-Behälters wird der Hash-Behälter 2 oder
der Hash-Behälter
3 beschrieben, je nachdem, welcher der ersten beiden Behälter als
Nächstes
aufgeteilt werden muss. Wenn als Nächstes der Inhalt des Behälters Nummer
eins aufgeteilt wird, wird der Inhalt des Hash-Behälters Nummer
3 in die Datei geschrieben, wodurch deren Größe von 2*s auf 4*s zunimmt
und am Offset 2*s der Datei, an dem der Inhalt des Hash-Behälters 2
gespeichert würde,
ein Loch zurückbleibt.
Tabelle 2 zeigt, wie der Hash-Baum in dem Beispiel der Tabelle 1
in einer Datei mit freien Bereichen gespeichert werden würde. TABELLE
2
- Tabelle 2: Abbildung des
Hash-Baums der Tabelle 1 in eine Datei mit freien Bereichen.
-
Wie
vorstehend beschrieben wurde, könnte
ein Datensatz mit einem gegebenen Schlüssel aufgefunden werden, indem
der Hash-Baum von
oben nach unten durchlaufen und dabei an der Wurzel (dem Behälter null)
begonnen würde.
Da wir jedoch davon ausgehen, dass alle Verzweigungen des Baums
ungefähr
die gleiche Tiefe haben, ist es sinnvoller, den Baum von unten nach
oben zu durchlaufen. Dies geschieht wie folgt. Wenn die Dateigröße bekannt
ist, können
wir die Tiefe der längsten
Verzweigung des Hash-Baumes berechnen, da alle Nummern von Hash-Behältern in
einem Hash-Baum mit der höchstmöglichen
Tiefe d eine Anzahl von d Bits oder weniger haben und mindestens
ein Hash-Behälter
eine Nummer haben muss, bei der das d-te Bit den Wert eins hat.
Daher kann die maximale Tiefe d als die Anzahl der Bits in der höchsten Nummer
des Hash-Behälters berechnet
werden, die gegeben ist durch f/s – 1, wobei f die Dateigröße ist.
Um nach einem Datensatz mit einem gegebenen Schlüssel zu suchen, berechnen wir
zuerst die Nummer b des Hash-Behälters,
die durch die d niedrigstwertigen Bits des Hash-Werts für den gegebenen
Schlüssel
angegeben ist. Wenn alle Verzweigungen des Hash-Baums dieselbe Tiefe
hätten,
würden
wir den Datensatz ganz sicher in dem von diesem Schlüssel angegebenen
Hash-Behälter
auffinden. Da die Tiefe der Verzweigung, die den gegebenen Schlüssel speichert,
kleiner als d sein kann, ist der Behälter b in dem Hash-Baum möglicherweise
noch nicht vorhanden. Wenn dies der Fall ist, hat die Datei ein
Loch an dem Offset, der durch b*s angegeben ist. Wenn also ein Loch
angetroffen wird, berechnen wir eine neue Nummer b' des Hash-Behälters, indem
wir ein Bit weniger vom Hash-Wert verwenden, was den Speicherplatz
des Datensatzes ergibt, wenn die Verzweigung des Hash-Baums die
Tiefe d-1 hat. Dieser Vorgang wird so lange wiederholt, wie ein
Loch in der Datei angetroffen wird. Sobald ein Nicht-Loch gefunden
wird, muss sich der Datensatz mit dem gegebenen Schlüssel in
diesem Hash-Behälter
befinden, sofern er vorhanden ist. Such- und Einfügeoperationen
werden wie folgt gehandhabt:
-
Suchoperation:
-
- 1. Berechne den Hash-Wert h des Schlüssels, nach
dem gesucht wird.
- 2. Berechne die Tiefe d des Hash-Baums als Logarithmus zur Basis
2 der Dateigröße, geteilt
durch die Größe des Hash-Behälters, gerundet
auf die nächste
ganze Zahl.
- 3. Berechne die Nummer b des Hash-Behälters als die d niedrigstwertigen
Bits von h: b = h mod (2¬d).
- 4. Rufe den Hash-Behälter
aus der Datei am Offset b*s ab, wobei s die Größe des Hash-Behälters ist.
- 5. Wenn der Hash-Behälter
b nicht vorhanden ist (die Datei enthält ein Loch am Offset b*s),
verringere d um eins, und schalte zurück zum Schritt 3.
- 6. Suche nach dem Datensatz mit dem angegebenen Schlüssel im
Hash-Behälter
b; liefere den Datensatz zurück,
sofern er gefunden wurde; gib ansonsten die Fehlermeldung "nicht gefunden" ("not found") zurück.
-
Einfügeoperation:
-
- 1. Berechne die Tiefe d des Hash-Baums und
die Nummer b des Hash-Behälters,
wie in den Schritten 1 bis 5 für
die Suchoperation beschrieben wurde, und verwende dabei den Schlüssel des
Datensatzes, der eingefügt
werden soll.
- 2. Wenn im Hash-Behälter
b bereits ein Datensatz mit dem gegebenen Schlüssel vorhanden ist, gib die Fehlermeldung "bereits vorhanden" ("already exists") zurück.
- 3. Wenn im Hash-Behälter
b ausreichend Platz für
den neuen Datensatz vorhanden ist, speichere den Datensatz und kehre
zurück.
Andernfalls muss der Inhalt des Hash-Behälters
b aufgeteilt werden, um Platz für
den neuen Datensatz zu schaffen, wie in den nachfolgenden Schritten
beschrieben wird.
- 4. Berechne b' =
2¬d + b
- 5. Wiederhole die folgenden Schritte für alle Datensätze im Hash-Behälter b:
5a.
Berechne v = h mod (2 ¬(d
+ 1)), wobei h der Hash-Wert
für den
Schlüssel
des Datensatzes ist. Beachte, dass v entweder gleich b oder b' sein muss, weil
h mod 2¬d
bei allen Datensätzen
im Hash-Behälter
b gleich b ist.
5b. Wenn v = b', verschiebe den Datensatz in den Hash-Behälter b'; belasse den Datensatz
andernfalls in b.
- 6. Erhöhe
d um eins und berechne b neu als h mod (2¬d), wobei h der Schlüssel des
Datensatzes ist, der eingefügt
werden soll.
-
Gehe
zum Schritt 3 zurück.
-
Obgleich
die hier beschriebene Ausführungsart
des erweiterbaren Streuwertverfahrens mit jeder beliebigen Größe eines
Hash-Behälters
funktioniert, ist sie leistungsfähiger,
wenn die Größe des Behälters gleich der
Blockgröße des Dateisystems
oder gleich einem Vielfachen der Blockgröße ist. Dies liegt daran, dass
eine wirksame Realisierung von Dateien mit freien Bereichen keine
Platten-E/A-Operationen erforderlich macht, um ein Loch zu lesen,
wenn das Loch an Blockgrenzen des Dateisystems ausgerichtet ist.
Daher erfordern alle Suchoperationen höchstens eine Platten-E/A-Operation, um den
eigentlichen Hash-Behälter
zu lesen, der den Datensatz aufnehmen würde, sofern sich dieser Hash-Behälter nicht
gerade im Cachespeicher befindet. Es sei angemerkt, dass hierbei
davon ausgegangen wird, dass die Metadaten der Datei, die die Position
der Plattenblöcke
der Datei enthalten, im Zwischenspeicher abgelegt werden.
-
Bei
gleichmäßig verteilten
Hash-Werten gehen wir davon aus, dass wir durchschnittlich auf 0,5
Löcher pro
Suchoperation stoßen.
Wenn die Ausführungsart
des erweiterbaren Streuwertverfahrens direkten Zugriff auf die Metadaten
des Dateisystems hat (zum Beispiel, wenn sie zur Realisierung von
Verzeichnissen in dem Dateisystem selbst verwendet wird), können Löcher erkannt
werden, indem man direkt in den Metadaten der Datei nachschlägt. Andernfalls
muss die Suchoperation für
jede Nummer eines Hash-Behälters,
die sie berechnet, mindestens einen Teil der Daten lesen und ein
Loch daran erkennen, dass die Leseoperationen alle Nullen zurückgeliefert
hat. Dies geschieht am einfachsten, indem Hash-Behälter mit
einem kurzen Kopfbereich gespeichert werden, der einen Wert ungleich
null enthält.
-
Nun
ermöglichen
wir Aufspaltungen und Zusammenführungen
von Hash-Behältern.
Datensätze
werden in jedem Hash-Behälter
gespeichert und bei der Aufteilung eines Hash-Behälters verschoben.
Speicherplatz auf der Platte wird zurückgefordert, indem Hash-Behälter nach
dem Löschen
von Datensätzen
zusammengeführt
werden.
-
Jeder
Hash-Behälter
enthält
einen Kopfbereich mit einem Feld "Stufe des Hash-Baums" ("hash
tree level"). Der
Wert dieses Felds zeigt die Stufe des Hash-Behälters in dem Hash-Baum an,
d.h., wie weit der Behälter
von der Wurzel des Hash-Baums entfernt ist. Anfangs hat der Baum
nur einen Behälter,
den Behälter null
auf der Stufe null des Hash-Baums. Wenn der Behälter null aufgeteilt wird, ändert sich
seine Stufe im Hash-Baum von null in eins; die neue Behälter-Nummer
eins ist ein Bruder des Behälters
null nach der Aufteilung, d.h., sie hat ebenfalls die Hash-Baum-Stufe
eins. Jedes Mal, wenn ein Hash-Behälter aufgeteilt wird, wird seine
Stufe um eins erhöht,
und dem neuen Behälter,
der hinzugefügt
wird, wird dieselbe Stufe des Hash-Baums zugeordnet, die auch der
Behälter
hat, der aufgeteilt wurde.
-
Immer
wenn ein neuer Datensatz zu einem Hash-Behälter hinzugefügt wird,
speichern wir zu diesem Zeitpunkt zusammen mit dem Datensatz die
Hash-Baum-Stufe des Hash-Behälters.
Wenn der Inhalt des Hash-Behälters
aufgeteilt wird, wird die Stufe des Hash-Baums, die in dem Kopfbereich
des Behälters
gespeichert ist, erhöht,
aber die letzte Stufe des Hash-Baums, die mit jedem Datensatz gespeichert
wird, bleibt unverändert.
-
Die
Datensätze,
die in den neuen Hash-Behälter
verschoben werden, behalten ebenfalls ihre ursprünglichen Hash-Baum-Stufenwerte. Indem
man die Werte der Hash-Baum-Stufen, die zu einem bestimmten Datensatz
gehören,
mit der Hash-Baum-Stufe vergleicht, die im Kopfbereich des Hash-Behälters gespeichert
ist, ist es folglich möglich,
festzustellen, ob der Datensatz eingefügt wurde, bevor oder nachdem
der Behälter
aufgeteilt worden ist. Die in Folge stattfindende Verzeichnisabfrage
bedarf dieser Möglichkeit,
wie nachstehend erklärt
wird.
-
Ein
weiteres Erfordernis der in Folge stattfindenden Abfrage besteht
darin, dass der Offset eines Datensatzes in einem Hash-Behälter stabil
bleibt, sobald der Datensatz eingefügt worden ist. Wenn wir einen
Datensatz in einen Hash-Behälter
einfügen
oder in einem Hash-Behälter
löschen,
verbleiben vorhandene Datensätze
daher an ihrer ursprünglichen
Position, d.h., freier Speicherbereich wird nicht komprimiert. Wenn
ein Datensatz aufgrund einer Aufteilung in einen neuen Hash-Behälter verschoben
wird, speichern wir den Datensatz überdies in dem neuen Behälter an
demselben relativen Offset wie in dem ursprünglichen Hash-Behälter. Zusammen
mit den Stufen des Hash-Baums können
wir damit den Inhalt eines Hash-Behälters, den er vor seiner Aufteilung
hatte, rekonstruieren.
-
Nach
einer bestimmten Anzahl von Löschoperationen
ist es gegebenenfalls wünschenswert,
nicht mehr benötigten
Speicherplatz auf der Platte zurückzufordern.
Dies kann geschehen, indem zwei Geschwisterblattknoten in dem Hash-Baum
zusammengeführt
werden, wenn die beiden Knoten über
so wenige Datensätze
verfügen,
dass sie in einen einzigen Hash-Behälter passen. Die in Folge stattfindende
Abfrage macht es jedoch erforderlich, dass Datensatz-Offsets sowohl
während
der Zusammenführungen
als auch während
der Aufteilungen erhalten bleiben. Dies bedeutet, dass es zur Feststellung,
ob zwei Hash-Behälter
zusammengeführt
werden können,
nicht ausreicht, einfach den freien Platz in beiden Behältern zu
addieren; stattdessen muss sichergestellt werden, dass es keine
zwei Datensätze
gibt, die einander überlappen
würden,
wenn sie zu einem einzigen Hash-Behälter zusammengeführt würden. Der
einfachste Weg, dies zu tun, besteht darin, die Zusammenführung von
zwei Hash-Behältern
so lange aufzuschieben, bis sich einer der beiden vollständig entleert
hat.
-
Wenn
zwei Hash-Behälter
zusammengeführt
werden, werden Datensätze
von dem einen Hash-Behälter
mit der höheren
Nummer in den Hash-Behälter
mit der niedrigeren Nummer verschoben, und die Stufe des Hash-Baums
im Kopfbereich des Behälters
mit der niedrigeren Nummer wird um eins verringert. Der Hash-Behälter mit
der höheren
Nummer wird aus der Datei entfernt, indem ihr Inhalt gelöscht wird.
In einem Unix ähnlichen
Dateisystem kann dies durch den Aufruf von "fclear" geschehen; wenn das Dateisystem Dateien
mit freien Bereichen wirksam ausführt, wird dadurch ein Loch
erzeugt, dass der zuvor von dem Hash-Behälter belegte Plattenspeicherbereich
freigegeben wird.
-
In
unserer bevorzugten Ausführungsform
ist zur Unterstützung
einer in Folge stattfindenden Abfrage aller Datensätze in einem
Verzeichnis oder einem Index eine Abfrageoperation vorgesehen, die
wiederholt aufgerufen werden kann, um den Inhalt des Hash-Baums
zurückzuliefern,
etwas, das wir als eine in Folge stattfindende Verzeichnisabfrage
(sequential directory scan) bezeichnen. Jeder Aufruf liefert einen
oder mehrere Datensätze
sowie einen "Cursor"-Wert zurück, der
dem nächsten
Abfrageaufruf übergeben
werden muss, um die nächste
Gruppe von Datensätzen
abzurufen. Wir beschreiben zuerst, wie diese Verzeichnisabfrage
funktioniert, wenn keine Datensätze
eingefügt
oder gelöscht
werden, während
die Abfrage in Ausführung
befindlich ist, und anschließend
betrachten wir, wie mit Änderungen
am Hash-Baum aufgrund von Einfüge-
oder Löschoperationen
zwischen Aufrufen der Abrufroutine umgegangen wird.
-
Die
Verzeichnisabfrage beginnt mit dem Abruf von Datensätzen aus
dem äußersten
linken Hash-Behälter
in dem Hash-Baum, der immer der Hash-Behälter Nummer null ist. Sobald
alle Datensätze
aus dem Behälter
Nummer null zurückgeliefert
worden sind, wird die Abfrage mit dem Bruder des Hash-Behälters Nummer null
in dem Hash-Baum fortgesetzt. Aufgrund der Art und Weise, in der
der Hash-Baum aufgebaut ist, unterscheiden sich die Nummern der
Hash-Behälter
von zwei Geschwistern in der Tiefe d im Hash-Baum nur in dem d-ten
Bit: Das d-te Bit der Nummer des Hash-Behälters hat bei dem linken Bruder
den Wert null und bei dem rechten Bruder den Wert eins. Daher ist
der Bruder des Hash-Behälters
null der Hash-Behälter
b1 = 2¬(d-1) (ein
einzelnes Bit mit dem Wert "eins" an der d-ten Position).
Nachdem alle Datensätze
aus dem Hash-Behälter b1
abgerufen worden sind, wird die Abfrage mit dem nächsten Hash-Behälter in
dem Hash-Baum in einem Tiefendurchlauf fortgesetzt. Der nächste Hash-Behälter nach
dem Behälter
b1 ist kein Bruder, hat aber einen gemeinsamen Vorfahr mit dem Hash-Behälter b1
in einer Tiefe d-1
in dem Baum. Daher hat dieser nächste Hash-Behälter ein
Bit mit dem Wert "1" an der Bitposition
d-1 und ein Bit mit dem Wert null an der Position d, was eine Hash-Behälter- Nummer b2 = 2¬(d-2) ergibt.
Unter der Annahme eines Hash-Behälters b
in der Tiefe d in dem Hash-Baum wird der nächste Blattknoten bei einem
Tiefendurchlauf des Baums im Allgemeinen gefunden, indem man die
d niedrigstwertigen Bits von b verwendet, diese Bits umkehrt, zu
dem sich ergebenden Wert eins modulo 2¬d addiert und das Ergebnis erneut
umkehrt.
-
Eine
Abfrage eines Hash-Baums kann daher mit einem Cursor c = (b, r)
durchgeführt
werden, der aus einer Hash-Behälter-Nummer b und einem
relativen Offset r innerhalb eines Hash-Behälters
besteht. Eine mit einem Cursor-Wert (b, r) aufgerufene Abfrageoperation
prüft zuerst,
ob es im Hash-Behälter b an
einem Offset, der größer als
oder gleich r ist, noch weitere Datensätze gibt. Wenn ja, liefert
die Abfrage den nächsten
Datensatz nach r und einen neuen Cursor-Wert (b, r') zurück, wobei
r' der nächste Offset
nach dem Datensatz ist, der zurückgeliefert
worden ist. Wenn es im Behälter
b an Offsets, die größer als
oder gleich r sind, keine weiteren Datensätze gibt, wird die Abfrage
mit einem Cursor-Wert von (b',
0) fortgesetzt, wobei b' die
nächste Nummer
des Hash-Behälters ist,
die mittels der vorstehend beschriebenen Bitumkehr-/Biterhöhungsprozedur mit
einem Wert von d berechnet wurde, welcher von der Stufe des Hash-Baums
angegeben wird, die im Kopfbereich des Behälters b gespeichert ist. Wenn
diese Berechnung einen Wert von 0 für b' ergibt, haben wir das Ende des Hash-Baums
erreicht, und es gibt keine Datensätze mehr, die zurückgeliefert
werden müssen.
-
Änderungen
am Hash-Baum aufgrund von Einfüge-
oder Löschoperationen
werden zwischen Aufrufen der Abfrageroutine durchgeführt. Da
wir vorhandenen Datensätze
innerhalb eines Blocks nicht verschieben, um einen neuen Datensatz
einzufügen oder
einen alten Datensatz zu löschen,
bleibt die in Folge stattfindende Abfrage von Einfüge- und
Löschoperationen
unbeeinflusst, solange diese nicht zu einer Aufteilung oder einer Zusammenführung von
Hash-Behältern
führen.
Da vorhandene Datensätze
in diesem Fall nicht verschoben werden, wird jeder Datensatz bei
der Abfrage höchstens
einmal gefunden, und es ist sichergestellt, dass bei der Abfrage
alle vorhandenen Datensätze
mit Ausnahme derjenigen, die gelöscht
wurden, während
die Abfrage in Ausführung
befindlich war, zurückgeliefert
werden. Ein neu eingefügter
oder gelöschter
Datensatz kann in Abhängigkeit
von seiner Position (Hash-Behälter und
Offset) und der Zeitsteuerung der Einfüge-/Löschoperation
in Bezug auf den Durchlauf des Hash-Baums durch die in Folge stattfindende
Abfrage gefunden oder auch nicht gefunden werden. Eine Aufteilung
oder Zusammenführung
eines Hash-Behälters
hat ebenfalls keine Auswirkungen auf die in Folge stattfindende
Abfrage, wenn die Aufteilung/Zusammenführung stattfindet, bevor die
in Folge stattfindende Abfrage die Hash-Behälter erreicht, die von der
Aufteilung/Zusammenführung betroffen
sind, oder wenn die Aufteilung/Zusammenführung stattfindet, nachdem
die Abfrage die betroffenen Behälter
bereits durchlaufen hat.
-
Besondere
Vorsicht ist nur in dem Fall geboten, in dem ein Hash-Behälter aufgeteilt
oder zusammengeführt
wird, wenn die in Folge stattfindende Abfrage einen Teil, aber nicht
alle Datensätze
in dem Hash-Behälter
zurückgeliefert
hat, der von der Aufteilung oder Zusammenführung betroffen ist. Bei der
Aufteilung eines Blocks könnten
einige der Datensätze,
die bereits von vorherigen Aufrufen der Abfrageroutine zurückgeliefert worden
sind, in den neuen Hash-Behälter
verschoben werden, wobei die in Folge stattfindende Abfrage in diesem
Fall dieselben Datensätze
erneut zurückliefern
würde,
wenn sie den neuen Block besucht. Umgekehrt könnte eine Zusammenführung von
Hash-Behältern
dazu führen,
dass die Abfrage Datensätze übersieht,
die von einem Block, der von der Abfrage noch nicht besucht wurde,
in den aktuellen Hash-Behälter an
einen Offset verschoben werden, der kleiner als der Offset ist,
der von dem aktuellen Wert des Abfrage-Cursors angegeben wird. Diese
Erfindung löst
diese Probleme, indem sie eine Aufteilung oder eine Zusammenführung eines Hash-Behälters erkennt,
die sich auf die in Folge stattfindende Abfrage auswirken würde, und
indem sie bei Bedarf den Zustand des Hash-Behälters wiederherstellt, den
dieser vor der Aufteilung/Zusammenführung hatte, um die Abfrage
fortzusetzen, ohne dass Datensätze übersehen
oder dupliziert werden. Eine Aufteilung oder eine Zusammenführung kann
festgestellt werden, indem man wie folgt eine Hash-Baum-Stufe in
den Cursor-Wert aufnimmt, der von der Abfrageroutine zurückgeliefert
wird. Wenn die Abfrageroutine den ersten Datensatz aus einem Hash-Behälter b zurückliefert,
liefert sie auch einen Cursor-Wert c = (h, b, r) zurück, der
die Hash-Behälter-Nummer
b und den relativen Offset, die vorstehend beschrieben wurden, sowie
den Wert h der Hash-Baum-Stufe enthält, den man zu dem Zeitpunkt,
zu dem der erste Datensatz gelesen wird, im Kopfbereich des Hash-Behälters findet.
Wenn dieser Cursor-Wert einem nachfolgenden Aufruf der Abfrageroutine übergeben
wird, wird die von dem Cursor-Wert angegebene Stufe h des Hash-Baums
mit der aktuellen Stufe h' des
Hash-Baums verglichen, die man im Kopfbereich des Hash-Behälters findet.
Wenn h' > h ist, muss der Hash-Behälter b zwischen
den beiden Aufrufen der Abfrageroutine aufgeteilt worden sein; wenn
h' < h oder wenn der
Hash-Behälter
b nicht mehr vorhanden ist (die Datei enthält jetzt ein Loch am Offset
b*s), muss er mit einem anderen Hash-Behälter zusammengeführt worden
sein.
-
Aufteilungen
von Hash-Behältern
(h' > h) werden durchgeführt, indem
wieder der Zustand des Hash-Behälters
hergestellt wird, den er zum Zeitpunkt der Erzeugung des Cursors
hatte. Ein temporärer
Pufferspeicher dient zur Aufnahme des wiederhergestellten Hash-Behälters. Nachkommen
des ursprünglichen Hash-Behälters werden
jeweils einzeln gelesen, und alle Datensätze, die in dem ursprünglichen
Hash-Behälter b
vorhanden waren, werden in den temporären Pufferspeicher kopiert.
Die zu kopierenden Datensätze
werden gekennzeichnet, indem die Stufe des Hash-Baums geprüft wird,
die zusammen mit jedem Datensatz gespeichert wird, wie im vorherigen
Abschnitt beschrieben wurde: Alle Datensätze, deren Hash-Baum-Stufe
weniger als oder gleich h ist, waren bereits vorhanden, bevor der
Hash-Behälter
b aufgeteilt wurde, und werden deshalb kopiert. Da bei einer Aufteilung
eines Hash-Behälters
der ursprüngliche
Offset der Datensätze,
die bei der Aufteilung in einen neuen Hash-Behälter verschoben werden, beibehalten
wird, ist gewährleistet,
dass diese Datensätze
an denselben Offset in dem temporären Pufferspeicher zurückkopiert
werden können,
so dass der temporäre
Pufferspeicher genau so aussieht, wie der ursprüngliche Pufferspeicher zum
Zeitpunkt der Erzeugung des Cursors aussah (mit Ausnahme der Datensätze, die
seitdem gelöscht
worden sind). Anschließend fährt die
Abfrageroutine mit der Verarbeitung fort, wobei sie den wiederhergestellten
Block im temporären
Pufferspeicher verwendet. Wenn sie das Ende des temporären Pufferspeichers
erreicht, berechnet die Abfrageroutine den nächsten zu besuchenden Hash-Behälter, wobei
sie die vorstehend beschriebene Bitumkehr- /Biterhöhungsprozedur mit einem Wert
von d verwendet, der von der Hash-Baum-Stufe h vom Abfrage-Cursor angegeben
wird.
-
Schließlich werden
Zusammenführungen
von Hash-Behältern
während
einer in Folge stattfindenden Abfrage durchgeführt. Eine Zusammenführung wird
festgestellt, wenn die Hash-Stufe h, die von dem Abfrage-Cursor
c = (h, b, r) angegeben wird, größer als
die Hash-Stufe h' ist,
die man im Kopfbereich des Hash-Behälters b
findet, oder wenn der Hash-Behälter
b nicht mehr vorhanden ist, d.h., wenn stattdessen ein Loch angetroffen
wurde. Ähnlich
wie im Fall der Zusammenführung
geschieht dies, indem wieder der Zustand der Hash-Behälter hergestellt
wird, den sie zum Zeitpunkt der Erzeugung des Cursors hatten, d.h.,
bevor sie aufgeteilt wurden. In diesem Fall muss der vorherige Inhalt
des Hash-Behälters
jedoch nicht in einem gesonderten Pufferspeicher wiederhergestellt
werden. Stattdessen führt
die Abfrage Operationen an dem zusammengeführten Hash-Behälter aus,
durchläuft
den Behälter
aber mehrfach. In jedem Durchlauf werden nur Datensätze aus
einem der ursprünglichen
Behälter
zurückgeliefert;
andere Datensätze
werden ignoriert. Dies geschieht, indem der Hash-Wert eines jeden
Datensatzes neu berechnet wird und die h niedrigstwertigen Bits
des Hash-Werts mit der Nummer b des Hash-Behälters verglichen werden, die
von dem aktuellen Abfrage-Cursor angegeben wird. Wenn sie gleich
sind, wäre
der Datensatz in den Hash-Behälter
b gestellt worden, bevor dieser mit einem anderen Hash-Behälter zusammengeführt worden
ist, und der Datensatz wird von der Abfrage zurückgeliefert. Andernfalls wird
der Datensatz ignoriert. Es sei angemerkt, dass in dem Fall, in
dem der Hash-Behälter
b nicht mehr vorhanden ist (es wurde stattdessen ein Loch angetroffen),
der Behälter,
der das Ergebnis der Zusammenführung
der Hash-Behälter
enthält,
gefunden wird, indem man eine oder mehrere Stufen in dem Hash-Baum
nach oben wandert, bis ein Nicht-Loch angetroffen wird (ähnlich einer
Suchoperation). Wenn die Abfrage das Ende von einem Durchlauf des
zusammengeführten
Hash-Behälters
erreicht, berechnet sie die nächste
Nummer b' des Hash-Behälters entsprechend
der Bitumkehr-/Biterhöhungsprozedur,
die vorstehend beschrieben wurde, mit einem Wert von d, der von
der Hash-Baum-Stufe h vom Abfrage-Cursor angegeben wird. Wenn der
neue Behälter
b' ein weiterer
Nachkomme des zusammengeführten
Hash-Behälters ist,
startet dies den nächsten
Durchlauf durch den zusammengeführten
Behälter
mit dem neuen Cursor-Wert c' =
(h, b', 0). Andernfalls
ist der letzte Durchlauf durch den zusammengeführten Behälter abgeschlossen und die
Abfrage wird normal mit dem Hash-Behälter b' und einem Cursor-Wert c' = (h'', b',
0) fortgesetzt, wobei h'' die Stufe des Hash-Baums
ist, die man im Kopfbereich des Behälters b' findet.
-
Ein
Programmierer kann das von uns beschriebene Verfahren in jeder Sprache
programmieren, in der der nachstehend zusammengefasste Algorithmus
für die
Abfrageoperation geschrieben werden kann:
-
- Eingabe: Cursor-Wert c = (h, b, r) Pufferspeicher zur Rückgabe von
einem oder mehreren Datensätzen
- Ausgabe: in dem bereitgestellten Pufferspeicher zurückgelieferte
Datensätze,
neuer Cursor-Wert
-
Hinweis:
Beim ersten Aufruf der Abfrageroutine sollte ein Cursor-Wert von
(0,0,0) übergeben
werden; bei nachfolgenden Aufrufen sollte der von dem vorherigen Aufruf
zurückgelieferte
Cursor-Wert dem nächsten Abfrageaufruf übergeben
werden.
- 1. Setze h' = h, b' = b
- 2. Lies den Hash-Behälter
b' aus der Datei
am Offset b*s, wobei s die Größe des Hash-Behälters ist.
Wenn der Hash-Behälter
b' nicht vorhanden
ist (die Datei enthält
ein Loch am Offset b'*s),
verringere h' um
eins, berechne b' neu
als b' modulo 2||h', und gehe zum Anfang
des Schritts 2 zurück.
- 3. Lege h' als
die Stufe des Hash-Baums fest, die man im Kopfbereich des Hash-Behälters b' findet. Wenn h,
b und r alle den Wert null haben (Beginn der Abfrage), setze h auf
denselben Wert wie h'.
- 4. Vergleiche h' mit
h. Fahre in Abhängigkeit
von dem Ergebnis des Vergleichs mit dem Schritt 5, 6 oder 7 fort,
wie nachstehend angegeben ist:
- 5. Wenn h' =
h:
Es sei angemerkt, das b in diesem Fall gleich b' sein muss.
5.1
Suche im Hash-Behälter
b nach dem nächsten
Datensatz an einem Offset, der größer als oder gleich r ist.
In Abhängigkeit
davon, ob es solch einen Datensatz noch gibt, fahre mit dem Schritt
5.2 oder dem Schritt 5.3 fort, wie nachstehend angegeben ist.
5.2
Wenn es einen solchen Datensatz gibt:
Prüfe, ob in dem bereitgestellten
Pufferspeicher noch Platz vorhanden ist, um den Datensatz zurückzugeben.
Wenn ja, kopiere den Datensatz in den bereitgestellten Pufferspeicher,
aktualisiere den Offset r in dem Abfrage-Cursor so, dass er der
nächste
Offset nach dem Datensatz ist, der soeben kopiert wurde, und gehe anschließend zum
Schritt 4 zurück.
Wenn
es in dem bereitgestellten Pufferspeicher keinen Speicherplatz mehr
gibt, verlasse die Abfrageroutine und gib den aktuellen Cursor-Wert
zurück.
5.3
Wenn es einen solchen Datensatz nicht gibt:
Berechne b'' so, dass er der nächste Hash-Behälter in
einem Tiefendurchlauf ist:
b'' =
reverse(reverse(b, h) + 1, h),
wobei Umkehr von (x, n) bedeutet,
die n niedrigstwertigen Bit von x zu nehmen und sie umzukehren.
Wenn
b'' gleich null ist,
haben wir das Ende der Abfrage erreicht. Verlasse in diesem Fall
die Abfrageroutine, und gib den aktuellen Cursor-Wert zurück.
Aktualisiere
andernfalls den Cursor c = (h, b, r) wie folgt: Setze b und b' gleich b''. Setze r auf null. Lies den von dem
neuen Wert von b angegebenen Hash-Behälter, und lege h und h' als die Stufe des
Hash-Baums fest, die man im Kopfbereich des Hash-Behälters findet.
Gehe dann zum Schritt 4 zurück.
- 6. Wenn h' > h: Dieser Fall bedeutet,
dass der Hash-Behälter b aufgeteilt
worden ist.
6.1 Falls noch nicht geschehen, stelle den Inhalt
des Hash-Behälters
b wieder so her, wie er vor der Aufteilung vorhanden war, indem
alle Nachkommen des Hash-Behälters
b in dem Hash-Baum
in einem temporären
Pufferspeicher zusammengeführt
werden. Dies ist für
den Behälter
b in einem vorherigen Wiederholungslauf möglicherweise schon geschehen;
in diesem Fall, kann dieser Schritt übersprungen werden.
6.2
Suche den nächsten
Datensatz im temporären
Pufferspeicher an einem Offset, der größer als oder gleich r ist.
In Abhängigkeit
davon, ob es solch einen Datensatz noch gibt, fahre mit dem Schritt
5.2 oder dem Schritt 5.3 fort, wie vorstehend angegeben ist.
- 7. Wenn h' < h:
Dieser
Fall bedeutet, dass der Hash-Behälter b mit
einem anderen Hash-Behälter
zusammengeführt
worden ist.
7.1 Suche den nächsten
Datensatz im Hash-Behälter b' an einem Offset,
der größer als
oder gleich r ist. In Abhängigkeit
davon, ob es solch einen Datensatz noch gibt, fahre mit dem Schritt
7.2 oder dem Schritt 7.3 fort, wie nachstehend angegeben ist.
7.2
Wenn es einen solchen Datensatz gibt:
Berechne den Hash-Wert
des Schlüssels
in dem Datensatz, und setze b'' so, dass es die
h niedrigstwertigen Bit des Hash-Werts darstellt. Wenn b'' ungleich b ist, überspringe diesen Datensatz,
d.h., aktualisiere den Offset r in dem Abfrage-Cursor so, dass er
der nächste
Offset nach diesem Datensatz ist, und gehe zum Schritt 7.1 zurück.
Prüfe, ob in
dem bereitgestellten Pufferspeicher noch Platz vorhanden ist, um
den Datensatz zurückzugeben;
wenn nicht, kehre mit dem aktuellen Cursor-Wert zurück.
Wenn
genügend
Platz vorhanden ist, kopiere den Datensatz in den bereitgestellten
Pufferspeicher und aktualisiere den Offset r in dem Abfrage-Cursor
so, dass er der nächste
Offset nach dem Datensatz ist, der soeben kopiert wurde.
Gehe
zum Schritt 4 zurück.
7.3
Wenn es einen solchen Datensatz nicht gibt:
Berechne b'' so, dass er der nächste Hash-Behälter in
einem Tiefendurchlauf ist:
b'' =
reverse(reverse(b, h) + 1, h)
Wenn b'' gleich
null ist, haben wir das Ende der Abfrage erreicht. Verlasse in diesem
Fall die Abfrageroutine, und gib den aktuellen Cursor-Wert zurück.
Prüfe andernfalls,
ob (b mod 2¬h') gleich (b' mod 2¬h') ist. Wenn ja, bedeutet
dies, dass der nächste
zu besuchende Behälter
noch einer der Behälter
ist, die zum Behälter
b' zusammengeführt worden
sind. Setze in diesem Fall r auf null, und gehe zum Anfang des Schritts
7 zurück,
was den nächsten
Durchlauf durch den zusammengeführten
Behälters
b' startet.
Andernfalls
ist der letzte Durchlauf des zusammengeführten Behälters beendet. Fahre in diesem
Fall wie im Schritt 5.3 angegeben fort, d.h., setze b und b' auf b'', setze r auf null, lege h und h' als die Stufe des Hash-Baums
fest, die man im Kopfbereich des Hash-Behälters b findet, und gehe anschließend zum
Schritt 4 zurück.
-
Nachdem
wir diese Ausführungsart
unserer in Folge stattfindenden Abfrageprozedur beschrieben haben,
wenden wir uns nun dem zur Codierung des Cursor-Werts angewandten
Verfahren zu.
-
Um
die Anzahl der Bits, die erforderlich sind, um einen Cursor-Wert
aufzunehmen, auf ein Mindestmaß zu
verringern, können
die Stufe des Hash-Baums und die Nummer des Hash-Behälters
zu einem einzigen Wert zusammengefasst werden, der nur ein Bit mehr
als die Anzahl der Bits erfordert, die zur Aufnahme der größten zulässigen Behälternummer
benötigt
werden. Dies ist möglich,
weil die Behälternummer
immer kleiner als oder gleich 2 ¬ L
sein muss, wobei L die Stufe ist. Die Codierung wird nachstehend
beschrieben. Ein von dieser Codierung verwendeter Parameter ist
die größtmögliche Stufe
des Hash-Baums, d.h. die größtmögliche Tiefe,
die eine Verzweigung des Baumes erreichen kann.
-
Die
Codierung des Cursors für
die Stufe L des Hash-Baums und die Nummer B des Hash-Behälters ist
wie folgt:
Sei M = die größtmögliche Stufe
des Hash-Baums
Berechne H = M – L
Berechne R = bitweise
Umkehr von B
Codiere die Behälternummer und die Stufe als
2¬H + R*2¬(H + 1).
-
Zähle zur
Decodierung die Anzahl der niederwertigen Null-Bits, und subtrahiere
diesen Wert von M, um die Stufe (L) zu erhalten. Um die Behälternummer
zu erhalten, verschiebe den codierten Wert um L + 1 Bit nach rechts,
und führe
eine bitweise Umkehr des Ergebnisses durch.
-
Selbstverständlich kann
sich der Fachmann auch optionale Funktionen vorstellen, nachdem
er diese Beschreibung gelesen hat. Zum Beispiel kann das System
eine Sperr- und Gleichzeitigkeitssteuerung durchführen, um
gleichzeitige Aktualisierungen in verschiedenen Hash-Behältern zu
ermöglichen
und auch Überlaufblöcke zu realisieren.
Da wir nicht wirklich einen temporären Pufferspeicher benötigen, um
Aufteilungen während
einer in Folge stattfindenden Abfrage durchzuführen, könnten wir den vom Aufrufer
bereitgestellten Pufferspeicher verwenden. Insbesondere könnte man
sich in den Fällen,
in denen es keinen Sinn macht, einen ganzen Behälter wiederherzustellen, nur
um einen einzigen Datensatz zurückzuliefern,
Anwendungen vorstellen, die eine Schnittstelle für eine in Folge stattfindende
Abfrage verwenden, welche nur jeweils einen Datensatz (z.B. database?)
zurückliefert.
-
Zuordnen von Speicherplatz
in einem Dateisystem mit gemeinsam benutzten Platten
-
Die
parallele Zuordnung ist ein Merkmal unserer bevorzugten Ausführungsform.
Dies bedeutet, dass wir zur Codierung eine Zuordnungsliste bereitstellen
(z.B. eine Bitmap), die im Vergleich zu einer herkömmlich codierten
Zuordnungsliste die Beeinträchtigungen
zwischen mehreren Knoten verringert, die gleichzeitig Plattenblöcke auf
mehreren Platten zuordnen, welche eine Dateistruktur mit gemeinsam
benutzten Platten bilden. Unser System ermöglicht auch die gleichzeitige Freigabe
von Plattenblöcken
durch mehrere Knoten bei geringerer gegenseitiger Beeinträchtigung.
-
Zwar
gibt es Zuordnungskonzepte, die in einem Dateisystem realisiert
werden, und es gibt auch herkömmliche
Verfahren, die von einem Dateisystem zur Zuordnung von Speicherplatz
verwendet werden können,
doch gibt es Probleme bei den herkömmlichen Verfahren, die in
einem Dateisystem mit gemeinsam benutzten Platten angewendet werden,
und dies hat eine Erfindung notwendig gemacht, die die Zuordnung
und die Freigabe von Speicherplatz ermöglicht und die in einem Dateisystem
mit gemeinsam benutzten Platten, wie sie in einem parallelen Dateisystem
verwendet werden, gut funktioniert.
-
Im
Allgemeinen ist ein Dateisystem ein Rechnerprogramm, das es anderen
Anwendungsprogrammen ermöglicht,
Daten auf Datenträgern
wie zum Beispiel Plattenlaufwerken zu speichern und von ihnen abzurufen. Der
Kürze halber
wird in der nachfolgenden Erörterung
der Begriff Platte verwendet, aber die Konzepte gelten für jedes ähnliche
Speichermedium mit Blockstruktur. Eine Datei ist ein benanntes Datenobjekt,
das eine beliebige Größe haben
kann. Das Dateisystem ermöglicht
Anwendungsprogrammen, Dateien anzulegen und einen Namen für sie zu
vergeben, Daten in ihnen zu speichern (oder Daten in sie zu schreiben),
Daten aus ihnen zu lesen, sie zu löschen und andere Operationen
an ihnen durchzuführen.
-
Im
Allgemeinen handelt es sich bei einer Dateistruktur um die Anordnung
von Daten auf den Plattenlaufwerken. Neben den Dateidaten selbst
enthält
die Dateistruktur Metadaten: ein Verzeichnis, das den entsprechenden
Dateien Dateinamen zuordnet, Datei-Metadaten, die Informationen über die
Datei enthalten, vorrangig den Speicherort der Dateidaten auf der
Platte (d.h., welche Plattenblöcke
die Dateidaten enthalten), eine Zuordnungsliste, die erfasst, welche
Plattenblöcke
gerade zur Speicherung von Metadaten und Dateidaten verwendet werden,
und einen Superblock, der allgemeine Informationen über die
Dateistruktur (z.B. den jeweiligen Speicherort des Verzeichnisses,
der Zuordnungsliste und anderer Metadatenstrukturen) enthält.
-
Andererseits
muss man erkennen, dass ein Dateisystem mit gemeinsam benutzten
Platten ein Dateisystem ist, bei dem auf eine Dateistruktur, die
sich auf einer oder auf mehreren Platten befindet, von mehreren Dateisystemen
zugegriffen wird, die auf getrennten Rechnern laufen. Zum Zweck
unserer bevorzugten Ausführungsform
gehen wir hinsichtlich der Dateistruktur davon aus, dass diese Rechner
(oder Knoten) keinen gemeinsam benutzten Speicher haben (obgleich
sie über
einen lokalen Speicher und mindestens einen Teil eines gemeinsam
benutzten Speichers, wie ihn viele SMPs haben, verfügen könnten und
in vielen geeigneten Ausführungsarten
auch darüber
verfügen
würden)
und mit den Platten, auf denen sich die Dateistruktur befindet, über ein
Mittel wie beispielsweise einen Bus oder ein Vermittlungsnetzwerk
verbunden sind, von denen jedes für diese Zwecke als ein Übertragungsnetzwerk
betrachtet werden kann. Darüber
hinaus gehen wir davon aus, dass die Knoten über ein ähnliches Mittel miteinander
Daten austauschen. Ein Dateisystem mit gemeinsam benutzten Platten
ermöglicht
es, eine Berechnung, die die Dateistruktur verwendet, in mehrere
Teile zu gliedern, die parallel auf mehreren Knoten ausgeführt werden
können.
Dadurch kann die Verarbeitungsleistung von diesen mehreren Knoten
bei der Berechnung vorteilhaft zum Tragen kommen.
-
Eine
Zuordnungsliste ist Teil unserer Dateistruktur. Betrachten wir eine
Dateistruktur, die auf N Platten, D0, D1, DN-1, gespeichert ist.
Jeder Plattenblock in der Dateistruktur wird von einem Paar (i,
j) gekennzeichnet, zum Beispiel kennzeichnet (5,254) den 254. Block
auf der Platte D5. Die Zuordnungsliste wird üblicherweise in einer Matrix
A gespeichert, wobei der Wert des Elements A(i, j) den Zuordnungszustand
(zugeordnet/frei) des Plattenblocks (i, j) angibt.
-
Die
Zuordnungsliste wird üblicherweise
auf der Platte als Teil der Dateistruktur gespeichert, die sich
in einem oder mehreren Plattenblöcken
befindet. Herkömmlicherweise
ist A(i, j) das k-te
sequenzielle Element in der Liste, wobei k = M + j und M eine Konstante
ist, die größer als
die höchste
Blocknummer auf einer beliebigen Platte ist.
-
Um
einen freien Block des Plattenspeichers zu finden, liest das Dateisystem
einen Block A in einen Speicherpuffer und durchsucht den Pufferspeicher,
um ein Element A(i, j) zu finden, dessen Wert anzeigt, dass der
entsprechende Block (i, j) frei ist. Vor der Verwendung des Blocks
(i, j) aktualisiert das Dateisystem den Wert von A(i, j) in dem
Pufferspeicher, um anzuzeigen, dass der Block (i, j) den Zustand "zugeordnet" hat, und dann schreibt
es den Inhalt des Pufferspeichers auf die Platte zurück. Um einen
Block (i, j) freizugeben, der nicht mehr gebraucht wird, liest das
Dateisystem den Block, der A(i, j) enthält, in einen Pufferspeicher,
aktualisiert den Wert von A(i, j), um anzuzeigen, dass der Block
(i, j) freigegeben ist, und schreibt den Block vom Pufferspeicher
auf die Platte zurück.
-
Die
Steuerung des gemeinsamen Zugriffs auf die Zuordnungsliste stellt
ein besonderes Erfordernis dar. Wenn die Knoten, die ein Dateisystem
mit gemeinsam benutzten Platten bilden, ihren Zugriff auf die gemeinsam
benutzten Platten nicht ordnungsgemäß synchronisieren, können sie
die Dateistruktur beschädigen. Dies
gilt insbesondere für
die Zuordnungsliste. Um dies zu veranschaulichen, betrachten wir
den Prozess der Zuordnung eines freien Blocks, der vorstehend beschrieben
wurde. Nehmen wir an, zwei Knoten versuchen gleichzeitig, einen
Block zuzuordnen. Dabei könnten
sie beide denselben Zuordnungslistenblock lesen, beide dasselbe
Element (A(i, j) finden, das den freien Block (i, j) beschreibt,
beide A(i, j) aktualisieren, um anzuzeigen, dass der Block (i, j)
zugeordnet ist, beide den Block zurück auf Platte schreiben und
beide damit beginnen, den Block (i, j) für verschiedene Zwecke zu nutzen,
wodurch sie die Unversehrtheit der Dateistruktur verletzen würden. Ein
subtileres, aber genauso ernstes Problem tritt selbst dann auf,
wenn die Knoten gleichzeitig verschiedene Blöcke, X und Y, zuordnen, sowohl
A(X) als auch A(Y) aber in demselben Zuordnungsblock enthalten sind.
In diesem Fall setzt der erste Knoten A(X) auf "zugeordnet", der zweite Knoten setzt A(Y) auf "zugeordnet", und beide schreiben
gleichzeitig ihre im Pufferspeicher abgelegten Kopien des Zuordnungsblocks
auf Platte. In Abhängigkeit
davon, welche Schreiboperation zuerst durchgeführt wird, erscheint entweder
der Block X oder der Block Y in dem Abbild auf der Platte frei.
Wenn zum Beispiel die Schreiboperation des zweiten Knotens nach
der Schreiboperation des ersten Knotens durchgeführt wird, ist der Block X in
dem Abbild auf der Platte frei. Der erste Knoten beginnt mit der
Verwendung des Blocks X (zum Beispiel, um einen Datenblock einer
Datei zu speichern), aber zu einem späteren Zeitpunkt könnte ein
anderer Knoten den Block X zu einem anderen Zweck zuordnen, wieder
mit der Folge, dass die Unversehrtheit der Dateistruktur verletzt
wird.
-
Um
zu verhindern, dass die Dateistruktur beschädigt wird, muss ein Knoten
für jeden
Bit-Zuordnungsblock eine Zeichenfolge (Token) abrufen, bevor er
ihn in den Speicher lesen kann, und wenn der Knoten den Block ändert (d.h.
indem er einen Block zuordnet oder freigibt), muss er den Block
auf Platte schreiben, bevor er das Token freigibt. Token erhält man normalerweise
von einem Verwaltungsprogramm für
verteilte Token ("distributed
token manager"),
an das sie auch freigegeben werden, wie zum Beispiel dem in der
US-Patentschrift 5 454 108 beschriebenen
Sperrenverwaltungsprogramm. Der zusätzliche Aufwand für den Erhalt
von Token vom Token-Verwaltungsprogramm und für das Zurückschreiben von Zuordnungsblöcken auf
Platte, bevor ein auf dem Block gehaltenes Token freigegeben wird,
kann die Leistungsfähigkeit
eines Dateisystems mit gemeinsam benutzten Platten erheblich herabsetzen.
-
Wir
gestatten es, Daten auf mehrere Platten zu verteilen, wie es in
einer RAID-Umgebung der Fall ist. Das Verteilen von Daten auf mehrere
Platten (Striping) ist ein Verfahren zur Speicherung von aufeinander
folgenden Datenblöcken
(z.B. von einer Datei) auf verschiedenen Platten. Zu den Vorteilen
des Striping gehören eine
hohe Leistungsfähigkeit
und die Verteilung der Last. Beim Striping schreibt das Dateisystem aufeinander folgende
Blöcke
einer Datei auf verschiedene Platten, und zwar entsprechend einer
zyklischen Umordnung (Permutation) der Plattennummern 0, ..., N-1.
Bei der herkömmlich
strukturierten Zuordnungsliste müssen
zum Schreiben einer Datei mit N Blöcken oder einer längeren Datei
N Zuordnungsblöcke
(oder die gesamte Zuordnungsliste, wenn sie kleiner als N Blöcke ist)
gesperrt, gesucht, aktualisiert und geschrieben werden. Dieser zusätzliche
Aufwand, der hierfür
notwendig ist, ist wesentlich höher
als die fortlaufende Zuordnung von N Blöcken auf einer einzelnen Platte.
In einem Dateisystem mit gemeinsam benutzten Platten kann überdies
der Knoten, der die Datei schreibt, beträchtliche Verzögerungen
erfahren, während
er darauf wartet, dass andere Knoten Sperren freigeben, mit denen
die benötigten
Zuordnungslistenblöcke
belegt sind.
-
Vor
diesem Hintergrund haben wir eine Plattenzuordnungseinheit bereitgestellt,
die eine segmentierte Zuordnungsliste verwendet, welche das Speichern
und Verwalten einer Zuordnungsliste ermöglicht, die das Verteilen von
Dateien auf mehrere Platten unterstützt, während der für das Belegen mit Sperren,
für die Ein-/Ausgabe
und die Suche notwendige zusätzliche
Aufwand bei der Zuordnung von Blöcken
so gering wie möglich
gehalten wird. Im Vergleich zu der herkömmlichen Zuordnungsliste, die
vorstehend beschrieben wurde, verringert unsere Plattenzuordnungseinheit
die Anzahl der Zuordnungslistenblöcke erheblich, auf die bei der
Zuordnung einer auf mehrere Platten verteilten Datei zugegriffen
wird. In einem Dateisystem mit gemeinsam benutzten Platten verringert
sie überdies
die Sperren-Konflikte und die Lese- und Schreiboperationen für den Zuordnungslistenblock,
wenn mehrere Knoten gleichzeitig Dateien zuordnen, die auf mehrere
Platten verteilt sind.
-
Die
grundlegende Idee hinter der Plattenzuordnungseinheit, die hier
beschrieben wird, besteht in der Unterteilung der Zuordnungsliste
in mehrere Bereiche. Wenn die Liste in K Bereiche aufgeteilt wird,
steuert jeder Bereich 1/K der Blöcke
auf jeder der N Platten. Das Dateisystem sperrt Bereiche und nicht
einzelne Zuordnungslistenblöcke,
um den Zugriff auf die Liste zu synchronisieren. Durch die Verwendung
von verschiedenen Bereichen können
mehrere Knoten gleichzeitig Dateien zuordnen, die auf mehrere Platten
verteilt sind, ohne sich gegenseitig zu beeinträchtigen.
-
Bei
Platten mit M Blöcken
enthält
jeder Bereich MN/K Elemente der Zuordnungsliste. Idealerweise passen
diese MN/K Elemente in einen einzelnen Zuordnungslistenblock, aber
wenn die Anzahl der Platten (oder die Größe einer jeden Platte) groß genug
ist oder wenn die Anzahl der Bereiche klein genug ist, können Bereiche
größer als
Zuordnungslistenblöcke
sein. Damit die Zuordnungsliste die gleiche Blockgröße wie gewöhnliche
Dateien verwendet, werden Bereiche aus einem oder mehreren Segmenten
gebildet, wobei jedes Segment höchstens
die Größe eines
Zuordnungsblocks hat und die Zuordnung von Blöcken auf einer Teilmenge der
N Platten steuert. Wenn Bereiche weniger als halb so groß wie Zuordnungsblöcke sind,
werden mehrere Bereiche in jeden einzelnen Zuordnungsblock gepackt.
-
Die
Parameter, die den Aufbau der segmentierten Zuordnungsliste bestimmen,
sind die Anzahl der Bereiche, K, sowie die Anzahl der Platten, N,
und die Plattenkapazität,
die als die Anzahl der Blöcke
pro Platte, M, ausgedrückt
wird. Die Anzahl der Bereiche sollte so gewählt werden, dass sie mindestens
so groß wie
die Anzahl der Dateisystemknoten ist, so dass jeder Knoten Zuordnungen
von einem anderen Bereich vornehmen kann.
-
Wenn
B Zuordnungslistenelemente in einen Block passen, ist die Mindestzahl
der Blöcke
und folglich die Mindestzahl der Segmente, die zur Speicherung eines
jeden Bereichs erforderlich sind, gegeben durch
ceil((NM/K)/B),
da
jeder Bereich 1/K der Elemente für
jede Platte speichert, d.h. NM/K Elemente je Bereich. Um einen Block auf
einer bestimmten Platte zuzuordnen, ist es jedoch wünschenswert,
alle Elemente der Zuordnungsliste, die auf dieselbe Platte verweisen,
in demselben Segment, d.h. in demselben Block der Zuordnungsliste,
zu halten. Bei dieser Vorgabe kann jedes Segment Zuordnungselemente
für d verschiedene
Platten halten, wobei d gegeben ist durch
d = floor(B/(/K)
= floor(BK/M).
-
Es
sei angemerkt, dass K so gewählt
werden muss, dass es mindestens M/B ist: Andernfalls hat d den Wert
null, d.h., die Elemente der Zuordnungsliste, die auf dieselbe Platte
verweisen, passen nicht in einen einzelnen Block. Die Anzahl der
Segmente je Bereich ist deshalb gegeben durch:
L = ciel(N/d)
= ceil(N/floor(BK/M)).
-
Wir
verwenden die Notation S(p, q), um auf das q-te Segment des p-ten
Bereichs der Zuordnungsliste zu verweisen, wobei p von 0 bis K-1
und q von 0 bis L-1 reicht. Die Elemente der Zuordnungsliste werden
dann wie folgt Segmenten zugewiesen. Das Element A(i, j), das den
Zuordnungszustand des j-ten Blocks auf der i-ten Platte angibt,
wird im Segment S(p, q) gespeichert, wobei
p j mod K
und
q
= floor(i/d).
-
Segmente
werden in aufeinander folgenden Zuordnungslistenblöcken in
der folgenden Reihenfolge angeordnet:
S(0,0), S(1,0), S(2,0),
..., S(K-1,0),
S(0,1), S(1,1), S(2,1), ..., S(K-1,1),
...
S(0,L-1),
S(1,L-1), S(2,L-1), ..., S(K-1,L-1).
-
Anders
ausgedrückt,
das erste Segment eines jeden Bereichs wird am Anfang der Zuordnungsliste
gespeichert, gefolgt von dem zweiten Segment eines jeden Bereichs
und so weiter. Diese Anordnung macht es möglich, das Dateisystem zu erweitern,
indem weitere Platten hinzugefügt
werden, ohne dass die Zuordnungsliste komplett umgestaltet werden
muss: Das Hinzufügen
von weiteren Platten zu dem Dateisystem macht es erforderlich, dass
mehr Elemente der Zuordnungsliste in jedem Bereich gespeichert werden,
wodurch jeder Bereich gegebenenfalls um ein oder mehrere Segmente
erweitert werden muss. (Die Zahl der erforderlichen Segmente wird
durch Neuberechnung von L mit einem neuen Wert für N ermittelt). Die zusätzlichen
Segmente werden einfach an das Ende der vorhandenen Zuordnungsliste
angefügt.
-
Um
aufeinander folgende Blöcke
einer auf mehreren Platten verteilten Datei zuzuordnen, beschafft sich
ein Knoten ein Token für
einen Bereich und ordnet aufeinander folgende Blöcke entsprechend der für das Verteilen
von Daten auf mehreren Platten vorgesehenen Permutation unter Verwendung
von freien Blöcken
in dem Bereich zu (d.h. von Blöcken,
deren Zuordnungslistenelemente ihren Zustand als "frei" angeben). Vor der Freigabe
des Token schreibt der Knoten den Bereich auf die Platte zurück. Wenn
bei dem Versuch, einen Block auf einer bestimmten Platte zuzuordnen,
festgestellt wird, dass der Bereich keinen freien Block auf dieser
Platte enthält,
wechselt der Knoten die Bereiche: Er schreibt den Bereich auf Platte
zurück
und gibt das Token frei, ruft dann ein Token für einen anderen Bereich ab
und versucht, von diesem Bereich eine Zuordnung vorzunehmen. Wenn
der Knoten bei dem Versuch, einen freien Block auf einer bestimmten
Platte zu finden, alle Bereiche ohne Erfolg ausprobiert, kann er
(in Abhängigkeit
von den für
das Verteilen von Daten auf mehreren Platten gültigen Regeln) entweder einen
Block auf einer anderen Platte zuordnen oder eine Zustandsmeldung "nicht genügend Speicherplatz
vorhanden" ("out of space") an die Anwendung
zurücksenden.
Im ersteren Fall, wenn alle Platten ohne Erfolg ausprobiert wurden,
schickt das Dateisystem die Zustandsmeldung "nicht genügend Speicherplatz vorhanden" zurück. Als
Leistungsverbesserung würde
das Dateisystem üblicherweise
anderen Knoten gestatten, zwischen Schreiboperationen von Dateiblöcken das
Token für
ihren Bereich zu "stehlen". Als Reaktion auf
die Anfrage, das Token stehlen zu dürfen, schreibt der Knoten den
Bereich auf die Platte und tritt das Token ab. Um die Zuordnung
eines Blocks aufzuheben, führt
das Dateisystem in dem Bereich, der die Zuordnungsliste enthält, welche
den Block beschreibt, eine Leseoperation durch, aktualisiert seinen
Zustand auf "frei" und schreibt den
Bereich auf Platte zurück,
bevor es das Token freigibt.
-
Zwar
verringern der vorstehend beschriebene Aufbau der Zuordnungsliste
und der vorstehend beschriebene Algorithmus die gegenseitige Beeinträchtigung
von Knoten, die zur gleichen Zeit Dateien erstellen, beträchtlich,
doch ist eine gewisse Beeinträchtigung
dennoch möglich.
Dies liegt daran, dass ein Knoten beim Wechseln von Bereichen über keine
Informationen verfügt,
auf deren Grundlage er eine Auswahl des Bereichs, in den er wechseln
könnte,
treffen könnte.
Idealerweise sollte er in einen Bereich wechseln, der gerade nicht von
einem anderen Knoten verwendet wird und der über ausreichend freie Blöcke verfügt, damit
er seine Schreiboperationen ohne weitere Bereichswechsel fortsetzen
kann.
-
Um
ein Mittel bereitzustellen, das es einem Knoten ermöglicht,
seine Bereichswahl gut informiert zu treffen, führen wir ein Zuordnungsverwaltungsprogramm
(allocation manager) ein, bei dem es sich um ein Programm handelt,
das überwacht,
welcher Knoten (gegebenenfalls) jeden Zuordnungsbereich verwendet
und wie viel freier Speicherplatz in jedem Bereich ungefähr übrig bleibt.
Während
der Initialisierung des Dateisystems prüft das Zuordnungsverwaltungsprogramm
jeden Bereich, um die Anzahl der freien Blöcke in jedem Bereich zu zählen, und
hinterlegt diese Informationen in einer Tabelle. Vor einem Bereichswechsel
sendet ein Knoten des Dateisystems eine Nachricht an das Zuordnungsverwaltungsprogramm,
um es über
den Bereich in Kenntnis zu setzen, aus dem er wechselt (einschließlich des
derzeit freien Speicherplatzes in dem Bereich), und um einen Bereich
vorgeschlagen zu bekommen, in den er wechseln könnte. Das Zuordnungsveraltungsprogramm
aktualisiert seine Tabelle, um den freien Speicherplatz in dem Bereich
anzuzeigen, aus dem der Wechsel stattfindet, und um ihn als einen
nicht mehr verwendeten Bereich auszuweisen. Anschließend prüft es seine
Tabelle, um einen anderen Bereich zu ermitteln, der nicht verwendet
wird und der den größten freien Speicherplatz
aufweist, es antwortet dem Dateisystemknoten unter Angabe der Nummer
dieses Bereichs und aktualisiert seine Tabelle, um anzuzeigen, dass
der Bereich verwendet wird. Wenn alle anderen Bereiche bereits verwendet
werden, wählt
das Zuordnungsverwaltungsprogramm willkürlich einen Bereich aus. Dieses Protokoll
verringert die Anzahl der Bereichswechsel, indem ein Wechsel in
nicht verwendete Bereiche begünstigt
wird.
-
Obgleich
der vorstehende Algorithmus Zugriffe auf die Zuordnungsliste zum
Zweck des Anlegens von Dateien begrenzt, ist es dennoch möglich, dass
Löschoperationen
von Dateien häufige
Bereichswechsel verursachen und deshalb Knoten beeinträchtigen,
die gleichzeitig Dateien erstellen. Selbst wenn die Blöcke in einzelnen
Dateien auf einen einzigen Bereich begrenzt werden, kommt es dennoch
häufig
vor, dass ein Knoten ein paar Dateien löscht (z.B. den Inhalt eines
Verzeichnisses), die von verschiedenen Knoten oder zu verschiedenen
Zeitpunkten angelegt und deshalb aus verschiedenen Bereichen zugeordnet
wurden. Dies führt
zu einer Aufhebung der Zuordnung und verursacht folglich häufige Bereichswechsel.
-
Um
diese Bereichswechsel zu verringern, stellen das Zuordnungsverwaltungsprogramm
und das Dateisystem Mittel bereit, um die Aufhebung der Zuordnung
des Blocks (gegebenenfalls) dem Knoten zu übertragen, der gerade den Bereich
verwendet, welcher den Block steuert, der gerade freigegeben wird.
Dies geht folgendermaßen
vonstatten:
Um einen Block zu löschen, sendet das Dateisystem
zuerst eine Nachricht an das Zuordnungsverwaltungsprogramm, um die
Kennung des Knotens zu erhalten, der den Bereich gerade verwendet.
Das Zuordnungsverwaltungsprogramm antwortet unter Angabe der Kennung
des Knotens oder eines Hinweises, dass der Bereich derzeit nicht
verwendet wird. Im letzteren Fall hebt der Knoten die Zuordnung
des Blocks auf. Im ersteren Fall sendet der Knoten eine Nachricht
an denjenigen Knoten, der von dem Zuordnungsverwaltungsprogramm angegeben
wird, und weist ihn an, die Zuordnung des Blocks aufzuheben. Wenn
der zweite Knoten den Bereich tatsächlich verwendet, hebt er die
Zuordnung des Blocks auf und teilt dies dem ersten Knoten in einer Antwort
mit. Wenn der zweite Knoten den Bereich gerade nicht verwendet,
antwortet er dem ersten Knoten, um ihn darüber zu informieren, woraufhin
der erste Knoten die Zuordnung des Blocks aufhebt.
-
Um
den Nachrichtenverkehr zu verringern, können Nachrichten über die
Aufhebung der Zuordnung im Stapelbetrieb verarbeitet werden. Beim
Löschen
einer Datei können
die Blöcke,
die zu der Datei gehören,
beispielsweise nach dem Zuordnungsbereich sortiert werden, und eine
einzige Nachricht über
die Aufhebung der Zuordnung, die Blöcke enthält, welche zu demselben Bereich gehören, kann
dann an den Knoten gesendet werden, der diesen Bereich gerade verwendet.
-
Handhabung der Beeinträchtigung von Dateisystemen
mit gemeinsam benutzten Platten:
-
Unser
System gestattet mehreren Knoten, die ein Dateisystem mit gemeinsam
benutzten Platten bilden, Speicherbereich ohne unnötige gegenseitige
Beeinträchtigung
gleichzeitig zuzuordnen. Verschiedene Verbesserungen wurden vorgenommen,
um dies zu erreichen.
-
Dynamischer Vorababruf bei
einem skalierbaren parallelen Dateisystem
-
Der
Vorababruf ist ein Verfahren, das in Dateisystemen angewendet wird,
um die E/A-Latenzzeit zu verringern, indem Blöcke von Dateien, auf die in
Folge zugegriffen wird, im Voraus gelesen werden, d.h., bevor die
Daten von Anwendungsprogrammen angefordert werden. Unser System
geht das Problem der dynamischen Terminierung und Anpassung von
Dateisystem-Ressourcen an, die für
den Vorababruf bestimmt sind, um einen höchstmöglichen Durchsatz zu erzielen
und die E/A-Latenzzeit in einem parallelen Dateisystem auf ein Mindestmaß zu verringern,
d.h. in einem Dateisystem, in dem Daten für dieselbe Datei über mehrere
Platteneinheiten verteilt sind.
-
In
dem System gibt es einen Systemdienst, der als "Pufferspeicherverwaltungsprogramm" ("buffer manager") bezeichnet wird
und über
die Inanspruchnahme der Speicherressourcen durch die verschiedenen
Systemkomponenten, die um den Speicher konkurrieren, entscheidet.
Jede Komponente muss dem Pufferspeicherverwaltungsprogramm Informationen
bereitstellen, die es benötigt,
um zu entscheiden, wie viel Speicherplatz jeder Komponente zugeordnet
wird. Diese Informationen bestehen aus den folgenden zwei Zahlen:
- 1. der gewünschten
Speichergröße. Diese
Zahl gibt an, wie viel Speicherplatz eine Komponente tatsächlich nutzen
könnte,
sofern er verfügbar
wäre.
- 2. dem aktuellen Auslastungsgrad. Diese Zahl muss ein Maß für die Häufigkeit
der Speicherbelegung einer Komponente angeben, wobei dies üblicherweise
in Form des Speichervolumens ausgedrückt wird, auf das je Zeiteinheit
zugegriffen wird.
-
Das
Pufferspeicherverwaltungsprogramm wiederum informiert jede Komponente über das
Speichervolumen, das es zur Verwendung durch diese Komponente zugeordnet
hat.
-
Eine
der Komponenten, die um Ressourcen konkurrieren, ist die Pufferspeichergruppe
des Dateisystems, die zur Zwischenspeicherung von Dateidaten, auf
die unlängst
zugegriffen wurde, und von Daten, die für aufeinanderfolgende Leser
vorab abgerufen wurden, verwendet wird. Wir stellen dem Pufferspeicherverwaltungsprogramm
geeignete Informationen bereit, damit es für den Vorababruf benötigte Ressourcen
berücksichtigen
kann, und terminieren die von dem Pufferspeicherverwaltungsprogramm
zugewiesenen Ressourcen, um einen höchstmöglichen Durchsatz des Dateisystems
zu erzielen und die E/A-Latenzzeit so gering wie möglich zu
halten.
-
Das
Folgende gibt einen Überblick
darüber,
wie dies realisiert wird. Weitere Einzelheiten sind in den Tabellen
3 und 4 aufgeführt
und werden im Anschluss an diese Übersicht ausführlicher
erklärt.
-
- – Die
Pufferspeichergruppe des Dateisystems wird logisch in zwei Teile
aufgeteilt, wobei einer für
den Vorababruf ("Vorababruf-Gruppe") und einer für die Zwischenspeicherung
von Dateiblöcken,
auf die unlängst zugegriffen
wurde ("allgemeine
Gruppe"), verwendet
wird. Mit "logisch
aufgeteilt" meinen
wir, dass einzelne Pufferspeicher nicht ausdrücklich der einen Gruppe oder
der anderen Gruppe zugewiesen werden müssen; vielmehr stellt sich
diese Aufteilung in der Form dar, dass eine einzelne Zahl verwaltet
wird, die anzeigt, wie viel von dem gesamten Pufferspeicherplatz
für den
Vorababruf verwendet werden soll.
- – Diese
beiden Gruppen stellen sich dem Pufferspeicherverwaltungsprogramm
als zwei getrennte Komponenten dar, d.h., das Dateisystem berechnet
getrennte gewünschte
Speichergrößen und
den Auslastungsgrad für
die allgemeine Gruppe und für
die Vorababruf-Gruppe.
- – Der
Auslastungsgrad von beiden Gruppen wird mit Hilfe von herkömmlichen
Verfahren wie zum Beispiel der Anzahl der Referenzen berechnet,
die die Datenzugriffsraten messen. Da die beiden Gruppen nur logisch
getrennt sind, geschieht dies, indem für jede Gruppe getrennte Zählstände erfasst
werden; bei jedem Zugriff auf den Pufferspeicher wird der entsprechende
Zählstand
auf der Grundlage der Feststellung aktualisiert, ob der Zugriff
auf den Pufferspeicher mittels sequenzieller oder zufälliger Ein-/Ausgabe
stattfindet.
- – Die
gewünschte
Größe der allgemeinen
Gruppe wird berechnet, indem unter Verwendung von Referenzbits und
Zählern
Arbeitsmengen gemessen werden, um die gesamte Menge an bestimmten
Dateidaten zu ermitteln, auf die über einen gewissen Zeitraum
zugegriffen wurde.
- – Die
gewünschte
Größe der Vorababruf-Gruppe
wird jedoch anders berechnet. Bei dieser Berechnung werden die Anzahl
und das Leistungsvermögen
der Platteneinheiten berücksichtigt,
die zu dem Dateisystem gehören,
sowie die Anzahl der Dateien, auf die in Folge zugegriffen wird,
und die Geschwindigkeit, mit der die Daten gelesen werden. Diese
Berechnung wird nachstehend näher
erklärt
und ist in Tabelle 3 ausführlich
beschrieben.
- – Die
in dem vorherigen Schritt berechnete jeweilige Anzahl wird dem Pufferspeicherverwaltungsprogramm bereitgestellt,
das sie zur Ermittlung des Speichervolumens verwendet, das den beiden
Komponenten zuzuweisen ist, die die allgemeine Gruppe und die Vorababruf-Gruppe
des Dateisystems darstellen. Das Dateisystem legt die Gesamtgröße seiner
Pufferspeichergruppe als die Summe des Speichervolumens fest, das
diesen beiden Komponenten zugewiesen wird. Mittels des Speichervolumens,
das der Komponente zugewiesen wird, die die Vorababruf-Gruppe darstellt,
wird die vorab abzurufende Datenmenge ermittelt.
-
Die
in den Tabellen 3 und 4 gezeigten Algorithmen lassen sich am besten
erklären,
indem mit einem einfachen Beispiel einer einzigen Anwendung begonnen
wird, die Daten aus einer Datei liest, die auf einem nichtparallelen
(nur über
eine Platte verfügenden)
Dateisystem gespeichert ist; anschließend prüfen wir, wie mit mehreren Anwendungen
und Dateisystemen mit mehreren Platten verfahren wird.
-
In
dem einfachen Beispiel reicht eine Doppelpufferung (zwei Vorababruf-Pufferspeicher)
aus, um einen optimalen Durchsatz und eine optimale Leistung zu
erreichen. Wenn die Anwendung mit dem Lesen der Datei beginnt, liest
das Dateisystem den ersten Block der Datei in einen der Vorababruf-Pufferspeicher.
Sobald die erste E/A-Operation abgeschlossen wird, liest das Dateisystem
den zweiten Block der Datei in den anderen Vorababruf-Pufferspeicher.
Während
die zweite E/A-Operation in Ausführung
befindlich ist, werden Leseanforderungen von der Anwendung erfüllt, indem
Dateidaten aus dem ersten Pufferspeicher abgerufen werden. Wenn
das Ende des ersten Pufferspeichers erreicht ist, können nachfolgende
Leseanforderungen aus dem zweiten Pufferspeicher erfüllt werden,
sobald die zweite E/A-Operation abgearbeitet ist. Sobald die zweite E/A-Operation
abgeschlossen ist und die Anwendung das letzte Byte von dem ersten
Block gelesen hat, wird der erste Vorababruf-Pufferspeicher erneut verwendet, um
den dritten Block der Datei vorab abzurufen, und so weiter.
-
Wenn
die Lesegeschwindigkeit der Anwendung geringer als die der Platte
ist, sind Vorababruf-E/A-Operationen abgeschlossen, bevor die Anwendung
mit dem Lesen der Daten in dem vorherigen Block fertig ist. In diesem
Fall wird die nächste
Vorababruf-E/A-Operation gestartet, sobald die Anwendung das letzte Byte
des vorherigen Pufferspeichers gelesen hat. In diesem Fall werden
die Daten mit derselben Geschwindigkeit geliefert, mit der die Anwendung
sie liest, und die Anwendung muss nie auf eine Platten-E/A-Operation warten.
Dies ist optimal. Wenn die Anwendung die Daten schneller liest als
sie von der Platte abgerufen werden können, muss sie jedes Mal, wenn
sie das Ende von einem Block erreicht, warten, bis die gerade aktive E/A-Operation
abgeschlossen ist, und eine neue Vorababruf-E/A-Operation wird gestartet,
sobald die vorherige endet. In diesem Fall werden die Daten so schnell
gelesen, wie sie von der Platte abgerufen werden können, was
ebenfalls optimal ist.
-
Der
in Tabelle 3 gezeigte Algorithmus verallgemeinert dieses Verhalten
auf mehrere Anwendungsprogramme und mehrere Platten pro Dateisystem;
er berechnet die Anzahl der Vorababruf-Pufferspeicher, die benötigt werden,
so dass: (1) Wenn die gesamte Datenrate, mit der alle Anwendungsprogramme
versuchen, Daten zu lesen, geringer als die gesamte verfügbare Bandbreite
der Platte ist, werden die Daten einer jeden Anwendung so schnell
zur Verfügung
gestellt, wie diese sie liest, ohne E/A-Wartezeiten. (2) Wenn die gesamte Datenrate
der Anwendungsprogramme höher
als die gesamte verfügbare
Bandbreite der Platte ist, werden die Daten so schnell gelesen,
wie sie von der Platte abgerufen werden können.
-
In
beiden Fällen
ist es erforderlich, die Geschwindigkeit zu ermitteln, mit der jedes
Anwendungsprogramm versucht, Daten zu lesen. Dies geschieht, indem
die Bedenkzeit ("think
time") der Anwendung
gemessen wird, d.h. die Zeit, die die Anwendung mit der Verarbeitung
der von dem Dateisystem gelieferten Daten verbringt. Die Bedenkzeit
beinhaltet den zusätzlichen
Zeitaufwand beim Leseaufruf des Systems, der erforderlich ist, um
auf Daten in der Pufferspeichergruppe des Dateisystems zuzugreifen
und sie in den Pufferspeicher der Anwendung zu kopieren, beinhaltet
aber nicht die Zeit, die in dem Dateisystem mit dem Warten auf Daten verbracht
wird, die von der Platte gelesen werden sollen. Wir definieren die "Datenverarbeitungsrate" ("data consumption
rate") der Anwendung über ein
bestimmtes Zeitintervall als die Datenmenge, die von der Anwendung
während
des Intervalls gelesen wird, geteilt durch die gesamte Bedenkzeit
in diesem Intervall.
-
Betrachten
wir zuerst den Fall, in dem die gesamte Verarbeitungsrate geringer
als die gesamte Bandbreite der Platte ist. In diesem Fall sollte
ein ordnungsgemäßer Vorababruf
in der Lage sein, die gewünschten Daten
zu liefern, ohne dass eine der Anwendungen je auf eine E/A-Operation
warten muss. Wenn die gesamte Verarbeitungsrate höher als
die Bandbreite einer einzigen Platte ist, sind parallele Vorababruf-E/A-Operationen
auf mehreren Platten erforderlich, um die gewünschte Datenrate zu halten.
Die Mindestanzahl der parallelen E/A-Operationen, die erforderlich
sind, lässt
sich berechnen, indem man die gesamte Verarbeitungsrate durch die
Bandbreite einer einzigen Platte teilt und das Ergebnis auf die
nächste
ganze Zahl aufrundet. Wir bezeichnen diese Zahl als den "Parallelitätsfaktor". Um die gewünschten
Daten zu liefern, ohne dass eines der Anwendungsprogramme auf eine
E/A-Operation warten
muss, müssen
genügend
zusätzliche
Pufferspeicher zur Verfügung
stehen, so dass jedes Anwendungsprogramm zuvor abgerufene Daten
aus einem anderen Pufferspeicher lesen kann, während Vorababruf-E/A-Operationen
im Gang sind. Die optimale Anzahl an Pufferspeichern für einen
Vorababruf ist daher durch Addition der Anzahl der Dateiinstanzen,
die für
in Folge stattfindende E/A-Operationen geöffnet sind, zum Parallelitätsfaktor
gegeben. Während
ein Anwendungsprogramm die letzten Daten aus einem zuvor abgerufenen
Block liest, wird dieser Pufferspeicher verfügbar, um die nächste Vorababruf-E/A-Operation
durchzuführen.
Wie in dem Algorithmus in der Tabelle 4 gezeigt ist, wird dieser
Pufferspeicher dann verwendet, um den nächsten Datenblock für die Anwendung
vorab abzurufen, die dem Ende des Pufferspeichers, aus dem sie gerade
Daten liest, am nächsten
ist. Mit "dem Ende
eines Pufferspeichers am nächsten
gelegene Anwendung" meinen
wir die Anwendung, die entsprechend ihrer aktuellen Datenverarbeitungsrate
am frühesten
Daten aus dem nächsten
Block anfordert.
-
Verwendet
man die optimale Anzahl von Vorababruf-Pufferspeichern, braucht keine Anwendung
auf eine E/A-Operation
zu warten, vorausgesetzt, dass sie Daten nie vor dem Zeitpunkt liest,
der auf der Grundlage der gemessenen Datenverarbeitungsrate vorhergesagt
wurde. Wenn die tatsächlichen
Verarbeitungsraten nicht konstant sind, kann die Anzahl der Vorababruf-Pufferspeicher
erhöht
werden, um Schwankungen bei den Verarbeitungsraten Rechnung zu tragen.
Dies geschieht, indem nicht nur die durchschnittlichen Bedenkzeiten gemessen
werden, sondern auch die Abweichung der Bedenkzeit bei jeder Anwendung.
Damit wird dann eine "abweichungsangepasste
Verarbeitungsrate" ("variance adjusted
consumption rate")
berechnet, d.h. eine Rate, bei der nahezu alle Leseanforderungen
(z.B. 90% oder 95% aller Anforderungen) nicht vor dem Zeitpunkt ankommen,
der auf der Grundlage der abweichungsangepassten Verarbeitungsrate
vorhergesagt wurde. Mit dieser abweichungsangepassten Verarbeitungsrate
wird dann der Parallelitätsfaktor
anstelle der durchschnittlichen Verarbeitungsrate berechnet.
-
Betrachten
wir nun den Fall, in dem die gesamte Verarbeitungsrate aller Anwendungen
die gesamte Plattenbandbreite des Dateisystems überschreitet. In diesem Fall
ist der Parallelitätsfaktor,
der wie vorstehend beschrieben berechnet wurde, eine Zahl, die größer als
die Anzahl der Platten ist, die dem Dateisystem zur Verfügung stehen.
Da es nicht möglich
ist, eine höhere
Anzahl von gleichzeitigen E/A-Operationen zu starten, als es Platten
gibt, macht es keinen Sinn, mehr Pufferspeicher für Vorababruf-E/A-Operationen
zuzuordnen, als es Platten gibt. Die gewünschte Anzahl von Vorababruf-Pufferspeichern
wird daher als die Anzahl der Dateiinstanzen berechnet, die für in Folge
stattfindende E/A-Operationen geöffnet
sind, zuzüglich
der Anzahl der Platten oder des Parallelitätsfaktors, je nachdem, welcher
Wert kleiner ist. Wenn die Verarbeitungsrate die gesamte Bandbreite
der Platte überschreitet,
ist diese Anzahl von Vorababruf-Pufferspeichern ausreichend, um weiterhin
alle Platten zu beschäftigen,
d.h., um eine neue Vorababruf-E/A-Operation zu starten, sobald die
vorherige E/A-Operationen auf einer Platte abgeschlossen ist. Folglich
werden Daten so schnell zur Verfügung gestellt,
wie sie von der Platte abgerufen werden können.
-
Schließlich beschreiben
wir zwei Verfeinerungen an der vorstehend beschriebenen Berechnung,
die den Eigenschaften des E/A-Teilsystems Rechnung tragen, mit dem
die Platten des Dateisystems verbunden sind. Die erste gilt für Systeme,
in denen es zu einer erheblichen Verzögerung zwischen dem Zeitpunkt
kommt, zu dem eine E/A-Anforderung dem Einheitentreiber übergeben
wird, und dem Zeitpunkt, zu dem die tatsächliche E/A-Operation gestartet
wird. Eine solche Verzögerung
tritt beispielsweise bei Platten auf, die mit einem Netzwerk verbunden
sind (z.B. VSD-Platten), bei denen eine E/A-Anforderung durch das
Netzwerk geleitet werden muss, bevor sie die Platte erreicht. Um
einen höchstmöglichen
Plattendurchsatz zu erreichen, muss die nächste an eine Platte gerichtete
E/A-Anforderung an den Einheitentreiber ausgegeben werden, bevor
die vorherige E/A-Operation abgeschlossen ist. Dazu muss ein Vorababruf-Pufferspeicher,
um die nächste E/A-Operation zu starten,
zu einem früheren
Zeitpunkt als üblich
verfügbar
sein. Folglich muss die Anzahl der Pufferspeicher, die für Vorababruf-E/A-Operationen
bestimmt sind, um einen Faktor von (1 + Epsilon) größer als
die Anzahl der Platten sein, wobei Epsilon durch das Verhältnis der
durchschnittlichen E/A-Anforderungsverzögerung und
der durchschnittlichen Platten-E/A-Zeit
gegeben ist.
-
Die
zweite Verfeinerung an der Berechnung des Pufferspeichers berücksichtigt
Beschränkungen
der Komponenten des E/A-Teilsystems
wie zum Beispiel von Plattensteuereinheiten und des E/A-Busses.
Wenn das Dateisystem über
eine hohe Anzahl von Platten verfügt, kann das Addieren von Plattenbandbreite
eine Zahl ergeben, die größer als
der gesamte E/A-Durchsatz der Platte ist, den das System unterstützen kann. Wenn
dies der Fall ist, braucht die Anzahl der Vorababruf-Pufferspeicher,
die für
Vorababruf-E/A-Operationen bestimmt sind, nicht so groß wie die
Anzahl der Platten zu sein. Stattdessen reicht eine Anzahl von Pufferspeichern,
die gleich dem gesamten E/A-Durchsatz,
geteilt durch die Bandbreite einer einzelnen Platte, ist, aus, um
so viele Platten-E/A-Operationen parallel zu starten, wie das System
tatsächlich
unterstützen
kann. Der gesamte E/A-Durchsatz der Platte kann entweder anhand
der Hardware-Spezifikationen ermittelt werden, indem speziell der
Durchsatz zum Zeitpunkt der Installation des Dateisystems gemessen
wird, oder indem der höchstmögliche Durchsatz,
der während
der Ausführung
des Dateisystems je beobachtet wurde, erfasst wird.
-
Beide
Verfeinerungen, die vorstehend beschrieben wurden, können durch
Berechnung einer "wirksamen
Anzahl von Platten" ausgedrückt werden,
die dann anstelle der tatsächlichen
Anzahl von Platten in den Berechnungen für den Vorababruf-Pufferspeicher verwendet
wird, wie in Tabelle 3 gezeigt ist.
-
TABELLE 3
-
Berechnen der gewünschten Größe der Vorababruf-Gruppe
-
- 1. Berechne die wirksame Anzahl der Platten
als
n_eff = MIN(ceil((1 + L_start/L_io)*n_disks), ceil(T_sys/T_disk)),
wobei
n_disks
= Anzahl der Platten, die dem Dateisystem zur Verfügung stehen
L_io
= durchschnittliche E/A-Latenzzeit, um einen Block von der Platte
zu lesen
L_start = durchschnittliche E/A-Anfangslatenzzeit
T_sys
= höchstmöglicher
gesamter E/A-Durchsatz des Platten-Teilsystems
T_disk = durchschnittlicher
E/A-Durchsatz einer einzelnen Platte
- 2. Bei jeder geöffneten
Dateiinstanz i, auf die in Folge zugegriffen wird. Berechne eine
angepasste Verarbeitungsrate c_i, so dass ein Teil f (z.B. 90%)
aller Anforderungen für
den nächsten
Datenblock nicht früher als
zu dem Zeitpunkt ankommen, der von der angepassten Verarbeitungsrate
vorhergesagt wird, d.h. in Intervallen, deren Länge durch die Blockgröße des Dateisystems,
geteilt durch c_i, gegeben ist. Dies kann statistisch berechnet
werden, indem man die durchschnittliche Verarbeitungsrate und Abweichung
für die Instanz
berechnet.
Berechne die gesamte angepasste Verarbeitung als
die Summe der angepassten Verarbeitungsraten von allen aufeinander
folgenden geöffneten
Dateiinstanzen:
c_total = sum c_i, for i = 1 ... n_inst
wobei
n_inst
= Anzahl der geöffneten
Dateiinstanzen, auf die in Folge zugegriffen wird.
-
Berechne den gewünschten
Vorababruf-Parallelitätsfaktor
als
n_para = c_total/T_disk
- 3. Die gewünschte
Anzahl der Vorababruf-Pufferspeicher wird dann wie folgt berechnet,
wobei die in den Schritten 1 und 2 berechneten Werte verwendet werden:
n_bufs_desired
= MIN(n_para, n_eff) + n_inst
-
TABELLE 4
-
Terminierung einer Vorababruf-E/A-Operation
-
Die
Eingabe in diese Prozedur ist die tatsächliche Anzahl der Vorababruf-Pufferspeicher, n_bufs_assigned,
die von dem Pufferspeicherverwaltungsprogramm auf der Grundlage
der gewünschten
Anzahl der Pufferspeicher, n_bufs_desired, die wie in Tabelle 3
gezeigt berechnet wurde, zugewiesen wurde.
-
Der
Algorithmus verwaltet zwei globale Zähler: n_io_total ist die Anzahl
der Vorababruf-E/A-Operationen, die gerade in Ausführung befindlich
sind (oder die dem Einheitentreiber übergeben wurden), und n_prefetched
ist die Anzahl der Pufferspeicher, die vorab abgerufene Blöcke halten,
die noch nicht von der Anwendung gelesen wurde, für die der
Block vorab abgerufen wurde. Die Summe dieser beiden Zahlen ist
die Anzahl der Pufferspeicher, die für den Vorababruf gerade verwendet
werden.
-
Weiterhin überwacht
der Algorithmus für
jede geöffnete
Instanz i, auf die in Folge zugegriffen wird, den vorhergesagten
Zeitpunkt, zu dem die Anwendung auf den nächsten Block zugreifen wird,
für den
noch keine Vorababruf-E/A-Operation gestartet worden ist. Wir bezeichnen
diese Zahl mit t_next[i].
- 1. Setze n_io_total
und n_prefetched auf den Anfangswert null.
Setze für jede geöffnete Dateiinstanz
i, auf die in Folge zugegriffen wird, n_io[i] auf den Anfangswert
null, und lege t_next[i] als den Zeitpunkt fest, zu dem die Anwendung
auf der Grundlage der angepassten Verarbeitungsrate c_i den nächsten Datenblock
anfordert.
Erstelle eine geordnete Instanzenliste, indem alle
geöffneten
Instanzen, auf die in Folge zugegriffen wird, nach t_next[i], kleinster
Wert zuerst, sortiert werden.
- 2. Wenn n_io_total + n_prefetched größer als oder gleich n_bufs_assigned
ist, gehe zum Schritt 4; fahre ansonsten mit dem nächsten Schritt
fort.
- 3. Übergib
die nächste
Vorababruf-E/A-Anforderung für
die erste Instanz i in der geordneten Instanzenliste (dies ist die
Instanz mit dem kleinsten Wert von t_next[i].
Aktualisiere
t_next[i] so, dass er der vorhergesagte Zeitpunkt ist, zu dem die
Anwendung den nächsten
Datenblock nach dem Datenblock anfordert, für den die Vorababruf-E/A-Operation
soeben gestartet worden ist. Ordne diese Instanz in der geordneten
Instanzenliste aller Instanzen entsprechend ihrem neuen Wert von
t_next[i] neu ein.
Erhöhe
n_io_total.
Gehe zum Schritt 2 zurück.
- 4. Warte, bis eines der folgenden Ereignisse stattfindet:
a)
Eine Vorababruf-E/A-Operation wird ausgeführt: Verringere n_io_total,
und erhöhe
n_prefetched. Gehe zum Anfang von Schritt 4 zurück (warte auf das nächste Ereignis).
b)
Eine Leseoperation erreicht das Ende eines Blocks, der vorab abgerufen
worden war: Da die Leseoperation die Daten aus dem Vorababruf-Pufferspeicher in
den Adressraum der Anwendung kopiert, steht dieser Pufferspeicher
nun für
eine weitere Vorababruf-Operation zur Verfügung. Verringere n_prefetched,
und gehe zum Schritt 2 zurück.
c)
Das Pufferspeicherverwaltungsprogramm hat die Anzahl der Pufferspeicher
geändert,
die der Vorababruf-Gruppe zugeordnet worden waren (n_bufs_assigned):
Gehe zum Schritt 2 zurück.
d)
Eine geöffnete
Instanz i wird geschlossen. Entferne die Instanz aus der Liste mit
den geordneten Instanzen.
Verringere n_prefetched um die Anzahl
der Pufferspeicher, die für
diese Instanz vorab abgerufen wurden.
Gehe zum Schritt 2 zurück.
-
Verwaltung des Pufferspeichers
bei verbesserter Leistungsfähigkeit
des Cachespeichers
-
Unser
paralleles Dateisystem wurde zum Einsatz auf Rechnern von IBM entwickelt,
bei denen die Leistungsfähigkeit
ein entscheidender Faktor ist. Einer der Aspekte, der sich auf die
Leistungsfähigkeit
auswirken kann, ist die Auslastung des Cachespeichers des Dateisystems.
Das Problem besteht darin, dass dem System Anforderungen für Cachespeicherplatz
unterschiedlicher Größe übergeben
werden und dies auf nicht vorhersagbare Weise geschieht. Wir haben
ein Cachespeicher-Verwaltungsschema
realisiert, bei dem wir das aktuelle Belegungsmuster in dem System
ermitteln, das Verhalten des Cachespeichers entsprechend anpassen
und auf diese Weise sowohl die Leistungsfähigkeit als auch die Speicherauslastung
verbessern. Im Allgemeinen verbessern wir die Leistungsfähigkeit
des Cachespeichers, die Speicherauslastung und die Verteilung durch
unsere Analyse des Belegungsmusters.
-
Die
Wirksamkeit unseres Cachespeicher-Belegungs- und -Ersetzungsschemas
wird enorm verbessert, da unser System die Art der Arbeitslast erkennt,
unter der es gerade arbeitet, und wir das Verhalten des Cachespeichers
entsprechend darauf abstimmen. Die beiden Arten der Arbeitslast,
die von dem vorgeschlagenen Schema erkannt werden und auf die es
reagiert, sind die sequenzielle Arbeitslast und die zufällige Arbeitslast.
Das Grundprinzip hinter dieser Trennung ergibt sich aus der unterschiedlichen
Definition der Arbeitsmengengröße zwischen
den beiden Arbeitslasten. Das zukünftige Verhalten wird vorhergesagt,
indem der aktuelle Zustand ausgewertet wird. Sobald das aktuelle
Belegungsmuster in dem System festgestellt worden ist und davon
ausgegangen wird, dass es verhältnismäßig stabil
ist, wird der Cachespeicher veranlasst, entsprechend zu reagieren.
-
Der
gesamte Cachespeicher wird in verschiedene Arbeitseinheiten aufgeteilt,
von denen jede einen Teil des gesamten Cachespeicherbereichs steuert
und für
Pufferspeicher unterschiedlicher Größe verantwortlich ist. Jede
Arbeitseinheit besteht aus zwei Teileinheiten, die die beiden Arten
der Arbeitslast, mit denen das System arbeitet, überwachen. Die Menge der verschiedenen
Arbeitseinheiten und die Größen der
Pufferspeicher, für
die sie verantwortlich sind, ändern
sich dynamisch. Das Cachespeicher-Verwaltungsprogramm erkennt in jedem
Moment die jeweilige Größe des Pufferspeichers,
für die
es mit hoher Wahrscheinlichkeit einen hohen Bedarf geben wird, und
legt die Arbeitseinheiten entsprechend fest. Es ist immer eine weitere
Arbeitseinheit vorhanden, die sich um eingehende Anforderungen für Pufferspeicherkapazitäten kümmert, welche sich
von der festen Größe aller
anderen Arbeitseinheiten unterscheiden. Dies verbessert die Antwortzeit
des Cachespeichers dadurch, dass eingehende Anforderungen direkt
an den Teil des Cachespeichers verwiesen werden, der Pufferspeicher
der gewünschten
Größe beherbergt.
Dieser Aspekt trägt
dazu bei, das Problem der Fragmentierung des Cachespeichers zu mildern,
indem das Problem auf eine Arbeitseinheit beschränkt wird und zusätzliche
Maßnahmen
ergriffen werden, wie zum Beispiel, dass nur dort Zusammenführungen
und Neuzuordnungen vorgenommen werden. Statistiken zur Belegung
werden für
jede Teileinheit einer jeden Arbeitseinheit ständig aktualisiert.
-
Die
erfassten Belegungsstatistiken werden in regelmäßigen Abständen geprüft. In der Folge wird der Speicherplatz
des Cachespeichers unter den verschiedenen Arbeitseinheiten neu
aufgeteilt. Da unser System zukünftige
Belegungsmuster vorhersagt, indem es aktuelle Belegungsmuster auswertet,
wirkt sich die Neuaufteilung des Speicherbereichs nicht sofort aus,
sondern wird vielmehr erst wirksam, wenn Bedarf besteht. Jede Arbeitseinheit
hat zwei Arten von Bereichsgrenzen, nämlich eine interne und eine
externe. Die interne Bereichsgrenze trennt die beiden Teilarbeitseinheiten.
Die externe Bereichsgrenze wird in zwei weitere Grenzarten unterteilt,
nämlich
in eine physische Grenze und in eine virtuelle Grenze. Die physische
Grenze stellt die tatsächliche
Speicherkapazität
dar, die von dem Verteilungsschema des Belegungsmusters gesteuert
wird, das zu der einzelnen Arbeitseinheit gehört. Die virtuelle Grenze ist
die Grenze, die von dem Vorhersageprozess der Belegungsmusterauswertung
als die physische Grenze projiziert wird, welche von dieser Arbeitseinheit
versuchsweise erreicht werden sollte. Die virtuelle Grenze dient
zur Folgerung, ob sich die physische Grenze einer bestimmten Arbeitseinheit
ausdehnen darf oder ob die Arbeitseinheit gezwungen ist, einen Teil des
von ihr gesteuerten Speicherbereichs nach einer Anforderung von
einer Arbeitseinheit, die sich vergrößern darf, aufzugeben, d.h.,
ob sie sich im Grunde verkleinern darf.
-
Der
Prozess der Festlegung von neuen virtuellen Grenzen funktioniert
wie folgt. Die Statistiken der Teilarbeitseinheiten werden ausgewertet
und zur Ableitung des Belegungsmusters und des Auslastungsgrades herangezogen,
die den für
ihre Erfordernisse optimalen Speicherbereich bestimmen. Jede Teilarbeitseinheit versucht,
die Speicherkapazität
zu erhalten, die sie als die für
ihre Erfordernisse optimale Speicherkapazität ermittelt hat (ihre Arbeitsmengengröße). Der
relative Auslastungsgrad der Teilarbeitseinheit stellt eine Obergrenze
für das
benötigte
Speicherbereichsoptimum dar.
-
Der
Erwerb von neuem Speicherbereich wird von einem Schema geregelt,
bei dem sich physische und virtuelle Grenzen innerhalb einer jeden
Arbeitseinheit wie folgt gegenseitig beeinflussen. Wenn eine Anforderung
für einen
neuen Pufferspeicher eintrifft, wird sie von der Arbeitseinheit
bedient, die die angeforderte Größe steuert.
Wenn es in der Arbeitseinheit einen freien Pufferspeicher oder einen
Pufferspeicher gibt, der sehr leicht und schnell zu bekommen ist,
wird er zur Erfüllung
der eintreffenden Anforderung verwendet. Die Arbeitseinheit fährt dann
mit einem Vergleich ihrer physischen Grenze mit ihrer virtuellen
Grenze fort. Wenn die physische Grenze nicht kleiner als die virtuelle
Grenze ist, fährt
die Arbeitseinheit damit fort, den Speicherbereich zu suchen, der
am einfachsten zu bekommen ist und den sie bereits steuert. Andernfalls
sucht die aktuelle Arbeitseinheit die Arbeitseinheit, die sich am
meisten verkleinern darf, und richtet eine Speicherbereich-Erwerbsanforderung
an sie. Die Empfangsarbeitseinheit sucht den am einfachsten zu bekommenden
Speicherbereich, der von ihr gesteuert wird, und gibt die Steuerung
dieses Speicherbereichs ab. Die ursprüngliche Arbeitseinheit übernimmt
dann die Kontrolle über
den neuen Speicherbereich und verwendet ihn, um die eintreffende Anforderung
zu erfüllen.
-
Die
Häufigkeit,
mit der der Belegungsmuster-Erkennungsprozess ausgeführt wird,
kann sich gegebenenfalls entscheidend auf die Wirksamkeit des ganzen
Schemas auswirken. Wenn der Prozess zu häufig ausgeführt wird, könnte er zu heftig auf sehr
kurze Aktivitätsspitzen
in einer bestimmten Teilarbeitseinheit reagieren. Wenn dieser Prozess
andererseits in großen
Zeitabständen
ausgeführt
wird, büßt er im
Laufe der Zeit an Wirksamkeit und Genauigkeit ein. Folglich bestimmt
der Prozess jedes Mal, wenn er ausgeführt wird, den Zeitpunkt, zu
dem er das nächste
Mal ausgeführt
werden sollte. Diese Berechnung beruht auf dem Zeitpunkt, zu dem
alle Arbeitseinheiten erwartungsgemäß auf den gesamten von ihnen
gesteuerten Speicherbereich zugreifen. Dieser Zeitraum wird einer
vorher festgelegten Ober- und Untergrenze unterworfen. Dieses Intervall
ermöglicht
dem Belegungsmuster-Erkennungsprozess, die aktuelle Verteilung der
Arbeitslast abzuleiten, ohne von einem einzelnen belastenden Ereignis
beeinflusst zu sein. Die Arbeitsmenge von Clients mit zufälligen Arbeitslasten
kann ebenso wie der Speicherbereich, der für das Vorauslesen von Clients
mit sequenziellen Arbeitslasten benötigt wird, abgeleitet werden.
-
Dieses
Schema schließt
eine erweiterte Leistungsfähigkeit
und Belegung von verfügbarem
Cachespeicherbereich in einer Mehrzweckumgebung ein.
-
Diejenigen,
die mit den bisherigen Möglichkeiten
der Verwaltung eines Cachespeichers eines Dateisystems vertraut sind,
werden nun verstehen, wie unser Verfahren zur Optimierung der Auslastung
eines Cachespeichers durch das Erkennen von Belegungsmustern eine
Verbesserung gegenüber
der bisherigen Behandlung darstellt, bei der der Cachespeicher als
eine einzelne Arbeitseinheit betrachtet wurde und bei der eintreffende
Anforderungen nach der LRU-Methode erfüllt wurden.
-
Wenn
wir die Art der eintreffenden Anforderungen vorhersehen und uns
darauf vorbereiten, wird jede eintreffende Anforderung an den Cachespeicherbereich
geleitet, bei dem die Wahrscheinlichkeit, dass er verwendet wird,
um die Anforderung zu erfüllen,
hoch ist. Überdies
kennen wir das Speichervolumen, das für jede Arbeitslast in jeder
Arbeitseinheit vorgesehen werden kann, und können folglich andere Systemaktionen
(z.B. die Vorababrufrate) entsprechend darauf abstimmen.
-
Erweiterte Dateiattribute
zur Unterstützung
der Zugriffssteuerungslisten
-
Wie
wir bereits erwähnt
haben, sind wir zu dem Schluss gekommen, dass es wünschenswert
wäre, Zugriffssteuerungslisten
für unser
Dateisystem mit gemeinsam benutzten Platten zur parallelen Ausführung durch
verschiedene Rechner in der Umgebung bereitzustellen. Dazuhaben
wir erweiterte Dateiattribute der in der Unix-Umgebung bekannten
Art zur wirksamen Unterstützung
von Zugriffssteuerungslisten vorgesehen.
-
Erweiterte
Attribute ermöglichen
auch die Zuordnung von Informationen veränderlicher Länge zu einer Datei,
auf die getrennt von den Daten zugegriffen werden kann, die in der
Datei selbst gespeichert sind. Eine Verwendungsmöglichkeit von erweiterten Attributen
ist die Speicherung von Zugriffssteuerungslisten (access control
lists oder kurz "ACLs"), mit denen gesteuert
wird, welche Benutzer oder Benutzergruppen in welcher Weise auf
eine Datei zugreifen dürfen
(Lesezugriff, Schreibzugriff usw.). ACLs stellen Anforderungen an
die Ausführungsart
eines erweiterten Attributs und sie unterscheiden sich von vielen
anderen Verwendungsmöglichkeiten
von erweiterten Attributen: Da alle Operationen des Dateisystems,
die die Zugriffsberechtigung prüfen,
auf die ACL der Datei zugreifen müssen, ist ein schneller und
wirksamer Zugriff auf die ACL-Daten entscheidend für die Leistungsfähigkeit
des Dateisystems. Andererseits sind ACLs üblicherweise kurz, sie ändern sich
nicht häufig,
und selbst wenn jede Datei eine ACL hat, sind viele dieser ACLs
gleich, d.h., es gibt gewöhnlich
wesentlich weniger unterschiedliche ACL-Werte, als es Dateien gibt.
Wir beschreiben nun, wie erweiterte Attribute in einer Weise realisiert
werden, die sich die Nutzungsmerkmale von ACLs zunutze macht und
einen platzsparenden Attributspeicher bereitstellt, der einen schnellen
Zugriff auf die Attributdaten ermöglicht. Außerdem unterstützt diese
Ausführungsart
sehr wirksam die Attributvererbung. Sie ist besonders zur Realisierung von
POSIX-ACLs geeignet.
-
Im
Wesentlichen verwendet unsere Ausführungsart der erweiterten Attribute
in dieser Erfindung die folgenden Komponenten:
- – die Attributdatei
(kurz "AttrFile"). Dies ist eine
spezielle Datei, die alle Attributdaten speichert. Sie besteht aus
einer Folge von Einträgen;
jeder Eintrag ist von einer der folgenden beiden Arten: ein Attributeintrag, der
den Wert eines bestimmten Attributs enthält, oder ein Eintrag zu freiem
Speicherbereich, der freien Speicherbereich in der Attributdatei
kennzeichnet, d.h. Speicherbereich, der das nächste Mal wiederverwendet werden
kann, wenn ein neuer Attributeintrag zur Attributdatei AttrFile
hinzugefügt
werden muss. Beide Arten von Einträgen haben eine veränderliche
Länge,
sind aber auf geeignete Grenzen ausgerichtet (z.B. ein Vielfaches
von 8 oder 16 Byte), um die Fragmentierung zu verringern. Die Wahl
einer bestimmten Ausrichtungsgröße hängt von
der Mindestgröße und der
durchschnittlichen Größe der Attributeinträge ab.
- – Attributverweise
(kurz "AttrRefs"). Dies sind kurze
Werte, die im Inode einer jeden Datei gespeichert werden und das
Auffinden der Attributdaten für
diese Datei in der Attributdatei AttrFile ermöglichen. Dieser Speicherort
wird durch den Offset des Attributeintrags in der Attributdatei
AttrFile dargestellt, welcher in Einheiten der Ausrichtungsgröße angegeben
wird, d.h., ein AttrRef wird als der Byte-Offset geteilt durch die Ausrichtungsgröße berechnet.
- – der
Attributindex (kurz "AttrIndex"). Dies ist eine
Datenstruktur, die das Auffinden eines bestimmten Attributwerts
in der Attributdatei AttrFile ermöglicht. Der Aufbau und die
Verwendung von AttrIndex wird im nächsten Abschnitt unter "Nachschlagen des
Attributwerts" ausführlicher
beschrieben.
- – ein
Attribut-Müllaufsammler
(garbage collector). Dies ist ein Prozess, der zu geeigneten Zeitpunkten
gestartet wird, um Attributeinträge
aus der Attributdatei AttrFile zu entfernen, auf die keine der vorhandenen Dateien
mehr verweist.
-
Gemeinsame Benutzung des Attributwerts
-
In
unserer bevorzugten Ausführungsform
eines Dateisystems mit gemeinsam benutzten Platten ist die gemeinsame
Benutzung des Attributwerts als eine Ausführungsart eines erweiterten
Attributs vorgesehen. Dadurch kann physischer Attributspeicher von
allen Dateien, die Attribute mit genau gleichen Werten haben, gemeinsam
benutzt werden. Realisiert wird dies, indem alle Attributdaten an
einem gemeinsamen Ort gespeichert werden, an dem Ort, den wir die
Attributdatei AttrFile nennen würden.
Der im Inode einer Datei "f" gespeicherte Attributverweis
AttrRef enthält
den Speicherort des Eintrags, der die Attributdaten für "f" in der Attributdatei AttrFile enthält, und
wird durch den Offset des Eintrags in AttrFile dargestellt. Dateien
mit identischen Attributwerten enthalten dieselben Werte von AttrRef
in ihrem Inode. Diese gemeinsame Benutzung des Attributwerts wird
auf die folgenden beiden Arten realisiert:
- 1.
Attributvererbung:
Attributvererbung bedeutet, dass beim Anlegen
einer neuen Datei deren erweiterte Attribute auf dieselben Werte
wie die der vorhandenen Datei gesetzt werden, von der sie abgeleitet
wird. Beim Kopieren einer Datei können beispielsweise die Attributwerte
der Kopie auf dieselben Werte wie die der ursprünglichen Datei gesetzt werden.
POSIX-ACLs sind ein Beispiel für
eine andere Art der Attributvererbung: Der vorgeschlagene POSIX-ACL-Standard
legt fest, dass beim Anlegen einer neuen Datei oder eines neuen
Verzeichnisses die ACL der Datei auf einen standardmäßigen ACL-Wert
gesetzt wird, der zu dem Verzeichnis gehört, in dem die Datei angelegt
wird. Anders ausgedrückt,
bei POSIX-ACLs erbt eine neue Datei ihre ACL von ihrem Elternverzeichnis.
Gemäß unserer
Erfindung wird diese Attributvererbung realisiert, indem einfach
der Attributverweis AttrRef von dem Inode der Datei oder des Verzeichnisses
kopiert wird, von der beziehungsweise dem das Attribut geerbt wird.
Auf diese Weise benutzt das geerbte Attribut denselben physischen
Speicher gemeinsam mit dem Attribut, von dem es geerbt wird.
- 2. Nachschlagen des Attributwerts:
Um ein Attribut auf
einen Wert zu setzen oder auf einen Wert zu ändern, der nicht von einer
anderen Datei geerbt wird, wird der Attributindex zur Feststellung
verwendet, ob in der Attributdatei AttrFile bereits ein Eintrag
mit demselben Wert vorhanden ist. Ein Indexierungsverfahren wie
zum Beispiel Hashing kann zu diesem Zweck verwendet werden: Um einen
Attributwert zu setzen oder zu ändern,
wird eine Hash-Funktion auf die Attributdaten angewandt. Der resultierende
Hash-Wert wird als ein Index in die Hash-Tabelle verwendet, in der
man auf eine Liste mit Attributverweisen (AttrRefs) trifft, die
auf Einträge
in der Attributdatei AttrFile mit Attributdaten verweisen, welche
mit Hilfe der Streuspeichertechnik auf denselben Hash-Wert abgebildet
werden. Die neuen Attributdaten, die gespeichert werden sollen,
werden mit den Daten in allen dieser Einträge verglichen. Wenn eine Übereinstimmung
festgestellt wird, wird ein Attributverweis AttrRef, der auf den
vorhandenen Eintrag verweist, im Inode der Datei gespeichert. Wenn
keine Übereinstimmung festgestellt
wird, wird ein neuer Eintrag, der den neuen Attributwert enthält, zur
Attributdatei AttrFile hinzugefügt,
und ein Attributverweis AttrRef auf den neuen Eintrag wird im Inode
der Datei und auch in der Hash-Tabelle gespeichert, so dass zukünftige Aktualisierungen
von Attributen, die denselben Attributwert verwenden, den neuen
Eintrag finden.
Um die Wahrscheinlichkeit zu erhöhen, dass
Attributwerte gemeinsam benutzt werden, werden neue Attributwerte,
wenn möglich,
in eine kanonische Form umgewandelt, bevor sie gespeichert oder
bevor nach ihnen gesucht wird. Die Einträge in einer Zugriffssteuerungsliste
können
zum Beispiel nach der Benutzerkennung oder der Gruppenkennung sortiert
werden; dadurch können
zwei ACLs, die funktional gleichwertig sind, denselben Speicher
in der Attributdatei AttrFile gemeinsam benutzen, obgleich die beiden
ACLs möglicherweise
nicht in genau demselben Format übergeben
wurden, als sie festgelegt wurden.
-
In
der realisierten Weise eignet sich unser System zur Speicherung
von erweiterten Attributen insbesondere zur Speicherung von ACLs
und zu anderen ähnlichen
Zwecken. Zwar ist es möglich,
dass ein Benutzer eine große
Anzahl von Dateien besitzt, doch ist es sehr unwahrscheinlich, dass
er jeder seiner Dateien eine andere ACL zuordnet. Vielmehr gibt
es üblicherweise
Gruppen von zusammengehörenden
Dateien, denen allen dieselben Zugriffsrechte zugeordnet sind. Dateien
beispielsweise, die zu einem bestimmten Projekt gehören, hätten üblicherweise
alle dieselbe ACL, die Zugriff auf Benutzer gewährt, die dem Projekt zugeordnet
sind. Als ein weiteres Beispiel benutzen Dateien in demselben Verzeichnis
oder in demselben Teilbaum der Verzeichnishierarchie oftmals dieselbe
ACL gemeinsam. Tatsächlich
hat die Vererbung der ACL in dem vorgeschlagenen POSIX-ACL-Standard
den Zweck, einem Benutzer die Verwaltung einer gemeinsamen ACL für Dateien
in demselben Verzeichnis zu erleichtern. Deshalb gehen wir davon
aus, dass die Gesamtzahl der unterschiedlichen ACL-Werte in einem
Dateisystem wesentlich geringer als die Gesamtzahl der Dateien ist;
tatsächlich
gehen wir davon aus, dass sie um einen großen Faktor geringer ist. Das
bedeutet, dass im Vergleich zu der Vorgehensweise, bei der jede
ACL einzeln gespeichert wird, die gemeinsame Benutzung des ACL-Speichers
durch Dateien mit gleichen ACLs den zusätzlichen Platzbedarf zur Speicherung
von ACLs um mindestens den gleichen Faktor verringert.
-
Außerdem enthalten
ACLs üblicherweise
keine lange Liste mit einzelnen Benutzern, da solche Listen schwierig
zu verwalten sind. Die meisten Systeme ermöglichen vielmehr die Festlegung
von Benutzergruppen; mit einer Gruppe kann dann in einer ACL auf
die Benutzer verwiesen werden, die zu dieser Gruppe gehören. Es
ist daher unüblich,
dass ACLs sehr lang sind, was bedeutet, dass eine ACL gewöhnlich wenig
Speicherplatz in Anspruch nimmt. Diese Tatsache in Verbindung mit
der gemeinsamen Benutzung der ACL bedeutet, dass es möglich ist,
ACL-Daten für
viele Dateien im Cachespeicher abzulegen. Dadurch kann die ACL für eine Datei schnell
abgerufen werden, da die Daten der ACL wahrscheinlich im Cachespeicher
abgelegt sind, so dass ohne zusätzliche
Platten-E/A-Operation darauf zugegriffen werden kann.
-
Wenn
ACLs für
sehr viele Dateien geändert
werden, werden viele dieser ACLs wahrscheinlich auf den gleichen
neuen Wert geändert.
Eine solche Änderung
würde zum
Beispiel stattfinden, um einem neuen Benutzer den Zugriff auf die
Dateien zu gewähren,
die zu einem bestimmten Projekt gehören. Aufgrund der gemeinsamen
Benutzung einer ACL erfordert nur die erste einer Reihe von zusammengehörenden Änderungsoperationen
an der ACL die Aktualisierung der Attributdatei AttrFile: Bei späteren Änderungsoperationen
an der ACL, bei denen derselbe ACL-Wert verwendet wird, muss nur
der ACL-Wert im Attributindex AttrIndex nachgeschlagen werden. Dies
bedeutet, dass der Zugriff selbst bei einer Arbeitslast mit sehr
vielen gleichzeitigen ACL-Aktualisierungen meist ein Nur-Lese-Zugriff
ist. Daher verursacht die Tatsache, dass alle Attribute an einem
gemeinsamen Ort gespeichert werden, kein Problem in Form eines Engpasses.
Dies ist besonders in einer verteilten Umgebung wichtig, in der
es wünschenswert
ist, Attributdaten lokal im Cachespeicher abzulegen, was Aktualisierungen
an der Attributdatei AttrFile aufgrund dessen, dass Attributdaten,
die an anderen Knoten im Cachespeicher abgelegt sind, ungültig gemacht
werden müssen,
wesentlich aufwändiger
macht.
-
Die
Bereinigung des Speichers (garbage collection) ist eine Funktion,
die bereitgestellt werden muss, da sie laufend benötigt wird.
Die gemeinsame Benutzung des Attributwerts macht es etwas schwierig, Speicherbereich
in der Attributdatei AttrFile zurückzufordern, wenn ein Attributeintrag
nicht mehr benötigt
wird. Das Problem besteht darin, festzustellen, wann es sicher ist,
den Eintrag zu löschen,
d.h., wenn die letzte Datei, die auf den Eintrag verwies, gelöscht oder
wenn ihr Attribut geändert
wird. Dieses Problem wird gewöhnlich so
gelöst,
dass für
jeden Eintrag die Anzahl der Referenzen festgehalten wird; die Referenzanzahl
würde erhöht werden,
wenn ein Attributverweis AttrRef, der auf den Eintrag verweist,
im Inode einer Datei gespeichert wird, und sie würde verringert werden, wenn
ein AttrRef gelöscht
wird. Der Eintrag in der Attributdatei AttrFile könnte dann
gelöscht
werden, wenn die Referenzanzahl auf null zurückgeht. Bei dieser Lösung wäre es jedoch notwendig,
die Referenzanzahl jedes Mal zu aktualisieren, wenn ein Attribut
geerbt, gespeichert oder aktualisiert wird, selbst wenn der neue
Attributwert in der Attributdatei AttrFile bereits vorhanden wäre. Folglich
wäre der
Zugriff auf die Attributdatei AttrFile in den meisten Fällen kein
Nur-Lese-Zugriff mehr, was möglicherweise einen
Engpass verursachen könnte.
-
Statt
die Referenzanzahl festzuhalten, fordert diese Erfindung Attributspeicherbereich
mittels Speicherbereinigung zurück.
Bei der Speicherbereinigung werden nicht verwendete Attributeinträge wie folgt aufgefunden
und gelöscht.
Ein Teil eines jeden Attributeintrags ist eine Verweismarkierung,
kurz "RefFlag", die immer gesetzt
wird, wenn ein neuer Eintrag in die Attributdatei AttrFile aufgenommen
wird. Die Speicherbereinigung findet in den folgenden drei Phasen
statt:
- Phase 1:
Fragt die ganze Attributdatei AttrFile
ab und schaltet die Markierung RefFlag in jedem Attributeintrag
in der Datei aus.
- Phase 2:
Fragt alle Inodes ab. Für jeden in einem Inode gefundenen
Attributverweis AttrRef schaltet sie die Markierung RefFlag für den entsprechenden
Attributeintrag in der Attributdatei AttrFile wieder ein.
- Phase 3:
Fragt die Attributdatei AttrFile erneut ab und
löscht
alle Attributeinträge,
bei denen die Markierung RefFlag immer noch ausgeschaltet ist.
-
Um
sicherzustellen, dass der Prozess der Speicherbereinigung keine
Einträge
löscht,
für die
während des
Prozesses neue Verweise erzeugt werden, muss sich der Prozess der
Speicherbereinigung mit der Suchoperation synchronisieren, die Teil
des Vorgangs ist, bei dem ein Dateiattribut gesetzt oder geändert wird,
wie unter "Nachschlagen
des Attributwerts" im
Abschnitt "Gemeinsame
Benutzung des Attributwerts" vorstehend beschrieben
wurde. Da die Speicherbereinigung verhältnismäßig lange dauern kann – insbesondere
die Phase 2 –,
ist es nicht erwünscht,
einfach alle Operationen, bei denen Attribute gesetzt oder geändert werden,
zu sperren, während
die Speicherbereinigung ausgeführt
wird. Wenn eine Operation, bei der Attribute gesetzt oder geändert werden,
in der Attributdatei AttrFile einen vorhandenen Eintrag mit einem
Wert findet, der mit dem neuen Wert, der gerade gesetzt wird, übereinstimmt,
prüft sie auch,
ob die Markierung RefFlag in dem Eintrag eingeschaltet ist, bevor
sie AttrRef im Inode der Datei speichert. Auf diese Weise ist eine
ausdrückliche Synchronisation
zwischen der Speicherbereinigung und dem Nachschlagen des Attributwerts
nur während
der letzten Phase der Speicherbereinigung notwendig, und dann nur,
wenn die Suchoperation nach dem Attributwert einen Attributeintrag
findet, bei dem die Markierung RefFlag ausgeschaltet ist.
-
Der
Prozess des Startens der Speicherbereinigung ist wichtig. Ohne Speicherbereinigung
könnte
die Datei AttrFile grenzenlos weiter an Größe zunehmen, selbst wenn die
Gesamtmenge der aktiven Attributdaten (Attributwerte, auf die immer
noch verwiesen wird) nicht weiter zunimmt. Die Geschwindigkeit,
mit der die Datei AttrFile an Größe zunehmen
würde,
hängt von
der Geschwindigkeit der Operationen ab, bei denen Attribute gesetzt
oder geändert
werden. Bei Attribut-Verwendungszwecken wie zum Beispiel ACLs ist
die Geschwindigkeit solcher Operationen im Grunde nicht vorhersagbar.
Daher ist eine Vorgehensweise, die die Speicherbereinigung in festen
regelmäßigen Intervallen
(z.B. einmal pro Tag) startet, nicht geeignet. Stattdessen überwachen
wir den Gesamtumfang der Attributdaten, d.h. die Größe der Datei
AttrFile abzüglich
des gesamten freien Speicherbereichs in AttrFile. Die Speicherbereinigung
wird jedes Mal gestartet, wenn die Menge der Attributdaten um einen
bestimmten Faktor (z.B. 1,5 oder 2) zugenommen hat. Mit dieser Vorgehensweise
kann wirksam verhindert werden, dass die Datei AttrFile an Größe zunimmt,
wenn die Menge der aktiven Attributdaten gleich bleibt.
-
Funktionsweise des Metadaten-Knotens
-
Dieser
Abschnitt beschreibt die Funktionsweise des Metadaten-Knotens, der die
Leistungsfähigkeit
in den Fällen
verbessert, in denen mehrere Rechner dasselbe Datenobjekt aktualisieren
oder vergrößern müssen. Wir
beginnen mit der Erzeugung eines Metaknotens für diese Funktionen und fahren
anschließend
mit der Beschreibung von Verfahren zur Kennzeichnung des Metadaten-Knotens
und seiner Wiederherstellung fort.
-
Verwendung eines Metadaten-Knotens
-
Der
erste Abschnitt über
unseren Metadaten-Knoten beschreibt allgemein, was unser Metadaten-Knoten
ist und welches Problem er löst.
Ein Metadaten-Knoten wird in unserem System zur Verwaltung von Datei-Metadaten
für parallele
Lese- und Schreiboperationen in der Umgebung mit gemeinsam benutzten
Platten verwendet. In dem parallelen Dateisystem können mehrere
Prozessoren auf alle Platten, die das Dateisystem bilden, unabhängig voneinander
zugreifen. Um diese Fähigkeit
nutzen zu können,
sollte eine Datei von mehreren Prozessoren sowohl für Lese-
als auch Schreiboperationen gemeinsam benutzt werden.
-
Es
gibt mehrere Probleme, die die Leistungsfähigkeit eines solchen Zugriffs
erheblich beeinträchtigen können. Obgleich
Knoten aus verschiedenen Bereichen der Datei lesen und diese beschreiben
können,
wenn sie auf die Abschnitte, die sie gerade lesen oder beschreiben,
eine entsprechende Sperre gesetzt haben, müssen sie alle auf dieselben
Metadaten zugreifen. Zu den Metadaten gehören die Dateigröße, der
Zeitpunkt, zu dem auf die Datei zugegriffen wurde, der Zeitpunkt,
zu dem die Datei geändert
wurde, und die Adressen der Datenblöcke der Datei. Alle Operationen
zum Beispiel, die die Datei lesen und in die Datei schreiben, müssen wissen,
ob sie die Dateigröße überschreiten,
und die Datei aktualisieren, wenn sie sie erweitern. Eine solche einzelne
Interessenfrage kann einen gravierenden Engpass darstellen, wenn
eine Datei für
wirklich parallele Schreiboperationen freigegeben werden muss.
-
Wir
haben ein System realisiert, das es jedem Knoten ermöglicht,
beim Lesen und Beschreiben derselben Dateien so unabhängig wie
möglich
zu agieren, und wir haben einen Mechanismus entwickelt, mit dem diese
Operationen synchronisiert werden können, so dass von allen Knoten
eine übereinstimmende
Ansicht der Datei verfügbar
ist, indem unser Verfahren zur Verwaltung von Metadaten-Informationen
bereitgestellt wird. Unser Verfahren zur Verwaltung von Metadaten-Informationen
für eine
Datei in einem Dateisystem mit gemeinsam benutzten Platten sieht
vor, dass für
jede Datei ein einzelner Knoten als Metadaten-Knoten (oder Metaknoten)
für diese
Datei ausgewählt
wird. Der Metaknoten ist für
die Abwicklung der gesamten E/A-Aktivität der Metadaten von und auf
die Platte (oder die Platten), auf der beziehungsweise auf denen
sich die Metadaten befinden, verantwortlich.
-
Alle
anderen Knoten tauschen mit dem Metadaten-Knoten Daten aus, um Metadaten-Informationen abzurufen
oder zu aktualisieren. Diese Knoten greifen jedoch nicht direkt
auf die Metadaten-Informationen auf der Platte zu.
-
Der
Metadaten-Knoten wird zum ersten Knoten bestimmt, der auf die Datei
zugreift. Wenn folglich nur ein Knoten auf die Datei zugreifen muss,
entsteht kein Mehraufwand, da der Knoten direkt auf die Metadaten zugreifen
kann. Weitere Knoten greifen zum Erhalt von Metadaten auf den Metaknoten
zu.
-
Durch
die Einführung
eines Metaknotens wird eine beträchtliche
Menge an Plattenaktivität
vermieden, was bei einem parallelen Dateisystem mit einer schnellen
Datenaustausch-Vermittlungsstelle
eine deutliche Leistungsverbesserung darstellt.
-
Der
Metaknoten hält
eine im Cachespeicher abgelegte Kopie der Metadaten, die die Metadaten
auf der Platte widerspiegelt. Andere Knoten halten ebenfalls eine
zwischengespeicherte Kopie der Metadaten, die sie in der Vergangenheit
von dem Metaknoten gelesen und nach Bedarf erweitert haben (indem
sie zum Beispiel die Zugriffszeit änderten).
-
Jedes
Element der Metadaten (Zugriffszeit, Änderungszeitpunkt, Dateigröße, Plattenadresse
des Datenblocks) hat sein eigenes Nutzungsmuster und seine eigenen
speziellen Merkmale. Unser System erfordert beispielsweise keine
sehr genaue Zugriffszeit, sondern lediglich eine Zugriffszeit mit
einer Toleranz von fünf Minuten.
Folglich müssen
Aktualisierungen des Metaknotens nicht häufig stattfinden, wodurch ein
beträchtlicher
Datenübertragungsaufwand
eingespart wird.
-
Auch
die Dateigröße muss
nicht auf allen Knoten ganz genau sein, solange sich das System
beständig gleich
verhält.
Eine durchdachte Vorgehensweise bei der Überwachung der Dateigröße auf allen
Knoten ermöglicht
die Umsetzung eines parallelen Schreibschemas, bei dem mehrere Knoten
die Datei gleichzeitig erweitern können.
-
Durch
die Verwendung eines Algorithmus für eine verzögerte Synchronisation können viele
Plattenzugriffe eingespart werden. Eine Synchronisations-Hintergrundroutine
(sync daemon) ist eine Softwarekomponente, die als Teil des Betriebssystems
auf jedem Knoten läuft.
Die Synchronisations-Hintergrundroutine
versucht, alle N Sekunden verfälschte
Daten und Metadaten auf die Platte zu schreiben. Wenn M Knoten parallel in
die Datei schreiben, bedeutet dies, dass nur für die Metadaten alle N Sekunden
M Plattenzugriffe stattfinden. Bei parallelen Schreiboperationen
senden alle Knoten ihre aktualisierten Metadaten an den Metaknoten,
der den Inhalt der Datei alle N Sekunden löscht, wenn er ein Signal von
der Synchronisations-Hintergrundroutine empfängt.
-
Jeder
Knoten würde
der Reihe nach auf die Platte zugreifen, um Metadaten zu lesen oder
zu schreiben.
-
Die Verwendung von Token
-
Der
zweite von den Abschnitten dieser Beschreibung, in denen es um parallele
Schreiboperationen geht, betrifft unsere Verwendung von Sperrmodi,
um den Metadaten-Verwaltungsknoten zu finden. Token, die zum Auffinden
des Metadaten-Verwaltungsknotens
Sperrmodi verwenden, dienen zur Auswahl und Kennzeichnung von Metadaten-Knoten
in unserem parallelen Dateisystem, in dem mehrere Prozessoren unabhängig voneinander
auf alle Platten, die das Dateisystem bilden, zugreifen können. Um
diese Fähigkeit
nutzen zu können,
sollte eine Datei von mehreren Prozessoren sowohl für Lese-
als auch Schreiboperationen gemeinsam benutzt werden.
-
Bei
diesem System wird für
jede Datei ein Knoten bestimmt, der für den Zugriff auf und die Aktualisierung
von den Metadaten der Datei zuständig
ist. Dieser Metadaten-Knoten (oder Metaknoten) teilt diese Information
auf Anfrage mit anderen Knoten.
-
Der
Metadaten-Knoten bewahrt die Informationen über die Metadaten der Datei
auf und hat die Funktion eines intelligenten Cachespeichers zwischen
der Platte und all denjenigen Knoten, die auf die Datei zugreifen.
Es gibt Situationen, in denen der Metadaten-Knoten (oder Metaknoten)
die Ausführung
dieser Funktion einstellt. Um einen reibungslosen Betrieb und eine
problemlose Wiederherstellung zu ermöglichen, müssen diese Situationen behandelt
werden. Knoten, die gewöhnlich
auf den Metaknoten zugegriffen haben, müssen auf direktem Weg einen
neuen Metaknoten wählen.
-
Wir
wählen
einen Metaknoten und stellen diese Information allen Knoten bereit.
Beim Wahlvorgang werden die Zugriffsmuster auf die Datei berücksichtigt.
Es sollte nur einen einzigen Metaknoten pro Datei geben. Zudem sollte
das Schema keine Übernahme
und keine Wiederherstellung von Metaknoten erlauben und tut dies
auch nicht. In unserem System werden Metaknoten ausgewählt, und
ihre Informationen sind anderen Knoten bekannt.
-
Wir
verwenden ein Token-Verwaltungsteilsystem. Ein Token-Verwaltungsprogramm
ist ein verteiltes Teilsystem, das Knoten Token gewährt. Jeder
Knoten kann um ein benanntes Token mit einer ganz bestimmten Betriebsart
bitten. Das Token-Verwaltungsprogramm
gewährt
dem Knoten das Token, wenn die Betriebsart nicht einen Konflikt
mit Token auslöst,
die denselben Namen haben und anderen Knoten gewährt wurden. Für jedes
Token gibt es eine Liste der möglichen
Betriebsarten und eine Konflikttabelle. Wenn das angeforderte Token
mit einem Token in Konflikt gerät,
das einem anderen Knoten gewährt
wurde, erfolgt ein Widerruf, und der den Konflikt auslösende Knoten
stuft seine Token-Betriebsart zurück auf eine Betriebsart, die
nicht mit der angeforderten Betriebsart in Konflikt gerät.
-
Der
Metadaten-Knoten wird zum ersten Knoten bestimmt, der auf die Datei
zugreift. Wenn folglich nur ein Knoten auf die Datei zugreifen muss,
sind keine Nachrichten beziehungsweise kein Mehraufwand erforderlich,
da der Knoten direkt auf die Metadaten zugreifen kann. Weitere Knoten
greifen zum Erhalt von Metadaten auf den Metaknoten zu.
-
Für jede Datei
legen wir das "Metaknoten-Token" fest. Es gibt drei
Betriebsarten für
das Metaknoten-Token: "ro" (read-only (Nur-Lese-Modus)), "ww" (weak-write (schwacher
Schreibmodus)) und "xw" (exclusive-write
(exklusiver Schreibmodus)). Die Regeln sind wie folgt: Das Token "xw" gerät mit allen
Betriebsarten in Konflikt. "ww" gerät mit "xw" und sich selbst
in Konflikt. "ro" gerät nur mit "xw" in Konflikt. Es
gibt folglich zwei Möglichkeiten:
Entweder 0 oder mehr Knoten halten das Token in "ro",
und dann kann höchstens
ein Knoten das Token in "ww" halten, oder ein
einziger Knoten hält
das Token in "xw". Das Teilsystem
Token Manager (oder kurz TM) ist für die Verwaltung von Token
für einen
Knoten und für
die Sicherstellung zuständig,
dass die Token-Betriebsarten mit dieser Festlegung übereinstimmen.
Die Konflikte zwischen den verschiedenen Betriebsarten lassen sich
in der folgenden Tabelle 5 zusammenfassen: TABELLE
5
-
Für den Metaknoten
haben wir den folgenden Algorithmus entwickelt: Wenn ein Knoten
eine Datei zum ersten Mal öffnet,
versucht er, das Token des Metaknotens in der Betriebsart "ww" zu erhalten. Das
Token-Verwaltungsprogramm TM gewährt
das Token in "ww", sofern dies möglich ist,
d.h., wenn kein anderer Knoten das Token in "ww" oder
in "xw" hält. Wenn
dies geschieht, übernimmt
der Knoten die Funktion des Metaknoten-Verwaltungsprogramms. Wenn jedoch ein
anderer Knoten das Token in "ww" hält, gewährt das
Token-Verwaltungsprogramm das Token in "ro".
Dann weiß der
Knoten, dass ein anderer Knoten der Metaknoten ist. Er kann eine
Anfrage an das Token-Verwaltungsprogramm
richten, um herauszufinden, welcher Knoten der Metaknoten für diese
Datei ist.
-
Es
gibt Situationen, in denen ein Knoten ein Metaknoten werden muss.
In diesem Fall hilft es nicht, um ein "ww"-Token
zu bitten, da der alte Metaknoten sein Token nicht zurückstuft.
Hier bittet der Knoten, der der Metaknoten werden will, um ein "xw"-Token. Dies bewirkt,
dass eine Widerrufnachricht an den aktuellen Metaknoten gesendet
wird. Der alte Metaknoten stuft dann sein Token auf "ro" zurück, und
das Token- Verwaltungsprogramm
schickt ein "ww"-Token an den neuen
Metaknoten zurück.
Wenn ein Knoten um ein "xw"-Token bittet und überhaupt
keine anderen Knoten dieses Token halten, gewährt das Token-Verwaltungsprogramm
das Token in dieser Betriebsart.
-
Wenn
ein Knoten das Token in "xw" hält, ist
er der Metaknoten für
diese Datei, aber darüber
hinaus ist diese Datei bei keinem anderen Knoten geöffnet. In
diesem Fall wird, wenn ein Knoten versucht, das Token in "ww" zu erhalten, eine
Widerrufnachricht an den Metaknoten gesendet. Folglich stuft der
Knoten sein "xw"-Token auf "ww" zurück, und
das Token-Verwaltungsprogramm
kann dem neuen Knoten somit ein "ro"-Token gewähren.
-
Verwendung von erweiterten
Token-Betriebsarten zur Steuerung der Dateigröße
-
Die
entsprechenden Dateisystem-Standards setzen voraus, dass die korrekte
Dateigröße auf Anforderung
verfügbar
ist; jedoch ist die parallel an allen Knoten erfolgende Verwaltung
der Dateigröße bei Vorhandensein
von mehreren Anwendungen, die Daten an die Datei anfügen, schwierig
und leistungsintensiv. Das nächste
dieser Reihe von Merkmalen beschreibt unsere Art der Verwaltung
der Dateigröße, so dass
sie bei Bedarf ohne ständigen
Mehraufwand verfügbar
ist. Dabei kann ein paralleles Dateisystem, bei dem mehrere Prozessoren
auf alle Platten, die das Dateisystem bilden, unabhängig voneinander
zugreifen können,
mit einer Datei, die von mehreren Prozessoren sowohl für Lese-
als auch Schreiboperationen gemeinsam benutzt wird, ohne ständigen Mehraufwand
verwendet werden.
-
Die
Freigabe von Dateien für
Lese- und Schreiboperationen beinhaltet den Zugriff auf die Dateigröße. Jede
Lese- und Schreiboperation muss prüfen, ob der Offset der Operation
außerhalb
der aktuellen Dateigröße liegt,
und wenn ja, muss sie eine EOF-Nachricht, die das Ende der Datei
(end of file, EOF) anzeigt, zurückschicken.
Jede Schreiboperation muss prüfen,
ob der Offset der Operation außerhalb
des aktuellen Dateiendes liegt, und wenn ja, sollte sie die Datei
erweitern. Wenn es mehrere Benutzer gibt, die Daten lesen und schreiben,
muss dies alles übereinstimmend
stattfinden. Wenn ein Knoten am Offset 1000 Daten schreibt, sollte
bei einer Leseoperation durch einen beliebigen Knoten an diesem
Speicherplatz folglich keine EOF-Nachricht zurückgeschickt werden.
-
Eine
Möglichkeit,
einen übereinstimmenden
Zustand aufrechtzuerhalten, besteht darin, die Zugriffe auf die
Dateigröße zeitlich
aufeinander folgend durchzuführen.
Dies stellt jedoch für
Benutzer, die parallel Schreiboperationen durchführen, einen großen Engpass
dar, da jede Schreiboperation (und jede Leseoperation) vor ihrer
Durchführung
die aktuelle Dateigröße abrufen
muss.
-
In
unserer bevorzugten Ausführungsform
hinterlegen wir in jedem Knoten eine lokale Kopie der Dateigröße. Zusammen
mit jeder Kopie wird auch eine Sperre hinterlegt. Ein Sperrenverwaltungsprogramm
stellt sicher, dass keine Sperrmodi gleichzeitig vorhanden sein,
die miteinander in Konflikt geraten. Ein geeigneter Sperrmodus für jede Lese-
und Schreiboperation stellt sicher, dass die lokal zwischengespeicherte
Dateigröße genau
genug ist, damit diese Operation ein korrektes Ergebnis erzielen
kann. Die verschiedenen Betriebsarten sind wie folgt:
- – "rw" bei Operationen,
die innerhalb der lokal zwischengespeicherten Dateigröße Lese-und
Schreiboperationen durchführen.
- – "rf" bei Operationen,
die außerhalb
der lokal zwischengespeicherten Dateigröße Leseoperationen durchführen.
- – "wf" bei Operationen,
die außerhalb
der lokal zwischengespeicherten Dateigröße Schreiboperationen durchführen.
- – "wa" bei Schreiboperationen,
die Daten an die Datei anfügen
- – "xw" bei Operationen,
die die Dateigröße verringern
wie zum Beispiel die Operation "kürzen" (truncate) und folglich
eine Sperre für
eine exklusiven Schreibzugriff benötigen.
-
Die
Konflikttabelle der Sperrmodi der Dateigröße schaut folgendermaßen aus: TABELLE
6
-
Immer,
wenn ein Knoten seinen Sperrmodus aktualisiert, liest er die neue
Dateigröße aus einem
speziellen Knoten, der die Dateigröße überwacht (dem Metadaten-Knoten
oder kurz Metaknoten). Immer, wenn ein Knoten seinen Sperrmodus
zurückstuft,
sendet er seine Dateigröße an den
Metaknoten. Der Metaknoten selbst behält eine Dateigröße, die
den Höchstwert
aller von ihm empfangenen Dateigrößen darstellt (außer, wenn
ein Knoten die Dateigröße in der
Betriebsart "xw" sperrt, was die
Verringerung der Dateigröße erlaubt).
-
Manche
Betriebsarten lassen nur das Lesen der Dateigröße zu (rw rf). Manche Betriebsarten
(wf, wa) gestatten es, dass die Dateigröße zunimmt. Eine Betriebsart
(xw) gestattet es, die Dateigröße zu verringern. Die
wirkliche Dateigröße ist der
Höchstwert
aller lokaler Kopien der Dateigrößen, die
der Knoten hält.
-
Operationen,
die in der lokal zwischengespeicherten Kopie der Dateigröße lesen
oder schreiben, benötigen
eine "rw"-Sperre auf die Dateigröße. Operationen,
die außerhalb
der lokal zwischengespeicherten Kopie der Dateigröße lesen,
müssen
sicherstellen, dass die Dateigröße nicht
zugenommen hat, seit sie die Dateigröße das letzte Mal gelesen haben.
Sie müssen folglich
in den Besitz einer "rf"-Sperre kommen (die
mit Betriebsarten, die die Dateigröße erhöhen, in Konflikt geraten).
-
Operationen,
die die Dateigröße erhöhen, erwerben
entweder eine "wf"-Sperre oder eine "wa"-Sperre. Eine "wf"-Sperre wird benötigt, wenn
derjenige, der Daten schreibt, die neue absolute Dateigröße kennt.
Eine "wa"-Sperre wird für Anfüge-Operationen (APPEND-Operationen)
benötigt.
Eine APPEND-Operation
schreibt an das aktuelle Dateiende (EOF). Bei mehreren APPEND-Operationen
schreibt folglich jede APPEND-Operation
jeweils an das Ende der anderen. Somit gerät "wa" in
Konflikt mit sich selbst, da eine APPEND-Operation auf andere APPEND-Operationen
warten sollte.
-
Die
einzige Betriebsart, die eine Verringerung der Dateigröße zulässt, ist "xw". Dabei handelt es
sich um eine exklusive Betriebsart, die bewirkt, dass alle anderen
Knoten ihre Sperren aufgeben und somit die lokal zwischengespeicherte
Dateigröße verlieren.
Nachdem also der Knoten, der die "xw"-Sperre erworben hat,
seine Operation beendet (beispielsweise eine Dateikürzungsoperation),
müssen
alle Knoten die neue Dateigröße von dem
Metaknoten abrufen.
-
Uns
ist kein System bekannt, bei dem verschiedene Dateigrößen an verschiedenen
Knoten zwischengespeichert werden, so dass die Freigabe der Datei
für parallele
Schreiboperationen auf ein Höchstmaß gesteigert
wird und das System dennoch allen Benutzen eine übereinstimmende Ansicht der
Datei liefert.
-
Die
Lösung
ermöglicht
Benutzern an verschiedenen Knoten, die Datei zu erweitern und auf
diese Weise einen sehr hohen Grad der Freigabe von Schreiboperationen
zu erreichen. Schreiboperationen brauchen selbst dann nicht zeitlich
aufeinander folgend durchgeführt
werden, wenn die Benutzer die Dateigröße erweitern.
-
Intelligente Zwischenspeicherung
von Bytebereichs-Token mit Hilfe von Dateizugriffsmustern
-
Die
nächste
unserer Entwicklungen im Rahmen von parallelen Schreiboperationen
befasst sich mit den für
alle Zugriffe verwendeten Sperrmechanismen, und zwar mit parallelen
und mit nichtparallelen. Das Sperren von nur demjenigen Teil der
Datei, der sofort benötigt
wird, ist aufwändig
und würde
bei jedem Aufruf der Anwendung Aufrufe des Sperrenverwaltungsprogramms
erforderlich machen. Dieser Algorithmus versucht, die Erfordernisse
der Anwendung vorwegzunehmen, indem er sorgfältig prüft, welche Abläufe noch
in dem System stattfinden, und die Anzahl der Aufrufe des Token-Verwaltungsprogramms
so gering wie möglich zu
halten.
-
Bei
parallelen Lese- und Schreiboperationen in die gleiche Datei wird
ein verteilter Sperrmechanismus verwendet, um die Zugriffe auf dieselben
Bereiche in einer Datei zeitlich aufeinander folgend durchzuführen. Der
Erhalt einer solchen Sperre setzt jedoch gewöhnlich voraus, dass zuerst
ein Token abgerufen wird, und dies gilt als eine aufwendige Operation.
Folglich wäre
es von Vorteil, Tokens an einem Knoten zwischenzuspeichern, indem
man die Zugriffsmuster der Datei vorhersieht. Andererseits könnte der
Erwerb eines Token, das nicht gebraucht wird, die Leistungsfähigkeit
herabsetzen, da dieses Token von einem anderen Knoten gebraucht
werden könnte.
Diese Beschreibung legt die Algorithmen dar, mit denen ein Knoten
ein Token erwirbt, um die Leistungsfähigkeit auf ein Höchstmaß zu steigern,
indem man die Zugriffsmuster der Datei vorhersieht.
-
Die
zeitlich aufeinanderfolgende Durchführung von Zugriffen auf verschiedene
Bereiche in einer Datei, in die Prozesse auf verschiedenen Knoten
parallel Daten schreiben, erfolgt durch verteilte Bytebereichssperren.
Wenn ein Prozess einen Bytebereich sperren muss, muss er zuerst
ein entsprechendes Bytebereichs-Token erwerben. Das Bytebereichs-Token
stellt die Zugriffsrechte des Knotens auf einen Teil einer Datei
dar. Wenn ein Knoten folglich ein Bytebereichs-Token für den Bereich
(100, 200) einer Datei X im Lesemodus hält, bedeutet dies, dass der
Knoten diesen Teil der Datei sicher lesen kann. Um jedoch zu verhindern,
dass das Token gestohlen wird, muss der Knoten das Token vor der
eigentlichen Leseoperation sperren, da ein anderer Knoten, der möglicherweise
denselben Bereich beschreiben muss, das Token stehlen könnte. Durch
das Sperren des Token wird verhindert, dass es gestohlen wird. Nach
erfolgter Leseoperation wird das Token entsperrt.
-
Man
kann Token als eine Möglichkeit
sehen, Sperren im Cachespeicher "zwischenzuspeichern". Wenn ein Knoten
einen Teil einer Datei sperren muss, muss er das Token sperren.
Zuerst erwirbt er ein Token und sperrt es. Sobald die Operation
abgeschlossen und das Token entsperrt ist, befindet es sich immer
noch an dem Knoten. Folglich müssten
nachfolgende Operationen in demselben Bereich nicht auf die Token-Instanz zugreifen.
Nur wenn das Token gestohlen wird, muss eine neue Anforderung für das Token
gestellt werden.
-
Wenn
man dies berücksichtigt,
mag es von Vorteil sein, ein größeres Token
als das, das gesperrt werden muss, anzufordern. Wenn ein Prozess
zum Beispiel eine Datei in Folge liest und dabei vom Bereich 1000 bis
zum Bereich 2000 liest, kann er, obgleich die nächste Sperre für den Bereich
1000 bis 2000 gilt, ein größeres Token
anfordern, zum Beispiel vom Bereich 1000 bis zum Bereich 10000.
Dies kann jedoch einen übermäßigem Token-Verkehr
auf anderen Knoten erzeugen. Wenn ein anderer Knoten gerade dabei
ist, vom Bereich 5000 bis zum Bereich 6000 zu schreiben, kann der
Vorgang des Erwerbs des Token die Operation verzögern.
-
Es
ist beabsichtigt, beim Erwerb eines Bytebereich-Token zwei Bereiche
zu vergeben: einen benötigten
Bereich (dies ist der größtmögliche Bereich,
der für
die Operation benötigt
wird) und den gewünschten
Bereich (dies ist der größtmögliche Bereich,
der von Nutzen sein soll). Das Token-Verwaltungsprogramm stellt sicher, dass
ein Token gewährt
wird, das den benötigten
Bereich abdeckt, aber nicht größer als
der gewünschte
Bereich ist.
-
Zwei
Algorithmen müssen
festgelegt werden: (1) wie der gewünschte Bereich und der benötigte Bereich
für jede
Operation zu berechnen ist; dies gilt für die anfordernde Seite; (2)
wie der gewährte
Bereich zu berechnen ist; dies gilt für Knoten, die miteinander in
Konflikt stehende Token halten.
-
Bei
den vorstehenden Algorithmen unterscheiden wir zwischen zwei Dateizugriffsmustern:
zufällig
und sequenziell. Bei zufälligen
Zugriffen kann der Anfangs-Offset der nächsten Operation nicht vorhergesagt
werden. Bei in Folge durchgeführten
Operationen wird davon ausgegangen, dass sie an dem Punkt starten,
an dem die vorherige Operation beendet wurde. Jede Datei kann auf
jedem Knoten mehrfach geöffnet
sein, und jede solche Instanz kann ein anderes Zugriffsmuster darstellen.
-
Wir
bevorzugen den folgenden Algorithmus. Das Hauptziel besteht darin,
den Token-Verkehr auf ein Mindestmaß zu verringern.
-
Bei
dem Versuch, einen Bytebereich mit einer Sperre zu belegen, fragen
wir zuerst das Token-Verwaltungsprogramm ab, um zu sehen, ob es
auf dem Knoten ein kompatibles Token gibt. Der Bereich, der geprüft wird,
ist der von der Operation benötigte
Mindestbereich. Wenn das Token lokal verfügbar ist, wird es gesperrt, und
es findet keine weitere Token-Aktivität statt.
-
Wenn
das Token jedoch nicht verfügbar
ist, wird ein Token angefordert. Der benötigte Bereich wird auf der
Grundlage des Offset und der Länge
der Dateioperation berechnet. Der gewünschte Bereich beruht auf dem
Zugriffsmuster der Datei. Wenn der Zugriff auf die Datei zufällig erfolgt,
ist der gewünschte
Bereich gleich dem benötigten
Bereich, da es wahrscheinlich nicht von Vorteil ist, Token (die
wahrscheinlich nicht benötigt würden) von
anderen Knoten zu stehlen. Wenn der Zugriff auf die Datei jedoch
sequenziell erfolgt, beginnt der gewünschte Bereich am Beginn des
benötigten
Bereichs, endet aber bei unendlich (es gibt einen speziellen Wert
zur Darstellung der Unendlichkeit). Dies geschieht in dem Versuch,
zukünftige
Token-Anforderungen auf ein Mindestmaß herabzusetzen, da wir die
zukünftigen
Sperren, die benötigt
werden, vorhersagen können.
-
Wenn
ein Knoten ein Token hält,
das mit einer Anforderung für
ein Token auf einem anderen Knoten in Konflikt gerät, erhält er eine
Widerrufaufforderung. Die Aufforderung enthält die benötigten und die gewünschten
Bereiche des anfordernden Knotens. Hier muss der Knoten eine Entscheidung
darüber
treffen, welchen Bereich er abtreten kann. Wenn der benötigte Bereich
gleich dem gewünschten
Bereich ist, ist die Entscheidung leicht, und der Bereich, der gewährt wird,
ist der benötigte
(und gewünschte)
Bereich. Wenn sich der gewünschte
Bereich jedoch von dem benötigten
Bereich unterscheidet, bedeutet dies, dass der anfordernde Knoten
sequenziell auf die Datei zugreift und ein Token haben möchte, das
am Anfang des benötigten
Bereichs beginnt, aber bei unendlich endet. Der Knoten durchläuft dann
alle seine aktiven Prozesse, die auf die Datei zugreifen, und prüft, ob sie
sequenziell oder zufällig
auf die Datei zugreifen. Wenn alle von ihnen zufällig auf die Datei zugreifen,
gewährt
der Knoten den gewünschten
Bereich. Wenn jedoch einer oder mehrere der Prozesse sequenziell
auf die Datei zugreifen, würde
das Abtreten des gewünschten
Bereichs eine Verschwendung darstellen, da wir mit hoher Wahrscheinlichkeit
wissen, welches Token bald angefordert werden wird. In diesem Fall
werden die Dateizeiger (d.h. der vorhergesehene Speicherort der
nächsten
Operation) von allen sequenziellen Operationen geprüft und der
Mindest-Offset berechnet. Es kann vorweggenommen werden, dass diese
Operationen nicht auf die Dateibereiche zugreifen, die diesen Mindestwert
unterschreiten, da es sequenzielle Operationen sind. Folglich wird
der gewährte
Bereich auf diesen berechneten Mindestwert gestreckt, wenn dieser
höher als
der benötigte
Bereich ist.
-
Uns
ist kein System bekannt, bei dem Bytebereich-Token auf der Grundlage
des Zugriffsmusters der Datei angefordert werden.
-
Die
Lösung
ermöglicht
es, Token mit Rücksicht
auf das Dateizugriffsmuster im Cachespeicher zwischenzuspeichern.
Dies erspart den Erwerb an Token, was eine aufwändige Operation ist, und verbessert
damit die Gesamtleistungsfähigkeit
des Systems.
-
Jedes
Parallelverarbeitungssystem, das es gestatten muss, dass Dateien
für parallele
Schreiboperationen freigegeben werden, muss Zugriffe auf dieselben
Bereich in der Datei zeitlich aufeinenderfolgend durchführen.
-
Bytebereich-Token-Schnittstelle
-
Diese
Verbesserung in Form von parallelen Schreiboperationen ermöglicht die
Verwaltung von Informationen, die Token beschreiben, welche einen
Bytebereich-Sperralgorithmus mit einer Bytebereich-Token-Schnittstelle
verwenden. Unser paralleles Dateisystem, in dem auf alle Platten,
die das Dateisystem bilden, von mehreren Prozessoren unabhängig voneinander
zugegriffen werden kann, macht es bei seiner Nutzung erforderlich,
dass eine Datei sowohl für
Lese- als auch für
Schreiboperationen von mehreren Prozessoren gemeinsam benutzt wird.
Um eine parallele Schreiboperation zu ermöglichen, während gleichzeitig eine Übereinstimmung
der Dateien gewährleistet
wird, ist ein Sperrmechanismus für
Bereiche in Dateien notwendig. In einer verteilten Umgebung werden
manchmal Token verwendet. Dieses Token stellt die Zugriffsrechte eines
Knotens auf ein Objekt dar. Ein Knoten könnte jedoch mehrere Prozesse
ausführen,
die versuchen, auf denselben Bereich einer Datei zuzugreifen; folglich
ist ein lokaler Sperrmechanismus auf die Tokens erforderlich. Ferner
muss vielleicht ein anderer Knoten auf denselben Bereich zugreifen
und versucht möglicherweise, das
Token von diesem Knoten zu widerrufen; ein Widerruf sollte also
nicht stattfinden, solange ein lokaler Prozess das Token sperrt.
Somit sollte irgendeine Art von Sperralgorithmen für diese
Tokens verwendet werden, die von unserem Token-Verwaltungsprogramm verwaltet werden,
das unsere Verbesserung gegenüber
der
US-Patentschrift 5 343 108 darstellt,
die auf die International Business Machines Corporation übertragen
wurde.
-
Um
Zugriff auf einen Bereich in einer Datei zu erlangen, muss ein Knoten
zuerst das entsprechende Token abrufen, es dann sperren, die Operation
durchführen
und das Token anschließend
entsperren. Es gibt mehrere Probleme in Verbindung mit dem Sperren
der Tokens; erstens kann ein Token bereits in dem Knoten zwischengespeichert
sein. In diesem Fall brauchen wir es nicht erneut zu erwerben. Zweitens
müssen
wir sicherstellen, dass Sperren in demselben Knoten nicht miteinander
in Konflikt geraten; drittens müssen
wir Widerrufanforderungen von anderen Knoten verarbeiten, die ein
Token brauchen, das aber mit einem Token in Konflikt steht, das
wir gerade halten. Unser hier vorgestellter Sperralgorithmus löst diese
Probleme wirksam.
-
Unser
Sperralgorithmus liegt in Form von einer Gruppe von APIs vor. Zwei
APIs werden zum Sperren und Entsperren eines Bytebereichs verwendet.
Eine dritte API ist eine Rückruffunktion,
die von dem Token-Verwaltungsprogramm aufgerufen wird. Es wird davon
ausgegangen, dass das Token-Verwaltungsprogramm ebenfalls
drei APIs bereitstellt. Eine API wird zum Erwerb des Bytebereich-Token
benötigt
("Acquire"). Eine zweite API
wird benötigt,
um zu prüfen,
ob ein Bytebereich-Token bereits in dem Knoten zwischengespeichert ist
("Test"). Eine dritte API
wird benötigt,
wenn ein Token als Reaktion auf einen Widerruf abgetreten wird ("Relinquish"). Zum Zweck des
Zugriffs auf Bereiche in Dateien enthält jedes Token ein Spektrum
(Anfang, Ende) des Bereichs der Datei, auf den es zugreifen kann.
-
Wir
beschreiben nun die APIs des Token-Verwaltungsprogramms näher, bei
denen es sich um eine Annahme handelt. Eine Erwerbsfunktion der
Form
Acquire(byte_range),
die aufgerufen wird, um ein
Bereichs-Token zu erwerben,
und eine Widerruf-Rückruffunktion
der Form
Revoke(byte_range),
die das Token-Verwaltungsprogramm
immer aufruft, wenn ein anderes Token dieses Token braucht.
-
Folglich
sollte der Knoten folgendes aufrufen:
Relinquish(byte_range)
-
Der
Algorithmus, den wir ausführen,
beruht außerdem
auf einer vierten Schnittstelle, die von dem Token-Verwaltungsprogramm
bereitgestellt werden muss:
Test(byte_range),
die das
Token-Verwaltungsprogramm auf das Vorhandensein des Token auf dem
Knoten abfragt.
-
Um
die Ausführung
zu vereinfachen, überwachen
wir nicht die Tokens, die wir halten; dies überlassen wir dem Token-Verwaltungsprogramm,
und wir verwenden die Schnittstelle "Test",
um abzufragen, ob der Erwerb eines Token erforderlich ist. Gewöhnlich müssen beim
Erwerb eines Token Aktionen durchgeführt werden. Folglich ist es
wünschenswert,
zu wissen, ob ein Token bereits gehalten wir, so dass man sich diese
Aktionen sparen kann.
-
Der
Algorithmus beruht auf einer Sperrentabelle (Bereichssperrentabelle
(Range lock table beziehungsweise RLT), die alle vorhandenen Sperren
enthält.
Die Tabelle wird von einer Mutex geschützt, um atomare Einfüge- und
Löschoperationen
von Sperren zu ermöglichen.
Drei Hauptfunktionen werden dargelegt: LOCK, die einen Bytebereich
sperrt; UNLOCK, die einen zuvor gesperrten Bereich entsperrt; und
REVOKE, die eine Widerrufaufforderung verarbeitet.
-
Wir
zeigen den Pseudocode für
diese Schnittstellen auf:
-
Wir
haben somit eine Bytebereich-Sperre beschrieben. Uns sind zwar keine
Algorithmen für
Bytebereich-Sperren bekannt, doch weisen wir darauf hin, dass bisherige
Lösungen
für Nicht-Bytebereich-Sperren eine
Kopie der Token-Zustände
außerhalb
des Token-Verwaltungsprogramms aufbewahren würden.
-
Hier
würden
wir anmerken, dass unser verteiltes Token-Verwaltungsprogramm Schnittstellen (Acquire, Revoke,
Relinquish und Test) bereitstellt, um Bereiche (d.h. Bytebereiche
einer Datei) zu sperren. Ein bestimmter Bereich kann entweder in
einer Betriebsart mit freigegebenem Lesezugriff oder mit exklusivem Schreibzugriff
angefordert werden kann.
-
Eines
der Merkmale unserer Erfindung ist, dass wir eine Token-Anforderung für einen
angegebenen Bytebereich prüfen,
um die Anforderung mit den vorhandenen in Konflikt stehenden Bereichen
in dem gesamten Mehrknoten-System zu vergleichen und den größtmöglichen
Bytebereich zu gewähren,
der keinen Widerruf eines Token von einem anderen Rechner erforderlich
macht. Dadurch wird die Wahrscheinlichkeit verringert, dass die
nächste
Operation auf dem anfordernden Knoten eine weitere Token-Anforderung
notwendig macht. Zähler
und Aufrufe von nicht blockierenden Sperren dienen zum Erwerb von
Token, während
andere Sperren geladen werden. Dieses Verfahren ermöglicht eine
wirksamere Serialisierung bei mehreren Anforderungen in einem einzelnen
Knoten, was die erforderliche Serialisierung von mehreren Knoten
gestattet.
-
Es
ist folglich vorgesehen, dass eine Betriebsart sowie zwei Bereiche,
ein Bereich "benötigt" und ein Bereich "gewünscht", die Eingabe in
die Schnittstelle "Acquire" des Token-Verwaltungsprogramms
bilden. Der gewünschte
Bereich muss eine Obermenge des benötigten Bereichs sein. Es ist
gewährleistet,
dass einer Anwendung, die die Schnittstelle "Acquire" aufruft, mindestens der benötigte Bereich
gewährt
wird. Das Token-Verwaltungsprogramm
stellt fest, ob anderen Knoten in Konflikt stehende Bereiche (d.h.
Bereiche, die den benötigten
Bereich in einer Betriebsart überlappen,
die einen Konflikt auslöst)
gewährt
wurden. Wenn Bereiche gefunden werden, die miteinander in Konflikt
stehen, verlangt das Token-Verwaltungsprogramm, dass jeder Knoten,
der einen Bereich hat, der mit anderen Bereichen in Konflikt steht,
den überlappenden
Bereich in eine konfliktfreie Betriebsart zurückstuft.
-
Wir
sehen ferner vor, dass die Schnittstelle "Acquire" nach erfolgter Auflösung von Konflikten mit dem benötigten Bereich
den größten zusammenhängenden
Bereich ermittelt, der den benötigten
Bereich vollständig
abdeckt und auch eine Untermenge des gewünschten Bereichs darstellt.
Dies ist der Bereich, den die Schnittstelle "Acquire" der aufrufenden Anwendung zurückschickt.
Tatsächlich
gewährt
das Token-Verwaltungsprogramm
den größtmöglichen
Bereich (der durch den Parameter für den gewünschten Bereich begrenzt ist),
bei dem keine zusätzliche
Verarbeitung aufgrund eines Widerrufs durchgeführt werden muss.
-
Über die
Schnittstelle "Revoke" des Token-Verwaltungsprogramms
werden einer Anwendung Informationen über eine einen Konflikt auslösende Bereichsanforderung
von einem anderen Knoten übermittelt.
Wenn eine Anforderung von der Schnittstelle "Acquire" miteinander in Konflikt stehende Bereiche
feststellt, die anderen Knoten gewährt wurden, verlangt sie, dass
die Anwendung, die auf jedem der in Konflikt stehenden Knoten läuft, die
Bereiche zurückstuft,
die ihnen gewährt
wurden. Zu den Informationen, die über die Schnittstelle "Revoke" übergeben werden, gehören die
Betriebsart sowie die benötigten/gewünschten
Bereiche, die in dem Aufruf der Schnittstelle "Acquire" genannt wurden.
-
Nach
dem Empfang einer Widerrufanforderung ruft eine Anwendung die Schnittstelle
Relinquish auf, um miteinander in Konflikt stehende Bereiche zurückzustufen,
die sie einer konfliktfreien Betriebsart gewährt hat. Die Anwendung muss
zumindest Bereiche, die mit dem "benötigten" Bereich in Konflikt
stehen, in eine konfliktfreie Betriebsart zurückstufen, kann aber auch einen
größeren Bereich
zurückstufen,
wenn sie möchte.
-
Das
Token-Verwaltungsprogramm stellt auch eine Schnittstelle "Test" bereit, die feststellt,
ob dem lokalen Knoten ein bestimmter Bereich gewährt worden ist. Diese kann
von einer Anwendung zur Feststellung verwendet werden, ob eine Anforderung
von der Schnittstelle "Acquire" für einen bestimmten
Bereich eine Datenübertragungsanforderung
an den Token-Serverknoten erforderlich macht.
-
Indem
bei der Verarbeitung Folgenummern für einen bestimmten Bytebereich
verwendet werden, ermöglichen
wir eine korrekte Verarbeitung von Erwerb- und Widerrufoperationen
in denselben Bytebereichen. Die Schnittstelle "Acquire" des Token-Verwaltungsprogramms verwendet eine
Folgenummer als Argument. Bei jedem Token verwaltet das Token-Verwaltungsprogramm
eine Folgenummer für
jeden Knoten, dem ein Bereich gewährt wurde. Das Token-Verwaltungsprogramm
aktualisiert das Feld, das die Folgenummer eines Knotens enthält, bei
Abschluss einer Erwerbsoperation "Acquire" mit dem in der Schnittstelle "Acquire" angegebenen Wert.
Wenn eine nachfolgende Erwerbsoperation "Acquire" Bereiche von Knoten widerrufen muss, die
miteinander in Konflikt stehen, übergibt
das Token-Verwaltungsprogramm
die Folgenummer der letzten erfolgreichen Erwerbsoperation von diesem
Knoten über
die Schnittstelle "Revoke" des Token-Verwaltungsprogramms.
-
Im
Hinblick auf die Schnittstellen zu dem verteilten Token-Verwaltungsprogramm
(Acquire, Revoke, Relinquish, Test) haben wir ein verbessertes Verfahren
zur Realisierung von lokalen Sperren auf Bytebereiche in dem verwendeten
Code vorgesehen. Mehrere mögliche
Schwierigkeiten lassen sich mit Hilfe dieser Programm-Verfahren
oder -Algorithmen elegant lösen,
während
gleichzeitig die Ausführung
einiger anspruchsvoller Funktionen ermöglicht wird:
Wir verarbeiten
mehrere Token-Erwerb- und -Widerruffunktionen parallel unter Verwendung
der nachstehend beschriebenen Sperrverfahren mit dem Pseudocode
in der ursprünglichen Offenlegung.
Wir gestatten die parallele Verarbeitung von mehreren Token-Erwerboperationen.
Dieser Fall kann zum Beispiel eintreten, wenn mehrere Operationen
des Dateisystems versuchen, auf verschiedene Abschnitte einer Datei
parallel zuzugreifen.
-
Und
wir gestatten es, dass der Widerruf eines Token für einen
Teil einer Datei gleichzeitig mit dem Erwerb eines Token stattfinden
kann, solange die beiden Operationen nicht miteinander in Konflikt
stehen.
-
Man
wird als vorteilhaft erkennen, dass wir keine Kopie des lokalen
Token-Zustands im Sperrcode des Bytebereichs hinterlegen müssen.
-
Wir
schließen
eine Situation einer dauerhaft blockierten Aktivität (livelock)
aus, bei der ein Token kurz nach seinem Erwerb, aber bevor es gesperrt
wird, von einem anderen Knoten widerrufen wird. Der andere Knoten
erwirbt das Token, und bevor es gesperrt wird, wird es erneut gestohlen.
Durch diesen Ping-Pong-Effekt ist kein Weiterkommen möglich.
-
Als
Folge, dass wir keine Kopie des lokalen Token-Zustands in dem Bytebereich-Sperrcode
hinterlegen müssen,
nimmt nun der Speicherbedarf unseres Programms ab, da diese Information
bereits im Token-Verwaltungsprogramm gespeichert ist. Eine API fragt
das Token-Verwaltungsprogramm an, um herauszufinden, ob das Token
bereits im Cachespeicher abgelegt ist. Nach erfolgter Sperrung des
Bytebereichs wird ein spezieller Mechanismus bereitgestellt, der
dazu dient, sicherzustellen, dass das Token nicht widerrufen worden
ist, nachdem eine Prüfung
auf das Vorhandensein des Token stattgefunden hat und bevor es gesperrt wurde.
Es ist möglich,
dass das Token zwischen diesen beiden Vorgängen widerrufen wurde. In diesem
Fall erwerben wir das Token und versuchen es erneut.
-
Derselbe
Bytebereich-Sperrcode, der von den Operationen des Dateisystems
verwendet wird, wird auch von der Widerruf-Rückruffunktion
verwendet. Eine spezielle Markierung hat jedoch erkennen lassen, dass
dies eine Sperre für
einen Widerruf (lock-for-revoke) ist. Dadurch wird der Code kompakter
und erlaubt die Verwendung derselben Sperrentabellen. Die API, die
zum Sperren eines Bytebereichs verwendet wird, unterstützt verschiedene
Optionen, die zu einer Verbesserung ihres Betriebs beitragen. Non-blocking
(blockierungsfrei); Local-lock (lokale Sperre); Test; und Sequential
(sequenziell). Die Option "non-blocking" ermöglicht einen
blockierungsfreien Betrieb; wenn wir das Token nicht haben oder
wenn eine Sperre, die einen Konflikt auslöst, gehalten wird, kehrt der
Sperrcode sofort mit einem entsprechenden Rückkehrcode zurück.
-
Die
Option "non-blocking" ermöglicht einen
blockierungsfreien Betrieb; wenn wir keine globale Sperre, sondern
nur eine Sperre innerhalb des Knotens setzen müssen, können wir diese Option verwenden.
-
Die
Option "Test" ermöglicht die
Feststellung, ob wir den Bytebereich sperren können, ohne jedoch wirklich
eine Sperre zu setzen.
-
Die
Option "sequential" gibt einen Hinweis
darauf, dass wir einen Bytebereich für eine Leseoperation (oder
eine Schreiboperation) in eine(r) Datei sperren, auf die sequenziell
zugegriffen wird. Dieser Hinweis wird verwendet, wenn ein Token
gebraucht wird. In diesem Fall wird ein Token, das größer als
das Token ist, das wirklich gebraucht wird, gewünscht (jedoch ist es nicht
notwendig).
-
Besondere
Vorkehrungen werden getroffen, um die verschiedenen Sperren, die
von den Threads gehalten werden, zu überwachen. Ein Dienstprogramm
zur Fehlerbehebung gibt die vorhandenen Bytebereich-Sperren und
die Nummern der Threads aus, wo sie gehalten werden. Zudem werden
Statistiken geführt, damit
sich die Dateizugriffsmuster und die Verhaltensmuster von Sperren
verstehen lassen.
-
Dadurch,
dass für
jede erfolgreiche Sperroperation eine Kennung zurückgeliefert
wird, ist eine Entsperroperation sehr schnell und erfordert weder
eine Such- noch eine Nachschlageoperation.
-
Indem
man Zähler
für die
verschiedenen vorhandenen Sperrbetriebsarten unterhält, ist
die Operation, die prüft,
ob eine Sperre vorhanden ist, die einen Konflikt auslöst, schnell.
Wenn wir beispielsweise einen Zähler
für die
Anzahl der aktiven Sperren für
freigegebene Leseoperationen und der aktiven Sperren für exklusive Schreiboperationen
unterhalten, können
wir in vielen Fällen
wissen, ob wir eine Prüfung
auf das Überlappen von
Bereichen durchführen
müssen.
Wenn es zum Beispiel keine Sperren für exklusive Schreiboperationen gibt
und wir eine Sperre für
eine freigegeben Leseoperation benötigen, wissen wir, dass es
keinen Konflikt gibt, und wir müssen
lediglich einen freien Platz in der Sperrentabelle finden.
-
Der
Sperrcode bietet Unterstützung
für eine
unbegrenzte Anzahl von Anforderungen für Bytebereich-Sperren. Falls
die Sperrentabelle voll wird oder eine Sperre angefordert wird,
die einen Konflikt auslöst, wird
der Thread, der um die Sperre bittet, in den Ruhemodus versetzt
und erst aufgeweckt, wenn eine Sperre entsperrt wird.
-
Bei
unserer Lösung
werden die Informationen über
das Token nicht dupliziert, und sie ist folglich kompakt und leistungsfähig.
-
Wiederherstellung in der Umgebung
eines Token-Verwaltungsprogramms
-
Die
Schwierigkeiten des parallelen Dateisystems sind gewaltig, da mehrere
Prozessoren aus verschiedenen Teile des Dateisystems in jeder Instanz
lesen und in sie schreiben. Es stellt sich die Frage, was geschieht,
wenn es in dieser Umgebung zu einem Ausfall kommt. Wir ermöglichen
eine Wiederherstellung in dieser Umgebung. Der erste Wiederherstellungsmechanismus
betrifft die Frage, was geschieht, wenn ein Knoten ausfällt und
die Metadaten zum Zeitpunkt des Ausfalls gerade aktualisiert werden.
Er beschreibt ein Verfahren, das die Wiederherstellung des Zustands
des Token, die Wiedergabe von Aufzeichnungen der Metadaten und eine
strenge Festlegung der Reihenfolge von Operationen beinhaltet.
-
Wiederherstellungsmodell für das parallele
Dateisystem
-
Unser
Wiederherstellungsmodell kann auf unser Dateisystem mit gemeinsam
benutzten Platten angewendet werden. Die Platten sind entweder über mehrere
Plattenkabel (z.B. SCSI oder SSA) oder in Form eines an das Netzwerk
angeschlossenen Speichers angeschlossen. Jeder Prozessor hat unabhängigen Zugriff
auf die Platte, und die Übereinstimmung
der Daten/Metadaten wird über
die Verwendung eines verteilten Sperrenverwaltungsprogramms gewahrt.
Aktualisierungen der Metadaten werden von jedem Prozessor unabhängig protokolliert,
um bei einem Ausfall ein Absuchen des Dateisystems zu verhindern.
-
Die
Schwierigkeit des Problems besteht darin, dass Prozessoren ausfallen
können
(entweder Software oder Hardware). Diese Ausfälle können in Form eines tatsächlich katastrophalen
Ausfalls des Prozessors oder eines Ausfalls der Datenübertragungsfunktion
zur Teilnahme an dem Sperrenverwaltungsprotokoll bestehen. Während dieser
Ausfälle
kann der fehlerhafte Prozessor Sperren halten, die es ihm ermöglichen,
bestimmte Bereiche der gemeinsam benutzten Platten zu ändern. In
Abhängigkeit
von der Topologie des Sperrenverwaltungsprogramms kann er sogar
in der Lage sein, weitere Sperren zu erwerben. Der fehlerhafte Prozessor
erkennt schließlich
seinen Zustand, aber der Zeitpunkt, zu dem dies geschieht, ist extern
nicht erkennbar, da dies davon abhängt, was im Innern des fehlerhaften
Prozessors geschieht.
-
Die
Zielsetzung besteht darin, allen nicht ausgefallenen Prozessoren
eine sichere Ausführung
ihrer Operationen unter Verwendung der gemeinsam benutzten Platte
zu gestatten und es dem fehlerhaften Prozessor zu ermöglichen,
ebenfalls Unterstützung
zur Nutzung von Anwendungen bereitzustellen, sobald er in einen
bekannten Zustand zurückkehren
kann.
-
Ein
Wiederherstellungsmodell setzt die folgenden Konzepte um:
- – Einen
Gruppenüberwachungsdienst
(wie Phoenix-Gruppendienste),
der Prozesse auf allen der Prozessoren überwacht und Ausfälle von
Prozessoren und Ausfälle
der Datenübertragung
erkennt. Dieser Dienst wird bereitgestellt, indem "Prozessgruppen" zusammengeführt werden;
alle Mitglieder einer Gruppe werden informiert, wenn ein Mitglied
ausfällt
oder wenn ein neuer Prozess versucht, sich einer Gruppe anzuschließen. Während der
Startzeit müssen
sich Prozessoren den "Prozessgruppen" anschließen.
– Verteiltes
Sperren. Alle Plattenzugriffe werden unter den Gruppenmitgliedern über verteiltes
Sperren koordiniert:
– Ein
Mitglied muss eine Sperre abrufen, bevor es einen bestimmten Teil
der Daten/Metadaten auf einer gemeinsam benutzten Platte lesen oder ändern kann.
- – Ein
Gruppenmitglied ist ein Sperrenkoordinator; der Sperrenkoordinator
weiß,
welche Sperren auf welchem Knoten gehalten werden könnten.
– Beschlussfähigkeit.
Während
des Starts und bei Datenübertragungsausfällen ist
es möglich,
dass sich mehr als eine Gruppe bildet. Dies könnte dazu führen, dass es in verschiedenen
Gruppen Sperrkoordinatoren gibt, die Sperrentscheidungen treffen,
welche miteinander in Konflikt stehen. Um dies zu verhindern, werden
Dateisystem-Operationen untersagt, wenn weniger als die Mehrzahl
der Prozessoren, die auf die Platte zugreifen können, Mitglieder einer "Prozessgruppe" sind.
– Aufzeichnen.
Alle Aktualisierungen der Daten/Metadaten, die nach einem Ausfall
zu Unstimmigkeiten führen
könnten,
werden aufgezeichnet. Jeder Prozessor hat sein eigenes Protokoll,
aber die Protokolle werden auf einer gemeinsam benutzten Platte
gespeichert, so dass im Falle eines Ausfalls alle Knoten auf die
Protokolle zugreifen können.
– Abschirmen.
Es muss eine Funktion geben, mit der sich der Zugriff eines bestimmten
Prozessors auf eine bestimmte Platte blockieren lässt.
– Barrieren.
Da die Schritte der Wiederherstellung zwangsläufig in Folge durchgeführt werden
und bestimmte Schritte der Wiederherstellung auf allen Knoten durchgeführt werden
müssen,
werden "Barrieren" verwendet, um sicherzustellen,
dass ein Schritt auf allen Knoten abgeschlossen ist, bevor irgendwo
der nächste
Schritt ausgeführt
wird.
-
Unser
Wiederherstellungsmodell handhabt Knotenausfälle, ohne die Hardware zu sperren.
Jede Instanz des Dateisystems funktioniert nur, wenn sie ein aktives
Mitglied einer "Prozessgruppe" sein kann. Wenn der
Ausfall eines Prozessors festgestellt wird, der einen tatsächlichen
Ausfall eines Prozessors oder die Unfähigkeit, seine funktionierenden Zustand
zu übermitteln,
darstellen kann, werden alle verbleibenden Gruppenmitglieder von
dem Gruppenüberwachungsdienst
informiert. Die Wiederherstellung des ausgefallenen Prozessors geschieht,
indem die nachstehend beschriebenen Wiederherstellungsschritte durchgeführt werden,
wobei unter den nicht ausgefallenen Gruppenmitgliedern ein Barrierensynchronisationsprotokoll
verwendet wird. Da ein Teil der Wiederherstellungsschritte auf einem
Prozessor durchgeführt
wird, wird zu ihrer Durchführung ein
Dateisystem-Koordinator
gewählt.
- – Alle
nicht ausgefallenen Prozessoren beenden den Datenaustausch mit dem
ausgefallenen Prozessor.
- – Der
Dateisystem-Koordinator schirmt den ausgefallenen Prozessor ab.
Dies bewirkt, dass das Platten-Teilsystem keine weiteren Plattenanforderungen
von dem ausgefallenen Prozessor mehr erfüllt. Der ausgefallene Prozessor
kann nicht auf die gemeinsam benutzte Platte zugreifen, selbst wenn
er den Datenübertragungsausfall
noch nicht festgestellt hat.
- – Die
nächste
Barriere stellt die Wiederherstellung des Sperrzustands dar, sofern
dies notwendig ist. Der Dateisystem-Koordinator informiert den Sperrenkoordinator.
Der Sperrenkoordinator stellt die Bewilligung von Sperren ein, die
von dem ausgefallenen Prozessor zum Zeitpunkt des Ausfalls gehalten
werden. Dadurch wird verhindert, dass andere Knoten auf Daten zugreifen,
die von dem ausgefallenen Knoten möglicherweise in einem nicht
konsistenten Zustand zurückgelassen
wurden. Wenn der ausgefallene Prozessor der Sperrenkoordinator war,
wird der neue Sperrzustand von einem alternativen Koordinator berechnet,
indem dieser die im Cachespeicher abgelegten Informationen über den
Zustand der Sperre von den nicht ausgefallenen Prozessoren erfasst.
Wenn diese Phase nicht erforderlich war, können normale Operationen des
Dateisystems für
Daten, die nicht unter die Sperren fallen, deren Bewilligung ausgesetzt
wurde, auf den nicht ausgefallenen Knoten wieder aufgenommen werden.
- – Die
dritte Barriere stellt die Wiedergabe des Protokolls des ausgefallenen
Knotens durch den Dateisystem-Koordinator dar. Diese Wiedergabe
erfolgt mit dem Wissen, dass der ausgefallene Prozessor von den Platten
abgeschirmt ist und die nicht ausgefallenen Prozessoren keine Sperren
gewähren,
die blockiert sind. Bei Beendigung dieses Schritts stimmen die Daten
auf der Platte überein,
und die Sperren können freigegeben
werden. Eine Befreiung von dieser Barriere schließt eine
erfolgreiche Wiederherstellung ein, und der normale Betrieb kann
auf allen nicht ausgefallenen Prozessoren wieder aufgenommen werden.
- – Prozessorausfälle, die
während
der Wiederherstellung festgestellt werden, werden behandelt, indem
wieder ganz von vorne angefangen wird. Die einzelnen Wiederherstellungsschritte
werden so durchgeführt, dass
sie idempotent sind, so dass es nichts ausmacht, wenn sie mehrmals
ausgeführt
werden, bis das Wiederherstellungsprotokoll ohne weitere Ausfälle vollständig ausgeführt ist.
-
Die
vorstehenden Wiederherstellungsschritte beschreiben die Wiederherstellung
bei einem Dateisystem, und wenn mehr als ein Dateisystem installiert
ist, werden alle Wiederherstellungsmaßnahmen in jedem Schritt auf
alle Dateisysteme angewandt.
-
Zur
Durchführung
der Wiederherstellung eines Knotens versucht der ausgefallene Prozessor,
der Gruppe wieder beizutreten, sobald ihm dies möglich ist. Wenn die Wiederherstellung
nach einem Ausfall immer noch im Gang ist, kann er sich der "Prozessgruppe" erst anschließen, wenn
das Fehlerbehebungsprotokoll vollständig ausgeführt ist. Zwei Wege sind möglich, entweder
der ausgefallene Knoten schließt
sich einer vorhandenen Gruppe an oder er schließt sich einer Gruppe an, die
auf die Beschlussfähigkeit
wartet. Wenn er sich einer Gruppe anschließt, die auf die Beschlussfähigkeit
wartet, erfolgt die Wiedergabe des Protokolls, sobald die Beschlussfähigkeit
vorhanden ist (es ist dann bekannt, dass keine in Konflikt stehenden
Sperren vorhanden sind). Wenn er sich einer vorhandenen Gruppe anschließt, hebt
er seine Abschirmung auf und ermöglicht
normale Dateisystem-Operationen.
-
Die
zweite der Wiederherstellungsfunktionen behandelt den Punkt, an
dem sich die Wiederherstellung und die Erfordernis von Metadaten-Knoten überschneiden.
Die Metadaten-Knoten befinden sich in einem Zustand, der über einen
Ausfall erhalten werden muss.
-
Synchrone und asynchrone Übernahme
des Metadaten-Knotens
-
Unser
paralleles Dateisystem funktioniert in den Fällen, in denen alle Platten,
die das Dateisystem bilden, in einem Datenübertragungsnetzwerk wie zum
Beispiel in einem TCP/IP-Netzwerk
oder in einer Vermittlungsstelle verteilt sind, das beziehungsweise
die es mehreren Prozessoren erlaubt, interaktiv miteinander zu kommunizieren,
wie es bei einem massiv parallelen Rechner oder einem Rechnerverbund
(Cluster) der Fall ist, und folglich muss und kann der Zugriff auf
eine Datei unabhängig
durch mehrere Prozessoren erfolgen. Um diese Fähigkeit nutzen zu können, sollte
eine Datei von mehreren Prozessoren sowohl für Lese- als auch Schreiboperationen
gemeinsam benutzt werden.
-
Die
Freigabe von Dateien für
Schreiboperationen in einem verteilten Dateisystem ist mit mehreren
Problemen verbunden. Eines davon ist der Zugriff und die Aktualisierung
der von uns bereitgestellten Metadaten. Unser Metadaten-Knoten ist
ein Mechanismus zur Steuerung von Metadaten in einem verteilten
Dateisystem. Jeder Knoten, der auf eine Datei zugreift, muss Metadaten-Informationen
von dem Metadaten-Knoten (oder Metaknoten) lesen oder auf ihn schreiben.
-
Der
Metadaten-Knoten hält
die Informationen über
die Metadaten der Datei fest und hat die Funktion eines intelligenten
Cachespeichers zwischen der Platte und all den Knoten, die auf die
Datei zugreifen. Es gibt Situationen, in denen der Metadaten-Knoten
(oder Metaknoten) die Ausführung
dieser Funktion einstellt. Um einen reibungslosen Betrieb und eine
problemlose Wiederherstellung zu ermöglichen, müssen diese Situationen behandelt
werden. Knoten, die gewöhnlich
auf den Metaknoten zugegriffen haben, müssen auf direktem Weg einen
neuen Metaknoten wählen.
-
Wir
beschreiben hiermit die Situation, die die Übernahme eines Metaknotens
auslösen
kann, und das Verfahren, das wir wählen, um eine Übernahme
zu ermöglichen.
-
Es
gibt drei Situationen, in denen ein Metaknoten seine Funktion als
ein Metaknoten einstellt; die ersten beiden sind asynchron, d.h.,
andere Knoten sind sich dessen nicht sofort bewusst. Die dritte
ist synchron, d.h., alle Knoten sind sich der Übernahme bewusst.
- 1. Der Metaknoten fällt
aus (stürzt
ab);
- 2. Der Metaknoten schließt
die Datei oder löscht
sie aus dem Cachespeicher;
- 3. Ein anderer Knoten muss der Metaknoten werden.
-
In
allen drei Fällen
müssen
wir sicherstellen, dass eine zuverlässige Übernahme stattfindet. Bei asynchronen
Operationen stellt der erste Knoten, der versucht, auf den alten
Metaknoten zuzugreifen, einen Fehler fest; entweder ist der Knoten
abgestürzt,
wobei er in diesem Fall eine Übertragungsfehler-Meldung
erhält,
oder der alte Knoten hat entschieden, dass er nicht länger als
Metaknoten fungiert, wobei der Knoten in diesem Fall eine entsprechende
Fehlermeldung von dem alten Metaknoten erhält. In beiden Fällen versucht
der Knoten, ein Metaknoten zu werden, indem er ein entsprechendes
Token vom Token-Verwaltungsprogramm anfordert. Wenn es keinen anderen
Metaknoten gibt (dies ist der Fall, wenn er der erste war, der auf
den alten Metaknoten zugriff), wird der Knoten der neue Metaknoten.
Andere Knoten, die anschließend
versuchen, auf den alten Metaknoten zuzugreifen, durchlaufen ebenso
den gleichen Prozess, aber es gelingt ihnen nicht, das entsprechende
Token zu erwerben. Eine Anfrage an das Token-Verwaltungsprogramm
legt den neuen Metaknoten offen. Folglich findet schließlich jeder
Knoten heraus, dass er entweder der neue Metaknoten geworden ist
oder dass sich der Metaknoten geändert
hat. In beiden Fällen
werden geeignete Maßnahmen
durchgeführt.
Wenn ein Knoten ein Metaknoten wurde, liest er die neuesten Metadaten
von der Platte. Wenn sich der Metaknoten eines Knotens geändert hat,
sendet der Knoten die Aktualisierungen seiner eigenen Metadaten
erneut an den neuen Metaknoten, da es möglich ist, dass der alte Metaknoten
ausgefallen ist, bevor er diese Aktualisierungen auf die Platte
geschrieben hat. Durch die Verwendung einer Versionsnummer für eine jede
dieser Aktualisierungen weiß jeder
Knoten, welche Aktualisierungen sich auf der Platte befinden und
welche erneut an den neuen Metaknoten gesendet werden müssen.
-
Da
ein Knoten abstürzen
kann, während
er versucht, ein Metaknoten zu werden, ist jede Operation, die den
Zugriff auf den Metaknoten beinhaltet, wie folgt aufgebaut:
-
TABELLE 7
-
-
Unser
beschriebenes System für
eine dynamische Übernahme
von Metaknoten ist bisher einmalig, und unsere spezielle Lösung hat
den Vorteil, dass sie ein Teilsystem nutzt, das weitere Verwendungsmöglichkeiten
(das Token-Verwaltungsprogramm) zur Auswahl eines neuen Metaknotens
auf der Grundlage der Dateiaktivität bietet. Da alle Operationen
einen naturgegebenen "Wiederholungs"-Mechanismus beinhalten
und da jeder Knoten die Funktion eines Metaknotens übernehmen
kann, wird schließlich
ein Metaknoten gewählt, und
folglich ist sichergestellt, dass schlussendlich in dynamischer
Weise eine Übernahme
stattfinden wird.
-
Die
in jedem Knoten hinterlegten Informationen stellen sicher, dass
der Wiederherstellungsprozess selbst bei Ausfall eines Metaknotens
alle Informationen in einer Weise wiederherstellen wird, dass eine übereinstimmende
Ansicht der Datei zur Verfügung
steht.
-
Zuteilung von Quoten
-
Als
Nächstes
erörtern
wir unsere Verbesserungen, zu denen die Zuteilung von Quoten in
diesem Dateisystem mit gemeinsam benutzten Platten gehört. Die
grundlegende Schwierigkeit besteht darin, dass Quoten über eine
Gruppe von Knoten strikt eingehalten werden müssen. Zwar könnte man
sich vorstellen, dass sie an einem zentralen Server verwaltet werden,
doch haben wir festgestellt, dass dies keine brauchbare Lösung ist,
da der zentrale Server zu einem Engpass werden würde, da jede neue Schreiboperation
von Daten diesen einzigen Server um Erlaubnis bitten müsste, bevor
sie die Daten schreiben könnte.
Hier beschreiben wir unser Verfahren zur Zuteilung von Quotenanteilen
zu Rechnern, die im Namen eines Benutzers, der über eine Quote verfügt, aktiv
Daten in ein Dateisystem schreiben. Später befassen wir uns mit Möglichkeiten,
einen solchen Anteil im Falle eines Ausfalls wiederherzustellen.
-
In
einem parallelen Dateisystem, in dem mehrere Prozessoren auf alle
Platten, die das Dateisystem bilden, unabhängig voneinander zugreifen
können,
um aktiv Daten in die Dateien auf den verschiedenen Platten zu schreiben
und sie daraus zu lesen, muss die jeweilige Nummer der Sektoren
einer Platte den Dateien auf jedem Prozessor zugeordnet werden,
der die Dateien erstellt. Die Sektoren, die Dateien zugeordnet werden,
welche einem bestimmten Benutzer gehören, sind durch eine Quote
begrenzt, die festlegt, wie viel Plattenbereich dieser Benutzer
oder diese Gruppe von Benutzern in Anspruch nehmen darf. Das Problem
ist, dass Benutzer auf mehreren Prozessoren gleichzeitig Operationen
ausführen
können
und dieselbe Quote belasten. Die Zentralisierung der Zuordnung von
neuen Plattenblöcken
verlangsamt die Nutzung unseres massiv parallelen Verarbeitungssystems.
-
Wir
haben ein System realisiert, das jedem Knoten Anteile an der Quote
zuteilt, sie auf der Grundlage der Nachfrage neu zuteilt und sie
bei Ausfällen
wiederherstellt. Unsere Lösung
besteht in einem Verfahren zur Verwaltung von Inode- und Plattenblockquoten
pro Dateisystem in einer massiv parallelen Datenverarbeitungsumgebung
oder einer anderen Umgebung mit mehreren Rechnern, die wir beschreiben.
Die Arbeit wird zwischen einem Quoten-Server je Dateisystem und
einem Quoten-Client
je Knoten pro Dateisystem aufgeteilt, der aktiv Operationen an Daten
in dem Dateisystem ausführt.
-
Eine
Quotengrenze ist ein Schwellenwert, bis zu dem ein Benutzer Inodes
oder Speicherplatz in dem Dateisystem zuordnen darf. In dieser Schrift
wird die Anzahl der Inodes und der Speicherbereich, der einem Benutzer
zugeteilt werden darf, als Quote bezeichnet. Ein lokaler Anteil
ist der Speicherbereich, der zugunsten eines Benutzers auf einem
Quoten-Client ohne Dialogverkehr mit dem Quoten-Server zugeordnet
werden kann.
-
Der
Server verwaltet eine auf der Platte befindliche Datei, die den
Quotengrenzwert s und die kumulierte Nutzung für alle Benutzer in dem gesamten
MPP-System enthält.
Diese Werte stehen nur auf dem Server zur Verfügung, der alle Lese- und Aktualisierungsoperationen
für diese
Datei für
alle Prozessoren durchführt. Folglich
hat nur der Server einen Gesamtüberblick über die
Quotenauslastung und den noch zur Zuordnung verfügbaren Speicherbereich.
-
Alle
Maßnahmen
in Bezug auf die gesamte Quotenverwaltung werden auf dem Quotenserver
durchgeführt. Änderungen
an den Grenzwerten, die Zuordnung des lokalen Anteils und die Anzeige
des aktuellen Status erfordern einen Dialogverkehr mit dem Quoten-Server.
Quoten-Clients nehmen Änderungen
an den Dateisystem-Zuordnungen entsprechend ihrem zugebilligten
lokalen Anteil vor und aktualisieren den Server in regelmäßigen Abständen auf
der Grundlage der Inanspruchnahme dieses Anteils. Der Server kann
einen dem Client zugebilligten Anteil widerrufen, um Anforderungen
für eine
gemeinsame Benutzung von einem anderen Client zu erfüllen.
-
Quoten-Clients
beginnen mit einem lokalen Anteil von null. Nur wenn eine Anwendung
auf dem Prozessor versucht, neue Dateisystem-Daten zu erzeugen,
wird ein lokaler Anteil für
den Benutzer angefordert. Nur wenn der Client einen angemessenen
lokalen Anteil erhält,
ist die Anforderung der Anwendung erfüllt; andernfalls gilt die Anforderung
der Anwendung als nicht erfüllt.
Der Quoten-Client führt
ein Protokoll über
den lokalen Anteil und über
die Menge, die davon in Anspruch genommen wurde. Anwendungen, die
Speicherplatz auf der Platte freigeben, erhöhen den lokalen Anteil für den Benutzer.
Der Quoten-Client aktualisiert seinen Nutzungsgrad in regelmäßigen Abständen auf
dem Quoten-Server und gibt einen überschüssigen Quotenanteil auf der
Grundlage von Anwendungs-Nutzungsmustern frei.
-
Der
Quoten-Server gibt lokale Anteile aus, solange er noch über Quoten
verfügt,
d.h., solange der systemweite Quotengrenzwert nicht überschritten
wird. Wenn die gesamte begrenzte Quote in Form von lokalen Anteilen
verteilt wurde, widerruft der Quoten-Server lokale Anteile, um neue
Anforderungen zu erfüllen.
Dies geschieht, indem ein Teil der lokalen Anteile widerrufen wird,
wobei der Client den verbleibenden Anteil weiterhin nutzen darf.
Diese Anforderungen werden immer fordernder, wobei immer größere Teile
des lokalen Anteils widerrufen werden, bis keine Quote mehr verfügbar ist,
um Anforderungen zu erfüllen,
was dazu führt,
dass Anforderungen von Anwendungen abgewiesen werden.
-
Die
Schwierigkeit bei diesem Verfahren besteht darin, dass es Ausfälle sowohl
von Clients als auch von Servern berücksichtigen muss. Clients können mit
teilweise verwendeten lokalen Anteilen ausfallen, und der Server
kann gleichzeitig mit einem ausfallenden Client ausfallen. Der Benutzer
darf die zugewiesene Quote nie überschreiten,
und er geht auch davon aus, dass er diese Speicherkapazität zugeteilt
bekommen kann. Dies setzt die Anwendung des "Zweifelhaft"-Verfahrens ("in-doubt") der Quotenzuteilung
voraus. Jedes Mal, wenn der Quoten-Server einen lokalen Anteil zuteilt,
wird ein Datensatz über
die Summe der lokalen Anteile, der "Zweifelhaft"-Wert ("in-doubt value"), auf der wiederherstellbaren Platte
gespeichert. Dies stellt die Kapazität des Quoten-Speicherbereichs
dar, über
den der Server keine genauen Informationen hat. Der zweifelhafte Speicherbereich
("in-doubt space") kann nicht neu
zugeordnet werden, ohne dass die Gefahr besteht, dass einem Benutzer
gestattet wird, seine Grenzwerte zu überschreiten. Die "Zweifelhaft"-Werte werden von
den Clients mit regelmäßig versandten
Nachrichten aktualisiert, um ihre Nutzung des lokalen Anteils anzuzeigen. Dieser
Speicherbereich ändert
seinen Status von "zweifelhaft" in "belegt". Bei Speicherbereich,
der von einem Client abgetreten wird, wird der "Zweifelhaft"-Wert auch verringert. Der gesamte Speicherbereich,
der zugeordnet werden kann und für
einen Benutzer zur Verfügung
steht, ist seine Zuteilung abzüglich
des Speicherbereichs, der als belegt bekannt ist, abzüglich des
Speicherbereichs, der als zweifelhaft gilt. Alle Änderungen an
dem "Zweifelhaft"-Wert werden unverzüglich gezwungenermaßen auf
die Platte geschrieben, um eine Wiederherstellung auszuführen.
-
Wenn
ein Client ausfällt,
steht der Speicherbereich, der als zweifelhaft gilt, einem Benutzer
erst zur Verfügung,
wenn ein Dienstprogramm "Quotenprüfung" ("quota check") ausgeführt wird,
das die tatsächliche Belegung
des Speichers durch diesen Benutzer prüft. Ein Teil des "Zweifelhaft"-Werts stellt die
tatsächliche Nutzung
durch den Benutzer dar; aber ein Teil stellt die mögliche Nutzung
dar, die vorübergehend
verloren geht. Der Algorithmus zur Zuteilung von Anteilen ist empfindlich
für die
Nutzung von neuem Plattenspeicher am Client und versucht, dem Client
aus Gründen
der Leistungsfähigkeit
das zu geben, was er bald in Anspruch nehmen wird, und einen überschüssigen lokalen
Anteil aus Wiederherstellungsgründen
zu begrenzen. Dieses Verfahren ermöglicht die Fortsetzung des
Betriebs durch den Benutzer in Abhängigkeit von dem Teil seiner Quote,
der nicht zweifelhaft ist, bis das Dienstprogramm zur Quotenprüfung ausgeführt wird.
-
Es
ermöglicht
auch die parallele Zuordnung von Plattenblöcken im Interesse einer höheren Leistungsfähigkeit.
-
Wenn
der Quoten-Server ausfällt,
wird ein neuer Quoten-Server gewählt.
Er verfügt über keine
Informationen über Änderungen,
die noch nicht auf die Platte geschrieben worden sind. Er erzeugt
diese Informationen, indem er alle lokalen Anteile widerruft und
zweifelhafte Werte auf der Grundlage der Antworten aktualisiert.
Es sei angemerkt, dass Ausfälle
von Clients, die gleichzeitig mit dem Ausfall des Servers stattfinden, dazu
führen,
dass Blöcke
verloren gehen, bis das Dienstprogramm zur Quotenprüfung ausgeführt wird.
Dieser Algorithmus ermöglicht
es, dass Quoten bei nicht zweifelhaften Zuteilungen nach einem Ausfall
korrekt und schnell durchgesetzt werden können.
-
Uns
ist kein paralleles Dateisystem bekannt, das Plattenblöcke unabhängig auf
allen Knoten eines parallelen Systems zuordnet. Das bedeutet, dass
kein anderer mit dem Problem konfrontiert wird, bis er es mit an
das Netzwerk angeschlossenen Speichersystemen versucht.
-
Aus
Gründen
der Leistungsfähigkeit
ordnen wir parallel Speicher zu. Jede Lösung mit einem Zuordnungsserver
hätte Engpässe und
Probleme mit der Wiederherstellung. Wir müssen eine Quote haben, da Benutzer
die Belegung von Plattenspeicher über das gesamte Parallelverarbeitungssystem
steuern möchten.
Die Lösung
gestattet eine parallele Zuordnung, erzwingt kein dauerndes Sperren
einer globalen Quote, was den Betrieb verlangsamen würde, und
ermöglicht
die rechtzeitige Wiederherstellung nach Verarbeitungsfehlern.
-
Jedes
Parallelverarbeitungssystem, das ein Modell mit gemeinsam benutzten
Platten verwendet, bei dem die Platten miteinander verbunden sind,
kann diese Entwicklung nutzen.
-
Wiederherstellung von lokalen
Anteil zur Quotenverwaltung bei der Parallelverarbeitung
-
Dieser
Abschnitt beschreibt die Funktionsweise unseres Dienstprogramms
zur Quotenprüfung
in dieser Umgebung. Die Funktionen der Quotenprüfung sind ähnlich denen von Quotachk,
einem Standarddienstprogramm zur Festlegung von Quotendateien nach
einem Ausfall in einer Unix-Betriebsumgebung, jedoch kann Quotachk
nicht mit mehreren Knoten ausgeführt
werden, die Quoten freigeben, wie in der älteren Erfindung beschrieben
wurde. Unsere Entwicklung ermöglicht
die Ausführung
eines "Quotachk"-Programms, ohne dass
alle Rechner, die auf die Daten zugreifen, heruntergefahren werden
müssen.
-
Dieser
Abschnitt beschreibt ein Dienstprogramm/Verfahren, das Anteile widerruft,
wenn nach einem Ausfall nicht bekannt ist, ob diese Anteile in Anspruch
genommen werden/zugeteilt sind oder ob sie noch verfügbar sind.
Das Dienstprogramm arbeitet, ohne dass es Benutzer an der Zuordnung
oder der Aufhebung der Zuordnung von Speicherbereich auf der Platte
in dem Dateisystem hindert.
-
Zur
Verwaltung von Inode- und Plattenblock-Quoten je Dateisystem in
einer massiv parallelen Datenverarbeitungsumgebung wird die Arbeit
zwischen einem Quoten-Server je Dateisystem und einem Quoten-Client
je Knoten pro Dateisystem aufgeteilt, der aktiv Operationen an Daten
in dem Dateisystem durchführt.
-
Eine
Quotengrenze ist ein Schwellenwert, bis zu dem ein Benutzer Inodes
oder Speicherplatz in dem Dateisystem zuordnen darf. In dieser Schrift
wird die Anzahl der Inodes und der Speicherbereich, der einem Benutzer
zugeteilt werden darf, als Quote bezeichnet. Ein lokaler Anteil
ist der Speicherbereich, der zugunsten eines Benutzers auf einem
Quoten-Client ohne Dialogverkehr mit dem Quoten-Server zugeordnet
werden kann.
-
Der
Server verwaltet eine auf der Platte befindliche Datei, die die
Quotengrenzwerte und die kumulierte Nutzung sowie den "Zweifelhaft"-Wert für alle Benutzer
in dem gesamten MPP-System
enthält.
Der "Zweifelhaft"-Wert stellt die
Kapazität
des Quoten-Speicherbereichs dar, über den der Server keine genauen
Informationen hat. Der zweifelhafte Speicherbereich ("in-doubt space") kann nicht neu
zugeordnet werden, ohne dass die Gefahr besteht, dass einem Benutzer
gestattet wird, seine Grenzwerte zu überschreiten. Ein Teil des "Zweifelhaft"-Werts stellt die
tatsächliche
Nutzung durch den Benutzer dar; aber ein Teil stellt die mögliche Nutzung
dar, die vorübergehend
verloren geht.
-
Die
hier beschriebene Lösung
ist ein Verfahren zur Wiederherstellung von lokalen Anteilen von
dem zweifelhaften Speicherbereich, so dass die ungenutzte, vorübergehend
verlorene Quote wieder zur Verfügung steht.
Dieser Mechanismus (der hier die Bezeichnung "quotacheck" (Quotenprüfung)) trägt, arbeitet an einem aktiven
Dateisystem, ohne dass er die Zuordnung und die Aufhebung der Zuordnung
von Speicherbereich auf der Platte und von Inodes stört.
-
Quotacheck
legt auf dem Quoten-Server eine Schattenkopie aller Quoten-Datensätze an und
summiert dort die Quoten-Nutzung auf, die es den Inode-Informationen
der Dateien entnimmt. Während
Quotacheck die Inodes abfragt, werden alle Änderungen an den Zuordnungen
und der Aufhebung der Zuordnungen in dem ursprünglichen Quoten-Datensatz und
in dem Schattendatensatz auf dem Quoten-Server vermerkt. Aktualisierungen
der Quotennutzung vor und nach der aktuellen Position von Quotacheck
(d.h. dem gerade gelesenen Inode) müssen unterschiedlich behandelt
werden. Änderungen
an der Zuordnung nach der aktuellen Position von Quotacheck (bereits
geprüfte
Inodes) werden in dem ursprünglichen
Quoten-Datensatz und in dem Schattendatensatz aktualisiert; Änderungen
an der Zuordnung vor der aktuellen Position von Quotacheck (noch
nicht geprüfte
Inodes) werden nur in dem ursprünglichen
Quoten-Datensatz aktualisiert. Der "Zweifelhaft"-Wert in beiden Datensätzen wird
auf den gleichen Wert aktualisiert, so dass die Summe der lokalen
Anteile auf den Quoten-Clients korrekt ist, nachdem das Programm
Quotacheck vollständig
ausgeführt
worden ist.
-
Die
Quoten-Clients werden über
die aktuelle Position von Quotacheck informiert und können folglich all
diejenigen Quoten in Schatteneinträgen erfassen, die hinter der
jeweiligen aktuellen Position von Quotacheck zugeordnet wurden beziehungsweise
deren Zuordnung aufgehoben wurde. Quoten-Clients senden ihre erfassten Änderungen
für den
Quoten-Schattendatensatz
an den Quoten-Server, sobald Quotacheck mit der Abfrage der Inodes
fertig ist und damit beginnt, die ursprünglichen Quoten-Einträge und die
Schatten-Quoteneinträge
zusammenzuführen.
-
Der "Zweifelhaft"-Wert des Schattendatensatzes
wird zusammen mit dem "Zweifelhaft"-Wert des ursprünglichen
Quoten-Datensatzes
auf dem Server aktualisiert, nachdem alle Schattendatensätze angelegt und
nachdem alle lokalen Anteile von den Clients widerrufen worden sind,
jedoch bevor Quotacheck mit der Abfrage von Inodes nach Quoten-Nutzungsinformationen
beginnt (d.h, der "Zweifehlhaft"-Schattenwert beginnt bei null, und der
reguläre "Zweifelhaft"-Wert zeigt die verlorenen Quoten an).
Bei der Zusammenführung von
Schatten- und regulären
Quoten-Datensätzen
am Ende der Ausführung
des Programms Quotacheck wird der "Zweifelhaft"-Wert
des Schattendatensatzes in den regulären Quoten-Datensatz kopiert.
-
Uns
ist kein paralleles Dateisystem bekannt, das Plattenblöcke unabhängig auf
allen Knoten eines parallelen Systems zuordnet. Das bedeutet, dass
kein anderer mit dem Problem konfrontiert wird, bis er es mit an
das Netzwerk angeschlossenen Speichersystemen versucht.
-
Aus
Gründen
der Leistungsfähigkeit
ordnen wir parallel Speicher zu und vermeiden eine Lösung mit einem
einzigen Server, der Engpässe
und Probleme mit der Wiederherstellung hat. Wir müssen eine
Quote haben, da Benutzer die Belegung von Plattenspeicher über das
gesamte Parallelverarbeitungssystem steuern möchten. Diese Lösung gestattet
eine parallele Zuordnung, erzwingt kein dauerndes Sperren einer
globalen Quote, was den Betrieb verlangsamen würde, und ermöglicht die
rechtzeitige Wiederherstellung nach Verarbeitungsfehlern.