Im ersten Artikel ĂŒber die Struktur der QVD-Datei habe ich die allgemeine Struktur beschrieben und mich eingehend mit Metadaten befasst, und im zweiten Artikel ĂŒber die Speicherung von Spalten (Zeichen). In diesem Artikel werde ich das Format zum Speichern von Informationen ĂŒber Zeichenfolgen beschreiben, PlĂ€ne und Erfolge zusammenfassen und darĂŒber sprechen.
Denken Sie also daran, dass die QVD-Datei der relationalen Tabelle entspricht. In der QVD-Datei wird die Tabelle in zwei indirekt verbundenen Teilen gespeichert:
Zeichentabellen (mein Begriff) enthalten eindeutige Werte fĂŒr jede Spalte in der Quelltabelle. Ich habe im zweiten Artikel darĂŒber gesprochen.
Die Zeilentabelle enthÀlt die Zeilen der Quelltabelle, jede Zeile speichert die Indizes der Spalten- (Feld-) Werte der Zeile in der entsprechenden Symboltabelle. Es geht darum, dass dieser Artikel sein wird.
Am Beispiel unserer Platte (denken Sie daran - aus dem ersten Teil)
SET NULLINTERPRET =<sym>; tab1: LOAD * INLINE [ ID, NAME 123.12,"Pete" 124,12/31/2018 -2,"Vasya" 1,"John" <sym>,"None" ];
In der Zeilentabelle unserer QVD-Datei entspricht diese Bezeichnung 5 Zeilen - immer eine exakte Ăbereinstimmung: Wie viele Zeilen befinden sich in der Tabelle, wie viele Zeilen befinden sich in der Zeilentabelle der QVD-Datei.
Eine Zeile in der Zeilentabelle besteht aus nicht negativen Ganzzahlen. Jede dieser Zahlen ist ein Index fĂŒr die entsprechende Symboltabelle. Auf der logischen Ebene ist alles einfach, es bleibt, die Nuancen zu klĂ€ren und ein Beispiel zu geben (zerlegen - wie unser Typenschild in QVD dargestellt ist).
Die Zeilentabelle besteht aus K * N Bytes, wobei
- K - die Anzahl der Zeilen in der Quelltabelle (der Wert des Metadaten-Tags "NoOfRecords")
- N - BytelÀnge der Zeile der Symboltabelle (der Wert des Metadaten-Tags "RecordByteSize")
Die Zeilentabelle beginnt mit dem Versatz "Offset" (Metadaten-Tag) relativ zum Anfang des binÀren Teils der Datei.
Informationen zur Zeilentabelle (LĂ€nge, ZeilengröĂe, Versatz) werden im allgemeinen Teil der Metadaten gespeichert.
Alle Zeilen der Zeilentabelle haben das gleiche Format und sind eine Verkettung von "vorzeichenlosen Zahlen". Die LÀnge der Zahl reicht minimal aus, um ein bestimmtes Feld darzustellen: Die LÀnge hÀngt von der Anzahl der eindeutigen Werte eines bestimmten Feldes ab.
FĂŒr Felder mit einem Wert (wie ich bereits geschrieben habe) ist diese LĂ€nge Null (dieser Wert ist in jeder Zeile der Quelltabelle gleich und wird in der entsprechenden Symboltabelle gespeichert).
Bei Feldern mit zwei Werten ist diese LÀnge gleich eins (die möglichen Indexwerte in der Symboltabelle sind 0 und 1) usw.
Da die GesamtlÀnge der Zeile der Zeilentabelle ein Vielfaches des Bytes sein sollte, wird die LÀnge des "letzten Zeichens" an der Bytegrenze ausgerichtet (siehe unten, wenn wir unsere Platte analysieren).
Informationen ĂŒber das Format jedes Feldes werden in dem Metadatenabschnitt gespeichert, der diesem Feld gewidmet ist (wir werden weiter unten nĂ€her darauf eingehen). Die LĂ€nge der Bitdarstellung des Feldes wird im Tag "BitWidth" gespeichert.
Speichern von NULL-Werten
Wie speichere ich fehlende Werte? Wenn ich das Thema Warum nicht diskutiere, werde ich folgendermaĂen antworten: Nach meinem VerstĂ€ndnis entspricht die folgende Kombination NULL-Werten
- Tag "Bias" des entsprechenden Feldes nimmt den Wert "-2" an (insgesamt bin ich auf zwei mögliche Werte dieses Tags gestoĂen - "0" und "-2")
- Der Feldindex fĂŒr die Zeile, in der dieses Feld NULL ist, ist 0
Dementsprechend werden alle anderen Indizes in der Spalte mit NULL-Werten um 2 erhöht - wir werden in unserem Beispiel etwas niedriger sehen.
Die Reihenfolge der Felder in der Zeile
Die Reihenfolge der Felder in der Zeile der Zeilentabelle entspricht dem Bitversatz des Felds, der im Tag "BitOffset" des Metadatenabschnitts gespeichert ist, der sich auf dieses Feld bezieht.
Lassen Sie uns unser Beispiel analysieren (siehe Metadaten im ersten Teil dieser Reihe).
ID-Feld
- Bitoffset 0 - das Feld ist das "ganz rechts"
- BitlÀnge 3 - Das Feld belegt 3 Bits in einer Zeile einer Zeilentabelle
- Die Abweichung ist "-2" - das Feld hat NULL-Werte, alle Indizes werden um 2 erhöht
Feld "NAME"
- Bitversatz 3 - Das Feld befindet sich um 3 Bits links vom ID-Feld
- BitlÀnge 5 - Das Feld belegt 5 Bits in der Zeile der Zeilentabelle (ausgerichtet an der Bytegrenze).
- Bias ist "0" - das Feld hat keine NULL-Werte, alle Indizes sind "ehrlich"
PrÀsentation unseres Typenschildes.
Schauen wir uns die echten "Nullen und Einsen" an - ich werde Fragmente der QVD-Datei als binÀre Darstellung "im hexadezimalen Format" (so kompakt) geben.
Erstens der gesamte binÀre Teil (der in Pink hervorgehobene Teil, die Metadaten werden abgeschnitten - es tut vielen weh ...)

