[해결] GA4 전날 데이터를 하루 한 번 조회하고 CloudFront로 캐시하는 방법
GA4 Data API 호출 쿼터 초과를 막고 응답 속도를 100배 향상시키기 위해 EventBridge, S3, CloudFront 캐시 시스템을 연동한 방법입니다.
[해결] GA4 전날 데이터를 하루 한 번 조회하고 CloudFront로 캐시하는 방법
한 줄 요약: 서비스 운영 대시보드나 사용자용 주간 보고서를 띄울 때 매번 비용이 발생하는 GA4 Data API 실시간 쿼리를 날리는 대신, 하루 1회 EventBridge 크론으로 전날 데이터를 인출해 S3 저장 후 CloudFront Edge에 캐시하여 API Quota 낭비와 응답 대기 시간을 완전히 제거했습니다.
1. 어떤 문제가 발생했나
GoodFruit 서비스에 "최근 7일간 가장 인기 있었던 과일 Top 5"나 "주간 과일 분석 보고서" 데이터를 사용자에게 노출하려 할 때, 클라이언트가 페이지를 열 때마다 실시간으로 GA4 Data API를 직접 쿼리하도록 구현하면 심각한 이슈가 일어납니다.
- **GA4 API Quota(할당량) 초과 에러**: 구글 analyticsdata.googleapis.com API는 속성당 시간당/일당 토큰 호출 제한(Token Quota Limit)이 존재합니다. 트래픽이 몰리면 `429 Rate Limit` 에러가 터집니다.
- **응답 속도 지연**: GA4 Data API 실행 응답 시간은 보통 800ms ~ 1.5초 이상 소요되어 페이지 로딩이 답답해집니다.
- **비용 증가**: 서버리스 Lambda가 매 쿼리마다 실행되므로 컴퓨팅 비용이 불필요하게 낭비됩니다.
2. 기존에는 어떻게 구현했나
초기 시도에서는 대시보드 요청 API(GET /api/weekly-report)가 들어올 때마다 Lambda가 즉시 GA4 Data API를 실시간 조회(On-demand API Fetching)하도록 구현했습니다.
// 기존 시도 (실시간 쿼리 - 429 오류 및 속도 지연 발생)
app.get("/api/weekly-report", async (req, res) => {
const data = await analyticsClient.runReport({ ... }); // 1초 대기
res.json(data);
});3. 원인은 무엇이었나
어차피 "전날(Yesterday)까지의 운영 데이터"는 하루 동안 변하지 않는 고정된 지표임에도 불구하고, 모든 요청마다 실시간 쿼리를 재실행한 비효율적인 설계가 원인이었습니다.
4. 실패했던 방법
- **Lambda In-memory Caching**: Lambda 글로벌 변수에 캐시했으나, Lambda 인스턴스가 콜드 스타트되거나 수평 확장(Scale-out)될 때마다 캐시가 파기되어 GA4 API 쿼리가 여전히 수백 번 쏟아짐.
5. 최종 해결 방법
Scheduled Batch Fetching & CloudFront CDN Caching Pattern을 구축했습니다.
- **EventBridge Scheduled Rule (하루 1회 실행)**: 매일 새벽 01:00 UTC에 `report-api` Lambda를 단 1회만 실행합니다.
- **S3 정적 파일 저장**: 전날 지표 데이터를 `reports/latest.json` 및 `reports/YYYY-MM-DD.json` 형태로 Amazon S3 버킷에 기록합니다.
- **CloudFront CDN Cache-Control 지정**: S3 업로드 시 `Cache-Control: public, max-age=86400` (24시간 캐시) 헤더를 지정합니다.
4. CloudFront Invalidation (캐시 즉시 갱신): 새 일별 보고서 업로드 완료 직후 AWS SDK CreateInvalidationCommand를 호출하여 CloudFront Edge의 reports/latest.json 캐시를 무효화(Invalidate)합니다.
6. 수정 전후 코드
`infra/report-api/index.mjs` (S3 정적 저장 및 CloudFront Invalidation 코드)
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { CloudFrontClient, CreateInvalidationCommand } from "@aws-sdk/client-cloudfront";
const s3Client = new S3Client({});
const cfClient = new CloudFrontClient({});
const BUCKET_NAME = process.env.REPORT_BUCKET || "fruit.nxnl.app";
const CF_DISTRIBUTION_ID = process.env.FRUIT_CF_DISTRIBUTION || "E1RZDJT0J7KKAW";
async function saveReportAndInvalidate(reportData) {
const jsonBody = JSON.stringify(reportData, null, 2);
const dateStr = reportData.date; // YYYY-MM-DD
// 1. S3 버킷에 날짜별 및 최신(latest.json) 파일 업로드 (24시간 캐시 헤더)
await s3Client.send(new PutObjectCommand({
Bucket: BUCKET_NAME,
Key: `reports/latest.json`,
Body: jsonBody,
ContentType: "application/json",
CacheControl: "public, max-age=86400, s-maxage=86400",
}));
// 2. CloudFront Edge 캐시 강제 무효화 (새 보고서 즉시 반영)
await cfClient.send(new CreateInvalidationCommand({
DistributionId: CF_DISTRIBUTION_ID,
InvalidationBatch: {
CallerReference: `report-invalidation-${Date.now()}`,
Paths: {
Quantity: 1,
Items: ["/reports/latest.json"],
},
},
}));
}7. 실제 테스트 결과
- **GA4 API 일일 호출 횟수**: 수천 회 -> **하루 정확히 1회 (99.9% 감소)**.
- **보고서 데이터 응답 속도**: 1.2초(1200ms) -> **12ms (CloudFront Edge 100배 가속)**.
- **GA4 429 Rate Limit 에러**: **완전 0건 달성**.
8. 다른 환경에서 주의할 점
- CloudFront Invalidation은 월 1,000건까지는 무료이지만 과도한 무효화 요청은 비용이 청구될 수 있습니다. 하루 1~2회 정기 배치 작업에는 부담 없이 사용할 수 있습니다.
- 데이터 수집 시 GA4의 데이터 집계 시차(Data Processing Lag, 보통 24시간 미만)를 감안하여 `startDate: 'yesterday'`로 설정해야 완전하게 집계된 전날 수치를 가져올 수 있습니다.
9. 정리
자주 바뀌지 않는 집계성 데이터를 매번 백엔드 DB나 3rd-party API로 조회하는 것은 자원 낭비입니다. 하루 1회 배치 인출 + S3 정적 저장 + CloudFront CDN 캐싱 패턴을 쓰면 100배 빠른 응답 속도와 0원에 가까운 아키텍처를 얻을 수 있습니다.
10. 관련 글
- [GA4 Data API로 주간 보고서 자동 생성하기](file:///Users/uram/dev/goodfruit/posts/08-ga4-data-api-weekly-report.md)
- [GA4 커스텀 이벤트로 분석 완료율 측정하기](file:///Users/uram/dev/goodfruit/posts/07-ga4-custom-events-completion-rate.md)
- [AI 서비스의 광고수익과 API 비용을 함께 계산하기](file:///Users/uram/dev/goodfruit/posts/10-ai-service-adsense-vs-api-cost.md)
실제 적용 서비스
0.01초 만에 응답하는 최적화된 백엔드 구조의 GoodFruit를 경험해보세요.
👉 [GoodFruit 서비스 방문하기](https://fruit.nxnl.app/?utm_source=tistory&utm_medium=content&utm_campaign=goodfruit&utm_content=09_daily_data_fetch_and_cloudfront_cache)