Cheondi
개발 · API

조회 기간을 바꾸면 왜 이전 결과가 돌아올까

날짜 범위를 빠르게 바꿀 때 오래된 응답이 최신 목록을 덮는 문제를 추적한 기록입니다.

  • #api
  • #async
  • #date-range

거래 내역 화면에서 기간을 한 달로 바꿨다가 바로 일주일로 바꾸면 가끔 한 달치 결과가 나타났다. 날짜 버튼에는 일주일이 선택돼 있어서 화면과 목록이 서로 다른 말을 했다. 서버 응답의 날짜가 잘못됐다고 생각했지만 각 요청 결과는 모두 정확했다.

문제는 한 달 요청이 더 느리게 끝나 마지막에 화면을 덮는 것이었다. 사용자의 최신 선택과 네트워크의 완료 순서는 같지 않았다.

요청 조건을 응답까지 들고 갔다

요청을 보낼 때 현재 화면 값을 나중에 다시 읽지 않고 그 순간의 범위를 불변 값으로 만들었다.

public record DateRange(DateTimeOffset From, DateTimeOffset To);

async Task LoadAsync(DateRange requested)
{
    var result = await api.LoadHistory(requested);
    if (requested != currentRange) return;
    Render(result);
}

응답 시점에 요청 조건과 현재 조건이 같은지 비교하니 오래된 결과를 버릴 수 있었다.

날짜의 끝 경계를 명확히 했다

2024-08-31까지라는 조건을 23시 59분 59초로 만들면 더 작은 시간 단위가 생길 때 누락될 수 있었다. 내부에서는 시작 이상, 다음 날 시작 미만의 반열린 구간으로 다뤘다.

2024-08-01 포함 <= timestamp < 2024-09-01 제외

사용자 시간대에서 선택한 날짜를 서버 기준 시각으로 변환하는 위치도 한 곳으로 모았다.

로딩 표시도 요청별 상태였다

첫 요청이 끝나며 로딩을 끄면 두 번째 요청이 아직 진행 중인데도 빈 화면처럼 보였다. 현재 요청 ID와 일치할 때만 로딩 상태를 종료했다. 새 요청이 시작되면 이전 요청은 가능하면 취소하고, 취소할 수 없어도 결과를 적용하지 않았다.

오류 메시지도 오래된 요청의 실패가 최신 성공 위에 나타나지 않도록 같은 기준을 사용했다.

필터 UI와 데이터는 한 상태였다

예전에는 날짜 버튼과 목록을 별개로 갱신했다. 이번에는 현재 조회 조건, 로딩, 결과, 오류를 하나의 화면 상태로 생각했다. 화면에 보이는 필터가 어떤 결과를 만들었는지 연결이 분명해졌다.

비동기 문제는 코드가 동시에 실행돼서만 생기는 게 아니었다. 서로 다른 시간에 시작한 올바른 결과 중 무엇이 현재 화면의 것인지 정하지 않은 게 원인이었다. 이후 검색이나 필터 API를 붙일 때는 요청 조건과 응답 적용 조건을 함께 설계하게 됐다.