• 카테고리

    질문 & 답변
  • 세부 분야

    풀스택

  • 해결 여부

    해결됨

pandas 로 csv 읽어서 django model 에 저장하는데 속도 느려지는 이슈 있음. 질문드립니다.

23.02.14 17:02 작성 조회수 469

0

 

200여개의 csv 파일이 있습니다. (용량은 각각 1메가에서 120메가 - 최대 100만건 데이터 등등 ). 결측치 가 있어서 판다스 에서 불러들여서 정리하고 for 반복문으로 파일 개별적으로 읽어 들여와 장고 모델에 save() 로 입력시키는 작업을 진행하고 있습니다.

초반 10여개 파일까지는 제법 속도가 나오는데 (7만행 데이터 20분 소요) 이후로 속도가 급격하게 감소해서 24시간 돌려서 30메가 파일 겨우 저장 중입니다(1건에 1초씩 걸리네요 ㅠㅠ). 개발중이라 로컬에 있는 장고 내장 sqlite 사용 했습니다. 속도를 좀 더 빠르게 하는 방법이 있을까요? 3일째 검색 해봤는데 별다른 해결책이 보이지 않아서 질문 남겨 봅니다.

 

app.py

# new 폴더에 정리된 csv 파일을 읽어서 DB에 저장


import pandas as pd

# django 프로젝트에 있는 settings.py 파일을 읽어서 환경변수로 설정
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dbking.settings")
import django
django.setup()

#django 프로젝트에 있는 models.py 파일에서 BasicData 클래스를 읽어온다
from common.models import BasicData


# new 폴더에 있는 파일명을 읽어서 product 변수에 리스트에 저장
product_list = os.listdir("./script/newdb")
# product_list 에 csv 파일 정렬(오름차순)

for x in product_list:

    # csv 파일 하나씩 읽어오기
    df = pd.read_csv("./script/newdb/" + x, encoding="cp949")
    
		# 결측치를 0으로 채운다
    df = df.fillna(0)

    for a in list_of_csv:

        # 파일마다 컬럼수가 달라서 remark1, remark2 라는 예비컬럼 2개 추가
				# -> 인덱스 에러 나는 경우 0 으로 저장
        if a[16] is None:
            a.insert(16, 0)
            a.insert(17, 0)
        elif a[17] is None:
            a.insert(17, 0)

        # DB에 저장
        try:
              db_insert = BasicData(
                        opnSvcId = a[2],
                        opnSfTeamCode = a[3],
                        mgtNo = a[4],
                        fileNumber = fileNumber,
                        businessType = businessType,
                        opnSvcNm = a[1],
                        apvPermYmd = a[5],
                        confirmNumber = a[6],
                        businessCondition = a[7],
                        siteTel = a[8],
                        sitePostNo = a[9],
                        siteWhlAddr = a[10],
                        rdnWhlAddr = a[11],
                        rdnPostNo = a[12],
                        bplcNm = a[13],
                        latitude = a[14],
                        longitude = a[15],
                        remark1 = a[16],
                        remark2 = a[17],
              )
              i += 1
              # print(i)

        except Exception as e:
            print("쿼리", e)
            continue

				#DB에 저장 입력
        try:
            db_insert.save()
        except Exception as e:
            print("저장중에러",e)
            continue

 

from django.db import models


class BasicData(models.Model):

    # 개방서비스아이디
    opnSvcId = models.CharField(max_length=100)
    #개방자치단체코드
    opnSfTeamCode = models.CharField(max_length=10)
    # 관리번호
    mgtNo = models.CharField(max_length=100)


    #파일번호
    fileNumber = models.IntegerField()
    #업종명
    businessType = models.CharField(max_length=100)
    #개방서비스명
    opnSvcNm = models.CharField(max_length=100)

    #인허가일자
    apvPermYmd = models.DateField()
    #영업상태구분코드(1-정상, 2-폐업, 3-휴업, 4-전환)
    confirmNumber = models.IntegerField()
    #영업상태명
    businessCondition = models.CharField(max_length=100)
    #소재지전화
    siteTel = models.CharField(max_length=100)
    #우편번호
    sitePostNo = models.CharField(max_length=100)
    #주소
    siteWhlAddr = models.CharField(max_length=100)
    #도로명주소
    rdnWhlAddr = models.CharField(max_length=100)
    #도로명우편번호
    rdnPostNo = models.CharField(max_length=100)
    #사업장명
    bplcNm = models.CharField(max_length=100)

    # 위도
    latitude = models.FloatField()
    # 경도
    longitude = models.FloatField()

    #비고1
    remark1 = models.CharField(max_length=100)
    #비고2
    remark2 = models.CharField(max_length=100)

    # 생성시점
    created = models.DateTimeField(auto_now_add=True)
    update = models.DateTimeField(auto_now=True)


    def save(self, *args, **kwargs):
        queryset = BasicData.objects.filter(mgtNo__exact=self.mgtNo)
        # 중복된 이름이 없을 때만 저장
        if len(queryset) == 0:
            super().save(*args, **kwargs)
            print('> Created new category')
            # if '&' in self.addr:
            #     self.addr = self.addr.replace('&', ' ')
            #     self.save()
        # 중복된 카테고리 있을 시 저장 안함
        else:
            print('> Cannot create category with existing name')


    def __str__(self):
        return self.name

답변 1

답변을 작성해보세요.

0

안녕하세요.

일단 매번 INSERT를 하는 것은 비효율적입니다. bulk_create가 도움이 되겠구요.

그런데 모델의 save 메서드 내에서 .filter 를 수행하고 저장하는 것은 데이터가 커질 수록, 동작이 느려질 수 밖에 없습니다. 게다가 mgtNo 컬럼에 인덱스가 없다면 없는 대로 .filter 시에 속도가 느려질 테고, 인덱스가 있다면 매번 insert 시마다 인덱스 생성에 비용이 발생될 수 밖에 없습니다.

그리고, len(쿼리셋) 은 쿼리셋의 Rows를 메모리에 로딩하고, 이 리스트에 대한 길이를 체크하는 것이기에 존재유무 체크에서는 비효율적인 접근입니다. 쿼리셋.exists() 가 더 적합합니다.

그런데, .save 함수는 저장의 책임만 있을 뿐, 값 변환 및 유효성 검사의 책임은 없습니다. 이에 대한 책임은 Form이나 Serializer에서 수행하는 것이 맞습니다. .save 로직을 제거하시는 것이 맞습니다.

pandas 단에서 미리 데이터 전처리를 모두 수행하신 후에, 전처리된 값들로 모델 인스턴스 리스트를 생성하신 후에 .bulk_create 를 활용해보시면 어떠실까 싶습니다.

그리고, 성능을 좀 더 개선해보신다면, 데이터가 아주 아주 많을 경우에는

pandas로 전처리하신 후에 INSERT할 데이터를 CSV 로 만드시고,

데이터베이스의 csv import 기능을 활용하시어 데이터를 insert 하는 것이 성능적으로는 좀 더 빠를 수 있습니다.

화이팅입니다. :-)

 

강사님을 여러 채널로 접하고 있지만 항상 감사합니다. 말씀해주신 부분들 하나씩 다시 확인해보겠습니다.