[PYTHON] Comment écrire un fichier auquel vous devez faire attention dans toutes les langues

Ce que je veux dire

Lors de l'écriture de fichiers importants, il est nécessaire de prendre en compte l'arrêt inattendu du système d'exploitation, etc. Si vous ne savez pas comment faire, des fichiers semi-finis et des fichiers vides seront générés, ce qui sera fatal au démarrage du système et dans le système lié.

Le langage C / Java / Python / JavaScript (node.js) est donné à titre d'exemple, mais il est nécessaire de prendre des mesures dans presque tous les langages.

Contexte

Un problème fatal s'est produit dans lequel le logiciel en production n'a pas démarré.

Lorsque j'ai collecté et analysé les journaux et les fichiers de configuration, les fichiers de configuration étaient complètement corrompus.

Le fichier de configuration est lu au démarrage, mais peut être écrit si nécessaire. En suivant le code, je remarque qu'il peut être écrit à mi-chemin s'il est interrompu de force pendant le processus d'écriture.

L'alimentation est coupée lorsqu'elle est épuisée et la synchronisation peut s'être miraculeusement chevauchée.

Première chose que j'ai essayée

Si vous l'écrivez directement dans le fichier de configuration, vous pouvez obtenir un état semi-fini (par exemple, lorsque vous voulez écrire 10 caractères, vous n'avez toujours qu'un caractère), même si cela prend peu de temps. Une fois que vous avez fini d'écrire dans config.yml.tmp, puis que vous le renommez en config.yml, tout peut être mis à jour ou non ou de manière atomique!

Résultat 1

Il sera publié avec entière satisfaction, mais il y a un problème qu'il ne recommencera pas. Cette fois, le fichier de configuration est de 0 Ko. Si l'écriture du fichier tmp échoue, il ne sera pas renommé, il ne devrait donc jamais être de 0 Ko sur le flux. La couleur est explicite et la fermeture est soignée.

J'ai trouvé un élément en feuilletant le livre O'Reilly sur la programmation système Linux que j'avais sous la main.

Quoi quoi··? Ecrire le tampon sale sur le disque ...?

À ce stade, je remarque enfin l'erreur et la solution. Non, je l'ai appris en tant que connaissance, mais cela n'est pas sorti.

Tampon sale

Même si le processus d'écriture est programmé, le fichier n'est pas écrit immédiatement et est temporairement sauvegardé au format d'un tampon sale.

Le processus d'exportation de disque est extrêmement lent, donc si vous exportez à chaque fois, le programme sera foiré (100 fois ou plus ou à ce niveau). Pour éviter cela, c'est une technique qui a toujours été adoptée dans les systèmes de fichiers récents. Avec cette approche, le processus sous-traite le traitement d'écriture lente au système d'exploitation et se permet de passer à autre chose. Le moment où le système d'exploitation écrit sur le disque dépend du système de fichiers avec lequel le système d'exploitation interagit. Dans certains cas, cela peut prendre 10 secondes ou plus, et il y a plus que suffisamment de temps pour corrompre le fichier.

Si le système d'exploitation tombe en panne en raison d'une panne de courant inattendue dans cet état, un fichier endommagé ou un fichier vide sera créé même s'il est vidé ou fermé dans le flux du programme. En d'autres termes, config.yml.tmp lui-même était à moitié terminé, donc même s'il était renommé config.yml, il était à moitié terminé.

L'appel de fsync () écrit immédiatement le tampon sale sur le disque, ce qui est terminé avant de passer à l'étape suivante.

fsync () et fdatasync ()

Sous Linux, un fichier comprend les deux types de données suivants.

--Métadonnées appelées inode

L'inode est la date et l'heure de la modification du fichier, ou les données affichées lors de sa gestion dans un répertoire.

fsync () écrit les deux et fdatasync () écrit uniquement les données du fichier lui-même.

Si le fichier n'est pas mis à jour très souvent ou que vous n'avez pas à vous soucier des performances, vous pouvez utiliser fsync ().

Utilisez fdatasync () si l'inœud (c'est-à-dire les métadonnées telles que l'heure de la dernière mise à jour) ne doit pas être mis à jour dans le pire des cas, ou s'il est mis à jour fréquemment et que les performances sont un problème.

Contre-mesures

Lors de la génération de fichiers importants, vous pouvez écrire de force sur le disque afin d'être assuré qu'il y aura un arrêt inattendu. Sous Linux, l'arrêt en suivant la procédure habituelle n'est pas un problème.

Voici quelques exemples spécifiques de code. Une certaine gestion des erreurs est omise.

sample.c


int main() {
    const char* tmp_file_path = "./fsync_test.txt";
    FILE* fp= fopen(tmp_file_path , "w");
    int fd = fileno(fp);
    fputs("fsync() test\n", fp);
    fflush(fp);

    //C'est le point!!
    int fsync_ret = fsync(fd);

    fclose(fp);
    return fsync_ret;
}

FsyncTest.java


import java.io.File;
import java.io.FileOutputStream;

public class FsyncTest {
    public static void main(String[] args) throws Exception {
        File file = new File("./fsync.txt");
        try (FileOutputStream output = new FileOutputStream(file);) {
            output.write("Fsync() test\n".getBytes("UTF-8"));

            //C'est le point!!
            output.getFD().sync();
        }
    }
}

sample.py


import os

with open('./fsync_test.txt', 'w') as f:
    f.write('fsync() test')
    f.flush() #Tampon sale avec juste ça

    #C'est le point!!
    os.fsync(f.fileno())

fsync.js


const http = require('http');

