Problemas con las cargas después del cambio del centro de datos de DO Spaces

Estimados todos,

Después de buscar en el formulario con el máximo de mis capacidades sin encontrar una respuesta que resuelva el problema, solicito soporte para una situación extraña surgida tras un cambio reciente del centro de datos de Digital Ocean.

Así que, teníamos todas nuestras cargas almacenadas en un bucket de Digital Ocean Spaces, en el centro de datos ams3.

Después de dos graves problemas de hardware y la consiguiente interrupción del servicio en poco más de un mes, el fin de semana pasado decidimos mover todos nuestros archivos al centro de datos fra1.

Aquí están los pasos que seguí:

  1. En preparación para la transferencia, subí todos los archivos que teníamos en ams3 (los 3 directorios clásicos: originals, optimized y tombstone) al nuevo bucket en fra1 usando s3cmd.
  2. Entré en la configuración del foro y establecí el nuevo endpoint para los adjuntos, cdnl y el bucket de respaldo.
  3. Lanzé una reconstrucción completa de los posts (full post rebake), esperando que solucionara todo de una sola vez.

Desafortunadamente, no fue así. La mayoría de los adjuntos se “portaron” correctamente, pero unos pocos cientos no. No está claro para mí qué sucedió, pero estos adjuntos faltantes se movieron al directorio tombstone.

Pensé que lanzar la tarea rake rake uploads:recover_from_tombstone se encargaría de eso, pero no. Los archivos se ven, pero al final de la tarea ningún adjunto se recupera; las imágenes siguen sin ser visibles en los posts.

Empecé a indagar un poco más y descubrí que al ejecutar UploadRecovery.new(dry_run: true).recover (encontrado investigando en meta) en la consola de Rails, me daba información valiosa, como la URL del post, así como la URL corta o larga de la imagen problemática.

Para las URLs devueltas en forma corta, escribí un poco de código en Python para “traducir” el nombre de archivo de carga corto a la forma larga, para poder ir a verificar la presencia del archivo en el bucket.
Lo hice, y puedo confirmar que todos los archivos faltantes están ahí, tanto en el nuevo bucket como en el antiguo. Parte de las cargas faltantes las encontré en el directorio tombstone, como era de esperar, pero otras algunas extrañamente siguen en el directorio original. Los archivos no están corruptos. Si accedo a ellos mediante URL, se abren correctamente en ambos centros de datos, y si los volco localmente en mi máquina Linux, puedo abrirlos sin errores.

De alguna manera, el proceso de recuperación de cargas falla al seleccionarlos y arreglar lo que esté mal en la base de datos. :man_shrugging:

Así que mis preguntas son:

  • ¿Existe alguna manera de entender por qué, incluso si los archivos de carga están en tombstone (o en original), la tarea rake falla al recuperarlos?
  • ¿Cuál sería el conjunto correcto de pasos para asegurar que, en caso de cambio de bucket o incluso transición de DO a otro entorno compatible con AWS, todos los adjuntos se muevan y preparen correctamente para el intercambio? En general, ¿qué se debe hacer, paso a paso, en tal caso? Claramente, una simple reconstrucción (rebake) no es suficiente. :confused:
  • ¿Qué hace la tarea posts:invalidate_broken_images? Quiero decir, ¿qué significa invalidar?

Gracias de antemano. Llevo una semana luchando con esto y realmente necesito resolverlo o me volveré loco :smiley: :stuck_out_tongue:
Por cierto, la sugerencia de volver a cargar manualmente los 800+ adjuntos no se considera una respuesta válida. Debe haber una razón algorítmica… :laughing:

2 Me gusta

Creo que te has perdido un DbHelper.remap('oldbucketurl', 'newbucketurl') entre los pasos 2 y 3.

4 Me gusta

Hola @falco, gracias por tu respuesta.
Sí, al principio se me olvidó.
Lo ejecuté después de encontrarlo buscando aquí en meta. :confused: y ayudó a recuperar algunos de los archivos.
Por cierto, hice una rebake completa después de ejecutarlo.

¿Qué más podría probar?

1 me gusta

Así que, quizás tenga una pista sobre lo que está ocurriendo aquí.
No se me ocurrió mencionar un dato relacionado con la salida de la tarea rake uploads:recover_from_tombstone, que podría dar alguna pista interesante.

Parece que la tarea encuentra los archivos de subida en el tumba, pero me lanza una advertencia sobre algo (el nombre completo del archivo de subida) que es incorrecto. Algo así:

Advertencia /t/i-miei-modellini-volanti/28272/212 tenía un 487b613752a0c338646fecc942512e5de9afeb3f incorrecto; debería ser c87c4f08d1a9aac3f43d19722cfd5a94f2544272. Guardando en el campo personalizado 'rake uploads:fix_relative_upload_links' puede solucionar esto.

Al ejecutar un comando find en mi copia local de los directorios de subidas, resulta que sí tengo un archivo llamado 487b613752a0c338646fecc942512e5de9afeb3f.jpeg.

El enlace corto correspondiente a esta subida específica es upload://alcIv6jVlmjiEOEBh8fNDJyRms7.jpeg, y al aplicar el algoritmo base62 que calcula el nombre completo del archivo correspondiente, resulta que el valor es 487b613752a0c338646fecc942512e5de9afeb3f, exactamente el nombre del archivo con el que la tarea recover_from_tombstone me advierte que hay un error. :thinking:

¿Por qué la herramienta afirma que es incorrecto y debería ser c87c4f08d1a9aac3f43d19722cfd5a94f2544272 en su lugar?

Por si acaso, ejecuté la tarea rake uploads:fix_relative_upload_links varias veces y luego volví a ejecutar rake uploads:recover_from_tombstone, pero nada parece cambiar.

