취미삼아 배우는 프로그래밍

페이지 최적화 예제(django queryset optimization example) 본문

코드 자가리뷰(장고)

페이지 최적화 예제(django queryset optimization example)

Nadure 2020. 8. 27. 22:22

겨우

하나의 뷰에 대한 최적화를 끝냈다.

 

쿼리식을 짜는데만 이틀이나 걸렸다(퇴근하고..)

이전 글인

nadure.tistory.com/40

 

ORM코드 최적화가 필요하다.

새벽에 파이썬 관련 카톡방에서 어느분이 쿼리가 한 번만 해야되는데 여러번 날라간다고 해서, 나도 한 번 debug toolbar를 깔아서 실행해봤다. 1. 2. 이건 48개 쿼리중에 36개가 중복.. 양호한가? 3. 이

nadure.tistory.com

 

에서

의 쿼리수는 대략

충격적이게도 485개 쿼리중 285개가 중복이다.

(내가 빨리 만드려고 너무 property를 남발한것 같다... 나중에 되서야 깨달았다. property하나를 호출할 때마다 쿼리 하나였다..ㅎㅎ,,)

 

그래서 이 페이지를 최적화 하는데만 2일정도 걸렸다.

뷰 코드

class ListDataView_v2(View):
    # and ajax_call is need;
    def get(self, request, coating_company_id, year, month):

        date_obj = datetime.date(year, month, 1)

        angle_list = ['ANGLE', 'ANGLE ', 'angle', 'Angle']
        template_name = 'pages/view_list_v2.html'
        end_day = calendar.monthrange(year, month)[1]
        
        data_range_query = Q(worked_date__range = [f"{year}-{month}-01",
                         f"{year}-{month}-{end_day}"])
        data_range_query_col = Q(target_date__range = [f"{year}-{month}-01",
                                 f"{year}-{month}-{end_day}"])

        coated_company = CoatCompanyModel.objects.get(id=coating_company_id).coated_company

        try:
            coll = CollectedFilesModel.objects \
                    .filter(data_range_query_col)\
                    .filter(target_coat_company_id=coating_company_id).get()
        except Exception as e:
            coll = None

        def get_total_from_obj(obj):
            query_base = 'base_data__one_district__detailData__data_rows__'
            sum_dict = obj.prefetch_related('base_data')\
                .annotate(A_Area_sum=Coalesce(Sum(query_base+'A_Area'), Value(0)),
                        B_Area_sum=Coalesce(Sum(query_base+'B_Area'), Value(0)),
                        C_Area_sum=Coalesce(Sum(query_base+'C_Area'), Value(0)) )\
                .aggregate(A_Area_Total=Coalesce(Sum('A_Area_sum'), Value(0)),
                        B_Area_Total=Coalesce(Sum('B_Area_sum'), Value(0)),
                        C_Area_Total=Coalesce(Sum('C_Area_sum'),Value(0)) )
            return sum_dict
        
        uploaded_files_obj = UploadedExcelFiles.objects.filter(data_range_query, 
            coated_company_id=coating_company_id).order_by('worked_date')

        not_angle = Q(base_data__one_district__detailData__data_rows__product_name__product__in=angle_list)
                                
        uploaded_files_list = list(uploaded_files_obj.values('worked_date__day', 'excel_file', 'is_file_locked', 'id'))

        for i in uploaded_files_list:
            i['excel_file'] = '/media/' + i['excel_file']
        
        total_sum = get_total_from_obj(uploaded_files_obj)
        total_Angle_sum = get_total_from_obj(uploaded_files_obj.filter(not_angle))
        sum_result = {
            'A_Area_Total':round_half_up(total_sum.get('A_Area_Total', 0) - total_Angle_sum.get('A_Area_Total', 0), 2),
            'B_Area_Total':round_half_up(total_sum.get('B_Area_Total', 0) - total_Angle_sum.get('B_Area_Total', 0), 2),
            'C_Area_Total':round_half_up(total_sum.get('C_Area_Total', 0) - total_Angle_sum.get('C_Area_Total', 0), 2),
        }
        for k in total_Angle_sum:
            total_Angle_sum[k] = round_half_up(total_Angle_sum[k], 2)
        
        sum_new_made = sum_result.get('A_Area_Total', 0) + sum_result.get('C_Area_Total', 0)
        sum_recovery_made = sum_result.get('B_Area_Total', 0)
        sum_total = sum_new_made + sum_recovery_made

        category_list = list(uploaded_files_obj.prefetch_related('base_data')\
                             .values_list('base_data__one_district__category', flat=True).distinct())
        category_list = list(dict.fromkeys(category_list))

        category_info = {}

        for cat in category_list:
            setQ = Q(base_data__one_district__category=cat)
            total_sum = get_total_from_obj(uploaded_files_obj.filter(setQ))
            _total_Angle_sum = get_total_from_obj(uploaded_files_obj.filter(setQ, not_angle))
            for k in _total_Angle_sum:
                _total_Angle_sum[k] = round_half_up(_total_Angle_sum[k], 2)

            category_info[cat] = {
                'result':{
                    'A_Area_Total':round_half_up(total_sum.get('A_Area_Total',0) - _total_Angle_sum.get('A_Area_Total',0), 2),
                    'B_Area_Total':round_half_up(total_sum.get('B_Area_Total',0) - _total_Angle_sum.get('B_Area_Total',0), 2),
                    'C_Area_Total':round_half_up(total_sum.get('C_Area_Total',0) - _total_Angle_sum.get('C_Area_Total',0), 2),
                },
                'sum_result':{
                    'new_made':round_half_up(total_sum.get('A_Area_Total',0) + 
                        total_sum.get('C_Area_Total',0) - _total_Angle_sum.get('A_Area_Total',0) ,2),
                    'recovery_made':round_half_up(total_sum.get('B_Area_Total',0) - _total_Angle_sum.get('A_Area_Total',0), 2),
                    'total_sum':round_half_up(total_sum.get('A_Area_Total',0) 
                        + total_sum.get('B_Area_Total',0) + total_sum.get('C_Area_Total',0) 
                        - _total_Angle_sum.get('A_Area_Total',0), 2)
                },
                'angle':_total_Angle_sum
            }
        # print(uploaded_files_list)
        # print(sum_result)

        context = {
            'uploaded_files_list':uploaded_files_list,
            'uploaded_files_list_length':len(uploaded_files_list),
            'sum_result':sum_result,
            'sum_new_made':sum_new_made,
            'sum_recovery_made':sum_recovery_made,
            'sum_total':sum_total,
            'total_Angle_sum':total_Angle_sum,
            'category_info':category_info,
            'date_obj':date_obj,
            'coated_company':coated_company,
            'coating_company_id':coating_company_id,

        }
        return render(request, template_name, context)

