Tips voor het werken met een groot aantal kleine bestanden

Het idee voor dit artikel is spontaan ontstaan uit een discussie in de reacties op een artikel. «Wat je moet weten over inode».

Tips voor het werken met een groot aantal kleine bestanden

Het punt is dat een interne specificiteit van onze services het opslaan van enorm veel kleine bestanden is. Op dit moment hebben we zo'n honderden terabytes aan dergelijke gegevens. We zijn tegen een aantal voor de hand liggende en minder voor de hand liggende problemen aangelopen en hebben deze succesvol overwonnen.

Daarom deel ik onze ervaring, hopelijk is het nuttig voor iemand.

Probleem één: «Geen ruimte meer op het apparaat»

Zoals genoemd in het eerder genoemde artikel, is het probleem dat er vrije blokken op het bestandssysteem zijn, maar dat de inodes op zijn.

Je kunt het aantal gebruikte en beschikbare inodes controleren met het commando df -ih:

Tips voor het werken met een groot aantal kleine bestanden

Ik zal het artikel niet herhalen, kort samengevat houdt het in dat er op de schijf zowel blokken zijn voor gegevens als blokken voor metadata, de zogenaamde inodes (index node). Hun aantal wordt vastgesteld bij de initialisatie van het bestandssysteem (het betreft ext2 en zijn afgeleiden) en verandert daarna niet. De balans tussen gegevensblokken en inodes wordt berekend op basis van gemiddelde gegevens. In ons geval, met veel kleine bestanden, moet de balans verschuiven in de richting van het aantal inodes — er moeten er meer zijn.

In Linux zijn al variaties met verschillende balansen voorzien, en al deze vooraf berekende configuraties zijn te vinden in het bestand /etc/mke2fs.conf.
Daarom kan bij de initiële initialisatie van het bestandssysteem via mke2fs het benodigde profiel worden aangegeven.

Hier zijn enkele voorbeelden uit het bestand:

    small = {
        blocksize = 1024
        inode_size = 128
        inode_ratio = 4096
    }

    big = {
        inode_ratio = 32768
    }

    largefile = {
        inode_ratio = 1048576
        blocksize = -1
    }

Je kunt de gewenste gebruiksvariant kiezen met de optie «-T» bij het aanroepen van mke2fs. Het is ook mogelijk om de vereiste parameters handmatig op te geven als er geen kant-en-klare oplossing is.

Meer details zijn beschreven in de handleidingen voor mke2fs.conf en mke2fs.

Een aspect dat niet aan bod kwam in het eerder genoemde artikel — het is mogelijk om de grootte van de gegevensblokken aan te geven. Het is duidelijk dat voor grote bestanden een grotere blokgrootte logisch is, terwijl voor kleine bestanden een kleinere blokgrootte nuttiger is.

Maar het is belangrijk om een interessant aspect in overweging te nemen, zoals de architectuur van de processor.
Op een gegeven moment vroeg ik me af dat ik voor grote foto's een grotere blokgrootte nodig had. Dit gebeurde thuis, op mijn WD-bestandsopslag met ARM-architectuur. Zonder er al te veel over na te denken, stelde ik de blokgrootte in op 8k of 16k in plaats van de standaard 4k, nadat ik de besparingen had gemeten. En alles ging perfect tot het moment dat de opslag zelf defect raakte, terwijl de schijf nog goed was. Toen ik de schijf in een normale computer met een Intel-processor plaatste, kreeg ik een verrassing: een niet-ondersteunde blokgrootte. Dat was het dan. De gegevens zijn er, alles is goed, maar ze zijn niet leesbaar. i386-processoren en soortgelijke kunnen niet omgaan met blokgroottes die niet overeenkomen met de paginagrootte in het geheugen, en die is precies 4k. Kortom, het eindigde met het gebruik van hulpmiddelen uit de gebruikersruimte; alles was langzaam en treurig, maar de gegevens zijn gered. Voor degenen die geïnteresseerd zijn — zoek op de naam van het hulpmiddel. fuseext2. Moraal: of denk aan alle gevallen van tevoren, of doe jezelf geen superheld voor en gebruik de standaardinstellingen voor huiseigenaren.

UPD. Zoals opgemerkt door de gebruiker berez , verduidelijk ik dat voor i386 de blokgrootte niet groter moet zijn dan 4k, maar niet noodzakelijkerwijs precies 4k moet zijn; 1k en 2k zijn ook toegestaan.

Dus, zo hebben wij de problemen opgelost.

Allereerst stuitten we op het probleem toen de meerterabyteschijf vol met gegevens was, en we konden de configuratie van het bestandssysteem niet veranderen.

Ten tweede was er een dringende oplossing nodig.

