빅쿼리 테이블에서 가지고 있는 정보를 가져와서 사용하고 싶은 요구사항이 들어왔습니다. 그러나 대용량 데이터를 가져올 때 속도가 꽤 걸리는 문제가 있었습니다.
임시 테이블에서는 스토리지 비용이 발생하지 않지만, 쿼리 결과를 영구 테이블에 쓰면 데이터 저장 요금이 청구됩니다. 대화형 및 일괄 쿼리가 모두 포함된 모든 쿼리 결과가 일부 예외를 제외하고 약 24시간 동안 임시 테이블에 캐시 처리됩니다.
공식 문서
방법을 찾기 위해 구글링을 하던 도중 공식 문서에서 임시 테이블이라는 개념을 접하게 되었습니다.
빅쿼리에서는 쿼리 결과를 임시 테이블에 저장해두고, 이 결과를 캐싱해두기 때문에 임시 테이블을 조회할 때 상당히 빠른 속도로 조회할 수 있습니다.
현재는 애플리케이션에서 페이지네이션 쿼리를 매번 실행하고 있었는데, 차라리 데이터를 한 번에 쿼리해서 임시 테이블에 데이터를 캐시 처리한다면, 이 정보를 더 빠르게 가져올 수 있지 않을까 싶었습니다.
하지만, 임시 테이블에 데이터를 한 번에 넣는다는 건 결국 애플리케이션에 한 번에 데이터를 들고 온다는 뜻이라 기존에 메모리 등의 자원을 고려한 페이지네이션 처리 로직의 존재 이유가 사라지게 됩니다.
그래서 임시 테이블에 필요한 데이터를 한 번에 캐시 처리하되, 애플리케이션에는 들고 오지 않는 방법이 없을까해서 찾아보다 빅쿼리 Job 개념을 알게 되었습니다.

빅쿼리에서 쿼리를 하게 되면 내부적으로 Job이 실행됩니다. 이 Job에는 고유한 ID가 있고, 해당 Job을 다시 실행하면 동일한 쿼리 결과를 얻을 수 있습니다.
class BigQueryExampleRepositoryImpl(
private val bigQuery: BigQuery,
) {
fun createJob(queryConfig: QueryJobConfiguration): Job {
val jobId = createJobId()
val job = bigQuery.createJob(JobInfo.of(jobId, queryConfig))
if (job.status.error != null) {
throw RuntimeException("Failed to execute BigQuery job: ${job.status.error}")
}
return job
}
fun createJobId(): JobId {
return JobId.newBuilder()
.setLocation("us")
.setJob(UUID.randomString())
.build()
}
}
이렇게 kotlin에서 빅쿼리의 Job을 생성할 수 있습니다. 이제 Job의 결과를 가져올 때 페이지네이션만 적용해서 쿼리를 가져오면 됩니다.
fun getQueryResult() {
val job = createJob(/* 생략 */)
val options = listOfNotNull(
BigQuery.QueryResultsOption.pageSize(size),
cursor?.let { BigQuery.QueryResultsOption.pageToken(it) }
)
return job.getQueryResults(*options)
}
BigQuery.QueryResultsOption을 통해 Job의 임시 테이블에서 쿼리 결과를 가져올 때 페이지네이션을 할 수 있습니다.
getQueryResult 반복을 비동기로 처리한다면 중간에 서버가 shutdown 될 때 graceful shutodwn을 적용받을 수 있습니다.jobId, cursor 값만 알 수 있다면 재시도 할 수 있습니다.