Saltar al contenido

Creación de archivos zip grandes en AWS S3 en trozos

Solución:

Lo que tengo que hacer es darle al cliente la capacidad de descargarlos todos en un ZIP (o similar), pero no puedo hacerlo en la memoria ni en el almacenamiento del servidor, ya que esta es una configuración sin servidor.

Cuando dice servidor menos, si lo que quiere decir es que le gustaría usar Lambda para crear un archivo zip en S3, se encontrará con algunas limitaciones:

  • Lambda tiene un límite de tiempo sobre cuánto tiempo se pueden ejecutar las funciones.
  • Como Lambda tiene un límite de memoria, es posible que tenga problemas para ensamblar un archivo grande en una función Lambda
  • Lambda tiene un límite en el tamaño máximo de una llamada PUT.

Por las razones anteriores, creo que el siguiente enfoque es mejor:

  • Cuando los archivos sean necesarios, cree una instancia EC2 sobre la marcha. Quizás su función lambda pueda desencadenar la creación de la instancia EC2.
  • copie todos los archivos en el almacén de instancias de la máquina o incluso en EFS.
  • Comprime los archivos en un zip
  • Cargue el archivo de nuevo en S3 o sirva el archivo directamente
  • Elimine la instancia EC2.

En mi opinión, esto simplificaría enormemente el código que tiene que escribir, ya que cualquier código que se ejecute en su computadora portátil / escritorio probablemente funcionará en la instancia EC2. Tampoco tendrá las limitaciones de tiempo / espacio de lambda.

Como puede deshacerse de la instancia EC2 una vez que el archivo zip se carga de nuevo en S3, no tiene que preocuparse por el costo de que el servidor esté siempre en funcionamiento; simplemente encienda uno cuando lo necesite y elimínelo cuando lo necesite. Listo.

El código para comprimir varios archivos en una carpeta podría ser tan simple como:

De: https://code.tutsplus.com/tutorials/compressing-and-extracting-files-in-python–cms-26816

import os
import zipfile
 
fantasy_zip = zipfile.ZipFile('C:\Stories\Fantasy\archive.zip', 'w')
 
for folder, subfolders, files in os.walk('C:\Stories\Fantasy'):
 
    for file in files:
        if file.endswith('.pdf'):
            fantasy_zip.write(os.path.join(folder, file), os.path.relpath(os.path.join(folder,file), 'C:\Stories\Fantasy'), compress_type = zipfile.ZIP_DEFLATED)
 
fantasy_zip.close()

import io


class S3File(io.RawIOBase):
    def __init__(self, s3_object):
        self.s3_object = s3_object
        self.position = 0

    def __repr__(self):
        return "<%s s3_object=%r>" % (type(self).__name__, self.s3_object)

    @property
    def size(self):
        return self.s3_object.content_length

    def tell(self):
        return self.position

    def seek(self, offset, whence=io.SEEK_SET):
        if whence == io.SEEK_SET:
            self.position = offset
        elif whence == io.SEEK_CUR:
            self.position += offset
        elif whence == io.SEEK_END:
            self.position = self.size + offset
        else:
            raise ValueError("invalid whence (%r, should be %d, %d, %d)" % (
                whence, io.SEEK_SET, io.SEEK_CUR, io.SEEK_END
            ))

        return self.position

    def seekable(self):
        return True

    def read(self, size=-1):
        if size == -1:
            # Read to the end of the file
            range_header = "bytes=%d-" % self.position
            self.seek(offset=0, whence=io.SEEK_END)
        else:
            new_position = self.position + size

            # If we're going to read beyond the end of the object, return
            # the entire object.
            if new_position >= self.size:
                return self.read()

            range_header = "bytes=%d-%d" % (self.position, new_position - 1)
            self.seek(offset=size, whence=io.SEEK_CUR)

        return self.s3_object.get(Range=range_header)["Body"].read()

    def readable(self):
        return True


if __name__ == "__main__":
    import zipfile

    import boto3

    s3 = boto3.resource("s3")
    s3_object = s3.Object(bucket_name="bukkit", key="bagit.zip")

    s3_file = S3File(s3_object)

    with zipfile.ZipFile(s3_file) as zf:
        print(zf.namelist())

