본문 바로가기
삵
Amazon Web Services

AWS HealthOmics 워크플로 실행과 분석

냉냉장고를사다줘·2026년 10월 28일·조회 0

유전체 분석하는 팀과 인프라 얘기를 하다 보면 결국 "파이프라인 돌릴 서버는 누가 보나"로 넘어간다. AWS Batch 위에 Cromwell이나 Nextflow를 직접 깔아서 쓰던 팀이 관리형으로 옮길지 고민하는 경우가 꽤 있다. 막상 HealthOmics에 워크플로를 올려 보면 실행은 금방 된다. 그런데 끝나고 나서 "어느 task가 시간을 잡아먹었고 어디에 메모리를 과하게 줬나"를 보려는 순간, 데이터가 여기저기 흩어져 있어서 한 번 막힌다.

AWS HealthOmics는 WDL, Nextflow, CWL로 작성한 생명정보학 워크플로를 서버 관리 없이 실행해 주는 관리형 서비스다. 실행 중 task별 CPU와 메모리 사용량은 CloudWatch에 OpenTelemetry 형식 지표로 올라오고, Grafana의 CloudWatch 데이터소스에서 PromQL로 바로 조회된다. 실행이 끝난 뒤의 기록은 /aws/omics/WorkflowLog 로그 그룹과 EventBridge 이벤트에 남는다. 이벤트를 S3에 쌓고 Athena로 묶으면 워크플로별 소요 시간과 할당 리소스 추이를 Grafana에서 볼 수 있다.

1. HealthOmics가 대신 맡아 주는 부분

용어부터 짚고 넘어간다. 시퀀서(유전체 해독 장비)가 뽑아낸 원시 데이터는 FASTQ라는 텍스트 포맷이고, 짧은 염기서열 조각(read)이 수억 개 들어 있다. 이걸 기준 유전체(reference genome, FASTA 포맷)에 맞춰 정렬하면 BAM이나 CRAM 파일이 된다. 정렬 결과에서 기준과 다른 위치를 뽑아내면 VCF(변이 목록 파일)가 나온다.

이 단계 하나하나가 별도 프로그램이라 컨테이너 수십 개를 순서대로, 또는 병렬로 돌려야 한다. 이 묶음을 정의하는 언어가 WDL, Nextflow, CWL이다. 인프라 쪽 사람이라면 Argo Workflows의 DAG를 생명정보학 전용 DSL로 쓴 것이라고 생각하면 이해가 빠르다.

HealthOmics는 이 정의 파일을 받아서 엔진 실행, 컴퓨팅 할당, 임시 파일시스템 준비, 결과의 S3 내보내기까지 처리한다. 사용자는 컨테이너 이미지를 private ECR에 올리고, 입력을 S3에 두고, 실행만 건다. API와 콘솔에서 계속 마주치는 이름은 아래 네 개다.

개념뜻
Workflow정의 파일과 파라미터 템플릿을 등록한 것. 숫자 ID가 붙는다.
Run워크플로를 입력 데이터로 한 번 실행한 것.
Taskrun 안의 개별 단계. 각각 컨테이너 하나로 돈다.
Run group최대 vCPU, 최대 실행 시간, 동시 run 수를 묶어서 제한하는 단위. 선택 사항이다.

워크플로 말고 스토리지와 애널리틱스 기능도 있다. Sequence store는 FASTQ, BAM, CRAM을 read set이라는 단위로 저장하는 전용 저장소고, reference store는 기준 유전체를 계정과 리전당 하나 두는 저장소다. 애널리틱스의 variant store와 annotation store는 현재 신규 고객에게 열려 있지 않고 기존 고객만 계속 쓸 수 있다. 새로 시작한다면 설계에서 빼고 간다.

리전도 확인하고 넘어가야 한다. 서울(ap-northeast-2)에는 워크플로 엔드포인트만 있고 스토리지와 애널리틱스 엔드포인트는 없다. 서울에서 쓸 거면 입력은 S3에 두고 워크플로 기능만 쓰는 구성이 된다. 전체 기능이 필요하면 us-east-1, us-west-2, eu-west-1 같은 리전을 봐야 한다.

과금은 run 상태가 RUNNING일 때만 붙고, PENDING, STARTING, STOPPING 동안은 요금이 없다. Task 요금은 정의 파일에 요청한 CPU와 메모리, 그리고 실행 시간으로 계산되며 task마다 최소 1분이 과금된다. 여기에 run storage(실행 중 쓰는 임시 파일시스템) 사용분이 더해진다. 단가는 리전마다 다르니 요금 페이지에서 확인한다.

