A ideia do artigo surgiu espontaneamente de uma discussão nos comentários de um artigo. .

A questão é que a especificidade interna do funcionamento dos nossos serviços é o armazenamento de um número enorme de arquivos pequenos. No momento, temos cerca de centenas de terabytes desses dados. E nos deparamos com algumas armadilhas óbvias e não tão óbvias e passamos por elas com sucesso.
Por isso, estou compartilhando nossa experiência, pode ser útil para alguém.
Primeiro problema: «Sem espaço disponível no dispositivo»
Como mencionado no artigo anteriormente citado, o problema é que há blocos livres no sistema de arquivos, mas inodes se esgotaram.
Você pode verificar o número de inodes usados e livres com o comando df -ih:

Não vou recontar o artigo, em resumo, no disco há tanto blocos diretamente para dados quanto blocos para metainformações, os quais são inodes (nodo índice). O número deles é definido na inicialização do sistema de arquivos (falamos do ext2 e seus sucessores) e não muda depois. O equilíbrio entre blocos de dados e inodes é calculado a partir de dados estatísticos, mas, em nosso caso, onde há muitos arquivos pequenos, o equilíbrio deve se deslocar na direção do número de inodes — deve haver mais deles.
No Linux, já foram previstas variantes com diferentes balanços, e todas essas configurações pré-calculadas estão no arquivo /etc/mke2fs.conf.
Portanto, na inicialização inicial do sistema de arquivos através do mke2fs, você pode especificar o perfil desejado.
Aqui estão alguns exemplos do arquivo:
small = {
blocksize = 1024
inode_size = 128
inode_ratio = 4096
}
big = {
inode_ratio = 32768
}
largefile = {
inode_ratio = 1048576
blocksize = -1
}
Você pode escolher a opção de uso desejada com a opção «-T» ao chamar o mke2fs. Também é possível especificar manualmente os parâmetros necessários, se não houver uma solução pronta.
Mais detalhes estão descritos nos manuais para mke2fs.conf e mke2fs.
Uma característica não abordada no artigo mencionado acima é que você pode definir o tamanho do bloco de dados. É evidente que para grandes arquivos vale a pena um tamanho de bloco maior; para pequenos, um menor.
No entanto, vale a pena considerar uma característica interessante, como a arquitetura do processador.
Um tempo atrás, pensei que precisava de um tamanho de bloco maior para arquivos grandes de fotos. Era um ambiente doméstico, em um armazenamento doméstico WD com arquitetura ARM. Sem pensar muito, defini o tamanho do bloco como 8k ou 16k em vez do padrão de 4k, depois de medir a economia. E tudo estava ótimo até o momento em que o próprio armazenamento falhou, embora o disco estivesse intacto. Ao colocar o disco em um computador comum com um processador Intel, recebi uma surpresa: tamanho de bloco não suportado. A situação ficou complicada. Os dados estavam lá, tudo bem, mas não era possível ler. Processadores i386 e similares não conseguem trabalhar com tamanhos de bloco que não correspondem ao tamanho da página de memória, que é exatamente 4k. No geral, a situação foi resolvida usando utilitários do espaço do usuário, tudo foi lento e triste, mas conseguimos salvar os dados. Para quem tiver interesse - procure pelo nome do utilitário. fuseext2. Moral da história: ou pense em todos os casos antecipadamente, ou não tente se passar por super-herói e use as configurações padrão para donas de casa.
UPD. Com a observação do usuário esclareço que para i386 o tamanho do bloco não deve exceder 4k, mas não necessariamente deve ser exatamente 4k, ou seja, tamanhos de 1k e 2k são permitidos.
Então, como nós resolvemos os problemas.
Primeiro, encontramos o problema quando um disco de múltiplos terabytes estava cheio de dados, e não podíamos refazer a configuração do sistema de arquivos.
Em segundo lugar, a solução precisava ser urgente.
No final, chegamos à conclusão de que precisávamos mudar o equilíbrio, reduzindo o número de arquivos.
Para reduzir o número de arquivos, decidimos compactar os arquivos em um único arquivo. Considerando nossa especificidade, agrupamos em um único arquivo todos os arquivos de um determinado período e realizamos a arquivação com uma tarefa cron diariamente à noite.
Selecionamos o arquivo zip. Nos comentários do artigo anterior, foi sugerido usar tar, mas isso traz uma dificuldade: ele não possui um índice, e os arquivos estão armazenados sequencialmente (não é à toa que "tar" é uma abreviação de "Tape Archive", um legado dos suportes em fita), ou seja, se você precisar ler um arquivo no final do arquivo, terá que ler todo o arquivo, pois não há deslocamentos para cada arquivo em relação ao início do arquivo. Portanto, esta é uma operação demorada. No zip, tudo é muito melhor: ele tem o tal índice e os deslocamentos dos arquivos dentro do arquivo, e o tempo de acesso a cada arquivo não depende de sua localização. E no nosso caso, poderíamos definir a opção de compactação "0", pois todos os arquivos já estavam previamente compactados em gzip.
Os clientes baixam arquivos através do nginx e, de acordo com a API antiga, basta informar o nome do arquivo, por exemplo:
http://www.server.com/hydra/20170416/0453/3bd24ae7-1df4-4d76-9d28-5b7fcb7fd8e5
Para descompactar arquivos on-the-fly, encontramos e conectamos o módulo nginx-unzip-module () e configuramos dois upstreams.
O resultado foi uma configuração como esta:

Os dois hosts nas configurações eram assim:
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;
}
}
E a configuração dos upstreams no nginx superior:
upstream storage {
server server.com:8081;
server server.com:8082;
}
Como funciona:
- O cliente vai para o nginx frontal
- O nginx frontal tenta entregar o arquivo do primeiro upstream, ou seja, diretamente do sistema de arquivos
- Se o arquivo não existir, tenta entregar do segundo upstream, que tenta encontrar o arquivo dentro do arquivo
O segundo problema: novamente "No space left on device"
Este é o segundo problema que encontramos quando há muitos arquivos no diretório.
Estamos tentando criar um arquivo, mas o sistema reclama que não há espaço. Mudamos o nome do arquivo e tentamos criá-lo novamente.
E funciona.
Aparentemente, fica assim:

A verificação dos inodes não mostrou nada — há muitos livres.
A verificação do espaço - o mesmo.
Pensamos que poderia haver muitos arquivos no diretório, e há um limite, mas novamente não: Número máximo de arquivos por diretório: ~1.3 × 10^20
E na verdade é possível criar um arquivo, se mudarmos o nome.
A conclusão é que o problema está no nome do arquivo.
Pesquisas adicionais mostraram que o problema está no algoritmo de hashing ao construir o índice do diretório; com um grande número de arquivos, ocorrem colisões com todas as consequências decorrentes. Mais informações podem ser lidas aqui:
É possível desativar esta opção, mas... a busca de arquivos pelo nome pode se tornar imprevisivelmente lenta ao percorrer todos os arquivos.
tune2fs -O "^dir_index" /dev/sdb3
De modo geral, como solução temporária pode funcionar.
Moral da história: ter muitos arquivos em um diretório geralmente é ruim. Não se deve fazer isso.
Normalmente, nesses casos, cria-se subdiretórios, por primeiras letras do nome do arquivo ou por outros parâmetros, como datas; na maioria das vezes isso ajuda.
Mas o número total de arquivos pequenos ainda é problemático, mesmo que sejam organizados em diretórios — então, veja o primeiro problema.
Terceiro problema: como visualizar a lista de arquivos, se houver muitos.
Em nossa situação, com muitos arquivos, de uma forma ou outra, nos deparamos com a questão de como ver o conteúdo do diretório.
A solução padrão é o comando ls.
Ok, vamos ver o que acontece com 4772098 arquivos:
$ time ls /home/app/express.repository/offercache/ >/dev/null
real 0m30.203s
user 0m28.327s
sys 0m1.876s
30 segundos... é bastante tempo. E o tempo principal é gasto no processamento de arquivos no espaço do usuário, e não na operação do núcleo.
Mas existe uma solução:
$ time find /home/app/express.repository/offercache/ >/dev/null
real 0m3.714s
user 0m1.998s
sys 0m1.717s
3 segundos. 10 vezes mais rápido.
Uhul!
UPD.
Outra solução ainda mais rápida do usuário — desativar a ordenação em ls
time ls -U /home/app/express.repository/offercache/ >/dev/null
real 0m2.985s
user 0m1.377s
sys 0m1.608s
Quarto problema: alto LA ao trabalhar com arquivos.
Periodicamente surge a situação em que é necessário copiar um monte de arquivos de uma máquina para outra. Muitas vezes, o LA aumenta drasticamente, pois tudo depende da performance dos próprios discos.
A coisa mais sensata é usar SSD. Realmente ótimo. A questão é o custo de SSDs de múltiplos terabytes.
Mas se os discos forem comuns, os arquivos precisam ser copiados, e isso em um sistema de produção, onde a sobrecarga leva a reclamações dos clientes? Existem pelo menos duas ferramentas úteis: nice e ionice.
nice — diminui a prioridade do processo, assim o escalonador dá mais quanta de tempo para outros processos com prioridade maior.
Em nossa prática, ajudou definir o nice no máximo (19 — minimal prioridade, -20 (menos 20) — máxima).
ionice — assim, ajusta a prioridade de entrada/saída (I/O scheduling).
Se você está usando RAID e de repente ele precisa se sincronizar (após uma reinicialização malsucedida ou para recuperar o conjunto RAID após a substituição de um disco), em algumas situações faz sentido reduzir a velocidade de sincronização, para que outros processos possam funcionar de maneira mais ou menos adequada. Para isso, o seguinte comando pode ajudar:
echo 1000 > /proc/sys/dev/raid/speed_limit_max
Problema cinco: Como sincronizar arquivos em tempo real
Temos a mesma enorme quantidade de arquivos que precisam ser backupados em um segundo servidor para evitar… Os arquivos estão sendo escritos constantemente, então, para ter o mínimo de perdas, precisamos copiá-los o mais rápido possível.
Solução padrão: Rsync over SSH.
Essa é uma boa opção, a menos que seja necessário fazer isso a cada alguns segundos. E há muitos arquivos. Mesmo que não seja preciso copiá-los, precisamos entender o que mudou, e comparar vários milhões de arquivos consome tempo e gera carga nos discos.
Ou seja, precisamos saber imediatamente o que deve ser copiado, sem ter que iniciar a comparação a cada vez.
A salvação é lsyncd. Lsyncd — . Ele também funciona através do rsync, mas adicionalmente monitora o sistema de arquivos em busca de alterações usando inotify e fsevents e inicia a cópia apenas dos arquivos que foram criados ou modificados.
Problema seis: como entender quem está sobrecarregando os discos
Provavelmente todos sabem, mas, mesmo assim, para completar o quadro: para monitorar o subsistema de disco existe o comando iotop — semelhante ao top, mas mostra os processos que utilizam os discos de forma mais ativa.

A propósito, o bom e velho top também permite entender se há problemas com os discos ou não. Para isso, existem dois parâmetros mais adequados: Load Average e IOwait.

O primeiro mostra quantos processos estão na fila para atendimento, geralmente mais de 2 já indica que algo não está certo. Durante a cópia ativa para servidores de backup, aceitamos até 6-8, após isso a situação é considerada anômala.
O segundo — quão ocupado o processador está com operações de disco. IOwait >10% — é motivo de preocupação, embora em nossos servidores com perfil de carga específico, isso possa estar estável entre 40-50%, e isso é realmente o normal.
Para finalizar, embora com certeza haja muitos aspectos que não encontramos, aguardo com prazer comentários e descrições de casos reais interessantes.
Fonte: habr.com