const server = http.createServer((request, response) => {
    const fs = require('fs');
    fs.open('./fsync_test.txt', 'w', (err, fd) => {
        fs.write(fd, 'fsync() test\n', () => {
            
            //C'est le point!!
            fs.fsyncSync(fd);

            response.writeHead(200, {'Content-Type': 'text/plain'})
            response.end('Write success\n');
            fs.close(fd, ()=>{});
        })
    })
})

server.listen(9000);

Résultat 2

Le fichier n'est plus corrompu.

À propos, le temps d'être un tampon sale est assez long. Selon le système de fichiers, cela peut prendre environ 30 secondes. Avant la contre-mesure, dans Windows 7 à portée de main, j'ai écrit un fichier, l'ai ouvert avec un éditeur de texte, confirmé que le contenu avait été écrit, débranché l'alimentation après 15 secondes et le fichier était cassé après le démarrage Était là. Quand je l'ai essayé dans l'environnement CentOS 6, le résultat était presque le même.

Après les mesures, il ne s'est pas cassé même immédiatement après l'écriture.

Conclusion

Quel que soit le langage, fsync () ou un traitement équivalent est essentiel lors de la génération de fichiers importants. Cependant, cela force des exportations de disques synchrones extrêmement lentes, ce qui peut avoir un impact sérieux sur les performances dans certaines conditions. C'est mal de le faire dans les nuages sombres parce que c'est sûr.

Supplément

Problèmes liés

Vous avez fourni un lien dans les commentaires qui est très pertinent pour cet article.

défis firefox

https://www.atmarkit.co.jp/flinux/rensai/watch2009/watch05a.html

C'est un problème causé par la lenteur de fsync (). C'est un article selon lequel l'utilisation de fdatasync () accélère d'autant la mise à jour de l'inode.

Défis PostgreSQL

https://masahikosawada.github.io/2019/02/17/PostgreSQL-fsync-issue/ Le problème est que si fsync () échoue, vous ne pouvez plus appeler fsync (). Dans PostgreSQL, si fsync () échoue, planter la base de données et à partir du journal des transactions (WAL) Il semble qu'il ait fait une correction pour le restaurer.

Je me suis demandé si cela échouerait en premier lieu, mais il semble que cela se produise facilement dans SAN et NFS. Si vous écrivez dans un système général, vous devez conserver les données à write () et recommencer à partir de write ().

Assistance sur Windows

fsync () est une fonction de bibliothèque Linux et ne peut pas être utilisée sous Windows. Sous Windows, cela peut être réalisé en utilisant l'API suivante.

BOOL FlushFileBuffers(HANDLE hFile);

En outre, dans Windows 7, cela peut être réalisé en définissant l'ensemble du système d'exploitation. [1] Ouvrez le panneau de configuration [2] Ouvrez le Gestionnaire de périphériques [3] Sélectionnez un disque dans le lecteur de disque et ouvrez les propriétés [4] Dans l'onglet Stratégie, décochez "Activer le cache d'écriture du périphérique"

Notez que cette méthode ralentira toutes les opérations, pas seulement l'application.

Recommended Posts

Comment écrire un fichier auquel vous devez faire attention dans toutes les langues
Vous devez faire attention aux commandes que vous utilisez quotidiennement dans l'environnement de production.
Résumé de l'écriture des fichiers .proto utilisés dans gRPC
Comment écrire sobrement avec des pandas
Comment lire des fichiers CSV avec Pandas
Comment écrire ce processus en Perl?
Comment écrire Ruby to_s en Python
Comment vérifier / extraire des fichiers dans un package RPM
Comment obtenir les fichiers dans le dossier [Python]
Comment écrire un document tuple nommé en 2020
Comment écrire une concaténation de chaînes sur plusieurs lignes en Python
Comment charger des fichiers dans Google Drive avec Google Colaboratory
[Python] Comment écrire une docstring conforme à PEP8
Comment utiliser des variables dans les fichiers de définition d'unité systemd
Comment télécharger des fichiers depuis Selenium of Python dans Chrome
Comment télécharger des fichiers dans la vue de classe générique Django
Comment référencer des fichiers statiques dans un projet Django
2 façons de lire tous les fichiers csv dans un dossier
Comment configurer un serveur SMTP simple qui peut être testé localement en Python
[Python3] Code qui peut être utilisé lorsque vous souhaitez redimensionner des images dossier par dossier
Comment écrire des conseils de type pour les variables qui sont affectées plusieurs fois sur une ligne
Résumé du savoir-faire en matière de mise en œuvre de Python et des conseils auxquels les ingénieurs en IA doivent faire attention
À propos de __all__ en python
Comment tester cette exception est déclenchée dans python unittest
Comment écrire une validation personnalisée dans Django REST Framework
Une histoire sur la façon de spécifier un chemin relatif en python.
traitement python3 qui semble utilisable dans paiza
Après tout, combien dois-je écrire un article sur Qiita?
Comment importer des fichiers où vous le souhaitez en Python
Comment rédiger un test de traitement utilisant BigQuery
Conversion par lots de tous les fichiers xlsx du dossier en fichiers CSV
Comment obtenir toutes les clés et valeurs du dictionnaire
Comment écrire une classe méta qui prend en charge à la fois python2 et python3
Comment doi peut-il être utile lorsque vous demandez comment écrire du code?
Une commande pour lister tous les fichiers par ordre de nom de fichier
Comment tromper et utiliser une terrible bibliothèque qui est censée être conservée globalement dans flask
Si vous écrivez un test piloté par table go en python, il peut être préférable d'utiliser subTest
Comment résoudre le problème qui se passe mal à chaque fois que vous mettez sous tension Linux