2. WDL 워크플로 하나 등록해서 돌려 보기

실행 전에 워크플로와 같은 리전의 S3 버킷, 컨테이너를 넣은 private ECR 저장소, HealthOmics가 assume하는 서비스 역할을 갖춰 둔다. 서비스 역할에는 입력 버킷 읽기, 출력 버킷 쓰기, ECR 이미지 pull, CloudWatch Logs 쓰기 권한이 들어간다. 뒤에서 Grafana로 실행 지표를 볼 거라면 cloudwatch:PutMetricData를 꼭 추가한다. 이 권한이 없으면 run 지표가 아예 발행되지 않는다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "cloudwatch:PutMetricData",
      "Resource": "*"
    }
  ]
}

워크플로 정의(main.wdl과 import 파일)를 zip으로 묶고, 파라미터 템플릿 JSON을 같이 넘긴다.

$ cat workflow-params.json
{
  "input_cram":  { "description": "변환할 CRAM 파일", "optional": false },
  "ref_fasta":   { "description": "기준 유전체 FASTA", "optional": false },
  "sample_name": { "description": "출력 파일 이름에 쓸 샘플명", "optional": false }
}

$ zip -r workflow-crambam.zip main.wdl
$ aws omics create-workflow \
    --name cram-converter \
    --engine WDL \
    --definition-zip fileb://workflow-crambam.zip \
    --parameter-template file://workflow-params.json
{
    "arn": "arn:aws:omics:ap-northeast-2:123456789012:workflow/1234567",
    "id": "1234567",
    "status": "CREATING",
    "tags": {}
}

위 출력은 형식 예시이고 ID와 계정 번호는 환경마다 다르다. 상태가 ACTIVE로 바뀌어야 실행할 수 있다.

$ aws omics get-workflow --id 1234567 --query status
"ACTIVE"

이제 실행이다. 여기서 한 번 헷갈리는 게 run storage 타입이다. 개발자 가이드는 기본값을 DYNAMIC으로 설명하는데, AWS CLI 레퍼런스의 start-run 옵션 설명에는 STATIC이 기본값으로 적혀 있다. 문서끼리 표기가 엇갈리니 --storage-type은 항상 명시한다.

DYNAMIC은 사용량에 따라 크기가 자동으로 늘고 줄며 준비 시간이 짧다. STATIC은 Lustre 파일시스템을 고정 크기로 잡는데, 최소 1,200 GiB이고 그 위로는 2,400 GiB 단위다. 크기를 작게 잡으면 Out of storage for file system으로 run이 죽는다. 개발 중에는 DYNAMIC, 용량이 예측되는 대량 운영 실행에는 STATIC을 쓴다.

$ aws omics start-run \
    --workflow-id 1234567 \
    --role-arn arn:aws:iam::123456789012:role/OmicsWorkflowRole \
    --name na12878-cram2bam \
    --output-uri s3://my-omics-output/runs/ \
    --storage-type DYNAMIC \
    --log-level ALL \
    --tags team=genomics,project=wgs-pilot \
    --parameters file://inputs.json
{
    "arn": "arn:aws:omics:ap-northeast-2:123456789012:run/2345678",
    "id": "2345678",
    "status": "PENDING",
    "tags": {}
}

--tags는 지금 붙여 둔다. 나중에 Cost Explorer에서 팀이나 프로젝트별로 비용을 나눌 때 이 태그가 기준이 된다.

--log-level은 OFF, FATAL, ERROR, ALL 중 하나다. OFF로 두면 로그뿐 아니라 run 지표도 발행되지 않는다.

실행 중이거나 끝난 run의 task 목록은 list-run-tasks로 뽑는다.

$ aws omics list-run-tasks --id 2345678
{
    "items": [
        {
            "cpus": 1,
            "creationTime": "2026-10-27T05:13:00.718651Z",
            "memory": 15,
            "name": "CramToBamTask",
            "startTime": "2026-10-27T05:17:47.016Z",
            "status": "COMPLETED",
            "stopTime": "2026-10-27T05:18:21.503Z",
            "taskId": "3456789"
        },
        ...
    ]
}