Tu pregunta es extremadamente compleja, porque resolverla puede enviarte por muchos agujeros de conejo.

Creo que Rahul Iyer está en el camino correcto, porque en mi humilde opinión sería más fácil iniciar una nueva instancia EC2 y comprimir los archivos en esta instancia y moverlos de nuevo a un depósito S3 que solo sirve archivos zip al cliente.

Si sus archivos fueran más pequeños, podría usar AWS Cloudfront para manejar la compresión cuando un cliente solicita un archivo.

Durante mi investigación, noté que otros lenguajes, como .Net y Java, tenían API que manejan la transmisión en archivos zip. También miré zipstream, que se ha bifurcado varias veces. No está claro cómo se puede usar zipstream para transmitir un archivo para comprimirlo.

El siguiente código fragmentará un archivo y escribirá los mandriles en un archivo zip. Los archivos de entrada estaban cerca de 12 Gbs y el archivo de salida era de casi 5 Gbs.

Durante las pruebas, no vi ningún problema importante con el uso de la memoria o grandes picos.

Agregué un código pseudo S3 a una de las publicaciones a continuación. Creo que se requieren más pruebas para comprender cómo funciona este código en archivos en S3.

from io import RawIOBase
from zipfile import ZipFile
from zipfile import ZipInfo
from zipfile import ZIP_DEFLATED

# This module is needed for ZIP_DEFLATED
import zlib


class UnseekableStream(RawIOBase):
def __init__(self):
    self._buffer = b''

def writable(self):
    return True

def write(self, b):
    if self.closed:
        raise ValueError('The stream was closed!')
    self._buffer += b
    return len(b)

def get(self):
    chunk = self._buffer
    self._buffer = b''
    return chunk


def zipfile_generator(path, stream):
   with ZipFile(stream, mode="w") as zip_archive:
       z_info = ZipInfo.from_file(path)
       z_info.compress_type = ZIP_DEFLATED
       with open(path, 'rb') as entry, zip_archive.open(z_info, mode="w") as dest: 
          for chunk in iter(lambda: entry.read(16384), b''): # 16384 is the maximum size of an SSL/TLS buffer.
             dest.write(chunk)
             yield stream.get()
 yield stream.get()


stream = UnseekableStream()
# each on the input files was 4gb
files = ['input.txt', 'input2.txt', 'input3.txt']
with open("test.zip", "wb") as f:
   for item in files:
      for i in zipfile_generator(item, stream):
         f.write(i)
         f.flush()
stream.close()
f.close()

pseudocódigo s3 / código postal

Este código es estrictamente hipotético, porque necesita ser probado.

from io import RawIOBase
from zipfile import ZipFile
from zipfile import ZipInfo
from zipfile import ZIP_DEFLATED
import os

import boto3

# This module is needed for ZIP_DEFLATED
import zlib

session = boto3.Session(
aws_access_key_id='XXXXXXXXXXXXXXXXXXXXXXX',
aws_secret_access_key='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
region_name="XXXXXXXXXX")

s3 = session.resource('s3')
bucket_name = s3.Bucket('bucket name')

class UnseekableStream(RawIOBase):
   def __init__(self):
      self._buffer = b''

   def writable(self):
      return True

   def write(self, b):
      if self.closed:
        raise ValueError('The stream was closed!')
    self._buffer += b
    return len(b)

    def get(self):
      chunk = self._buffer
      self._buffer = b''
      return chunk


def zipfile_generator(path, stream):
   with ZipFile(stream, mode="w") as zip_archive:
       z_info = ZipInfo.from_file(path)
       z_info.compress_type = ZIP_DEFLATED
       with open(path, 'rb') as entry, zip_archive.open(z_info, mode="w") as dest:
           for chunk in iter(lambda: entry.read(16384), b''):
            dest.write(chunk)
              yield stream.get()
    yield stream.get()


stream = UnseekableStream()
with open("test.zip", "wb") as f:
   for file in bucket_name.objects.all():
     obj = s3.get_object(Bucket=bucket_name, Key=file.key)
     for i in zipfile_generator(obj.get(), stream):
        f.write(i)
        f.flush()
stream.close()
f.close()
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *