1 분 소요

결론만 보고싶으면 조금만 밑으로 내려주세요

시작

공공데이터포털 의 오픈API들을 동시에 여러개&번 요청하고 싶어졌다.

왜냐? 기존에 사용하던 for loop requests 형태는 한개의 요청을 하고 응답을 받은 다음 다른 한개의 요청을 하고 응답을 받는 형식인데

사용할 API중 개당 몇초나 걸리는 서비스가 있었기 때문이다. 개당 2초라 해도 5번만 쓰면 10초다.

첫 번째 시도 (실패)

아무튼 그래서 인터넷의 여러 예제를 살펴보고 제일 보편적으로 보이는 형태인 asyncio와 aiohttp를 사용해 시도해봤다.

import asyncio
import aiohttp

urls = [대충 링크들로 이루어진 list]

async def fetch(session, url):
  async with session.get(url) as response:
    return await response.json
    
async def fetch_all(urls):
  async with aiohttp.ClientSession() as session:
    results = await asyncio.gather()
    return results
    
asyncio.run(fetch_all(urls))

구글신이 알려준 대다수의 페이지에서 대략 이런 형식의 “세션을 가져오기” 후 “get하기”를 여러개 수행하는 형식의 코드가 있었다.

그래서 성공했는가?

실패했다. 아니 돼긴 됐다. 단지 말도 안 되게 오래 걸렸을 뿐이었다.

아무리 그래도 단 두번 호출하는데 첫 요청은 제대로 2초 걸리는데 두번째 요청 connect하는데 몇 분 걸렸다.

다른 일반적인 api (내가 시도해본건 포켓몬 api다) 또한 처음의 무작위 갯수의 요청은 바로 처리가 되었지만

그 이후는 느리게 띄엄띄엄 몇개씩 되다가 기본 timeout인 5분 제한에 걸려 에러를 띄우고 꺼졌다.

구글신께 물어봐도 비슷한 현상이 한두개씩 보이긴 했지만 결국 해결책은 없었다.

두 번째 시도 (성공?)

그래서 결국엔 레퍼런스를 펼쳤다. 뭔가 있지 않을까?

aiohttp 클라이언트 레퍼런스

쭉 읽어보면서 내리는 도중에 Basic API 를 발견했다.

ClientSession을 사용하는걸 추천하지만 “keepalive, 쿠키나 복잡한 연결 관련 무언가”가 필요 없는 간단한 HTTP 요청에는 Basic API도 좋다고 한다.

ClientSession 부분을 날리고 이걸 써 봤다.

import asyncio
import aiohttp

urls = [대충 링크들로 이루어진 list]

async def fetch(url):
  async with aiohttp.request('GET', url) as response:
    return await response.json
    
async def fetch_all(urls):
  tasks = [asyncio.create_task(fetch(url)) for url in urls]
  results = await asyncio.gather(*tasks)
  return results
    
asyncio.run(fetch_all(urls))

와! 타임아웃하던 포켓몬 api도 몇초만에 끝나고 기존에 사용하려 했던 요청당 2초 api도 여러개 요청해도 대략 2초쯤에 완료된다! 성공!

고찰

왜 그랬을까?

첫 방식은 복잡한 무언가를 지원하는 상황이라고 가정하고 작동하는데 지원하지 않아서 그런 일이 생겼던 것일까? 이건 더 찾아봐야 알 것 같다.

이 방식은 asyncio만 쓰고 coroutine 내부에서 requests를 쓴거랑 크게 차이가 없는건 아니지 않을까?

차이는 별로 없어도 이 방법이 더 낫지 않을까 싶다. 결국엔 requests는 블락하니까…

스레드 쓰면 되지 않는가?

고양이가 되어서 스레드가 엮인 실타래를 가지고 놀고싶다