Problèmes de téléchargement après le changement de centre de données DO Spaces

Bonjour à tous,

Après avoir recherché dans le forum de mon mieux sans trouver de réponse satisfaisante, je sollicite l’assistance pour une situation étrange survenue suite à un récent changement de centre de données chez Digital Ocean.

En effet, nous avions stocké tous nos fichiers sur un bucket Digital Ocean Spaces dans le centre de données ams3. Après deux gros problèmes matériels et une interruption de service consécutive survenue en un peu plus d’un mois, nous avons décidé, le week-end dernier, de déplacer tous nos fichiers vers le centre de données fra1.

Voici les étapes que j’ai suivies :

  1. En préparation du transfert, j’ai téléchargé tous les fichiers présents sur ams3 (les 3 répertoires classiques : originals, optimized et tombstone) vers le nouveau bucket sur fra1 en utilisant s3cmd.
  2. Je suis allé dans les paramètres du forum et j’ai défini le nouveau point de terminaison (endpoint) pour les pièces jointes, le CDN et le bucket de sauvegarde.
  3. J’ai lancé un rebake complet des publications, en espérant que cela résoudrait tout d’un coup.

Malheureusement, ce n’était pas le cas. La majorité des pièces jointes ont été « portées » correctement, mais quelques centaines ne l’ont pas été. Je ne sais pas exactement ce qui s’est passé, mais ces pièces jointes manquantes ont été déplacées dans le répertoire tombstone.

J’ai pensé que lancer la tâche rake rake uploads:recover_from_tombstone réglerait le problème, mais non. Les fichiers sont détectés, mais à la fin de la tâche, aucune pièce jointe n’est récupérée et les images restent invisibles dans les publications.

J’ai creusé un peu plus et j’ai découvert que l’exécution de UploadRecovery.new(dry_run: true).recover (trouvé en explorant la section méta) dans la console Rails me fournissait des informations précieuses, telles que l’URL de la publication ainsi que l’URL courte ou longue de l’image problématique.

Pour les URLs retournées sous forme courte, j’ai donc écrit un petit script Python pour « traduire » le nom de fichier de téléchargement court en sa forme longue, afin de pouvoir vérifier la présence du fichier dans le bucket.
Je l’ai fait, et je peux confirmer que tous les fichiers manquants sont bien là, aussi bien dans le nouveau bucket que dans l’ancien. Une partie des téléchargements manquants se trouvait dans le répertoire tombstone, comme prévu, mais d’autres se trouvent étrangement toujours dans le répertoire original. Les fichiers ne sont pas corrompus. Si j’y accède via une URL, ils s’ouvrent correctement dans les deux centres de données, et si je les télécharge localement sur ma machine Linux, je peux les ouvrir sans erreur.

D’une manière ou d’une autre, le processus de récupération des téléchargements échoue à les prendre en charge et à corriger ce qui est défectueux dans la base de données. :man_shrugging:

Mes questions sont donc les suivantes :

  • Y a-t-il un moyen de comprendre pourquoi, même si les fichiers de téléchargement sont dans tombstone (ou dans original), la tâche rake échoue à les récupérer ?
  • Quelle serait la bonne série d’étapes pour s’assurer que, en cas de changement de bucket ou même de transition de DO vers un autre environnement compatible AWS, toutes les pièces jointes sont déplacées et préparées correctement pour le basculement ? Plus généralement, que faut-il faire, étape par étape, dans un tel cas ? Clairement, un simple rebake ne suffit pas. :confused:
  • Que fait la tâche posts:invalidate_broken_images ? Je veux dire, que signifie invalidate (invalider) ?

Merci d’avance. Je me bats avec ce problème depuis une semaine et j’ai vraiment besoin de le résoudre, sinon je vais devenir fou :smiley: :stuck_out_tongue:

PS : La suggestion de recharger manuellement les 800+ pièces jointes n’est pas considérée comme une réponse valable. Il doit y avoir une raison algorithmique… :laugh:

2 « J'aime »

Je pense que vous avez oublié un DbHelper.remap('oldbucketurl', 'newbucketurl') entre les étapes 2 et 3.

4 « J'aime »

Salut @falco, merci pour ta réponse.
Oui, au début, j’avais oublié.
Je l’ai exécuté après l’avoir trouvé en fouillant ici sur meta. :confused: et cela a aidé à récupérer certains fichiers.
Au passage, j’ai fait un rebake complet après l’avoir exécuté.

Que puis-je essayer d’autre ?

1 « J'aime »

Donc, j’ai peut-être une idée de ce qui se passe ici.
Je n’avais pas pensé à mentionner un fait lié à la sortie de la tâche Rake rake uploads:recover_from_tombstone, qui pourrait donner un indice intéressant.

Il semble que la tâche trouve bien les fichiers de téléchargement dans la tombe, mais elle me lance un avertissement concernant quelque chose (le nom complet du fichier de téléchargement) qui serait incorrect. Voici un exemple :

