首页 > 解决方案 > Django视图处理太慢

问题描述

在我的 Django 应用程序中,我有一个视图需要花费太多时间才能交付,而我不知道究竟是为什么。也许有一种方法可以优化这个过程。

方案

我定期使用视图 1 加载页面,当该页面准备好 $(document).ready(function() 时,我通过来自视图 2 的 AJAX 加载带有其余数据的 Json。

尽管我们只讨论了大约 180 个查询集行,但视图 2 花费了太多时间才能提供结果。

更重要的是,我有服务器缓存设置,因此不参考实际数据库。

服务器缓存和通用应用程序服务器都可以正常工作,我的应用程序的其余视图可以在很短的时间内提供结果。您可以查看:www.kinemed.com.ar

AJAX 调用

我想问题不在这里,但以防万一。

 $(document).ready(function(){
    $.ajax({
        url:'/website/some/',
        type:'GET',
        data: {},
        success:function(data){
            window.data = JSON.parse(data);
            makeMenu(data);
        },
        error:function(){
            console.log('something went wrong here');
        },
    });
 });

观点 2

如你看到的。“Productos”查询集是从缓存中加载的,但如果不是,则数据量很低。

完整的“ProductosBase”模型有大约 600 个项目,过滤后我们只得到大约 180 个项目。

@transaction.atomic
def ShopSegundoPlanoView(request):

    productos = cache.get("productos_argentina")
    if not productos:
        productos = ProductosBase.objects.filter(venta_arge_ok=True)
        cache.set("productos_argentina", productos)

    productos_shop = productos.exclude(foto_1=None).exclude(foto_1='').exclude(foto_2=None).exclude(foto_2='').order_by(
        'marca', 'producto', 'packaging')

    array_productos_shop = []

    for x in productos_shop:

        uds_en_orden = 0
        subtotal_en_orden = 0

        if x.colecciones.all():
            colecciones = str(x.colecciones.all())
        else:
            colecciones = ''

        to_add = {
            'id': x.id,
            'producto': x.producto,
            'packaging': x.packaging,
            'marca': x.marca.marca,
            "color": x.color_link.img_color.url,
            "color_nombre": x.color_link.color,
            "precio": x.precio_lista,
            "medida": str(x.ancho) + ' ' + str(x.medida_ancho) + ' x ' + str(x.largo) + ' ' + str(x.medida_largo),
            "compra_minima": x.pedido_minimo,
            "categoria": x.categoria_producto.categoria_producto,
            "colecciones": colecciones,
            "imagen": x.foto_1.url,
            "imagen_2": x.foto_2.url,
            "uds_en_orden": uds_en_orden,
            "subtotal_en_orden": subtotal_en_orden,
            "descripcion": x.descripcion,
            "usos": x.categoria_producto.usos,
        }

        array_productos_shop.append(to_add)

array_productos_shop = json.dumps(array_productos_shop, cls=DjangoJSONEncoder)
data = {'array_productos_shop': array_productos_shop}

return JsonResponse(data)

我想在迭代中消耗了时间来生成结果数组,但我再一次猜想它不应该花那么多时间来处理。

有什么线索吗?

提前致谢。

更新

正如威廉建议的那样,我将查询更改为预取外部字段,甚至将其包含在缓存中。

    productos_shop = cache.get("productos_argentina_shop")
    if not productos_shop:
        productos_shop = ProductosBase.objects.filter(venta_arge_ok=True).exclude(Q(foto_1=None) | Q(foto_1='') | Q(foto_2=None) | Q(foto_2='')).select_related('categoria_producto', 'marca', 'color_link').prefetch_related('colecciones').order_by('marca', 'producto', 'packaging')
        cache.set("productos_argentina_shop", productos_shop)

结果是我的反应从 30 秒缩短到了 25 秒。我仍然认为花这么多时间来交付 json 是没有意义的。结果只有 180 条记录和 400k。

一定有其他东西在影响这个吗?在同一应用程序的其他视图中,我没有此性能问题。

在这里您可以看到响应的时间。

在此处输入图像描述

更新二

我使用 Server Timing API 测量了服务器端进程。

我得到查询集的块只需要 54 毫秒。这似乎是对的。

对于 180 个元素,forloop 块需要 25 秒。每次迭代大约 140 毫秒。这个数额合理吗?我不是这方面的专家,但听起来很慢。

该模型

class ProductosBase(models.Model):
codigo_kinemed = models.CharField(max_length=50, help_text="Código de profucto Kinemed", blank=True, null=True)
codigo_barra = models.CharField(max_length=50, help_text="Código de barras", blank=True, null=True)
codigo_inner = models.CharField(max_length=50, help_text="Código de barras", blank=True, null=True)
codigo_master = models.CharField(max_length=50, help_text="Código de barras", blank=True, null=True)
item_number = models.CharField(max_length=50, help_text="Item number", blank=True, null=True)
marca = models.ForeignKey("Marcas", help_text="Marca", blank=True, null=True, on_delete=models.CASCADE)
categoria_producto = models.ForeignKey("Categorias_Producto", help_text="Categorias de producto", blank=True,
                                       null=True, on_delete=models.CASCADE)
sub_categoria_producto = models.ForeignKey(Sub_Categorias_Producto, help_text="", blank=True, null=True,
                                           on_delete=models.CASCADE)
producto = models.CharField(max_length=50, help_text="Producto", blank=True, null=True)
producto_apellido = models.CharField(max_length=50, help_text="Producto", default="", blank=True, null=True)
color = models.CharField(max_length=50, help_text="Color", blank=True, null=True)
color_ing = models.CharField(max_length=50, help_text="Color", blank=True, null=True)
color_link = models.ForeignKey(ColorBase, help_text="Marca", blank=True, null=True, on_delete=models.CASCADE)

CMS = "cm"
MTS = "mt"
MMS = "mm"
medidas_CHOICES = ((CMS, 'cm'), (MTS, 'mt'), (MMS, 'mm'),)

ancho = models.DecimalField(max_digits=5, decimal_places=2, help_text="Valor del ancho", blank=True, null=True)
medida_ancho = models.CharField(max_length=5, choices=medidas_CHOICES, default=CMS, help_text="Tipo",)

largo = models.DecimalField(max_digits=5, decimal_places=1, help_text="Largo en mts", blank=True, null=True)
medida_largo = models.CharField(max_length=5, choices=medidas_CHOICES, default=MTS, help_text="Tipo",)

packaging = models.CharField(max_length=50, help_text="Formato de packaging", blank=True, null=True)
pedido_minimo = models.IntegerField(help_text="Pedido Mínimo", blank=True, null=True)
units_inner = models.IntegerField(help_text="Units por Inner box", blank=True, null=True)
inner_master = models.IntegerField(help_text="Inner boxes por Master box", blank=True, null=True)
caja_venta_arg = models.IntegerField(blank=True, null=True)
tier = models.IntegerField(help_text="Master boxes por nivel de pallet", blank=True, null=True)
descripcion = models.TextField(max_length=500, help_text="Descripción del producto", blank=True)
foto_1 = models.ImageField(upload_to='', default="", blank=True, null=True)
foto_1_M = models.ImageField(upload_to='', default="", blank=True, null=True)
foto_1_L = models.ImageField(upload_to='', default="", blank=True, null=True)
foto_2 = models.ImageField(upload_to='', default="", blank=True, null=True)
foto_2_M = models.ImageField(upload_to='', default="", blank=True, null=True)
foto_2_L = models.ImageField(upload_to='', default="", blank=True, null=True)
video_link = models.URLField("Video link", max_length=128, blank=True, null=True)
estatus_contenido = models.ForeignKey("WorkflowContenidos", help_text="Estatus del contenido", blank=True,
                                      null=True, on_delete=models.CASCADE)
priority = models.IntegerField(help_text="Prioridad para mostrar", default=0, blank=True, null=True)
priority_web = models.IntegerField(help_text="Prioridad para mostrar", default=0, blank=True, null=True)
precio_lista = models.IntegerField(help_text="Precio de lista", blank=True, null=True)
precio_pvp = models.IntegerField(help_text="PVP sugerido", blank=True, null=True)
region = models.ForeignKey("ListasRegionales", help_text="Lista regional", blank=True, null=True, on_delete=models.CASCADE)
lista_nicho = models.ForeignKey("ListasNicho", help_text="Lista nicho", blank=True, null=True, on_delete=models.CASCADE)
producto_ing = models.CharField(max_length=50, help_text="Nombre en inglés", blank=True, null=True)
packaging_ing = models.CharField(max_length=50, help_text="Packaging inglés", blank=True, null=True)
uom = models.CharField(max_length=50, help_text="UOM", blank=True, null=True)
precio_compra = models.DecimalField(max_digits=5, decimal_places=2, help_text="Valor del ancho", blank=True, null=True)
precio_por = models.ForeignKey("PrecioPor", help_text="Lista nicho", blank=True, null=True, on_delete=models.CASCADE)
precio_lista_internacional = models.DecimalField(max_digits=5, decimal_places=2, help_text="Valor del ancho", blank=True, null=True)
precio_lista_internacional_B = models.DecimalField(max_digits=5, decimal_places=2, help_text="Valor del ancho", blank=True, null=True)
total_vendidas = models.IntegerField(help_text="Total ventas historicas", default=0, blank=True, null=True)
media_ventas_mes = models.IntegerField(help_text="Total ventas historicas", default=0, blank=True, null=True)
ventas_ultimo_mes = models.IntegerField(help_text="Total ventas historicas", default=0, blank=True, null=True)
existencias = models.IntegerField(help_text="Total ventas historicas", default=0, blank=True, null=True)
existencias_bloqueada = models.IntegerField(help_text="Total ventas historicas", default=0, blank=True, null=True)
duracion_stock = models.IntegerField(help_text="Total ventas historicas", default=0, blank=True, null=True)
estatus_reposicion = models.CharField(max_length=50, help_text="Nombre en inglés", blank=True, null=True)
made_in = models.CharField(max_length=200, help_text="Nombre producto", blank=False, null=True)
nombre_zetti = models.CharField(max_length=30, help_text="max 30 char", blank=True, null=True)
nombre_anmat = models.ForeignKey("CertificadoPM", help_text="Marca", blank=True, null=True, on_delete=models.CASCADE)
peso_master_box = models.DecimalField(max_digits=5, decimal_places=2, help_text="Valor del ancho", blank=True, null=True)
page = models.IntegerField(help_text="Total ventas historicas", default=0, blank=True, null=True)
cuenta = models.IntegerField(help_text="Total ventas historicas", default=0, blank=True, null=True)
descuento_max = models.IntegerField(help_text="Descuento max", default=0, blank=True, null=True)
venta_arge_ok = models.BooleanField(default=False)
venta_inte_ok = models.BooleanField(default=False)
is_father = models.BooleanField(default=False)
is_mother = models.BooleanField(default=False)
largo_master_box = models.DecimalField(max_digits=5, decimal_places=2, help_text="Valor del largo", blank=True, null=True)
ancho_master_box = models.DecimalField(max_digits=5, decimal_places=2, help_text="Valor del ancho", blank=True, null=True)
alto_master_box = models.DecimalField(max_digits=5, decimal_places=2, help_text="Valor del alto", blank=True, null=True)
ratio_importacion = models.DecimalField(max_digits=5, decimal_places=2, help_text="Valor del alto", blank=True, null=True)
pos_arancelaria = models.ForeignKey("PosicionArancelaria", help_text="Marca", blank=True, null=True, on_delete=models.CASCADE)
image_gallery = models.ManyToManyField('stockbucket.ImageGalery', related_name='image_gallery', blank=True, help_text="")
colecciones = models.ManyToManyField('Colecciones', related_name='Colecciones', blank=True, help_text="")

打印连接

正如威廉建议的那样,我测试了:

from django.db import connection
print(connection.queries)

有了这个结果

[{'sql': 'BEGIN', 'time': '0.000'}, {'sql': 'SELECT "catalog_productosbase"."id", "catalog_productosbase"."codigo_kinemed", "catalog_productosbase"."codigo_barra", "catalog_productosbase"."codigo_inner", "catalog_productosbase"."codigo_master", "catalog_productosbase"."item_number", "catalog_productosbase"."marca_id", "catalog_productosbase"."categoria_producto_id", "catalog_productosbase"."sub_categoria_producto_id", "catalog_productosbase"."producto", "catalog_productosbase"."producto_apellido", "catalog_productosbase"."color", "catalog_productosbase"."color_ing", "catalog_productosbase"."color_link_id", "catalog_productosbase"."ancho", "catalog_productosbase"."medida_ancho", "catalog_productosbase"."largo", "catalog_productosbase"."medida_largo", "catalog_productosbase"."packaging", "catalog_productosbase"."pedido_minimo", "catalog_productosbase"."units_inner", "catalog_productosbase"."inner_master", "catalog_productosbase"."caja_venta_arg", "catalog_productosbase"."tier", "catalog_productosbase"."descripcion", "catalog_productosbase"."foto_1", "catalog_productosbase"."foto_1_M", "catalog_productosbase"."foto_1_L", "catalog_productosbase"."foto_2", "catalog_productosbase"."foto_2_M", "catalog_productosbase"."foto_2_L", "catalog_productosbase"."video_link", "catalog_productosbase"."estatus_contenido_id", "catalog_productosbase"."priority", "catalog_productosbase"."priority_web", "catalog_productosbase"."precio_lista", "catalog_productosbase"."precio_pvp", "catalog_productosbase"."region_id", "catalog_productosbase"."lista_nicho_id", "catalog_productosbase"."producto_ing", "catalog_productosbase"."packaging_ing", "catalog_productosbase"."uom", "catalog_productosbase"."precio_compra", "catalog_productosbase"."precio_por_id", "catalog_productosbase"."precio_lista_internacional", "catalog_productosbase"."precio_lista_internacional_B", "catalog_productosbase"."total_vendidas", "catalog_productosbase"."media_ventas_mes", "catalog_productosbase"."ventas_ultimo_mes", "catalog_productosbase"."existencias", "catalog_productosbase"."existencias_bloqueada", "catalog_productosbase"."duracion_stock", "catalog_productosbase"."estatus_reposicion", "catalog_productosbase"."made_in", "catalog_productosbase"."nombre_zetti", "catalog_productosbase"."nombre_anmat_id", "catalog_productosbase"."peso_master_box", "catalog_productosbase"."page", "catalog_productosbase"."cuenta", "catalog_productosbase"."descuento_max", "catalog_productosbase"."venta_arge_ok", "catalog_productosbase"."venta_inte_ok", "catalog_productosbase"."is_father", "catalog_productosbase"."is_mother", "catalog_productosbase"."largo_master_box", "catalog_productosbase"."ancho_master_box", "catalog_productosbase"."alto_master_box", "catalog_productosbase"."ratio_importacion", "catalog_productosbase"."pos_arancelaria_id", "catalog_marcas"."id", "catalog_marcas"."marca", "catalog_marcas"."slug", "catalog_marcas"."marca_anmat", "catalog_marcas"."descripcion_brief", "catalog_marcas"."descripcion_long", "catalog_marcas"."logo", "catalog_marcas"."logo_full", "catalog_marcas"."logo_transparente", "catalog_marcas"."foto_1", "catalog_marcas"."brand_video", "catalog_marcas"."web", "catalog_marcas"."pais", "catalog_marcas"."pais_origen_productos", "catalog_marcas"."orden_lista", "catalog_marcas"."tiempo_entrega", "catalog_marcas"."modalidad_compra_id", "catalog_marcas"."venta_arge_ok", "catalog_categorias_producto"."id", "catalog_categorias_producto"."categoria_producto", "catalog_categorias_producto"."slug", "catalog_categorias_producto"."categoria_producto_ing", "catalog_categorias_producto"."descripcion_brief", "catalog_categorias_producto"."descripcion_long", "catalog_categorias_producto"."usos", "catalog_categorias_producto"."foto_1", "catalog_categorias_producto"."foto_2", "catalog_categorias_producto"."foto_1_M", "catalog_categorias_producto"."foto_2_M", "catalog_categorias_producto"."video", "catalog_categorias_producto"."perfil_cliente", "catalog_categorias_producto"."estatus_contenido_id", "catalog_categorias_producto"."orden_lista", "catalog_categorias_producto"."orden_marca", "catalog_categorias_producto"."venta_arge_ok", "catalog_colorbase"."id", "catalog_colorbase"."color", "catalog_colorbase"."color_ing", "catalog_colorbase"."img_color", "catalog_colorbase"."priority" FROM "catalog_productosbase" LEFT OUTER JOIN "catalog_marcas" ON ("catalog_productosbase"."marca_id" = "catalog_marcas"."id") LEFT OUTER JOIN "catalog_categorias_producto" ON ("catalog_productosbase"."categoria_producto_id" = "catalog_categorias_producto"."id") LEFT OUTER JOIN "catalog_colorbase" ON ("catalog_productosbase"."color_link_id" = "catalog_colorbase"."id") WHERE ("catalog_productosbase"."venta_arge_ok" = 1 AND NOT (("catalog_productosbase"."foto_1" IS NULL OR ("catalog_productosbase"."foto_1" = \'\' AND "catalog_productosbase"."foto_1" IS NOT NULL) OR "catalog_productosbase"."foto_2" IS NULL OR ("catalog_productosbase"."foto_2" = \'\' AND "catalog_productosbase"."foto_2" IS NOT NULL)))) ORDER BY "catalog_productosbase"."marca_id" ASC, "catalog_productosbase"."producto" ASC, "catalog_productosbase"."packaging" ASC', 'time': '0.015'}, {'sql': 'SELECT ("catalog_productosbase_colecciones"."productosbase_id") AS "_prefetch_related_val_productosbase_id", "catalog_colecciones"."id", "catalog_colecciones"."nombre", "catalog_colecciones"."prioridad", "catalog_colecciones"."short_text", "catalog_colecciones"."perfil_cliente" FROM "catalog_colecciones" INNER JOIN "catalog_productosbase_colecciones" ON ("catalog_colecciones"."id" = "catalog_productosbase_colecciones"."colecciones_id") WHERE "catalog_productosbase_colecciones"."productosbase_id" IN (78, 3, 4, 5, 8, 9, 1, 2, 11, 13, 14, 133, 16, 135, 18, 19, 20, 100, 101, 102, 130, 50, 51, 52, 53, 54, 55, 56, 99, 84, 85, 148, 88, 89, 520, 86, 87, 22, 140, 141, 424, 425, 426, 427, 372, 373, 523, 524, 107, 108, 109, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 150, 151, 175, 23, 24, 25, 26, 27, 28, 29, 30, 122, 123, 126, 127, 128, 129, 160, 161, 31, 32, 33, 34, 35, 36, 37, 38, 250, 251, 39, 41, 124, 125, 154, 155, 222, 343, 177, 178, 179, 143, 428, 429, 431, 79, 43, 44, 45, 46, 432, 437, 47, 48, 49, 268, 264, 77, 65, 72, 73, 61, 62, 63, 64, 71, 69, 70, 67, 68, 66, 236, 237, 238, 239, 240, 242, 95, 96, 97, 98, 233, 234, 91, 92, 93, 94, 235, 241, 353, 356, 354, 355, 357, 358, 359, 360, 361, 362, 366, 367, 368, 369, 370, 371, 412, 413, 406, 408, 423, 409, 410, 411, 414, 417, 637, 612, 634, 633, 636) ORDER BY "catalog_colecciones"."prioridad" ASC', 'time': '0.000'}]

更新三

我按照威廉的建议使用了 nplusone 包,并摆脱了 n+1 和 egaer 警报。所以这个问题应该优化。

但是处理forloop仍然需要25秒。每次迭代大约 140 毫秒。

更新四

我发现从 25 秒开始,17.5 与图像字段相关:

"imagen": x.foto_1.url,
"imagen_2": x.foto_2.url,

我想这与此有关。

标签: jsondjangoajax

解决方案


最后我找到了过程缓慢的原因。

Williams 的建议有助于优化数据库查询以避免 n+1 问题。但主要问题在于 image.url 字段。

尽管它们没有被检测为 n+1,但我认为它们的行为类似于一个,因为 url 实际上不是数据库中的一个值,而是一个属性。

我的理解是在这些字段上进行了另一个调用。

当我使用 Google Cloud Storage for Media 时,我猜想交互是需要时间从图像字段中收集 URL 属性的。

我解决了使用 cahe 服务器存储处理后的视图的方法。

@timed_wrapper('ShopSegundoPlanoView', 'Json load')
@transaction.atomic

def ShopSegundoPlanoView(request):

json_timmer = TimedService('first', 'Json load')
json_timmer.start()

array_productos_shop = cache.get("productos_argentina_shop")
if not array_productos_shop:
    productos_shop = ProductosBase.objects.filter(venta_arge_ok=True).exclude(Q(foto_1=None) | Q(foto_1='') | Q(foto_2=None) | Q(foto_2='')).select_related('categoria_producto', 'marca', 'color_link').prefetch_related('colecciones').order_by('marca', 'producto', 'packaging')

    array_productos_shop = []

    for x in productos_shop:

        uds_en_orden = 0
        subtotal_en_orden = 0

        if x.colecciones.all().exists():
            colecciones = str(x.colecciones.all())
            print(colecciones)
        else:
            colecciones = ''

        to_add = {
            'id': x.id,
            'producto': x.producto,
            'packaging': x.packaging,
            'marca': x.marca.marca,
            "color": x.color_link.img_color.url,
            "color_nombre": x.color_link.color,
            "precio": x.precio_lista,
            "medida": str(x.ancho) + ' ' + str(x.medida_ancho) + ' x ' + str(x.largo) + ' ' + str(x.medida_largo),
            "compra_minima": x.pedido_minimo,
            "categoria": x.categoria_producto.categoria_producto,
            "colecciones": colecciones,
            "imagen": x.foto_1.url,
            "imagen_2": x.foto_2.url,
            "uds_en_orden": uds_en_orden,
            "subtotal_en_orden": subtotal_en_orden,
            "descripcion": x.descripcion,
            "usos": x.categoria_producto.usos,
        }

        array_productos_shop.append(to_add)

    array_productos_shop = json.dumps(array_productos_shop, cls=DjangoJSONEncoder)
    array_productos_shop = {'array_productos_shop': array_productos_shop}
    cache.set("productos_argentina_shop", array_productos_shop)
    json_timmer.end()

return JsonResponse(array_productos_shop)

推荐阅读