Entfernen Sie führende Nullblöcke aus einer Datei mit geringer Dichte

1334
BobC

Ich benutze logrotatemit der copytruncateOption. Dies funktioniert gut, indem eine spärliche Datei erstellt wird, die mit einer zunehmenden Anzahl "virtueller" Nullblöcke beginnt, die keinen Speicherplatz auf der Festplatte belegen.

Das Problem liegt bei den kopierten Dateien: Während sie auf der Festplatte nur wenig Speicherplatz lessbeanspruchen, dauert der Versuch, sie mit Hilfe von Dateien zu untersuchen, ewig, da die "virtuellen" Nullblöcke auf tatsächliche Nullen erweitert werden. Ich möchte wirklich die anfänglichen spärlichen Nullblöcke vom Anfang der kopierten Datei entfernen.

Folgendes weiß ich bisher: ls -lsund dukann mir sagen, wie viel von der Datei "echt" ist. Und ich denke, man ddkann eine Kopie ohne die führenden leeren Blöcke machen. Aber ich habe Probleme, alles in etwas zusammenzufassen, das ich in den postrotateAbschnitt meiner logrotate.confAkte einfügen kann.

Ich habe Methoden gefunden, die die Nullen verwenden troder sedlöschen, aber dazu muss die Datei erweitert werden (die virtuellen Nullen werden physisch), und im Laufe der Zeit kann die Datei über ein Terabyte anwachsen! Ich brauche einen "chirurgischeren" Ansatz, der funktioniert, ohne die Datei zu erweitern. Es sollte nur ein Durcheinander mit den Inodes erforderlich sein, da dort die spärlichen Blöcke leben (nicht im tatsächlich zugewiesenen Bereich).

Natürlich besteht die "echte" Lösung darin, das Erzeugungsprogramm dazu zu verwenden SIGHUP, die Ausgabedatei wieder zu öffnen, aber in diesem Fall ist dies nicht möglich.

Was ist der einfachste und schnellste Weg, führende Nullblöcke direkt aus einer spärlichen Datei zu entfernen?


Nachtrag: So erstellen Sie Ihre eigene spärliche Datei zum Spielen:

$ dd if=/dev/zero of=sparse.txt bs=1 count=0 seek=8G 0+0 records in 0+0 records out 0 bytes (0 B) copied, 0.000226785 s, 0.0 kB/s  $ echo 'Hello, World!' >>sparse.txt  $ ls -ls sparse.txt 4 -rwxrwxrwx 1 me me 8589934606 Nov 6 10:20 sparse.txt  $ ls -lsh sparse.txt  4.0K -rwxrwxrwx 1 me me 8.1G Nov 6 10:20 sparse.txt 

Diese "riesige" Datei nimmt auf der Festplatte fast keinen Platz ein. Versuchen Sie es jetzt less sparse.txt. Sie müssen 8G von Nullen durchlaufen, um zu den Zeichen am Ende zu gelangen. Es tail -n 1 sparse.txtdauert sogar eine gute Zeit.

0
Ich habe noch nie von einem Programm gehört, das eine Datei ändert, indem es sich mit den Inodes beschäftigt. Dafür gibt es keinen Systemaufruf, dies müsste durch Ändern der Raw-Festplatte erfolgen, und dies wäre nur dann sicher, wenn Sie das Dateisystem zuerst aushängen. Barmar vor 9 Jahren 0
Sie können die Anzahl der Nullblöcke ermitteln, indem Sie den Festplattenspeicher der Datei mit der Länge vergleichen. Verwenden Sie dann `dd` mit der Option` seek = n`, um die Nullblöcke zu überspringen. Dies verwendet `lseek`, sodass die virtuellen Blöcke nicht gelesen werden müssen. Barmar vor 9 Jahren 0
@barmar Ich habe es versucht, aber es ist fehlgeschlagen: Können Sie ein Beispiel freigeben, das mit jeder Datei funktioniert? Ich verwende `stat -c"% o% B% b% s "`, um die Informationen zu erhalten, die ich für nötig halte. BobC vor 9 Jahren 0
Das scheint komplizierter zu sein als ich dachte. `stat% b sparse.txt` meldet 32 ​​Blöcke für die 8,1-GB-Datei. Ich denke, es muss indirekte Blöcke enthalten. Wenn ich am Anfang eine Datei mit 16-KByte oder 32-KByte-Nullen erstelle, sagen beide 8 Blöcke. Barmar vor 9 Jahren 0
Und sowohl 1M als auch 2M sagen 16 Blöcke aus. Barmar vor 9 Jahren 0
Vielleicht sollten Sie sich den Quellcode von `cp` ansehen, um zu sehen, wie spärliche Dateien entdeckt werden Barmar vor 9 Jahren 0
Vor einigen Jahren gab es einen Vorschlag für eine API, die das Auffinden von Löchern in spärlichen Dateien erleichtern soll: http://lwn.net/Articles/260795/ Barmar vor 9 Jahren 0