Avertissement : /t/i-miei-modellini-volanti/28272/212 avait un 487b613752a0c338646fecc942512e5de9afeb3f incorrect qui devrait être c87c4f08d1a9aac3f43d19722cfd5a94f2544272. L'exécution de 'rake uploads:fix_relative_upload_links' peut corriger cela.

En exécutant une commande find sur ma copie locale des répertoires de téléchargement, il s’avère que j’ai bien un fichier nommé 487b613752a0c338646fecc942512e5de9afeb3f.jpeg.

Le lien court associé à ce téléchargement spécifique est upload://alcIv6jVlmjiEOEBh8fNDJyRms7.jpeg, et en appliquant l’algorithme base62 qui calcule le nom de fichier complet correspondant, il s’avère que la valeur est 487b613752a0c338646fecc942512e5de9afeb3f, exactement le nom de fichier que la tâche Rake recover_from_tombstone signale comme étant erroné. :thinking:

Pourquoi l’outil prétend-il qu’il est incorrect et qu’il devrait être c87c4f08d1a9aac3f43d19722cfd5a94f2544272 à la place ?

Par précaution, j’ai exécuté la tâche rake uploads:fix_relative_upload_links plusieurs fois, puis j’ai relancé rake uploads:recover_from_tombstone, mais rien ne semble changer.

Édition :
En recherchant 487b613752a0c338646fecc942512e5de9afeb3f dans une sauvegarde de base de données que j’ai effectuée avant de changer de bac, je constate que l’enregistrement dans la table des téléchargements correspondant à cette image affichait exactement ce nom de fichier hexadécimal. Je ne comprends donc toujours pas pourquoi la tâche Rake se plaint de cela.

image

C’est l’un des malentendus les plus anciens sur Meta.
Vous n’avez pas besoin de faire un rebaking après un remappage bien ciblé.

2 « J'aime »

Tu as peut-être raison, mais le problème, c’est qu’il est difficile de savoir exactement quoi faire et quoi éviter dans ces cas-là sans un tutoriel ou un guide de la part des développeurs.
On a toujours l’impression d’avoir dû faire quelque chose de plus ou dans un ordre différent, comme si on devait découvrir une recette qui fonctionne en distillant des dizaines de posts écrits au cours des 3 ou 4 dernières années. :stuck_out_tongue:
Le rebaking semble être la panacée pour beaucoup de choses et sans danger pour les posts existants.

C’est une façon compliquée de dire que, vu le nombre de fois où les gens ont buté sur des problèmes de gestion des uploads et autres, un bon guide officiel de la part de l’équipe serait une référence importante. :wink:

1 « J'aime »

Désolé, je dois remettre cette question sur le tapis.

La semaine dernière, j’ai passé du temps à examiner le code des tâches Rake d’upload pour comprendre ce qui se passe sous le capot des tâches recover_from_tombstone et recover. C’est difficile à cause de l’encapsulation des classes, donc je dirais que j’ai surtout échoué.

Ce que j’ai compris (merci @Falco de me corriger si je me trompe), c’est que le nom de fichier sur le disque d’un upload est créé en combinant son SHA1 et son extension d’origine. Ensuite, il est stocké sur le disque ou sur AWS dans un répertoire dont le chemin dépend de la première, et parfois de la deuxième, lettre de son nom, dans des dossiers 1X, 2X, 3X… (je ne comprends pas comment ces dossiers sont déterminés).

Enfin, le SHA1 et le nom de fichier sont stockés, entre autres, dans les enregistrements de la table uploads de PostgreSQL.

Pour revenir à ce qui s’est passé lors de notre changement de centre de données Digital Ocean, voici ce qui s’est produit, selon ma meilleure compréhension :

  1. Nous avons copié tous les fichiers de ams3 vers fra1.
  2. Nous n’avons pas réussi à exécuter DbHelper.remap('oldbucketurl', 'newbucketurl') comme suggéré par @falco, mais il n’était pas clair pour nous que nous devions le faire dans ce cas.
  3. Nous avons lancé un rebake global. À ce stade, des milliers d’images se sont « brisées » et beaucoup ont été déplacées vers le tombstone. Ce n’est pas tout à fait clair pour moi pourquoi.
  4. J’ai réalisé qu’il y avait un problème, j’ai interrompu le rebake en cours et j’ai découvert la commande remap en cherchant ici sur Meta. Nous avons ensuite lancé la tâche DbHelper.remap('oldbucketurl', 'newbucketurl').
  5. Afin de récupérer les images qui avaient été déplacées vers le tombstone à l’étape 3, nous avons lancé rake uploads:recover_from_tombstone, qui en a récupéré certaines, mais en a laissé des centaines d’autres non récupérées, et a affiché des erreurs concernant le SHA1 des fichiers, telles que : Warning /t/eclisse-parziale-di-sole-04-01-2011/14456/50 had an incorrect 3f5a1c136b97aebac4a188432c8e3ab7487f3bca should be ec88ee9eea18f3b8424bfef796345c68582911b5 storing in custom field 'rake uploads:fix_relative_upload_links' can fix this, comme si le fichier avait été modifié et que le SHA1 était donc maintenant différent. La récupération de ces fichiers échoue.

