Cheondi
개발 · Unity

데이터 갱신 순서와 화면 결과

목록과 상세 화면의 갱신 순서 때문에 이전 값이 남는 문제를 데이터 의존성으로 정리한 기록입니다.

  • #unity
  • #state
  • #refresh

계좌를 바꾸면 목록은 새 데이터로 바뀌는데 상세 패널에는 이전 계좌의 값이 잠깐 남았다. 조금 뒤에는 정상으로 돌아와 네트워크가 느린 문제처럼 보였다. 요청 응답을 로그로 확인하니 데이터 자체는 맞았다. 같은 데이터를 받았지만 어느 화면을 먼저 비우고 그리느냐에 따라 결과가 달랐다.

기존 코드는 계좌 변경, 목록 응답, 선택 이벤트가 각각 화면 갱신을 호출했다. 빠른 응답과 느린 응답의 도착 순서가 바뀌면 마지막으로 그린 것이 최신이라는 보장이 없었다.

화면 간 의존성 기록

상세 패널은 선택된 목록 항목이 있어야 그릴 수 있었다. 그런데 목록이 바뀌기 전에 상세를 갱신하면 이전 선택을 사용했다.

계좌 변경
 -> 이전 선택 해제
 -> 목록 로딩 표시
 -> 새 목록 적용
 -> 유효한 항목 선택
 -> 상세 갱신

순서를 코드에 드러내니 상세 화면이 목록 변경 이벤트를 임의로 따라가는 구조보다 이해하기 쉬웠다.

응답 세대 번호

사용자가 계좌를 빠르게 두 번 바꾸면 먼저 보낸 요청이 나중에 도착할 수 있었다. 요청을 취소할 수 있으면 취소하고, 적용 단계에서도 현재 요청 세대와 같은지 확인했다.

int generation;

async Task ChangeAccount(int accountId)
{
    int current = ++generation;
    ClearSelection();
    var items = await LoadItems(accountId);

    if (current != generation) return;
    ApplyList(items);
    SelectFirstValidItem();
    RefreshDetail();
}

이 값은 최신 결과인지 확인하는 용도라 서버의 데이터 버전과는 다른 개념이었다.

이전 값 유지 정책

로딩 중에 이전 목록을 그대로 보여주면 화면이 덜 흔들리지만 다른 계좌의 정보로 오해할 수 있었다. 이 화면에서는 정확성이 더 중요해 선택과 상세를 즉시 비우고 로딩 상태를 표시했다. 화면마다 같은 선택이 정답은 아니었다.

빈 결과와 오류도 새 계좌의 상태로 확정해야 했다. 실패했다고 이전 계좌 데이터를 다시 보여주면 소속이 모호해졌다.

비동기 응답 도착 순서 설계

API 호출 코드를 처음 배울 때는 요청과 응답만 생각했다. 여러 화면과 이벤트가 연결되니 시작한 순서와 끝난 순서가 다를 수 있다는 점이 더 중요했다.

이 문제를 해결하며 화면 갱신을 단순한 Refresh() 호출 묶음으로 보지 않게 됐다. 어떤 데이터가 먼저 준비돼야 하는지, 오래된 결과를 어떻게 버릴지, 로딩 중 무엇을 보여줄지를 함께 정해야 했다. 같은 값도 순서가 다르면 다른 화면이 됐다.

후속 확인 항목

이 문제를 고친 뒤 관련 공식 자료를 찾아 읽으면서, 당시 코드에서 우연히 맞았던 부분과 규칙으로 남겨야 할 부분을 나눠 봤다.

RFC 9110은 HTTP 메서드의 의미와 멱등성을 구분한다. 같은 요청을 다시 보내도 의도한 서버 효과가 한 번과 같아야 멱등하다고 말할 수 있다. 클라이언트에서 버튼을 잠그는 것은 중복 조작을 줄이지만, 타임아웃 뒤 재전송이나 다른 기기 요청까지 막아 주지는 않는다.

또 상태 코드만으로 화면 상태를 모두 결정하기보다 응답이 어떤 요청과 선택 조건에 대한 것인지 확인해야 한다. 네트워크 성공과 현재 화면에 적용 가능한 응답은 다른 판단이다.

다음에 데이터 갱신 순서와 화면 결과 같은 문제를 보면 아래 순서부터 확인하려고 한다.

  • 재귀 호출 대신 명시적 반복과 상한
  • 동시에 실행되는 갱신 작업의 단일 소유자
  • 이전 세대 응답을 버리는 번호
  • 중단 뒤 사용자가 다시 시작하는 경로

재시도는 횟수보다 같은 요청을 식별하는 키와 서버의 중복 처리 규칙이 먼저 정해져야 한다.

공식 참고 자료