2 Antworten auf die Frage

0
BobC

Hier ist mein erster Versuch, mit statund zu arbeiten dd, der nur für spärlich führende Dateien funktioniert :

#! /bin/bash for f in $@; do echo -n "$f : " fields=( `stat -c "%o %B %b %s" $f` ) xfer_block_size=$ alloc_block_size=$ blocks_alloc=$ size_bytes=$  bytes_alloc=$(( $blocks_alloc * $alloc_block_size ))  alloc_in_xfer_blocks=$(( ($bytes_alloc + ($xfer_block_size - 1))/$xfer_block_size )) size_in_xfer_blocks=$(( ($size_bytes + ($xfer_block_size - 1))/$xfer_block_size )) null_xfer_blocks=$(( $size_in_xfer_blocks - $alloc_in_xfer_blocks )) null_xfer_bytes=$(( $null_xfer_blocks * $xfer_block_size )) non_null_bytes=$(( $size_bytes - $null_xfer_bytes ))  if [ "$non_null_bytes" -gt "0" -a "$non_null_bytes" -lt "$size_bytes" ]; then cmd="dd if=$f of=$f.new bs=1 skip=$null_xfer_bytes count=$non_null_bytes" echo $cmd exec $cmd else echo "Nothing to do: File is not sparse." fi done 

Was denkst du?

Mir wurde gerade klar, dass das obige Skript nur komplette Blöcke mit führenden Nullen verarbeitet. Es sollte eine einfache Änderung sein, damit alle anfänglichen Null-Bytes korrekt verarbeitet werden. BobC vor 9 Jahren 0
Ja, man muss nur noch ein Vielfaches der Blockgröße abrunden. Barmar vor 9 Jahren 0
Beachten Sie die Rundung in der Einstellung * _in_xfer_blocks. Das zeigt jedoch noch nicht, wie viele Nullen sich im ersten physischen Block befinden können, da die Datei mit einem Teilblock von Nullen beginnen kann. Die "beste" Lösung kann also darin bestehen, die Datei wie oben beschrieben zu erstellen, dann den ersten Block zu lesen, die führenden Nullen zu zählen und erneut zu kopieren, um sie zu entfernen. BobC vor 9 Jahren 0
Ooooh! Ich habe es gerade herausgefunden. Zumindest in ext3 (und vermutlich in anderen Dateisystemen) hat die Inode für ein spärliches Loch eine Auflösung von Bytes, sodass das erste Byte des ersten physischen Blocks auch das erste Byte nach dem Loch ist. Daher kann der obige Code unter allen Umständen tatsächlich korrekt sein. Test benötigt! BobC vor 9 Jahren 0
Ich habe nicht gedacht, dass Sie sich am Anfang zu viele Sorgen um eine kleine Reihe von Nullen machten. Das Wichtigste ist, nicht über die Nullen hinaus zu suchen. Ich habe die "- 1" in den Berechnungen nicht bemerkt. Barmar vor 9 Jahren 0
0
Michael Matthews

Ich habe hier ein Konto erstellt, um @BobC für seine Antwort (und seine Frage) zu danken. Es war der Katalysator, den ich brauchte, um unser langjähriges Problem mit Solr-Protokollen zu lösen.

Ich habe das Skript von BobC geändert, um es ein wenig für den logrotate-Anwendungsfall zu optimieren (mit $xfer_block_sizefor ibsund einem beliebig großen (8M) obs, gefolgt von einem tr -d "\000", um die verbleibenden Nullen zu entfernen), und habe es dann im firstactionAbschnitt meiner logrotateKonfiguration verwendet.

Ich denke, meine Lösung ist etwas hässlich, aber es ist viel besser, als kritische Produktionsservices abprallen zu müssen, wenn eine Protokolldatei von über 80 GB die Festplatte zu füllen droht.

Dies ist, was ich endete mit:

#! /bin/bash # truncat.sh # Adapted from @BobC's script http://superuser.com/a/836950/539429 # # Efficiently cat log files that have been previously truncated.  # They are sparse -- many null blocks before the interesting content. # This script skips the null blocks in bulk (except for the last)  # and then uses tr to filter the remaining nulls. # for f in $@; do fields=( `stat -c "%o %B %b %s" $f` ) xfer_block_size=$ alloc_block_size=$ blocks_alloc=$ size_bytes=$  bytes_alloc=$(( $blocks_alloc * $alloc_block_size ))  alloc_in_xfer_blocks=$(( ($bytes_alloc + ($xfer_block_size - 1))/$xfer_block_size )) size_in_xfer_blocks=$(( ($size_bytes + ($xfer_block_size - 1))/$xfer_block_size )) null_xfer_blocks=$(( $size_in_xfer_blocks - $alloc_in_xfer_blocks )) null_xfer_bytes=$(( $null_xfer_blocks * $xfer_block_size )) non_null_bytes=$(( $size_bytes - $null_xfer_bytes ))  if [ "$non_null_bytes" -gt "0" -a "$non_null_bytes" -lt "$size_bytes" ]; then cmd="dd if=$f ibs=$xfer_block_size obs=8M skip=$null_xfer_blocks " $cmd | tr -d "\000" else cat $f fi done 

Die Verwendung größerer Blöcke macht ddGrößenordnungen schneller. ddmacht einen ersten Schnitt und schneidet dann trden Rest der Nullen ab. Als Bezugspunkt für eine 87 GiB-Datei mit geringer Dichte (die 392 MiB-Daten enthält):

# ls -l 2015_10_12-025600113.start.log -rw-r--r-- 1 solr solr 93153627360 Dec 31 10:34 2015_10_12-025600113.start.log # du -shx 2015_10_12-025600113.start.log 392M 2015_10_12-025600113.start.log # # time truncat.sh 2015_10_12-025600113.start.log > test1 93275+1 records in 45+1 records out 382055799 bytes (382 MB) copied, 1.53881 seconds, 248 MB/s  real 0m1.545s user 0m0.677s sys 0m1.076s  # time cp --sparse=always 2015_10_12-025600113.start.log test2  real 1m37.057s user 0m8.309s sys 1m18.926s  # ls -l test1 test2 -rw-r--r-- 1 root root 381670701 Dec 31 10:07 test1 -rw-r--r-- 1 root root 93129872210 Dec 31 10:11 test2 # du -shx test1 test2 365M test1 369M test2 

Wenn ich das logrotateverarbeiten lasse copytruncate, dauerte es fast eine Stunde und führte zu einer vollständig materialisierten, nicht spärlichen Feile - die dann über eine Stunde dauerte gzip.

Hier ist meine endgültige logrotateLösung:

/var/log/solr/rotated.start.log { rotate 14 daily missingok dateext compress create firstaction # this actually does the rotation. At this point we expect  # an empty rotated.start.log file. rm -f /var/log/solr/rotated.start.log # Now, cat the contents of the log file (skipping leading nulls)  # onto the new rotated.start.log for i in /var/log/solr/20[0-9][0-9]_*.start.log ; do /usr/local/bin/truncat.sh $i >> /var/log/solr/rotated.start.log > $i # truncate the real log done endscript } 

Das Hacker-Bit ist, dass Sie beim ersten Einrichten eine leere rotated.start.logDatei erstellen müssen. Andernfalls logrotatewird sie niemals abgerufen und das firstactionSkript ausgeführt.

Ich habe Ihr logrotateBug-Ticket gesehen, für das ein Update veröffentlicht wurdelogrotate 3.9.0 . Wenn ich es richtig lese, behebt das implementierte Update leider nur einen Teil des Problems. Die spärliche Protokolldatei wird korrekt kopiert, um eine weitere spärliche Datei zu erstellen. Aber wie Sie bemerkt haben, wollen wir das nicht. Die Kopie soll alle irrelevanten Nullblöcke ausschließen und nur die Protokolleinträge beibehalten. Nachdem die copytruncate, hat logrotateimmer noch gzipdie Datei, und gzipbehandelt spärliche Dateien nicht effizient (es liest und verarbeitet jedes null Byte).

Unsere Lösung ist besser als die copytruncateKorrektur, logrotate 3.9.xda saubere Protokolle erstellt werden, die leicht komprimiert werden können.