Uiteindelijk kwamen we tot de conclusie dat we de balans moesten aanpassen door het aantal bestanden te verminderen.
Om het aantal bestanden te verminderen, werd besloten om bestanden in één gezamenlijke archief te plaatsen. Gezien onze specificiteit, hebben we alle bestanden die binnen een bepaalde periode zijn gemaakt, in één archief geplaatst en hebben we de archivering dagelijks 's nachts uitgevoerd met een cron-taak.

Gekozen voor een zip-archief. In de opmerkingen bij het vorige artikel werd tar voorgesteld, maar daarmee zijn er complicaties: het heeft geen inhoudsopgave en de bestanden liggen er in een stroom (niet voor niets is 'tar' een afkorting voor 'Tape Archive', een nalatenschap van magneetbanden), dat wil zeggen, als je een bestand aan het einde van het archief wilt lezen, moet je het hele archief doorlezen, omdat er geen offsets zijn voor elk bestand ten opzichte van het begin van het archief. Dit maakt het een langzame operatie. Bij zip is alles veel beter: het heeft die inhoudsopgave en offsets voor bestanden binnen het archief, en de toegangstijd tot elk bestand is niet afhankelijk van de plaatsing ervan. Bovendien kon in ons geval de compressie-optie '0' worden ingesteld, aangezien alle bestanden al van tevoren waren gecomprimeerd in gzip.

Klanten halen bestanden op via nginx, en volgens de oude API, wordt simpelweg de bestandsnaam opgegeven, bijvoorbeeld zo:

http://www.server.com/hydra/20170416/0453/3bd24ae7-1df4-4d76-9d28-5b7fcb7fd8e5

