terraform apply가 느려서 -parallelism=50을 붙여 볼지, 순서가 불안해서 depends_on을 여기저기 걸어 둘지 고민될 때가 있어요. 결론부터 말하면 parallelism은 기본값 10을 그대로 두고, 느리면 먼저 그래프가 긴 사슬인지 보고, 순서는 속성 참조로 드러내고 depends_on은 값이 오가지 않는 숨은 의존에만 써요. 교체 때 끊김이 문제라면 create_before_destroy를 켜되, 이 설정이 의존 대상 리소스에도 번진다는 점을 먼저 따져요.
이 글은 그래프가 어떻게 걸리는지보다 "그래서 어떤 값을 언제 고르나"를 다뤄요. 테라폼 1.9.8에서 네트워크를 쓰지 않는 내장 리소스 terraform_data로 직접 잰 실행 시간, 설정별 판단표, 운영에서 자주 나는 문제의 확인 순서를 정리했어요.
parallelism 값 판단표 — 언제 바꾸나
terraform apply 명령 문서에 적힌 -parallelism=n의 뜻은 "그래프를 걸을 때 동시에 진행하는 작업 수 제한, 기본 10"이에요. 이 값은 리소스 종류별이 아니라 실행 한 번 전체에 걸려요. 상황별로 고르면 이래요.
| 지금 상황 | 고를 값 | 이렇게 고르는 이유 |
|---|---|---|
| 특별한 문제 없음 | 지정하지 않음(10) | 문서가 일반 사용에는 필요 없다고 해요 |
| 로그가 뒤섞여 어디서 멈췄는지 모르겠음 | 일시적으로 1 | 한 번에 한 노드만 돌아 로그가 한 줄로 서요. 순서는 그대로예요 |
| 서로 기대지 않는 리소스가 수백 개이고 느림 | 20~30 정도로 올려 재 보기 | 넓은 그래프에서만 효과가 있어요. 올린 뒤 스로틀링 오류가 늘면 되돌려요 |
Throttling·Rate exceeded가 남 | 10 유지, 프로바이더 재시도 설정 조정 | 문서가 속도 제한은 프로바이더 API 클라이언트가 처리한다고 적어요 |
| 실행 머신 CPU·메모리가 빠듯함 | 10보다 낮게 | 세마포어의 원래 목적이 실행 머신 보호예요 |
| apply가 느린데 리소스가 줄줄이 이어짐 | 값 대신 그래프 손보기 | 사슬은 값을 올려도 안 빨라져요(아래 측정) |
Resource Graph 문서는 동시 처리 수를 세마포어로 묶는 이유를 "테라폼을 돌리는 머신의 자원을 압도하지 않으려고"라고 설명해요. 그리고 AWS 같은 프로바이더는 더 아래 단계에서 물러섰다 다시 시도하는 방식으로 속도 제한을 다루므로, 테라폼은 이 기능을 속도 제한 해결에 직접 쓰지 않는다고 덧붙여요. AWS 프로바이더라면 프로바이더 설정 문서의 max_retries(스로틀링·일시 오류 때 재시도 횟수)와 retry_mode를 먼저 봐요.
CI에서 값을 고정하고 싶다면 명령마다 붙이는 대신 환경 변수 문서의 TF_CLI_ARGS_apply="-parallelism=20"처럼 명령별 변수에 넣을 수 있어요.
숫자로 보는 parallelism 효과 — 넓은 그래프
값을 바꿨을 때 실제로 얼마나 달라지는지 쟀어요. 서로 의존하지 않는 terraform_data 30개에 각각 sleep 1을 거는 프로비저너를 달아, API 호출 하나가 1초 걸리는 상황을 흉내 냈어요. Apple Silicon 맥, 테라폼 1.9.8에서 값마다 두 번씩 apply한 평균이에요.
읽을 점이 두 가지예요. 첫째, 이론 하한은 ⌈리소스 수 ÷ 값⌉ × 한 작업 시간이라 값을 올릴수록 효과가 계단식으로 줄어요. 둘째, 10에서 30으로 올려 아낀 시간은 2.2초인데 같은 순간 API 요청은 세 배가 돼요. 실제 클라우드에서는 이 요청 폭이 속도 제한에 먼저 닿아요.
같은 순간 나가는 요청 수는 어림할 수 있어요. 한 작업이 평균 L초 동안 요청을 하나씩 낸다면 초당 요청 수의 상한은 대략 값 ÷ L이에요. 생성 요청 하나에 0.2초가 걸린다면 10은 초당 50건, 30은 초당 150건이에요. 이 계산은 예시이고 실제 한도는 클라우드 서비스마다 달라요.
긴 사슬은 parallelism으로 못 줄여요
이번엔 리소스 6개를 c1 → c2 → … → c6처럼 앞 리소스의 id를 참조하게 이어서 같은 실험을 했어요.
apply 전체 시간의 하한은 가장 긴 의존 경로(임계 경로)에 놓인 작업 시간의 합이에요. 실무에서 흔한 사슬은 이래요. VPC → 서브넷 → 데이터베이스 서브넷 그룹 → 데이터베이스 인스턴스 → 읽기 전용 복제본 → DNS 레코드. 데이터베이스처럼 한 단계에 몇 분씩 걸리는 리소스가 사슬 중간에 있으면 parallelism을 아무리 올려도 그 몇 분은 그대로 남아요.
사슬을 짧게 만드는 방법은 두 가지예요.
- 필요 없는 간선을 지워요. 값을 읽지 않는데
depends_on으로 걸어 둔 간선이 있으면 그만큼 사슬이 길어져요. 아래 판단표로 남길지 정해요. - 상태를 나눠요. 거의 안 바뀌는 네트워크와 자주 바뀌는 앱을 다른 상태로 두면 앱 apply에 네트워크 사슬이 아예 끼지 않아요. 나누는 기준은 테라폼 plan 옵션 고르는 법의 상태 분리 표를 참고해요.
terraform graph로 DOT 출력을 뽑아 보면 사슬 길이를 눈으로 확인할 수 있어요. terraform graph 명령 문서에 따르면 옵션 없이 돌리면 리소스만 남긴 단순한 그래프가 나와요.
depends_on을 걸지 말지 — 판단표
depends_on 문서는 이 기능을 최후의 수단으로 쓰라고 적어요. 테라폼이 위쪽 객체에 어떤 변화가 생길지 확신하지 못해 더 많은 값을 (known after apply)로 두고, 그래서 필요보다 많은 리소스를 교체하는 보수적인 계획을 만들 수 있다는 이유예요. 모듈에 걸 때 특히 그렇다고 해요.
이게 실제로 어떻게 보이는지 확인했어요. upstream 리소스가 교체되는 상황에서, 그와 값으로는 상관없는 데이터 소스에 depends_on = [terraform_data.upstream]을 걸고 그 데이터 소스 값을 app이 쓰게 했어요.
| 데이터 소스 설정 | plan 결과 |
|---|---|
depends_on 있음 | 데이터 소스가 "read during apply"로 밀리고, app도 값이 unknown이 돼 교체. 2 to add, 2 to destroy |
depends_on 없음 | upstream만 교체. 1 to add, 1 to destroy |
같은 코드에서 한 줄 차이로 교체 대상이 둘로 늘었어요. 두 plan 출력을 텍스트 비교 도구에 나란히 붙이면 depends_on을 넣고 뺐을 때 늘어난 줄을 바로 찾을 수 있어요.
그래서 남기는 기준은 이래요.
| 상황 | 판단 |
|---|---|
| IAM 정책이 붙어야 인스턴스 안 프로그램이 동작함(값은 안 읽음) | depends_on 유지. 문서가 드는 대표적인 숨은 의존이에요 |
| 다른 리소스의 ID·이름을 실제로 씀 | 지우기. 참조가 이미 순서를 만들어요 |
| "혹시 몰라서" 걸어 둠 | 지우고 plan 비교. 순서 오류가 나면 그때 그 리소스 하나에만 다시 걸어요 |
| 모듈 블록에 걸어 둠 | 모듈 안 리소스 하나에 걸거나, 필요한 출력을 참조로 바꿔요 |
| 다른 리소스가 바뀔 때 이것도 새로 만들어야 함 | depends_on이 아니라 lifecycle의 replace_triggered_by |
create_before_destroy 켤지 말지
교체가 필요한 변경에서 기본 순서는 옛 객체를 지운 뒤 새 객체를 만드는 거예요. lifecycle 문서의 create_before_destroy를 켜면 새 객체를 먼저 만들고 옛 객체를 나중에 지워요. 문서는 이 설정이 의존 대상 리소스 전체에 전파된다고 적어요. A가 B에 기대고 A에만 켰어도 B가 교체될 때 B 역시 먼저 만들고 나중에 지워요. B에 false를 적어 끌 수도 없어요.
직접 확인한 결과도 같았어요. A에만 켜고 B를 교체하자 plan 머리말이 +/- create replacement and then destroy로 나왔고, apply 순서는 B 생성 → A 생성 → A의 옛 객체(deposed) 삭제 → B의 옛 객체 삭제였어요.
| 리소스 성격 | 판단 | 확인할 것 |
|---|---|---|
이름을 자동 생성(name_prefix 지원) | 켜기 좋음 | name 대신 name_prefix를 써서 두 벌이 공존할 수 있게 해요 |
| 이름·도메인·포트처럼 유일해야 하는 값 고정 | 그대로 켜면 충돌 | 이름에 버전·랜덤 접미사를 넣거나 켜지 않아요 |
| 교체 중 잠깐 끊겨도 되는 개발 환경 | 기본값 유지 | 리소스가 두 벌 생기는 비용이 없어요 |
| 앞단에 트래픽을 받는 리소스(인증서·시작 템플릿 등) | 켜는 쪽 검토 | 이 리소스가 기대는 리소스까지 전부 공존 가능한지 봐요 |
| 삭제 때 실행하는 프로비저너가 있음 | 켜면 그 프로비저너가 안 돌아요 | 문서에 적힌 제약이에요. 정리 작업을 다른 방법으로 옮겨요 |
전파 때문에 생기는 문제는 대개 켠 리소스가 아니라 그 아래 리소스에서 나요. 켜기 전에 terraform graph로 그 리소스가 기대는 리소스 목록을 뽑고, 각각이 이름 충돌 없이 두 벌 존재할 수 있는지 하나씩 확인해요.
운영에서 자주 나는 문제와 확인 순서
1. apply가 오류로 끝났는데 일부 리소스는 만들어졌어요. 정상 동작이에요. 실패한 노드에 기대는 노드만 건너뛰고, 상관없는 가지는 끝까지 진행돼요. 리소스 둘 중 하나의 프로비저너를 실패시켜 본 실험에서 성공한 쪽과 그 아래 3개는 만들어졌고, 실패한 쪽 아래 리소스만 생기지 않았어요. 실패한 리소스는 상태에 남되 tainted로 표시돼 다음 plan에서 is tainted, so must be replaced로 다시 만들어져요. 그러니 오류가 나면 콘솔에서 손으로 지우지 말고, 원인을 고친 뒤 plan으로 남은 작업만 확인하고 다시 apply해요.
2. Error: Cycle: A, B가 나요. 두 리소스가 서로의 값을 참조하는 경우예요. 대표 예는 보안 그룹 두 개가 인라인 규칙으로 서로를 가리키는 경우예요. 규칙을 별도 리소스로 빼면 그룹 둘이 먼저 만들어지고 규칙이 나중에 붙어 고리가 끊겨요. 고리를 그림으로 보려면 terraform graph -type=plan -draw-cycles를 써요. graph 문서에 따르면 -draw-cycles는 -type과 함께 써야 해요.
3. 교체할 때 "이미 존재합니다" 오류가 나요. create_before_destroy가 직접 켜졌거나 전파돼서 새 객체를 옛 객체와 같은 이름으로 만들려는 거예요. plan 머리말이 +/-인지 확인하고, 이름 고정 리소스에 접두사·접미사를 넣어요.
4. 스로틀링 오류가 나요. 먼저 프로바이더의 재시도 설정을 늘려요. 그래도 나면 parallelism을 낮추되, 같은 계정·리전에 동시에 도는 다른 파이프라인이 있는지도 봐요. 한도는 계정 단위로 걸리는 경우가 많아서 파이프라인 두 개가 각자 10이면 실제로는 20이에요.
5. apply가 늘 느려요. parallelism을 1로 한 번 돌려 노드별 Creation complete after Ns 시간을 모으고, 가장 긴 사슬 위에 오래 걸리는 리소스가 있는지 봐요. 사슬이 원인이면 값을 올려도 소용없어요.
6. 바꾼 적 없는 리소스가 교체 계획에 올라와요. 그 리소스 쪽 줄에 (known after apply)와 # forces replacement가 있는지 보고, 그 값이 어느 데이터 소스나 모듈에서 오는지 거슬러 올라가요. 중간에 depends_on이 걸려 있으면 위 판단표로 지울 수 있는지 따져요.
도입 전에 따질 것
테라폼을 여러 팀이 같이 쓰기 전에 그래프와 병렬 실행 관점에서 정해 둘 것들이에요.
- parallelism 기본값을 바꿀 권한: 파이프라인마다 제각각 올리면 같은 계정의 속도 제한을 서로 깎아 먹어요. 값을 바꾸는 곳을 한 군데(공용 CI 템플릿)로 모으고 기본은 10으로 둬요.
- depends_on 리뷰 규칙: 새
depends_on에는 "어떤 동작에 기대는지" 주석을 달게 해요. 모듈 블록의depends_on은 리뷰에서 한 번 더 묻는 항목으로 둬요. - 교체가 끊김을 일으키는 리소스 목록: 서비스 앞단 리소스는 미리 골라
create_before_destroy를 켤지, 이름 규칙을 접두사 방식으로 바꿀지 정해 둬요. 사고가 난 뒤 급히 켜면 전파 때문에 아래 리소스에서 이름 충돌이 날 수 있어요.
자주 묻는 질문
parallelism을 100으로 올리면 apply가 10배 빨라지나요?
독립 리소스가 100개 이상이고 각각 오래 걸릴 때만 그 근처까지 빨라져요. 측정에서 30개를 10에서 30으로 올리자 4.1초가 1.9초가 됐지만, 6단 사슬은 1이든 30이든 7.7초였어요. 실제 클라우드에서는 요청이 몰려 스로틀링 재시도가 늘면서 오히려 느려질 수도 있어요.
count로 만든 리소스 100개는 parallelism을 하나만 쓰나요?
아니에요. 인스턴스마다 노드가 따로 생기고 각자 자리 하나씩을 써요. 기본값 10이면 100개가 열 개씩 진행돼요. 그래서 큰 count나 for_each가 있는 설정에서 값의 효과가 가장 크게 보여요.
depends_on을 지웠다가 순서가 틀어지면 어떻게 알 수 있나요?
값을 참조하는 의존은 지워도 순서가 유지되니 plan 결과가 같아요. 순서가 정말 필요한 숨은 의존이었다면 apply 중 "권한 없음"이나 "찾을 수 없음" 같은 오류로 드러나요. 스테이징에서 먼저 확인하고, 그 리소스 하나에만 다시 걸어요.
create_before_destroy는 한번 켰다 끄면 바로 원래대로 돌아가나요?
lifecycle 문서에 따르면 lifecycle 규칙 가운데 create_before_destroy만 상태에 기록돼요. 테라폼 1.9.8에서 위 실험의 lifecycle 줄을 지우고 다시 교체하자 plan 머리말이 곧바로 -/+ destroy and then create replacement로 바뀌었어요. 다만 그 리소스에 기대는 다른 리소스에 이 설정이 켜져 있으면 전파 때문에 계속 먼저 만들어지니, 끈 뒤 첫 교체는 머리말을 확인하고 적용해요.
정리
- parallelism은 기본 10을 두고, 디버깅 때만 1, 넓은 그래프가 느릴 때만 올려서 재 봐요. 스로틀링은 프로바이더 재시도 설정이 먼저예요.
- 측정에서 독립 30개는 값에 따라 37.2초 → 1.9초로 줄었지만 6단 사슬은 7.7초 그대로였어요. 느리면 가장 긴 사슬부터 봐요.
depends_on은 값이 오가지 않는 숨은 의존에만 걸어요. 데이터 소스·모듈에 걸면 바뀔 이유 없는 리소스까지 교체될 수 있어요.create_before_destroy는 의존 대상에 전파되니, 켜기 전에 아래 리소스들이 두 벌 공존할 수 있는지 확인해요.- plan·상태 운영은 테라폼 plan 옵션 고르는 법에서, 파이프라인 일정은 크론 표현식 읽는 법에서 이어서 볼 수 있어요.
더 깊이 읽기
이 글은 실무에서 판단할 때 필요한 것 위주로 정리했어요. 같은 주제의 내부 동작 원리는 devboi 기술 블로그의 테라폼 의존성 그래프와 병렬 실행 원리 — parallelism에서 도식과 코드로 단계별로 풀었어요.