원문 캡처 · dbos.dev
원문 캡처 · dbos.dev

DBOS는 Postgres 기반 워크플로 시스템에서 데이터 보존 정책에 따른 삭제 성능을 개선했다고 밝혔다. 외래 키를 제거해 테이블별로 배치 삭제하고, 보존 작업 전후에 수동으로 VACUUM을 실행해 기존 방식보다 삭제 성능을 한 자릿수 배수만큼 높였다.

Postgres는 트랜잭션 격리를 위해 행을 즉시 물리적으로 지우지 않는다. 다중 버전 동시성 제어(MVCC)에 따라 삭제된 행은 트랜잭션 ID가 표시된 뒤 보이지 않게 되고, VACUUM이 더는 활성 트랜잭션에서 필요하지 않은 이전 행 버전인 데드 튜플을 정리한다. 이 과정은 디스크 공간을 회수하지만 삭제가 많은 시스템에서는 캐시와 인덱스 성능에도 영향을 준다.

DBOS의 워크플로 시스템은 완료 시점을 기준으로 오래된 워크플로를 일정량씩 배치 삭제한다. 처음에는 workflow_status의 워크플로 행을 지울 때 외래 키의 ON DELETE CASCADE로 관련 step_outputs 행도 함께 삭제했다. 그러나 워크플로 단계 데이터가 디스크 곳곳에 흩어져 있어, 각 행을 지울 때마다 관련 페이지를 불러오고 갱신해야 했다. DBOS는 이 캐시 부담 때문에 워크플로 삭제가 실행보다 느려졌다고 설명했다.

이를 개선하기 위해 workflow_status를 참조하는 외래 키를 제거하고 테이블마다 별도로 배치 삭제하도록 바꿨다. 먼저 워크플로 행을 지운 뒤, 관련 데이터를 저장한 테이블에서도 배치 삭제를 수행한다. 이 데이터는 물리적으로 생성된 순서대로 삭제돼 캐시 지역성이 높아진다. 워크플로는 완료 시점을 기준으로 삭제하지만 관련 데이터 테이블에는 생성 시점만 있으므로, 실행 중인 장기 워크플로의 데이터를 지우지 않도록 워크플로가 이미 삭제된 경우에만 관련 행을 삭제하는 안티 조인도 사용했다.

구현 과정에서는 created_at 인덱스를 사용하는 배치 삭제 쿼리가 데이터베이스 부하가 높은 상황에서 최대 20분까지 걸리는 문제가 나타났다. 이 쿼리는 인덱스에서 가장 오래된 행부터 살피지만, 행 삭제 순서도 오래된 것부터였다. 오토배큠이 정리하기 전에 인덱스 앞부분에 데드 튜플이 수백만 개 쌓이면, 쿼리는 다음 행을 찾기 전에 각 튜플을 확인해야 했다. DBOS는 보존 작업 전후에 테이블에서 수동으로 VACUUM을 실행해 이를 해결했다고 밝혔다. 외래 키 제거와 수동 VACUUM을 함께 적용한 결과 삭제 성능이 한 자릿수 배수만큼 개선됐다.

DBOS는 시간 기준으로 파티션을 나눠 오래된 파티션을 삭제하는 방식도 설명했다. 이 방식은 MVCC를 거치지 않고 디스크의 파일을 삭제하므로 SQL DELETE보다 빠르지만, 파티션 안의 데이터가 모두 더는 필요하지 않아야 파티션 전체를 없앨 수 있다. 따라서 수주에서 수개월 이어지는 일부 워크플로가 짧게 실행된 워크플로의 대량 데이터 삭제를 막을 수 있고, 실패한 워크플로를 성공한 워크플로보다 오래 보관하는 등 복잡한 정책에도 맞지 않을 수 있다고 DBOS는 설명했다.

  • 파티션 전체를 한꺼번에 삭제할 수 없는 장기 워크플로와 다양한 보존 정책이 필요한 시스템에서는 배치 DELETE가 대안이 될 수 있다.
  • DBOS의 개선은 외래 키를 제거한 뒤 관련 테이블을 따로 삭제하고, 각 보존 작업 전후에 VACUUM을 실행하는 방식이다.

DBOS의 Quickstart에서 제품 사용 방법을 확인할 수 있다.