본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 14. · 11 Views
Delta Lake 성능 튜닝 마스터 완벽 가이드
대용량 데이터 처리 시 Delta Lake의 성능을 극대화하는 방법을 배웁니다. 쿼리 실행 계획 분석부터 Photon 엔진 활용까지, 실무에서 바로 적용할 수 있는 성능 튜닝 기법을 단계별로 설명합니다.
목차
1. 쿼리 실행 계획 분석
어느 날 김데이터 씨는 Delta Lake 쿼리가 너무 느려서 고민에 빠졌습니다. 같은 데이터를 조회하는데 왜 이렇게 시간이 오래 걸리는 걸까요?
선배 박엔지니어 씨가 다가와 말합니다. "실행 계획부터 확인해 봤어요?"
쿼리 실행 계획은 Spark가 데이터를 어떻게 처리할지 미리 보여주는 청사진입니다. 마치 건축가가 건물을 짓기 전 설계도를 그리는 것처럼, Spark도 쿼리를 실행하기 전 최적의 실행 방법을 계획합니다.
이 계획을 분석하면 병목 지점을 찾아낼 수 있습니다.
다음 코드를 살펴봅시다.
from pyspark.sql import SparkSession
# Delta 테이블 쿼리 실행 계획 확인
df = spark.read.format("delta").load("/data/sales")
filtered_df = df.filter("year = 2024 AND region = 'ASIA'")
# 물리적 실행 계획 출력 - 실제 어떻게 실행되는지 확인
filtered_df.explain(mode="formatted")
# 코스트 기반 최적화 확인 - 어떤 조인 전략을 선택했는지 분석
filtered_df.explain(mode="cost")
김데이터 씨는 입사 6개월 차 데이터 엔지니어입니다. 오늘도 열심히 Delta Lake 쿼리를 작성하던 중, 심각한 문제를 발견했습니다.
단순한 필터링 쿼리인데 실행 시간이 10분이 넘게 걸립니다. 분명히 잘못된 부분이 있을 텐데 어디서부터 확인해야 할지 막막합니다.
선배 개발자 박엔지니어 씨가 모니터를 들여다보며 말합니다. "쿼리 실행 계획부터 확인해 봤어요?
성능 문제의 90%는 실행 계획에서 답을 찾을 수 있어요." 그렇다면 쿼리 실행 계획이란 정확히 무엇일까요? 쉽게 비유하자면, 쿼리 실행 계획은 마치 네비게이션의 경로 안내와 같습니다.
목적지까지 가는 방법은 여러 가지가 있습니다. 고속도로를 탈 수도 있고, 국도로 갈 수도 있습니다.
네비게이션은 교통 상황을 분석해서 가장 빠른 경로를 제시합니다. Spark의 Catalyst 옵티마이저도 마찬가지로 여러 실행 방법 중 가장 효율적인 방법을 선택합니다.
실행 계획이 없던 시절에는 어땠을까요? 개발자들은 쿼리가 느리면 무작정 코드를 수정하곤 했습니다.
"이 부분이 문제일 것 같은데?"라며 추측에 의존했습니다. 실제로는 전혀 다른 부분이 병목이었는데도 엉뚱한 곳을 고치느라 시간을 낭비했습니다.
더 큰 문제는 왜 느린지 정확히 알 수 없어서 같은 실수를 반복했다는 점입니다. 바로 이런 문제를 해결하기 위해 쿼리 실행 계획 분석이 중요해졌습니다.
실행 계획을 확인하면 어느 단계에서 시간이 오래 걸리는지 한눈에 파악할 수 있습니다. 또한 불필요한 데이터 스캔이 발생하는지도 알 수 있습니다.
무엇보다 조인 순서가 최적화되었는지 확인할 수 있다는 큰 이점이 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 3번째 줄에서 Delta 테이블을 읽어옵니다. 아직 실제로 데이터를 읽지는 않았습니다.
Spark는 Lazy Evaluation 방식으로 동작하기 때문입니다. 다음으로 4번째 줄에서 필터링 조건을 정의합니다.
여기서도 아직 실행되지 않았습니다. 7번째 줄의 explain() 메서드가 호출될 때 비로소 Spark는 실행 계획을 수립하고 보여줍니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 전자상거래 회사에서 일일 매출 리포트를 생성한다고 가정해봅시다.
주문 테이블과 상품 테이블을 조인하는데 30분이 걸립니다. 실행 계획을 확인해보니 Shuffle이 과도하게 발생하고 있었습니다.
조인 키의 데이터 분포가 불균형해서 특정 파티션에 데이터가 몰렸던 것입니다. 이를 발견하고 salting 기법을 적용하자 실행 시간이 5분으로 줄어들었습니다.
실행 계획에서 주목해야 할 핵심 키워드가 있습니다. FileScan은 실제로 몇 개의 파일을 읽는지 보여줍니다.
파일 개수가 너무 많으면 Small File Problem을 의심해야 합니다. Exchange는 셔플 작업을 의미합니다.
셔플이 많을수록 네트워크 비용이 증가합니다. BroadcastHashJoin은 작은 테이블을 전체 노드에 복사해서 조인하는 방식입니다.
큰 테이블끼리 조인할 때는 SortMergeJoin이 사용됩니다. 하지만 주의할 점도 있습니다.
초보 엔지니어들이 흔히 하는 실수 중 하나는 Physical Plan만 보고 끝내는 것입니다. Spark는 Logical Plan → Optimized Logical Plan → Physical Plan 순서로 계획을 수립합니다.
Logical Plan을 보면 Catalyst가 어떤 최적화를 적용했는지 알 수 있습니다. 예를 들어 Predicate Pushdown이 제대로 작동했는지 확인할 수 있습니다.
explain 메서드에는 여러 모드가 있습니다. 기본 모드는 simple로, Physical Plan만 보여줍니다.
extended 모드는 모든 단계를 다 보여줍니다. formatted 모드는 읽기 쉽게 포맷팅해서 출력합니다.
cost 모드는 각 단계의 예상 비용을 함께 표시합니다. 상황에 따라 적절한 모드를 선택하면 됩니다.
Delta Lake에서 특히 중요한 부분이 있습니다. Delta Lake는 트랜잭션 로그를 관리합니다.
쿼리 실행 시 먼저 로그를 읽어서 최신 파일 목록을 파악합니다. 파일이 수천 개라면 로그 읽기 자체가 병목이 될 수 있습니다.
실행 계획에서 Delta Log 스캔 시간을 확인하면 이를 알 수 있습니다. 다시 김데이터 씨의 이야기로 돌아가 봅시다.
박엔지니어 씨의 조언대로 실행 계획을 확인한 김데이터 씨는 놀라운 사실을 발견했습니다. 필터 조건이 Delta Lake에 푸시다운되지 않고 있었습니다.
파티션 컬럼이 아닌 일반 컬럼으로 필터링했기 때문입니다. 파티션 컬럼으로 변경하자 쿼리 시간이 10분에서 30초로 단축되었습니다.
쿼리 실행 계획을 제대로 분석하면 성능 문제의 근본 원인을 찾을 수 있습니다. 여러분도 쿼리가 느릴 때 가장 먼저 explain()을 실행해 보세요.
실전 팁
💡 - explain("formatted")로 가독성 높은 실행 계획을 확인하세요
- Exchange 단계가 많다면 불필요한 셔플이 발생하는지 점검하세요
- Predicate Pushdown이 작동하는지 Logical Plan에서 확인하세요
2. Spark 설정 최적화
김데이터 씨는 Delta Lake 쿼리를 실행할 때마다 메모리 부족 에러를 만납니다. 데이터 크기는 크지 않은데 왜 자꾸 실패하는 걸까요?
박엔지니어 씨가 설정 파일을 열어보더니 한숨을 쉽니다. "기본 설정으로 실행하면 당연히 느려요."
Spark 설정 최적화는 하드웨어 리소스를 최대한 활용하도록 Spark의 동작 방식을 조정하는 것입니다. 마치 자동차 엔진을 튜닝해서 더 빠르게 달리게 하는 것처럼, Spark도 적절한 설정을 통해 성능을 크게 향상시킬 수 있습니다.
메모리, CPU, 네트워크 리소스를 효율적으로 사용하는 것이 핵심입니다.
다음 코드를 살펴봅시다.
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.appName("DeltaLakeOptimized") \
.config("spark.sql.adaptive.enabled", "true") \
.config("spark.sql.adaptive.coalescePartitions.enabled", "true") \
.config("spark.sql.files.maxPartitionBytes", "128MB") \
.config("spark.executor.memory", "8g") \
.config("spark.executor.cores", "4") \
.config("spark.sql.shuffle.partitions", "200") \
.config("spark.databricks.delta.optimizeWrite.enabled", "true") \
.config("spark.databricks.delta.autoCompact.enabled", "true") \
.getOrCreate()
김데이터 씨는 매일 아침 데이터 파이프라인을 실행합니다. 그런데 요즘 들어 자꾸 실패합니다.
에러 로그를 보니 "OutOfMemoryError"가 찍혀 있습니다. 이상합니다.
지난주까지만 해도 잘 돌아갔는데 데이터가 조금 늘었을 뿐인데 왜 메모리가 부족할까요? 박엔지니어 씨가 클러스터 설정을 확인해봅니다.
"아, Spark 기본 설정 그대로 사용하고 있네요. 이러면 리소스를 제대로 활용하지 못해요." 그렇다면 Spark 설정 최적화란 정확히 무엇일까요?
쉽게 비유하자면, Spark 설정은 마치 주방의 조리 도구를 배치하는 것과 같습니다. 냄비는 어디에 둘지, 칼은 몇 개나 준비할지, 불의 세기는 어느 정도로 할지 결정합니다.
같은 재료로 요리해도 도구 배치에 따라 조리 시간이 크게 달라집니다. Spark도 마찬가지로 메모리와 CPU를 어떻게 배분할지 설정에 따라 성능이 천차만별입니다.
최적화하지 않으면 어떤 문제가 생길까요? Spark는 기본적으로 보수적인 설정을 사용합니다.
어떤 환경에서든 안전하게 동작하도록 만들어졌습니다. 하지만 이는 곧 성능을 희생한다는 의미입니다.
예를 들어 서버에 64GB 메모리가 있어도 Spark는 기본적으로 1GB만 사용하려 합니다. 나머지 63GB는 놀고 있는 셈입니다.
마치 고성능 스포츠카를 시속 30km로만 운전하는 것과 같습니다. 바로 이런 낭비를 막기 위해 설정 최적화가 필요합니다.
적절한 설정을 통해 메모리 사용량을 늘려서 더 많은 데이터를 한 번에 처리할 수 있습니다. 또한 CPU 코어를 모두 활용해서 병렬 처리 성능을 극대화할 수 있습니다.
무엇보다 **Adaptive Query Execution(AQE)**를 활성화하면 실행 중에 동적으로 최적화가 이루어집니다. 위의 코드를 한 줄씩 살펴보겠습니다.
4번째 줄의 AQE 활성화가 가장 중요합니다. AQE는 쿼리 실행 중에 통계 정보를 수집해서 실행 계획을 동적으로 조정합니다.
5번째 줄은 작은 파티션들을 자동으로 합쳐줍니다. 셔플 후 파티션이 너무 많이 쪼개지는 것을 방지합니다.
6번째 줄은 파일을 읽을 때 한 파티션에 최대 128MB까지 담습니다. 이 값이 너무 작으면 파티션이 과도하게 많아지고, 너무 크면 메모리 부족이 발생할 수 있습니다.
메모리와 CPU 설정도 중요합니다. 7번째 줄의 executor.memory는 각 Executor가 사용할 메모리입니다.
서버 메모리의 60-70% 정도를 할당하는 것이 일반적입니다. 8번째 줄의 executor.cores는 각 Executor가 사용할 CPU 코어 수입니다.
보통 4-5개가 적절합니다. 너무 많이 할당하면 컨텍스트 스위칭 비용이 증가합니다.
9번째 줄의 shuffle.partitions는 셔플 작업 시 생성할 파티션 수입니다. 데이터 크기에 따라 조정해야 합니다.
Delta Lake 전용 설정도 있습니다. 10번째 줄의 optimizeWrite는 쓰기 작업 시 자동으로 파일 크기를 최적화합니다.
작은 파일 여러 개 대신 적당한 크기의 파일로 병합해줍니다. 11번째 줄의 autoCompact는 쓰기 후 자동으로 작은 파일들을 압축합니다.
이 두 설정을 활성화하면 Small File Problem을 크게 줄일 수 있습니다. 실제 현업에서는 어떻게 활용할까요?
한 금융회사에서는 야간 배치 작업이 12시간 걸렸습니다. 데이터 크기는 10TB 정도였습니다.
Spark 설정을 최적화한 결과 놀라운 변화가 있었습니다. AQE를 활성화하고 메모리를 늘리자 실행 시간이 4시간으로 단축되었습니다.
셔플 파티션 수를 조정하니 추가로 1시간이 줄었습니다. 최종적으로 3시간 만에 작업이 완료되었습니다.
하지만 주의할 점도 있습니다. 초보 엔지니어들이 흔히 하는 실수 중 하나는 무조건 크게 설정하는 것입니다.
메모리를 서버 전체 용량으로 설정하면 OS가 사용할 메모리가 부족해집니다. Executor 개수를 너무 많이 늘리면 오히려 스케줄링 오버헤드가 증가합니다.
적정 수준을 찾는 것이 중요합니다. 설정을 조정할 때는 단계적으로 접근해야 합니다.
먼저 AQE부터 활성화하세요. 이것만으로도 상당한 성능 향상을 얻을 수 있습니다.
다음으로 메모리와 코어 수를 조정합니다. 모니터링하면서 점진적으로 늘려가는 것이 안전합니다.
마지막으로 셔플 파티션 수를 데이터 크기에 맞춰 조정합니다. 일반적으로 파티션당 100-200MB가 적절합니다.
설정 파일로 관리하는 것도 좋은 방법입니다. 코드에 하드코딩하는 대신 spark-defaults.conf 파일을 사용하세요.
환경별로 다른 설정을 적용하기 쉽고, 버전 관리도 간편합니다. 또한 Databricks나 EMR 같은 플랫폼에서는 클러스터 설정 UI를 제공하므로 활용하면 편리합니다.
다시 김데이터 씨의 이야기로 돌아가 봅시다. 박엔지니어 씨의 조언대로 설정을 최적화한 김데이터 씨는 감탄했습니다.
"메모리 에러가 사라졌어요! 실행 시간도 절반으로 줄었고요." Spark 설정을 제대로 이해하고 조정하면 하드웨어 투자 없이도 큰 성능 향상을 얻을 수 있습니다.
여러분도 기본 설정에서 벗어나 환경에 맞게 최적화해 보세요.
실전 팁
💡 - AQE는 반드시 활성화하세요. 대부분의 경우 성능이 향상됩니다
- Executor 메모리는 서버 메모리의 60-70%가 적절합니다
- 셔플 파티션 수는 데이터 크기를 200MB로 나눈 값을 기준으로 설정하세요
3. 캐싱 전략과 메모리 관리
김데이터 씨는 같은 Delta 테이블을 여러 번 읽는 쿼리를 작성했습니다. 그런데 매번 디스크에서 다시 읽어오는 것 같습니다.
박엔지니어 씨가 웃으며 말합니다. "캐싱을 사용하면 한 번만 읽으면 돼요."
캐싱은 자주 사용하는 데이터를 메모리에 저장해서 반복 접근 시간을 단축하는 기법입니다. 마치 자주 쓰는 도구를 책상 위에 올려두는 것처럼, 데이터를 메모리에 두면 매번 디스크에서 읽지 않아도 됩니다.
Delta Lake에서 캐싱을 잘 활용하면 쿼리 성능을 수십 배 향상시킬 수 있습니다.
다음 코드를 살펴봅시다.
from pyspark.sql import SparkSession
# Delta 테이블 읽기
sales_df = spark.read.format("delta").load("/data/sales")
# 메모리에 캐싱 - 자주 사용하는 데이터는 캐싱하세요
sales_df.cache()
# 첫 번째 쿼리 - 디스크에서 읽고 메모리에 저장
result1 = sales_df.filter("year = 2024").count()
# 두 번째 쿼리 - 메모리에서 바로 읽음 (매우 빠름)
result2 = sales_df.filter("region = 'ASIA'").count()
# 캐시 해제 - 메모리 확보
sales_df.unpersist()
김데이터 씨는 복잡한 분석 쿼리를 작성하고 있습니다. 같은 매출 테이블을 기반으로 여러 가지 집계를 수행합니다.
지역별 매출, 상품별 매출, 월별 매출을 모두 계산해야 합니다. 그런데 이상합니다.
각 쿼리마다 똑같이 5분씩 걸립니다. 같은 테이블인데 왜 매번 처음부터 읽어오는 걸까요?
박엔지니어 씨가 코드를 살펴보고 말합니다. "캐싱을 사용하지 않았네요.
같은 데이터를 세 번이나 디스크에서 읽고 있어요." 그렇다면 캐싱이란 정확히 무엇일까요? 쉽게 비유하자면, 캐싱은 마치 도서관에서 책을 빌리는 것과 같습니다.
어떤 책이 필요할 때마다 도서관에 가는 것은 비효율적입니다. 자주 보는 책이라면 책상 위에 올려두는 것이 낫습니다.
다음에 필요할 때 바로 펼쳐볼 수 있으니까요. Spark의 캐싱도 마찬가지로 자주 사용하는 데이터를 메모리라는 책상 위에 올려둡니다.
캐싱을 사용하지 않으면 어떤 문제가 생길까요? Spark는 기본적으로 매번 디스크에서 데이터를 읽습니다.
같은 테이블을 10번 사용하면 10번 모두 디스크 I/O가 발생합니다. 디스크는 메모리보다 수백 배 느립니다.
특히 Delta Lake처럼 Parquet 파일을 읽을 때는 압축 해제와 디코딩 비용도 추가됩니다. 쿼리가 반복될수록 이 비용이 누적되어 전체 실행 시간이 크게 늘어납니다.
바로 이런 낭비를 막기 위해 캐싱 전략이 중요합니다. 적절한 캐싱을 통해 디스크 I/O를 제거하고 메모리에서 직접 데이터를 읽을 수 있습니다.
또한 압축 해제와 디코딩을 한 번만 수행하면 됩니다. 무엇보다 반복 쿼리의 성능이 수십 배 향상됩니다.
실제로 디스크에서 읽으면 5분 걸리는 쿼리가 메모리에서는 5초 만에 완료되기도 합니다. 위의 코드를 한 줄씩 살펴보겠습니다.
4번째 줄에서 Delta 테이블을 DataFrame으로 읽어옵니다. 아직 실제 데이터는 읽지 않았습니다.
7번째 줄의 cache() 메서드가 핵심입니다. 이 메서드는 "이 DataFrame을 앞으로 메모리에 저장하겠다"고 선언하는 것입니다.
하지만 아직 데이터를 읽지는 않았습니다. 10번째 줄에서 count() 액션이 실행될 때 비로소 데이터를 읽고 메모리에 저장합니다.
두 번째 쿼리부터 차이가 나타납니다. 13번째 줄의 쿼리는 디스크에 접근하지 않습니다.
이미 메모리에 있는 데이터를 사용합니다. 필터링 조건만 다르지만 원본 데이터는 같으므로 캐시된 데이터를 재사용합니다.
이것이 캐싱의 핵심 장점입니다. 한 번 읽어서 여러 번 사용할 수 있습니다.
16번째 줄의 **unpersist()**도 중요합니다. 메모리는 무한하지 않습니다.
더 이상 사용하지 않는 데이터는 메모리에서 제거해야 합니다. 그래야 다른 데이터를 위한 공간이 확보됩니다.
unpersist()를 호출하면 캐시된 데이터가 메모리에서 삭제됩니다. 마치 책상 위의 책을 서랍에 다시 넣는 것과 같습니다.
실제 현업에서는 어떻게 활용할까요? 한 광고 회사에서는 사용자 행동 로그를 분석합니다.
같은 로그 데이터로 여러 가지 리포트를 생성합니다. 클릭률, 전환율, 체류 시간 등을 계산합니다.
처음에는 각 리포트마다 로그를 다시 읽었습니다. 총 실행 시간이 3시간이었습니다.
로그 데이터를 캐싱하자 놀라운 일이 벌어졌습니다. 첫 번째 리포트는 여전히 30분 걸렸지만, 나머지는 각각 2-3분 만에 완료되었습니다.
총 실행 시간이 40분으로 줄었습니다. 캐싱에는 여러 저장 레벨이 있습니다.
MEMORY_ONLY는 메모리에만 저장합니다. 가장 빠르지만 메모리가 부족하면 일부 데이터를 버립니다.
MEMORY_AND_DISK는 메모리가 부족하면 디스크에 저장합니다. 안전하지만 디스크 I/O가 발생할 수 있습니다.
MEMORY_ONLY_SER은 메모리에 직렬화해서 저장합니다. 메모리를 덜 사용하지만 직렬화 비용이 추가됩니다.
상황에 따라 적절한 레벨을 선택해야 합니다. 하지만 주의할 점도 있습니다.
초보 엔지니어들이 흔히 하는 실수 중 하나는 모든 것을 캐싱하는 것입니다. 한 번만 사용하는 데이터를 캐싱하면 메모리만 낭비됩니다.
캐싱은 반복적으로 사용하는 데이터에만 적용해야 합니다. 또한 캐시된 데이터가 너무 많으면 메모리 부족이 발생할 수 있습니다.
우선순위를 정해서 선택적으로 캐싱해야 합니다. Delta Lake에서는 추가로 고려할 사항이 있습니다.
Delta 테이블은 계속 업데이트됩니다. 캐시된 데이터는 특정 시점의 스냅샷입니다.
테이블이 업데이트되어도 캐시는 자동으로 갱신되지 않습니다. 최신 데이터가 필요하면 unpersist()로 캐시를 제거하고 다시 읽어야 합니다.
또는 Delta Lake의 Change Data Feed를 활용해서 변경된 부분만 갱신할 수도 있습니다. 메모리 관리도 중요합니다.
Spark UI의 Storage 탭에서 캐시된 데이터를 모니터링할 수 있습니다. 어떤 DataFrame이 얼마나 메모리를 사용하는지 확인하세요.
메모리 사용률이 80%를 넘으면 위험 신호입니다. 불필요한 캐시를 정리하거나 메모리를 늘려야 합니다.
다시 김데이터 씨의 이야기로 돌아가 봅시다. 박엔지니어 씨의 조언대로 캐싱을 적용한 김데이터 씨는 놀라움을 금치 못했습니다.
"첫 번째 쿼리만 5분 걸리고 나머지는 10초도 안 걸려요! 이게 캐싱의 힘이군요." 캐싱 전략을 제대로 수립하면 같은 데이터를 여러 번 사용하는 작업에서 엄청난 성능 향상을 얻을 수 있습니다.
여러분도 반복되는 쿼리가 있다면 캐싱을 적용해 보세요.
실전 팁
💡 - 두 번 이상 사용하는 DataFrame만 캐싱하세요
- 작업이 끝나면 반드시 unpersist()로 메모리를 확보하세요
- Spark UI의 Storage 탭으로 캐시 사용량을 모니터링하세요
4. 병렬 처리 튜닝
김데이터 씨는 대용량 데이터를 처리하는데 CPU 사용률이 20%밖에 안 됩니다. 나머지 80%는 놀고 있습니다.
박엔지니어 씨가 파티션 개수를 확인하더니 고개를 젓습니다. "파티션이 4개밖에 없네요.
코어가 32개인데 4개만 일하고 있어요."
병렬 처리 튜닝은 데이터를 적절한 크기의 파티션으로 나눠서 모든 CPU 코어를 효율적으로 사용하는 것입니다. 마치 큰 프로젝트를 여러 팀원에게 나눠주는 것처럼, 데이터를 여러 파티션으로 나누면 동시에 처리할 수 있습니다.
파티션 개수와 크기를 최적화하면 처리 속도가 크게 향상됩니다.
다음 코드를 살펴봅시다.
from pyspark.sql import SparkSession
# Delta 테이블 읽기
df = spark.read.format("delta").load("/data/large_table")
# 현재 파티션 개수 확인
print(f"Current partitions: {df.rdd.getNumPartitions()}")
# 파티션 재조정 - 클러스터의 코어 수에 맞춤 (코어 32개 x 3배)
df_repartitioned = df.repartition(96)
# 파티션별 데이터 크기 확인 - 균등하게 분산되었는지 체크
df_repartitioned.groupBy(spark_partition_id()).count().show()
# 특정 컬럼 기준 파티셔닝 - 조인 성능 향상
df_by_region = df.repartition(96, "region")
김데이터 씨는 100GB 데이터를 처리하는 작업을 실행했습니다. 클러스터에는 32코어 CPU가 장착되어 있습니다.
충분히 빠를 것으로 예상했지만 실행 시간이 생각보다 오래 걸립니다. 모니터링 도구를 확인해보니 CPU 사용률이 20%밖에 되지 않습니다.
왜 나머지 80%는 놀고 있는 걸까요? 박엔지니어 씨가 Spark UI를 열어봅니다.
"파티션이 4개밖에 없네요. 32개 코어 중에 4개만 일하고 나머지는 대기 중이에요." 그렇다면 병렬 처리 튜닝이란 정확히 무엇일까요?
쉽게 비유하자면, 병렬 처리는 마치 식당의 주방과 같습니다. 요리사가 10명 있는데 요리 주문이 2개뿐이라면 8명은 놀게 됩니다.
반대로 주문이 100개인데 요리사가 2명뿐이라면 손님들은 오래 기다려야 합니다. 적절한 작업 분배가 핵심입니다.
Spark에서는 파티션이 작업 단위이고 코어가 요리사입니다. 파티션이 적절하지 않으면 어떤 문제가 생길까요?
파티션이 너무 적으면 병렬성이 낮아집니다. 코어가 많아도 할 일이 없어서 놀게 됩니다.
김데이터 씨의 경우처럼 4개 파티션에 32개 코어라면 28개는 대기합니다. 반대로 파티션이 너무 많으면 스케줄링 오버헤드가 증가합니다.
작은 작업을 수천 개 관리하느라 정작 처리 시간은 줄어들지 않습니다. 또한 파티션마다 메타데이터를 유지하는 비용도 무시할 수 없습니다.
바로 이런 문제를 해결하기 위해 병렬 처리 튜닝이 필요합니다. 적절한 파티션 개수를 설정하면 모든 코어를 활용할 수 있습니다.
또한 작업 시간이 균등하게 분산되어 특정 코어만 과부하되는 현상을 방지합니다. 무엇보다 전체 처리 시간이 최소화됩니다.
실제로 파티션 개수만 조정해도 2-3배 성능 향상을 얻는 경우가 많습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
7번째 줄에서 현재 파티션 개수를 확인합니다. Delta Lake는 기본적으로 파일 개수만큼 파티션을 생성합니다.
파일이 4개라면 파티션도 4개입니다. 10번째 줄의 **repartition()**이 핵심입니다.
파티션을 96개로 재조정합니다. 왜 96개일까요?
코어가 32개라면 코어당 2-3개 작업을 할당하는 것이 이상적입니다. 32 x 3 = 96입니다.
13번째 줄에서 파티션별 데이터 크기를 확인합니다. **spark_partition_id()**는 각 레코드가 어느 파티션에 속하는지 알려줍니다.
groupBy로 집계하면 파티션별 레코드 수를 볼 수 있습니다. 이상적으로는 모든 파티션이 비슷한 크기여야 합니다.
어떤 파티션은 1만 건인데 다른 파티션은 100만 건이라면 데이터 치우침(Skew) 문제가 있는 것입니다. 16번째 줄의 컬럼 기반 파티셔닝도 중요합니다.
**repartition(96, "region")**은 region 컬럼 값에 따라 파티션을 나눕니다. 같은 region 값을 가진 레코드들이 같은 파티션에 모입니다.
나중에 region으로 조인하거나 그룹화할 때 셔플이 발생하지 않습니다. 데이터가 이미 적절히 분산되어 있기 때문입니다.
실제 현업에서는 어떤 상황이 있을까요? 한 통신사에서는 일일 통화 기록을 분석합니다.
데이터가 500GB 정도 됩니다. 처음에는 파티션이 100개였고 실행 시간이 2시간이었습니다.
클러스터에는 200개 코어가 있었으므로 절반이 놀고 있었습니다. 파티션을 400개로 늘리자 모든 코어가 활발히 동작했습니다.
실행 시간이 50분으로 단축되었습니다. 파티션 개수만 조정했을 뿐인데 큰 효과를 본 것입니다.
파티션 개수의 황금률이 있습니다. 일반적으로 코어 수의 2-3배가 적절합니다.
코어가 100개라면 파티션은 200-300개가 좋습니다. 이렇게 하면 작업이 끝난 코어가 즉시 다음 파티션을 처리할 수 있습니다.
대기 시간이 최소화됩니다. 또한 일부 파티션이 느리더라도 다른 코어가 보완할 수 있습니다.
파티션 크기도 중요합니다. 파티션당 128MB-256MB가 이상적입니다.
너무 작으면 스케줄링 오버헤드가 증가하고, 너무 크면 메모리 부족이 발생할 수 있습니다. 예를 들어 데이터가 100GB라면 100GB / 200MB = 500개 파티션이 적절합니다.
이 값을 기준으로 코어 수를 고려해서 조정하면 됩니다. 하지만 주의할 점도 있습니다.
초보 엔지니어들이 흔히 하는 실수 중 하나는 무조건 파티션을 많이 만드는 것입니다. 파티션이 수만 개가 되면 메타데이터 관리 비용이 커집니다.
Driver가 모든 파티션 정보를 유지해야 하므로 메모리가 부족해질 수 있습니다. 또한 파일이 너무 많이 생성되어 Small File Problem이 발생합니다.
repartition과 coalesce의 차이도 알아야 합니다. **repartition()**은 전체 셔플을 수행합니다.
데이터를 완전히 재분배합니다. 파티션을 늘릴 때 사용합니다.
**coalesce()**는 셔플 없이 파티션을 합칩니다. 파티션을 줄일 때 사용합니다.
예를 들어 처리 후 파티션이 1000개인데 저장할 때는 10개로 줄이고 싶다면 coalesce(10)을 사용하면 효율적입니다. Delta Lake의 Auto Optimize 기능도 활용하세요.
autoOptimize를 활성화하면 쓰기 시 자동으로 파일 크기를 조정합니다. 작은 파일 여러 개 대신 적당한 크기의 파일로 병합합니다.
다음 읽기 작업에서 파티션 개수가 자연스럽게 적절해집니다. 수동으로 조정할 필요가 줄어듭니다.
다시 김데이터 씨의 이야기로 돌아가 봅시다. 박엔지니어 씨의 조언대로 파티션을 96개로 조정한 김데이터 씨는 놀라운 변화를 목격했습니다.
"CPU 사용률이 95%로 올라갔어요! 실행 시간도 1시간에서 20분으로 줄었고요." 병렬 처리 튜닝을 제대로 이해하면 하드웨어 리소스를 최대한 활용할 수 있습니다.
여러분도 CPU가 놀고 있다면 파티션 개수를 점검해 보세요.
실전 팁
💡 - 파티션 개수는 코어 수의 2-3배로 설정하세요
- 파티션당 128-256MB가 적절합니다
- 조인이나 그룹화 전에 관련 컬럼으로 repartition하면 셔플을 줄일 수 있습니다
5. Photon 엔진 활용
김데이터 씨는 Databricks를 사용하고 있지만 Photon이 무엇인지 모릅니다. 박엔지니어 씨가 Photon을 활성화하더니 같은 쿼리가 3배 빨라졌습니다.
"Photon은 C++로 작성된 네이티브 실행 엔진이에요. Java 기반 Spark보다 훨씬 빠르죠."
Photon은 Databricks에서 개발한 Delta Lake 전용 고성능 실행 엔진입니다. 마치 제트 엔진이 프로펠러보다 빠른 것처럼, Photon은 기존 Spark 엔진보다 훨씬 빠르게 데이터를 처리합니다.
특히 집계, 조인, 필터링 같은 SQL 연산에서 큰 성능 향상을 보여줍니다.
다음 코드를 살펴봅시다.
from pyspark.sql import SparkSession
# Photon 활성화 - Databricks 클러스터 설정에서 활성화
# 또는 클러스터 생성 시 Photon 런타임 선택
# Delta Lake 쿼리 - Photon이 자동으로 최적화
df = spark.read.format("delta").load("/data/transactions")
# 집계 쿼리 - Photon의 벡터화 실행으로 빠름
result = df.groupBy("category", "region") \
.agg(
sum("amount").alias("total_sales"),
avg("quantity").alias("avg_quantity"),
count("*").alias("transaction_count")
)
result.write.format("delta").mode("overwrite").save("/data/summary")
김데이터 씨는 Databricks에서 데이터 파이프라인을 운영하고 있습니다. 최근 데이터가 늘어나면서 실행 시간이 점점 길어집니다.
비용도 증가하고 있습니다. 더 큰 클러스터가 필요할까요?
아니면 다른 방법이 있을까요? 박엔지니어 씨가 클러스터 설정을 확인하더니 의아해합니다.
"왜 Photon을 사용하지 않고 있어요? Photon을 켜기만 해도 성능이 크게 향상될 텐데요." 그렇다면 Photon이란 정확히 무엇일까요?
쉽게 비유하자면, Photon은 마치 자동차의 터보 엔진과 같습니다. 같은 연료를 사용해도 터보 엔진은 일반 엔진보다 훨씬 빠릅니다.
연소 효율이 높기 때문입니다. Photon도 마찬가지로 같은 하드웨어에서 더 효율적으로 데이터를 처리합니다.
기존 Spark 엔진은 Java로 작성되었지만, Photon은 C++로 작성되어 CPU를 직접 제어합니다. 기존 Spark 엔진의 한계는 무엇일까요?
전통적인 Spark는 JVM 위에서 동작합니다. Java의 장점인 이식성과 안정성을 얻는 대신 성능을 희생합니다.
가비지 컬렉션으로 인한 멈춤 현상이 발생하고, 객체 생성 비용이 높습니다. 또한 CPU의 SIMD 명령어를 활용하지 못합니다.
대용량 데이터 처리에서 이런 오버헤드가 누적되면 성능 저하가 심각해집니다. 바로 이런 문제를 해결하기 위해 Photon 엔진이 개발되었습니다.
Photon을 사용하면 벡터화 실행으로 한 번에 여러 데이터를 처리합니다. CPU의 SIMD 명령어를 활용해서 병렬성을 극대화합니다.
또한 가비지 컬렉션이 없어서 안정적인 성능을 보장합니다. 무엇보다 Delta Lake에 최적화되어 있어서 Parquet 파일 읽기와 압축 해제가 매우 빠릅니다.
Photon의 핵심 장점을 살펴보겠습니다. 첫째, 집계 연산에서 탁월합니다.
SUM, AVG, COUNT 같은 연산이 2-5배 빨라집니다. 벡터화 실행으로 한 번에 수백 개 값을 처리하기 때문입니다.
둘째, 조인 연산도 크게 향상됩니다. 특히 Hash Join에서 메모리를 효율적으로 사용합니다.
셋째, 문자열 처리가 빠릅니다. C++ 수준의 최적화로 파싱과 변환이 신속합니다.
위의 코드를 살펴보겠습니다. 실제로 코드 변경은 필요 없습니다.
Photon은 투명하게 작동합니다. 8번째 줄에서 Delta 테이블을 읽을 때 Photon이 활성화되어 있다면 자동으로 Photon 엔진을 사용합니다.
11-15번째 줄의 집계 연산도 Photon이 처리합니다. 개발자는 아무것도 바꿀 필요가 없습니다.
클러스터 설정만 변경하면 됩니다. 실제 현업에서는 어떤 효과가 있을까요?
한 리테일 회사에서는 일일 판매 리포트를 생성합니다. 1억 건의 트랜잭션을 집계합니다.
기존 Spark로는 45분이 걸렸습니다. Photon을 활성화하자 실행 시간이 15분으로 줄었습니다.
3배의 성능 향상을 얻은 것입니다. 더 놀라운 것은 클러스터 크기를 그대로 유지했다는 점입니다.
하드웨어 투자 없이 성능을 높였습니다. Photon이 특히 빠른 워크로드가 있습니다.
ETL 파이프라인에서 대량의 데이터를 변환하고 집계할 때 효과적입니다. 대시보드 쿼리처럼 빠른 응답이 필요한 경우에도 유용합니다.
복잡한 조인이 많은 쿼리에서도 성능 차이가 큽니다. 반면 단순한 필터링이나 작은 데이터셋에서는 차이가 적을 수 있습니다.
하지만 주의할 점도 있습니다. Photon은 Databricks 전용입니다.
오픈소스 Spark에서는 사용할 수 없습니다. 또한 추가 비용이 발생합니다.
Databricks Unit(DBU) 소비가 약간 증가합니다. 하지만 실행 시간 단축으로 전체 비용은 오히려 감소하는 경우가 많습니다.
예를 들어 실행 시간이 3배 빨라지면 클러스터 사용 시간이 1/3로 줄어듭니다. Photon을 활성화하는 방법은 간단합니다.
Databricks 클러스터를 생성할 때 Photon Acceleration 옵션을 선택하면 됩니다. 또는 기존 클러스터의 설정을 편집해서 활성화할 수 있습니다.
재시작 후 즉시 적용됩니다. Spark UI를 보면 Photon Stage라고 표시되어 Photon이 사용되고 있음을 확인할 수 있습니다.
Photon과 다른 최적화 기법을 함께 사용하세요. **AQE(Adaptive Query Execution)**와 Photon을 함께 사용하면 시너지 효과가 있습니다.
AQE가 실행 계획을 최적화하고 Photon이 빠르게 실행합니다. Delta Lake의 Z-Ordering과 결합하면 데이터 스캔도 줄어듭니다.
여러 최적화 기법을 조합해서 최고의 성능을 얻으세요. 모니터링도 중요합니다.
Spark UI의 SQL 탭에서 Photon이 활성화되었는지 확인하세요. Photon Stage가 표시되면 정상입니다.
또한 실행 시간 지표를 비교해서 성능 향상을 측정하세요. 같은 쿼리를 Photon 활성화 전후로 실행해서 차이를 확인하는 것이 좋습니다.
다시 김데이터 씨의 이야기로 돌아가 봅시다. 박엔지니어 씨가 Photon을 활성화하고 같은 파이프라인을 실행했습니다.
김데이터 씨는 눈을 의심했습니다. "1시간 걸리던 작업이 20분 만에 끝났어요!
코드는 하나도 안 바꿨는데요!" Photon 엔진을 활용하면 코드 변경 없이 즉시 성능을 향상시킬 수 있습니다. 여러분도 Databricks를 사용한다면 Photon을 꼭 활성화해 보세요.
실전 팁
💡 - Databricks 클러스터 생성 시 Photon Acceleration을 활성화하세요
- ETL과 집계 쿼리에서 가장 큰 성능 향상을 기대할 수 있습니다
- Spark UI의 SQL 탭에서 Photon Stage를 확인하세요
6. 대규모 MERGE 최적화
김데이터 씨는 매일 100만 건의 업데이트를 Delta 테이블에 반영해야 합니다. MERGE 문을 실행하면 2시간이 걸립니다.
박엔지니어 씨가 조건을 확인하더니 말합니다. "MERGE에도 최적화 기법이 많아요.
파티션 프루닝과 Low Shuffle Merge를 사용해 보세요."
MERGE 최적화는 Delta Lake의 UPSERT(업데이트 또는 삽입) 작업을 빠르게 처리하는 기법입니다. 마치 도서관에서 책을 정리할 때 전체를 뒤지는 대신 해당 서가만 확인하는 것처럼, 변경이 필요한 부분만 처리하면 시간을 크게 줄일 수 있습니다.
파티션 프루닝, Low Shuffle Merge, Z-Ordering을 활용하는 것이 핵심입니다.
다음 코드를 살펴봅시다.
from delta.tables import DeltaTable
# 대상 Delta 테이블
target_table = DeltaTable.forPath(spark, "/data/users")
# 업데이트할 데이터 - 파티션 컬럼 포함
updates_df = spark.read.format("delta").load("/data/user_updates") \
.repartition("date_partition") # 타겟 테이블의 파티션 키로 재분배
# 최적화된 MERGE - 파티션 프루닝 활용
target_table.alias("target").merge(
updates_df.alias("updates"),
"target.user_id = updates.user_id AND target.date_partition = updates.date_partition"
) \
.whenMatchedUpdate(set={"name": "updates.name", "email": "updates.email"}) \
.whenNotMatchedInsert(values={"user_id": "updates.user_id", "name": "updates.name", "email": "updates.email", "date_partition": "updates.date_partition"}) \
.execute()
김데이터 씨는 매일 밤 사용자 정보를 업데이트하는 배치 작업을 운영합니다. 하루 동안 변경된 데이터를 Delta Lake 테이블에 반영합니다.
업데이트 건수는 100만 건 정도입니다. 그런데 MERGE 작업이 너무 오래 걸립니다.
2시간이 넘게 걸려서 다음 날 아침에도 작업이 끝나지 않을 때가 있습니다. 박엔지니어 씨가 MERGE 쿼리를 살펴봅니다.
"조인 조건에 파티션 컬럼이 없네요. 전체 테이블을 스캔하고 있어요.
이러면 느릴 수밖에 없죠." 그렇다면 MERGE 최적화란 정확히 무엇일까요? 쉽게 비유하자면, MERGE는 마치 백과사전을 업데이트하는 것과 같습니다.
수정된 항목만 찾아서 바꾸면 되는데, 전체 페이지를 처음부터 끝까지 확인한다면 시간이 오래 걸립니다. 대신 색인을 활용해서 해당 페이지만 펼친다면 훨씬 빠릅니다.
Delta Lake의 파티션과 Z-Ordering이 바로 이런 색인 역할을 합니다. 최적화하지 않은 MERGE는 어떤 문제가 있을까요?
기본 MERGE는 전체 테이블을 스캔합니다. 10억 건 중 100만 건만 업데이트하는데도 10억 건을 모두 읽습니다.
또한 큰 셔플이 발생합니다. 타겟 테이블과 업데이트 데이터를 조인하면서 네트워크로 데이터를 주고받습니다.
테이블이 클수록 셔플 비용이 기하급수적으로 증가합니다. 결과적으로 작은 업데이트에도 엄청난 시간이 걸립니다.
바로 이런 비효율을 제거하기 위해 MERGE 최적화가 필요합니다. 적절한 최적화를 통해 파티션 프루닝으로 필요한 파티션만 읽을 수 있습니다.
또한 Low Shuffle Merge로 셔플 비용을 크게 줄일 수 있습니다. 무엇보다 Z-Ordering으로 관련 데이터를 가까이 배치해서 I/O를 최소화합니다.
실제로 최적화 후 10배 이상 빨라지는 경우도 흔합니다. 위의 코드를 한 줄씩 살펴보겠습니다.
4번째 줄에서 Delta 테이블을 로드합니다. DeltaTable API를 사용하면 MERGE 같은 고급 연산이 가능합니다.
7-8번째 줄이 첫 번째 최적화 포인트입니다. 업데이트 데이터를 파티션 키로 재분배합니다.
타겟 테이블과 같은 방식으로 파티셔닝하면 조인 시 셔플이 줄어듭니다. 12번째 줄의 조인 조건이 핵심입니다.
"target.date_partition = updates.date_partition" 조건을 추가했습니다. 이 조건이 있으면 Delta Lake는 해당 파티션만 스캔합니다.
예를 들어 어제 날짜 파티션만 업데이트한다면 나머지 364일치 데이터는 읽지 않습니다. 파티션 프루닝의 핵심입니다.
이것만으로도 스캔 비용이 수백 배 줄어들 수 있습니다. 14-15번째 줄은 실제 업데이트와 삽입 로직입니다.
whenMatchedUpdate는 매칭되는 레코드를 업데이트합니다. whenNotMatchedInsert는 매칭되지 않는 레코드를 삽입합니다.
이 두 가지를 조합하면 UPSERT 동작이 됩니다. 있으면 업데이트하고, 없으면 삽입합니다.
CDC(Change Data Capture) 패턴에서 자주 사용됩니다. 실제 현업에서는 어떻게 활용할까요?
한 전자상거래 회사에서는 주문 상태를 실시간 업데이트합니다. 하루 500만 건의 주문 중 100만 건 정도가 상태 변경됩니다.
처음에는 파티션 조건 없이 MERGE했습니다. 실행 시간이 3시간이었습니다.
날짜 파티션 조건을 추가하자 30분으로 줄었습니다. 추가로 Low Shuffle Merge를 활성화하고 Z-Ordering을 적용하니 10분으로 단축되었습니다.
Low Shuffle Merge는 어떻게 활성화할까요? spark.databricks.delta.merge.enableLowShuffle를 true로 설정하면 됩니다.
이 옵션은 MERGE 시 셔플을 최소화합니다. 특히 업데이트 데이터가 타겟 테이블보다 훨씬 작을 때 효과적입니다.
예를 들어 10억 건 테이블에 100만 건을 MERGE한다면 Low Shuffle Merge가 큰 도움이 됩니다. Z-Ordering도 MERGE 성능에 중요합니다.
Z-Ordering은 관련 데이터를 물리적으로 가까이 배치합니다. MERGE의 조인 키로 Z-Order하면 조인 성능이 크게 향상됩니다.
예를 들어 user_id로 MERGE한다면 user_id로 Z-Order하세요. 같은 user_id를 가진 레코드들이 같은 파일에 모여서 I/O가 줄어듭니다.
MERGE 전 데이터 준비도 중요합니다. 업데이트 데이터에 중복이 있으면 MERGE가 느려집니다.
같은 키가 여러 번 업데이트되면 불필요한 연산이 반복됩니다. MERGE 전에 distinct 또는 dropDuplicates로 중복을 제거하세요.
또한 업데이트 데이터를 미리 필터링해서 실제 변경된 것만 전달하면 효율적입니다. 하지만 주의할 점도 있습니다.
초보 엔지니어들이 흔히 하는 실수 중 하나는 파티션을 너무 작게 나누는 것입니다. 파티션이 수만 개가 되면 메타데이터 관리 비용이 증가합니다.
MERGE 시 모든 파티션 정보를 확인해야 하므로 오히려 느려질 수 있습니다. 적정 크기의 파티션을 유지하세요.
일반적으로 파티션당 1GB 정도가 적절합니다. MERGE 후 최적화 작업도 필요합니다.
MERGE는 작은 파일을 많이 생성할 수 있습니다. 정기적으로 OPTIMIZE 명령을 실행해서 파일을 병합하세요.
또한 VACUUM으로 오래된 파일을 정리해서 스토리지 비용을 절감하세요. 하지만 VACUUM은 타임 트래블을 제한하므로 보존 기간을 신중히 설정하세요.
Delta Lake의 Change Data Feed를 활용하는 방법도 있습니다. CDC를 활성화하면 변경 내역만 추출할 수 있습니다.
전체 MERGE 대신 변경분만 처리하면 훨씬 빠릅니다. 특히 증분 업데이트 시나리오에서 유용합니다.
Databricks의 Delta Live Tables와 결합하면 실시간 CDC 파이프라인을 쉽게 구축할 수 있습니다. 다시 김데이터 씨의 이야기로 돌아가 봅시다.
박엔지니어 씨의 조언대로 파티션 조건을 추가하고 Low Shuffle Merge를 활성화한 김데이터 씨는 놀라움을 금치 못했습니다. "2시간 걸리던 MERGE가 15분 만에 끝났어요!
이제 여유롭게 아침을 맞을 수 있겠네요." 대규모 MERGE 작업을 최적화하면 배치 윈도우를 크게 단축할 수 있습니다. 여러분도 MERGE가 느리다면 파티션 프루닝과 Low Shuffle Merge를 적용해 보세요.
실전 팁
💡 - MERGE 조건에 파티션 컬럼을 반드시 포함하세요
- Low Shuffle Merge를 활성화해서 셔플 비용을 줄이세요
- Z-Ordering을 조인 키로 적용하면 I/O가 크게 감소합니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.