휴대폰이 잠들어도 채팅이 살아 있도록 하기
Mythex의 에이전트는 한 번의 요청에 몇 분 동안 작업할 수 있고, 휴대폰으로 지켜보고 있을 수도 있습니다. 휴대폰은 잠금이 걸리고, 앱을 바꾸고, 네트워크를 바꾸면서 작업합니다. 두 가지 버그가 이 상황을 망가뜨렸습니다. 하나는 "페이지가 다시 보입니다"라는 신호를 받을 때마다 채팅이 연결이 끊겼다고 판단하고 "연결 중…"에 멈출 때까지 반복하는 것이었습니다. 다른 하나는 새 채팅의 첫 답변 동안 휴대폰이 잠들었다가 돌아왔을 때 답변의 처음 몇 조각만 보이고, 수동으로 새로고침하기 전까지 그대로 있는 것이었습니다. 둘 다 고쳤습니다.
채팅이 실행 중인 턴을 따라가는 방식
에이전트가 작업하는 동안 채팅은 우리 서버에서 오는 스트림을 열어 둡니다. 각 단계(생각, 도구 호출, 답변의 한 조각)가 이벤트로 도착합니다. 스트림이 끊기면 채팅이 다시 연결하고, 서버는 최근 120초를 담은 버퍼에서 최근 이벤트를 다시 재생합니다. 턴이 끝나면 채팅은 서버에 저장된 기록도 요청하여, 화면에 보이던 것과 저장된 것을 맞춥니다. 이 마지막 단계는 스트림이 놓친 것을 잡아냅니다.
아무 일도 일어나지 않을 때 연결이 살아 있는지 죽었는지 알기 위해, 서버는 15초마다 작은 "핑"을 보냅니다.
버그 1: 깨어나는 것이 멀쩡한 스트림을 죽였다
브라우저는 페이지가 다시 보이게 되면 알려 줍니다. 탭을 바꾼 뒤나 화면 잠금이 풀린 뒤입니다. 우리는 그 신호를 연결을 확인하라는 신호로 썼습니다. 실제로는 스트림을 중단하고 다시 연결하는 신호였습니다. 숨겨진 동안 연결이 죽었을 것이라고 가정했기 때문입니다.
휴대폰과 내장 브라우저에서는 이 이벤트가 스트림이 죽는 것보다 훨씬 자주 발생합니다. 탭을 바꿀 때마다, 잠금을 풀 때마다, 그리고 일부 내장 브라우저에서는 몇 초마다 한 번씩 발생합니다. 그때마다 멀쩡한 스트림이 중단되었습니다. 채팅은 다시 연결하고, 재생하고, 또 중단되고, 결국 "연결 중…"에 머물렀습니다. 그러다가 에이전트가 아직 작업 중인데도 턴이 끝났다고 표시했습니다.
해결: 스트림이 말을 하고 있는지로 판단하기
이제 채팅은 프로젝트별 스트림이 마지막으로 무엇이든 받은 시각을 기록합니다. 이벤트와 15초마다 오는 핑 모두 포함합니다. "다시 보임" 신호는 25초 동안 조용했던 스트림만 중단합니다. 25초는 핑 간격에 느린 네트워크 구간을 더한 시간보다 길기 때문에, 살아 있는 스트림은 절대 해당되지 않습니다. 테스트는 핑이 살아 있다는 증거로 계산되는지, 그리고 방금 핑을 받은 스트림은 죽은 것으로 취급되지 않는지 확인합니다.
이 과정에서 얻은 교훈입니다. 페이지의 가시성은 페이지에 대한 정보이지 네트워크 연결에 대한 정보가 아닙니다. 연결에 직접 물어보세요.
버그 2: 휴대폰이 깨어난 뒤 답변이 절반만 보였다
이 버그는 iOS 시뮬레이터에서 매번 재현할 수 있었습니다.
- 새 채팅을 시작하고, 시간이 걸리는 요청을 보냅니다.
- 빌드 동안 휴대폰을 잠들게 둡니다.
- 턴이 끝나고 2분짜리 재생 버퍼가 만료된 뒤 돌아옵니다.
채팅에는 답변의 처음 몇 블록이 완료된 것으로 표시되었고, 페이지를 새로고침할 때까지 그대로였습니다.
원인
새 채팅에는 저장된 대화인 스레드가 없습니다. 첫 메시지가 스레드를 만들기 전까지는 없습니다. 그 생성은 전송 함수 안에서 일어나는데, 전송 함수는 이미 reconcile 단계를 캡처한 뒤였습니다. 그 reconcile은 여전히 스레드가 없다고 생각했습니다. 그래서 새 채팅의 첫 턴이 끝날 때 reconcile은 스레드가 없는 것을 보고 아무것도 요청하지 않고 조용히 반환했습니다.
보통은 아무도 알아채지 못했습니다. 스트림이 턴 전체를 이미 전달했기 때문입니다. 하지만 턴 동안 잠들었다가 120초 재생 버퍼가 만료된 뒤에 돌아온 휴대폰은 스트림에서 빠진 이벤트를 받을 수 없었습니다. 그 빈틈을 채워야 할 reconcile은 한 번도 실행되지 않았습니다.
해결
- 전송이 reconcile에 턴이 속한 스레드를 알려 줍니다. 그래서 새 채팅의 첫 턴도 다른 턴과 똑같이 reconcile됩니다. 답변이 돌아올 때 다른 채팅을 열고 있다면, 결과는 엉뚱한 곳에 보이는 대신 버려집니다.
- 실패한 reconcile은 다시 시도합니다. 나쁜 연결 상태에서 깨어난 휴대폰은 턴이 이미 끝났는데도 기록을 가져오지 못할 수 있습니다. 이제 그것을 기억해 두고, 탭이 다시 보이거나 네트워크가 돌아오면 다시 요청합니다.
- "작업 시간"을 정직하게 보여 줍니다. 각 답변에는 에이전트가 작업한 시간이 표시됩니다. 탭이 서버의 턴 종료 메시지를 보지 못했다면, 예전에는 자신의 시계로 그 값을 채웠습니다. 전송 버튼을 누른 시점부터 휴대폰이 깨어난 시점까지, 떠나 있던 시간까지 포함한 값이었습니다. 이제 그 수치는 추정치로 표시되고, 기록이 도착하면 저장된 실제 시간으로 바뀝니다.
얻은 교훈
- 사람들이 실제로 쓰는 기기에서, 실제로 쓰는 방식대로 테스트하세요. 두 버그 모두 "잘 작동하는 스트림"과 "주머니 속 휴대폰" 사이의 틈에 있었습니다.
- 한 번도 실행되지 않는 대비책은 대비책이 아닙니다. reconcile 단계는 바로 이 경우를 잡기 위해 있었지만, 조용히 건너뛰고 있었습니다.
- 복구도 다시 시도하세요. 휴대폰이 깨어나는 순간은 연결 상태가 가장 나쁠 때인 경우가 많습니다.
- 추측을 사실처럼 보여 주지 마세요. 잘못된 시계에서 나온 시간은 아무것도 없는 것보다 나쁩니다. 표시하거나 교체하세요.
이 작업에서 턴이 실행되는 동안 보이는 몇 가지도 함께 정리되었습니다. 셸 단계는 cd 대신 실제 작업을 하는 명령으로 표시되고, 채팅의 스크린샷은 페이지의 왼쪽 위를 유지하며 칼럼에 맞고, 새 프로젝트는 새로고침 없이 생성된 이름을 받습니다.
휴대폰에서 쓰는 앱을 직접 만들고 있다면, 모바일 친화적인 웹 앱 만들기와 네이티브 앱과 프로그레시브 웹 앱 비교에 기본 내용이 있습니다. 채팅 자체에 대해서는 채팅 문서를 참고하세요.