Journal Don’t fear the fsync!

Posté par (page perso) .
Tags : aucun
12
17
mar.
2009
Je ne résiste pas à l'envie de vous partager cet excellent post de Teodore T'so sur fsync, l'atomicité, les ordinateurs portables et ext4, qui fait suite à la récente polémique à ce sujet.

C'est un excellent article qui m'a permis d'apprendre beaucoup de choses, et dont la lecture peut à mon avis être intéressante pour tous ceux qui développe des applications qui utilisent des fichiers :)

Seul bémol, l'article est en anglais :-/

Le lien: http://thunk.org/tytso/blog/2009/03/15/dont-fear-the-fsync/

Bonne lecture!
  • # Résumé à la louche

    Posté par (page perso) . Évalué à 4.

    "Les applications devraient utiliser la fonction fsync() pour être sûres que les données sont écrites physiquement sur le disque. Par contre, elles ne doivent pas le faire trop souvent, ou le système peut devenir lent.

    Il note que Firefox 3 écrit 2,5Mo sur le disque à chaque clic, pour préserver l'historique en cas de crash. Cela fait bien trop à son vis.

    Enfin, il pense qu'il faudrait pouvoir retarder l'application effective de fsync() si on est sur un portable et qu'on a demandé de ne réveiller le disque que toutes les 5 minutes : dès lors qu'on indique au noyau qu'on est prêt à perdre 5 minutes de travail, cela doit s'appliquer à toute écriture."

    Pour ma part, cela m'a permis de comprendre pourquoi Firefox réveille tout le temps le disque dur en LAPTOP_MODE... je vais donc prendre plutôt Konqueror ;-)

    ⚓ À g'Auch TOUTE! http://afdgauch.online.fr

    • [^] # Re: Résumé à la louche

      Posté par (page perso) . Évalué à 2.

      Petite correction : le problème avec fsync dans Firefox 3.0 n'est pas originellement à cause de Firefox 3, mais à cause de sqlite. Tu auras le problème avec toute application qui embarque sqlite et qui fait des requêtes sql en écriture de manière intensive.

      Maintenant, dans Firefox 3.5 (anciennement 3.1), cela a été plus ou moins corrigé. Ils ont essayé de faire le moins de requêtes possibles, et de regrouper les requetes quand c'était possible, afin de moins solliciter sqlite (et donc moins faire de fsync).
      • [^] # Commentaire supprimé

        Posté par . Évalué à 5.

        Ce commentaire a été supprimé par l'équipe de modération.

      • [^] # Re: Résumé à la louche

        Posté par . Évalué à 5.

        Petite correction : le problème avec fsync dans Firefox 3.0 n'est pas originellement à cause de Firefox 3, mais à cause de sqlite.

        Non non, c'est bien la faute de Firefox qui a décidé d'utiliser une base données transactionnelle ACID-compliant pour sauver des données parfaitement accessoires (historique de navigation). Sqlite, lui, fait le boulot pour lequel il a été conçu.

        Ceci dit, si Firefox embarque son propre sqlite lié en static (c'est une hypothèse que je fais), ils auraient pu patcher le source pour enlever les fsync().
        • [^] # Re: Résumé à la louche

          Posté par (page perso) . Évalué à 4.

          >pour sauver des données parfaitement accessoires

          peut être de ton point de vue, pas du mien. J'aime bien que mon navigateur me retrouve l'url d'un site visité en tapant seulement un mot ou deux (ou une syllabe). ;-)

          >si Firefox embarque son propre sqlite lié en static (c'est une hypothèse que je fais), ils auraient pu patcher le source pour enlever les fsync()

          la version vanilla embarque sqlite en static (les sources de sqlite sont incluses dans les sources de mozilla), mais il y a une option de compil qui permet d'utiliser en dynamique (ce que font les distrib).

          Pas de patch spécifique sur sqlite. Toutes les améliorations faites par les dev de Firefox ont été proposées en upstream dans sqlite et ont été acceptées.

          Pour ce qui est d'enlever fsync, ça ne serait pas top pour certaines données qui peuvent être jugées critiques. la base sqlite ne sert pas que pour l'historique, mais pour beaucoup d'autres choses dans Firefox ou ses extensions (mots de passe, données offline des applis web, cookies...).
          • [^] # Re: Résumé à la louche

            Posté par . Évalué à 4.

            peut être de ton point de vue, pas du mien. J'aime bien que mon navigateur me retrouve l'url d'un site visité en tapant seulement un mot ou deux (ou une syllabe). ;-)
            Je suppose qu'il voulait dire que dans le contexte d'un crash, ce n'est pas grave de perdre l'historique des 30 dernières secondes de ta navigation ...
            • [^] # Re: Résumé à la louche

              Posté par (page perso) . Évalué à 4.

              Ben en même temps, quand firefox crash, la première chose qu'on a envie, c'est de retrouver sa session telle quelle 1/2 seconde avant le crash, donc bon...
              • [^] # Re: Résumé à la louche

                Posté par . Évalué à 4.

                <citeBen en même temps, quand firefox crash, la première chose qu'on a envie, c'est de retrouver sa session telle quelle 1/2 seconde avant le crash, donc bon...

                Je préfererais qu'il ne crashe pas ...
              • [^] # Re: Résumé à la louche

                Posté par . Évalué à 4.

                Oui, enfin comme l'explique Ted Ts'o, on ne peut pas faire de magie non plus : soit ton disque sync toutes les 5s, mais dans ce cas là bye-bye l'autonomie, soit il ne le fait pas mais alors forcément, on perd le contenu de la RAM ... Bref, tu auras beau vouloir tout ce que tu veux, il y a des fois où c'est impossible ...
              • [^] # Re: Résumé à la louche

                Posté par (page perso) . Évalué à 2.

                fsync, c'est pour assurer la cohérence suite à un crash du système.

                pour garantir que les données sont « sorties du processus », c'est juste fflush, c'est beaucoup moins cher ...
    • [^] # Re: Résumé à la louche

      Posté par (page perso) . Évalué à 4.

      On peut pas faire des écritures incrémentales alors ?
      Un fichier "base", et plusieurs fichiers qui contiennent des changements d'états successifs. Et une fois de temps en temps, on recrée un fichier "base" en relisant les petits, et toutes les deux créations de base, on fait une rotation (qui permet donc de pas tout perdre si jamais une action non atomique est interrompue brusquement pendant le renomage ou la copie).
  • # Le post de Matthew Garett est aussi très intéressant

    Posté par . Évalué à 6.

    Pour apporter un autre point de vue, voir http://mjg59.livejournal.com/108257.html

    Sa position est de dire que le pattern qui consiste à faire un open("foo.tmp"), write(), close() and then rename("foo.tmp", "foo") devrait correctement fonctionner, c'est à dire que l'on s'attend à avoir un fichier foo qui a soit l'ancienne version, soit la nouvelle version étant donné que rename() est atomique. Mais en aucun cas on ne devrait avoir de perte de données en cas de crash.
    Le fait de préconiser un fsync() est une fausse solution d'après lui, car on ne cherche pas à garantir que le fichier est écrit, on cherche à garantir que le fichier sera dans un état correct et on n'a pas besoin de sans arrêt synchroniser sur le disque pour cela.


    Étienne
    • [^] # Re: Le post de Matthew Garett est aussi très intéressant

      Posté par . Évalué à 8.

      Et voici le point de vue de Matt Mackall, développeur de Mercurial :

      « fsync is a) immensely slow (can cause system-wide filesystem stalls for
      many seconds) and b) doesn't guarantee file integrity on modern hardware
      (many drives only guarantee that data has made it to their onboard
      caches).

      Because fsync has such a negative effect on average I/O bandwidth, it
      can actually increase the odds of hardware corruption by widening the
      corruption window. It's not at all clear that fsync is desirable. »

      http://www.selenic.com/pipermail/mercurial/2009-March/024582(...)
      • [^] # Re: Le post de Matthew Garett est aussi très intéressant

        Posté par . Évalué à 3.

        L'un des argument de T'so est le suivant:
        Si on écrit 300To de données sur le disque, il faut bien s'attendre à un moment à ce que le disque écrive ces 300To. Ça, ça prend du temps. Si fsync prend du temps, c'est qu'il y avait beaucoup à écrire, c'est tout.

        Je trouve cet argument plein de bon sens.
        • [^] # Re: Le post de Matthew Garett est aussi très intéressant

          Posté par . Évalué à 3.

          Son argument ne tient pas la route car il y a des cas d'utilisation pour lesquels fsync(), qui attend la complétion effective des écritures, est overkill -- on a besoin dans certains cas uniquement d'une sémantique de barrière avec visibilité sur les plateaux d'une sous séquence de celle écrite via les syscall...

          Imaginons qu'on écrive une "conf" de 1 Mo chaque seconde remplaçant la précédente, que le besoin alors que le système tourne est de pouvoir lire la plus récente à chaque instant, et que le besoin en cas de crash est simplement d'en récupérer une pas trop vielle (et surtout une ne consistant pas non plus en 0 octets pour cause de perte de données...) : si le FS se contente de garantir ce que Posix dit un fsync() est obligatoire à chaque fois, on va donc écrire 1 Mo par seconde. Alors que si un pattern ultra courant à la open() write() close() rename() induit implicitement une barrière entre l'écriture des data et des metadata, le FS peut se contenter de commiter 1 fois par minute (ou plus ou moins selon la configuration) et tout le monde est content.

          C'est une garantie de la part du FS qui va au delà de Posix, certes. Une appli utilisant ça serait non portable ; mais on peut être malin et la rendre néanmoins portable grâce aux fonction Posix :

          long fpathconf(int fildes, int name);
          long pathconf(const char *path, int name);

          (vu dans un commentaire de http://lwn.net/Articles/323752/ , article encore réservé aux abonnés à ce jour)

          Grâce à ces fonctions, il suffit pour écrire une application portable mais qui sait profiter des systèmes biens conçus, de vérifier la présence d'une capacité publiée par le FS indiquant qu'il garanti que les séquences open() write() close() rename() sont implicitement "sûres". Si la capacité n'est pas présente, l'application rajoute simplement un fsync().

          Ce serait la bonne chose à faire. Bien meilleure que de demander à tout le monde de mettre tout le temps des fsync(), quitte à introduire des threads rien que pour ça et à écrire 42x plus sur le disque que nécessaire...
    • [^] # Re: Le post de Matthew Garett est aussi très intéressant

      Posté par . Évalué à 3.

      Ce pattern est valable uniquement si le fichier n'est pas trop gros. Ce qui n'est pas le cas pour une base de donné.

      Je me demande d'ailleurs si on peut garantir l'existence partielle d'un fichier. (si on le voit comme une suite d'enregistrement) Il faudrait avoir une modification atomique d'un fichier. Par contre, cela veut dire beaucoup de choses sur la manière de fonctionner des disques durs.

      "La première sécurité est la liberté"

      • [^] # Re: Le post de Matthew Garett est aussi très intéressant

        Posté par . Évalué à 3.

        En quoi la taille du fichier change-t-elle la validité de ce processus ? Le renommage ne dépend pas de la taille du fichier.
        • [^] # Re: Le post de Matthew Garett est aussi très intéressant

          Posté par (page perso) . Évalué à 4.

          Y'a pas qu'un renommage, y'a un "write()" aussi. Si le fichier est gros, ça va être hyper lent. Enfin heureusement qu'une bonne base de données ne ré-écrit pas le fichier à chaque écriture ;-).
          • [^] # Re: Le post de Matthew Garett est aussi très intéressant

            Posté par . Évalué à 4.

            Oui mais si j'ai bien compris ce qu'on veut c'est avoir un fichier foo correct (soit en ancienne version, soit en nouvelle version). On évite le problème de réécrire directement sur l'ancien fichier, car dans ce cas si jamais ya un problème pendant l'écriture on perd tout).
            Mais je n'ai pas forcément bien compris à quel problème on voulait s'attaquer ;)
            • [^] # Re: Le post de Matthew Garett est aussi très intéressant

              Posté par . Évalué à 4.

              C'est exactement ça, en pratique c'est surtout utilisé pour les petits fichiers de configuration par exemple, mais ça marche aussi pour de gros fichiers. Comme tu dis l'objectif c'est d'avoir un fichier complet, soit toujours en ancienne version, soit dans la nouvelle version.
      • [^] # Re: Le post de Matthew Garett est aussi très intéressant

        Posté par . Évalué à 5.

        Dans son post, il explique que c'est une utilisation très courante que de passer par de petits fichiers par exemple pour les configurations. Et d'ajouter qu'on est d'accord pour perdre une modification, mais pas pour mettre toute sa conf en vrac.

        Le problème lié à ext4 c'est que les le filesystem ne garantie pas que les modifications seront faite dans l'ordre où elles ont été appelées. Donc le rename peut survenir avant le write() en pratique car les modifications sur les données sont gérées séparément des modifications sur la table des inodes de ce que j'ai pu comprendre. En faisant son rename() à la fin (le rename doit être atomique, en tout cas on n'a pas une fenêtre d'ouverture de 50 secondes) on se dit que si tous le reste a été effectué, alors seulement on fera le rename(), si un plantage a eu lieu avant d'arriver au rename(), on aura au pire un fichier en trop sur le filesystem mais on aura gardé l'ancienne configuration.
        • [^] # Re: Le post de Matthew Garett est aussi très intéressant

          Posté par . Évalué à 4.

          Le problème n'est pas lié à ext4 mais à la norme POSIX comme il l'explique bien dans https://bugs.launchpad.net/ubuntu/+source/linux/+bug/317781/(...)

          So, what is the problem. POSIX fundamentally says that what happens if the system is not shutdown cleanly is undefined. If you want to force things to be stored on disk, you must use fsync() or fdatasync().
          • [^] # Re: Le post de Matthew Garett est aussi très intéressant

            Posté par . Évalué à 2.

            C'est pas pcq la norme POSIX n'interdit pas de faire des choses idiotes qu'il _faut_ faire de telles choses idiotes.

            Un rename() étant une opération atomique, cela n'a quasiment aucun sens de ne pas garantir que le pattern open() write() close() rename() soit atomique quant à la transition entre les anciennes données et les nouvelles, et ce même en cas de crash. J'ose imaginer que les disques courants (ATA, SATA, SCSI, ...) ont des mécanisme de commandes de barrières qui permettrait d'apporter "facilement" de telles garanties - si ce n'est pas le cas il est urgent que leur comités normalisateurs respectifs se penchent sur le sujet....

            fsync() est beaucoup plus fort qu'une simple barrière. L'utiliser dans tous les cas serait désastreux. C'est étonnant que ça ne paraisse pas évident à un kernel hacker.
            • [^] # Re: Le post de Matthew Garett est aussi très intéressant

              Posté par . Évalué à 2.

              je sais pas c'est bien trop bas niveau pour moi cette discussion. Ce que j'ai retenu c'est que comme indiqué par une autre personne j'ai enfn compris pourquoi XFS était pas top du tout (pour mon utilisation) et que ext4 sans les patchs récent auraient de même.
        • [^] # Re: Le post de Matthew Garett est aussi très intéressant

          Posté par (page perso) . Évalué à 3.

          Le problème lié à ext4
          Ext4 n'est pas le seul à appliquer de cette façon cette partie de POSIX, XFS fait pareil et je perd la moitié ma conf KDE à chaque coupure électrique, ext3 doit également avoir le même comportement si le journal est en mode « writeback » et non « ordered »

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.