필드 구성은 실제 응답과 같고 값은 예시다. 응답에는 이 밖에 gpus, instanceType, cacheHit 같은 필드가 올 수 있다. 여기 memory는 task에 할당된 값이지 실제 사용량이 아니다. CLI 레퍼런스는 이 필드를 GB 단위의 memory use로 정의해 두었으니 단위 표기가 문서마다 조금 다르다는 점만 알고 넘어간다. 사용량은 3장에서 다룬다.

리소스 요청에서 자주 걸리는 부분이 하나 더 있다. Task에 CPU와 메모리를 지정하지 않으면 가장 작은 omics.c.large가 배정된다. 15 vCPU, 33 GiB처럼 애매하게 요청하면 그걸 담을 수 있는 omics.m.4xl(16 vCPU, 64 GB)이 잡히지만 task는 요청한 만큼만 쓴다. 요청값을 omics 인스턴스 크기에 맞춰 써야 낭비가 없다.

3. 실행이 남기는 데이터는 다섯 군데로 흩어진다

분석 도구를 붙이기 전에 어디에 무엇이 쌓이는지부터 알아야 한다. 공식 문서로 확인한 출처를 표로 묶었다.

데이터위치언제담긴 것
Run 지표 (OTel)CloudWatch, cloudwatch.aws/omics 범위실행 중, 30초 간격task별 CPU, 메모리, 스크래치 스토리지, 네트워크와 파일시스템 I/O, GPU
워크플로 로그CloudWatch Logs /aws/omics/WorkflowLog대부분 실시간, manifest는 완료 후엔진 로그, run 로그, task 로그, run manifest
상태 변경 이벤트EventBridge, source aws.omics상태가 바뀔 때마다Run과 Task 상태, task의 cpus, memory, instanceType, 시작과 종료 시각
엔진 로그 사본, outputs.json출력 S3 경로완료 후엔진 로그, WDL과 CWL의 출력 목록
비용Billing, Cost Explorer, Data ExportsData Exports는 시간 단위(hourly) 또는 일 단위(daily) 선택omics 인스턴스 타입과 스토리지 타입별 요금, 태그별 집계

실행 분석에 실제로 쓰는 건 위 표의 첫 세 줄이다. 나머지 둘은 결과 확인과 금액 대조용으로 둔다.

Run 지표

Run 지표는 일반 CloudWatch 지표가 아니라 OpenTelemetry 형식으로 들어온다. 그래서 기존 Metrics 화면이 아니라 PromQL로 조회한다. 문서에 나온 지표 중 분석에 주로 쓰는 것들이다.

aws.omics.task.cpu.usage          # task가 쓰는 vCPU
aws.omics.task.cpu.limit          # task에 예약된 vCPU
aws.omics.task.memory.usage       # 사용 메모리 (bytes)
aws.omics.task.memory.limit       # 예약 메모리 (bytes)
aws.omics.task.filesystem.scratch.storage.usage
aws.omics.task.network.io         # network.io.direction = receive | transmit
aws.omics.task.filesystem.io      # filesystem.io.direction = read | write
aws.omics.task.gpu.utilization    # GPU task만, gpu.id 별
aws.omics.run.filesystem.usage    # run 공유 파일시스템 사용량

모든 지표에 @resource.aws.omics.run.id와 @resource.aws.omics.workflow.id 라벨이 붙고, task 지표에는 @resource.aws.omics.task.id가 더 붙는다. 이 라벨로 run과 task를 골라낸다.

지표는 task가 RUNNING에 들어간 뒤부터 COMPLETED까지만 발행된다. 30초 안에 끝나는 task는 지표가 아예 없을 수 있다. DYNAMIC 스토리지 run의 aws.omics.run.filesystem.usage는 30분 넘게 늦게 올라올 수 있어서 짧은 run에서는 비어 있기도 한다. Tel Aviv(il-central-1) 리전에서는 run 지표가 나오지 않고, run을 시작한 계정에만 쌓인다.

HealthOmics가 지표에 따로 요금을 받지는 않지만 CloudWatch가 수집 데이터량(GB) 기준으로 과금한다. 콘솔과 대시보드에서 PromQL을 돌리는 건 무료이고, API로 조회하면 스캔한 샘플 수만큼 요금이 붙는다.

로그

로그 스트림 이름 규칙을 알아야 Logs Insights에서 원하는 로그만 걸러낼 수 있다.