Nous n’avons jamais modifié les fichiers lors de leur déplacement entre les deux centres de données. En utilisant s3cmd, nous les avons littéralement téléchargés localement depuis l’ancien bucket et immédiatement réuploadés dans le nouveau.

Pourquoi le SHA1 calculé par Discourse serait-il différent ?

Serait-il possible de forcer la tâche recover à ignorer la divergence de SHA1 et simplement adapter l’import dans la base de données à ce qui existe, ou de renommer les fichiers existants avec le nouveau SHA1 lors de leur récupération ?

Est-ce que je manque quelque chose d’évident ? Merci à tous pour votre aide.

Donc, juste pour clore ce fil d’une manière qui pourrait être utile à quelqu’un d’autre, voici comment nous avons résolu la situation.

Essentiellement, comme il était impossible de récupérer les pièces jointes manquantes via les diverses tâches de récupération par rake, j’ai écrit un script Ruby (désolé à l’avance, je ne suis absolument pas développeur Ruby ou Rails, donc je parie que le code est inefficace et moche, mais ce n’est pas le sujet ici :P) qui :

  1. Trouve tous les messages contenant la chaîne upload://
  2. Extrait le lien court de chaque pièce jointe et le transforme en son hachage sha1 complet
  3. Interroge la table Uploads
  4. Si une pièce jointe avec ce hachage sha1 est trouvée dans Uploads, cette pièce jointe est ignorée ; sinon, l’URL de cette pièce jointe est vérifiée dans l’ancien bucket/espace Digital Ocean.
  5. Si le lien est trouvé dans l’ancien bucket/espace, alors le lien court est remplacé par l’URL vers la même pièce jointe dans l’ancien bucket.
  6. Si modifié, déclencher un rebake du message original, pour laisser Discourse faire le gros du travail de re-téléchargement local de la pièce jointe « perdue » et recréer tout ce dont il a besoin dans la base de données.

Pour éviter le blacklistage et réduire la charge sur le serveur, un intervalle de 20 secondes est introduit à chaque fois qu’un rebake est demandé.

def remoteFileExist(url, retries=3)
    puts "Requesting #{url} ..."
    uri = URI(url)
    response = nil
    res = Net::HTTP.get_response(uri)
    puts res['content-type']
    if res.code[0,1] == "2" and res['content-type'].include? 'image'
        return true
    else
        return false
    end
rescue Net::ReadTimeout => e
    puts "TRY #{retries}/n ERROR: timed out while trying to connect #{e}"
    if retries <= 1
        raise
    end
    remoteFileExist(url, retries - 1)
end


####################################################################


posts=Post.where("raw like '%upload://%' ").order('topic_id ASC, post_number DESC');
idx = 0;
posts.each do |p|
    idx = idx + 1;
    puts ""

    matches = p.raw.scan(/(!\[(.)*\]\(upload:\/\/([a-zA-Z0-9]+)\.(jpeg|jpg|png|gif|pdf|mp3|mp4|mov)\))/)

    new_raw = p.raw

    matches.each do |m|  
        short_url = m[0];
        short_sha = m[2];
        ext = m[3];
        long_sha = Base62.decode(short_sha).to_s(16).rjust(40,"0")

        upload = Upload.where('sha1 = ?', long_sha)

        puts "#{short_url} -> #{long_sha}\n"

        if upload.all.count == 0
            puts "#{long_sha} not found in DB. Recovering from ams3...\n"

            subdir1 = long_sha[0]
            subdir2 = long_sha[1]

            new_url1 = "https://discourse-data.ams3.digitaloceanspaces.com/original/3X/#{subdir1}/#{subdir2}/#{long_sha}.#{ext}"
            test1 = remoteFileExist(new_url1)
            if test1
                new_raw = new_raw.gsub(short_url, "\n#{new_url1}")
            else
                new_url2 = "https://discourse-data.ams3.digitaloceanspaces.com/original/2X/#{subdir1}/#{long_sha}.#{ext}"
                if remoteFileExist(new_url2)
                    new_raw = new_raw.gsub(short_url, "\n#{new_url2}")
                end
            end
            puts ""
            sleep 5
        end
    end

    if p.raw != new_raw
        puts "OLD\n"
        puts p.raw
        puts "-----------"
        puts "NEW\n"
        puts new_raw
        puts "-----------"
        puts "UPDATING!"
        # goahead = gets
        p.raw = new_raw
        p.cooked = ''
        p.save
        p.rebake!(invalidate_broken_images: true);
        puts "*******************************************"
        sleep 30
    else
        puts "SKIP!"
        puts "*******************************************"
        sleep 1
    end
end
1 « J'aime »