tutorial subir rutas carga archivos django nginx amazon-s3 gunicorn amazon-elb

subir - static en django



¿Cómo evitar el tiempo de espera de la conexión inactiva al cargar un archivo grande? (3)

Considera nuestra arquitectura actual:

+---------------+ | Clients | | (API) | +-------+-------+ ∧ ∨ +-------+-------+ +-----------------------+ | Load Balancer | | Nginx | | (AWS - ELB) +<-->+ (Service Routing) | +---------------+ +-----------------------+ ∧ ∨ +-----------------------+ | Nginx | | (Backend layer) | +-----------+-----------+ ∧ ∨ ----------------- +-----------+-----------+ File Storage | Gunicorn | (AWS - S3) <-->+ (Django) | ----------------- +-----------------------+

Cuando un cliente, móvil o web, intenta cargar archivos de gran tamaño (más de un GB) en nuestros servidores, a menudo se enfrenta a tiempos de espera inactivos de conexión. Ya sea desde su biblioteca cliente, en iOS, por ejemplo, o desde nuestro equilibrador de carga.

Cuando el cliente realmente carga el archivo, no se producen tiempos de espera porque la conexión no está "inactiva", se están transfiriendo bytes. Pero creo que cuando el archivo se transfirió a la capa de fondo de Nginx y Django comienza a cargar el archivo a S3, la conexión entre el cliente y nuestro servidor queda inactiva hasta que se completa la carga.

¿Hay alguna manera de evitar que esto suceda y en qué capa debo abordar este problema?


He enfrentado el mismo problema y lo solucioné usando django-queued-storage en la parte superior de django-storage . Lo que el almacenamiento en cola de django hace es que cuando se recibe un archivo crea una tarea de apio para cargarlo en el almacenamiento remoto como S3 y en el tiempo medio si alguien accede al archivo y aún no está disponible en S3 lo sirve desde el local sistema de archivos. De esta forma, no tiene que esperar que el archivo se cargue en S3 para enviar una respuesta al cliente.

Como su aplicación detrás de Load Balancer es posible que desee utilizar el sistema de archivos compartidos como Amazon EFS para utilizar el enfoque anterior.


Puede intentar omitir la carga del archivo en su servidor y subirlo a s3 directamente, y luego recuperar una url para su aplicación.

Hay una aplicación para eso: django-s3direct , puedes intentarlo.


Puede crear un controlador de carga para subir archivos directamente a s3. De esta forma, no deberías encontrar el tiempo de espera de la conexión.

https://docs.djangoproject.com/en/1.10/ref/files/uploads/#writing-custom-upload-handlers

Hice algunas pruebas y funciona perfectamente en mi caso.

Tienes que iniciar una nueva carga multiparte con boto, por ejemplo, y enviar trozos progresivamente.

No te olvides de validar el tamaño del fragmento. 5Mb es el mínimo si su archivo contiene más de 1 parte. (Limitación S3)

Creo que esta es la mejor alternativa para django-queued-storage si realmente quieres cargar directamente a s3 y evitar el tiempo de espera de la conexión.

Probablemente también necesite crear su propio campo de archivos para administrar el archivo correctamente y no enviarlo por segunda vez.

El siguiente ejemplo es con S3BotoStorage.

S3_MINIMUM_PART_SIZE = 5242880 class S3FileUploadHandler(FileUploadHandler): chunk_size = setting(''S3_FILE_UPLOAD_HANDLER_BUFFER_SIZE'', S3_MINIMUM_PART_SIZE) def __init__(self, request=None): super(S3FileUploadHandler, self).__init__(request) self.file = None self.part_num = 1 self.last_chunk = None self.multipart_upload = None def new_file(self, field_name, file_name, content_type, content_length, charset=None, content_type_extra=None): super(S3FileUploadHandler, self).new_file(field_name, file_name, content_type, content_length, charset, content_type_extra) self.file_name = "{}_{}".format(uuid.uuid4(), file_name) default_storage.bucket.new_key(self.file_name) self.multipart_upload = default_storage.bucket.initiate_multipart_upload(self.file_name) def receive_data_chunk(self, raw_data, start): buffer_size = sys.getsizeof(raw_data) if self.last_chunk: file_part = self.last_chunk if buffer_size < S3_MINIMUM_PART_SIZE: file_part += raw_data self.last_chunk = None else: self.last_chunk = raw_data self.upload_part(part=file_part) else: self.last_chunk = raw_data def upload_part(self, part): self.multipart_upload.upload_part_from_file( fp=StringIO(part), part_num=self.part_num, size=sys.getsizeof(part) ) self.part_num += 1 def file_complete(self, file_size): if self.last_chunk: self.upload_part(part=self.last_chunk) self.multipart_upload.complete_upload() self.file = default_storage.open(self.file_name) self.file.original_filename = self.original_filename return self.file