Kompakt genug, stimme zu. Schauen wir uns das genauer an - direkt nach den Metadaten gibt es Symboltabellen (Metadaten in dieser Datei endeten ĂŒbrigens mit einem Zeilenvorschub und einem Null-Byte - technisch gesehen geschieht dies, Null-Bytes, nachdem die Metadaten ĂŒbersprungen werden mĂŒssen ...).
Die erste Symboltabelle ist in der folgenden Abbildung hervorgehoben.

Wir sehen:
Der erste eindeutige Wert des ID-Felds ist
- Typ "6" (das erste zugewiesene Byte) ist eine Gleitkommazahl mit einer Zeichenfolge (siehe den zweiten Artikel)
- Nach dem ersten Byte sind 8 der nÀchsten Bytes eine binÀr dargestellte Gleitkommazahl
- Nach ihnen folgt die Zeichenfolgendarstellung - sehr praktisch (Sie mĂŒssen sich nicht erinnern - was war die Zahl) und endet mit einem Null-Byte
Die verbleibenden drei eindeutigen Werte sind vom Typ 5 (eine Ganzzahl mit einer Zeichenfolge) - die Werte sind "124", "-2" und "1" (entlang der Linien leicht zu erkennen).
In der folgenden Abbildung habe ich die zweite Symboltabelle hervorgehoben (fĂŒr das Feld "NAME").

Der erste eindeutige Wert des Felds "NAME" ist Typ "4" (das erste zugewiesene Byte) - eine Zeichenfolge, die mit Null endet.
Die anderen vier eindeutigen Werte sind auch die Zeichenfolgen "31.12.2008", "Vaysa", "John" und "Keine".
Jetzt - die Zeilentabelle (in der folgenden Abbildung hervorgehoben)