run/{runID}/engine                 # 엔진 로그, 실시간
run/{runID}                        # run 로그, 실시간
run/{runID}/task/{taskID}          # task 로그, 실시간
manifest/run/{runID}/{runUUID}     # run manifest, 실행 완료 후
runCache/{runCacheId}/{runCacheUUID}

사후 분석의 핵심은 run manifest다. Task별 상태, 시작과 종료 시각, 실패 사유가 들어 있고, cpusAverage, cpusMaximum, cpusReserved, gpusReserved, memoryAverageGiB, memoryMaximumGiB, memoryReservedGiB, runningSeconds 같은 사용률 통계도 함께 남는다. HealthOmics에서 run을 지워도 manifest는 CloudWatch Logs에서 지워지지 않는다.

대신 로그 그룹 보존 기간이 기본으로 무기한이다. 저장 비용이 걱정되면 보존 기간을 직접 정해 둔다.

$ aws logs put-retention-policy \
    --log-group-name /aws/omics/WorkflowLog \
    --retention-in-days 400

실행 중인 run을 터미널에서 따라가고 싶으면 Live Tail이 편하다.

$ aws logs start-live-tail \
    --mode interactive \
    --log-group-identifiers arn:aws:logs:ap-northeast-2:123456789012:log-group:/aws/omics/WorkflowLog

EventBridge 이벤트

이벤트는 "source": "aws.omics"로 들어오고, 분석에 쓸 건 Run Status Change와 Task Status Change다. Task 이벤트의 detail에는 이런 필드가 담긴다(값은 예시).

"detail": {
    "status": "COMPLETED",
    "runId": "2345678",
    "runName": "na12878-cram2bam",
    "workflowId": "1234567",
    "workflowName": "cram-converter",
    "taskName": "CramToBamTask",
    "cpus": "4",
    "gpus": "0",
    "memory": "16",
    "instanceType": "omics.c.xlarge",
    "creationTime": "...",
    "startTime": "...",
    "stopTime": "..."
}

cpus와 memory가 숫자가 아니라 문자열로 온다. Athena에서 CAST가 필요한 이유다.

HealthOmics는 이벤트 전달을 best effort라고 명시하고 있다. 이벤트 테이블은 추이 분석에 쓰고, 청구 금액 대조에는 Billing 데이터를 쓴다.

4. Grafana에서 병목 task와 과다 할당 찾기

Grafana는 Amazon Managed Grafana든 셀프호스트든 상관없다. Amazon CloudWatch 데이터소스 하나로 PromQL 지표와 Logs Insights를 같이 보고, 이벤트 테이블은 Amazon Athena 데이터소스로 붙인다.

4-1. CloudWatch 데이터소스에서 PromQL 켜기

CloudWatch 데이터소스의 PromQL 쿼리 타입은 버전 조건이 있다. 셀프호스트라면 CloudWatch 데이터소스 플러그인 12.10.0 이상, Amazon Managed Grafana라면 Grafana 13 이상 워크스페이스여야 한다. Grafana가 쓰는 IAM 주체에는 cloudwatch:GetMetricData와 cloudwatch:ListMetrics가 필요하고, 로그 쿼리도 쓸 거라 Logs Insights 권한도 함께 준다.

셀프호스트 Grafana를 EC2 인스턴스 역할로 인증하는 경우의 프로비저닝 파일이다.

$ sudo cat /etc/grafana/provisioning/datasources/cloudwatch.yaml
apiVersion: 1
datasources:
  - name: CloudWatch-Seoul
    type: cloudwatch
    jsonData:
      authType: default
      defaultRegion: ap-northeast-2

$ sudo systemctl restart grafana-server

패널 쿼리 편집기에서 쿼리 타입을 PromQL로 바꾸고 Code 모드로 넘어간다. Builder 모드도 있지만 라벨에 점과 @이 섞여 있어서 직접 치는 게 빠르다.

버전이 낮아서 PromQL 타입이 안 보이면 Amazon Managed Service for Prometheus 데이터소스 플러그인을 대신 쓴다. URL을 https://monitoring.ap-northeast-2.amazonaws.com으로 주고, Service 값을 기본값 aps에서 monitoring으로 바꾼다. 이걸 그대로 두면 모든 쿼리가 HTTP 403으로 실패한다.

Credential should be scoped to correct service: 'monitoring'

