굿바이 다이아

2024년 8월 9일 금요일

Today I Learned

날짜

2024년 8월 9일 금요일

내용

대기열 상태추적

아임리포트에서 로우데이터 생성 요청을 보내면 서버는 로그를 만들고 True를 반환한다. 클라이언트는 True를 반환받으면 로우데이터에 대한 GET 요청을 보내는데, 이떄 각 로우데이터는 현재 상태를 가지고 있다. 이때 현재상태라 함은 이 로우데이터와 연관된 로그 중 가장 최근에 만들어진 것의 상태를 의미한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class RawDataImportStatus(IntEnum, Enum):
    """
    ENUM: 로우데이터 수집 상태

    | key | value | description |
    | --- | --- | --- |
    | FAILED | 0 | 실패 |
    | CREATE_WAITING | 1 | 스프레드시트 생성 대기 중 |
    | UPDATE_WAITING | 2 | 실시간 업데이트 대기 중 |
    | CREATING | 3 | 스프레드시트 생성 중 |
    | IMPORTING | 4 | 플랫폼에서 데이터 수집 중 |
    | INSERTING | 5 | 스프레드시트에 데이터 삽입 중 |
    | ADDING | 6 | 템플릿 추가 중 |
    | SUCCESS | 7 | 완료 |
    """

    FAILED = 0
    CREATE_WAITING = 1
    UPDATE_WAITING = 2
    CREATING = 3
    IMPORTING = 4
    INSERTING = 5
    ADDING = 6
    SUCCESS = 7

이 중 하나의 값을 가진다. 로우데이터를 생성할 때, 현재 생성중인 것이 없으면 바로 3으로 넘어가지만, 무언가 생성중일 경우 1이나 2로 넘어가 대기상태에 돌입한다.

클라이언트는 현재 임포팅을 진행중인(3~6) 로우데이터가 있다면 10초에 한번 status 요청을 보내고 서버는 상태를 반환한다. 만약 완성(7)되면? 서버에선 현재 대기열(1~2) 상태의 로우데이터가 있는지 확인하고 있다면 그 값을 3으로 바꿔준다. 클라이언트 입장에선 7을 받고 다시 GET 요청을 보내는데, 또 다른 로우데이터가 진행중(3~6)이므로 다시 status를 계속 보내게 된다. 이게 우리가 구현한 대기열 시스템의 로직이다.

전혀 예상치 못한 부분에서 문제가 생겼다. 임포팅 하나가 끝나고 그 다음 대기열에 있는 것이 시작되려면 status 요청이 있어야 한다. 근데 유저가 임포팅 해놓고 너무 오래걸려서 창을 꺼놓는다면? status 요청은 발생하지 않게 된다. 따라서 기존에 진행중이던 로직이 끝나더라도 대기열 첫번째에 있는 임포팅이 진행되지 않는다. 이걸 생각 못했다…

어쩔수 없이, 재귀함수 형태로 바꿨다. 로직을 총괄하는 함수를 만들고 함수가 종료되는 시점에 대기열에 있는 것이 있는지 확인한다. 있다면 스스로를 다시 호출해서 로직이 또 진행되도록 만들었다. 진작에 이렇게 만들지 않은 이유는, 애초에 이 프로세스가 AWS Lambda에서 호출할 거라고 예상했기 때문이다. 람다에는 15분이라는 시간 제한이 있어 하나의 람다 호출내에서 여러가지 임포팅을 진행하기 무리일 거라고 생각했고, 하나의 임포팅이 하나의 람다를 호출하도록 설계했었다. 이제는 ECS 태스크를 이용하니 별도의 시간제한이 없어 재귀로 호출해도 무방하다. 굿

드디어 찾은 원인

드디어 어디가 잘못되었는지 찾았다. 이전에 수정한대로, 결국 오더는 중복 생성되었고 메시징로그도 중복생성 됐었지만, 메일을 발송할 떄 로직을 추가하여 제대로 한번만 보내도록 처리했었다. 이로 인한 부작용은 발송메일 생성 내역에 중복되어 나타나는 것과 거기서 즉시 발송버튼을 눌렀을 경우 제대로 작동하지 않는 문제가 있었다.