Wie erwartet - 5 Bytes (5 Zeilen mal 1 Byte).
Die erste Zeile (entspricht Zeile 123.12, "Pete" unserer Platte)
Der Zeichenfolgenwert ist Byte "02" (binÀr 000000010).
Trennen Sie es (denken Sie an die obige Beschreibung)
- rechts 3 Bits (binÀr 010, unserer Meinung nach ist es 2) - dies ist ein Index in die Symboltabelle des Feldes "ID"
- wir haben das Feld "ID" enthÀlt NULL, so dass der Index um 2 erhöht wird, d.h. Der resultierende Index ist 0, was dem Zeichen "123.12" entspricht.
- Die nÀchsten 5 Bits (binÀr und dezimal 0) sind der Index in der Symboltabelle des Felds "NAME". Sie enthalten kein NULL. Daher ist dies der Index "Pete" in der Symboltabelle.
Zweite Zeile (124.12 / 31/2018) in der Zeilentabelle
Wert - Byte "0B" (binÀr 00001011)
- rechte 3 Bits (binÀr 011, unserer Meinung nach 3) - dies ist der Index in der Symboltabelle des Feldes "ID"
- wir haben das Feld "ID" enthÀlt NULL, so dass der Index um 2 erhöht wird, d.h. Der resultierende Index ist 1, was dem Symbol "124" entspricht.
- Die nÀchsten 5 Bits (binÀr und dezimal 1) sind der Index in der Symboltabelle des Felds "NAME". Sie enthalten kein NULL. Dies ist also der Index "31.12.2008" in der Symboltabelle.
Nun und so weiter, werfen wir einen kurzen Blick auf die letzte Zeile - dort hatten wir es, "None" (dh NULL und die Zeichenfolge "None"):
Der Wert ist Byte "20" (binÀr 0010000)
- rechts 3 Bits (binÀr und dezimal 0) - dies ist der Index in der Symboltabelle des Feldes "ID"
- wir haben das Feld "ID" enthĂ€lt NULL, so dass der Index um 2 erhöht wird, d.h. Der endgĂŒltige Index ist -2, was dem NULL-Wert entspricht.
- Die nÀchsten 5 Bits (binÀr 100, dezimal 4) sind der Index in der Symboltabelle des Felds "NAME". Sie enthalten kein NULL. Dies ist also der Index "None" in der Symboltabelle.
WICHTIG Ich kann kein Beispiel finden, das dies bestĂ€tigt, aber ich bin auf Dateien gestoĂen, die einen endgĂŒltigen Index von -1 fĂŒr NULL-Werte enthielten. Daher betrachte ich in meinen Programmen alle Felder, deren endgĂŒltiger Index negativ ist, als NULL.
LĂ€ngere Zeilen in einer Zeilentabelle
Am Ende der Analyse des QVD-Formats werde ich kurz auf wichtige Nuancen eingehen - lange Zeilen in den Zeilentabellen speichern Felder in der Reihenfolge von rechts nach links, wobei das Feld mit dem Null-Bit-Offset ganz rechts ist (wie oben beschrieben). ABER die Bytereihenfolge ist umgekehrt, d.h. Das erste Byte ist das am weitesten rechts stehende (und enthÀlt das "rechte" Feld - ein Feld mit Null-Bit-Offset), das letzte Byte ist das erste (dh enthÀlt das am meisten "linke" Feld - ein Feld mit maximalem Bit-Offset).
Ein Beispiel sollte gegeben, aber nicht mit Details ĂŒberladen werden. Schauen wir uns eine solche Bezeichnung an (ich zitiere ein Fragment - um lange Zeilen in der Zeilentabelle zu erhalten, mĂŒssen Sie die Anzahl der eindeutigen Werte erhöhen).
tab2: LOAD * INLINE [ ID, VAL, NAME, PHONE, SINGLE 1, 100001, "Pete1", "1234567890", "single value" 2, 200002, "Pete2", "2234567890", "single value" ... ];
Kurzinformationen zu den Feldern (Auspressen von Metadaten):
- ID: Breite 8 Bit, Bitversatz - 0, Bias - 0
- VAL: Breite 5 Bit, Bitversatz - 8, Bias - 0
- NAME: Breite 6 Bit, Bitversatz - 18, Bias - 0
- TELEFON: Breite 5 Bit, Bitversatz - 13, Bias - 0
- SINGLE: Breite 0 Bits (hat einen Wert)
Die Zeilentabelle besteht aus Zeichenfolgen mit einer LĂ€nge von jeweils 3 Bytes. In der Zeile der Zeilentabelle werden die Daten zu den Feldern wie folgt logisch zerlegt:
- erste 6 Bits - Feld "NAME"
- nÀchste 5 Bits - Feld "PHONE"
- dann 5 Bits - Feld "VAL"
- letzte 8 Bits - ID-Feld
Die logische Sequenz wird in umgekehrter Reihenfolge in physikalische Bytes umgewandelt, d.h.
- Das Feld "ID" belegt vollstÀndig das erste Byte (das in der logischen Reihenfolge das letzte ist).
- Das Feld "VAL" belegt die unteren 5 Bits des zweiten Bytes
- Das Feld "PHONE" belegt die oberen 3 Bits des zweiten Bytes und die unteren 2 Bits des dritten Bytes
- Das Feld "NAME" belegt die oberen 6 Bits des dritten Bytes
Schauen wir uns Beispiele an. Hier ist, wie die erste Zeile der Zeilentabelle aussieht (rosa hervorgehoben).

