TL; DR: Se o kernel do Linux perder uma gravação de E / S em buffer , existe alguma maneira de o aplicativo descobrir?
Eu sei que você tem que fsync()
o arquivo (e seu diretório pai) para maior durabilidade . A questão é se o kernel perde buffers sujos com gravação pendente devido a um erro de E / S, como o aplicativo pode detectar isso e recuperar ou anular?
Pense em aplicativos de banco de dados, etc., onde a ordem de gravação e a durabilidade da gravação podem ser cruciais.
Gravações perdidas? Quão?
Pode camada do bloco do kernel Linux em algumas circunstâncias perdem solicitações de E / S que foram submetidos com sucesso por tamponada write()
, pwrite()
etc, com um erro como:
Buffer I/O error on device dm-0, logical block 12345
lost page write due to I/O error on dm-0
(Veja end_buffer_write_sync(...)
e end_buffer_async_write(...)
entrefs/buffer.c
).
Nos kernels mais recentes, o erro conterá "escrita de página assíncrona perdida" , como:
Buffer I/O error on dev dm-0, logical block 12345, lost async page write
Como o aplicativo write()
já retornou sem erro, parece não haver maneira de relatar um erro ao aplicativo.
Detectando-os?
Não estou familiarizado com as fontes do kernel, mas acho que ele define AS_EIO
o buffer que falhou ao ser gravado se estiver fazendo uma gravação assíncrona:
set_bit(AS_EIO, &page->mapping->flags);
set_buffer_write_io_error(bh);
clear_buffer_uptodate(bh);
SetPageError(page);
mas não está claro para mim se ou como o aplicativo pode descobrir isso quando mais tarde for fsync()
o arquivo para confirmar que está no disco.
Parece que wait_on_page_writeback_range(...)
nomm/filemap.c
poder por do_sync_mapping_range(...)
emfs/sync.c
que se vire chamado por sys_sync_file_range(...)
. Retorna -EIO
se um ou mais buffers não puderam ser gravados.
Se, como suponho, isso se propaga para fsync()
o resultado, se o aplicativo entrar em pânico e cair fora se receber um erro de E / S fsync()
e souber como refazer seu trabalho quando reiniciado, isso deve ser uma proteção suficiente?
Presumivelmente, não há como o aplicativo saber quais desvios de bytes em um arquivo correspondem às páginas perdidas, para poder reescrevê-las se souber, mas se o aplicativo repetir todo o seu trabalho pendente desde o último êxito fsync()
do arquivo e que reescreve quaisquer buffers de kernel sujos correspondentes a gravações perdidas no arquivo, que devem limpar os sinalizadores de erro de E / S nas páginas perdidas e permitir que o próximo fsync()
seja concluído - certo?
Existem outras circunstâncias inofensivas nas quais fsync()
pode voltar -EIO
onde o resgate e o refazer do trabalho seriam muito drásticos?
Por quê?
Claro que esses erros não deveriam acontecer. Nesse caso, o erro surgiu de uma interação infeliz entre os dm-multipath
padrões do driver e o código de detecção usado pela SAN para relatar falha na alocação de armazenamento thin-provisioned. Mas essa não é a única circunstância em que eles podem acontecer - eu também vi relatórios sobre LVM thin provisionado, por exemplo, conforme usado por libvirt, Docker e muito mais. Um aplicativo crítico como um banco de dados deve tentar lidar com esses erros, em vez de continuar cegamente como se tudo estivesse bem.
Se o kernel achar que não há problema em perder gravações sem morrer de pânico, os aplicativos precisam encontrar uma maneira de lidar com isso.
O impacto prático é que eu encontrei um caso em que um problema de caminhos múltiplos com uma SAN causava gravações perdidas que acabavam causando corrupção no banco de dados porque o DBMS não sabia que suas gravações haviam falhado. Não tem graça.
fonte
Respostas:
fsync()
retorna-EIO
se o kernel perdeu uma gravação(Nota: a parte inicial faz referência a kernels antigos; atualizado abaixo para refletir os kernels modernos)
Parece que a gravação do buffer assíncrono em
end_buffer_async_write(...)
falhas define um-EIO
sinalizador na página do buffer sujo com falha para o arquivo :que é então detectado por
wait_on_page_writeback_range(...)
enquanto chamado pordo_sync_mapping_range(...)
enquanto chamado porsys_sync_file_range(...)
enquanto chamado porsys_sync_file_range2(...)
implementar a chamada biblioteca Cfsync()
.Mas apenas uma vez!
Este comentário em
sys_sync_file_range
sugere que, quando
fsync()
retornar-EIO
ou (não documentado na página de manual)-ENOSPC
, limpará o estado do erro, para que um subsequentefsync()
relate o êxito, mesmo que as páginas nunca tenham sido gravadas.Com certeza,
wait_on_page_writeback_range(...)
limpa os bits de erro ao testá-los :Portanto, se o aplicativo espera que ele possa tentar novamente
fsync()
até que seja bem-sucedido e confie que os dados estão em disco, isso estará muito errado.Tenho certeza de que essa é a fonte da corrupção de dados que encontrei no DBMS. Ele tenta novamente
fsync()
e acha que tudo ficará bem quando for bem-sucedido.Isso é permitido?
Os documentos POSIX / SuS
fsync()
realmente não especificam isso de qualquer maneira:A página de manual do Linux
fsync()
simplesmente não diz nada sobre o que acontece em caso de falha.Portanto, parece que o significado de
fsync()
erros é "não sei o que aconteceu com suas gravações, pode ter funcionado ou não, é melhor tentar novamente para ter certeza".Kernels mais recentes
Em 4.9
end_buffer_async_write
conjuntos-EIO
na página, apenas viamapping_set_error
.No lado da sincronização, acho que é semelhante, embora a estrutura agora seja bastante complexa de seguir.
filemap_check_errors
nomm/filemap.c
agora faz:que tem o mesmo efeito. Parece que todas as verificações de erro passam pelo
filemap_check_errors
teste e limpeza:Estou usando
btrfs
no meu laptop, mas quando crio umext4
loopback para teste/mnt/tmp
e configuro um probe perf nele:Eu encontro a seguinte pilha de chamadas em
perf report -T
:Uma leitura sugere que sim, os kernels modernos se comportam da mesma maneira.
Isto parece significar que, se
fsync()
(ou, presumivelmente,write()
ouclose()
retornos)-EIO
, o arquivo está em algum estado indefinido entre quando você último com êxitofsync()
D ouclose()
D, e seu mais recentementewrite()
estado dez.Teste
Implementei um caso de teste para demonstrar esse comportamento .
Implicações
Um DBMS pode lidar com isso inserindo a recuperação de falhas. Como diabos um aplicativo de usuário normal deve lidar com isso? A
fsync()
página de manual não dá aviso de que significa "fsync-se-você-sente-o-it" e espero que muitos aplicativos não lidem bem com esse comportamento.Relatório de erros
Leitura adicional
O lwn.net abordou isso no artigo "Manipulação de erro na camada de bloco aprimorada" .
thread da lista de discussão postgresql.org .
fonte
errno
é completamente uma construção da biblioteca C do espaço do usuário. É comum ignorar as diferenças de valor de retorno entre os syscalls e a biblioteca C dessa maneira (como Craig Ringer faz acima), pois o valor de retorno do erro identifica de forma confiável qual deles (syscall ou função da biblioteca C) está sendo referido: "-1
witherrno==EIO
"refere-se a uma função de biblioteca C, enquanto"-EIO
"refere-se a um syscall. Finalmente, as páginas de manual do Linux online são a referência mais atualizada para as páginas de manual do Linux.fsync()
/fdatasync()
quando o tamanho da transação é um arquivo completo; Usandommap()
/msync()
quando o tamanho da transação é um registro alinhado por página; e Usando I de baixo nível / O,fdatasync()
e vários descritores de arquivo simultâneos (um descritor e um encadeamento por transação) para o mesmo arquivo " . Os bloqueios de descrição de arquivo aberto específicos do Linux (fcntl()
,F_OFD_
) são muito úteis com o último.Eu não concordo.
write
pode retornar sem erro se a gravação for simplesmente enfileirada, mas o erro será relatado na próxima operação que exigirá a gravação real no disco, ou seja, na próximafsync
, possivelmente na gravação a seguir, se o sistema decidir liberar o cache e em menos no último arquivo fechado.Essa é a razão pela qual é essencial que o aplicativo teste o valor de retorno de close para detectar possíveis erros de gravação.
Se você realmente precisa executar um processamento inteligente de erros, deve presumir que tudo o que foi escrito desde o último êxito
fsync
pode ter falhado e que, pelo menos, algo falhou.fonte
fsync()
ouclose()
do arquivo se ele recebe um-EIO
dewrite()
,fsync()
ouclose()
. Bem, isso é divertido.write
(2) fornece menos do que você espera. A página de manual é muito aberta sobre a semântica de umawrite()
chamada bem-sucedida :Podemos concluir que um sucesso
write()
significa apenas que os dados atingiram os recursos de buffer do kernel. Se a persistência do buffer falhar, um acesso subsequente ao descritor de arquivo retornará o código de erro. Como último recurso que pode serclose()
. A página de manual daclose
chamada do sistema (2) contém a seguinte frase:Se seu aplicativo precisar persistir com a gravação de dados, ele deverá usar
fsync
/fsyncdata
regularmente:fonte
fsync()
é necessário. Mas no caso específico onde o kernel perde as páginas devido a um erro de E / S iráfsync()
falhar? Sob que circunstâncias ele pode ter sucesso depois?fsync()
retornos-EIO
sobre questões de E / S (para o que seria bom, caso contrário?). Portanto, o banco de dados sabe que parte de uma gravação anterior falhou e pode entrar no modo de recuperação. Não é isso que você quer? Qual é a motivação da sua última pergunta? Deseja saber qual gravação falhou ou recuperar o descritor de arquivo para uso posterior?fsync()
possa retornar-EIO
onde é seguro tentar novamente e se é possível dizer a diferença.-EIO
. Se cada descritor de arquivo for usado apenas por um encadeamento de cada vez, esse encadeamento poderá voltar ao últimofsync()
e refazer aswrite()
chamadas. Mas, ainda assim, se esseswrite()
apenas escrevem parte de um setor, a parte não modificada ainda pode estar corrompida.Use o sinalizador O_SYNC ao abrir o arquivo. Ele garante que os dados sejam gravados no disco.
Se isso não lhe agradar, não haverá nada.
fonte
O_SYNC
é um pesadelo para o desempenho. Isso significa que o aplicativo não pode fazer mais nada enquanto a E / S do disco está ocorrendo, a menos que produza threads de E / S. Você também pode dizer que a interface de E / S em buffer é insegura e todos devem usar o AIO. Gravações perdidas silenciosamente não podem ser aceitáveis em E / S em buffer?O_DATASYNC
é apenas um pouco melhor a esse respeito)Verifique o valor de retorno do fechamento. O fechamento pode falhar enquanto as gravações em buffer parecem ter êxito.
fonte
open()
ing eclose()
ing o arquivo a cada poucos segundos. é por isso que temosfsync()
...