프로비저닝 YAML에서는 sigv4Service: monitoring 키에 해당한다. 다만 이 플러그인 쪽은 Service 값이 무시된다는 사용자 이슈도 GitHub에 올라와 있어서, 가능하면 CloudWatch 데이터소스를 올리는 쪽으로 해결한다.

4-2. 실행 중 리소스 패널

대시보드 변수로 Textbox 타입 run을 하나 만들어 run ID를 받는다. 첫 패널은 task별 vCPU 사용량 시계열이다. 지표 이름에 점이 있어서 중괄호 안에 따옴표로 감싸야 한다. aws.omics.task.cpu.usage처럼 맨 이름으로 쓰면 PromQL 문법 오류가 난다.

{"aws.omics.task.cpu.usage", "@resource.aws.omics.run.id"="$run"}

Legend는 task ID 라벨로 지정해 선을 구분한다. Task ID만으로는 무슨 단계인지 모르니, 2장의 list-run-tasks 결과를 옆에 표로 두거나 4-4의 Athena 테이블과 맞춰 본다.

과다 할당을 찾는 핵심 쿼리는 최대 사용량을 예약량으로 나눈 비율이다. 패널 Type을 Instant, Format을 Table로 둔다.

max by ("@resource.aws.omics.task.id") (
  max_over_time({"aws.omics.task.memory.usage", "@resource.aws.omics.run.id"="$run"}[1d])
)
/
max by ("@resource.aws.omics.task.id") (
  max_over_time({"aws.omics.task.memory.limit", "@resource.aws.omics.run.id"="$run"}[1d])
)

Instant 쿼리는 대시보드 시간 범위의 끝 시각 한 점에서 평가된다. 실제로 보는 구간은 그 끝 시각에서 range selector([1d]) 길이만큼 거슬러 올라간 부분뿐이라, 대시보드 범위를 run 전체로 넓혀 놔도 앞쪽 task가 빠질 수 있다. 범위를 넉넉히 잡았는데 task가 몇 개 안 보이면 거의 이 때문이다.

대시보드 끝 시각을 run 종료 직후로 맞추고, range selector 길이가 run 시작 시각까지 닿게 잡으면 된다. 하루 넘게 도는 run이면 [1d]를 [2d]처럼 run 길이만큼 늘린다. 단 CloudWatch PromQL은 range selector와 lookback을 합친 조회 범위를 요청당 최대 7일로 제한하고, 넘으면 422를 돌려준다. run 최대 실행 시간 기본 쿼터가 7일이라 그 근처까지 도는 run은 구간을 나눠서 조회한다.

CPU도 같은 모양으로 cpu.usage와 cpu.limit를 넣는다. 비율이 얼마 아래면 줄여도 되는지에 대한 공식 기준은 없다. 여기서는 표에서 눈으로 후보를 거르는 용도로 0.3을 예시로 쓰는데, 이건 경험적으로 잡은 값이지 권장값이 아니다.

실제 크기를 정할 때는 4-5의 Run Analyzer 방식을 따른다. 관측된 최대 사용량을 기준으로 권장 크기를 뽑고, headroom 파라미터로 여유분을 얹는 식이라 근거가 남는다. 반대로 메모리 비율이 1에 붙어 있으면 OOM 직전이니 올린다. 샘플 하나만 보고 줄이면 큰 샘플이 들어왔을 때 터질 수 있으니, 크기가 다른 샘플 여러 개로 확인한 뒤 바꾼다.

PromQL 조회 범위는 요청당 최대 7일이고 쿼리 하나가 돌려주는 시계열은 최대 500개다. Task가 수백 개인 run 여러 개를 한꺼번에 걸면 결과가 잘리니 run 단위로 끊어서 본다.

스크래치 스토리지도 패널 하나로 둔다. scratchStorageMode가 LOCAL인 task는 limit 지표가 같이 나와서 비율을 볼 수 있다.

{"aws.omics.task.filesystem.scratch.storage.usage", "@resource.aws.omics.run.id"="$run"}

같은 PromQL은 CloudWatch 콘솔의 Query Studio에서 바로 알람으로 만들 수도 있다. 예를 들어 메모리 비율이 0.95를 넘으면 SNS로 알리는 식이다.

4-3. manifest 로그를 Logs Insights로

같은 CloudWatch 데이터소스에서 쿼리 모드를 Logs로 바꾸고 로그 그룹 /aws/omics/WorkflowLog를 고른다. 일단 manifest 레코드가 어떤 모양인지부터 본다.