Feldwerte
- ID - binÀr 00000000, dezimal 0
- VAL - binÀr 00010, dezimal 2, subtrahiere 2 von der Vorspannung - erhalte 0
- TELEFON - binÀr 00010, dezimal 2, 2 von Bias subtrahieren - 0 erhalten
- NAME - binÀr 000000, dezimal 0
Das heiĂt, die erste Zeile enthĂ€lt die ersten Zeichen aus den entsprechenden Zeichentabellen.
Im Allgemeinen ist es praktisch, mit dem Parsen ab der ersten Zeile zu beginnen - normalerweise enthÀlt es Nullen als Index (die QVD-Datei wird so erstellt, dass die Werte aus der ersten Zeile zuerst in die Zeichentabelle gelangen).
Schauen wir uns die zweite zu behebende Zeile an

Feldwerte
- ID - binÀr 00000001, dezimal 1
- VAL - binÀr 00011, dezimal 3, 2 von Bias subtrahieren - 1 erhalten
- TELEFON - binÀr 00011, dezimal 3, subtrahieren 2 von Bias - erhalten 1
- NAME - binÀr 000001, dezimal 1
Das heiĂt, die zweite Zeile enthĂ€lt die zweiten Zeichen aus den entsprechenden Zeichentabellen.
Ich werde ein wenig Erfahrung teilen - wie ich QVD technisch "lese".
Die erste Version wurde in Python geschrieben (ich werde sie veredeln und auf Github setzen).
Die Hauptprobleme wurden schnell klar:
- Symboltabellen können nur "in einer Reihe" gelesen werden (es ist unmöglich, die Symbolnummer N zu lesen, ohne alle vorherigen Zeichen zu lesen).
- echte Dateien passen nicht in den RAM
- der langsamsten Operationen (auĂer beim Arbeiten mit Dateien) - Bitoperationen (Entpacken einer Zeile einer Zeichenfolgentabelle)
- Die Leistung sinkt stark bei "breiten" QVD-Dateien (wenn viele Spalten vorhanden sind).
Einige dieser Probleme können durch Ăndern der Sprache gelöst werden (z. B. von Python auf C). Teil erforderte einige zusĂ€tzliche MaĂnahmen.
Die derzeitige recht schnelle Implementierung sieht folgendermaĂen aus: Die allgemeine Logik ist in Python implementiert, und die kritischsten VorgĂ€nge werden in separaten C-Programmen ausgefĂŒhrt, die parallel ausgefĂŒhrt werden.
Kurz
- Symboltabellen werden in Dateien geschrieben, zusĂ€tzlich werden Indizes fĂŒr Textfelder erstellt, wodurch die Symbolnummer N gelesen werden kann
- Arbeiten Sie mit QVD und Dateien mit Symboltabellen, die durch Speicherzuordnungsdateien implementiert sind (also schneller).
- ZunÀchst werden parallel (mit einer Begrenzung der Anzahl der Prozessoren) Dateien mit Symboltabellen (und Indizes) erstellt.
- dann werden parallel (mit einer Àhnlichen EinschrÀnkung) die Zeilen der Zeilentabelle gelesen und CSV-Dateien erstellt (in HDFS)
- Der letzte Schritt besteht darin, diese Dateien in eine ORC-Tabelle zu konvertieren (mit Hive-Tools).
- In C wurde die Erstellung von Dateien mit Symboltabellen und die Erstellung einer CSV-Datei fĂŒr eine Reihe von Zeilen implementiert
Ich möchte keine Leistungsangaben machen - sie erfordern eine Bindung an die Hardware. Auf qualitativer Ebene stellt sich heraus, dass die QVD-Datei mit etwa der Geschwindigkeit des Kopierens von Daten ĂŒber das Netzwerk in die ORC-Tabelle kopiert wird. Mit anderen Worten, Daten aus QVD zu entnehmen ist ziemlich realistisch (auf Haushaltsebene).
Ich habe auch die Logik zum Erstellen von QVD-Dateien implementiert - sie funktioniert unter Python recht schnell (anscheinend habe ich noch keine groĂen Volumes erreicht - es besteht keine Notwendigkeit. Ich werde dorthin gelangen - ich werde sie auf die gleiche Weise wie die "Lese" -Version umschreiben).
ZukunftsplÀne
Was weiter:
- Ich habe vor, die Python-Version des Codes in Github zu erstellen (mit dieser Version können Sie die QVD-Datei "erkunden" - Metadaten anzeigen, Zeichen und Zeichenfolgen lesen und schreiben. Die Version ist so einfach und offensichtlich langsam wie möglich - ohne Dateien fĂŒr Zeichentabellen, mit sequentiellem Lesen und Standardbibliotheken zum Arbeiten Bits usw.)
- Ich denke darĂŒber nach, etwas fĂŒr Pandas zu tun (wie read_qvd ()), es schrĂ€nkt ein, dass es auf Python langsam sein wird, sowie die Tatsache, dass offensichtlich nicht jeder QVD in den Speicher "passt"
- Ich denke darĂŒber nach, die QVD-Datei zu einer Datenquelle fĂŒr Spark zu machen - es sollte kein Problem geben, "nicht in den Speicher zu gelangen" (und die Sprache dort - Scala - ist nĂ€her an der Hardware).
Anstelle eines Nachwortes
Lange Zeit habe ich mich mit QVD-Dateien beschÀftigt und es schien, als sei "dort alles kompliziert". Es stellte sich heraus, dass es schwierig, aber nicht sehr, ein guter Anstoà war Github, den ich im ersten Teil erwÀhnte (eine Art Katalysator). Dann war es eine Frage der Technologie. Ich und alle bemerken (noch eine BestÀtigung) - alles kann in der Programmierung gemacht werden, die Frage ist Zeit und Motivation.
Ich hoffe, ich bin nicht sehr mĂŒde von den Details, ich bin bereit, Fragen zu beantworten (in den Kommentaren oder auf andere Weise). Wenn es eine Fortsetzung gibt - werde ich schreiben.