Om bestanden on the fly uit te pakken, hebben we de nginx-unzip-module gevonden en toegevoegd (https://github.com/youzee/nginx-unzip-module) en twee upstreams ingesteld.

Het resultaat was een configuratie als volgt:

Tips voor het werken met een groot aantal kleine bestanden

Twee hosts in de instellingen zagen er als volgt uit:

server {
  listen *:8081;

  location / {
    root      /home/filestorage;
  }
}

server {
  listen *:8082;

  location ~ ^/hydra/(d+)/([\d]+)/(.+)$ {
    root      /home/filestorage;
    file_in_unzip_archivefile "/home/filestorage/hydra/$1/$2.zip";
    file_in_unzip_extract "$2/$3";
    file_in_unzip;
  }
}

En de configuratie van de upstreams op de bovenliggende nginx:

upstream storage {
  server server.com:8081;
  server server.com:8082;
}

Hoe het werkt:

  • De klant gaat naar de front nginx
  • Front nginx probeert het bestand van de eerste upstream te geven, dat wil zeggen, rechtstreeks vanaf het bestandssysteem
  • Als het bestand er niet is, probeert het het van de tweede upstream te geven, die probeert het bestand binnen het archief te vinden

Probleem twee: weer 'No space left on device'

Dit is het tweede probleem waar we mee te maken kregen, toen er veel bestanden in de map stonden.
We proberen een bestand te maken, het systeem klaagt dat er geen ruimte is. We veranderen de bestandsnaam en proberen het opnieuw.

Het lukt.

Het ziet er ongeveer zo uit:

Tips voor het werken met een groot aantal kleine bestanden

Een controle van inodes gaf niets, er zijn er veel vrij.
De controle van de ruimte, hetzelfde.
We dachten dat er misschien te veel bestanden in de map zijn, en daar is een limiet voor, maar opnieuw nee: Maximum aantal bestanden per map: ~1.3 × 10^20

En een bestand kan worden aangemaakt, als je de naam verandert.
Conclusie — het probleem zit in de bestandsnaam.

Verdere zoektochten toonden aan dat het probleem in het hash-algoritme bij het construeren van de index van de map ligt, bij een groot aantal bestanden zijn er botsingen met alle gevolgen van dien. Meer gedetailleerde informatie is hier te lezen: https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout#Hash_Tree_Directories

Je kunt deze optie uitschakelen, maar... het zoeken naar bestanden op naam kan onvoorspelbaar lang duren bij het doorlopen van alle bestanden.

 tune2fs -O "^dir_index" /dev/sdb3

Over het algemeen kan dit als een tijdelijke oplossing werken.

Les: veel bestanden in een map is meestal slecht. Dat moet je niet doen.

In dergelijke gevallen is het gebruikelijk om geneste mappen te maken, op basis van de eerste letters van de bestandsnaam of op andere parameters, zoals datums; dit redt in de meeste gevallen de situatie.
Maar een hoge hoeveelheid kleine bestanden is nog steeds slecht, zelfs als ze over mappen verdeeld zijn — zie dan het eerste probleem.

Probleem drie: hoe een lijst van bestanden te bekijken als er veel zijn.

In onze situatie, waarbij we veel bestanden hebben, stuiten we onvermijdelijk op het probleem hoe we de inhoud van de map kunnen bekijken.

De standaardoplossing is de opdracht ls.
Oké, laten we eens kijken wat we krijgen met 4772098 bestanden:


$ time ls /home/app/express.repository/offercache/ >/dev/null

real	0m30.203s
user	0m28.327s
sys	0m1.876s

30 seconden... dat is te veel. Het meeste tijd gaat verloren met het verwerken van bestanden in de gebruikersruimte en niet met de werking van de kernel.

Maar er is een oplossing:


$ time find /home/app/express.repository/offercache/ >/dev/null

real	0m3.714s
user	0m1.998s
sys	0m1.717s

3 seconden. 10 keer sneller.
Hoera!

UPD.

Een nog snellere oplossing van een gebruiker berez — het uitschakelen van sortering in ls


time ls -U /home/app/express.repository/offercache/ >/dev/null
real	0m2.985s
user	0m1.377s
sys	0m1.608s

Probleem vier: hoge LA bij het werken met bestanden

Soms ontstaat de situatie waarin je een hoop bestanden van de ene machine naar de andere moet kopiëren. Vaak resulteert dit in een aanzienlijke stijging van LA, omdat alles afhankelijk is van de prestaties van de schijven zelf.

Het meest verstandige zou zijn om SSD's te gebruiken. Echt geweldig. De enige vraag is de prijs van multi-terabyte SSD's.

Maar als de schijven standaard zijn, moeten bestanden worden gekopieerd, en dit is bovendien een productiesysteem, waar overbelasting leidt tot onvrede onder klanten? Er zijn minstens twee nuttige hulpmiddelen: nice en ionice.

nice — verlaagt de prioriteit van het proces, waardoor de scheduler meer tijdsquantums toekent aan andere, hogere prioriteitsprocessen.
In onze praktijk hielp het om nice op het maximum in te stellen (19 is de minimale prioriteit, -20 (min 20) is de maximale).

ionice — corrigeert de invoer/uitvoer prioriteit (I/O scheduling)

Als uw RAID-systeem onverwacht moet synchroniseren (na een mislukte reboot of als het RAID-array opnieuw moet worden hersteld na het vervangen van een schijf), kan het in sommige situaties zinvol zijn om de synchronisatiesnelheid te verlagen, zodat andere processen redelijk goed kunnen functioneren. Hiervoor kunt u het volgende commando gebruiken:


echo 1000 > /proc/sys/dev/raid/speed_limit_max

Probleem vijf: Hoe bestanden in real-time synchroniseren

We hebben weer die enorme hoeveelheden bestanden die we moeten back-uppen naar een tweede server om… Bestanden worden voortdurend geschreven, dus om minimaal verlies te hebben, moeten ze zo snel mogelijk worden gekopieerd.

Standaardoplossing: Rsync over SSH.

Dit is een goede optie, tenzij het moet gebeuren om de paar seconden. En er zijn veel bestanden. Zelfs als ze niet gekopieerd worden, moeten we toch ergens begrijpen wat veranderd is, en het vergelijken van meerdere miljoenen bestanden kost tijd en belast de schijven.

Dat wil zeggen, we moeten meteen weten wat we moeten kopiëren, zonder elke keer een vergelijking uit te voeren.

De redding is - lsyncd. Lsyncd — Live Syncing (Mirror) Daemon. Het werkt ook via rsync, maar monitort daarnaast het bestandssysteem op wijzigingen met behulp van inotify en fsevents en start het kopiëren alleen voor die bestanden die zijn toegevoegd of gewijzigd.

Probleem zes: hoe te begrijpen wie de schijven belast

Dit weet waarschijnlijk iedereen, maar voor de volledigheid: voor het monitoren van de schijf subsystem is er het commando iotop — vergelijkbaar met top, maar het toont de processen die het meest actief de schijven gebruiken.

Tips voor het werken met een groot aantal kleine bestanden

Overigens kan de oude vertrouwde top ook helpen om te begrijpen of er problemen zijn met de schijven of niet. Hiervoor zijn er twee meest geschikte parameters: Load Average en IOwait.

Tips voor het werken met een groot aantal kleine bestanden

De eerste toont hoeveel processen in de wachtrij staan voor dienstverlening, meestal meer dan 2 — dan gaat er al iets niet goed. Bij actieve kopieën naar backupsystemen tolereren we tot 6-8, daarna wordt de situatie als kritiek beschouwd.

De tweede — hoe druk de processor is met schijfoperaties. IOwait >10% — reden tot bezorgdheid, hoewel we op servers met een specifieke belasting gewoonlijk stabiel 40-50% hebben, en dat is echt normaal.

Daarmee eindig ik, hoewel er ongetwijfeld veel momenten zijn waarmee we nog niet zijn geconfronteerd, ik kijk ernaar uit om commentaar en beschrijvingen van interessante echte gevallen te ontvangen.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster