TL; DR: Si le noyau Linux perd une écriture d'E / S tamponnée , y a-t-il un moyen pour l'application de le savoir?
Je sais que vous devez fsync()
le fichier (et son répertoire parent) pour la durabilité . La question est de savoir si le noyau perd les tampons sales qui sont en attente d'écriture en raison d'une erreur d'E / S, comment l'application peut-elle détecter cela et récupérer ou abandonner?
Pensez aux applications de base de données, etc., où l'ordre des écritures et la durabilité des écritures peuvent être cruciaux.
Écritures perdues? Comment?
La couche de blocs du noyau Linux peut dans certaines circonstances perdre les demandes d'E / S tamponnées qui ont été soumises avec succès par write()
, pwrite()
etc., avec une erreur comme:
Buffer I/O error on device dm-0, logical block 12345
lost page write due to I/O error on dm-0
(Voir end_buffer_write_sync(...)
et end_buffer_async_write(...)
dansfs/buffer.c
).
Sur les noyaux plus récents, l'erreur contiendra à la place "écriture de page asynchrone perdue" , comme:
Buffer I/O error on dev dm-0, logical block 12345, lost async page write
Étant donné que l'application write()
sera déjà retournée sans erreur, il ne semble y avoir aucun moyen de signaler une erreur à l'application.
Les détecter?
Je ne suis pas très familier avec les sources du noyau, mais je pense qu'il est défini AS_EIO
sur le tampon qui n'a pas pu être écrit s'il fait une écriture asynchrone:
set_bit(AS_EIO, &page->mapping->flags);
set_buffer_write_io_error(bh);
clear_buffer_uptodate(bh);
SetPageError(page);
mais je ne sais pas si ou comment l'application peut le découvrir plus tard fsync()
le fichier pour confirmer qu il est sur le disque.
Il ressemble wait_on_page_writeback_range(...)
enmm/filemap.c
puissance par do_sync_mapping_range(...)
dansfs/sync.c
ce qui est à son tour appelé par sys_sync_file_range(...)
. Il retourne-EIO
si un ou plusieurs tampons n'ont pas pu être écrits.
Si, comme je le suppose, cela se propage au fsync()
résultat de, alors si l'application panique et renonce si elle reçoit une erreur d'E / S fsync()
et sait comment refaire son travail lors du redémarrage, cela devrait être une sauvegarde suffisante?
Il n'y a probablement aucun moyen pour l'application de savoir quels décalages d'octets dans un fichier correspondent aux pages perdues afin qu'elle puisse les réécrire si elle sait comment, mais si l'application répète tout son travail en attente depuis le dernier succès fsync()
du fichier, et cela réécrit des tampons de noyau sales correspondant à des écritures perdues sur le fichier, qui devraient effacer tous les indicateurs d'erreur d'E / S sur les pages perdues et permettre au suivant fsync()
de se terminer - n'est-ce pas?
Y a-t-il alors d'autres circonstances, inoffensives, où fsync()
peuvent revenir -EIO
où renflouer et refaire des travaux serait trop drastique?
Pourquoi?
Bien entendu, de telles erreurs ne devraient pas se produire. Dans ce cas, l'erreur provenait d'une interaction malheureuse entre les dm-multipath
valeurs par défaut du pilote et le code de détection utilisé par le SAN pour signaler l'échec de l'allocation du stockage alloué de manière dynamique. Mais ce n'est pas la seule circonstance où ils peuvent se produire - j'ai également vu des rapports de LVM à provisionnement fin par exemple, tel qu'utilisé par libvirt, Docker, etc. Une application critique comme une base de données devrait essayer de faire face à de telles erreurs, plutôt que de continuer aveuglément comme si tout allait bien.
Si le noyau pense qu'il est acceptable de perdre des écritures sans mourir avec une panique du noyau, les applications doivent trouver un moyen de faire face.
L'impact pratique est que j'ai trouvé un cas où un problème de trajets multiples avec un SAN a causé des écritures perdues qui ont abouti à une corruption de la base de données parce que le SGBD ne savait pas que ses écritures avaient échoué. Pas drôle.
la source
Réponses:
fsync()
retourne-EIO
si le noyau a perdu une écriture(Remarque: la première partie fait référence à des noyaux plus anciens; mis à jour ci-dessous pour refléter les noyaux modernes)
Il semble que l' écriture de tampon asynchrone en cas d'
end_buffer_async_write(...)
échecs définisse un-EIO
indicateur sur la page de tampon sale ayant échoué pour le fichier :qui est ensuite détectée par
wait_on_page_writeback_range(...)
comme demandé pardo_sync_mapping_range(...)
comme demandé parsys_sync_file_range(...)
comme demandé parsys_sync_file_range2(...)
la mise en œuvre de l'appel de la bibliothèque Cfsync()
.Mais une seule fois!
Ce commentaire sur
sys_sync_file_range
suggère que lorsque
fsync()
retourne-EIO
ou (non documenté dans la page de manuel)-ENOSPC
, il effacera l'état d'erreur afin qu'un suivantfsync()
signalera le succès même si les pages n'ont jamais été écrites.Assez sûr
wait_on_page_writeback_range(...)
efface les bits d'erreur lorsqu'il les teste :Donc, si l'application s'attend à pouvoir réessayer
fsync()
jusqu'à ce qu'elle réussisse et qu'elle sache que les données sont sur disque, c'est terriblement faux.Je suis presque sûr que c'est la source de la corruption de données que j'ai trouvée dans le SGBD. Il réessaye
fsync()
et pense que tout ira bien quand il réussira.Est-ce permis?
Les documents POSIX / SuS sur
fsync()
ne le spécifient pas vraiment de toute façon:La page de manuel de Linux
fsync()
ne dit rien sur ce qui se passe en cas d'échec.Il semble donc que la signification des
fsync()
erreurs est "je ne sais pas ce qui est arrivé à vos écritures, cela a peut-être fonctionné ou non, mieux vaut réessayer pour être sûr".Noyaux plus récents
Sur 4.9
end_buffer_async_write
ensembles-EIO
sur la page, juste viamapping_set_error
.Du côté de la synchronisation, je pense que c'est similaire, même si la structure est maintenant assez complexe à suivre.
filemap_check_errors
en faitmm/filemap.c
maintenant:qui a à peu près le même effet. Les vérifications d'erreurs semblent toutes passer par
filemap_check_errors
ce qui fait un test et effacement:J'utilise
btrfs
sur mon ordinateur portable, mais lorsque je crée unext4
bouclage pour tester/mnt/tmp
et configurer une sonde de perf dessus:Je trouve la pile d'appels suivante dans
perf report -T
:Une lecture directe suggère que oui, les noyaux modernes se comportent de la même manière.
Cela semble signifier que si
fsync()
(ou vraisemblablementwrite()
ouclose()
) le rendement-EIO
, le fichier est dans un état indéfini entre lors de la dernière avec succèsfsync()
d ouclose()
d , cela et son dernierwrite()
état dix.Tester
J'ai implémenté un cas de test pour démontrer ce comportement .
Implications
Un SGBD peut faire face à cela en entrant la récupération après incident. Comment diable une application utilisateur normale est-elle censée faire face à cela? La
fsync()
page de manuel ne donne aucun avertissement que cela signifie "fsync-if-you-feel-like-it" et je m'attends à ce que beaucoup d'applications ne supportent pas bien ce comportement.Rapports de bogues
Lectures complémentaires
lwn.net en a parlé dans l'article "Amélioration de la gestion des erreurs de couche de bloc" .
Fil de discussion de la liste de diffusion postgresql.org .
la source
errno
est complètement une construction de la bibliothèque C de l'espace utilisateur. Il est courant d'ignorer les différences de valeur de retour entre les appels système et la bibliothèque C comme ceci (comme le fait Craig Ringer, ci-dessus), car la valeur de retour d'erreur identifie de manière fiable à laquelle (appel système ou fonction de bibliothèque C) fait référence: "-1
avecerrno==EIO
"fait référence à une fonction de bibliothèque C, tandis que"-EIO
"fait référence à un appel système. Enfin, les pages de manuel Linux en ligne sont la référence la plus à jour pour les pages de manuel Linux.fsync()
/fdatasync()
lorsque la taille de la transaction est un fichier complet; en utilisantmmap()
/msync()
lorsque la taille de la transaction est un enregistrement aligné sur la page; et en utilisant I de bas niveau / O,fdatasync()
et plusieurs descripteurs de fichiers simultanés (un descripteur et un thread par transaction) dans le même fichier sinon " . Les verrous de description de fichier ouverts spécifiques à Linux (fcntl()
,F_OFD_
) sont très utiles avec le dernier.Je ne suis pas d'accord.
write
peut retourner sans erreur si l'écriture est simplement mise en file d'attente, mais l'erreur sera signalée lors de l'opération suivante qui nécessitera l'écriture réelle sur le disque, c'est-à-dire au suivantfsync
, éventuellement lors d'une écriture suivante si le système décide de vider le cache et à moins lors de la dernière fermeture de fichier.C'est la raison pour laquelle il est essentiel pour l'application de tester la valeur de retour de close pour détecter d'éventuelles erreurs d'écriture.
Si vous avez vraiment besoin de pouvoir faire un traitement intelligent des erreurs, vous devez supposer que tout ce qui a été écrit depuis le dernier succès a
fsync
peut- être échoué et que dans tout cela au moins quelque chose a échoué.la source
fsync()
ouclose()
du fichier s'il obtient un-EIO
fromwrite()
,fsync()
ouclose()
. Eh bien, c'est amusant.write
(2) fournit moins que ce que vous attendez. La page de manuel est très ouverte sur la sémantique d'unwrite()
appel réussi :Nous pouvons conclure qu'un succès
write()
signifie simplement que les données ont atteint les capacités de mise en mémoire tampon du noyau. Si la persistance du tampon échoue, un accès ultérieur au descripteur de fichier renverra le code d'erreur. En dernier recours, cela peut êtreclose()
. La page de manuel de l'close
appel système (2) contient la phrase suivante:Si votre application a besoin de conserver des données écrites, elle doit utiliser
fsync
/fsyncdata
régulièrement:la source
fsync()
c'est nécessaire. Mais dans le cas spécifique où le noyau perd les pages en raison d'une erreur d' E / S vafsync()
échouer? Dans quelles circonstances peut-il réussir ensuite?fsync()
retours-EIO
sur les problèmes d'E / S (à quoi cela servirait-il autrement?). Ainsi, la base de données sait qu'une partie d'une écriture précédente a échoué et pourrait passer en mode de récupération. N'est-ce pas ce que tu veux? Quelle est la motivation de votre dernière question? Voulez-vous savoir quelle écriture a échoué ou récupérer le descripteur de fichier pour une utilisation ultérieure?fsync()
possible de revenir-EIO
là où il est sûr de réessayer, et s'il est possible de faire la différence.-EIO
. Si chaque descripteur de fichier n'est utilisé que par un thread à la fois, ce thread pourrait revenir au dernierfsync()
et refaire leswrite()
appels. Mais quand même, si ceswrite()
s n'écrit qu'une partie d'un secteur, la partie non modifiée peut toujours être corrompue.Utilisez l'indicateur O_SYNC lorsque vous ouvrez le fichier. Il garantit que les données sont écrites sur le disque.
Si cela ne vous satisfait pas, il n'y aura rien.
la source
O_SYNC
est un cauchemar pour la performance. Cela signifie que l'application ne peut rien faire d' autre pendant les E / S disque, sauf si elle génère des threads d'E / S. Autant dire que l'interface d'E / S tamponnée n'est pas sûre et que tout le monde devrait utiliser AIO. Les écritures perdues silencieusement ne peuvent pas être acceptées dans les E / S tamponnées?O_DATASYNC
n'est que légèrement meilleur à cet égard)Vérifiez la valeur de retour de close. close peut échouer tandis que les écritures tamponnées semblent réussir.
la source
open()
ING et ,close()
du fichier toutes les quelques secondes. c'est pourquoi nous avonsfsync()
...