Edición:
Buscando 487b613752a0c338646fecc942512e5de9afeb3f en una copia de seguridad de la base de datos que hice antes de cambiar el bucket, puedo ver que el registro en la tabla de subidas correspondiente a esta imagen mostraba exactamente este nombre hexadecimal, por lo que aún más no entiendo por qué la tarea rake se queja de ello.

image

Este es uno de los malentendidos más antiguos en Meta.
No es necesario volver a cocinar después de un remapeo bien dirigido.

2 Me gusta

Puede que tengas razón, pero el problema es que resulta difícil saber exactamente qué hacer y qué no hacer en estos casos sin un tutorial o guía de los desarrolladores.
Siempre queda la sensación de que se debería haber hecho algo más o en un orden diferente, como si uno tuviera que descubrir una receta que funcione extrayéndola de decenas de publicaciones escritas a lo largo de los últimos 3 o 4 años. :stuck_out_tongue:
Volver a hornear parece ser la panacea para muchas cosas y no daña las publicaciones existentes.

Es una forma complicada de decir que, dado lo frecuente que ha sido que la gente tropiece con problemas de gestión de cargas y cosas por el estilo, una buena guía oficial del personal sería una referencia muy importante. :wink:

1 me gusta

Lo siento, necesito reabrir este tema.
La semana pasada dediqué tiempo a leer el código de las tareas rake de subida para entender qué ocurre en el interior de recover_from_tombstone y recover. Es algo difícil debido al encapsulamiento de las clases, así que diría que en gran parte no lo logré.

Sin embargo, lo que entendí (por favor, @Falco, corrígeme si me equivoco) es que el nombre del archivo en disco de una subida se crea combinando su sha1 y su extensión original. Luego se almacena en disco/AWS en un directorio cuya ruta depende de la primera y, a veces, de la segunda letra de su nombre, dentro de 1X, 2X, 3X… (no entiendo cómo se determinan estos valores).
Finalmente, el sha1 y el nombre del archivo se almacenan, entre otras cosas, en los registros de la tabla uploads en PostgreSQL.

Volviendo a lo ocurrido durante nuestro cambio de centro de datos de Digital Ocean, esto es lo que sucedió, según mi mejor comprensión:

  1. Copiamos todos los archivos de ams3 a fra1.
  2. No realizamos DbHelper.remap('oldbucketurl', 'newbucketurl') como sugirió @falco, pero no estaba claro para nosotros que fuera necesario en este caso.
  3. Lanzamos una rebake global. En esta etapa, miles de imágenes “se rompieron” y muchas se movieron a tombstone. No me queda del todo claro por qué.
  4. Me di cuenta de que algo iba mal, interrumpí la rebake en curso y descubrí el comando remap buscando aquí en meta. Lanzamos la tarea DbHelper.remap('oldbucketurl', 'newbucketurl').
  5. Para recuperar las imágenes que se movieron a tombstone en el paso 3, ejecutamos rake uploads:recover_from_tombstone, que recuperó algunas, pero dejó cientos sin recuperar y mostró errores relacionados con el sha1 de los archivos, como: 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, como si el archivo hubiera sido modificado de alguna manera y, por tanto, el sha1 ahora fuera diferente. La recuperación de dichos archivos falla.

Nunca modificamos los archivos al moverlos entre los dos centros de datos. Usando s3cmd, literalmente los descargamos localmente desde el bucket antiguo y los volvimos a subir inmediatamente al nuevo.

¿Por qué debería ser diferente el sha1 calculado por Discourse?

¿Sería posible forzar la tarea recover para que ignore la discrepancia del sha1 y simplemente adapte la importación en la base de datos según lo que hay, o renombrar los archivos existentes con el nuevo sha1 mientras los recupera?

¿Me estoy perdiendo algo obvio? Gracias a todos por su ayuda.

Así que, solo para dar un cierre a este hilo que pueda ser útil a alguien más, así es como resolvimos la situación.

Básicamente, como era imposible recuperar los archivos adjuntos perdidos mediante las diversas tareas de recuperación de cargas (rake tasks), he preparado un script de Ruby (disculpas de antemano, definitivamente NO soy desarrollador de Ruby ni de Rails, así que apuesto a que el código es ineficiente y feo, pero eso es algo secundario :P) que:

  1. Encuentra todos los posts que contienen la cadena upload://
  2. Extrae el enlace corto de cada carga y lo transforma en su hash SHA1 de forma larga
  3. Consulta la tabla Uploads
  4. Si se encuentra un archivo adjunto con el hash SHA1 en Uploads, se omite esa carga; de lo contrario, se verifica la URL de esa carga en el antiguo bucket/espacio de Digital Ocean.
  5. Si el enlace se encuentra en el antiguo bucket/espacio, se reemplaza el enlace corto con la URL de la misma carga en el antiguo bucket.
  6. Si se modifica, se activa un rebake del post original, para permitir que Discourse haga el trabajo pesado de volver a descargar localmente la carga “perdida” y recrear todo lo necesario en la base de datos.

Para evitar el bloqueo por blacklist y reducir la carga en el servidor, se introduce un intervalo de 20 segundos cada vez que se solicita un rebake.

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} no encontrado en la BD. Recuperando desde 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 "ANTIGUO\n"
        puts p.raw
        puts "-----------"
        puts "NUEVO\n"
        puts new_raw
        puts "-----------"
        puts "¡ACTUALIZANDO!"
        # goahead = gets
        p.raw = new_raw
        p.cooked = ''
        p.save
        p.rebake!(invalidate_broken_images: true);
        puts "*******************************************"
        sleep 30
    else
        puts "¡OMITIR!"
        puts "*******************************************"
        sleep 1
    end
end
1 me gusta