[Django] CPU 사용량 증가 원인 파악(FilterSet model)

2024. 2. 16. 08:54Python/Django

목차

    이슈 상황

    • FilterSet의 Meta에 model을 추가하게 되면 해당 모델의 FK(Foreign Key)가 fields에 포함되면 CPU사용량이 급격하게 증가하는 상황이 발생
    • CPU가 높은 AWS EC2를 사용하게되면  문제가 되지 않지만 docker로 동작하게되면 cpu가 적어 timeout이 발생하거나 속도가 매우 느려짐

     

    FilterSet 검증

    1. 문제의 FilterSet

    class StudnetFilterSet(FilterSet):
        class Meta:
            model = Student
            fields = [
                'id', 'status', 'name',
                'parent', # FK
            ]

    CPU 사용정도

    • 38478ms = 38.478s

    문제의 FilterSet CPU 사용량


    2. Meta의 model 제거 시

    class StudnetFilterSet(FilterSet):
    	id = filters.NumberFilter(help_text='pk')
    	status = filters.NumberFilter(help_text='상태')
    	name = filters.CharFilter(help_text='이름')
    	parent = filters.NumberFilter(help_text='부모')
        class Meta:
            fields = [
                'id', 'status', 'name',
                'parent', # FK
            ]

    문제점

    • model이 정의가 되어 있지않으면 각 필드에 대해서 전부 재정의 필요

    CPU 사용정도

    • 971ms = 0.971s
     

    model 제거 시 CPU 사용량


    3. Meta의 fields FK filter 재정의 시

    방법 1.

    class StudnetFilterSet(FilterSet):
    	parent = filters.NumberFilter(help_text='부모')
        class Meta:
        	model = Student
            fields = [
                'id', 'status', 'name',
                'parent', # FK
            ]

    방법 2.

    class StudnetFilterSet(FilterSet):
        class Meta:
        	model = Student
            fields = [
                'parent', # FK
            ]
            filter_overrides = {
                models.ForeignKey: {
                    'filter_class': django_filters.NumberFilter,
                },
            }

    CPU 사용정도

    • 824ms = 0.824s

    FK filter 재정의 시 CPU 사용량

     

    FilterSet 내부동작 확인

    1. Filter 내부엔 DB Field에 따른 filter가 정의 되어 있다.(FILTER_FOR_DBFIELD_DEFAULTS 확인)

    FILTER_FOR_DBFIELD_DEFAULTS = {
    	...
        
        # Forward relationships
        models.OneToOneField: {
            'filter_class': ModelChoiceFilter,
            'extra': lambda f: {
                'queryset': remote_queryset(f),
                'to_field_name': f.remote_field.field_name,
                'null_label': settings.NULL_CHOICE_LABEL if f.null else None,
            }
        },
        models.ForeignKey: {
            'filter_class': ModelChoiceFilter,
            'extra': lambda f: {
                'queryset': remote_queryset(f),
                'to_field_name': f.remote_field.field_name,
                'null_label': settings.NULL_CHOICE_LABEL if f.null else None,
            }
        },
        models.ManyToManyField: {
            'filter_class': ModelMultipleChoiceFilter,
            'extra': lambda f: {
                'queryset': remote_queryset(f),
            }
        },
    
        # Reverse relationships
        OneToOneRel: {
            'filter_class': ModelChoiceFilter,
            'extra': lambda f: {
                'queryset': remote_queryset(f),
                'null_label': settings.NULL_CHOICE_LABEL if f.null else None,
            }
        },
        ...

     

    2. remote_queryset(field) 로직파악

    • 아래 내용처럼 field에 해당하는 related_model을 가져온다. CPU 사용량이 증가하는 이유로 추정됨.
    def remote_queryset(field):
        """
        Get the queryset for the other side of a relationship. This works
        for both `RelatedField`s and `ForeignObjectRel`s.
        """
        model = field.related_model
    
        # Reverse relationships do not have choice limits
        if not hasattr(field, 'get_limit_choices_to'):
            return model._default_manager.all()
    
        limit_choices_to = field.get_limit_choices_to()
        return model._default_manager.complex_filter(limit_choices_to)

     

     

    결론

    • model을 정의해야되는 경우에 FK field의 related_model에 대한 filter를 사용하지 않는다면 filter_overrides를 사용하거나 field를 재정의 해줘야한다.