fields @timestamp, @message
| filter @logStream like /^manifest\/run\/2345678\//
| limit 20

Task 레코드의 arn은 ...:task/... 형태라 run 레코드와 구분된다. 공식 예시에 나온 필드로 task 목록을 뽑는 쿼리다.

fields name, cpus, memory, status, startTime, stopTime
| filter @logStream like /^manifest\/run\/2345678\//
| filter arn like /:task\//
| sort cpus desc

cpusMaximum, memoryMaximumGiB, runningSeconds 같은 사용률 필드는 공식 문서에 존재만 나와 있고, JSON 안에서 어떤 경로에 들어가는지는 예시가 없다. 첫 쿼리 결과의 @message를 열어 실제 키 경로를 확인한 뒤 그대로 fields에 넣는다. Logs Insights는 JSON 로그의 중첩 필드를 점 표기로 자동 인식한다.

실패 원인을 빨리 보고 싶을 때는 엔진 로그 스트림에서 에러 줄만 뽑는다.

fields @timestamp, @message
| filter @logStream = "run/2345678/engine"
| filter @message like /(?i)error|exception/
| sort @timestamp asc

4-4. 이벤트를 S3에 쌓아 Athena로 추이 보기

PromQL은 7일 범위 제한이 있고 manifest는 run 하나씩 보기 좋은 구조다. "지난 석 달 동안 이 워크플로의 어떤 task가 계속 느려지고 있나" 같은 질문에는 둘 다 불편하다. Task Status Change 이벤트를 S3에 쌓아 두면 이런 질문을 SQL로 바로 묶을 수 있다. 연결 순서는 EventBridge 규칙, Data Firehose, S3, Athena 테이블, Grafana Athena 데이터소스다.

$ cat omics-pattern.json
{
  "source": ["aws.omics"],
  "detail-type": ["Run Status Change", "Task Status Change"]
}

$ aws events put-rule \
    --name omics-status-to-s3 \
    --event-pattern file://omics-pattern.json

$ aws events put-targets \
    --rule omics-status-to-s3 \
    --targets 'Id=firehose,Arn=arn:aws:firehose:ap-northeast-2:123456789012:deliverystream/omics-events,RoleArn=arn:aws:iam::123456789012:role/EventBridgeToFirehose'

Firehose 스트림의 대상은 s3://my-omics-events/events/로 잡는다. 여기서 막히기 쉬운 게 레코드 구분자다. Firehose가 JSON 레코드를 줄바꿈 없이 이어 붙여 쓰면 Athena가 한 줄에 붙은 여러 이벤트를 제대로 읽지 못한다. Firehose 대상 설정에서 줄바꿈 구분자(new line delimiter) 옵션을 켠다.

Athena 테이블은 OpenX JSON SerDe로 만든다. detail-type처럼 하이픈이 들어간 키는 컬럼명으로 못 쓰니 매핑으로 바꾼다.

