[TIL] 2022.11.09 Open API 서울 하수관 수위, 강우량-02

xddongx-hyeon2·2022년 11월 13일

1. 진행 상황

  • OpenAPI를 조회할 것이므로 Model 없이 진행
  • 하수관 데이터와 강우량 데이터를 다룰 것이므로 각각 클래스 생성
  • 최신 데이터 조회 API 개발
  • 추가 구현으로 각각 00시부터 현 시간까지의 데이터 조회 API 구현

2. 진행 상황 리뷰

(1) 하수관 수위

utils/drainpipe.py
class DrainPipe:
    def __init__(self, data, gu_name):
        self.IDN = data.get("IDN")
        self.GUBN = data.get("GUBN")
        self.GUBN_NAM = gu_name
        self.MEA_YMD = data.get("MEA_YMD")
        self.MEA_WAL = data.get("MEA_WAL")
        self.SIG_STA = data.get("SIG_STA")
        self.REMARK = data.get("REMARK")

    def __str__(self):
        return (
            f"IDN: {self.IDN}, GUBN: {self.GUBN}, GUBN_NAM: {self.GUBN_NAM}, "
            f"MEA_YMD: {self.MEA_YMD}, MEA_WAL: {self.MEA_WAL}, "
            f"SIG_STA: {self.SIG_STA}, REMARK: {self.REMARK},"
        )
  • 하수관 수위 클래스 생성
# utils/drainpipecontroller.py

class DrainPipeController(SeoulOpenApi):
    GUBN_CODE = {
        "종로구": "01",
        "중구": "02",
        "용산구": "03",
        "성동구": "04",
        "광진구": "05",
        ...
	}
    
    ...
    def get_result(self, datas, set_len):
        """
        latest 결과값 추출
        """
        result = []
        for data in datas[: -(set_len + 1) : -1]:
            result.append(DrainPipe(data, self.gu_name))

        return result
        ...

(2) 강우량

# utils/rainfall.py
class RainFall:
    def __init__(self, data):
        self.RAINGAUGE_CODE = data.get("RAINGAUGE_CODE")
        self.RAINGAUGE_NAME = data.get("RAINGAUGE_NAME")
        self.GU_CODE = data.get("GU_CODE")
        self.GU_NAME = data.get("GU_NAME")
        self.RAINFALL10 = data.get("RAINFALL10")
        self.RECEIVE_TIME = data.get("RECEIVE_TIME")

    def __str__(self):
        return (
            f"RAINGAUGE_CODE: {self.RAINGAUGE_CODE}, RAINGAUGE_NAME: {self.RAINGAUGE_NAME}, GU_CODE: {self.GU_CODE}, "
            f"GU_NAME: {self.GU_NAME}, RAINFALL10: {self.RAINFALL10}, RECEIVE_TIME: {self.RECEIVE_TIME}"
        )
  • 강우량 클래스 생성
# utils/rainfallcontroller.py
class RainFallController(SeoulOpenApi):
    def __init__(self, gu_name):
        super(RainFallController, self).__init__()
        self.function_name = "ListRainfallService/"
        self.gu_name = Util().get_gu_name(gu_name)
    ...
    
	def get_result(self, datas, set_len):
        # 최신 데이터 리스트
        result = list(map(lambda x: RainFall(x), datas[:set_len]))

        return result
    ...
    

3. Today I Learned

SeoulOpenApi 클래스 생성

OpenAPI를 요청할 때 필요한 기본 틀의 클래스를 생성하여 요청할 api의 클래스에서 상속받아 사용하게끔 만들어 주었다.

DrainPipeController 클래스

SeoulOpenApi 클래스를 상속받아 전반적인 작업을 수행하는 클래스이다. 요청 시간, url 생성, 데이터 추출 등의 기능을 관리한다.

RainFallController 클래스

SeoulOpenApi 클래스를 상속받아 전반적인 작업을 수행하는 클래스이다. url 생성, 데이터 추출 등의 기능을 관리한다.

하루 데이터

강우량은 하루 데이터를 구하기 위해 간단한 계산으로 금방 구현이 가능하였다. 하지만 하수관 데이터는 시간별로 데이터를 조회해야 하므로 많은 url 생성과 요청이 필요하다.

  • url list 만들기
    1. 00시 ~ url -> 리스트에 추가
    2. total_count와 end와 비교 => 처음 얻으면 다른 시간 데에도 같은 카운트...
    3. end가 크면 다음 시간, end가 작다면
      3-1. start += 1000, end += 1000
      3-2. url 리스트에 추가, 다시 3번으로
    4. 다음 시간으로
    이러한 순서로 url 리스트 생성(조회하는 마다 결과 개수가 달라 주의)
  • 만들어진 url 리스트로 데이터 수집하여 해결...
  • 단순 for 문으로 데이터를 조회하는 데 시간이 걸려 다른 방법을 찾아보았다.

for 문 VS ProcessPoolExecutor VS ThreadPoolExecutor

ProcessPoolExecutor
멀티프로세싱은 프로세스를 여러 개 두어 병렬로 처리하는 방법이다.

# Test
* max_work = 3 >>> 6.709592342376709
* max_work = 4 >>> 6.519164562225342
* max_work = 5 >>> 6.145772695541382
* max_work = default >>> 5.745122671127319
* max_work = 10 >>> 9.75408148765564
...
with ThreadPoolExecutor(max_work=) as executor:
	result = sum(executor.map(drain.test_method, url_list), [])
    ...
ProcessPoolExecutor 최고 속도 max_work=default로 했을 때 9.8

ThreadPoolExecutor
스레드 기반의 병렬 처리를 지원하는 모듈이다.

# Test
* max_work = 3 >>> 3.136035919189453
* max_work = 4 >>> 2.3571252822875977
* max_work = 5 >>> 2.1027050018310547
* max_work = 6 >>> 1.7558386325836182
* max_work = default >>> 1.4996910095214844
* max_work = 10 >>> 1.2695400714874268
* max_work = 20 >>> 1.1200051307678223
* max_work = 30 >>> 1.0350220203399658
* max_work = 40 >>> 1.0153048038482666
* max_work = 50 >>> 0.9869108200073242
* max_work = 60 >>> 1.06866455078125
...
with ThreadPoolExecutor(50) as executor:
	result = sum(executor.map(drain.test_method, url_list), [])
...
ThreadPoolExecutor 최고 속도 max_work=50로 했을 때 0.99

속도 비교

  • for문 : 8.9초
  • ProcessPoolExecutor: 9.8초
  • ThreadPollExecutor: 0.99초

약 8배 정도의 시간 차이가 있어 ThreadPoolExecutor를 사용하여 데이터 조회 시간을 단축할 수 있었다. 그러나 모든 데이터를 하나의 응답하다 보니 응답시간은 최악이 되었다. pagenation이 필요한 상황이 되었다. 다음엔 pagenation을 같이 구현해야겠다.

0개의 댓글