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