나는 유지보수 중 특정 경우에 갑작스레 클라이언트에서 샵 액세스 코드를 담지 않은 요청을 보내고 이로인해 0 Unknown error 가 뜨는 현상을 알아보려고 열심히 추적하고 있었다. 알아 보던 중, 과거 어떤 시점에 우리는 고객들에게 리뷰요청 메일을 중복해서 보내고 있었는데 이때 작성했음에도 다시 메일을 받고 작성하려고 접근하는 고객에게서 발생하는 문제가 아닐까? 하는 근거없는 가설을 세웠다.

이걸 확인해보고자 주문을 생성하고 메일을 보내려는데 메일이 이상한 오류를 띄우며 안보내질 때가 있었다… 이건 또 뭔가 싶어서 열심히 알아보던 중 과거의 주문 데이터들을 발견했는데 과거에는 주문 데이터가 한 개 만 있다. 이떄 아차 싶었다. 쇼피파이에서 똑같은 주문이 수정(발송내역, 환불 내역 등)되어 갱신된 데이터를 보낼 경우 새로 만들지 않고 이미 있는 주문데이터를 찾아 고쳐주는 로직은 이미 존재하고 있었다. 다만 제대로 작동하고 있지 않았을 뿐… 따라서 현재 어떤 이유로 오더가 중복되서 생성되는게 문제일 뿐, 메시징로그를 생성하고 보내는 것에는 이상이 없을 거라고 생각했다.

문제가 되는 부분은, 주문 객체를 생성하고 난 이후, 그 객체의 id가 없다면 기존에 없다고 판단하여 새로 생성하는 부분이었다. 하지만 그 객체에는 데이터베이스에서 primary key로 사용하는 Id가 있을리가 없다. 데이터베이스에서 조회해 가져온 것도 아니고, flush로 임시로 붙여놓지도 않았으니까.. 그냥 객체를 만들었을 뿐이다. 당연히 id는 없다.

이걸 조건을 지우면 해결되겠다 싶었는데, 각주에 그 부분을 수정할 경우 order importer에 문제가 생길 수 있다고 경고 하고있었다. 하… 하지만 지우지 않고서는 주문이 중복생성되는 걸 막을 수가 없다.. 일단 지우고 order importer 부분을 또 뜯어봤다. 슬프게도 여기는 애초에 고장나있었다…

csv로 고객이 올린 주문데이터를 데이터베이스에 저장해야 하는데, 제대로 안되고 있었다. 첫번째론 실제 론 11개의 칼럼만 필요하다고 되어있었으나 실제 코드에선 13개의 칼럼을 참조하고 있었다. 웹푸시나 sms 사용 여부에 대한 칼럼까지 포함되어 있었기 떄문인데, 과거에 우리가 이 기능을 사용하지 않기로 했었다. 그래서 칼럼에도 필요하지 않다고 생각해서 일부분을 수정했는데 남은 부분이 있었나보다. 이 부분을 지워주고, csv에서 데이터 처리하는 부분을 좀 손봤더니 해결됐다.

얏호!

동일한 네이버 계정

아임리포트가 끝나는 시점에 충격적인 사실을 들었다. 사실 광고대행사에서는 하나의 네이버 계정을 쓴다. 더 정확히 말하자면 한 계정에서 신청해서 받은 API KEY를 쓴다. 이 키는 네이버 검색광고 API에 접근하기 위한 키다. 네이버에선 API 통신시 하나의 키가 초당 20~30회 이상 요청을 보낼경우 429를 반환한다. 너무 많은 요청이 한번에 들어왔단 뜻이다. 문제는 이게 한 계정이 사용할 때도 함수가 너무 빨리 작동되면 이 429를 반환할 때가 있다.. 그래서 현재는 재귀로 어느정도의 반복 호출에 대한 대비는 해놨다.

예를 들어, 우리 MX에 있는 분들이 febreast 계정의 API key를 자신의 계정에 등록하고 아침에 업데이트가 발생하면 어떻게 될까. 당연히 초당 30회는 훌쩍 넘어갈거다. 한명이 한개로 호출해도 넘을 때가 있는데 여러명이, 동시에 여러 요청을 보내면 처리할 리가 없다.

이 부분을 어떻게 해야할까. 동시 진행 방지를 하나의 광고계정 기준이 아닌 네이버 검색광고 API Key를 기준으로 동시 진행이 불가능하도록 해야할까? 그럼 아침 7시 30분에 일괄적으로 업데이트하는 데이터는 어느세월에 다할까. 큰일이다 허허

회고

좋은 PO님이 떠났다. 안녕…