구글 클라우드 회사 계정을 만들어서 이메일에서 필요한 데이터를 자동으로 추출해서 구글 시트에 기록하는 걸 만들고 있어요.
찾아보니 기본적으로 apps script가 있길래 한번 이거로 만들어봤더니 아주 만족스러웠습니다.
근데 아쉽게도 기본 기능은 시간트리거만 가능해서 이메일이 수신되었을 때 실시간으로 작동은 안되더라구요
구글 클라우드에 또 pub/sub 기능을 쓰면 이메일이 도착하면 작동하게 만들 수 있다고해서 적용을 해봤는데...
이게 작동을 하는데 로그를 보면 doPost가 몇번 실행됬음에도 불구하고 새 이메일이 있음에도 그걸 파악하지 못하고 처리가 안되거나 1회성 트리거가 작동했는데 불구하고요
혹은 처리가 되어도 계속해서 doPost가 실행되는 오류가 생깁니다.
수동으로 그냥 작동하면 잘 되는데...
지금도 수동으로만 써도 정말 만족스럽긴 합니다만 기왕 시작한거 완전 자동으로 처리되게 하고 싶은데 정말 쉽지 않네요...
도대체 뭘 확인을 해봐야할지 막막합니다.
### Gmail Pub/Sub 기반 Apps Script 프로그램 문제 요약
#### 1. **간략한 프로그램 구조**
- **목적**: Gmail에 새 이메일 도착 시 Pub/Sub 푸시 알림으로 받아서, 이메일 처리 로직(processUnreadInquiries())을 자동 실행. 처리 로직은 LLM(Gemini)으로 이메일 분류/추출 후 재고 조회, 답장 생성 등.
- **주요 컴포넌트**:
- **doPost(e)**: Pub/Sub 푸시 엔드포인트. 푸시 받은 historyId를 확인하고, PENDING_HISTORY_ID (가장 큰 historyId)를 PropertiesService에 저장. 즉시 200 OK 반환 (재시도 방지).
- **worker()**: 1회성 시간 기반 트리거로 호출됨. PENDING이 있으면 Gmail history를 증분 처리(_processGmailHistoryPush())해서 새 메시지만 골라 _processThread() 호출.
- **watchdog()**: worker가 안 동작할 때 재스케줄링 (2분마다).
- **processUnreadInquiries()**: 수동/폴링용. Gmail.search()로 UNREAD 이메일 찾아 처리 (이건 잘 동작함).
- **setupGmailWatch()**: Gmail watch 설정 (INBOX만, 7일 만료).
- **환경**: Apps Script (Gmail/Drive/Spreadsheet API 사용), Pub/Sub 토픽/구독 설정됨.
- **저장소**: PropertiesService로 historyId 관리 (LAST_HISTORY_ID: 마지막 처리 baseline, PENDING_HISTORY_ID: 대기 중 max historyId).
#### 2. **자세한 실시간 작동 구조**
- **푸시 수신 시 (doPost)**:
1. Pub/Sub가 푸시 → doPost가 JSON 파싱 (historyId 추출).
2. historyId가 LAST보다 크면 PENDING 업데이트 (max 값으로 유지).
3. worker 트리거 스케줄링 (_scheduleWorkerOnce(): 65초 후 1회성 트리거 생성).
4. watchdog 스케줄링 (180초 후, worker가 안 돌면 재생성).
5. 즉시 200 OK 반환 (ACK).
- **worker 실행 시**:
1. Lock 획득 후 PENDING 확인.
2. Gmail.Users.History.list()로 startHistoryId=LAST부터 messageAdded 이벤트만 뽑음.
3. 새 메시지(INBOX/UNREAD 확인)만 _processThread()로 처리 (max 20개).
4. 처리 후 LAST_HISTORY_ID = PENDING으로 업데이트, PENDING 삭제.
- **watchdog 역할**: worker 트리거가 유실되면 (e.g., 생성 후 안 터짐) 재생성.
- **전체 흐름**: 푸시 → 큐(PENDING) 쌓기 → 지연(1분 내) worker 처리 → 이메일 처리 후 아카이브/라벨링 (INBOX/UNREAD 제거로 후속 푸시 최소화).
#### 3. **주요 문제: 트리거 발화 불안정**
- **증상**: doPost가 푸시 받고 worker 트리거를 스케줄링(ScriptApp.newTrigger().timeBased().after(65000))하는데, 가끔 트리거가 안 터짐. (로그에 [SCHED] created worker... 나오지만, worker() 로그가 안 찍힘.)
- **가능 원인**:
- Apps Script 시간 기반 트리거 유실 (Google 서버 측 이슈, 쿼터 초과, 지연).
- Lock 경쟁: 여러 푸시가 동시에 와서 Lock.tryLock() 실패 → worker 스킵.
- 트리거 중복/삭제 누락: _scheduleWorkerOnce()가 Lock으로 보호되지만, race condition 가능.
- **영향**: 새 이메일 도착해도 처리 안 됨. 수동 processUnreadInquiries() 호출 시엔 잘 됨.
#### 4. **주요 문제: 302 반복 호출**
- **증상**: doPost가 여러 번 반복 호출됨 (하나의 이메일 도착에 3~4회 푸시). 로그에 [PUSH] incoming historyId=... 반복.
- **가능 원인**:
- Pub/Sub 재시도: doPost가 느리거나 오류 시 (e.g., PropertiesService 지연) 재시도. 하지만 코드상 항상 200 반환함.
- Gmail history 이벤트 다중: messageAdded 외에 labelAdded (e.g., UNREAD) 이벤트로 추가 푸시.
- 중복 푸시: Pub/Sub at-least-once 보장으로 동일 메시지 반복 전달.
- 304 관련?: Pub/Sub 로그에서 304 (Not Modified) 에러가 보이는데, 이는 watch 설정 문제나 캐싱 이슈일 수 있음. (Gmail watch가 historyId를 제대로 업데이트 못 함?)
- **영향**: doPost 반복 → PENDING 중복 업데이트 → worker가 여러 번 불필요 실행 (하지만 Lock으로 막힘).
#### 추가 문제/관찰
- **푸시 지연**: Pub/Sub latencyMs=100~500ms, 하지만 worker 지연으로 전체 1~2분 걸림.
- **쿼터 초과 위험**: 매 푸시마다 PropertiesService 호출 → 하루 1000회 초과 시 에러.
- **테스트 결과**: 수동 watch/stop 후 이메일 보내면 doPost 1~몇 회 찍히지만, worker가 가끔 안 돔. 폴링(processUnreadInquiries())은 100% 성공.
- **시도한 해결**: Lock 타임아웃 늘림, 트리거 clean 함수 추가, BigInt로 historyId 관리 – 부분 개선 but 여전.
제발 살려주세요
댓글 0