O gerenciador de pacotes Debian poderia dpkg
obter uma melhoria notável de desempenho usando uma das operações AIO fsync(), em vez de sync_file_range() + fsync()?
A API fsync2() [proposta] é essencialmente idêntica à API AIO_FSYNC/AIO_FDSYNC existente, exceto que é síncrona e é isso que os aplicativos desejam evitar.
O único argumento que me foi apresentado contra [usar] AIO_FSYNC é que "a implementação é apenas uma fila de trabalho", o que é em grande parte sem sentido porque é independente da implementação do sistema de arquivos, mas permite a paralelização automática do lado do kernel de todas as operações fsync emitidas. Isso permite que o (s) sistema (s) de arquivos otimizem automaticamente as gravações desnecessárias do diário ao concluir operações fsync simultâneas - XFS, ext4, etc. já fazem isso quando os aplicativos do usuário executam fsync() simultaneamente a partir de muitos processos/threads.....
Esta implementação simples permite uma carga de trabalho simples de "descompactar com aio fsync" (ou seja, "escrever muitos arquivos de 4kB e aio_fsync() em lotes à medida que avançamos, retirando fsync()s completos antes de enviarmos um novo lote") carga de trabalho no XFS para ir de cerca de 2.000 arquivos/s (latência de E/S de gravação síncrona vinculada) a mais de 40.000 arquivos/s (iops de gravação vinculados ao armazenamento de back-end).
A carga de trabalho de exemplo tem semelhanças com apt-get install
ou dpkg -i
(em parte dependendo do tamanho dos arquivos nos pacotes instalados :-). dpkg
deve efetivamente fsync() todos os arquivos descompactados, antes de renomeá-los no lugar.
dpkg
foi otimizado usando conselhos de Ted T'so. A otimização consiste em adicionar chamadas para sync_file_range() em determinados pontos. Esta chamada de sistema faznãofornecem as mesmas garantias que fsync(). Por favor, leia a documentação paraintervalo_de_arquivo_sincronizado()e observe o aviso proeminente :-).
Nenhuma dessas operações grava os metadados do arquivo. Portanto, a menos que o aplicativo execute substituições estritamente de blocos de disco já instanciados, não há garantias de que os dados estarão disponíveis após uma falha.
dpkg
aciona o write-back de dados imediatamente após gravar cada arquivo, usando SYNC_FILE_RANGE_WRITE
. Ele grava todos os arquivos do pacote primeiro. Em seguida, há uma segunda passagem pelos arquivos, que aguarda o write-back dos dados usando SYNC_FILE_RANGE_WAIT_BEFORE
, chama fsync()
e, finalmente, renomeia o arquivo no lugar.
Veja commits:
- Desabilitar o uso da sincronização síncrona(2) por padrão
- Adicionado novo --force-unsafe-io para desabilitar operações de E/S seguras ao descompactar
- No Linux, inicie o write-back dos arquivos descompactados o mais rápido possível
- No Linux, termine o write-back antes do fsync
Minha hipótese é que paralelizar as operações fsync() poderia melhorar o desempenho, permitindo lotes mais eficientes dometadadosgrava, particularmente agrupando em lote as barreiras associadas/liberações de cache de disco que são necessárias para garantir que os metadados no disco sejam consistentes em todos os momentos.
EDIT: Parece que minha hipótese era muito simples, pelo menos ao usar o sistema de arquivos ext4:
A segunda série de chamadas sync_file_range(), com a operação
SYNC_FILE_RANGE_WAIT_BEFORE
, será bloqueada até que o write-back iniciado anteriormente seja concluído. Isto basicamente garante que a alocação atrasada foi resolvida; isto é, os blocos de dados foram alocados e gravados, e o inode atualizado (na memória), mas não necessariamente enviado para o disco.A chamada [fsync()] forçará o inode para o disco. No caso do sistema de arquivos ext4, o primeiro [fsync()] irá realmente enviar todos os inodes para o disco, e todas as chamadas [fsync()] subsequentes são, na verdade, sem operação (assumindo que os arquivos 'a', 'b' e 'c' estejam todos no mesmo sistema de arquivos). Mas o que isso significa é que minimiza o número de commits (pesados) do jbd2 ao mínimo.
Ele usa uma chamada de sistema específica do Linux ---sync_file_range() --- mas o resultado deve ser um desempenho mais rápido em todos os sistemas de arquivos. Portanto, não considero isso um hack específico do ext4, embora provavelmente torne as coisas mais rápidas para o ext4 do que qualquer outro sistema de arquivos.
--Ted T'so
Pode ser que algum outro sistema de arquivos se beneficie do uso de operações AIO fsync().
bcachefs
(em desenvolvimento) afirma isolar IO entre arquivos diferentes muito melhor que o ext4. Portanto, isso pode ser particularmente interessante de testar.
Parece que o ext4 pode não ser tão otimizado para um padrão AIO fsync() puro (acho que outros sistemas de arquivos também poderiam ter a mesma restrição). Nesse caso, suponho que seria possível fazer todas as mesmas chamadas sync_file_range() primeiro, depois iniciar todas as operações AIO fsync() como uma segunda rodada e terminar renomeando todos os arquivos no lugar como fsync() operações concluídas.
VELHO:
O primeiro passo em tal investigação deve ser a medição :-).
É possível desabilitar a parte fsync(), usando echo "force-unsafe-io" > /etc/dpkg/dpkg.cfg.d/force-unsafe-io
.
Até agora, tentei rodar apt-get install
em strace -f -wc
, em um contêiner Debian 9. Por exemplo, instalando o aptitude
pacote usando "unsafe io", existem apenas 495 chamadas fsync() síncronas. Durante a instalação aptitude
normalmente, existem 1011 chamadas fsync(). "unsafe io" também desativou a SYNC_FILE_RANGE_WAIT_BEFORE
chamada, reduzindo o número de chamadas sync_file_range() de 1036 para 518.
No entanto, ficou muito menos claro se isso reduziu o tempo médio gasto. Se isso aconteceu, não parece ser mais do que a variação aleatória entre as execuções. Até agora, testei isso em ext4 e XFS, em um HDD mecânico.
apt-get
diz que o tamanho total dos 518 arquivos descompactados foi de 21,7 MB (veja o resultado abaixo).
Em relação às chamadas 495 fsync(), que permaneceram presentes mesmo ao solicitar "inseguro io":
No ext4, a saída do strace mostrou o tempo gasto nas chamadas fsync() restantes em cerca de 11 segundos. No XFS, o valor correspondente foi de cerca de 7 segundos. Em todos os casos, esse foi o tempo necessário para a instalação aptitude
.
Portanto, mesmo que "inseguro io" esteja proporcionando uma pequena melhoria na instalação aptitude
, parece que você precisaria /var
ser montado em um dispositivo significativamente mais rápido (menor latência) do que o resto do sistema, antes que a diferença fosse realmente perceptível. Mas não estou interessado em otimizar esse nicho.
A execução em strace -f -y -e trace=fsync,rename
mostrou que, para as chamadas fsync() restantes, 2 delas estavam ativadas /etc/ld.so.cache~
e 493 delas eram para arquivos dentro, /var/lib/dpkg/
ou seja, o banco de dados do pacote.
318 das chamadas fsync() estão em /var/lib/dpkg/updates/
. Estes são incrementos no banco de dados dpkg /var/lib/dpkg/status
. Os incrementos são acumulados no banco de dados principal ("checkpointed") no final da execução do dpkg.
The following NEW packages will be installed:
aptitude aptitude-common libboost-filesystem1.62.0 libboost-iostreams1.62.0 libboost-system1.62.0 libcgi-fast-perl libcgi-pm-perl
libclass-accessor-perl libcwidget3v5 libencode-locale-perl libfcgi-perl libhtml-parser-perl libhtml-tagset-perl libhttp-date-perl
libhttp-message-perl libio-html-perl libio-string-perl liblwp-mediatypes-perl libparse-debianchangelog-perl libsigc++-2.0-0v5 libsqlite3-0
libsub-name-perl libtimedate-perl liburi-perl libxapian30
0 upgraded, 25 newly installed, 0 to remove and 0 not upgraded.
Need to get 0 B/6000 kB of archives.
After this operation, 21.7 MB of additional disk space will be used.
Responder1
A questão sugere que isso não ajudará no ext4 ou no XFS.
Também testei a instalação de um pacote muito maior ( linux-image-4.9.0-9-amd64
). Ainda parecia levar o mesmo tempo, independentemente de --force-unsafe-io
.
ext2
No ext2, --force-unsafe-io
reduziu o tempo de instalação linux-image
de 50 segundos para 13 segundos.
O kernel em que executei os testes foi o 5.0.17-200.fc29.x86_64
, que usa CONFIG_EXT4_USE_FOR_EXT2
.
Testei ext2 usando a implementação aio_fsync() do espaço do usuário. No entanto, a melhor melhoria não dependeu do uso do AIO fsync().
Minha melhora foi na verdade devido a um efeito colateral. Eu mudei o dpkg para fazer todas as operações fsync() primeiro e depois todas as operações rename(). Enquanto o dpkg sem patch chamava rename() após cada fsync(). Usei profundidades de fila AIO de até 256. AIO fsync() com profundidade de fila 1 foi significativamente mais lento que fsync() síncrono - parece que houve alguma sobrecarga. A melhor melhoria também exigia a realização de todas as SYNC_FILE_RANGE_WRITE
operações originais primeiro. A versão melhorada foi instalada linux-image
em cerca de 18 segundos.
Esta ordem de operações é na verdade o que Ted T'so sugeriu originalmente :-D. O que acontece é que CONFIG_EXT4_USE_FOR_EXT2
fsync() também sincroniza o diretório pai de maneira útil. Você deseja fazer toda a manipulação do nome do arquivo primeiro, para evitar várias atualizações no disco para cada diretório. Acho que isso não acontece com a CONFIG_EXT2
implementação antiga ou com um ext4
sistema de arquivos normal.
ext4: faça fsync para sincronizar o diretório pai sem diário de verdade desta vez
[...] Isso também inclui o modo padrão ext2, obviamente. [...]
https://elixir.bootlin.com/linux/v5.0.17/source/fs/ext4/fsync.c#L38
* If we're not journaling and this is a just-created file, we have to
* sync our parent directory (if it was freshly created) since
* otherwise it will only be written by writeback, leaving a huge
* window during which a crash may lose the file. This may apply for
* the parent directory's parent as well, and so on recursively, if
* they are also freshly created.
Como antes, substituir o estágio fsync() por sync() parece fornecer um desempenho perturbadoramente bom, correspondendo --force-unsafe-io
:-). sincronizar() ou sincronizar() parecem ser muito bons se você conseguir usá-los.
btrfs
Quando comecei a testar aio_fsync() no btrfs, descobri que as operações fsync() podem causar o bloqueio de rename() do arquivo, devido a uma correção recente de integridade de dados. Decidi que não estou interessado em btrfs.
Por que rename() demora mais quando fsync() é chamado primeiro?