CREATE EXTERNAL TABLE omics_events (
  detail_type string,
  `time` string,
  detail struct<
    status:string, runid:string, runname:string,
    workflowid:string, workflowname:string, taskname:string,
    cpus:string, gpus:string, memory:string, instancetype:string,
    creationtime:string, starttime:string, stoptime:string
  >
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
WITH SERDEPROPERTIES ('mapping.detail_type' = 'detail-type')
LOCATION 's3://my-omics-events/events/';

완료된 task를 오래 걸린 순서로 뽑는 쿼리다. 이게 병목 task 패널이 된다.

SELECT detail.workflowname,
       detail.runid,
       detail.taskname,
       detail.instancetype,
       CAST(detail.cpus AS integer)   AS cpus,
       CAST(detail.memory AS integer) AS memory_gib,
       date_diff('second',
                 from_iso8601_timestamp(detail.starttime),
                 from_iso8601_timestamp(detail.stoptime)) AS running_sec
FROM omics_events
WHERE detail_type = 'Task Status Change'
  AND detail.status = 'COMPLETED'
ORDER BY running_sec DESC
LIMIT 20;

추이를 보려면 워크플로와 task 이름으로 묶고 날짜별 평균 소요 시간을 뽑는다. vCPU 수에 실행 초를 곱해 합치면 vCPU 시간이 나온다. 금액은 아니지만 어떤 task가 컴퓨팅을 많이 먹는지 순위를 매기기에는 충분하다.

SELECT date_trunc('day', from_iso8601_timestamp(detail.stoptime)) AS day,
       detail.workflowname,
       detail.taskname,
       count(*) AS runs,
       avg(date_diff('second', from_iso8601_timestamp(detail.starttime),
                               from_iso8601_timestamp(detail.stoptime))) AS avg_sec,
       sum(CAST(detail.cpus AS integer) *
           date_diff('second', from_iso8601_timestamp(detail.starttime),
                               from_iso8601_timestamp(detail.stoptime))) / 3600.0 AS vcpu_hours
FROM omics_events
WHERE detail_type = 'Task Status Change'
  AND detail.status = 'COMPLETED'
GROUP BY 1, 2, 3
ORDER BY 1, vcpu_hours DESC;

여기서 재시도가 집계를 꼬이게 만들 수 있다. WDL과 CWL 워크플로는 서비스 오류(5XX)로 실패한 task를 최대 두 번까지 재시도하고, 재시도마다 새 task ID를 받는다. Nextflow도 v23 이상이면 5XX 서비스 오류로 실패한 task를 기본으로 최대 두 번 재시도하며, 이 동작은 Nextflow 설정에서 omicsRetryOn5xx = false로 끌 수 있다. 다만 AWS 문서 중 run 수명주기 페이지는 Nextflow의 기본 재시도를 없음으로 적어 두어 문서끼리 표기가 엇갈린다. 여기서는 Nextflow 정의 페이지 설명을 따랐고, 집계 쿼리를 짜기 전에 실제 run에서 같은 task 이름이 여러 번 찍히는지 한 번 확인해 두는 게 안전하다.

Nextflow에서 재시도가 일어나지 않는 건 errorStrategy를 설정하지 않은 상태에서 task 자체가 non-zero exit 코드로 실패한 경우다. 서비스 오류와 task 자체 오류는 따로 봐야 한다.

결국 엔진과 상관없이 같은 run에 같은 taskname이 FAILED와 COMPLETED로 여러 번 찍힐 수 있다. 실패율을 이벤트 줄 수로 세면 부풀려지니, FAILED 이벤트를 따로 뽑아 run과 taskname 단위로 묶어 센다. 소요 시간 평균에는 COMPLETED만 넣는다. 반대로 vCPU 시간을 컴퓨팅 사용량으로 볼 때는 실패한 시도도 돈 만큼 자원을 썼으니, COMPLETED만 넣으면 실제보다 적게 잡힐 수 있다.

Grafana에서는 Amazon Athena 데이터소스를 추가하고 리전, 데이터베이스, 워크그룹, 쿼리 결과 S3 경로를 지정한다. 위 SQL을 패널에 넣고 시간 컬럼을 day로 두면 시계열로 그려진다. 데이터가 쌓이면 S3 경로를 날짜로 파티셔닝해서 Athena 스캔량을 줄인다.

4-5. 비용은 태그와 Run Analyzer로

실제 청구 금액은 Billing 데이터가 기준이다. Billing and Cost Management의 Bills에서 Omics 항목을 펼치면 omics 인스턴스 타입별, run storage 타입별로 나뉜다.

Run 단위로 보려면 Data Exports에서 CUR 내보내기를 만들어 S3로 받는다. 시간 단위는 hourly와 daily 중 고를 수 있다. 이것도 Athena 테이블로 만들어 같은 Grafana Athena 데이터소스에 붙인다. 첫 보고서는 생성 후 24시간 안에 나온다. 2장에서 붙인 team, project 태그를 비용 할당 태그로 활성화해 두면 Cost Explorer에서 태그별로 묶인다.

Task 단위 추정 비용과 권장 인스턴스 크기는 AWS가 공개한 Run Analyzer로 뽑는 게 제일 빠르다. aws-healthomics-tools 공식 README는 소스에서 설치할 때의 전제 조건으로 Python 3.10 이상을 적어 둔다. PyPI로 받을 때도 venv를 만들기 전에 python3 --version부터 확인해 두면 설치 중에 막힐 일이 줄어든다.

$ python3 -m venv ~/omics-tools && source ~/omics-tools/bin/activate
$ pip install aws-healthomics-tools

$ aws-healthomics-tools run_analyzer -t5d
Workflow run IDs (<completionTime> <UUID>):
2345678 (2026-10-27T06:02:11 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)

$ aws-healthomics-tools run_analyzer 2345678 -o run-2345678.csv
omics-run-analyzer: wrote run-2345678.csv

run ID와 시각은 형식 예시다. CSV에는 estimatedUSD(예약 크기와 실행 시간 기준 추정 요금), minimumUSD(권장 인스턴스로 바꿨을 때의 추정 요금), recommendedCpus, recommendedMemoryGiB, cpuUtilizationRatio, memoryUtilizationRatio 같은 컬럼이 들어간다. 추정 요금은 도구를 돌린 시점의 공시 가격 기준이다.

--batch는 여러 run을 묶어 task별 최대치를 뽑아 주니 샘플 크기 편차를 반영할 때 쓴다. -H(headroom)는 권장값에 여유분을 얹는 옵션이라, 4-2에서 눈으로 고른 후보를 실제로 얼마나 줄일지는 이 값으로 정한다. --cloudwatch를 붙이면 3장의 run 지표를 같이 반영한다.

완료된 run을 매일 이걸로 돌려 CSV를 S3에 올리고 Athena 테이블로 만들면 Grafana에 권장 대비 낭비 금액 패널까지 만들 수 있다.

남은 숙제는 I/O 지표다. aws.omics.task.network.io와 aws.omics.task.filesystem.io는 sum 타입인데, 누적(cumulative)인지 구간별(delta)인지 문서에 적혀 있지 않다. 습관적으로 rate()를 씌웠다가 delta로 들어오는 값이면 결과가 틀린다. I/O 병목 패널을 만들기 전에 원본 시계열을 한 번 띄워서 값이 계속 커지는지, 30초마다 새로 찍히는지부터 확인한다.

자주 묻는 질문

AWS HealthOmics는 서울 리전에서 쓸 수 있나?

쓸 수 있다. 다만 서울(ap-northeast-2)에는 워크플로 엔드포인트만 있다. Sequence store, reference store 같은 스토리지와 애널리틱스 기능은 서울에 없어서, 입력 데이터는 S3에 두고 워크플로 실행 기능만 쓰는 구성이 된다.

HealthOmics run의 task별 CPU, 메모리 사용량은 어디서 보나?

실행 중에는 CloudWatch의 OpenTelemetry 지표(aws.omics.task.cpu.usage, aws.omics.task.memory.usage 등)를 PromQL로 조회한다. 지표가 발행되려면 서비스 역할에 cloudwatch:PutMetricData 권한이 있어야 한다. 실행이 끝난 뒤에는 /aws/omics/WorkflowLog 로그 그룹의 manifest 스트림에 cpusMaximum, memoryMaximumGiB 같은 사용률 통계가 남는다. Grafana에서 Instant 쿼리로 볼 때는 대시보드 끝 시각을 run 종료 직후로 맞추고, range selector 길이를 run 길이보다 길게 잡는다.

Grafana에서 HealthOmics run 지표가 403 에러로 안 보인다면?

Amazon Managed Service for Prometheus 데이터소스로 CloudWatch 엔드포인트에 붙였다면 SigV4 Service 값이 기본값 aps로 남아 있을 가능성이 크다. monitoring으로 바꿔 본다. 이 플러그인이 서명 서비스를 aps로 고정해 버린다는 GitHub issue가 있어서, 바꿔도 403이 계속되면 아래 방법으로 간다. 아니면 CloudWatch 데이터소스 플러그인을 12.10.0 이상(Amazon Managed Grafana는 Grafana 13 이상)으로 올려 PromQL 쿼리 타입을 쓴다.

HealthOmics variant store를 새로 만들 수 있나?

만들 수 없다. variant store와 annotation store는 신규 고객에게 더 이상 열려 있지 않고, 기존 고객만 계속 쓸 수 있다. 새 설계에서는 빼고 간다.

HealthOmics 비용은 run 단위로 어떻게 추적하나?

start-run에 태그를 붙이고 비용 할당 태그로 활성화하면 Cost Explorer에서 태그별로 묶인다. 더 자세히 보려면 Data Exports로 CUR을 hourly 또는 daily 단위로 S3에 받아 Athena로 조회한다. Task 단위 추정 비용과 권장 인스턴스 크기는 aws-healthomics-tools의 run_analyzer로 뽑고, 권장값에 얹을 여유분은 headroom(-H) 옵션으로 정한다.

관련 글

댓글 0

로그인 후 댓글을 남길 수 있습니다.

아직 댓글이 없습니다.