최근에 Android 앱에서 실험용 성능 로그를 Firestore에 쌓아두고 있었는데,
이 데이터를 좀 더 쉽게 확인할 수 있는 대시보드가 필요해졌다.
그래서 Retool을 활용해서 간단한 내부용 분석 페이지를 만들어보았다.
Firestore REST API를 그대로 호출하고, 받은 데이터를 Retool에서 가공해 UI로 보여주는 흐름이다.
Firestore 데이터를 가져오기 위한 기본 설정
Firestore 문서를 REST API로 읽기 위해 다음과 같이 GET 요청을 구성했다.
프로젝트 ID와 컬렉션 이름만 맞게 넣으면 바로 호출할 수 있다.
GET https://firestore.googleapis.com/v1/projects/{projectId}/databases/(default)/documents/metrics_log?pageSize=1000
여기서는 서버 로그가 꽤 많아서 pageSize를 직접 지정해 한 페이지에 100개씩 내려받도록 했다.
또한 Firestore REST API는 페이지네이션이 필요할 때 nextPageToken 값을 내려주기 때문에
이 값을 적절히 넘겨주면 다음 페이지를 이어서 읽을 수 있다.
아래 사진 처럼, url 파라미터를 통해 값을 넣어줬다.
Retool에서 페이징 처리 흐름 만들기
처음에는 단일 쿼리로 모든 데이터를 보고 싶었지만, Firestore REST API는 무조건 페이지 단위로 끊어서 내려준다.
그래서 다음 페이지를 로딩하는 버튼과 내부 상태를 관리하는 구조를 직접 만들었다.
- 숨겨진 TextInput 하나를 만들어 pageTokenInput 용도로 사용했다.
- query1의 URL 파라미터에서 pageToken={{ pageTokenInput.value }} 형태로 읽도록 구성했다.
- Firestore REST API는 페이지 단위로 데이터를 내려주기 때문에, 응답의 맨 아래에 nextPageToken 값이 함께 포함된다. 이 값을 다음 페이지 요청에 그대로 넘기면 이어서 데이터를 불러올 수 있다.

- “다음 페이지” 버튼에서는 Firestore 응답의
nextPageToken을pageTokenInput에 넣고 query1을 다시 실행하도록 만들었다
이렇게 구성하면 버튼을 누를 때마다 자연스럽게 다음 페이지 로그가 이어서 로딩된다.
페이지가 더 이상 없을 때는 nextPageToken이 null이므로 버튼이 자동으로 비활성화되도록 했다.
단말기별 필터링 기능
로그에는 여러 단말기에서 기록된 값이 섞여 있기 때문에 단말기별로 나눠서 보고 싶었다.
Firestore 응답 안의 deviceId 값들을 읽어서 Select 컴포넌트의 옵션으로 만들고,
테이블에 바인딩할 때 select1.value에 따라 필터링되도록 했다.
예시는 다음과 같은 방식이다.
const data = query1.data || {};
const docs = data.documents || [];
const deviceList =
[
{ label: "전체", value: "" },
...Array.from(
new Set(
docs.map(d => d.fields.deviceId?.stringValue).filter(Boolean)
)
).map(id => ({ label: id, value: id }))
];
필터를 바꾸면 테이블이 즉시 변경되어 단말기별 성능을 빠르게 비교할 수 있다.
평균 지표를 계산해서 요약 테이블 만들기
로그 항목이 많아지다 보니 개별 행만 보는 것으로는 성능 추세를 파악하기 어렵다고 느꼈다.
그래서 주요 지표의 평균값을 계산한 Summary 테이블을 따로 만들었다.
필요한 필드는 숫자 값으로 변환해 배열에 모은 뒤 평균을 구했다.
예를 들면 다음과 같은 구조다.
const rows = table1.data || [];
const list = rows.map(r => r.fasTotalMsNum).filter(v => v != null);
const avg = list.reduce((a, b) => a + b, 0) / list.length;
Retool에서 스코프 문제 때문에 URL 파라미터가 동작하지 않았던 이유
처음에는 Firestore 페이지네이션을 위해 pageTokenInput.value를query1의 URL 파라미터에서 그대로 참조하려고 했다.
그런데 Retool이 계속해서 "Unable to access 'pageTokenInput'" 라는 오류를 발생시켰다.
텍스트 인풋 이름을 바꿔봐도 같은 문제가 반복되었다.
이 오류의 원인을 찾아보니, 단순히 컴포넌트 이름의 문제가 아니라
Retool 내부의 스코프 구조 때문이었다.
Retool에서는 다음과 같은 규칙이 있다.
- 페이지 상단(앱 전체)에 존재하는 쿼리는 App scope
- 개별 페이지 내부에 놓인 컴포넌트는 Page scope
즉, 앱 전체에서 동작하는 Query는 페이지 내부에 있는 컴포넌트의 값을
직접 참조할 수 없다.
내 경우에도 다음과 같은 구조였다.
query1→ App scopepageTokenInput→ Page scope
그래서 URL 파라미터에서 다음과 같은 코드를 사용하면:
{{ pageTokenInput.value }}
Retool이 해당 컴포넌트를 찾지 못해서 스코프 오류가 발생한다.
Retool 입장에서는 “페이지 내부에 있는 값인데, 앱 전체에서 실행되는 쿼리가 왜 그걸 참조하냐”라는 식이다.
이 문제를 해결하기 위해 아래 사진과 같이 쿼리를 페이지 내부로 옮기는 방법을 사용했다.
query1을 Page scope로 이동시키자pageTokenInput.value를 정상적으로 참조할 수 있게 되었다.
결과적으로, Firestore 페이지네이션에 필요한 pageToken 전달이 제대로 동작했고
버튼을 통해 다음 페이지를 불러오는 기능이 정상적으로 완성되었다.
전체적인 구성 정리
정리해보면 이번 페이지에서는 다음과 같은 요소들을 Retool로 구성했다.
- Firestore REST API를 직접 호출하는 Query
- nextPageToken 기반 페이지네이션 로직
- 단말기별 필터링을 위한 동적 Select 옵션
- 주요 성능 지표에 대한 평균 요약 테이블
- 타임스탬프를 KST로 변환해 보기 쉽게 만드는 후처리
Firestore에 쌓아둔 실험 데이터를 별도 서버 없이도 손쉽게 시각화할 수 있었고,
지금은 실제 내부 검증 작업에서 빠르게 로그를 확인하는 용도로 잘 활용하고 있다.
Retool이 데이터 정제와 UI 조합을 거의 코드 없이 처리해줘서,
로그 기반 분석 도구를 만들 때 꽤 편하게 사용할 수 있었다!!