이게 아직 정리가 안 된 버전인데, 코드를 정리하고 쿼리식도 조금 더 간결하게 한다면 더 나아질 것으로 보인다.(지금말고 더 많이 나중에 한다는 말.)

이 결과는,,, 두둔,,

극강의 다이어트.

485개를 12개로 줄였다. 이게 다이어트지

근데 이거도 내가 조금 삽질을 해서 그렇지, 쿼리식을 조금 더 만지면 더 간단하게 할 수 있을듯 하다. 그치만, 여기에는 큰 비밀이 있다.

 

사실 일자버튼을 클릭하면 데이터를 아래처럼 띄우는데,

일자 버튼을 누르면 아래로 촥 하고

 

이렇게 내려온다.

 

 

이거 하나를 위해서 딱히 모든 데이터를 한 번에 불러올 필요가 없었던 것.

그래서 일자부분을 통해서 데이터를 불러오는 과정은 ajax를 통해 처리했다.

그 방법은 아약스 요청을 통해 렌더링된 페이지 소스를 받아다가 innerHTML로 사이에다가 때려박고 onclick 이벤트도 기존 toggle_show 형식으로 바꿔준 후에 마치 '데이터를 이전에 이미 다 받아온 양' 인 척 하게끔 했다. 사실은 요청버튼을 누르면서 데이터를 다운받는다.

def get_rendered_one_district_info(request):
    file_id = request.POST.get('file_id', None)
    coating_company_id = request.POST.get('coating_company_id', None)

    angle_list = ['ANGLE', 'ANGLE ', 'angle', 'Angle']
    result = UploadedExcelFiles.objects.get(id=file_id).base_data.prefetch_related('one_district')\
        .annotate(
            worked_date = F('finishing_date'),
            company = F('one_district__company'), 
            location = F('one_district__location_name'), 
            district = F('one_district__district'),
            category = F('one_district__category'),
            detail_id = F('one_district__detailData_id'),
            new_made = Coalesce(Sum('one_district__detailData__data_rows__A_Area'), Value(0))
                     + Sum('one_district__detailData__data_rows__C_Area')
                     - Coalesce(Sum(
                                Case(
                                    When(
                                        one_district__detailData__data_rows__product_name__product__in=angle_list,
                                        then=F('one_district__detailData__data_rows__A_Area')
                                        )
                                    ), 
                                output_field=DecimalField(), default=0),Value(0)), 
            recovery_made = Coalesce(Sum('one_district__detailData__data_rows__B_Area'), Value(0)),
            total_sum = Coalesce(
                Sum('one_district__detailData__data_rows__A_Area')
                + Sum('one_district__detailData__data_rows__B_Area')
                + Sum('one_district__detailData__data_rows__C_Area')
                - Sum( Case(
                    When(
                        one_district__detailData__data_rows__product_name__product__in=angle_list,
                        then=F('one_district__detailData__data_rows__A_Area')
                        )
                    ), output_field=DecimalField(), default=0), Value(0)),
            angle_sum = Coalesce(Sum(
                Case(
                    When(
                        one_district__detailData__data_rows__product_name__product__in=angle_list,
                        then=F('one_district__detailData__data_rows__A_Area')
                        )
                    ), 
                output_field=DecimalField(), default=0),Value(0)),
        ).values('company', 'location', 'district', 'new_made',
                 'recovery_made', 'category', 'angle_sum', 'detail_id', 'worked_date', 'total_sum')
    html_list = []
    for ctx in result:
        html_list.append(render_to_string('components/excel_file_list_group_item.html', ctx ))
    data = {
        'result':html_list
    }
    return JsonResponse(data)
// javascript

function toggle_show(ths){
            var data_day = ths.dataset.day;
            // var target_elements = $('.day'+data_day);
            var target_elements = $('div[name ="day'+ data_day +'"]');
            if (target_elements.hasClass("list-hide")){
                target_elements.removeClass('list-hide');
                return
            }
            else {
                target_elements.addClass('list-hide');
                return
            }
        }
        
// ...        

function uploaded_file_info_request(e){
        console.log(e);
        console.log(e.dataset.file_id);
        var coating_company_id = {{ coating_company_id }};

        var data = {
            'file_id':e.dataset.file_id,
            'coating_company_id':coating_company_id,
        };
        var path_url = "{% url 'ajax_get_uploaded_info' %}";
        $.ajax({
            type : 'post',
            url : path_url,
            data : data,
            dataType : 'json',
            error: function(xhr, status, error){
                alert(error);
            },
            success : function(json){
                console.log('on ajax');
                e.parentElement.parentElement.insertAdjacentHTML('afterend', json['result'][0]);
                for (var i = 0; i < json['result'].length; i++) {
                    e.parentElement.parentElement.insertAdjacentHTML('afterend', json['result'][i]);
                }
                e.onclick = function(){toggle_show(e);}; 
                console.log(e);
                console.log(json);
            },
        });
    }

이걸 위해서 지옥같은 쿼리식을 두 번이나 짰어야 했다.

이렇게 Ajax와 When, Sum, Case, prefetch_related, select_related, Q, F 를 이용해서 쿼리식을 최소한으로 줄였다. 그치만 내 스트레스는 줄이지 못했다.(...)

뿌듯하긴하다.

 

#

Comments