<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>mildsalmon (김학진)</title><link>http://blex.me/@mildsalmon</link><description>흔치않고, 진귀하다.</description><atom:link href="http://blex.me/rss/@mildsalmon" rel="self"/><language>ko</language><lastBuildDate>Mon, 16 Feb 2026 14:13:23 +0900</lastBuildDate><image><url>/resources/media/images/avatar/c1/mildsalmon/aspHJ.jpg</url><title>mildsalmon (김학진)</title><link>http://blex.me/@mildsalmon</link></image><item><title>[Apache Iceberg - The Definitive Guide] Practical Hands-On</title><link>http://blex.me/@mildsalmon/apache-iceberg-the-definitive-guide-%EC%8B%A4%EC%8A%B5-Y9qzmLSD</link><description>&lt;p&gt;Iceberg를 공부하다보니 내부 구조가 궁금해졌다.&lt;/p&gt;&lt;p&gt;그래서 책을 정리한 내용을 바탕으로 AI 도움을 받아 Iceberg를 분해해서 가이드하는 실습 파일을 만들었다.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://github.com/mildsalmon/iceberg-learning"&gt;https://github.com/mildsalmon/iceberg-learning&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;</description><pubDate>Mon, 16 Feb 2026 14:13:23 +0900</pubDate><guid>http://blex.me/@mildsalmon/apache-iceberg-the-definitive-guide-%EC%8B%A4%EC%8A%B5-Y9qzmLSD</guid></item><item><title>[Apache Iceberg - The Definitive Guide] Optimizing the Performance of Iceberg Tables</title><link>http://blex.me/@mildsalmon/apache-iceberg-the-definitive-guide-optimizing-t</link><description>&lt;h2&gt;Chapter 4: Iceberg 테이블 성능 최적화&lt;/h2&gt;&lt;hr&gt;&lt;h2&gt;개요&lt;/h2&gt;&lt;p&gt;3장에서 살펴본 것처럼, Apache Iceberg 테이블은 쿼리 엔진이 더 나은 성능을 위해 더 스마트한 쿼리 플랜을 생성할 수 있도록 하는 메타데이터 레이어를 제공합니다. 그러나 이 메타데이터는 데이터 성능을 최적화할 수 있는 방법의 시작에 불과합니다.&lt;/p&gt;&lt;p&gt;사용할 수 있는 다양한 최적화 레버에는 데이터파일 수 줄이기, 데이터 정렬, 테이블 파티셔닝, 행 수준 업데이트 처리, 메트릭 수집, 외부 요인 등이 있습니다. 이러한 레버들은 데이터 성능 향상에 중요한 역할을 하며, 이 장에서는 각각을 탐구하고 잠재적인 성능 저하를 해결하며 가속화 인사이트를 제공합니다.&lt;/p&gt;&lt;hr&gt;&lt;h2&gt;1. 컴팩션 (Compaction)&lt;/h2&gt;&lt;h4&gt;1.1 컴팩션의 필요성&lt;/h4&gt;&lt;p&gt;모든 절차나 프로세스는 시간 비용이 듭니다. 즉, 더 긴 쿼리와 더 높은 컴퓨팅 비용을 의미합니다. 다시 말해, 무언가를 하기 위해 더 많은 단계를 거쳐야 할수록 그것을 하는 데 더 오래 걸립니다.&lt;/p&gt;&lt;p&gt;Apache Iceberg 테이블을 쿼리할 때, 각 파일을 열고 스캔한 다음 작업이 완료되면 파일을 닫아야 합니다. 쿼리에서 스캔해야 할 파일이 많을수록 이러한 파일 작업이 쿼리에 미치는 비용이 커집니다.&lt;/p&gt;&lt;p&gt;이 문제는 스트리밍 또는 "실시간" 데이터 환경에서 더욱 악화됩니다. 데이터가 생성되는 대로 수집되어 각각 몇 개의 레코드만 있는 많은 파일이 생성되기 때문입니다.&lt;/p&gt;&lt;p&gt;반면, 배치 수집은 하루치 또는 일주일치 레코드를 하나의 작업으로 수집할 수 있어, 더 잘 정리된 파일로 데이터를 쓰는 방법을 보다 효율적으로 계획할 수 있습니다.&lt;/p&gt;&lt;p&gt;배치 수집을 사용하더라도 "작은 파일 문제(small files problem)"가 발생할 수 있습니다. 너무 많은 작은 파일은 다음과 같은 이유로 스캔 속도와 성능에 영향을 미칩니다:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;더 많은 파일 작업 수행&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;읽어야 할 메타데이터 증가 (각 파일에 메타데이터가 있음)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;정리 및 유지보수 작업 시 삭제해야 할 파일 증가&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262612_3CCGusvJ6TYDqJFdcmu4.png" alt="Pasted image 20260201044933.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;figure style="text-align: left; display: flex; justify-content: flex-start; flex-direction: column; align-items: flex-start;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262612_jee9hEUgdoh2HtDi782e.png" alt="Pasted image 20260202063752.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;핵심 개념:&lt;/strong&gt; 데이터를 읽을 때 피할 수 없는 고정 비용(쿼리와 관련된 특정 데이터 읽기)과 피할 수 있는 가변 비용(파일 접근을 위한 파일 작업)이 있습니다. 이 장에서 논의할 다양한 전략을 사용하면 가변 비용을 최대한 줄일 수 있습니다.&lt;/p&gt;&lt;h4&gt;1.2 컴팩션의 개념&lt;/h4&gt;&lt;p&gt;이 문제의 해결책은 모든 작은 파일들의 데이터를 주기적으로 가져와 더 적은 수의 큰 파일로 다시 쓰는 것입니다 (데이터파일 수에 비해 매니페스트가 너무 많은 경우 매니페스트도 다시 쓸 수 있습니다). 이 프로세스를 &lt;strong&gt;컴팩션(compaction)&lt;/strong&gt;이라고 합니다. 많은 파일을 적은 수로 압축하는 것입니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262612_FukRJ4XPCsAbRLIoFJpX.png" alt="Pasted image 20260201044941.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;1.3 Hands-on with Compaction&lt;/h4&gt;&lt;p&gt;해결책이 간단해 보이지만 Java나 Python으로 광범위한 코드를 작성해야 할 것이라고 생각할 수 있습니다. 다행히 Apache Iceberg의 Actions 패키지에는 여러 유지보수 프로시저가 포함되어 있습니다 (Actions 패키지는 특히 Apache Spark용이지만, 다른 엔진도 자체 유지보수 작업 구현을 만들 수 있습니다).&lt;/p&gt;&lt;h4&gt;SparkActions의 주요 옵션들&lt;/h4&gt;&lt;table style="min-width: 50px;"&gt;&lt;colgroup&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;옵션&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;설명&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;filter&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;특정 파일만 대상으로 하는 표현식&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;sort&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;정렬된 순서로 데이터를 재작성&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;option&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;단일 설정 지정&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;options&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;맵으로 여러 설정 지정&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;target-file-size-bytes&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;목표 파일 크기 (바이트)&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;max-file-group-size-bytes&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;최대 파일 그룹 크기&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;max-concurrent-file-group-rewrites&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;동시 파일 그룹 재작성 최대 수&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;partial-progress-enabled&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;부분 진행 활성화 여부&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;partial-progress-max-commits&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;부분 진행 시 최대 커밋 수&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;h4&gt;Spark SQL 확장을 사용한 컴팩션 예제&lt;/h4&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;-- rewrite_data_files 프로시저를 사용한 컴팩션 작업
CALL catalog.system.rewrite_data_files(
  table =&amp;gt; 'musicians',
  strategy =&amp;gt; 'binpack',
  where =&amp;gt; 'genre = "rock"',
  options =&amp;gt; map(
    'rewrite-job-order','bytes-asc',
    'target-file-size-bytes','1073741824', -- 1GB
    'max-file-group-size-bytes','10737418240' -- 10GB
  )
)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 시나리오에서는 musicians 테이블에 일부 데이터를 스트리밍했고 rock 밴드에 대해 많은 작은 파일이 생성된 것을 발견했습니다. 전체 테이블에 컴팩션을 실행하는 대신(시간이 많이 소요됨), 문제가 있는 데이터만 대상으로 했습니다.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;참고:&lt;/strong&gt; &lt;code&gt;where&lt;/code&gt; 필터에서 큰따옴표 사용에 주의하세요. 필터 주변에 작은따옴표를 사용했으므로, SQL이 일반적으로 사용하는 작은따옴표 대신 문자열 내에서 큰따옴표를 사용합니다.&lt;/p&gt;&lt;/blockquote&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262612_ixwWkaZC5wgTxjoIZoOx.png" alt="Pasted image 20260201050320.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;파일 그룹 (File Groups)&lt;/h4&gt;&lt;p&gt;엔진이 컴팩션 작업에서 작성할 새 파일을 계획할 때, 이러한 파일들을 병렬로 작성될 파일 그룹으로 그룹화하기 시작합니다 (각 그룹에서 하나의 파일이 동시에 작성될 수 있음). 컴팩션 작업에서 이러한 파일 그룹의 크기와 동시에 작성할 수를 구성하여 메모리 문제를 방지할 수 있습니다.&lt;/p&gt;&lt;h4&gt;부분 진행 (Partial Progress)&lt;/h4&gt;&lt;p&gt;부분 진행을 사용하면 파일 그룹이 완료될 때마다 새 스냅샷이 생성됩니다. 이를 통해 쿼리가 다른 파일이 완료되는 동안 이미 컴팩션된 파일의 이점을 얻을 수 있습니다. 또한 진행 상황이 작업 완료 시 저장되고 메모리에 보관해야 하는 데이터가 줄어들어 대규모 컴팩션 작업의 OOM(Out-of-Memory) 상황을 방지하는 데 도움이 됩니다.&lt;/p&gt;&lt;p&gt;더 많은 스냅샷은 테이블 위치에서 저장 공간을 차지하는 더 많은 메타데이터 파일을 의미한다는 점을 명심하세요. 하지만 읽기 작업이 컴팩션 작업의 이점을 더 빨리 얻기를 원한다면 이것은 유용한 기능이 될 수 있습니다.&lt;/p&gt;&lt;h4&gt;1.4 컴팩션 전략 (Compaction Strategies)&lt;/h4&gt;&lt;p&gt;rewriteDataFiles 프로시저를 사용할 때 선택할 수 있는 여러 컴팩션 전략이 있습니다. 아래 표는 각 전략의 장단점을 요약합니다.&lt;/p&gt;&lt;table style="min-width: 100px;"&gt;&lt;colgroup&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;전략&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;기능&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;장점&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;단점&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;strong&gt;Binpack&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;파일만 결합; 전역 정렬 없음 (태스크 내 로컬 정렬은 수행)&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;가장 빠른 컴팩션 작업&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;데이터가 클러스터링되지 않음&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;strong&gt;Sort&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;태스크 할당 전에 하나 이상의 필드로 순차적 정렬 (예: 필드 a로 정렬, 그 안에서 필드 b로 정렬)&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;자주 쿼리되는 필드로 클러스터링된 데이터는 훨씬 빠른 읽기 시간 제공&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;binpack에 비해 더 긴 컴팩션 작업 시간&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;strong&gt;Z-order&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;태스크 할당 전에 동등한 가중치를 가진 여러 필드로 정렬 (이 범위의 X와 Y 값은 한 그룹에, 다른 범위는 다른 그룹에)&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;쿼리가 여러 필드의 필터에 자주 의존하는 경우 읽기 시간을 더욱 개선&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;binpack에 비해 더 긴 컴팩션 작업 시간&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;참고:&lt;/strong&gt; Apache Iceberg 테이블에 정렬 순서가 설정되어 있으면, binpack을 사용하더라도 이 정렬 순서가 단일 태스크 내에서 데이터를 정렬하는 데 사용됩니다 (로컬 정렬). sort와 z-order 전략을 사용하면 쿼리 엔진이 레코드를 다른 태스크에 할당하기 전에 데이터를 정렬하여 태스크 간 데이터 클러스터링을 최적화합니다.&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;Binpack 전략&lt;/h4&gt;&lt;p&gt;기본적으로 rewriteDataFiles 프로시저는 &lt;strong&gt;binpack&lt;/strong&gt; 전략을 사용합니다. 이 전략은 데이터파일을 병합하는 데 초점을 맞추며, 데이터의 순서를 변경하지 않습니다. 이 방법은 단순히 여러 파일을 더 적은 수의 파일로 압축할 때 필요한 최소한의 작업입니다.&lt;/p&gt;&lt;p&gt;binpack 전략은 본질적으로 파일 크기 외에 데이터가 어떻게 구성되는지에 대한 다른 고려 없이 순수한 컴팩션입니다. 세 가지 전략 중 binpack이 가장 빠릅니다. 작은 파일의 내용을 목표 크기의 큰 파일에 그냥 쓸 수 있기 때문입니다. 반면 sort와 z-order는 파일 그룹을 쓰기 위해 할당하기 전에 데이터를 정렬해야 합니다. 이는 스트리밍 데이터가 있고 SLA(서비스 수준 계약)를 충족하는 속도로 컴팩션을 실행해야 할 때 특히 유용합니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262612_Sx85Wdsy0h4GlKy28etb.png" alt="Pasted image 20260202063842.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;1.5 컴팩션 자동화 (Automating Compaction)&lt;/h4&gt;&lt;p&gt;컴팩션 작업을 수동으로 실행하면 모든 SLA를 충족하기 어려울 수 있으므로, 이러한 프로세스를 자동화하는 방법을 찾는 것이 실질적인 이점이 될 수 있습니다:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;오케스트레이션 도구 사용&lt;/strong&gt;: Airflow, Dagster, Prefect, Argo, Luigi 등을 사용하여 수집 작업 완료 후 또는 특정 시간이나 주기적 간격으로 Spark나 Dremio와 같은 엔진에 적절한 SQL을 전송&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;서버리스 함수&lt;/strong&gt;: 클라우드 오브젝트 스토리지에 데이터가 도착한 후 작업을 트리거&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;크론 작업&lt;/strong&gt;: 특정 시간에 적절한 작업을 실행하도록 설정&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;또한 Dremio Arctic과 Tabular과 같이 컴팩션을 포함한 자동화된 테이블 유지보수 기능을 갖춘 관리형 Apache Iceberg 카탈로그 서비스도 있습니다.&lt;/p&gt;&lt;hr&gt;&lt;h2&gt;2. 정렬 (Sorting)&lt;/h2&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262612_bGjqdBuHi6xZepVGE0fv.png" alt="Pasted image 20260202063903.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;2.1 정렬의 이점&lt;/h4&gt;&lt;p&gt;정렬 또는 "클러스터링"은 쿼리에 있어 매우 특별한 이점이 있습니다: 쿼리에 필요한 데이터를 얻기 위해 스캔해야 하는 파일 수를 제한하는 데 도움이 됩니다. 데이터를 정렬하면 유사한 값을 가진 데이터가 더 적은 파일에 집중되어 보다 효율적인 쿼리 계획이 가능해집니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262612_2neQ1uCsvdsAUppPOaAa.png" alt="Pasted image 20260201053649.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;예시:&lt;/strong&gt; NFL 팀의 모든 선수를 나타내는 데이터셋이 특별한 순서 없이 100개의 Parquet 파일에 걸쳐 있다고 가정합니다. Detroit Lions 선수만 쿼리하면, 100개의 레코드 중 Detroit Lions 선수가 단 하나뿐인 파일이 있더라도 해당 파일은 쿼리 플랜에 추가되어 스캔되어야 합니다. 이는 최대 53개의 파일(NFL 팀의 최대 선수 수)을 스캔해야 할 수 있음을 의미합니다.&lt;/p&gt;&lt;p&gt;팀 이름을 알파벳순으로 정렬하면 모든 Detroit Lions 선수는 약 4개의 파일에 있어야 합니다 (100개 파일 ÷ 32개 NFL 팀 = 3.125). 따라서 데이터를 정렬함으로써 스캔해야 할 파일 수를 53개에서 4개로 줄일 수 있습니다.&lt;/p&gt;&lt;h4&gt;2.2 테이블 생성 시 정렬 설정&lt;/h4&gt;&lt;p&gt;테이블을 생성하는 두 가지 주요 방법:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;방법 1: 표준 CREATE TABLE 문&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;-- Spark SQL
CREATE TABLE catalog.nfl_players (
  id bigint,
  player_name varchar,
  team varchar,
  num_of_touchdowns int,
  num_of_yards int,
  player_position varchar,
  player_number int
)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;방법 2: CREATE TABLE…AS SELECT (CTAS) 문&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;-- Spark SQL
CREATE TABLE catalog.nfl_players
AS (SELECT * FROM non_iceberg_teams_table);&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;2.3 정렬 순서 설정&lt;/h4&gt;&lt;p&gt;테이블 생성 후 정렬 순서를 설정합니다. 이 속성을 지원하는 모든 엔진은 쓰기 전에 데이터를 정렬하는 데 사용하며, sort 컴팩션 전략 사용 시 기본 정렬 필드로도 사용됩니다:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;ALTER TABLE catalog.nfl_teams WRITE ORDERED BY team;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;CTAS를 수행하는 경우 AS 쿼리에서 데이터를 정렬합니다:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;CREATE TABLE catalog.nfl_teams
AS (SELECT * FROM non_iceberg_teams_table ORDER BY team);

ALTER TABLE catalog.nfl_teams WRITE ORDERED BY team;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;INSERT INTO에서도 지정할 수 있습니다:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;INSERT INTO catalog.nfl_teams
SELECT *
FROM staging_table
ORDER BY team&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;2.4 Sort 컴팩션 전략&lt;/h4&gt;&lt;p&gt;NFL 데이터셋이 매년 팀 로스터 변경을 위해 업데이트된다면, 여러 쓰기 작업에서 Lions와 Packers 선수를 분할하는 많은 파일이 생길 수 있습니다. 이는 현재 연도의 새 Lions 선수를 포함하는 새 파일을 작성해야 하기 때문입니다. 이때 sort 컴팩션 전략이 유용합니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262612_tdY04U8qgtHAydUGNLVN.png" alt="Pasted image 20260201054500.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;p&gt;sort 컴팩션 전략은 작업 대상 모든 파일에 걸쳐 데이터를 정렬합니다:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;CALL catalog.system.rewrite_data_files(
  table =&amp;gt; 'nfl_teams',
  strategy =&amp;gt; 'sort',
  sort_order =&amp;gt; 'team ASC NULLS LAST'
)&lt;/code&gt;&lt;/pre&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262612_XdNVeMaIfllMAGIEEO93.png" alt="Pasted image 20260201054517.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;핵심 포인트:&lt;/strong&gt; 정렬의 최대 이점을 얻으려면 최종 사용자가 묻는 질문 유형을 이해하여 그들의 질문에 효과적으로 대응할 수 있도록 데이터를 정렬해야 합니다.&lt;/p&gt;&lt;hr&gt;&lt;h2&gt;3. Z-order&lt;/h2&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262612_HPpEoDHiHnsuQKVTyLCx.png" alt="Pasted image 20260202071426.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;3.1 Z-order의 개념&lt;/h4&gt;&lt;p&gt;테이블을 쿼리할 때 여러 필드가 우선순위인 경우가 있으며, 이때 z-order 정렬이 매우 유용할 수 있습니다. z-order 정렬은 여러 데이터 포인트로 데이터를 정렬하여 엔진이 최종 쿼리 플랜에서 스캔할 파일을 줄일 수 있는 더 큰 능력을 제공합니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262612_9QRIkgykfGAFDBjwSAG4.png" alt="Pasted image 20260201054742.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;3.2 Z-order의 작동 방식&lt;/h4&gt;&lt;p&gt;4×4 그리드에서 항목 Z를 찾으려 한다고 상상해 보세요. 값(z)이 3.5와 같다고 할 때:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;X와 Y 값의 범위에 따라 필드를 4개의 사분면으로 나눕니다&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;검색하려는 데이터가 z-order로 정렬된 필드에 기반한다면, 데이터의 큰 부분 검색을 피할 수 있습니다&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;해당 사분면을 더 세분화하고 사분면 내 데이터에 또 다른 z-order 정렬을 적용할 수 있습니다&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;검색이 여러 요소(X와 Y)에 기반하므로, 이 접근 방식을 취함으로써 검색 가능한 영역의 75%를 제거할 수 있습니다&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h4&gt;3.3 Z-order 실제 예시&lt;/h4&gt;&lt;p&gt;의료 코호트 연구에 참여한 모든 사람의 데이터셋이 있고, 나이와 키로 코호트의 결과를 정리하려 한다면 z-order가 매우 유용할 수 있습니다.&lt;/p&gt;&lt;p&gt;특정 사분면에 속하는 데이터는 동일한 데이터파일에 있게 되어, 다른 연령/키 그룹에 대한 분석을 실행할 때 스캔할 파일을 정말로 줄일 수 있습니다. 키가 6피트이고 나이가 60세인 사람을 검색하면, 다른 세 사분면에 속하는 데이터를 가진 데이터파일을 즉시 제거할 수 있습니다.&lt;/p&gt;&lt;p&gt;데이터파일은 네 가지 범주로 분류됩니다:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;A&lt;/strong&gt;: Age 1–50, Height 1–5 레코드가 있는 파일&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;B&lt;/strong&gt;: Age 51–100, Height 1–5 레코드가 있는 파일&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;C&lt;/strong&gt;: Age 1–50, Height 5–10 레코드가 있는 파일&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;D&lt;/strong&gt;: Age 51–100, Height 5–10 레코드가 있는 파일&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;60세이고 6피트인 사람을 검색하면, A, B, C 범주의 모든 데이터파일이 제거되어 스캔되지 않습니다. 나이만으로 검색하더라도 최소 네 사분면 중 두 개를 제거할 수 있어 클러스터링의 이점을 볼 수 있습니다.&lt;/p&gt;&lt;h4&gt;3.4 Z-order 컴팩션 작업 실행&lt;/h4&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;CALL catalog.system.rewrite_data_files(
  table =&amp;gt; 'people',
  strategy =&amp;gt; 'sort',
  sort_order =&amp;gt; 'zorder(age,height)'
)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;sort 및 z-order 컴팩션 전략을 사용하면 데이터가 존재하는 파일 수를 줄일 수 있을 뿐만 아니라, 해당 파일의 데이터 순서가 더욱 효율적인 쿼리 계획을 가능하게 합니다.&lt;/p&gt;&lt;h4&gt;3.5 정렬의 한계&lt;/h4&gt;&lt;p&gt;정렬은 효과적이지만 몇 가지 과제가 있습니다:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;새 데이터 수집 시&lt;/strong&gt;: 새 데이터가 수집되면 정렬되지 않은 상태가 되며, 다음 컴팩션 작업까지 데이터는 여러 파일에 걸쳐 다소 흩어진 상태로 남습니다&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;파일 내 다중 값&lt;/strong&gt;: 파일에 정렬된 필드의 여러 값에 대한 데이터가 여전히 포함될 수 있어, 특정 값의 데이터만 필요한 쿼리에 비효율적일 수 있습니다&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;이러한 문제를 해결하기 위해 &lt;strong&gt;파티셔닝&lt;/strong&gt;을 사용합니다.&lt;/p&gt;&lt;hr&gt;&lt;h2&gt;4. 파티셔닝 (Partitioning)&lt;/h2&gt;&lt;h4&gt;4.1 파티셔닝의 개념&lt;/h4&gt;&lt;p&gt;데이터 액세스 방식에 특정 필드가 핵심적이라면, 정렬을 넘어 파티셔닝을 고려할 수 있습니다. 테이블이 파티션되면, 필드에 기반하여 순서만 정렬하는 대신, 대상 필드의 고유 값을 가진 레코드를 자체 데이터파일에 씁니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;예시:&lt;/strong&gt; 정치에서 유권자 데이터를 유권자의 정당 소속에 따라 자주 쿼리할 가능성이 높아, 이것이 좋은 파티션 필드가 됩니다. 이는 "Blue" 정당의 모든 유권자가 "Red", "Yellow", "Green" 정당의 유권자와 별도의 파일에 나열됨을 의미합니다. "Yellow" 정당의 유권자를 쿼리하면, 스캔하는 데이터파일에는 다른 정당의 사람이 포함되지 않습니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262612_EKZPcpAnUiR7ds061MKo.png" alt="Pasted image 20260201071348.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;4.2 전통적인 파티셔닝의 문제점&lt;/h4&gt;&lt;p&gt;전통적으로 특정 필드의 파생 값을 기반으로 테이블을 파티셔닝하려면 별도로 관리해야 하는 추가 필드를 생성해야 했고, 사용자가 쿼리할 때 해당 별도 필드에 대한 지식이 필요했습니다:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;날짜 기반 파티셔닝&lt;/strong&gt;: 타임스탬프 열의 일, 월, 또는 연도로 파티셔닝하려면 년, 월, 또는 일을 분리하여 표현하는 타임스탬프 기반의 추가 열을 생성해야 했습니다&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;텍스트 기반 파티셔닝&lt;/strong&gt;: 텍스트 값의 첫 글자로 파티셔닝하려면 해당 글자만 있는 추가 열을 생성해야 했습니다&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;버킷 파티셔닝&lt;/strong&gt;: 해시 함수에 기반하여 레코드를 균등하게 분배하는 일정 수의 분할(버킷)로 파티셔닝하려면 레코드가 속한 버킷을 명시하는 추가 열을 생성해야 했습니다&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;문제점:&lt;/strong&gt; 엔진은 원래 필드와 파생 필드 간의 관계를 인식하지 못합니다. 이는 다음 쿼리가 파티셔닝의 이점을 받지만:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;SELECT * FROM MYTABLE WHERE time BETWEEN '2022-07-01 00:00:00' AND '2022-07-31 00:00:00' 
AND month = 7;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;사용자는 종종 이 우회 열을 인식하지 못합니다(인식해야 할 필요도 없습니다). 이는 대부분의 경우 사용자가 다음과 유사한 쿼리를 발행하여 전체 테이블 스캔이 발생하고, 쿼리 시간이 훨씬 오래 걸리고 훨씬 더 많은 리소스를 소비함을 의미합니다:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;SELECT * FROM MYTABLE WHERE time BETWEEN '2022-07-01 00:00:00' AND '2022-07-31 00:00:00';&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;4.3 히든 파티셔닝 (Hidden Partitioning)&lt;/h4&gt;&lt;p&gt;Apache Iceberg는 파티셔닝을 상당히 다르게 처리하여 테이블을 파티셔닝으로 최적화할 때 이러한 많은 문제점을 해결합니다. 이 접근 방식의 결과적인 기능을 &lt;strong&gt;히든 파티셔닝&lt;/strong&gt;이라고 합니다.&lt;/p&gt;&lt;p&gt;Iceberg가 파티셔닝을 추적하는 방식에서 시작됩니다. 파일이 물리적으로 배치되는 방식에 의존하는 대신, Iceberg는 스냅샷과 매니페스트 수준에서 파티션 값의 범위를 추적하여 많은 수준의 새로운 유연성을 허용합니다:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;변환 값을 기반으로 파티셔닝하기 위해 추가 열을 생성할 필요 없이, 쿼리 계획 시 엔진과 도구가 메타데이터에서 적용할 수 있는 내장 변환을 사용할 수 있습니다&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;이러한 변환을 사용할 때 추가 열이 필요하지 않으므로 데이터파일에 저장하는 양이 줄어듭니다&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;메타데이터가 원래 열에 대한 변환을 엔진이 인식할 수 있게 하므로, 원래 열만으로 필터링해도 파티셔닝의 이점을 얻을 수 있습니다&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;예시:&lt;/strong&gt; 월별로 파티션된 테이블을 생성하면:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;CREATE TABLE catalog.MyTable (...) PARTITIONED BY months(time) USING iceberg;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;다음 쿼리가 파티셔닝의 이점을 받습니다:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;SELECT * FROM MYTABLE WHERE time BETWEEN '2022-07-01 00:00:00' AND '2022-07-31 00:00:00';&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;4.4 파티션 변환 함수&lt;/h4&gt;&lt;p&gt;파티셔닝 계획 시 사용 가능한 여러 변환:&lt;/p&gt;&lt;table style="min-width: 50px;"&gt;&lt;colgroup&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;변환&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;설명&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;year&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;연도만&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;month&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;월과 연도&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;day&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;일, 월, 연도&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;hour&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;시간, 일, 월, 연도&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;truncate&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;값을 잘라서 파티셔닝&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;bucket&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;해시 함수를 사용하여 지정된 버킷 수로 분배&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;code&gt;year&lt;/code&gt;, &lt;code&gt;month&lt;/code&gt;, &lt;code&gt;day&lt;/code&gt;, &lt;code&gt;hour&lt;/code&gt; 변환은 타임스탬프 열에서 작동합니다. &lt;code&gt;month&lt;/code&gt;를 지정하면 메타데이터에서 추적되는 파티션 값은 타임스탬프의 월과 연도를 반영하고, &lt;code&gt;day&lt;/code&gt;를 사용하면 연도, 월, 일을 반영하므로 더 세분화된 파티셔닝을 위해 여러 변환을 사용할 필요가 없습니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;truncate 변환 예시:&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;-- 사람 이름의 첫 글자를 기반으로 파티셔닝
CREATE TABLE catalog.MyTable (...) PARTITIONED BY truncate(name, 1) USING iceberg;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;bucket 변환 예시:&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;-- 우편번호 기반 유권자 데이터 파티셔닝 (너무 많은 고유 값이 있는 경우)
CREATE TABLE catalog.voters (...) PARTITIONED BY bucket(24, zip) USING iceberg;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;모든 버킷에 여러 우편번호가 포함되지만, 적어도 특정 우편번호를 찾으면 전체 테이블 스캔이 아닌 검색하는 우편번호가 포함된 버킷만 스캔하면 됩니다.&lt;/p&gt;&lt;h4&gt;4.5 파티션 진화 (Partition Evolution)&lt;/h4&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262613_52QfmF7GMvYfnYyEKAUN.png" alt="Pasted image 20260202070443.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;p&gt;전통적인 파티셔닝의 또 다른 과제는 파일이 하위 디렉토리로 배치되는 물리적 구조에 의존했기 때문에, 테이블이 파티션되는 방식을 변경하려면 전체 테이블을 다시 작성해야 했다는 것입니다. 이는 데이터와 쿼리 패턴이 진화하면서 피할 수 없는 문제가 되어, 데이터를 파티션하고 정렬하는 방법을 재고해야 합니다.&lt;/p&gt;&lt;p&gt;Apache Iceberg는 메타데이터 추적 파티셔닝으로 이 문제를 해결합니다. 메타데이터가 파티션 값뿐만 아니라 과거 파티션 체계도 추적하기 때문에 파티션 체계가 진화할 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;예시:&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;-- 회원 등록 연도별로 파티션된 테이블
CREATE TABLE catalog.members (...) PARTITIONED BY years(registration_ts) USING iceberg;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;수년 후, 회원 성장 속도가 월별로 레코드를 세분화할 가치가 있게 되면:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;ALTER TABLE catalog.members ADD PARTITION FIELD months(registration_ts)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;더 이상 특정 필드로 파티셔닝하지 않으려면:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;ALTER TABLE catalog.members DROP PARTITION FIELD bucket(24, id);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;핵심 포인트:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;파티셔닝 체계가 업데이트되면 앞으로 테이블에 쓰이는 새 데이터에만 적용되므로 기존 데이터를 다시 쓸 필요가 없습니다&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;rewriteDataFiles&lt;/code&gt; 프로시저로 다시 쓴 모든 데이터는 새 파티셔닝 체계를 사용하여 다시 쓰입니다&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;이전 데이터를 이전 체계로 유지하려면 컴팩션 작업에서 적절한 필터를 사용하여 다시 쓰지 않도록 해야 합니다&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;4.6 Hive vs Iceberg 파티션 조회 방식 비교&lt;/h4&gt;&lt;table style="min-width: 75px;"&gt;&lt;colgroup&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;구분&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;Hive Table Format&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;Iceberg&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;strong&gt;파티션 컬럼&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;별도 컬럼 존재 (year, month, day 등)&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;별도 컬럼 없음&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;strong&gt;쿼리 시&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;파티션 컬럼 직접 명시 필요&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;원본 컬럼으로 필터링 (자동 프루닝)&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;strong&gt;파티션 정보 조회&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;SHOW PARTITIONS table&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;메타데이터 테이블 (&lt;code&gt;table.partitions&lt;/code&gt;)&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;strong&gt;파티션 변경&lt;/strong&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;테이블 재작성 필요&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;메타데이터만 변경 (Partition Evolution)&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Iceberg의 히든 파티셔닝은 사용자에게 파티션 구조를 숨기면서도, 메타데이터 테이블을 통해 관리자/엔지니어가 파티션 상태를 모니터링하고 최적화할 수 있게 해줍니다.&lt;/p&gt;&lt;h4&gt;쿼리 예시&lt;/h4&gt;&lt;h6&gt;Hive Table Format&lt;/h6&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;-- 별도 파티션 컬럼이 실제로 존재
SELECT * FROM orders WHERE year = 2024 AND month = 1 AND day = 15;

-- 파티션 컬럼을 직접 명시해야 파티션 프루닝 적용&lt;/code&gt;&lt;/pre&gt;&lt;h6&gt;Iceberg (Hidden Partitioning)&lt;/h6&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;-- 원본 컬럼만으로 쿼리 (파티션 프루닝 자동 적용)
SELECT * FROM orders WHERE order_ts BETWEEN '2024-01-15 00:00:00' AND '2024-01-15 23:59:59';&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Iceberg는 메타데이터가 &lt;code&gt;order_ts&lt;/code&gt;와 &lt;code&gt;day(order_ts)&lt;/code&gt; 변환 간의 관계를 알고 있어서, 원본 타임스탬프 컬럼으로 필터링해도 자동으로 파티션 프루닝이 적용됩니다.&lt;/p&gt;&lt;h4&gt;Iceberg 파티션 메타데이터 조회 방법&lt;/h4&gt;&lt;h6&gt;1. partitions 메타데이터 테이블&lt;/h6&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;-- Spark SQL
SELECT * FROM my_catalog.table.partitions;

-- Trino
SELECT * FROM "test_table$partitions";&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;partitions 테이블 스키마:&lt;/strong&gt;&lt;/p&gt;&lt;table style="min-width: 100px;"&gt;&lt;colgroup&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;필드명&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;데이터 타입&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;예시 값&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;설명&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;partition&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;List&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;{20211001, 11}&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;실제 파티션 값&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;spec_id&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;Int&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;0&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;파티션 스펙 ID&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;record_count&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;Int&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;1&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;파티션 내 레코드 수&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;file_count&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;Int&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;1&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;파티션 내 파일 수&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;position_delete_record_count&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;Int&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;0&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;위치 삭제 레코드 수&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;position_delete_file_count&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;Int&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;0&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;위치 삭제 파일 수&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;equality_delete_record_count&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;Int&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;0&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;동등 삭제 레코드 수&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;equality_delete_file_count&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;Int&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;0&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;동등 삭제 파일 수&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;h6&gt;2. files 메타데이터 테이블 (파일별 파티션 정보)&lt;/h6&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;-- Spark SQL
SELECT partition, file_path, record_count, file_size_in_bytes 
FROM my_catalog.table.files;

-- Dremio
SELECT * FROM TABLE(table_files('catalog.table'));&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;실용적인 파티션 조회 쿼리 예시&lt;/h4&gt;&lt;h6&gt;파티션별 파일 수 확인 (컴팩션 대상 식별)&lt;/h6&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;SELECT partition, file_count 
FROM catalog.table.partitions;&lt;/code&gt;&lt;/pre&gt;&lt;h6&gt;파티션별 총 크기 확인&lt;/h6&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;SELECT partition, SUM(file_size_in_bytes) AS partition_size 
FROM catalog.table.files 
GROUP BY partition;&lt;/code&gt;&lt;/pre&gt;&lt;h6&gt;파티션 스펙별 파티션 수 확인 (파티션 진화 후)&lt;/h6&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;SELECT
  spec_id,
  COUNT(*) as partition_count
FROM
  catalog.table.partitions
GROUP BY
  spec_id;&lt;/code&gt;&lt;/pre&gt;&lt;h6&gt;컴팩션이 필요한 파티션 식별&lt;/h6&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;SELECT
  partition,
  COUNT(*) AS num_files,
  AVG(file_size_in_bytes) AS avg_file_size
FROM
  catalog.table.files
GROUP BY
  partition
ORDER BY
  num_files DESC,
  avg_file_size ASC;&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;4.7 기타 파티셔닝 고려사항&lt;/h4&gt;&lt;h4&gt;테이블 마이그레이션 시 파티션 변환&lt;/h4&gt;&lt;p&gt;migrate 프로시저(13장에서 논의)를 사용하여 Hive 테이블을 마이그레이션할 때, 현재 파생 열(예: 같은 테이블의 타임스탬프 열에 기반한 월 열)로 파티션되어 있지만 Apache Iceberg에 Iceberg 변환을 대신 사용해야 함을 표현하고 싶을 수 있습니다. 이를 위해&amp;nbsp;&lt;code&gt;REPLACE PARTITION&lt;/code&gt;&amp;nbsp;명령을 사용할 수 있습니다:&lt;/p&gt;&lt;p&gt;sql&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;ALTER TABLE catalog.members REPLACE PARTITION FIELD registration_day 
WITH days(registration_ts) AS day_of_registration;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 명령은 데이터파일을 변경하지 않지만, 메타데이터가 Iceberg 변환을 사용하여 파티션 값을 추적할 수 있게 해줍니다.&lt;/p&gt;&lt;hr&gt;&lt;h2&gt;5. Copy-on-Write vs Merge-on-Read&lt;/h2&gt;&lt;h4&gt;5.1 개요&lt;/h4&gt;&lt;p&gt;Apache Iceberg를 사용할 때 행 수준 업데이트(updates)와 삭제(deletes)가 테이블에서 어떻게 처리될지 선택해야 합니다. 선택하는 전략은 업데이트와 삭제를 많이 하는지, 테이블을 쿼리 최적화에 대해 얼마나 관심이 있는지에 따라 달라집니다.&lt;/p&gt;&lt;table style="min-width: 100px;"&gt;&lt;colgroup&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;전략&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;읽기 성능&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;업데이트/삭제 성능&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;최적화 방법&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;Copy-on-Write&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;빠른 읽기&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;느린 업데이트/삭제&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;수정 빈도가 낮은 데이터에 이상적&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;Merge-on-Read (positional deletes)&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;읽기 부하 있음&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;빠른 업데이트/삭제&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;정기적인 컴팩션을 사용하여 읽기 비용 최소화&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;Merge-on-Read (equality deletes)&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;느린 읽기&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;가장 빠른 업데이트/삭제&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;더 빈번한 컴팩션을 사용하여 읽기 비용 최소화&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262613_7MqCNvzO8s830IcodrmM.png" alt="Pasted image 20260202070519.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;5.2 Copy-on-Write (COW)&lt;/h4&gt;&lt;p&gt;기본 접근 방식은 &lt;strong&gt;copy-on-write(COW)&lt;/strong&gt;라고 합니다. 이 접근 방식에서는 데이터파일의 단일 행이라도 업데이트되거나 삭제되면, 해당 데이터파일이 다시 쓰이고 새 파일이 새 스냅샷에서 그 자리를 대신합니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262613_SuY5HORmTeh86yA4VCmc.png" alt="Pasted image 20260201080314.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262613_3L0O6EltidkngikCfsmH.png" alt="Pasted image 20260202070537.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;p&gt;이것은 읽기 쿼리가 삭제되거나 업데이트된 파일을 조정할 필요 없이 데이터를 읽을 수 있어 &lt;strong&gt;읽기에 최적화&lt;/strong&gt;하려는 경우 이상적입니다. 그러나 작업 부하가 매우 정기적인 행 수준 업데이트로 구성되면, 해당 업데이트를 위해 전체 데이터파일을 다시 쓰는 것이 SLA가 허용하는 것 이상으로 업데이트를 느리게 할 수 있습니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;장점:&lt;/strong&gt; 더 빠른 읽기&lt;br&gt;&lt;strong&gt;단점:&lt;/strong&gt; 더 느린 행 수준 업데이트 및 삭제&lt;/p&gt;&lt;h4&gt;5.3 Merge-on-Read (MOR)&lt;/h4&gt;&lt;p&gt;copy-on-write의 대안은 &lt;strong&gt;merge-on-read(MOR)&lt;/strong&gt;입니다. 전체 데이터파일을 다시 쓰는 대신, 삭제 파일에 기존 파일에서 업데이트할 레코드를 캡처하고, 삭제 파일이 어떤 레코드를 무시해야 하는지 추적합니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262613_hTJ1WZhqopdxIDC0bYm9.png" alt="Pasted image 20260202070602.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;p&gt;&lt;strong&gt;레코드 삭제 시:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;레코드가 삭제 파일에 나열됩니다&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;리더가 테이블을 읽을 때, 데이터파일과 삭제 파일을 조정합니다&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;레코드 업데이트 시:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;업데이트할 레코드가 삭제 파일에 추적됩니다&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;업데이트된 레코드만 있는 새 데이터파일이 생성됩니다&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;리더가 테이블을 읽을 때, 삭제 파일 때문에 레코드의 이전 버전을 무시하고 새 데이터파일의 새 버전을 사용합니다&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262613_dVIk80Aqoh7AKUiRZUtY.png" alt="Pasted image 20260201080927.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;p&gt;이는 업데이트할 레코드와 함께 데이터파일에 존재한다는 이유만으로 변경되지 않은 레코드를 새 파일에 다시 쓸 필요를 피하고, 쓰기 트랜잭션을 가속화합니다. 그러나 쿼리가 삭제 파일을 스캔하여 적절한 데이터파일에서 어떤 레코드를 무시해야 하는지 알아야 하므로 더 느린 읽기의 비용이 따릅니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;읽기 비용을 최소화하기 위해:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;정기적인 컴팩션 작업을 실행합니다&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;컴팩션 작업이 효율적으로 실행되도록 다음 속성을 활용합니다:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;filter/where&lt;/code&gt; 절을 사용하여 마지막 시간 프레임(시간, 일)에 수집된 파일에만 컴팩션을 실행&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;부분 진행 모드를 사용하여 파일 그룹이 다시 쓰일 때마다 커밋하여 리더가 점점 개선을 더 빨리 볼 수 있도록 함&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;장점:&lt;/strong&gt; 더 빠른 행 수준 업데이트&lt;br&gt;&lt;strong&gt;단점:&lt;/strong&gt; 삭제 파일을 조정해야 하므로 더 느린 읽기&lt;/p&gt;&lt;h4&gt;5.4 삭제 파일 유형&lt;/h4&gt;&lt;p&gt;MOR 쓰기를 할 때, 삭제 파일을 통해 향후 읽기를 위해 기존 데이터파일에서 어떤 레코드를 무시해야 하는지 추적할 수 있습니다.&lt;/p&gt;&lt;h4&gt;Positional Delete Files (위치 삭제 파일)&lt;/h4&gt;&lt;p&gt;특정 행을 제거하고 싶을 때, 데이터셋에서 위치에 따라 행 데이터를 찾을 수 있습니다. 영화관에서 좌석 번호로 친구를 찾는 것과 같습니다.&lt;/p&gt;&lt;p&gt;위치 삭제는 어떤 파일의 어떤 행을 무시해야 하는지 추적합니다:&lt;/p&gt;&lt;table style="min-width: 50px;"&gt;&lt;colgroup&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;Filepath&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;Position&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;001.parquet&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;0&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;001.parquet&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;5&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;006.parquet&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;5&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;지정된 파일을 읽을 때, 위치 삭제 파일은 지정된 위치의 행을 건너뜁니다. 이는 행을 건너뛰어야 하는 상당히 구체적인 지점이 있으므로 읽기 시 훨씬 작은 비용이 필요합니다. 그러나 삭제 파일의 작성자가 삭제된 레코드의 위치를 알아야 하므로 삭제된 레코드가 있는 파일을 읽어 해당 위치를 식별해야 하는 쓰기 시 비용이 있습니다.&lt;/p&gt;&lt;h4&gt;Equality Delete Files (동등 삭제 파일)&lt;/h4&gt;&lt;p&gt;레코드가 일치하면 무시해야 하는 값을 대신 지정합니다. 군중에서 밝은 빨간 모자를 쓰고 있어서 친구를 고르는 것과 같습니다.&lt;/p&gt;&lt;table style="min-width: 50px;"&gt;&lt;colgroup&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;Team&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;State&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;Yellow&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;NY&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;Green&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;MA&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;파일을 열고 읽어 대상 값을 추적할 필요가 없으므로 쓰기 시 비용이 없지만, 훨씬 더 큰 읽기 시 비용이 있습니다. 읽기 시 비용은 일치하는 값이 있는 레코드가 어디에 있는지에 대한 정보가 없어서, 데이터를 읽을 때 일치하는 레코드를 포함할 수 있는 모든 레코드와 비교해야 하기 때문입니다.&lt;/p&gt;&lt;h4&gt;5.5 COW 및 MOR 구성&lt;/h4&gt;&lt;p&gt;행 수준 업데이트와 삭제를 수행하려는 쓰기 유형을 기본적으로 테이블 속성에서 지정할 수 있습니다:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;ALTER TABLE catalog.MyTable SET TBLPROPERTIES (
  'write.delete.mode'='copy-on-write',
  'write.update.mode'='merge-on-read',
  'write.merge.mode'='merge-on-read',
);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;테이블 속성을 설정하는 대신, 쓰기 옵션에서 직접 COW 또는 MOR 속성을 지정할 수도 있습니다 (Spark의 DataFrame API 사용 시):&lt;/p&gt;&lt;pre&gt;&lt;code class="language-python"&gt;df.write \
  .option("write-format", "parquet") \
  .option("merge-mode", "merge-on-read")&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;비 Apache Spark 엔진 작업 시 주의사항:&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;테이블 속성이 준수될 수도 있고 아닐 수도 있습니다. 엔진이 지원을 구현하는지에 따라 달라집니다&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;MOR 사용 시, 데이터를 쿼리하는 데 사용하는 엔진이 삭제 파일을 읽을 수 있는지 확인해야 합니다&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h2&gt;6. 기타 고려사항 (Other Considerations)&lt;/h2&gt;&lt;h4&gt;6.1 메트릭 수집 (Metrics Collection)&lt;/h4&gt;&lt;p&gt;2장에서 논의한 것처럼, 각 데이터파일 그룹에 대한 매니페스트는 min/max 필터링 및 기타 최적화를 돕기 위해 테이블의 각 필드에 대한 메트릭을 추적합니다. 추적되는 열 수준 메트릭 유형:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;값, null 값, 고유 값의 개수&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;상한 및 하한 값&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;매우 넓은 테이블(필드가 많은 테이블, 예: 100개 이상)이 있다면, 추적되는 메트릭의 수가 메타데이터를 읽는 데 부담이 되기 시작할 수 있습니다.&lt;/p&gt;&lt;p&gt;Apache Iceberg의 테이블 속성을 사용하면 어떤 열의 메트릭을 추적하고 어떤 열은 추적하지 않을지 세밀하게 조정할 수 있습니다:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;ALTER TABLE catalog.db.students SET TBLPROPERTIES (
  'write.metadata.metrics.column.col1'='none',
  'write.metadata.metrics.column.col2'='full',
  'write.metadata.metrics.column.col3'='counts',
  'write.metadata.metrics.column.col4'='truncate(16)'
);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;메트릭 수집 레벨:&lt;/strong&gt;&lt;/p&gt;&lt;table style="min-width: 50px;"&gt;&lt;colgroup&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;레벨&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;설명&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;none&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;메트릭을 수집하지 않음&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;counts&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;개수만 수집 (값, 고유 값, null 값)&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;truncate(XX)&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;개수를 세고 값을 특정 문자 수로 잘라서 상한/하한을 그에 기반함&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;full&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;전체 값을 기반으로 개수와 상한/하한 계산&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;기본적으로 Iceberg는 이를 &lt;code&gt;truncate(16)&lt;/code&gt;로 설정합니다.&lt;/p&gt;&lt;h4&gt;6.2 매니페스트 재작성 (Rewriting Manifests)&lt;/h4&gt;&lt;p&gt;때때로 문제는 데이터파일이 아닐 수 있습니다. 데이터파일은 잘 정렬된 데이터로 적절한 크기를 가지고 있지만, 여러 스냅샷에 걸쳐 작성되어 개별 매니페스트가 더 많은 데이터파일을 나열할 수 있습니다. 매니페스트가 더 가볍지만, 더 많은 매니페스트는 여전히 더 많은 파일 작업을 의미합니다.&lt;/p&gt;&lt;p&gt;Apache Iceberg에는 매니페스트를 재작성하는 프로시저가 있습니다:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;CALL catalog.system.rewrite_manifests('MyTable')&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 프로시저는 기본적으로 매니페스트당 데이터파일 평균에 기반하여 매니페스트를 재작성합니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;전달할 수 있는 인수:&lt;/strong&gt;&lt;/p&gt;&lt;table style="min-width: 50px;"&gt;&lt;colgroup&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;인수&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;설명&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;table&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;작업을 실행할 테이블&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;use_caching&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;true이면 데이터파일을 읽을 때 Spark 캐시 사용&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;h4&gt;6.3 스토리지 최적화 (Optimizing Storage)&lt;/h4&gt;&lt;h4&gt;스냅샷 만료 (Expire Snapshots)&lt;/h4&gt;&lt;p&gt;테이블에서 스냅샷을 절대 만료시키지 않으면 증가하는 메타데이터 양이 시간이 지남에 따라 부담이 될 수 있습니다. 모든 스냅샷을 유지하면 다음과 같은 추가 비용이 있습니다:&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262613_5MfimSw4tGqGszOilOJv.png" alt="Pasted image 20260202070641.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;테이블을 구성하는 파일을 호스팅하는 스토리지 비용&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;쿼리 전에 테이블의 메타데이터 구조를 파싱하는 데 드는 계산 비용&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;CALL catalog.system.expire_snapshots(table =&amp;gt; 'MyTable', older_than =&amp;gt; TIMESTAMP '2023-06-01 00:00:00')&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;특정 스냅샷 ID를 만료시킬 수도 있습니다:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;CALL catalog.system.expire_snapshots(table =&amp;gt; 'MyTable', snapshot_ids =&amp;gt; ARRAY(53))&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;expire_snapshots 프로시저에 전달할 수 있는 인수:&lt;/strong&gt;&lt;/p&gt;&lt;table style="min-width: 50px;"&gt;&lt;colgroup&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;인수&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;설명&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;table&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;작업을 실행할 테이블&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;older_than&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;이 타임스탬프 이전의 모든 스냅샷 만료&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;retain_last&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;유지할 최소 스냅샷 수&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;snapshot_ids&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;만료할 특정 스냅샷 ID&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;max_concurrent_deletes&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;파일 삭제에 사용할 스레드 수&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;stream_results&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;true이면 삭제된 파일을 RDD 파티션별로 Spark 드라이버로 전송&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;h4&gt;고아 파일 관리 (Orphan File Management)&lt;/h4&gt;&lt;p&gt;스토리지를 최적화할 때 또 다른 고려사항은 고아 파일입니다. 이는 테이블의 데이터 디렉토리에 축적되지만 실패한 작업에 의해 작성되어 메타데이터 트리에서 추적되지 않는 파일과 아티팩트입니다. 이러한 파일은 스냅샷 만료로 정리되지 않으므로 이를 처리하기 위해 특별한 프로시저를 간헐적으로 실행해야 합니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262613_Tc6Euy5xpcik2YxCVxUu.png" alt="Pasted image 20260202070658.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;CALL catalog.system.remove_orphan_files(table =&amp;gt; 'MyTable')&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;removeOrphanFiles 프로시저에 전달할 수 있는 인수:&lt;/strong&gt;&lt;/p&gt;&lt;table style="min-width: 50px;"&gt;&lt;colgroup&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;인수&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;설명&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;table&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;작업할 테이블&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;older_than&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;이 타임스탬프 이전에 생성된 파일만 삭제&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;location&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;고아 파일을 찾을 위치; 기본값은 테이블의 기본 위치&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;dry_run&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;true이면 파일을 삭제하지 않고 삭제될 파일 목록만 반환&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;max_concurrent_deletes&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;파일 삭제를 위한 최대 스레드 수&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;h4&gt;6.4 쓰기 분배 모드 (Write Distribution Mode)&lt;/h4&gt;&lt;p&gt;쓰기 분배 모드는 대규모 병렬 처리(MPP) 시스템이 파일 쓰기를 처리하는 방식을 이해해야 합니다. 이러한 시스템은 작업을 여러 노드에 분배하며, 각각 작업이나 태스크를 수행합니다. 쓰기 분배는 쓰려는 레코드가 이러한 태스크에 어떻게 분배되는지입니다.&lt;/p&gt;&lt;p&gt;특정 쓰기 분배 모드가 설정되지 않으면, 데이터는 임의로 분배됩니다. 첫 X개의 레코드는 첫 번째 태스크로, 다음 X개는 다음 태스크로, 등등.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;세 가지 옵션:&lt;/strong&gt;&lt;/p&gt;&lt;table style="min-width: 50px;"&gt;&lt;colgroup&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;모드&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;설명&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;none&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;특별한 분배 없음. 쓰기 시 가장 빠르며 미리 정렬된 데이터에 이상적&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;hash&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;파티션 키로 해시 분배됨&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;&lt;code&gt;range&lt;/code&gt;&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;파티션 키 또는 정렬 순서로 범위 분배됨&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;ALTER TABLE catalog.MyTable SET TBLPROPERTIES (
  'write.distribution-mode'='hash',
  'write.delete.distribution-mode'='none',
  'write.update.distribution-mode'='range',
  'write.merge.distribution-mode'='hash'
);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;해시 분배:&lt;/strong&gt; 각 레코드의 값이 해시 함수를 통과하고 결과에 따라 함께 그룹화됩니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;범위 분배:&lt;/strong&gt; 데이터가 정렬되고 분배됩니다. 파티션 값 또는 테이블에 SortOrder가 있는 경우 SortOrder에 의해 정렬됩니다. 이는 특정 필드에서 클러스터링의 이점을 받을 수 있는 데이터에 이상적입니다.&lt;/p&gt;&lt;h4&gt;6.5 오브젝트 스토리지 고려사항 (Object Storage Considerations)&lt;/h4&gt;&lt;p&gt;오브젝트 스토리지는 데이터를 저장하는 독특한 방식입니다. 전통적인 파일시스템처럼 깔끔한 폴더 구조에 파일을 유지하는 대신, 오브젝트 스토리지는 모든 것을 버킷이라고 불리는 곳에 넣습니다.&lt;/p&gt;&lt;p&gt;오브젝트 스토리지의 아키텍처와 병렬 처리 방식 때문에, 종종 같은 "prefix" 아래의 파일로 갈 수 있는 요청 수에 제한이 있습니다. &lt;code&gt;/prefix1/fileA.txt&lt;/code&gt;와 &lt;code&gt;/prefix1/fileB.txt&lt;/code&gt;에 접근하려 할 때, 서로 다른 파일이지만 둘 다 접근하는 것은 prefix1에 대한 제한에 포함됩니다. 이는 많은 파일이 있는 파티션에서 문제가 되며, 쿼리가 이러한 파티션에 많은 요청을 보내 스로틀링이 발생하여 쿼리가 느려질 수 있습니다.&lt;/p&gt;&lt;p&gt;Apache Iceberg는 파일이 물리적으로 배치되는 방식에 의존하지 않으므로 이 시나리오에 독특하게 적합합니다. 같은 파티션의 파일을 여러 prefix에 걸쳐 쓸 수 있습니다.&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;ALTER TABLE catalog.MyTable SET TBLPROPERTIES (
  'write.object-storage.enabled'= true
);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;이전:&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class="language-plaintext"&gt;s3://bucket/database/table/field=value1/datafile1.parquet
s3://bucket/database/table/field=value1/datafile2.parquet
s3://bucket/database/table/field=value1/datafile3.parquet&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;이후:&lt;/strong&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class="language-plaintext"&gt;s3://bucket/4809098/database/table/field=value1/datafile1.parquet
s3://bucket/5840329/database/table/field=value1/datafile2.parquet
s3://bucket/2342344/database/table/field=value1/datafile3.parquet&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;해시가 파일 경로에 있어서, 같은 파티션의 각 파일이 이제 다른 prefix 아래에 있는 것처럼 취급되어 스로틀링을 피할 수 있습니다.&lt;/p&gt;&lt;h4&gt;6.6 데이터파일 블룸 필터 (Datafile Bloom Filters)&lt;/h4&gt;&lt;p&gt;블룸 필터는 값이 데이터셋에 존재할 가능성이 있는지 알 수 있는 방법입니다. 결정한 길이의 비트(이진 코드의 0과 1) 줄을 상상해 보세요. 데이터를 데이터셋에 추가할 때, 각 값을 해시 함수라는 프로세스를 통해 실행합니다. 이 함수는 비트 줄의 한 지점을 내놓고, 그 비트를 0에서 1로 뒤집습니다. 이 뒤집힌 비트는 "이 지점에 해시되는 값이 데이터셋에 있을 수 있다"고 말하는 플래그와 같습니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;예시:&lt;/strong&gt; 10비트의 블룸 필터를 통해 1,000개의 레코드를 공급하면:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-plaintext"&gt;[0,1,1,0,0,1,1,1,1,0]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;값 X를 찾고 싶다고 합니다. X를 같은 해시 함수를 통해 넣으면 비트 줄의 3번째 지점을 가리킵니다. 블룸 필터에 따르면 3번째 지점에 1이 있습니다. 이는 값 X가 데이터셋에 있을 가능성이 있음을 의미합니다. 그래서 X가 정말 있는지 데이터셋을 확인합니다.&lt;/p&gt;&lt;p&gt;값 Y를 찾는다고 합니다. Y를 해시 함수를 통해 실행하면 4번째 지점을 가리킵니다. 하지만 블룸 필터에 그 지점에 0이 있어서, 이 지점에 해시된 값이 없다는 의미입니다. 따라서 Y가 데이터셋에 확실히 없다고 자신있게 말할 수 있고, 데이터를 뒤지지 않아 시간을 절약할 수 있습니다.&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;ALTER TABLE catalog.MyTable SET TBLPROPERTIES (
  'write.parquet.bloom-filter-enabled.column.col1'= true,
  'write.parquet.bloom-filter-max-bytes'= 1048576
);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;블룸 필터가 명확히 필요한 데이터가 존재하지 않음을 나타내는 데이터파일을 건너뛰어 데이터파일 읽기를 더욱 빠르게 할 수 있습니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/2/6/20262613_SFmfj4HixVqef6JPmBCV.png" alt="Pasted image 20260202070721.png" style="object-fit: cover;"&gt;&lt;/figure&gt;</description><pubDate>Fri, 06 Feb 2026 13:06:56 +0900</pubDate><guid>http://blex.me/@mildsalmon/apache-iceberg-the-definitive-guide-optimizing-t</guid></item><item><title>[Apache Iceberg - The Definitive Guide] Lifecycle of Write and Read Queries</title><link>http://blex.me/@mildsalmon/apache-iceberg-the-definitive-guide-lifecycle-of</link><description>&lt;h2&gt;Apache Iceberg 쿼리 생명주기 완벽 가이드&lt;/h2&gt;&lt;h2&gt;1. 아이스버그의 3계층과 쿼리 엔진의 상호작용&lt;/h2&gt;&lt;p&gt;쿼리 엔진은 읽기/쓰기 작업 시 아이스버그의 세 계층과 다음과 같이 상호작용합니다&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/27/20261278_8cl5xrXvWQkoz0UAFgiI.png" alt="Pasted image 20250928153941.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;1.1. 카탈로그 계층 (Catalog Layer)&lt;/h4&gt;&lt;p&gt;모든 쿼리의 시작점입니다.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;읽기&lt;/strong&gt;: 엔진은 카탈로그를 통해 테이블의 현재 상태, 즉 최신 메타데이터 파일의 위치를 파악합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;쓰기&lt;/strong&gt;: 엔진은 카탈로그를 조회하여 테이블의 스키마와 파티셔닝 전략을 확인하고, 작업 완료 후 새 메타데이터 파일의 위치를 원자적으로(atomically) 업데이트하여 커밋을 완료합니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;1.2. 메타데이터 계층 (Metadata Layer)&lt;/h4&gt;&lt;p&gt;성능 최적화의 핵심입니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;계층적 필터링&lt;/strong&gt;: 이 계층은 &lt;strong&gt;메타데이터 파일(metadata file), 매니페스트 리스트(manifest list), 매니페스트 파일(manifest file)&lt;/strong&gt;로 구성됩니다. 각 단계는 다음 단계에서 스캔할 파일의 범위를 좁히기 위한 통계 정보를 담고 있습니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;File Pruning&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;엔진은 먼저 &lt;strong&gt;매니페스트 리스트&lt;/strong&gt;의 파티션 경계값 통계를 이용해 관련 없는 &lt;strong&gt;매니페스트 파일&lt;/strong&gt;들을 건너뜁니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;그다음, 남은 &lt;strong&gt;매니페스트 파일&lt;/strong&gt;을 열어 개별 &lt;strong&gt;데이터 파일&lt;/strong&gt;의 컬럼별 통계(최대/최소값, null 개수 등)를 확인하고, 쿼리 조건에 맞지 않는 데이터 파일들을 건너뜁니다. 이 과정을 통해 불필요한 I/O를 최소화합니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;1.3. 데이터 계층 (Data Layer)&lt;/h4&gt;&lt;p&gt;실제 데이터가 저장되는 곳입니다.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;읽기&lt;/strong&gt;: 엔진은 메타데이터 계층에서 필터링된 데이터 파일들만 스캔하여 결과를 반환합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;쓰기&lt;/strong&gt;: 새로운 데이터 파일이 파일 스토리지에 생성되고, 이와 관련된 메타데이터 파일들이 업데이트됩니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h2&gt;2. 쓰기 쿼리의 생명주기 (Writing Queries in Apache Iceberg)&lt;/h2&gt;&lt;p&gt;쓰기 프로세스는 여러 단계에 걸쳐 데이터 파일과 메타데이터 파일을 생성하고, 마지막에 카탈로그를 원자적으로 업데이트하여 트랜잭션을 완료합니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/27/20261278_7sARqD9xu64d40d5FHNx.png" alt="Pasted image 20250928154000.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;2.1. 테이블 생성 (&lt;code&gt;CREATE TABLE&lt;/code&gt;)&lt;/h4&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;# Spark SQL
CREATE TABLE orders (
    order_id BIGINT,
    customer_id BIGINT,
    order_amount DECIMAL(10, 2),
    order_ts TIMESTAMP
)
USING iceberg
PARTITIONED BY (HOUR(order_ts))&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;2.1.1. 1단계 (쿼리를 엔진으로 전송)&lt;/h4&gt;&lt;p&gt;쿼리 엔진이 &lt;code&gt;CREATE TABLE&lt;/code&gt; 문을 파싱합니다.&lt;/p&gt;&lt;h4&gt;2.1.2. 2단계 (메타데이터 파일 생성)&lt;/h4&gt;&lt;p&gt;엔진은 테이블 스키마, 파티션 명세, 고유 식별자(table-uuid) 등의 정보를 담은 &lt;strong&gt;첫 번째 메타데이터 파일(&lt;/strong&gt;&lt;code&gt;v1.metadata.json&lt;/code&gt;&lt;strong&gt;)&lt;/strong&gt;을 데이터 레이크 파일 시스템에 생성합니다. 이 시점에는 데이터가 없으므로 데이터 파일이나 매니페스트 파일은 생성되지 않습니다.&lt;/p&gt;&lt;h4&gt;2.1.3. 3단계 (카탈로그 업데이트)&lt;/h4&gt;&lt;p&gt;카탈로그의 현재 메타데이터 포인터가 &lt;code&gt;v1.metadata.json&lt;/code&gt;을 가리키도록 업데이트하여 커밋을 완료합니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/27/20261278_WO6YYqTpNwp3cBrVTuaO.png" alt="Pasted image 20250928154057.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;hr&gt;&lt;h4&gt;2.2. 데이터 삽입 (&lt;code&gt;INSERT INTO&lt;/code&gt;)&lt;/h4&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;# Spark SQL
INSERT INTO orders VALUES (
    123,
    456,
    36.17,
    '2023-03-07 08:10:23'
)&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;2.2.1. 1단계 (쿼리를 엔진으로 전송)&lt;/h4&gt;&lt;p&gt;INSERT문이므로 query planning을 시작하기 위해 스키마 등의 테이블 정보가 필요합니다.&lt;/p&gt;&lt;h4&gt;2.2.2. 2단계 (카탈로그 조회)&lt;/h4&gt;&lt;p&gt;엔진은 카탈로그에서 현재 메타데이터 파일(&lt;code&gt;v1.metadata.json&lt;/code&gt;)의 위치를 확인하여 테이블 스키마와 파티셔닝 구성을 파악합니다.&lt;/p&gt;&lt;h4&gt;2.2.3. 3단계 (데이터 파일과 메타데이터 파일 생성)&lt;/h4&gt;&lt;p&gt;파티셔닝 전략에 따라 데이터를 &lt;strong&gt;Parquet 파일&lt;/strong&gt;로 저장합니다. 정렬 순서가 정의되어 있으면 데이터 파일에 쓰기 전에 레코드 정렬합니다.&lt;/p&gt;&lt;p&gt;새 데이터 파일의 경로와 컬럼별 통계 정보(최대/최소값 등)를 포함하는 &lt;strong&gt;매니페스트 파일&lt;/strong&gt;을 생성합니다.&lt;/p&gt;&lt;p&gt;생성된 매니페스트 파일을 추적하는 &lt;strong&gt;매니페스트 리스트&lt;/strong&gt; 파일을 만듭니다.&lt;/p&gt;&lt;p&gt;이전 메타데이터 파일(&lt;code&gt;v1&lt;/code&gt;)을 기반으로 새로운 스냅샷 정보를 추가한 &lt;strong&gt;새 메타데이터 파일(&lt;/strong&gt;&lt;code&gt;v2.metadata.json&lt;/code&gt;&lt;strong&gt;)&lt;/strong&gt;을 생성합니다.&lt;/p&gt;&lt;h4&gt;2.2.4. 4단계 (원자적 커밋)&lt;/h4&gt;&lt;p&gt;엔진은 카탈로그에 다시 접근하여 다른 쓰기 작업과의 충돌이 없는지 확인한 후, 메타데이터 포인터를 &lt;code&gt;v2.metadata.json&lt;/code&gt;으로 원자적으로 업데이트합니다. 이를 통해 트랜잭션의 일관성을 보장합니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/27/20261278_iHwN48D2xHt2PXicJKbr.png" alt="Pasted image 20250928154136.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;hr&gt;&lt;h4&gt;2.3. 데이터 병합 (&lt;code&gt;MERGE INTO&lt;/code&gt;)&lt;/h4&gt;&lt;p&gt;&lt;code&gt;MERGE INTO&lt;/code&gt;는 조건에 따라 기존 행을 업데이트하거나 새 행을 삽입하는 작업입니다.&lt;/p&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;# Spark SQL
MERGE INTO orders o
USING (SELECT * FROM orders_staging) s
ON o.order_id = s.order_id
WHEN MATCHED THEN UPDATE SET order_amount = s.order_amount
WHEN NOT MATCHED THEN INSERT *;&lt;/code&gt;&lt;/pre&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/27/20261278_BZ9oBFihCWuRdTCzC1KG.png" alt="Pasted image 20260126050306.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;2.3.1. 1단계 (쿼리를 엔진으로 전송)&lt;/h4&gt;&lt;p&gt;query planning을 위해 두 테이블의 데이터가 필요합니다.&lt;/p&gt;&lt;h4&gt;2.3.2. 2단계 (카탈로그 조회)&lt;/h4&gt;&lt;p&gt;현재 메타데이터 파일(&lt;code&gt;v2.metadata.json&lt;/code&gt;)의 위치를 확인하여 테이블 스키마와 파티셔닝 구성을 파악합니다.&lt;/p&gt;&lt;h4&gt;2.3.3. 3단계 (데이터 파일과 메타데이터 파일 생성)&lt;/h4&gt;&lt;p&gt;엔진은 소스 테이블과 대상 테이블의 데이터를 메모리에 로드하여 일치하는 레코드를 찾습니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;쓰기 전략&lt;/strong&gt; (Copy-on-Write (COW), Merge-on-Read (MOR)):&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;COW&lt;/strong&gt;: 테이블 업데이트 시 관련 데이터 파일을 새 데이터 파일로 재작성&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;MOR&lt;/strong&gt;: 데이터 파일을 재작성하지 않고 새로운 Delete File을 생성하여 변경사항 추적&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;COW 전략 예시&lt;/strong&gt;:&lt;/p&gt;&lt;p&gt;매칭되는 데이터가 있는 경우:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;../order_ts_hour=2023-03-07-08/0_0_0.parquet&lt;/code&gt; 파일을 메모리로 읽음&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;메모리에서 order_amount 업데이트&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;수정된 데이터를 새 parquet 파일(&lt;code&gt;../order_ts_hour=2023-03-07-08/0_0_0_1.parquet&lt;/code&gt;)로 작성&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;매칭되지 않는 데이터가 있는 경우:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;매칭되지 않은 레코드는 일반 INSERT로 처리&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;다른 파티션에 새 데이터 파일(&lt;code&gt;../order_ts_hour=2023-01-27-10/0_0_0.parquet&lt;/code&gt;)로 작성&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;2.3.4. 4단계 (카탈로그 파일 업데이트)&lt;/h4&gt;&lt;p&gt;&lt;code&gt;INSERT&lt;/code&gt;와 동일하게 새 매니페스트 파일, 매니페스트 리스트, 새 메타데이터 파일(&lt;code&gt;v3.metadata.json&lt;/code&gt;)을 순차적으로 생성한 후 카탈로그 포인터를 원자적으로 업데이트하여 작업을 완료합니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/27/20261278_hXyFmiDgzpawXRvaKu1C.png" alt="Pasted image 20250928154156.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;hr&gt;&lt;h4&gt;2.4. 동시성 제어와 원자적 커밋&lt;/h4&gt;&lt;p&gt;앞서 살펴본 쓰기 쿼리들(CREATE, INSERT, MERGE)에서 공통적으로 마지막 단계는 "카탈로그 파일을 업데이트하여 변경사항 커밋"이었습니다. 이 과정이 단순해 보일 수 있지만, 실제로는 Apache Iceberg의 가장 핵심적인 메커니즘인 &lt;strong&gt;원자적 커밋&lt;/strong&gt;과 &lt;strong&gt;동시성 제어&lt;/strong&gt;가 작동하는 중요한 순간입니다.&lt;/p&gt;&lt;p&gt;실제 운영 환경에서는 여러 사용자나 프로세스가 동시에 같은 테이블에 데이터를 쓰려고 할 수 있습니다. Apache Iceberg는 이러한 동시 쓰기 상황에서도 데이터 무결성을 보장하고 모든 변경사항이 손실 없이 반영되도록 &lt;strong&gt;낙관적 동시성 제어(Optimistic Concurrency Control, OCC)&lt;/strong&gt; 메커니즘을 사용합니다.&lt;/p&gt;&lt;h4&gt;2.4.1. 낙관적 동시성 제어(Optimistic Concurrency Control)란?&lt;/h4&gt;&lt;p&gt;낙관적 동시성 제어는 "기본적으로 트랜잭션들이 서로 충돌하지 않을 것"이라고 가정하고 작업을 진행합니다. 각 쓰기 작업은 다른 작업의 간섭 없이 독립적으로 진행되며, &lt;strong&gt;마지막 커밋 단계에서만&lt;/strong&gt; 충돌이 있었는지 검사합니다. 충돌이 감지되면 해당 작업은 실패하고 재시도됩니다.&lt;/p&gt;&lt;p&gt;이는 &lt;strong&gt;비관적 동시성 제어(Pessimistic Concurrency Control)&lt;/strong&gt;와 대비됩니다. 비관적 방식은 작업 시작 전에 락(lock)을 획득하여 다른 작업이 접근하지 못하도록 막지만, 낙관적 방식은 락 없이 진행하다가 커밋 시점에만 검증합니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;락을 획득하고 유지하는 오버헤드가 없어 대부분의 경우 더 빠름&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;읽기 작업이 쓰기 작업에 의해 블로킹되지 않음&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;충돌이 드문 환경에서 특히 효율적&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;충돌이 발생하면 재시도해야 하므로 충돌이 빈번한 경우 비효율적일 수 있음&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;2.4.2. 동시 쓰기 시나리오: 실제 예제&lt;/h4&gt;&lt;p&gt;두 개의 MERGE 쿼리가 거의 동시에 같은 테이블에 실행되는 상황을 단계별로 살펴보겠습니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;초기 상태&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;테이블의 현재 메타데이터: &lt;code&gt;v3.metadata.json&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;쓰기 작업 A: 고객 456의 주문 금액을 업데이트&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;쓰기 작업 B: 고객 789의 새로운 주문 삽입&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;2.4.3. 1단계: 현재 메타데이터 확인&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;쓰기 A&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-plaintext"&gt;카탈로그 확인 → 현재 메타데이터: v3.metadata.json
기준 버전: v3&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;쓰기 B&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-plaintext"&gt;카탈로그 확인 → 현재 메타데이터: v3.metadata.json
기준 버전: v3&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;두 작업 모두 &lt;code&gt;v3&lt;/code&gt;를 기준으로 작업을 시작합니다. 이 시점에는 서로의 존재를 모릅니다.&lt;/p&gt;&lt;h4&gt;2.4.4. 2단계: 데이터 및 메타데이터 파일 생성 (낙관적 작업)&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;쓰기 A&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-plaintext"&gt;1. 데이터 파일 작성: 0_0_5.parquet (고객 456 업데이트)
2. 매니페스트 파일 생성: manifest-A.avro
3. 매니페스트 리스트 생성: snap-A.avro
4. 새 메타데이터 파일 생성: v4_A.metadata.json (v3 기반)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;쓰기 B&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-plaintext"&gt;1. 데이터 파일 작성: 0_0_6.parquet (고객 789 신규)
2. 매니페스트 파일 생성: manifest-B.avro
3. 매니페스트 리스트 생성: snap-B.avro
4. 새 메타데이터 파일 생성: v4_B.metadata.json (v3 기반)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;중요&lt;/strong&gt;: 이 단계에서는 파일들이 파일 시스템에 작성되었을 뿐, &lt;strong&gt;아직 테이블에 공식적으로 반영되지 않았습니다&lt;/strong&gt;. 두 작업 모두 &lt;code&gt;v3&lt;/code&gt;를 기준으로 자신만의 메타데이터를 생성했지만, 카탈로그는 여전히 &lt;code&gt;v3&lt;/code&gt;를 가리키고 있습니다.&lt;/p&gt;&lt;h4&gt;2.4.5. 3단계: 커밋 시도 - 쓰기 A 성공&lt;/h4&gt;&lt;p&gt;쓰기 A가 먼저 카탈로그에 커밋을 시도합니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Compare-And-Swap (CAS) 연산&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-plaintext"&gt;조건: 현재 메타데이터가 v3.metadata.json인가?
→ 예 (v3가 맞음)
→ 포인터를 v4_A.metadata.json으로 변경
→ 커밋 성공!&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;결과&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;테이블의 현재 메타데이터: &lt;code&gt;v3.metadata.json&lt;/code&gt; → &lt;code&gt;v4_A.metadata.json&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;쓰기 A의 변경사항이 공식적으로 테이블에 반영됨&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;2.4.6. 4단계: 커밋 시도 - 쓰기 B 실패&lt;/h4&gt;&lt;p&gt;직후에 쓰기 B가 카탈로그에 커밋을 시도합니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Compare-And-Swap (CAS) 연산&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-plaintext"&gt;조건: 현재 메타데이터가 v3.metadata.json인가?
→ 아니오 (현재는 v4_A.metadata.json)
→ 조건 불일치!
→ 커밋 실패&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;실패 이유&lt;/strong&gt;: 쓰기 B가 작업을 시작했을 때는 메타데이터가 &lt;code&gt;v3&lt;/code&gt;였지만, 커밋을 시도하는 시점에는 이미 쓰기 A가 &lt;code&gt;v4_A&lt;/code&gt;로 변경했습니다. 이는 쓰기 B가 작업한 기준 상태가 더 이상 최신이 아니라는 뜻입니다.&lt;/p&gt;&lt;h4&gt;2.4.7. 5단계: 쓰기 B 재시도&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;쓰기 B의 재시도 과정&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-plaintext"&gt;1. 카탈로그 재확인 → 현재 메타데이터: v4_A.metadata.json
2. 새로운 기준 버전: v4_A
3. v4_A를 기반으로 작업 재수행:
   - 쓰기 A의 변경사항 포함된 상태에서 MERGE 로직 재실행
   - 새 데이터 파일: 0_0_7.parquet (필요시)
   - 새 매니페스트 파일 및 리스트 생성
   - 새 메타데이터 파일 생성: v5.metadata.json (v4_A 기반)
4. 커밋 재시도:
   조건: 현재 메타데이터가 v4_A.metadata.json인가?
   → 예 (v4_A가 맞음)
   → 포인터를 v5.metadata.json으로 변경
   → 커밋 성공!&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;최종 결과&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;테이블의 현재 메타데이터: &lt;code&gt;v5.metadata.json&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;쓰기 A와 쓰기 B의 변경사항이 모두 선형적 이력으로 반영됨&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;메타데이터 이력: &lt;code&gt;v3&lt;/code&gt; → &lt;code&gt;v4_A&lt;/code&gt; (쓰기 A) → &lt;code&gt;v5&lt;/code&gt; (쓰기 B)&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;2.4.8. Compare-And-Swap (CAS) 연산의 원자성&lt;/h4&gt;&lt;p&gt;CAS 연산은 다음 두 단계를 &lt;strong&gt;원자적으로(atomically)&lt;/strong&gt; 수행합니다:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compare (비교)&lt;/strong&gt;: 현재 값이 예상한 값(expected value)과 같은지 확인&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Swap (교체)&lt;/strong&gt;: 같다면 새로운 값으로 교체&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;이 두 단계 사이에 다른 작업이 끼어들 수 없기 때문에, 여러 작업이 동시에 커밋을 시도해도 정확히 하나만 성공하고 나머지는 실패합니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;카탈로그별 CAS 구현&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hadoop Catalog&lt;/strong&gt;: 파일 시스템의 원자적 rename 연산 활용&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hive Metastore&lt;/strong&gt;: Hive의 트랜잭션 메커니즘 활용&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS Glue&lt;/strong&gt;: Glue의 optimistic locking 기능 활용&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Nessie/REST Catalog&lt;/strong&gt;: 버전 관리 시스템의 commit 메커니즘 활용&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;각 카탈로그 구현체는 자신의 저장소가 제공하는 원자적 연산을 활용하여 CAS를 보장합니다.&lt;/p&gt;&lt;h4&gt;2.4.9. 왜 낙관적 동시성 제어를 사용하는가?&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;1. 성능 최적화&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;락(lock)을 획득하고 유지할 필요가 없어 대부분의 경우 더 빠름&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;쓰기 작업이 서로를 블로킹하지 않음&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;2. 읽기 성능 보장&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;읽기 작업은 쓰기 락의 영향을 받지 않음&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;읽기와 쓰기가 서로 방해하지 않음&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;3. 분산 환경 적합성&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;데이터 레이크는 분산 스토리지 환경&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;중앙 집중식 락 관리가 어렵고 비효율적&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;낙관적 방식이 분산 환경에 더 적합&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;4. 충돌이 드문 실제 환경&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;실무에서 정확히 같은 시점에 같은 테이블에 쓰기가 발생하는 경우는 드묾&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;대부분의 경우 재시도 없이 한 번에 성공&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;2.4.10. 선형 이력(Linear History)의 중요성&lt;/h4&gt;&lt;p&gt;낙관적 동시성 제어 덕분에 아이스버그는 &lt;strong&gt;모든 변경사항이 선형적인 이력으로 기록&lt;/strong&gt;됩니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;선형 이력이 보장하는 것&lt;/strong&gt;:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;추적 가능성&lt;/strong&gt;: 모든 변경사항의 순서를 정확히 파악 가능&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Time Travel&lt;/strong&gt;: 특정 시점의 데이터 상태로 정확히 되돌아갈 수 있음&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;감사(Audit)&lt;/strong&gt;: 누가, 언제, 무엇을 변경했는지 추적 가능&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;디버깅&lt;/strong&gt;: 문제 발생 시 변경 이력을 따라가며 원인 파악 가능&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;선형 이력 예시&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-plaintext"&gt;v1.metadata.json (CREATE TABLE)
  ↓
v2.metadata.json (INSERT - 사용자 A)
  ↓
v3.metadata.json (MERGE - 사용자 B)
  ↓
v4.metadata.json (UPDATE - 사용자 A)
  ↓
v5.metadata.json (DELETE - 사용자 C)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;각 메타데이터 파일은 이전 상태(parent)를 참조하므로, 전체 이력을 재구성할 수 있습니다.&lt;/p&gt;&lt;h4&gt;2.4.11. 요약&lt;/h4&gt;&lt;p&gt;Apache Iceberg의 낙관적 동시성 제어는 다음과 같이 작동합니다:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;낙관적 작업&lt;/strong&gt;: 각 쓰기 작업은 현재 메타데이터를 기준으로 독립적으로 새 파일들을 생성&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;원자적 검증&lt;/strong&gt;: 커밋 시점에 Compare-And-Swap 연산으로 메타데이터 버전 확인&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;충돌 처리&lt;/strong&gt;: 버전이 변경되었다면 커밋 실패 후 최신 버전 기준으로 재시도&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;선형 이력 보장&lt;/strong&gt;: 모든 변경사항이 순차적으로 기록되어 데이터 무결성 유지&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;이러한 메커니즘 덕분에 여러 쓰기 작업이 동시에 발생하더라도 &lt;strong&gt;데이터 손실이나 덮어쓰기 없이 모든 변경사항이 안전하게 반영&lt;/strong&gt;됩니다. 이것이 바로 Apache Iceberg가 데이터 레이크에서 ACID 트랜잭션을 보장하는 핵심 원리입니다.&lt;/p&gt;&lt;hr&gt;&lt;h2&gt;3. 읽기 쿼리의 생명주기 (Reading Queries in Apache Iceberg)&lt;/h2&gt;&lt;p&gt;읽기 쿼리는 계층적 메타데이터를 활용하여 스캔해야 할 데이터 파일의 수를 최소화함으로써 높은 성능을 달성합니다.&lt;/p&gt;&lt;h4&gt;3.1. 일반 조회 (&lt;code&gt;SELECT&lt;/code&gt;)&lt;/h4&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;# Spark SQL/Dremio Sonar
SELECT *
FROM orders
WHERE order_ts BETWEEN '2023-01-01' AND '2023-01-31'&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;3.1.1. 1단계: 쿼리를 엔진으로 전송&lt;/h4&gt;&lt;p&gt;엔진이 메타데이터 파일을 기반으로 쿼리 계획을 시작합니다.&lt;/p&gt;&lt;h4&gt;3.1.2. 2단계: 카탈로그 조회&lt;/h4&gt;&lt;p&gt;현재 메타데이터 파일 경로를 요청합니다.&lt;/p&gt;&lt;p&gt;현재 메타데이터 파일: &lt;code&gt;/orders/metadata/v3.metadata.json&lt;/code&gt;&lt;/p&gt;&lt;h4&gt;3.1.3. 3단계: 메타데이터 파일 로드&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;테이블 스키마 확인 (내부 메모리 구조 준비)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;파티셔닝 방식 확인 (데이터 구성 방식 이해)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;current-snapshot-id 확인 (테이블의 현재 상태)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;현재 스냅샷 기준으로 매니페스트 리스트 파일 경로 찾기&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;3.1.4. 4단계: 매니페스트 리스트에서 정보 가져오기&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;snap-*.avro&lt;/code&gt; 파일 읽기&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;중요 정보:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;실제 데이터 파일 참조를 포함하는 매니페스트 파일 경로&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;추가/삭제된 데이터 파일 수, 파티션 통계 정보&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;partition-spec-id: 특정 스냅샷 작성에 사용된 파티션 방식&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;파티션별 통계: 매니페스트의 파티션 컬럼 상한/하한값&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;파일 프루닝을 위해 어떤 매니페스트 파일을 건너뛸지 결정&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;3.1.5. 5단계: 매니페스트 파일에서 정보 가져오기&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;프루닝되지 않은(쿼리와 관련된) 매니페스트 파일 열기&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;각 항목은 이 매니페스트 파일이 추적하는 데이터 파일을 나타냄&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;각 데이터 파일의 파티션 값을 쿼리 필터 값과 비교&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;파티션 값이 필터 값 범위와 일치하지 않으면 무시&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;통계 정보 수집: 각 컬럼의 상한/하한값, null 값 개수 등&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;관련없는 파일 건너뛰기&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;3.1.6. 6단계: 최종 결과 반환&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;파티셔닝 및 메트릭 기반 필터링(컬럼의 상한/하한값) 같은 최적화 기법으로 전체 테이블 스캔 방지&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;필요한 데이터 파일 읽기&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;사용자에게 레코드 반환&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/27/20261278_N86CTJD733N3bAJuoGY7.png" alt="Pasted image 20250928154257.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;hr&gt;&lt;h4&gt;3.2. 타임 트래블 쿼리 (Time-Travel Query)&lt;/h4&gt;&lt;p&gt;데이터베이스와 데이터 웨어하우스 세계에서 중요한 기능은 테이블의 특정 시점으로 돌아가 과거 데이터(변경되거나 삭제된 데이터)를 쿼리할 수 있는 능력입니다. Apache Iceberg는 데이터 레이크하우스 아키텍처에 유사한 time-travel 기능을 제공합니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;활용 사례&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;이전 분기 데이터 분석&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;실수로 삭제된 행 복원&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;분석 결과 재현&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Time-Travel 방법&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;타임스탬프 사용&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;snapshot id 사용&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;3.2.1. 테이블 히스토리 확인&lt;/h4&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;# Spark SQL
SELECT * FROM catalog.db.orders.history;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;결과 예시&lt;/strong&gt;:&lt;/p&gt;&lt;table style="min-width: 100px;"&gt;&lt;colgroup&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;col style="min-width: 25px;"&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;made_current_at&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;snapshot_id&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;parent_id&lt;/p&gt;&lt;/th&gt;&lt;th colspan="1" rowspan="1"&gt;&lt;p&gt;is_current_ancestor&lt;/p&gt;&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;2023-03-06 21:28:35.360&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;7327164675870333694&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;null&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;true&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;2023-03-07 20:45:08.914&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;8333017788700497002&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;7327164675870333694&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;true&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;2023-03-09 19:58:40.448&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;5139476312242609518&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;8333017788700497002&lt;/p&gt;&lt;/td&gt;&lt;td colspan="1" rowspan="1"&gt;&lt;p&gt;true&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;&lt;strong&gt;스냅샷 설명&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;첫 번째 스냅샷: CREATE 문 실행 후 생성&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;두 번째 스냅샷: INSERT 문으로 새 레코드 삽입 후 생성&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;세 번째 스냅샷: MERGE INTO 쿼리로 생성&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;3.2.2. Time-Travel 쿼리 예시&lt;/h4&gt;&lt;pre&gt;&lt;code class="language-sql"&gt;# 타임스탬프 사용 (Spark SQL)
SELECT * FROM orders
TIMESTAMP AS OF '2023-03-07 20:45:08.914'

# 스냅샷 ID 사용 (Spark SQL)
SELECT *
FROM orders
VERSION AS OF 8333017788700497002&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;중요 사항&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;정확한 타임스탬프 값을 제공하지 않으면 Iceberg는 지정된 값보다 오래된 스냅샷을 찾아 결과 반환&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;오래된 스냅샷이 없으면 예외 발생&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;3.2.3. 처리 과정&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;1. 쿼리를 엔진으로 전송&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;모든 SELECT 문과 마찬가지로 엔진으로 전송되어 파싱&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;테이블 메타데이터를 활용하여 쿼리 계획 시작&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;2. 카탈로그 확인&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;현재 메타데이터 파일 위치 요청&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;현재 메타데이터 파일 읽기 (&lt;code&gt;v3.metadata.json&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;3. 메타데이터 파일에서 정보 가져오기&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;현재 메타데이터 파일은 Iceberg 테이블에 대해 생성된 모든 스냅샷 추적 (메타데이터 유지 전략의 일부로 의도적으로 만료되지 않은 경우)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;사용 가능한 스냅샷 목록에서 time-travel 쿼리에 지정된 특정 스냅샷 결정 (타임스탬프 값 또는 스냅샷 ID 기반)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;테이블 스키마 및 파티셔닝 방식 확인 (파일 프루닝에 사용)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;해당 스냅샷의 매니페스트 리스트 경로 가져오기&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;4. 매니페스트 리스트에서 정보 가져오기&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;매니페스트 리스트 경로 기반으로 &lt;code&gt;.avro&lt;/code&gt; 파일 열기&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;중요 정보:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;실제 데이터 파일 참조를 포함하는 매니페스트 파일 경로&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;추가/삭제된 데이터 파일 수, 파티션 통계 정보&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;5. 매니페스트 파일에서 정보 가져오기&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;쿼리와 일치하는 매니페스트 파일 읽기&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;데이터 파일 경로 확인 (쿼리 레코드가 있는 파일 경로)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;각 데이터 파일을 확인하여 읽을지 여부 결정&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;통계 정보 수집&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;6. 최종 결과 반환&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;해당 데이터 파일 읽기&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;사용자에게 결과 반환&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/27/20261278_Bp8fHz5ymW4pS2rM5rHh.png" alt="Pasted image 20250928154310.png" style="object-fit: cover;"&gt;&lt;/figure&gt;</description><pubDate>Tue, 27 Jan 2026 08:28:40 +0900</pubDate><guid>http://blex.me/@mildsalmon/apache-iceberg-the-definitive-guide-lifecycle-of</guid></item><item><title>[Apache Iceberg - The Definitive Guide] The Architecture of Apache Iceberg</title><link>http://blex.me/@mildsalmon/apache-icebergthe-definitive-guide-the-architecture-of-apache-iceberg</link><description>&lt;h2&gt;Apache Iceberg 완벽 가이드: 2장 - 아파치 아이스버그 아키텍처&lt;/h2&gt;&lt;p&gt;이 장에서는 아파치 아이스버그 테이블의 아키텍처와 사양에 대해 심층적으로 다루며, 하이브(Hive) 테이블 형식의 내재된 문제들을 어떻게 해결하는지 설명합니다. 아이스버그 테이블은 세 가지 계층으로 구성됩니다: &lt;strong&gt;카탈로그 계층&lt;/strong&gt;, &lt;strong&gt;메타데이터 계층&lt;/strong&gt;, 그리고 &lt;strong&gt;데이터 계층&lt;/strong&gt;입니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612515_7IPsIqqgwjsAdcQHQoZ8.png" alt="Pasted image 20250928152346.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612516_n9m3XxZTP75AF6bOzO4b.png" alt="Pasted image 20260125160148.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;hr&gt;&lt;h2&gt;1. 데이터 계층 (The Data Layer)&lt;/h2&gt;&lt;p&gt;아파치 아이스버그 테이블의 데이터 계층은 &lt;strong&gt;실제 데이터를 저장하는 곳&lt;/strong&gt;이며, 주로 &lt;strong&gt;데이터 파일(datafiles)&lt;/strong&gt;과 &lt;strong&gt;삭제 파일(delete files)&lt;/strong&gt;로 구성됩니다. 이 계층은 사용자 쿼리에 필요한 데이터를 제공하며, 아이스버그 테이블 트리 구조의 &lt;strong&gt;리프(leaves)&lt;/strong&gt;를 이룹니다.&lt;/p&gt;&lt;h4&gt;1.1. 데이터 파일 (Datafiles)&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;주요 포맷&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;Apache Parquet, Apache ORC&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;데이터는 &lt;strong&gt;컬럼 지향(columnar)&lt;/strong&gt;으로 저장되어 대량의 레코드를 처리하는 쿼리에 효율적입니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;각 파일은 여러 &lt;strong&gt;로우 그룹(row groups)&lt;/strong&gt;을 가질 수 있으며, 각 로우 그룹은 컬럼별로 페이지(pages)로 나뉘어 저장됩니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;파일 내 통계 정보(예: 컬럼별 최소/최대 값)를 저장하여 쿼리 엔진이 불필요한 로우 그룹을 건너뛸 수 있도록 돕습니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612516_yffPkxgNsKYBX2cuNEIv.png" alt="Pasted image 20250928152412.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;hr&gt;&lt;h4&gt;1.2. 삭제 파일 (Delete Files)&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;주요 역할&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;데이터셋에서 논리적으로 삭제된 레코드를 추적합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;데이터 레이크 저장소의 &lt;strong&gt;불변성(immutability)&lt;/strong&gt; 원칙을 유지하기 위해, 파일을 직접 업데이트하는 대신 새로운 파일을 작성하는 방식(Copy-on-Write, Merge-on-Read)을 사용합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Merge-on-Read (MOR)&lt;/strong&gt; 전략을 사용하여 업데이트 및 삭제 작업을 수행할 때 사용됩니다. (Iceberg v2 형식에서만 지원됩니다).&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612516_m9ybLLglyx3iqinIsmjl.png" alt="Pasted image 20250928152429.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;1.2.1. 위치 삭제 파일 (Positional Delete Files)&lt;/h4&gt;&lt;p&gt;레코드의 정확한 위치(파일 경로 및 파일 내 레코드 번호)를 식별하여 삭제합니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;읽기 시 비용이 적습니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;쓰기 시 삭제 레코드의 위치를 파악하기 위해 파일을 읽어야 하는 비용이 발생합니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612516_p0CELIjUWSMu2lxlXmCv.png" alt="Pasted image 20250928152607.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;1.2.2. 등가 삭제 파일 (Equality Delete Files)&lt;/h4&gt;&lt;p&gt;레코드의 하나 이상의 필드 값으로 레코드를 식별하여 삭제합니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;특징&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;일반적으로 고유 식별자(기본 키)가 있는 경우에 적합합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;쓰기 시 비용이 없습니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;읽기 시 모든 레코드를 비교해야 하므로 읽기 시 비용이 더 큽니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612516_mmMi6u7mnhyJHEBZN8Gy.png" alt="Pasted image 20250928152634.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;1.2.3. 시퀀스 번호 활용&lt;/h4&gt;&lt;p&gt;삭제 파일이 적용될 때 시퀀스 번호(sequence numbers)를 사용하여 새로 추가된 레코드가 삭제 목록에 잘못 포함되지 않도록 보장합니다.&lt;/p&gt;&lt;hr&gt;&lt;h2&gt;2. 메타데이터 계층 (The Metadata Layer)&lt;/h2&gt;&lt;p&gt;메타데이터 계층은 아이스버그 테이블 아키텍처의 필수적인 부분으로, 테이블의 데이터 파일 및 관련 작업에 대한 모든 메타데이터 파일을 포함하는 &lt;strong&gt;트리 구조&lt;/strong&gt;입니다. 이 계층은 대규모 데이터셋을 효율적으로 관리하고 &lt;strong&gt;시간 여행(time travel)&lt;/strong&gt; 및 &lt;strong&gt;스키마 진화(schema evolution)&lt;/strong&gt;와 같은 핵심 기능을 가능하게 합니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612516_yqaTtJafrb7IYabhxorA.png" alt="Pasted image 20260125154829.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;2.1. 매니페스트 파일 (Manifest Files)&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;주요 역할&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;데이터 계층의 파일(데이터 파일 및 삭제 파일)과 각 파일에 대한 추가 세부 정보 및 통계(예: 컬럼의 최소/최대 값)를 추적합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;각 파일이 기록될 때 통계가 수집되므로, 하이브(Hive)와 달리 &lt;strong&gt;경량화된 통계 수집&lt;/strong&gt;이 가능하며 쿼리 성능에 더 나은 최신 정보를 제공합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;매니페스트 파일은 &lt;strong&gt;Avro 형식&lt;/strong&gt;으로 작성됩니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h4&gt;2.2. 매니페스트 목록 (Manifest Lists)&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;주요 역할&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;특정 시점의 아이스버그 테이블의 &lt;strong&gt;스냅샷(snapshot)&lt;/strong&gt;입니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;해당 시점의 테이블에 대한 모든 매니페스트 파일 목록과 파티션 컬럼의 상한 및 하한과 같은 매니페스트에 대한 통계를 포함합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;매니페스트 목록은 &lt;strong&gt;Avro 형식&lt;/strong&gt;으로 작성됩니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h4&gt;2.3. 메타데이터 파일 (Metadata Files)&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;주요 역할&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;특정 시점의 아이스버그 테이블에 대한 메타데이터를 저장합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;여기에는 테이블의 &lt;strong&gt;스키마&lt;/strong&gt;, &lt;strong&gt;파티션 정보&lt;/strong&gt;, &lt;strong&gt;스냅샷&lt;/strong&gt;, 그리고 &lt;strong&gt;현재 스냅샷&lt;/strong&gt;이 무엇인지에 대한 정보가 포함됩니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;핵심 특징&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;아이스버그 테이블에 변경이 있을 때마다 새로운 메타데이터 파일이 생성되고 &lt;strong&gt;원자적으로 최신 버전으로 등록&lt;/strong&gt;됩니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;이는 테이블 커밋의 &lt;strong&gt;선형 기록(linear history)&lt;/strong&gt;을 보장하고 동시 쓰기(concurrent writes) 시나리오를 지원합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;쿼리 엔진은 항상 테이블의 최신 버전을 볼 수 있습니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h4&gt;2.4. 퍼핀 파일 (Puffin Files)&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;주요 역할&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;데이터 파일 및 메타데이터 파일에 저장된 통계보다 더 고급 구조를 저장하여 특정 유형의 쿼리 성능을 향상시키는 데 사용됩니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;구조&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;파일에는 임의의 바이트 시퀀스인 &lt;strong&gt;블롭(blobs)&lt;/strong&gt; 세트와 블롭을 분석하는 데 필요한 관련 메타데이터가 포함됩니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;현재 지원&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;현재 Apache DataSketches 라이브러리의 &lt;strong&gt;Theta 스케치&lt;/strong&gt;만 지원하며, 이를 통해 컬럼의 근사 고유값 수를 계산하여 자원을 적게 사용하고 빠르게 연산할 수 있습니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h2&gt;3. 카탈로그 계층 (The Catalog)&lt;/h2&gt;&lt;p&gt;카탈로그는 아이스버그에서 테이블의 최신 메타데이터 파일 위치를 추적하는 &lt;strong&gt;중앙 위치&lt;/strong&gt;입니다. 이는 여러 사용자와 도구가 데이터에 효율적으로 상호 작용할 수 있도록 테이블에 대한 추상화(abstraction)를 제공하는 데 중요한 역할을 합니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612516_8nUhsvOwZYF6diUbQraE.png" alt="Pasted image 20260125155611.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;3.1. 주요 역할&lt;/h4&gt;&lt;p&gt;쿼리 엔진이 테이블의 현재 상태를 알기 위해 가장 먼저 상호 작용하는 구성 요소입니다.&lt;/p&gt;&lt;h4&gt;3.2. 핵심 요구 사항&lt;/h4&gt;&lt;p&gt;현재 메타데이터 포인터를 업데이트하기 위한 &lt;strong&gt;원자적(atomic) 작업&lt;/strong&gt;을 지원해야 합니다. 이는 모든 읽기 및 쓰기 작업이 특정 시점에 테이블의 동일한 상태를 보도록 보장하기 위함입니다.&lt;/p&gt;&lt;h4&gt;3.3. 다양한 백엔드 지원&lt;/h4&gt;&lt;p&gt;Hadoop 카탈로그(예: Amazon S3), Hive Metastore, AWS Glue, Project Nessie, REST, JDBC 등 다양한 시스템이 아이스버그 카탈로그로 사용될 수 있습니다.&lt;/p&gt;&lt;h4&gt;3.4. 저장 방식 차이&lt;/h4&gt;&lt;p&gt;각 카탈로그 구현은 현재 메타데이터 포인터를 다르게 저장합니다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt;:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;파일 시스템 기반 카탈로그&lt;/strong&gt;: &lt;code&gt;version-hint.txt&lt;/code&gt; 파일에 현재 메타데이터 파일의 버전 번호를 저장&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hive Metastore&lt;/strong&gt;: 테이블 속성 &lt;code&gt;location&lt;/code&gt;에 현재 메타데이터 파일의 위치를 저장&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;p&gt;아파치 아이스버그 아키텍처의 이러한 계층과 구성 요소는 하이브 테이블 형식의 문제점을 해결하고 데이터 레이크에서 ACID 트랜잭션, 시간 여행, 스키마 진화와 같은 고급 기능을 제공할 수 있게 합니다.&lt;/p&gt;&lt;hr&gt;&lt;h2&gt;99. 참고문헌&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;https://www.oreilly.com/library/view/apache-iceberg-the/9781098148614/&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;</description><pubDate>Sun, 25 Jan 2026 16:08:15 +0900</pubDate><guid>http://blex.me/@mildsalmon/apache-icebergthe-definitive-guide-the-architecture-of-apache-iceberg</guid></item><item><title>[Apache Iceberg - The Definitive Guide] Introduction to Apache Iceberg</title><link>http://blex.me/@mildsalmon/apache-icebergthe-definitive-guide-introduction-to</link><description>&lt;h2&gt;Apache Iceberg 완벽 가이드: 1장 - 아파치 아이스버그 소개&lt;/h2&gt;&lt;p&gt;이 장은 &lt;strong&gt;데이터 레이크하우스&lt;/strong&gt;의 역사적 맥락과 &lt;strong&gt;아파치 아이스버그&lt;/strong&gt;의 핵심 개념을 탐구합니다.&lt;/p&gt;&lt;hr&gt;&lt;h2&gt;1. 데이터 아키텍처의 발전 과정&lt;/h2&gt;&lt;h4&gt;1.1. 전통적인 관계형 데이터베이스 관리 시스템 (RDBMS)&lt;/h4&gt;&lt;p&gt;관계형 데이터베이스 관리 시스템(RDBMS)은 오랫동안 트랜잭션 데이터 기록을 위한 표준 옵션이었습니다. 예를 들어, PostgreSQL, MySQL, Microsoft SQL Server는 &lt;strong&gt;온라인 트랜잭션 처리(OLTP)&lt;/strong&gt; 워크로드에 최적화되어 있습니다.&lt;/p&gt;&lt;p&gt;이러한 시스템은 한 번에 하나 또는 소수의 데이터 행과 매우 빠르게 상호 작용하도록 설계 및 최적화되어 비즈니스의 일상적인 운영을 지원하는 데 적합합니다. 하지만 데이터 양이 충분히 커지면 대규모 분석 쿼리(OLAP)를 수행할 때 상당한 성능 문제가 발생할 수 있습니다.&lt;/p&gt;&lt;hr&gt;&lt;h4&gt;1.2. OLAP Workloads&lt;/h4&gt;&lt;p&gt;현대적인 분석(OLAP) 워크로드를 지원하기 위해 설계된 시스템은 크게 6가지 핵심 기술 구성 요소로 이루어져 있습니다. 각 요소의 역할과 특징은 다음과 같습니다.&lt;/p&gt;&lt;h4&gt;1.2.1. 저장소 (Storage)&lt;/h4&gt;&lt;p&gt;대규모 데이터를 저장하기 위한 물리적 계층입니다. 로컬 파일 시스템(DAS), 분산 파일 시스템(HDFS), 또는 클라우드 제공업체의 &lt;strong&gt;객체 저장소(Amazon S3 등)&lt;/strong&gt;가 포함됩니다. 최근에는 방대한 데이터를 다룰 때 효율적인 &lt;strong&gt;컬럼 지향(Columnar) 방식&lt;/strong&gt;이 많이 채택되고 있습니다.&lt;/p&gt;&lt;h4&gt;1.2.2. 파일 포맷 (File Format)&lt;/h4&gt;&lt;p&gt;로우 데이터가 파일 내에 조직되는 방식으로, 압축 방식과 성능에 영향을 미칩니다. CSV, Avro와 같은 &lt;strong&gt;행 지향(Row-oriented)&lt;/strong&gt; 포맷은 적은 수의 레코드 처리에 유리하며, Parquet, ORC와 같은 &lt;strong&gt;열 지향(Column-oriented)&lt;/strong&gt; 포맷은 대규모 데이터 집계 처리에 더 적합합니다.&lt;/p&gt;&lt;h4&gt;1.2.3. 테이블 포맷 (Table Format)&lt;/h4&gt;&lt;p&gt;파일 포맷 위의 메타데이터 계층으로, 수많은 데이터 파일을 하나의 통합된 &lt;strong&gt;'테이블'&lt;/strong&gt;로 인식하게 만듭니다. 물리적 데이터 구조의 복잡성을 추상화하여 &lt;strong&gt;DML 작업(삽입, 수정, 삭제)&lt;/strong&gt;과 스키마 변경을 용이하게 하며, 데이터 작업에 대한 &lt;strong&gt;원자성과 일관성&lt;/strong&gt;을 보장합니다.&lt;/p&gt;&lt;h4&gt;1.2.4. 스토리지 엔진 (Storage Engine)&lt;/h4&gt;&lt;p&gt;테이블 포맷이 지정한 형태대로 데이터를 실제로 배치하고, 파일 및 데이터 구조를 최신 상태로 유지하는 시스템입니다. &lt;strong&gt;데이터의 물리적 최적화&lt;/strong&gt;, 인덱스 유지 관리, 불필요한 오래된 데이터를 정리하는 핵심 작업을 수행합니다.&lt;/p&gt;&lt;h4&gt;1.2.5. 카탈로그 (Catalog)&lt;/h4&gt;&lt;p&gt;메타데이터를 활용하여 필요한 데이터셋을 빠르게 찾을 수 있게 돕는 &lt;strong&gt;중앙 저장소&lt;/strong&gt;입니다. 컴퓨팅 엔진과 사용자가 테이블의 이름, 스키마, 저장 위치를 확인할 수 있는 창구 역할을 하며, Hive나 Project Nessie처럼 모든 시스템이 접근 가능한 &lt;strong&gt;개방형 카탈로그&lt;/strong&gt;도 존재합니다.&lt;/p&gt;&lt;h4&gt;1.2.6. 컴퓨팅 엔진 (Compute Engine)&lt;/h4&gt;&lt;p&gt;저장된 데이터를 처리하기 위해 사용자의 워크로드를 실제로 실행하는 요소입니다. 데이터 양과 연산 부하에 따라 하나 이상의 엔진을 사용할 수 있으며, 대규모 데이터 세트를 위해 &lt;strong&gt;분산 처리 방식인 MPP(Massively Parallel Processing)&lt;/strong&gt; 기반 엔진(Apache Spark, Dremio 등)이 주로 활용됩니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612515_LE6YILHMk1Vn1ef4k7xz.png" alt="Pasted image 20250815143315.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;hr&gt;&lt;h4&gt;1.3. 데이터 웨어하우스&lt;/h4&gt;&lt;p&gt;데이터 웨어하우스 또는 OLAP 데이터베이스는 운영 시스템, 애플리케이션 데이터베이스, 로그 등 다양한 소스에서 수집된 대량의 데이터를 저장할 수 있도록 지원하는 &lt;strong&gt;중앙 집중식 저장소&lt;/strong&gt;입니다.&lt;/p&gt;&lt;h4&gt;장점&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;다양한 소스에서 데이터를 저장하고 쿼리할 수 있어 &lt;strong&gt;단일 진실 공급원(single source of truth)&lt;/strong&gt; 역할을 합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;대량의 이력 데이터를 쿼리할 수 있어 분석 워크로드를 빠르게 실행할 수 있습니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;효과적인 데이터 거버넌스 정책을 제공하여 데이터 가용성, 사용성, 보안 정책 준수를 보장합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;데이터 레이아웃을 최적화하여 쿼리에 유리하게 구성합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;테이블에 기록된 데이터가 기술 스키마를 따르도록 보장합니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;단점&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;데이터가 &lt;strong&gt;벤더 고정(vendor-specific system)&lt;/strong&gt;되어 해당 웨어하우스의 컴퓨팅 엔진만 데이터를 사용할 수 있습니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;스토리지와 컴퓨팅 모두에서 &lt;strong&gt;비용이 비싸고&lt;/strong&gt;, 워크로드 증가에 따라 비용 관리가 어려워집니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;주로 &lt;strong&gt;정형 데이터만 지원&lt;/strong&gt;합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;머신러닝(ML)&lt;/strong&gt;과 같은 고급 분석 워크로드를 직접 실행하는 데 제한적입니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;스토리지와 컴퓨팅 구성 요소가 &lt;strong&gt;밀접하게 결합&lt;/strong&gt;되어 있어 독립적인 확장이 어렵습니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612515_wB4VQ5JlXAebxNAFnMt9.png" alt="Pasted image 20250815143341.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;hr&gt;&lt;h4&gt;1.4. 데이터 레이크&lt;/h4&gt;&lt;p&gt;데이터 레이크는 저렴한 비용으로 대량의 다양한 데이터를 저장하기 위해 등장했습니다.&lt;/p&gt;&lt;h4&gt;장점&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;확장성&lt;/strong&gt;과 &lt;strong&gt;유연성&lt;/strong&gt;을 제공하여 정형, 반정형, 비정형 데이터를 저장할 수 있습니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;데이터를 변환하지 않고 원시 형태로 저장하여 유연성을 높입니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;데이터가 특정 벤더에 고정되지 않습니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;데이터 웨어하우스에 비해 비용 효율적입니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;단점&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;강력한 트랜잭션 무결성(ACID)&lt;/strong&gt;이 부족하여 금융 시스템과 같은 중요한 애플리케이션에는 적합하지 않았습니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;파일 수준 변경의 비효율성&lt;/strong&gt;, 여러 파티션에 대한 원자적 업데이트 불가능, 동시 업데이트 지원 부족, 과도한 파일 목록화로 인한 쿼리 계획 지연과 같은 문제가 있었습니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;데이터 레이크에는 스토리지 엔진 기능에 대한 서비스를 제공하지 않으며, 데이터는 종종 최적화되지 않은 채로 남습니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612515_mYUZk7A2m9d33dMLvS4Z.png" alt="Pasted image 20250815143355.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;hr&gt;&lt;h4&gt;1.5. 데이터 레이크하우스&lt;/h4&gt;&lt;p&gt;데이터 레이크하우스 아키텍처는 데이터 웨어하우스와 데이터 레이크의 장점을 결합하여 데이터 레이크의 &lt;strong&gt;확장성과 비용 효율성&lt;/strong&gt;을 유지하면서 &lt;strong&gt;ACID 트랜잭션&lt;/strong&gt;, 성능, 그리고 확장성과 같은 데이터 웨어하우스 기능을 제공합니다. 이는 &lt;strong&gt;아파치 아이스버그&lt;/strong&gt;와 같은 &lt;strong&gt;개방형 table format&lt;/strong&gt; 위에 구축되어 벤더 종속성을 피합니다.&lt;/p&gt;&lt;h4&gt;가치 제안&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;복사본 감소 = 데이터 불일치 감소&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;ACID 보장 및 향상된 성능으로 인해 일반적으로 데이터 웨어하우스에서 수행하던 업데이트 및 데이터 조작 작업을 데이터 레이크하우스로 옮길 수 있어 비용을 절감하고 데이터 이동을 줄입니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;이력 데이터 스냅샷 = 실수를 두려워하지 않음&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;데이터 레이크하우스 Table Format은 이력 데이터 스냅샷을 유지하여 테이블을 이전 스냅샷으로 쿼리하고 복원할 수 있습니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;저렴한 아키텍처 = 비즈니스 가치&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;데이터 레이크하우스는 수익 증대뿐만 아니라 비용 절감에도 도움이 됩니다. 데이터 중복을 피하고, 추가 ETL 작업으로 인한 컴퓨팅 비용을 피하며, 기존 데이터 웨어하우스 요율에 비해 스토리지 및 컴퓨팅 비용이 저렴합니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;개방형 아키텍처 = 안심&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;아파치 아이스버그와 같은 &lt;strong&gt;개방형 형식&lt;/strong&gt;을 기반으로 구축되어 벤더 고정을 피하고 다양한 도구가 데이터를 읽고 쓸 수 있도록 합니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612515_Ivm2JQN6QviLoGCUV6Nz.png" alt="Pasted image 20250815143517.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;hr&gt;&lt;h2&gt;2. Table Format이란?&lt;/h2&gt;&lt;p&gt;Table Format은 데이터 세트의 파일을 하나의 &lt;strong&gt;통합된 "테이블"로 구성하는 방법&lt;/strong&gt;입니다. 사용자 관점에서는 "이 테이블에 어떤 데이터가 있는가?"라는 질문에 대한 답으로 정의될 수 있습니다. 주요 목적은 사용자 및 도구가 기본 데이터와 효율적으로 상호 작용할 수 있도록 테이블에 대한 &lt;strong&gt;추상화(abstraction)&lt;/strong&gt;를 제공하는 것입니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612515_JjWA3kzWvitaJCXUsE7l.png" alt="Pasted image 20250815143822.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;hr&gt;&lt;h4&gt;2.1. 하이브 (Hive): 초기 Table Format&lt;/h4&gt;&lt;p&gt;하이브는 2009년 페이스북에서 개발한 프레임워크로, 하둡 데이터 레이크에서 SQL을 작성하여 분석을 쉽게 할 수 있도록 했습니다. 하이브 Table Format은 특정 디렉터리(또는 객체 스토리지의 접두사) 내의 모든 파일을 하나의 테이블로 정의하고, &lt;strong&gt;하이브 메타스토어(Hive Metastore)&lt;/strong&gt;를 통해 테이블 경로를 추적했습니다.&lt;/p&gt;&lt;h4&gt;장점&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;파티셔닝&lt;/strong&gt; 및 버킷팅과 같은 기술을 통해 전체 테이블 스캔보다 효율적인 쿼리 패턴을 가능하게 했습니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;파일 형식에 구애받지 않아(file format agnostic)&lt;/strong&gt; Apache Parquet와 같은 더 나은 파일 형식을 사용할 수 있었습니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;하이브 메타스토어에서 나열된 디렉터리의 원자적 스왑을 통해 &lt;strong&gt;단일 파티션에 대한 원자적 변경&lt;/strong&gt;이 가능했습니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;시간이 지남에 따라 사실상 표준이 되어 대부분의 데이터 도구에서 작동했습니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;단점&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;파일 수준 변경이 비효율적이었습니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;여러 파티션에 걸친 원자적 업데이트를 지원하지 않았습니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;파티션 열이 다른 열에서 파생되는 경우가 많아 사용자가 파티션 열을 명시적으로 필터링하지 않으면 전체 테이블 스캔으로 이어질 수 있었습니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;테이블 통계는 비동기식 작업으로 수집되어 최신 정보가 아니거나 아예 없는 경우가 많아 쿼리 엔진이 쿼리를 최적화하기 어려웠습니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;객체 스토리지에서 동일한 접두사에 대한 요청을 스로틀링하는 경우가 많아, 단일 파티션에 파일이 많은 테이블에서 성능 문제가 발생할 수 있었습니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2025/8/30/202583014_gV3k7QqvA4QuqeXH1Xii.png" alt="Pasted image 20250815143834.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;hr&gt;&lt;h4&gt;2.2. 현대 데이터 레이크 Table Format&lt;/h4&gt;&lt;p&gt;하이브 Table Format의 한계를 해결하기 위해 새로운 세대의 Table Format이 등장했습니다. 아파치 아이스버그, 아파치 후디(Apache Hudi), 델타 레이크(Delta Lake)와 같은 현대 Table Format은 테이블 정의를 &lt;strong&gt;디렉터리 내용이 아닌 정규 파일 목록&lt;/strong&gt;으로 하는 접근 방식을 취했습니다. 이는 &lt;strong&gt;ACID 트랜잭션&lt;/strong&gt;, 타임 트래블(Time Travel)과 같은 기능을 가능하게 했습니다.&lt;/p&gt;&lt;h4&gt;핵심 이점&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;ACID 트랜잭션&lt;/strong&gt;을 허용합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;여러 작성기(multiple writers)에서 &lt;strong&gt;안전한 트랜잭션&lt;/strong&gt;을 가능하게 합니다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;쿼리 엔진이 스캔을 효율적으로 계획할 수 있도록 &lt;strong&gt;테이블 통계 및 메타데이터&lt;/strong&gt;를 더 잘 수집합니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h2&gt;3. 아파치 아이스버그 (Apache Iceberg)란?&lt;/h2&gt;&lt;p&gt;아파치 아이스버그는 넷플릭스에서 2017년에 개발하고 2018년에 오픈 소스화된 Table Format으로, 하이브의 성능, 일관성 및 기타 문제를 극복하기 위해 만들어졌습니다. 아이스버그는 테이블을 디렉터리 목록이 아닌 &lt;strong&gt;정규 파일 목록&lt;/strong&gt;으로 정의하는 데 중점을 둡니다.&lt;/p&gt;&lt;h4&gt;3.1. 핵심 목표&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;일관성 (Consistency)&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;다중 파티션에 걸친 업데이트 시에도 최종 사용자가 일관되지 않은 데이터를 경험하지 않도록 보장합니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;성능 (Performance)&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;과도한 파일 목록화를 피하고 필요한 파일만 스캔하여 쿼리 계획 및 실행을 가속화합니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;사용 용이성 (Ease to use)&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;숨겨진 파티셔닝(hidden partitioning)&lt;/strong&gt;을 통해 사용자가 테이블의 물리적 구조를 알 필요 없이 파티셔닝의 이점을 누릴 수 있도록 합니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;진화 가능성 (Evolvability)&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;스키마 및 파티셔닝 스키마를 안전하게 업데이트할 수 있도록 하여 전체 테이블을 다시 작성할 필요가 없습니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;확장성 (Scalability)&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;페타바이트 규모의 데이터에서도 모든 목표를 달성할 수 있도록 설계되었습니다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h2&gt;4. 아파치 아이스버그 아키텍처 및 주요 기능&lt;/h2&gt;&lt;p&gt;아파치 아이스버그는 &lt;strong&gt;메타데이터 트리(metadata tree)&lt;/strong&gt;를 사용하여 테이블의 파티셔닝, 정렬, 스키마 변화 등을 추적하며, 이는 세 가지 구성 요소(매니페스트 파일, 매니페스트 목록, 메타데이터 파일)로 이루어져 있습니다. &lt;strong&gt;카탈로그(Catalog)&lt;/strong&gt;는 테이블의 최신 메타데이터 파일 위치를 추적하는 중앙 위치입니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612515_dTgloXBTAy5WmgFY8jSW.png" alt="Pasted image 20250815144451.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;hr&gt;&lt;h4&gt;4.1. 주요 기능&lt;/h4&gt;&lt;h4&gt;4.1.1. ACID 트랜잭션&lt;/h4&gt;&lt;p&gt;낙관적 동시성 제어(optimistic concurrency control)를 통해 다중 읽기/쓰기 환경에서도 ACID 보장을 제공합니다.&lt;/p&gt;&lt;h4&gt;4.1.2. 파티션 진화 (Partition Evolution)&lt;/h4&gt;&lt;p&gt;테이블 전체를 다시 작성할 필요 없이 파티셔닝 방식을 언제든지 업데이트할 수 있습니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612515_A263johGkl3Q2ITPWtOU.png" alt="Pasted image 20250815144528.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;4.1.3. 숨겨진 파티셔닝 (Hidden Partitioning)&lt;/h4&gt;&lt;p&gt;사용자가 파티션 열을 명시적으로 필터링할 필요 없이 기본 열에 대한 쿼리에서도 파티셔닝의 이점을 얻을 수 있습니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612515_EFVBtOfjpJkJdxJjymQd.png" alt="Pasted image 20250815144553.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;4.1.4. 행 수준 테이블 작업 (Row-level table operations)&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;Copy-on-Write (COW)&lt;/strong&gt; 또는 &lt;strong&gt;Merge-on-Read (MOR)&lt;/strong&gt; 방식을 사용하여 행 수준 업데이트 및 삭제 작업을 최적화할 수 있습니다.&lt;/p&gt;&lt;h4&gt;4.1.5. 타임 트래블 (Time Travel)&lt;/h4&gt;&lt;p&gt;테이블의 과거 상태에 대한 불변 스냅샷을 제공하여 특정 시점의 데이터를 쿼리할 수 있습니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612515_FomQBfUxXhtvd9zckXjs.png" alt="Pasted image 20250815144605.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;4.1.6. 버전 롤백 (Version Rollback)&lt;/h4&gt;&lt;p&gt;테이블의 현재 상태를 이전 스냅샷으로 되돌릴 수 있습니다.&lt;/p&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2026/1/25/202612515_PuGQc139Xu6SVkSgDqld.png" alt="Pasted image 20250815144614.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;h4&gt;4.1.7. 스키마 진화 (Schema Evolution)&lt;/h4&gt;&lt;p&gt;열 추가/제거, 이름 변경, 데이터 타입 변경 등을 안전하게 수행할 수 있습니다.&lt;/p&gt;&lt;hr&gt;&lt;p&gt;아파치 아이스버그는 이러한 기능들을 통해 데이터 레이크에서 고성능과 신뢰성을 제공하며 데이터 레이크하우스 아키텍처의 핵심 기술이 됩니다.&lt;/p&gt;&lt;hr&gt;&lt;h2&gt;99. 참고문헌&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;https://www.oreilly.com/library/view/apache-iceberg-the/9781098148614/&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;</description><pubDate>Sun, 25 Jan 2026 15:16:52 +0900</pubDate><guid>http://blex.me/@mildsalmon/apache-icebergthe-definitive-guide-introduction-to</guid></item><item><title>2025년 연말회고 - 하루하루는 성실하게, 인생 전체는 되는대로</title><link>http://blex.me/@mildsalmon/2025%EB%85%84-%EC%97%B0%EB%A7%90%ED%9A%8C%EA%B3%A0-%EA%B1%B0%EB%B4%90-%ED%9A%8C%EA%B3%A0%ED%95%98%EA%B8%B8-%EC%9E%98%ED%96%88%EC%A7%80</link><description>&lt;h1&gt;1. 남들이 정해준 답, 내가 선택하는 답&lt;/h1&gt;&lt;h3&gt;1.0. 네 번째 맺음말을 시작하며&lt;/h3&gt;&lt;p&gt;벌써 네 번째 연말 회고다. 2022년의 첫 취업 설렘과 2023년의 상실의 아픔, 2024년의 쉼 없이 달리는 기차 같았던 시간들을 지나 여기까지 왔다. 매년 이맘때마다 느끼지만 상반기의 기억은 희미하다. 그래도 매월 회고를 작성해둔 덕분에 지난 열두달의 궤적을 되돌아보기에는 부족함이 없다. 그래도 내년부터는 반기에 한 번씩 회고를 엮어보려고 한다.&lt;/p&gt;&lt;h3&gt;1.1. 타자의 욕망을 욕망하던 꼭두각시&lt;/h3&gt;&lt;p&gt;우리 사회에는 소위 &lt;strong&gt;'표준 성공 공식'&lt;/strong&gt;이라 불리는 정답지가 존재한다. 인서울 대학, 대기업, 서울 아파트, 좋은 자동차, 적절한 시기에 하는 결혼, 자식을 잘 키우는 것 등. 나는 이 공식과 동떨어진 삶을 지향한다고 자부해 왔지만, 부동산 가격이 들썩이자 나도 모르게 조바심이 났다. 영혼까지 끌어모은 돈으로 서울 아파트를 구매할 수 있을지 가늠해보던 순간, 어디선가 스치듯 들었던 &lt;strong&gt;"인간은 타자의 욕망을 욕망한다."&lt;/strong&gt;는 자크 라캉의 문장이 떠올랐다.&lt;/p&gt;&lt;p&gt;결국 나도 내가 진정으로 원하는 것이 아닌 남들이 정해둔 &lt;strong&gt;'정답'&lt;/strong&gt;을 쫓고 있다는 것을 깨달았다. 강릉의 바다가 보이는 곳에서 느릿하게 살고 싶은 마음은 &lt;strong&gt;'비합리적'&lt;/strong&gt;이라는 타인의 잣대에 밀려났다. 나의 취향과 색깔이 지워진 채 정답만을 쫓는 삶은 결국 꼭두각시 인형의 삶과 다를 바 없지 않을까? 화폐 가치 하락을 방어하는 &lt;strong&gt;'합리적 선택'&lt;/strong&gt;이 과연 내가 지향하는 &lt;strong&gt;'삶의 본질'&lt;/strong&gt;보다 우선일 수 있을까?&lt;/p&gt;&lt;h3&gt;1.2. 오마카세의 교훈: 점이 아닌 선으로 채우는 삶&lt;/h3&gt;&lt;p&gt;과정보다 결과에 집착하던 나의 모습은 대학 시절 무분별하게 즐기던 오마카세의 기억과 닿아있다. 일용직 노가다로 번 돈을 털어 10만원이 넘는 한 끼를 먹었지만, 지금 나에게 남은 기억은 &lt;strong&gt;'맛있게 먹었다'&lt;/strong&gt;는 결과뿐이다. 어떤 생선이 어떤 방식으로 숙성되었는지, 내 취향에 맞는 생선은 무엇인지 고민하는 &lt;strong&gt;'과정'&lt;/strong&gt;은 없었다. 그저 셰프가 주는 대로 입에 넣기 바빴던 그 시절은, 목적이라는 &lt;strong&gt;'점'&lt;/strong&gt;을 찍기 위해 달려가느라 삶의 &lt;strong&gt;'선'&lt;/strong&gt;을 놓치고 있는 지금의 내 모습과 묘하게 겹쳐 보인다.&lt;/p&gt;&lt;p&gt;삶을 성공이라는 &lt;strong&gt;'점'&lt;/strong&gt;을 찍기 위해 달려가는 레이스라 생각했던 적이 있다. 하지만 삶의 &lt;strong&gt;본질&lt;/strong&gt;은 그 점들을 잇는 선의 궤적이며, 그 선들이 겹겹이 쌓여 만들어지는 면의 확장이다. 결과라는 점에 매몰되기보다 &lt;strong&gt;과정에서의 몰입&lt;/strong&gt;으로 나를 채워나가는 태도.&amp;nbsp;어떤 외부의 충격에도 나를 무너뜨리지 않는 이 단단한 마음가짐이야말로, 내가 그토록 추구하던 안티프레질한 삶의 본질일 것이다.&lt;/p&gt;&lt;h3&gt;1.3. 2025년의 나침반: 하루하루는 성실하게, 인생 전체는 되는대로&lt;/h3&gt;&lt;p&gt;나는 무엇을 위해 그토록 많은 돈을 추구했을까? 결국 그 돈으로 사고 싶었던 것은 &lt;strong&gt;자유&lt;/strong&gt;와 &lt;strong&gt;몰입할 수 있는 환경&lt;/strong&gt;이었다. 하지만 아이러니하게도 내가 진짜 추구하는 가치들은 돈으로 살 수 없는 것들이었다.&lt;/p&gt;&lt;p&gt;올해의 나는 &lt;strong&gt;'긴 터널'&lt;/strong&gt; 속에 있었다. 깜깜한 어둠 속에서 &lt;strong&gt;우울&lt;/strong&gt;과 &lt;strong&gt;번아웃&lt;/strong&gt;이라는 안개를 만났지만, 반대편에 분명 구멍이 뚫려 있다는 믿음으로 걸어왔다. 이제는 타인이 설계한 길에서 벗어나 오롯이 내 의지로 &lt;strong&gt;나만의 선&lt;/strong&gt;을 그어보려 한다. 내 시야를 흐리는 타인의 소음에서 고개를 돌려, 오직 나라는 본질이 가리키는 방향에만 나의 모든 감각을 집중할 것이다.&lt;/p&gt;&lt;h1&gt;2. 변화의 기록: 작년보다 더 깊고 넓게&lt;/h1&gt;&lt;h3&gt;2.1. [기술 및 커리어] 조금 더 깊게&lt;/h3&gt;&lt;h5&gt;2.1.1. Spark&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;[25년 기록] ✅ Spark를 사용하면서 발생한 Skew를 문제를 고민하고 해결함.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] Spark를 면접때 대답할 수 있을 정도로 코어 컨셉 공부하기&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h5&gt;2.1.2. Databricks&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;[25년 기록] ✅ Databricks를 Private Link로 연결하고 테라폼으로 IaC&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] Databricks에서 사용할 Dimensional Modeling을 개발하여 완성하기&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h5&gt;2.1.3. API Server 개발&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;[25년 기록] ✅ OOP를 정말 깊게 고민하며 공통 채번 서비스 개발&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] Data 관련 문제를 해결하기 위해 필요한 Backend 개발 작업들을 더 하기&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h5&gt;2.1.4. 이력서&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;[25년 기록] ✅ 이력서의 가독성을 올리기 위해 여러 시도를 했다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;2.2. [공간 및 이동] 역마살&lt;/h3&gt;&lt;h5&gt;2.2.1. 자동차&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;[25년 기록] ✅ 중고차 구매 대신 단기 렌트를 선택 (1년동안 1달 정도 빌려서 4500km 정도 운전)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 기록] 단기 렌트로 전기차를 빌려서 타보기&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h5&gt;2.2.2. 거주 지역의 확장&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;[25년 기록] ✅ 강원도 영진에서 2주, 주문진에서 1주 살기 실천&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] 거제, 제주 지역에서도 몇 주 거주해보기&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] 마음이 편안해지는 지역에서 자취하기&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;2.3. [경제 및 재테크] 안티프레질한 구조&lt;/h3&gt;&lt;h5&gt;2.3.1. 부동산&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;[25년 기록] ❌ 임장 대신 동네 탐방을 계획했지만 하지 못함.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] 흥미 생기는 지역에서 1년 정도 자취를 하면서 그 동네 탐방하기&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h5&gt;2.3.2. 패시브 인컴&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;[25년 기록] ❌ 새로 투자한 것이 없다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] 당분간은 계획이 없다.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;2.4. [자기 관리 및 철학] 꾸준하게&lt;/h3&gt;&lt;h5&gt;2.4.1. 철학적 사고&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;[25년 기록] ✅ 소유냐 존재냐, 안티프레질 등의 독서를 통해 나에 대해 더 깊이 이해하는 계기가 됨&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] 장바구니에 쌓아둔 철학 관련 책을 더 구매해서 읽어보기&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h5&gt;2.4.2. 루틴&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;[25년 기록] ✅ 거의 매주 목욕탕에서 사우나를 하면서 명상을 함&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[25년 기록] ❗️ 상반기에 사무실로 출근하는 날은 7시 ~ 9시, 18시 ~ 20시에 책을 읽거나 공부를 함. 하반기에는 거의 하지 못함.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[25년 기록] ❗️ 상반기에는 주에 몇 번씩 하와이안 음식을 먹었으나 하반기에는 거의 하지 못함&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] 사우나 명상 루틴 지키기&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] 평일에 12시간 ~ 20시간 책 읽거나 공부하기&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] 주 1회 이상은 하와이안 음식 먹기&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] 운동을 루틴으로 만들기&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] 7시간 이상 잠자기 (21시 30분 ~ 4시 30분)&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h5&gt;2.4.3. 운동&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;[25년 기록] ❗️ 여름 새벽에 단거리 달리기를 꾸준히 하다가 가을이 되자 거의 하지 않음&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] 어떻게 해서든 매주 10km 달리기 (하루 30분 정도 유산소 운동하기)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] 클라이밍을 꾸준히 해보기&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h5&gt;2.4.4. 독서&lt;/h5&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;[25년 기록] ✅ 미뤄두던 어려운 책들을 포함하여 14권 읽었다.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;[26년 예정] 기술서적들을 좀 더 많이 읽고 정리하고 소화시키기&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h1&gt;3. 본질을 향한 궤적: 시스템으로 구축하는 안티프레질한 삶&lt;/h1&gt;&lt;h3&gt;3.1. [업무 및 기술 성과] 엔지니어링의 정밀함과 도구의 재해석&lt;/h3&gt;&lt;p&gt;올해는&amp;nbsp;&lt;code&gt;데이터 신뢰성&lt;/code&gt;을 보장하기 위해 그 어느 때보다 깊게 고민했다. 파편화된 업스트림 테이블을 일관되게 관리하고 단일 진실 공급원(&lt;strong&gt;SSOT&lt;/strong&gt;)을 확보하기 위해 dbt를 전격 도입했다. 단순히 정규화된 테이블을 복제해 JOIN 하던 기존 방식에서 더 나아가 가상자산 입출고 Fact Modeling 같은&amp;nbsp;&lt;strong&gt;Dimensional Modeling&lt;/strong&gt;을 수행했다. 덕분에 쿼리 작성자가 복잡한 비즈니스 히스토리를 일일이 챙길 필요와 엣지 케이스나 중복 키 문제에서 자유로울 수 있게 되었다. 결과적으로 누구나 믿고 쓸 수 있는 데이터 기반을 마련했다. 이러한 모델링 과정은 파편화된 로직을 응집시켜 어떤 방식으로 조회해도 동일한 결과가 도출되는&amp;nbsp;&lt;strong&gt;데이터 일관성&lt;/strong&gt;을 확보하는 결정적인 계기가 되었다.&lt;/p&gt;&lt;p&gt;인프라 설계 측면에서는 &lt;strong&gt;'관심사의 분리'&lt;/strong&gt;에 집중했다. Airflow에 dbt를 직접 설치하기보다 별도의 ECS Task를 호출하는 방식을 택했는데, 이는 라이브러리 의존성 문제를 방지하고 &lt;strong&gt;'스케줄러'&lt;/strong&gt;와 &lt;strong&gt;'트랜스폼'&lt;/strong&gt;의 역할을 명확히 나누고 싶었기 때문이다. 이렇게 분리하니 Static Data인 Seed 파일만 업데이트될 경우 Jenkins를 통해 Airflow를 경유하지 않고 독립적으로 배포할 수 있는 유연함까지 챙길 수 있었다. 역할 단위로 인프라를 쪼개어 관리하는 것이 변화에 기민하게 대응할 수 있는&amp;nbsp;&lt;strong&gt;설계의 유연성&lt;/strong&gt;&amp;nbsp;측면에서 얼마나 큰 이점을 주는지 다시금 확신했다. 기술적 결합도를 낮추는 선택이 결국 운영의 제약을 풀고 더 자유로운 배포 환경을 만들어준다는 것을 체감한 순간이었다.&lt;/p&gt;&lt;p&gt;Databricks 인프라 구축은 &lt;strong&gt;Private Link&lt;/strong&gt;라는 까다로운 보안 요구사항과 Git Proxy 설정 등 험난한 네트워크 환경을 극복해야 하는 과정이었다. 초기에는 Git 연결을 위해 프록시 설정을 타협하기도 했지만, 결과적으로 &lt;strong&gt;Asset Bundle&lt;/strong&gt;을 활용한 배포 체계를 갖추면서 Git 연결에 의존하지 않고도 유연하게 대응할 수 있는 환경을 완성했다. 특히 &lt;strong&gt;DLT 파이프라인&lt;/strong&gt;에서 &lt;strong&gt;Materialized View&lt;/strong&gt;가 자꾸 &lt;strong&gt;Full Recompute&lt;/strong&gt; 되어 물리 데이터가 중복으로 쌓이는 문제가 발생했을 때, 원인이 Static Data를 Delta Table로 생성했기 때문임을 밝혀내고 이를 MV로 변경하여 &lt;strong&gt;Incremental Update&lt;/strong&gt; 방식으로 전환하며 문제를 해결한 경험은 짜릿했다. 또한 기존에 복제하던 데이터 규모가 커지며 발생한 Spark Skew 문제를 Partition 재조정으로 해결하며, 데이터 엔지니어링의 기본기를 다시금 체감할 수 있었다.&lt;/p&gt;&lt;p&gt;UTMS API 서버에서는 보고용 데이터 식별 등을 위한 &lt;strong&gt;공통 채번 시스템 기능&lt;/strong&gt;을 새롭게 개발했다. 요구사항이 복잡하지 않고 호출 빈도가 낮아 &lt;strong&gt;MySQL의 Row Level Lock&lt;/strong&gt;만으로도 충분히 동시성 제어가 가능했지만, Persistence Layer가 무엇으로 바뀌더라도 비즈니스 로직이 견고하게 유지될 수 있도록 &lt;strong&gt;다형성을 고려한 깊이 있는 설계&lt;/strong&gt;를 시도했다. 이 과정에서 발생하는 &lt;strong&gt;‘의도된 오버 엔지니어링’&lt;/strong&gt;에 대해 팀 내 합의를 이끌어냈고, 최종적으로 &lt;strong&gt;MySQL과 Redis를 병행&lt;/strong&gt;하여 동시성을 제어하는 구조를 선택했다. 특히 Redis를 캐시가 아닌 단순 락(Lock) 목적으로만 사용했기에 고비용의 AWS Managed Redis(ElastiCache) 대신, Stateful 서비스인 Redis를 Stateless 환경인 Fargate(Compliance ECS Cluster)에 직접 띄워 사용하는 방식을 택했다. 이는 인프라 비용을 최적화하면서도 설계의 정밀함을 포기하지 않은, 엔지니어로서 매우 합리적인 타협이자 도전이었다.&lt;/p&gt;&lt;p&gt;이번 경험을 통해 기술이라는 도구를 &lt;strong&gt;목적에 따라 얼마나 유연하게 해석&lt;/strong&gt;할 수 있는지 다시금 체감했다. 특정 기술의 정석적인 사용법에만 목매기보다, 그 특성을 깊게 이해하고 상황에 맞춰 변주를 줄 때 엔지니어링의 재미와 효율이 동시에 발생한다는 것을 깨달았다. 이번 오버 엔지니어링은 단순히 코드를 복잡하게 만든 것이 아니라, 기술을 다르게 해석하고 적용해 보며 나만의 엔지니어링 관점을 확장하는 소중한 임상 시험이었다.&lt;/p&gt;&lt;h3&gt;3.3. [개인 성장 및 철학] 안티프레질(Antifragile)한 삶의 설계&lt;/h3&gt;&lt;p&gt;과거부터 인생은 점이 아닌 선이고 면이라는 생각을 늘 해왔지만, 나 역시 마음 한구석에서는 구체적인 목적지라는 &lt;strong&gt;'점'&lt;/strong&gt;에 도달하기 위해 아등바등 살아왔음을 인정한다. 지금까지의 삶의 관성을 한순간에 바꾸기는 어렵겠지만, 이제는 목표보다 과정이라는 &lt;strong&gt;'선'&lt;/strong&gt;에 더 집중하며 살아가기로 했다. 그래서 지난 9월에는 10년 단위의 인생 로드맵을 작성했다. 나에게 가장 중요한 가치가 무엇인지 &lt;strong&gt;방향성&lt;/strong&gt;을 정하고, 그 길을 가기 위한 &lt;strong&gt;마일스톤&lt;/strong&gt;들을 그려보았다. 결과에 일희일비하며 스스로를 자책하기보다, 그 순간에 충실했다는 사실 자체에 의미를 두며 나만의 궤적을 그려 나갈 것이다.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;하루하루는 성실하게, 인생 전체는 되는대로 - 이동진&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;이러한 철학적 사고의 중심에는 《소유냐 존재냐》와 《안티프레질》이라는 두 권의 책이 있었다. 대한민국 사회가 규정한 표준 성공 공식 같은 &lt;strong&gt;'소유적 삶'&lt;/strong&gt;이 얼마나 취약(Fragile)한지 깨달았고, 회사의 월급이나 타이틀에 의존하지 않는 &lt;strong&gt;'존재론적 삶'&lt;/strong&gt;을 진지하게 고민하기 시작했다. 거대한 미래를 내 의지대로 통제할 수 있다고 믿는 오만이 나를 프레질하게 만든다는 것을 알기에, 오히려 &lt;strong&gt;"인생 전체는 되는대로"&lt;/strong&gt; 흘러가게 두는 대범함이 필요함을 느꼈다. 그나마 통제할 수 있는 유일한 영역인 &lt;strong&gt;"하루하루의 성실함"&lt;/strong&gt;으로 삶의 밀도를 채워나가는 것, 그것이 외부의 충격에도 무너지지 않는 나만의 안티프레질을 구축하는 길이라 믿는다.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Show don't tell.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;결국 본질은 &lt;strong&gt;'Show, don't tell'&lt;/strong&gt;에 있다. 세상에는 자신이 어떤 사람인지 말로만 포장하려는 이들이 많지만, 진짜는 &lt;strong&gt;결과로 증명&lt;/strong&gt;해낸다. 자기를 포장하는 PR 능력도 중요하겠지만, &lt;strong&gt;본질에 대한 고민&lt;/strong&gt; 없이 주객이 전도되는 상황은 경계해야 한다. 타인에게 나를 설명하려 애쓰기보다, 묵묵히 내 실력을 닦고 본질에 집중하며 나라는 존재를 오롯이 드러내는 삶을 살고 싶다.&lt;/p&gt;&lt;h3&gt;3.5. [신체 건강 및 생활 습관] 생존을 위한 루틴 관리&lt;/h3&gt;&lt;p&gt;결국 정신의 붕괴는 &lt;strong&gt;체력의 부재&lt;/strong&gt;에서 왔음을 뼈저리게 인정한다. 체력이 고갈된 상태에서 환경만 바꿔 문제를 해결하려다 보니 상황은 더욱 악화되었다. 새벽 5시에 일어나 고요한 시간을 사수하고 아보카도 중심의 식단을 유지하던 루틴들도, 갑작스러운 스트레스 앞에서는 힘없이 무너져 내렸다. &lt;strong&gt;다정함조차 체력에서 나온다&lt;/strong&gt;는 말처럼, 바닥난 에너지를 억지로 쪼개 쓰다 보니 나도 모르게 주변에 날카로운 날을 세웠던 것 같다. 본질인 체력이 뒷받침되지 않으면 아무리 좋은 루틴도 지속될 수 없다는 사실을 깨달은 한 해였다.&lt;/p&gt;&lt;p&gt;상반기에는 출근 전후로 두 시간씩 독서와 공부에 몰입했으나, 하반기 업무 강도가 높아지며 그 자리를 업무가 대신하게 된 점은 큰 아쉬움으로 남는다. 여름철 반짝 이어갔던 새벽 러닝 역시 계절의 변화와 함께 무너지는 과정을 보며, 여전히 &lt;strong&gt;'자신을 돌보는 법'&lt;/strong&gt;에 서투르다는 것을 확인했다. 이러한 반성을 토대로 2026년에는 매주 10km 달리기와 클라이밍을 루틴으로 정착시켜, 외부 환경에 휘둘리지 않는 기초 체력을 반드시 확보할 계획이다.&lt;/p&gt;&lt;p&gt;디지털 환경에서 몰입을 방해하는 요소들을 걷어내기 위한 &lt;strong&gt;'디지털 디톡스'&lt;/strong&gt;도 시도했다. 유튜브 추천 알고리즘을 차단해 맑은 정신을 유지하려 노력했지만, 시스템이 아닌 개인의 의지에만 의존하다 보니 적막함이 찾아올 때면 다시 알고리즘을 활성화하는 한계를 발견했다. 2026년에는 이를 시스템적으로 더 견고하게 보완하고, 7시간 이상의 수면 시간을 반드시 확보하려 한다. 충분한 수면과 꾸준한 운동, 그리고 매일 2~4시간의 공부 시간을 사수하여 체력이 내 정신을 붙잡고 늘어지지 않는 건강한 삶의 구조를 만들어 나갈 것이다.&lt;/p&gt;&lt;h1&gt;4. 2026년의 밑그림: 본질을 지탱하는 견고한 요새 구축&lt;/h1&gt;&lt;p&gt;2026년은 &lt;strong&gt;'점'&lt;/strong&gt;을 향해 달려가는 관성을 멈추고, 나만의 &lt;strong&gt;'선'&lt;/strong&gt;과 &lt;strong&gt;'면'&lt;/strong&gt;을 더욱 단단하게 다지는 시간이 될 것이다. 지난 한 해 긴 터널을 지나며 깨달은 것은, 삶의 다정함과 예리한 통찰은 결국 &lt;strong&gt;흔들리지 않는 본질&lt;/strong&gt;에서 나온다는 사실이다. 내 의지가 외부의 거대한 우연 앞에서 얼마나 무력한지 목격했기에, 이제는 막연한 희망보다 &lt;strong&gt;견고한 시스템&lt;/strong&gt;에 의지하려 한다. 외부의 충격에 쉽게 부서지지 않는 안티프레질한 요새를 짓는 것, 그것이 내가 2026년에 완수해야 할 가장 중요한 과제다.&lt;/p&gt;&lt;p&gt;거창한 말로 나를 설명하기보다 묵묵히 결과로 증명하는 &lt;strong&gt;'Show, don't tell'&lt;/strong&gt;의 정신을 삶의 중심에 둘 것이다. 타인의 속도에 휩쓸려 에너지를 낭비하지 않고, 오직 나만이 견딜 수 있는 나만의 이상함을 긍정하며 묵묵히 내 길을 걸어갈 것이다. 타인에 대한 기대를 내려놓음으로써 얻게 된 평온함을 유지하고, 그 빈자리를 나를 향한 깊은 이해와 돌봄으로 채우려 한다. 2026년의 깃발은 성취의 기록이 아니라, 내가 어떤 환경에서도 나 자신으로 온전히 서 있음을 보여주는 자존의 이정표가 될 것이다.&lt;/p&gt;&lt;p&gt;나중을 위해 정리해보면..&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;시드 자금 확보:&lt;/strong&gt;&amp;nbsp;현실의 무게를 견뎌낼 수 있는 압도적인 시드 자금을 모으는 데 집중하기.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;자립의 터전 마련:&lt;/strong&gt;&amp;nbsp;2026년 자취를 시작하며, 타인에게 의지하지 않고 나 홀로 오롯이 서 있을 수 있는 나만의 안식처 구축하기.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;엔지니어링 자산화:&lt;/strong&gt;&amp;nbsp;어떻게 하면 더 ADR과 같은 문서를 잘 작성할 수 있을지 고민하고 AI를 활용할 적절한 방법 고민하기.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;신체적 역치 높이기:&lt;/strong&gt;&amp;nbsp;매일의 유산소와 클라이밍을 통해, 정신이 무너질 때 끝까지 나를 붙잡아줄 수 있는 신체적 방어선 구축하기.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;커리어 리포지셔닝:&lt;/strong&gt;&amp;nbsp;2025년의 성과를 바탕으로 이력서를 업데이트하고, 나의 성장 욕구와 동료들의 지향점이 일치할 수 있는 최적의 환경을 모색하기.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;[루틴]&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;[수면]&lt;/strong&gt;&amp;nbsp;7시간 이상의 수면 시간을 반드시 확보하여 맑은 정신 유지하기&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;[미라클 모닝]&lt;/strong&gt;&amp;nbsp;새벽 5시 이전에 일어나 고요한 시간 속에 공부와 독서에 몰입하기 (출근 전 2시간)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;[운동]&lt;/strong&gt;&amp;nbsp;매일 30분 이상의 유산소 운동 또는 클라이밍 즐기기&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;[식단]&lt;/strong&gt;&amp;nbsp;주 며칠은 아보카도 중심의 하와이안 식단으로 몸 정화하기&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;[디톡스]&lt;/strong&gt;&amp;nbsp;유튜브 추천 알고리즘 차단을 시스템적으로 유지하여 도파민 관리하기&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;[성장]&lt;/strong&gt;&amp;nbsp;퇴근 후 2시간은 세상의 소음에서 벗어나 나만의 공부 시간 갖기&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;</description><pubDate>Mon, 29 Dec 2025 09:42:46 +0900</pubDate><guid>http://blex.me/@mildsalmon/2025%EB%85%84-%EC%97%B0%EB%A7%90%ED%9A%8C%EA%B3%A0-%EA%B1%B0%EB%B4%90-%ED%9A%8C%EA%B3%A0%ED%95%98%EA%B8%B8-%EC%9E%98%ED%96%88%EC%A7%80</guid></item><item><title>2025년도 읽은 것 중 인상적인 것들</title><link>http://blex.me/@mildsalmon/2025%EB%85%84%EB%8F%84%EC%97%90-%EC%9D%BD%EC%97%88%EB%8D%98-%EA%B2%83%EB%93%A4</link><description>&lt;blockquote&gt;&lt;p&gt;마크다운 포멧이 안맞아서 글이 이상하게 보이는데… 뭐 어쩔 수 없지&lt;/p&gt;&lt;/blockquote&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;똑똑하고 부지런한 사람과 일을 할 수 있는건 엄청난 행운 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223713038638"&gt;https://blog.naver.com/travis88/223713038638&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;쉬운 결정보다 나은 결정을 하자 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223721273510"&gt;https://blog.naver.com/lhd1371/223721273510&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;figure style="text-align: center; display: flex; justify-content: center; flex-direction: column; align-items: center;"&gt;&lt;img src="https://blex.me/resources/media/images/content/2025/12/22/202512229_z2HbJk41cyUp102z9tUt.png" alt="Pasted image 20251219101030.png" style="object-fit: cover;"&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;2. 쉬운 결정이 아니라 나은 결정을 하자.&lt;/p&gt;&lt;p&gt;1) 가장 첫번째 드는 생각, 선택지는 보류한다.&lt;/p&gt;&lt;p&gt;- : 가장 첫번째로 든 생각은 보통 회피/도피성 또는 쉬운 선택인 경우가 많기 때문.&lt;/p&gt;&lt;p&gt;2) 되돌림이 가능한(나은) 선택인가?&lt;/p&gt;&lt;p&gt;- : 퇴사보다는 휴직, 휴직보다는 단기 휴가 순으로 업무공백이 적고 다녀온후 되돌림이 나은 선택지겠죠. 극단값 말고도 중간 선택지 여러개를 만들어본 후 비교.&lt;/p&gt;&lt;p&gt;3) '해볼만큼 했다' 라는 기준을 세울수 있는 선택인가.&lt;/p&gt;&lt;p&gt;- : 감정적 (정성적)인 부분보다 계량이 가능한 정량적인 노력이 가능한 기준이 있는 선택이면 더 좋습니다.&lt;/p&gt;&lt;p&gt;4. 인생을 쉼게 사는 방법 8가지 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://m.blog.naver.com/kinvestors/223711340137"&gt;https://m.blog.naver.com/kinvestors/223711340137&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 8가지&lt;/p&gt;&lt;p&gt;1. 심각하게 생각하지 말기&lt;/p&gt;&lt;p&gt;2. 맛있는 거 먹고 좋아하는 거 하고 살기&lt;/p&gt;&lt;p&gt;3. 생각보다 큰일은 나지 않으니 걱정 말기&lt;/p&gt;&lt;p&gt;4. 누가 내 욕하면 나도 네 욕하면서 무시하기&lt;/p&gt;&lt;p&gt;5. 어떻게든 세상은 돌아간다 생각하며 살기&lt;/p&gt;&lt;p&gt;6. 자기가 좋아하는 거 찾으면서 즐기기&lt;/p&gt;&lt;p&gt;7. 거절해도 아무일도 안 일어나니 싫은 건 거절하기&lt;/p&gt;&lt;p&gt;8. 생각보다 사람들은 나에게 관심 없으니 눈치 보지 말기&lt;/p&gt;&lt;p&gt;2. 인생에서 승패란 그리 중요하지 않다. 최선을 다했다면, 그것으로 성공한 인생을 산 것이다.&lt;/p&gt;&lt;p&gt;5. 우린 왜 화가 나는가? (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223739961728"&gt;https://blog.naver.com/travis88/223739961728&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 내가 옳다는 생각이 강할 때 상대가 인정을 안 하면 화가 난다. 사실은 자기 자신에게 사로잡히는 것이다. 집착을 하면 누구든지 화가 날 수 있다. 화를 오래 간직하고 있으면 손해니까 '내가 사로잡혔구나'하고 금방 내려놔야 한다.&lt;/p&gt;&lt;p&gt;2. 스스로 생각하더라도 정신적으로 어수선하거나 혹은 멘탈이 많이 안 좋아졌다고 생각할 때, 잠깐만 일시중단을 누르고 무엇이 본인에게 그런 나쁜 긴장감을 부여하고 있는지 체크할 필요가 있음. 이건 분명히 어딘가에서 무언가가 고장났다는 것을 의미하니까.&lt;/p&gt;&lt;p&gt;6. 진짜 고수들의 특징 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223742246588"&gt;https://blog.naver.com/travis88/223742246588&lt;/a&gt;, &lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223769078173"&gt;https://blog.naver.com/travis88/223769078173&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 보이지 않는 싸움&lt;/p&gt;&lt;p&gt;2. 어떤 일이든 본질적으로 뭐가 중요한지 아는 사람들은 불필요한 액션을 하지 않음. 곧바로 본질적인 부분에만 담백하게 집중함.&lt;/p&gt;&lt;p&gt;3. 누군가는 노력하고 있으니까 그것만으로도 할 일을 하고 있다고 생각하는데, 노력도 이게 본질적인 부분에서 하지 않고 부차적인 부분에만 매몰되면 시간을 엄청나게 낭비하는 것이나 다름 없음.&lt;/p&gt;&lt;p&gt;1. 노력도 중요하지만 포기하는 것도 중요하다.&lt;/p&gt;&lt;p&gt;7. 운과 확률, 기회를 다루는 태도 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223746384464"&gt;https://blog.naver.com/lhd1371/223746384464&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 이 글은 투자(주식), 사업, 그리고 포커(도박)가 모두 &lt;strong&gt;'불확실성이라는 강을 건너는 게임'&lt;/strong&gt;이라는 공통점을 가지고 있으며, 여기서 승리하기 위해 필요한 마인드셋과 생존 전략을 다루고 있습니다.&lt;/p&gt;&lt;p&gt;2. # 1. 투자의 본질과 돈을 대하는 태도&lt;/p&gt;&lt;p&gt;- &lt;strong&gt;불확실성의 관리:&lt;/strong&gt; 주식, 사업, 포커는 방법만 다를 뿐 모두 불확실성을 다루는 게임입니다.&lt;/p&gt;&lt;p&gt;- &lt;strong&gt;돈의 무게:&lt;/strong&gt; 포커판의 돈은 순식간에 사라지거나 두 배가 될 수 있는 '좀비 같은 돈'인 반면, 증권 계좌의 돈은 천천히 부패하는 고기와 같습니다.&lt;/p&gt;&lt;p&gt;- &lt;strong&gt;점수판의 의미:&lt;/strong&gt; 칩, 주가 차트, 매출은 그 자체로 목적이 아니라, 우리가 얼마나 현명한 선택을 했고 감정과 리스크를 잘 관리했는지 보여주는 '점수판'일 뿐입니다. 점수판에만 집착하면 게임의 본질을 잃게 됩니다.&lt;/p&gt;&lt;p&gt;3. # 2. 기회와 '올인'에 대한 착각&lt;/p&gt;&lt;p&gt;- &lt;strong&gt;아마추어 vs 프로:&lt;/strong&gt; 아마추어는 '지금이 아니면 안 된다'며 인생을 걸고 덤비지만(올인), 프로에게 올인은 마지막 카드가 아니라 첫 번째 카드일 뿐입니다.&lt;/p&gt;&lt;p&gt;- &lt;strong&gt;진짜 기회의 모습:&lt;/strong&gt; 기회는 "이번이 마지막이야"라고 소리치며 오지 않습니다. 준비된 자에게 오래된 연인처럼 자연스럽게 다가옵니다. "이번에는 다르다", "한 번만 더"를 외치는 순간은 대부분 패배의 전조입니다.&lt;/p&gt;&lt;p&gt;- &lt;strong&gt;승리의 위험성:&lt;/strong&gt; 지고 있을 때보다 이기고 있을 때가 더 위험합니다. 승리에 취하면 판단력이 흐려지기 때문입니다. 진정한 승자는 이기는 순간에도 패배를 기억합니다.&lt;/p&gt;&lt;p&gt;4. # 3. 가장 중요한 원칙: 생존 (Survival)&lt;/p&gt;&lt;p&gt;- &lt;strong&gt;운을 기다리는 자세:&lt;/strong&gt; 실력은 쌓여가지만 운은 일시적입니다. 가장 중요한 것은 &lt;strong&gt;운이 찾아올 때까지 망하지 않고 버티는 것&lt;/strong&gt;입니다.&lt;/p&gt;&lt;p&gt;- &lt;strong&gt;지속 가능성:&lt;/strong&gt; 급락장이나 블랙스완(예상치 못한 위기)이 와도 '아웃되지 않고' 게임을 계속할 수 있는 구조(Repeatable)를 만드는 것이 핵심입니다.&lt;/p&gt;&lt;p&gt;5. # 4. 위기에 대비하는 3가지 무기&lt;/p&gt;&lt;p&gt;- 주식 투자자가 극단적인 상황에서도 얼어붙지 않고 버티기 위해 반드시 갖춰야 할 3가지 요소가 있습니다.&lt;/p&gt;&lt;p&gt;1. &lt;strong&gt;치밀한 자금 관리&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;2. &lt;strong&gt;강건한 멘탈&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;3. &lt;strong&gt;회사(투자 대상)에 대한 철저한 이해&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;- 이 중 하나는 반드시 가지고 있어야 생존할 수 있으며, 둘 이상을 갖추면 폭락장이 오히려 기회가 됩니다. 셋 다 없다면 시장을 떠나는 것이 답입니다.&lt;/p&gt;&lt;p&gt;6. &amp;gt; "노력할수록 운은 좋아진다. 하지만 중요한 건 운이 올 때까지 시장에서 퇴출당하지 않고 살아남는(Survival) 것이다."&lt;/p&gt;&lt;p&gt;8. 내로남불과 솔선수범 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223753176856"&gt;https://blog.naver.com/lhd1371/223753176856&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;9. 많은 사람들이 뭔가의 이름을 아는 것을 / 그것을 이해하고 있는 것으로 착각한다.&lt;/p&gt;&lt;p&gt;10. 일론 머스크의 병목 규칙 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://x.com/mad20130301/status/1889886105111241106?s=46"&gt;https://x.com/mad20130301/status/1889886105111241106?s=46&lt;/a&gt;, &lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223821645030"&gt;https://blog.naver.com/travis88/223821645030&lt;/a&gt;?)&lt;/p&gt;&lt;p&gt;1. 병목현상을 찾고 -&amp;gt; 직접 해결하며 -&amp;gt; 반복하는 것&lt;/p&gt;&lt;p&gt;2. 인생의 모든 일이 다 마찬가지인데, 문제 발생 -&amp;gt; 문제 진단 -&amp;gt; 문제 수습&lt;/p&gt;&lt;p&gt;11. 나와 자녀를 위해 해야하는 것들 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223762618281"&gt;https://blog.naver.com/lhd1371/223762618281&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;12. 부자의 기술 - 머리, 몸, 시간의 활용 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223763116557"&gt;https://blog.naver.com/lhd1371/223763116557&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. ![[Pasted image 20251219104202.png]]&lt;/p&gt;&lt;p&gt;13. 3개월에 하나만 바꿔도 됨 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223763474103"&gt;https://blog.naver.com/travis88/223763474103&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 3개월에 하나씩 바꾸기&lt;/p&gt;&lt;p&gt;1. 쇼츠나 릴스는 아예 시청하지 않는다.&lt;/p&gt;&lt;p&gt;2. 매일 30분 이상 독서를 한다.&lt;/p&gt;&lt;p&gt;3. 험담이나 아첨을 하지 않는다.&lt;/p&gt;&lt;p&gt;4. 필사와 낭독으로 언어 수준을 높인다.&lt;/p&gt;&lt;p&gt;5. 차분하게 듣고 조용히 말할 줄 안다.&lt;/p&gt;&lt;p&gt;6. 기품 있게 행동하며 믿음을 준다.&lt;/p&gt;&lt;p&gt;7. 일상의 글쓰기를 실천한다.&lt;/p&gt;&lt;p&gt;2. 한번에 너무 많은 것을 하지 않으려고 한다.&lt;/p&gt;&lt;p&gt;14. 부자들이 지키는 단 하나의 룰 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/fantasticpotato/223765746044"&gt;https://blog.naver.com/fantasticpotato/223765746044&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 투자 수입을 창출하는 자산을 확보한 후에 사치품을 구매하는 것&lt;/p&gt;&lt;p&gt;1. 소비재와 사치재를 구매할때 배당이나 수익으로 구매하게 되면 원금은 그대로다.&lt;/p&gt;&lt;p&gt;15. 좋은 사람을 만나야 하는 이유는 환경이 곧 내가 되기 때문이다.&lt;/p&gt;&lt;p&gt;1. 밝은 사람과 있으면 더 자주 웃게 되고, 꿈이 있는 사람을 보면 덩달아 뜨거워지고, 잘 들어주는 사람과 있으면 남에게는 털어놓지도 못할 말을 꺼내게 된다. 다정한 사람과 있으면 나 또한 그래지고 술 없이도 잘 노는 사람과 가까워지면 맛있는 밥과 커피, 수다만으로도 한 사람과 충분히 깊어질 수 있다는 것을 배우게 된다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;2. 우리는 가까운 사람을 닮게 된다.&lt;/p&gt;&lt;p&gt;16. 행복의 역치 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223766245546"&gt;https://blog.naver.com/lhd1371/223766245546&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;17. 왜 살아야 하는지 아는 사람 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/sofoswh/223768922167"&gt;https://blog.naver.com/sofoswh/223768922167&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 왜 살아야 하는지 아는 사람은 그 어떤 어려운 상황도 견딜 수 있다. - 니체&lt;/p&gt;&lt;p&gt;18. 소중한 것을 깨닫는 장소는 언제나 컴퓨터 앞이 아니라 푸른 하늘 아래였다.&lt;/p&gt;&lt;p&gt;19. "남이 안되길 바라면 그 해로운 마음이 나에게로 와."&lt;/p&gt;&lt;p&gt;1. 저도 아무리 싫은 사람, 국가, 기업이여도 무의식적이든, 의식적이든 망하길 바라는 생각이나 글은 적지 않으려고 노력합니다.&lt;/p&gt;&lt;p&gt;2. 그러한 생각을 하면 결국 그 업이 내 자신에게도 오는 것 같습니다.&lt;/p&gt;&lt;p&gt;20. 태도에 대한 좋은 문구, 그림들 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223769894994"&gt;https://blog.naver.com/lhd1371/223769894994&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;21. 그냥 이번생은 그렇게 살기로 했어 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223779749251"&gt;https://blog.naver.com/lhd1371/223779749251&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 내 의지든 아니든 상황이 주어졌다면, 그 상황을 위해 충실해야 한다.&lt;/p&gt;&lt;p&gt;22. 인생에서 If를 줄이자 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223780936987"&gt;https://blog.naver.com/lhd1371/223780936987&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 행복은 행복하게 살아가는 습관이다.&lt;/p&gt;&lt;p&gt;2. ~ 때문에 행복한 건 가짜&lt;/p&gt;&lt;p&gt;1. 조건이 붙는 순간 조건이 사라지면 결과도 사라진다. (가수가 되면 행복할거야, 돈을 많이 벌면 행복할꺼야. 등)&lt;/p&gt;&lt;p&gt;3. 그럼에도 불구하고 행복한게 진짜&lt;/p&gt;&lt;p&gt;23. 누군가 뒤애서 당신을 욕하더라도 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/alex267/223785370717"&gt;https://blog.naver.com/alex267/223785370717&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 뒤에서 널 욕하는 사람들 신경 쓰지 마라. 그들이 너보다 뒤에 있는 이유다. - 바실 로마첸코&lt;/p&gt;&lt;p&gt;2. 그 누구도 아닌 자기 걸음을 걸어라. 나는 독특하다는 것을 믿어라. 누구나 몰려가는 줄에 설 필요는 없다. 자기 걸음으로 자기 길을 가라. 바보 같은 사람들이 뭐라고 비웃든 간에 - 죽은 시인의 사회&lt;/p&gt;&lt;p&gt;24. 언어의 힘, 질문의 힘 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223787960518"&gt;https://blog.naver.com/lhd1371/223787960518&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 명확성 (Clarity)&lt;/p&gt;&lt;p&gt;1. 질문이 모호하지 않고, 듣는 사람이 이해하기 쉬워야 한다.&lt;/p&gt;&lt;p&gt;2. 구체성 (Specificity)&lt;/p&gt;&lt;p&gt;1. 질문이 너무 넓거나 일반적이면 답변이 피상적일 수 있다.&lt;/p&gt;&lt;p&gt;3. 목적의식 (Purposefulness)&lt;/p&gt;&lt;p&gt;1. 질문을 통해 무엇을 얻고 싶은지 명확해야 한다.&lt;/p&gt;&lt;p&gt;4. 맥락 제공 (Contextualization)&lt;/p&gt;&lt;p&gt;1. 상대방이 질문을 이해할 수 있도록 필요한 배경 정보를 간략히 제공해야 한다.&lt;/p&gt;&lt;p&gt;5. 개방형 vs 폐쇄형 질문의 균형 (Open vs Closed Questions)&lt;/p&gt;&lt;p&gt;1. 개방형 질문은 깊이 있는 답변을 유도하고, 폐쇄형 질문은 명확한 정보를 얻는 데 유용하다.&lt;/p&gt;&lt;p&gt;6. 가정에 대한 검토 (Avoiding Assumptions)&lt;/p&gt;&lt;p&gt;1. 질문에 잘못된 가정이 포함되지 않도록 주의해야 한다.&lt;/p&gt;&lt;p&gt;7. 객관성 유지 (Neutrality)&lt;/p&gt;&lt;p&gt;1. 편견없이 객관적으로 질문해야 답변의 질이 높아진다.&lt;/p&gt;&lt;p&gt;8. 답변 가능성 (Answerability)&lt;/p&gt;&lt;p&gt;1. 질문이 지나치게 추상적이거나 답변하기 어려운 방식으로 구성되지 않아야 한다.&lt;/p&gt;&lt;p&gt;9. 추가 질문을 고려한 설계 (Follow-up Readiness)&lt;/p&gt;&lt;p&gt;1. 답변을 바탕으로 추가 질문을 던질 수 있도록 연계된 질문을 준비하는 것이 중요하다.&lt;/p&gt;&lt;p&gt;25. 25.3 1주차 기록 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223788356660"&gt;https://blog.naver.com/lhd1371/223788356660&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;26. 크래프톤 장병규의장 조언들 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/gmyhhj/223789787498"&gt;https://blog.naver.com/gmyhhj/223789787498&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 20대에 몰입해라. 30대 중반이 되면 몸도 정신도 안따라준다.&lt;/p&gt;&lt;p&gt;2. 일 이외의 중요한 것에 집중하라. 하지만 그것도 일에 몰입해본 사람들만 알 수 있는 것이다.&lt;/p&gt;&lt;p&gt;27. 내 주변 사람들의 에너지 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223794685435"&gt;https://blog.naver.com/travis88/223794685435&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 사소한 것에 행복할 수 있어야 인생 전체를 견딜 수 있다.&lt;/p&gt;&lt;p&gt;28. 만화 명언과 노력, 확률론 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223796126188"&gt;https://blog.naver.com/lhd1371/223796126188&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 되는 방향으로 가고 있느냐, 될 확률을 높이고 있느냐를 마음에 담고 있다면, 실패에 우울할 이유가 없고 어떻게 하면 꾸준히 할 수 있을까 더 나아질 수 있을까. 그 고민과 실천만 하면 끝이다.&lt;/p&gt;&lt;p&gt;29. 안성재 셰프 마인드셋 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223796981515"&gt;https://blog.naver.com/travis88/223796981515&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 워라밸을 지금 지키면 미래의 워라밸이 없다고 생각해야죠.&lt;/p&gt;&lt;p&gt;1. 나는 워라밸을 조금 다르게 생각하고 있긴 하지만..&lt;/p&gt;&lt;p&gt;2. 워라밸을 버릴 각오를 했다면, 적어도 그럴만한 가치가 있는 방식으로 해야된다. 죽어라 일해도 보상도 없고 평판이 나아지지도 않는 소모만 되는 방식은 경계해야 한다.&lt;/p&gt;&lt;p&gt;30. 부정적인 사람과 건전한 비판러의 차이 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223798346597"&gt;https://blog.naver.com/lhd1371/223798346597&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 건전한 비판을 하는 사람은 그 일을 맡는다.&lt;/p&gt;&lt;p&gt;2. 기존 업무에 들어가는 리소스를 조정해주면 좋겠지만 그렇지 않다 해도 망설이지 않는다. 필요한 일이니까. 누군가는 해야 하는 일이니까.&lt;/p&gt;&lt;p&gt;3. 반면, 부정적인 사람들은 직접 해보라는 말을 들으면 엇나간다.&lt;/p&gt;&lt;p&gt;1. "제가요, 왜요?"&lt;/p&gt;&lt;p&gt;4. 그리고 공개적인 자리에서 하지 못한 이야기를 뒤에서 수근댄다.&lt;/p&gt;&lt;p&gt;1. "있잖아. 의견을 내면 직접 해야 한다? 세상에, 무서워서 말도 못하겠네. 그냥 입 닥치고 있으라는 거지."&lt;/p&gt;&lt;p&gt;5. 나를 설명하는건 태도다.&lt;/p&gt;&lt;p&gt;31. 인생은 공책이다. (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223816355088"&gt;https://blog.naver.com/lhd1371/223816355088&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 일기를 쓴다는 것은 누구도 보지 않을 책에 헌신할 만큼 자신의 삶이 가치가 잇다고 판단하는 것이다.&lt;/p&gt;&lt;p&gt;2. 벽돌이 쌓인다고 집이 되지 않듯이 시간이 쌓인다고 삶이 만들어 지지 않는다. - 에리스 로럴드 미리에리&lt;/p&gt;&lt;p&gt;32. 좋아하는 일 가장 빨리 찾는 방법 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://www.youtube.com/watch?v=VY4ONED6P2c"&gt;https://www.youtube.com/watch?v=VY4ONED6P2c&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. &lt;strong&gt;흥미&lt;/strong&gt;로운 일을 찾아서, &lt;strong&gt;재미&lt;/strong&gt;난 일을 하다보면 &lt;strong&gt;의미&lt;/strong&gt;가 생긴다.&lt;/p&gt;&lt;p&gt;33. 나를 깎아 내리는 것들을 무시할 수 있는 이유 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223824101204"&gt;https://blog.naver.com/lhd1371/223824101204&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 내가 나를 못믿어주면 누가 나를 믿어주겠는가?&lt;/p&gt;&lt;p&gt;34. 욕망과 욕심의 차이는? (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://www.ohmynews.com/NWS_Web/View/at_pg.aspx?CNTN_CD=A0002397945"&gt;https://www.ohmynews.com/NWS_Web/View/at_pg.aspx?CNTN_CD=A0002397945&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 우리의 인생에도 임계점이 있다. 바람직한 일의 결실을 보기 위해서는 임계점에 도달할 때까지 열심히 노력해야 하지만, 자신의 분수를 알고 적당한 선에서 멈추는 자족의 마음이 절대 필요하다. 욕망이라는 강렬한 에너지를 잘 조절해야 삶이 아름다워진다. 우리는 밝고 건강한 가치를 욕망해야 한다. 젊었을 때는 욕망을 채우면서 살아야 하고, 늙어서는 부단히 욕망을 빼면서 살아야 한다. 이것이 인생 최고의 지혜이다.&lt;/p&gt;&lt;p&gt;35. 1902년 클레멘스 J. 프랑스(Clemens J. France)라는 사람이 "도박 충동(The Gambling Impulse)"&lt;/p&gt;&lt;p&gt;1. 도박꾼의 성공에 필수적인 여러 작은 요소들이 있는데, 이는 위기 순간에 차분하고 열정적인 태도를 유지하며 감정을 드러내지 않는 것, 패배 후 빠르게 회복하는 능력, 항상 경계심을 유지하는 것, 상대방을 가장 면밀히 분석하는 습관을 기르는 것, 도박꾼보다 사람을 더 잘 평가하는 사람은 드물다는 것, 신중함과 대담함의 균형을 유지하는 것, 그리고 행운이 좋든 나쁘든 차분히 받아들이는 법을 배우는 것 등이 포함된다. 이러한 요소들은 어떤 활동이든 적극적이고 기회를 활용하는 데 필수적이다.&lt;/p&gt;&lt;p&gt;36. 꾸준함에 대한 착각 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223856208287"&gt;https://blog.naver.com/lhd1371/223856208287&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 인간인 이상 매일 100%일 수는 없다. 100%가 아닌 70%, 50%더라도 그날 그날의 컨디션, 상태에서의 최선을 다해보는 것.&lt;/p&gt;&lt;p&gt;2. 매일 매일 무언가를 한다는 건, 매일 완벽을 기록하는 게 아닌 기세를 이어가는 것&lt;/p&gt;&lt;p&gt;37. "자신이 이미 알고 있다고 생각하는 것을 배우는 것은 불가능하다." - 에픽테토스&lt;/p&gt;&lt;p&gt;1. 모든 것을 다 안다고 생각하는 순간 우리는 성장을 멈추게 됩니다. 초보자가 된다는 것은 호기심과 겸손, 그리고 항상 배우는 자세를 유지하게 하는 초능력입니다.&lt;/p&gt;&lt;p&gt;38. 시간이 흐르면 격차는 더 벌어진다 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://m.blog.naver.com/fallsmile3/223869787449"&gt;https://m.blog.naver.com/fallsmile3/223869787449&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 평범한 남들과는 조금 다르게 살아가길 원한다면 나에게 주어진 벽을 어떻게든 넘어보면 좋겠다.&lt;/p&gt;&lt;p&gt;39. 최적화된 환경에 놓여 있는가 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://m.blog.naver.com/PostList.naver?blogId=sealight77"&gt;https://m.blog.naver.com/PostList.naver?blogId=sealight77&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 한정된 시간에 내 주변의 모든것을 다 해결하기에는 너무 많은 노력과 더 많은 시간을 필요로 할 때가 대부분이다. 그래서 정말로 필요로 하는 것에 최대한 집중을 하는 것이 나를 위한 것이고, 최적의 환경이 된 이후로는 시간이 남아돌기 시작한다. 이렇게 남는 시간을 나와 가족을 위해 최대한 사용한다면 최적의 환경속에서 살아간다고 할 수 있지 않을까?&lt;/p&gt;&lt;p&gt;40. 싫어하는 사람이 있을 때 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223902714272"&gt;https://blog.naver.com/travis88/223902714272&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 불교의 용어에 &lt;strong&gt;시절 인연&lt;/strong&gt; 이란게 있습니다. 모든 인연에는 오고 가는 시기, 때가 있다는 뜻입니다. ... 사람이나 일, 재물과의 만남도, 깨달음과의 만남도, 그 때가 있는 법입니다. 아무리 만나고 싶은 사람이 있어도, 아무리 갖고 싶은 물건이 있어도, 시절 인연이 무르익지 않으면, 즉 때가 되지 않으면 바로 옆에 두고도 만날 수 없고, 손에 넣을 수 없는 법입니다.&lt;/p&gt;&lt;p&gt;41. 10대, 20대를 위한 조언들 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://m.blog.naver.com/bizucafe/223901246364"&gt;https://m.blog.naver.com/bizucafe/223901246364&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 한 분야의 전문가가 되세요. 배우려고 하면 끝까지 배우세요.&lt;/p&gt;&lt;p&gt;2. 일을 빡세게 하시고, 즐기세요. 그냥 할 수 있는 만큼 하세요. 하는 만큼 늡니다. 일을 빡세게 해도 별로 힘들지 않으면, 재능이 있는 겁니다. 그걸 감사히 여기고, 더 빡세게 하세요.&lt;/p&gt;&lt;p&gt;3. 책 많이 읽으세요.&lt;/p&gt;&lt;p&gt;4. 주변 사람들이랑 비교하지 마세요. 주변 친구들보다 '나는 더 잘났네' 하면 거기서 성장 끝입니다. 주변 환경을 기준으로 삼지 마세요. 세상에 뛰어난 사람 많습니다. 그 사람들이랑 비교하세요.&lt;/p&gt;&lt;p&gt;5. 사람들에게 잘 해주세요. 사교성 있는 것은 엄청난 장점입니다. 첫인상을 잘 만들려고 노력하고, 사람들이 함께 하고 싶은 사람이 되면 엄청나게 좋습니다.&lt;/p&gt;&lt;p&gt;6. 스스로 배우세요. 누구한테 배우려고 하지 마세요. 다른 사람들도 잘 모릅니다. 혼자 생각해서 결론내는 연습을 하세요.&lt;/p&gt;&lt;p&gt;42. 사실 용서는 만병통치약이다. (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/alex267/223893827153"&gt;https://blog.naver.com/alex267/223893827153&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 용서는 자신을 해방시키는 것이다. - 석가모니&lt;/p&gt;&lt;p&gt;43. 고급스러운 말투라는데... (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223891469683"&gt;https://blog.naver.com/travis88/223891469683&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;44. 포즈랑님 글을 보며... 가지 않은 길과 육아빠 투자자의 숙명 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/223889813641"&gt;https://blog.naver.com/lhd1371/223889813641&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 불광불급&lt;/p&gt;&lt;p&gt;2. 미치지 않으면 이룰 수 없고. 세상에 꽁짜는 없으며. 본인이 내어놓은 노력과 희생만큼 가져가라는 것. 그러다보니, 이 정도 노력과 이 정도 속도로 나는 괜찮은 걸까? 라는 자기 의심이 종종 스스로를 괴롭힙니다.&lt;/p&gt;&lt;p&gt;45. “타인보다 우월하다는 데에는 고귀함이 없다. 진정한 고귀함이란 과거의 나보다 우월한 자신이 되는 것이다.” – 어니스트 헤밍웨이&lt;/p&gt;&lt;p&gt;46. 나는 열심히 했다고 생각하겠지만 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/tkos3333/223906816872"&gt;https://blog.naver.com/tkos3333/223906816872&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 쉬지 않고 매일 일만 하며 지내는 시기가 있다. 그런 시기는 필요하다. 하지만 그렇게 평생 하면 탈이 난다.&lt;/p&gt;&lt;p&gt;47. 포커 플레이어가 헤지펀드로 넘어오면서 느낀 공통점&lt;/p&gt;&lt;p&gt;1. 포커와 시장의 공통점은 자기 성찰적 태도, 즉 자신이 본질적으로 어떤 사람인지, 게임을 어떻게 플레이하는지, 어떤 내면적 편향이 있는지를 아는 것이 매우 중요하다는 점입니다.&lt;/p&gt;&lt;p&gt;2. 결과에 감정적으로 휘둘리지 않고, "내 결정은 합리적인가? 내가 좋은 결정을 하고 있는가?"만을 생각합니다. 그 이후 무슨 일이 일어나든 개의치 않게 됩니다.&lt;/p&gt;&lt;p&gt;3. 투자도 마찬가지로 내가 베팅하는 순간 판단의 퀄리티가 결정됩니다. 해당 시행의 결과는 좋을수도 있고 나쁠 수도 있습니다. 그러나 판단의 퀄리티가 좋다면 시행이 많아질수록 결과는 좋은 쪽으로 수렴하게 됩니다. 그러면 결국 수익률도 올라갑니다.&lt;/p&gt;&lt;p&gt;4. 그러나 많은 개인투자자들은 이와 반대로 판단합니다. 시행의 결과를 보고 투자자의 퀄리티를 판단합니다. 그러다가 결국 사기를 당합니다.&lt;/p&gt;&lt;p&gt;48. 직장인의 쉬운 결정 vs 투자자·사업가로서의 힘든 결정&lt;/p&gt;&lt;p&gt;1. 직장인으로 살아가는 것은 많은 면에서 ‘쉬운 결정’의 연속일 수 있다. 매달 안정적인 급여가 들어오고, 회사가 정해준 업무를 수행하면 일정한 보상이 주어진다. 특별히 큰 책임을 지지 않아도 되고, 리스크를 감수할 필요도 없다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;2. 조직이 제공하는 울타리 안에서 정해진 역할을 충실히 수행하는 삶은 예측 가능하고 안정적이다. 하지만 그만큼 변화의 기회도, 자산의 비약적인 성장은 기대하기 어렵다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;3. 결국 이 ‘쉬운 결정’의 반복은 시간이 지날수록 더 단단한 벽이 되어, 삶의 선택지를 줄이고 경제적 자유로부터 멀어지게 만든다.&lt;/p&gt;&lt;p&gt;4. 반면, 투자나 사업에 도전하는 길은 수많은 ‘힘든 결정’으로 가득하다. 불확실한 미래를 감수해야 하고, 자본을 투입하면서 손실의 가능성도 감내해야 한다. 때로는 실패를 겪고, 누구도 책임져주지 않는 상황에서 다시 일어나야 한다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;5. 매출이 없을 때도, 누군가의 월급을 먼저 챙겨야 하는 순간도 온다. 하지만 이런 과정을 견디며 축적되는 역량과 경험, 그리고 성공했을 때 얻게 되는 수익 구조는 직장인의 삶과는 전혀 다른 수준의 자유와 성장을 가져다준다.&lt;/p&gt;&lt;p&gt;6. 직장인의 쉬운 결정은 오늘을 편하게 만들지만, 내일의 제약을 키우고 사업가, 투자자의 힘든 결정은 오늘을 어렵게 만들지만, 내일의 가능성을 넓힌다. 진짜 단단한 삶은 리스크를 감수하고 어려운 결정을 내릴 수 있는 용기에서 비롯된다.&lt;/p&gt;&lt;p&gt;7. 그렇다면 투자는 삶이 무너질 수 있을 만큼의 위험은 가급적 피하면서 생활이 무탈할 수 있는 자산까지 가고, 그 자산을 유지할 수 있으면서 조금씩 쌓아갈 수 있는 투자능력을 기르고 유지하는 것이다.&lt;/p&gt;&lt;p&gt;8. 그렇게 투자 자체를 하나의 직업이나 취미로서 삶의 일부로 함께 해가다 보면 결국 늙어 죽을때는 부자로 남게 되지 않을까 싶고, 그것이 행복한 투자자의 모습이라고 생각한다.&lt;/p&gt;&lt;p&gt;9. 투자능력을 기르는 과정과 투자를 통해 얻으려고 하는 것.&lt;/p&gt;&lt;p&gt;10. 그리고 그것들을 위해 내가 해야할 것과 하지 말아야 할 것들을 완전히 이해하고 철저히 지키는 것이 투자자의 삶을 사는 사람들에게 기본 소양이 되었으면 한다.&lt;/p&gt;&lt;p&gt;49. 아주 솔직한 대화 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223914764653"&gt;https://blog.naver.com/travis88/223914764653&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 자기 객관화가 100% 되는 사람은 없겠지만, 아예 이것을 시도조차 하지 않으려고 하면 진짜 빠르게 후진 사람이 되고 만다.&lt;/p&gt;&lt;p&gt;50. “몸 아껴쓰면 안돼”…저속노화좌, 정희원 교수의 첫 ‘쉼표’ (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://m.mk.co.kr/news/hot-issues/11356737"&gt;https://m.mk.co.kr/news/hot-issues/11356737&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 나이가 들수록 편안함을 추구하게 된다. 돈과 건강도 쉽고 편하게 얻길 바란다. 그러나 그러한 삶의 태도가 오히려 몸과 마음을 약하게 만드는 원인이 된다. 왜 약간의 불편함을 받아들여야 하는지, 그 불편이 우리 삶에 어떤 긍정적인 영향을 주는지 말씀드리겠다.&lt;/p&gt;&lt;p&gt;51. 살아갈 이유가 있다면 우리는 버텨낼 수 있다 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/alex267/223929473991"&gt;https://blog.naver.com/alex267/223929473991&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 살아가는 이유가 있는 사람은 어떻게든 살아갈 수 있다.&lt;/p&gt;&lt;p&gt;52. 내가 100% 동의하는 글들 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://m.blog.naver.com/lhd1371/223932173123"&gt;https://m.blog.naver.com/lhd1371/223932173123&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 태도가 9할&lt;/p&gt;&lt;p&gt;1. 태도가 좋은 분들은 지금 좀 어렵다 한들. 어느 순간 자신만의 그 무엇으로 극복해 나간다.&lt;/p&gt;&lt;p&gt;2. 태도가 나쁜 분들은 지금 잘 나간다 한들. 어느 순간 저 나락 어딘가에 가 있게 된다.&lt;/p&gt;&lt;p&gt;2. 낮은 확률을 이기는 것은 오랜 기간의 빌드업 뿐&lt;/p&gt;&lt;p&gt;1. 불가능은 없다는 말은 말이 안되는 말이지만, 매우 낮은 확률도 시간만 충분하면 이겨낼 수 있다라는 말로 바꾼다면 맞는 말일 수 있다.&lt;/p&gt;&lt;p&gt;2. 그런데 그걸 하는 게 무슨 의미가 있나, 내 인생에 도움이 되나.. 라는 끝없는 자기 의심과 미래에 대한 신뢰가 없는 사람들은 그런 것들을 장기간 지켜 나가지 못한다.&lt;/p&gt;&lt;p&gt;3. 그 어떤 것도 방관자를 통해 이루어 낼 수는 없다. 그 누가 이루어낸 일도 아무리 높은 성과와 그 어떤 것도 내가 한 게 아니라면 아무 의미가 없다.&lt;/p&gt;&lt;p&gt;4. 결국 내 손으로 내 눈 앞에서 하는 것만이 내 것이며, 그것이 나의 인생이고 가치 있는 것이다.&lt;/p&gt;&lt;p&gt;5. 어떤 사람이 순간적인 결정이나 단시간에 많은 정보 없이 훌륭한 결정을 반복해서 계속 내린다면 그 사람을 대단하다고 생각하지 말고 그 사람이 저렇게까지 된 빌드업된 것을 봐야 한다.&lt;/p&gt;&lt;p&gt;6. 그리고 나도 그 빌드업을 매일 하고 있는지 생각해 봐야 한다.&lt;/p&gt;&lt;p&gt;3. 아무리 작은 성취라도 본인이 직접 스스로 얻은 과정의 경험과 성취가 있는 사람은 본인이 '확률이 높은 쪽'으로 움직이고 노력하면 나아진다는 것을 알고 있고 평소에도 그러한 관점과 태도로 살아갑니다.&lt;/p&gt;&lt;p&gt;4. 그러나 과정과 결과 둘 중 하나만 본인의 노력으로 되었거나 혹은 둘 다 수동적으로 떠밀려서 한 경우에는 선택에 있어서 책임을 지고 싶지 않아 합니다. 그게 기본 태도가 되어버립니다.&lt;/p&gt;&lt;p&gt;5. 결과가 &lt;strong&gt;재능 &lt;em&gt;노력 &lt;/em&gt;운의 함수&lt;/strong&gt;라는 것을 알고 있음에도 불구하고 노력은 애써 무시하고 재능과 운을 탓합니다. 반면 태도와 확률론이 갖춰진 사람은 재능과 운은 내가 어쩔 수 없는 부분이니 노력에 더욱 집중하려고 합니다.&lt;/p&gt;&lt;p&gt;6. 누군가는 나보다 노력을 적게 했더라도 나보다 더 잘 살 겁니다. 그런데 어쩌겠습니까. 그게 내가 노력 안 할 이유는 되지 못합니다.&lt;/p&gt;&lt;p&gt;53. 누구나 벙커를 만들어야 하는 이유 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223946412567"&gt;https://blog.naver.com/travis88/223946412567&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;54. 유산소 운동이 미래다 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223940791913"&gt;https://blog.naver.com/travis88/223940791913&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;55. 장동민이 방송하면서 깨달은 것 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223934987883"&gt;https://blog.naver.com/travis88/223934987883&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;56. 1억 모으기에서 10억까지 투자수익이 원금을 넘어서는 순간들 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://m.blog.naver.com/tkos3333/222762555445"&gt;https://m.blog.naver.com/tkos3333/222762555445&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;57. 후생가외라더니.. (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/pillion21/223947548993"&gt;https://blog.naver.com/pillion21/223947548993&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 다문 다독 다상량(多聞多讀多商量 ) 많이 듣고, 많이 읽고, 많이 생각하라..&lt;/p&gt;&lt;p&gt;58. 누군가의 생각&lt;/p&gt;&lt;p&gt;1. 굉장히 추상적인 레벨의 이야기이긴한데, 결국 제가 하고 싶었던 말은 '이 세상에는 생각보다 다양한 게임과 룰이 존재하고, 결국 각자만의 방법으로 그 게임을 클리어한 사람들이 자산가가 되어있더라'라는 이야기를 하고 싶었습니다. 적어도 지금까지 제가 관찰하기로는 그래요.&amp;nbsp;&lt;/p&gt;&lt;p&gt;2. 그리고 위의 이야기와 이어지는 이야기일 수 있는데, 자산가 분들은 삶의 어느 특정한 부분에서 무언가에 완전히 몰입한 순간들이 있으시더라고요. 그 몰입의 순간을 얼마나 잘 견디고 결과물까지 이어내는가가 자산가로 가는 길이구나 싶었습니다.&amp;nbsp;&lt;/p&gt;&lt;p&gt;3. 여담으로 어느정도 큰 부를 일구신 자산가분들은 어느 순간부터 '의미'에 대해서 깊게 탐색하시는 것 같습니다. 어느 순간부터는 돈을 버는 것 자체에서는 큰 의미를 찾지 못하시더라고요. 그걸 보면서 저는 '사명'은 최대한 일찍 찾고 가지고 있는 것이 바람직하겠구나, 그런 생각을 가지게 되었습니다.&lt;/p&gt;&lt;p&gt;59. 운동선수의 루틴처럼, 투자 성공을 위한 건전한 과정의 중요성 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/jeunkim/223948709730"&gt;https://blog.naver.com/jeunkim/223948709730&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 루틴은 운동선수가 통제할 수 있는 몇 안되는 것들 중 하나이며, 당면한 일에 집중할 수 있도록 해주기 때문입니다.&lt;/p&gt;&lt;p&gt;60. 중요한 건 운이 올 때까지 계속하는 것이다. (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/alex267/223949388282"&gt;https://blog.naver.com/alex267/223949388282&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 운이란 준비가 기회를 만났을 때 생기는 것이다. - 세네카&lt;/p&gt;&lt;p&gt;61. 멘탈 건강을 위해 외워야 할 문장 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223957663457"&gt;https://blog.naver.com/travis88/223957663457&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 세상엔 다양한 사람이 있다.&lt;/p&gt;&lt;p&gt;62. Paradigm Shifts and the Winner’s Curse (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://stratechery.com/2025/paradigm-shifts-and-the-winners-curse/"&gt;https://stratechery.com/2025/paradigm-shifts-and-the-winners-curse/&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 새로운 기술이 나올 때 자기 것을 지키려는 태도보단, 자기를 완전히 바꾼다는 마음가짐으로 대하는 것이 결국 살아남는 길인 것 같다.&lt;/p&gt;&lt;p&gt;63. 30대, 40대에도 쉼 없이 해야하는 것 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223961144495"&gt;https://blog.naver.com/travis88/223961144495&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 청춘이 매력적인 근본은, 남아도는 체력에 있다.&lt;/p&gt;&lt;p&gt;2. 무언가를 좋아할 체력, 좋아하는 것에 뛰어들 체력, 뛰어들었다가 실패하고 좌절할 체력, 그 와중에 친구가 부르면 나가 놀 체력, 그래놓고 나는 쓰레기라며 자책할 체력&lt;/p&gt;&lt;p&gt;64. 주식에서 성장과 고독에 관한 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://m.blog.naver.com/going_tothe_moon/223964980098"&gt;https://m.blog.naver.com/going_tothe_moon/223964980098&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 젊었을 때는 몰랐는데 살아보니 쓸데없는 계산하느라, 남들과 비교하느라 힘과 시간을 허비하지 않으면 제법 많은 것을 이룰 수 있더라. 세상의 정말 중요한 일들은 '외로움의 힘'으로 이루어진다. 외로움은 '정말 중요한 일'을 이뤄내는 원동력이라고 생각한다.&lt;/p&gt;&lt;p&gt;2. '돈을 많이 버는 사람들 사이에 끼어 있으면 나도 자연스럽게 돈을 벌게 된다'라는 생각은 반은 맞고 반은 틀린 이야기.&lt;/p&gt;&lt;p&gt;3. 늘 부의 기준을 느낄 수 있는 사람에게 노출되도록 노력하되, 결정적인 성장은 고독에서 이루어져야 한다.&lt;/p&gt;&lt;p&gt;4. 하지만 무리 지어 다니면서 성공한 사람은 없습니다. 뭔가를 배우거나 공부할 때에도 먼저 '홀로서기'를 해야 해요. ... 혼자 오롯이 에너지를 쏟아 몰입하다 보면 언젠가 거기서 성과를 얻게 돼요. 고독한 시간을 단지 좌절하며 보내는 사람과, 이를 자양분 삼는 사람의 차이가 여기서 벌어지기 시작합니다.&lt;/p&gt;&lt;p&gt;65. 세계 최고의 가르침 한 줄 요약 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223971159903"&gt;https://blog.naver.com/travis88/223971159903&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 인생은 원래 괴롭고, 욕심부리면 더 괴로움&lt;/p&gt;&lt;p&gt;66. 힘든 일을 겪고 있다면 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/gmyhhj/223979997739"&gt;https://blog.naver.com/gmyhhj/223979997739&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 먼저 힘든 감정의 소용돌이에서 최대한 빨리 빠져나와야 한다.&lt;/p&gt;&lt;p&gt;2. &amp;lt;에스키모의 막대기 전략&amp;gt; 활용하기&lt;/p&gt;&lt;p&gt;3. 해결해야 할 핵심에 집중해야 한다.&lt;/p&gt;&lt;p&gt;4. 해당 사건과 자신을 절대 동일시 해서는 안 된다.&lt;/p&gt;&lt;p&gt;67. 어떤 글&lt;/p&gt;&lt;p&gt;1. 무엇이든 일단 만들고 보세요. 그냥 시도하세요. 실패하는 것을 두려워하지 마세요. 왜냐하면 실패는 바로 성공의 씨앗이기 때문입니다. 모든 실패를 통해 '실용적인 배움'을 얻는 것, 그것이 바로 성공이에요. 우리 주변에도 '절대 배우지 않는' 사람, 혹은 세 번은 넘어져야 겨우 제대로 가는 사람 꼭 있잖아요.&lt;/p&gt;&lt;p&gt;68. 뻔하지만, 가장 중요한 것 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223980981224"&gt;https://blog.naver.com/travis88/223980981224&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 정말 뻔하지만, 가장 중요한 것이 결국 불편한 것을 얼마나 자주, 얼마나 밀도 높은 방식으로 감수했느냐는 것임.&lt;/p&gt;&lt;p&gt;2. 지금 누군가가 가치 있는 것을 누리고 있다면, 그 결과만 보고 끝낼 것이 아니라 저 사람이 그 과정에서 과연 어느 정도로 불편함을 감수했을지도 함께 고려해서 봐야 한다. 그게 본질이니까.&lt;/p&gt;&lt;p&gt;3. 불편함을 기꺼이 받아들이고 내성을 쌓고 그 불편함이 빚어내는 가치들에서 보람과 충만함을 느끼면서 살다 보면 중장기적으로 보면 오히려 삶은 더 편해지는 방식으로 가긴 함. 반대로, 지금 이 순간의 편안함에 안주하다 보면 오히려 후반부엔 불편함의 골짜기로 떨어질 수도 있음.&lt;/p&gt;&lt;p&gt;69. 워런버핏&lt;/p&gt;&lt;p&gt;1. "인생에서 아주 몇 가지 일만 제대로 해내면 됩니다. 너무 많은 잘못만 하지 않는다면요."&lt;/p&gt;&lt;p&gt;2. 즉, 인생에서 모든 것을 완벽히 맞힐 필요는 없습니다. 몇 번만 제대로 해내면 되고, 중요한 것은 치명적인 실수를 반복하지 않는 것입니다.&lt;/p&gt;&lt;p&gt;3. 10번 중 7번 정도만 제대로 맞혀도 충분히 훌륭한 성과를 거둘 수 있습니다. 버핏이 수차례 강조해온 핵심은 완벽함이 아닌, 확률적 사고와 장기적 누적 승률입니다.&lt;/p&gt;&lt;p&gt;4. 결국 목표는 완벽히 맞히는 것이 아니라, 더 자주 맞히는 것입니다.&lt;/p&gt;&lt;p&gt;70. "불변의 법칙" 중에서&lt;/p&gt;&lt;p&gt;1. 사랑이든 일이든 투자든, 우리 인생에서 중요한 것들은 &lt;strong&gt;인내심&lt;/strong&gt;과 &lt;strong&gt;희소성&lt;/strong&gt;이 있어야 가치 있는 뭔가가 된다. 인내심을 지녀야 그것이 성장하는 것을 지켜볼 수 있고, 희소성이 있어야 그것의 소중함을 느끼며 감사할 수 있다.&lt;/p&gt;&lt;p&gt;71. 귀멸의 칼날 무한성&lt;/p&gt;&lt;p&gt;1. "중요한건 올바른 호흡과 올바른 움직임, 최소한의 동작으로 최대한의 힘을 끌어내는 거란다. 그렇게 하면, 점점 머릿속이 투명해져. 많은 것들을 다 외우고 흡수한 후에는 불필요한 것들을 깎아내는 거야."&lt;/p&gt;&lt;p&gt;2. 그때, 광명이 비추고 길이 펼쳐져. 힘이 닿는 한, 발버둥 치고 괴로워했기에 비로소 도달한 '영역'이야.&lt;/p&gt;&lt;p&gt;72. taste (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://www.canda.blog/taste/"&gt;https://www.canda.blog/taste/&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 다양한 분야에서 좋은 taste를 가지는 것이 중요하겠지만, 어느 분야든 산출물(artifact)을 내는 과정을 거친다고 한다면, 같은 일을 하지만 어떤 사람의 artifact는 훌륭하고 어떤 사람의 artifact는 그저 그렇다(bland). 결국 그 일을 하는 사람의 어떠한 감각 같은 것이 작용하여 차이를 만드는 것이 아닐까 싶다.&lt;/p&gt;&lt;p&gt;2. 그런데 좋은 taste는 어떻게 기를 수 있을까? 취향과 맛에 대한 감각은 타고나는 것이 사실 없다고 생각하는 편이다. 누구든 지나온 경험과 환경을 통해 감각을 기른다고 생각한다. 그러나 단순하게 많이 보고 많이 듣고 경험하는 것으로는 충분하지 않다. 좋은 taste는 의식적인 훈련의 결과물이다. 좋은 것이 왜 좋은지를 설명하고자 하는 스스로의 사유와 남들과의 교류를 통해 형성되어 간다.&lt;/p&gt;&lt;p&gt;3. 좋은 taste를 가진 사람은 어떤 대상에 대한 열의를 가진 사람이다. 어떤 분야에 대해 열의 없는 사람이 그 분야에 있어 좋은 taste를 가질 수는 없다.&lt;/p&gt;&lt;p&gt;73. 인생을 잘 사는 기본 원칙(&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223989872298"&gt;https://blog.naver.com/travis88/223989872298&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 모든 것을 문제 -&amp;gt; 문제 해결의 방식으로 취득하는 것&lt;/p&gt;&lt;p&gt;74. 메리츠 김용범 부회장의 젊은 투자자를 위한 조언 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://x.com/tjgoongye/status/1961781111220318401?s=46"&gt;https://x.com/tjgoongye/status/1961781111220318401?s=46&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 투자는 지능보단 기질의 문제.&amp;nbsp;&lt;/p&gt;&lt;p&gt;2. 두려움과 공포를 얼마나 잘 다루느냐의 싸움이다.&lt;/p&gt;&lt;p&gt;3. 매일 운동하기. 1시간에서 2시간씩 20년간 했다.&lt;/p&gt;&lt;p&gt;4. 자기전 20분동안 명상.. 편도체 안정으로 공포를 컨트롤하는데 도움&lt;/p&gt;&lt;p&gt;5. 말버릇을 의식적으로 바꿀 것. (어두워지면 불을 켜라, 힘든 일은 없다 등..&amp;nbsp;)&lt;/p&gt;&lt;p&gt;6. 매일 책을 보려고 노력하고, 지식을 습관처럼 쌓기&lt;/p&gt;&lt;p&gt;7. 마법같은 비법은 역시 없었다. 그냥.. 매일 치열하게 생각하면서 노력하면 된다.&lt;/p&gt;&lt;p&gt;75. 예민한 사람이 살아남는 방법 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223991026826"&gt;https://blog.naver.com/travis88/223991026826&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 문제는 쓸데없이 뇌 에너지를 낭비하다 보면 정작 중요한 건 다 놓친다는 것. 그래서 예민할수록 불필요한 생각을 최대한 덜어내는 게 핵심인데, 그나마 내가 생각한 좋은 방법들.&lt;/p&gt;&lt;p&gt;1. 경제적으로 생각하기&lt;/p&gt;&lt;p&gt;2. 우주적으로 생각하기&lt;/p&gt;&lt;p&gt;3. 진짜 중요한 것에 집중하기&lt;/p&gt;&lt;p&gt;76. 사람이 구려지는 이유 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223990973848"&gt;https://blog.naver.com/travis88/223990973848&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 잠을 많이 잔다.&lt;/p&gt;&lt;p&gt;77. 이 원칙들이 있어야 한다 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/223990239710"&gt;https://blog.naver.com/travis88/223990239710&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 누군가에게 부정적인 생각이 들 때 '나라면 안 그럴 텐데.'라는 생각보다는 '그게 네 삶의 방식이구나, 재밌게 사네.'라고 생각하는 것이 훨씬 인생을 살 맛 나게 합니다.&lt;/p&gt;&lt;p&gt;78. 블라인드&lt;/p&gt;&lt;p&gt;1. "종착점에 거의 다다라보니 참 많은 게 허무하고 또 낮아 보입니다. 그렇게나 불가능해 보였었던 많은 일들은 사실 충분히 넘을 수 있는 담장이었고 할 수 있는 것들이었습니다. 잠시 부끄럽고 민망하고 곤란할 것 같아도 그냥 해도 됩니다. 내일이 있다는 건 축복이에요"&lt;/p&gt;&lt;p&gt;79. 누군가&lt;/p&gt;&lt;p&gt;1. 극단적인 결정이 좋은 결정일 가능성은 희박함&lt;/p&gt;&lt;p&gt;80. 행복은 만드는 것 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/gmyhhj/223998252818"&gt;https://blog.naver.com/gmyhhj/223998252818&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 우리는 행복의 조건을 찾는다. 행복은 조건이 존재함과 동시에 불행의 씨앗이 된다.&lt;/p&gt;&lt;p&gt;2. 행복은 조건이 아니다. 행복은 찾고 만들고 의미를 부여하고 감사할 줄 아는 것이다.&lt;/p&gt;&lt;p&gt;3. 일상에서 조건없이 행복을 느낄 수 있는 사람이야말로 지속적으로 행복을 느낄 수 있다.&lt;/p&gt;&lt;p&gt;81. 적절한 밸런스 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/gmyhhj/223997801663"&gt;https://blog.naver.com/gmyhhj/223997801663&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 행복 = 소유 / 욕망&lt;/p&gt;&lt;p&gt;2. 소유한 것이 많더라도 욕망이 더 크면 행복하지 못하고 소유한 것이 적더라도 욕망이 더 적으면 행복해지는 것이다. 이 둘의 밸런스를 적절하게 유지하는 것이 무엇보다 중요하다.&lt;/p&gt;&lt;p&gt;82. 누군가&lt;/p&gt;&lt;p&gt;1. 우위를 점할 수 있는 유일한 방법은 오랫동안 열심히 노력하는 것입니다. 좋아하는 일을 하다 보면 자연스럽게 그 일을 하게 되고, 쉬는 동안에도 항상 그 일에 대해 생각하게 됩니다. 이렇게 자연스럽게 몸에 배면 시간이 지나면서 큰 이점을 쌓을 수 있습니다.&lt;/p&gt;&lt;p&gt;83. 소소일상&lt;/p&gt;&lt;p&gt;1. 산다는 건 이기고 지고의 문제가 아니야. 그런 건 정말 하찮은 문제라니까. 중요한 건 말이야. 스스로 납득할 만한 좋은 사람이 되는 거지. 제대로 된 눈빛을 만들어 가는 거라구.&lt;/p&gt;&lt;p&gt;84. 차분하게 괴로운 시간을 이겨내는 몇가지 생각들 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/224010442287"&gt;https://blog.naver.com/lhd1371/224010442287&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 인생이 버거운 이유 중 하나는, 모르는데 아는 척하고 없는데 가진 척하고 안 친한데 친한 척하고 힘든데 안 힘든 척하기 때문이다.&lt;/p&gt;&lt;p&gt;2. 척하는 인생은 에너지가 몇 배가 든다.&lt;/p&gt;&lt;p&gt;85. 낯선 여성분과의 출근길 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/tkos3333/224011667262"&gt;https://blog.naver.com/tkos3333/224011667262&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 사람이 여유가 없으면 작은일에도 짜증나고, 지치고, 서운해진다. 행복하려면 어느 정도 여유가 있어야 한다.&lt;/p&gt;&lt;p&gt;86. 인생에서 만날 수 있는 귀인 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/224028187271"&gt;https://blog.naver.com/travis88/224028187271&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 나에게 새로운 세계를 소개해 주는 사람들&lt;/p&gt;&lt;p&gt;87. 이런 말을 무시해야 잘 산다. (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/224028177602"&gt;https://blog.naver.com/travis88/224028177602&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 사람들은 꼭 이유를 따진다니까. 그냥 하면 안 됩니까?&lt;/p&gt;&lt;p&gt;88. 운동의 중요성 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://x.com/esprecchiato/status/1974989228858630185"&gt;https://x.com/esprecchiato/status/1974989228858630185&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 운동은 하루 빡세게보다 매일 꾸준히가 더 효과적이다.&lt;/p&gt;&lt;p&gt;2. 하루 30분만 땀 흘려도 뇌 건강이 달라진다.&lt;/p&gt;&lt;p&gt;3. 운동은 체중보다 멘탈을 먼저 바꾼다.&lt;/p&gt;&lt;p&gt;4. 체력은 돈으로 살 수 없다.&lt;/p&gt;&lt;p&gt;5. 꾸준함이 진짜 실력이다.&lt;/p&gt;&lt;p&gt;89. 아이러니 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://x.com/4_wonderfullife/status/1974843016667160690"&gt;https://x.com/4_wonderfullife/status/1974843016667160690&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 운동을 못할수록 더 운동을 해야 하고 보는 눈이 없으면 더욱 더 많은 것을 보면서 눈을 길러야 하고 말을 잘 못하면 그만큼 더 말할 기회를 가져야 하고 사회생활을 못할수록 사회에 스스로를 더 자주 내던져야 한다는 것이..&lt;/p&gt;&lt;p&gt;2. 못한다고 피하면 점점 더 못해질 수 밖에 없다는 것이 인생의 아이러니&lt;/p&gt;&lt;p&gt;90. 트레이더의 운이란? (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://m.blog.naver.com/song7639/224032232798"&gt;https://m.blog.naver.com/song7639/224032232798&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 확률적 노출을 늘리는 것 (Exposure Theory)&lt;/p&gt;&lt;p&gt;1. 운이 좋은 사람의 특징은&lt;/p&gt;&lt;p&gt;1. 더 많은 사람, 정보, 사건에 노출됨&lt;/p&gt;&lt;p&gt;2. 정보를 개방적으로 인식함&lt;/p&gt;&lt;p&gt;3. 실패를 빠르게 샘플링함&lt;/p&gt;&lt;p&gt;2. 인지적 편향을 통제하는 것 (Cognitive Bias Calibration)&lt;/p&gt;&lt;p&gt;1. 운이 나쁜 사람은 같은 사건을 비관적으로 해석하는 경향이 있다.&lt;/p&gt;&lt;p&gt;3. 감정 에너지의 관리 (Affect Regulation)&lt;/p&gt;&lt;p&gt;1. 운이 좋은 사람일수록 감정적 안정성과 에너지 일관성이 높다.&lt;/p&gt;&lt;p&gt;91. 과거의 10년 앞으로 10년 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/lhd1371/224033919463"&gt;https://blog.naver.com/lhd1371/224033919463&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;92. 세상에 정답이라고 알려진 방향들과 내가 원하는 방향이 일치하지 않을때.. 어떻게 해야 할까?&lt;/p&gt;&lt;p&gt;93. 살명서 깨달은 것 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/224034880340"&gt;https://blog.naver.com/travis88/224034880340&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 된다고 생각하고 갔을 경우 -&amp;gt; 될 수도 있고, 안될 수도 있음.&lt;/p&gt;&lt;p&gt;2. 시작할 때부터 안 된다고 생각하는 경우 -&amp;gt; 매우 높은 확률로 진짜 안 된다.&lt;/p&gt;&lt;p&gt;94. Do not fear&lt;/p&gt;&lt;p&gt;95. 인생 편해지는 태도 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/224036503899"&gt;https://blog.naver.com/travis88/224036503899&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 인생을 너무 심각하게 받아들이지 마라&lt;/p&gt;&lt;p&gt;96. 하수라고 느껴지는 사람 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/224036513894"&gt;https://blog.naver.com/travis88/224036513894&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 표정 관리가 아예 안 되는 사람&lt;/p&gt;&lt;p&gt;97. 모든 문제의 본질 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/224039292182"&gt;https://blog.naver.com/travis88/224039292182&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 인간에게 일어나는 거의 모든 문제의 본질이 있음. -&amp;gt; 너무 큰 기대&lt;/p&gt;&lt;p&gt;98. No winter lasts forever; no spring skips its turn. (영원한 겨울은 없고, 봄은 자신의 차례를 거르지 않는다.)&lt;/p&gt;&lt;p&gt;99. 직업으로서의 투자자(하루키의 '직업으로서의 소설가' 를 읽고) (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://m.blog.naver.com/travelingcompanion/224045296627"&gt;https://m.blog.naver.com/travelingcompanion/224045296627&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 판단 보다는 관찰&lt;/p&gt;&lt;p&gt;2. 꾸준한 몸관리&lt;/p&gt;&lt;p&gt;100. 심플하지만 강력한 마인드 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/224046377433"&gt;https://blog.naver.com/travis88/224046377433&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 굳이 신경을 쓸 필요가 없는 것에 신경을 쓰지 않는 것&lt;/p&gt;&lt;p&gt;101. 인생이 정말 답답하고 벽을 마주쳤을때 당장은 답이 안보이더라도 하루하루 잘 먹고 잘 자고 좋은 책 읽고 좋은 분들 만나서 이야기하면서 버티다보면 좋은시절이 오는게 아니라 나쁜시절은 가더라구요..&lt;/p&gt;&lt;p&gt;102. 공짜로 돈을 뿌리는 것 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/224055338406"&gt;https://blog.naver.com/travis88/224055338406&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. "즉각적으로 나의 기분을 좋게 하는 것" 대다수가 중장기적으로 보면 나를 망치는 것들임&lt;/p&gt;&lt;p&gt;103. 40대 아저씨가 돈으로 진정 사고 싶은 것들 (자산 배분. 등산) (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://cafe.naver.com/vilab/274934?tc=shared_link"&gt;https://cafe.naver.com/vilab/274934?tc=shared_link&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 자산배분을 할 때 양적 성장에 집중해서 수익률이나 자본효율만을 고려하기 보다는 &lt;strong&gt;나에게 중요한 가치를 충족시켜주는 방향으로 돈이 자연스럽게 흘러갈 수 있도록 틀을 만드는 것&lt;/strong&gt;입니다.&lt;/p&gt;&lt;p&gt;2. 건물을 지을 때 사용자의 일상, 취향, 동선 등에 맞는 공간을 구상하고 거기에 맞는 설계도를 먼저 그린 후에 설계도 대로 먹줄을 튕기고 거푸집(형틀)을 만들어 콘크리트를 붓습니다. 이제 콘크리트가 굳으면 처음 머릿속에서 구상한 공간들이 현실로 구현이 됩니다. 똑같은 콘크리트를 붓더라도 설계도에 따라 그 건물에서 사는 사람들은 전혀 다른 느낌으로 다른 삶을 살게 됩니다.&lt;/p&gt;&lt;p&gt;3. 아저씨는 머릿속에서 앞으로 살고 싶은 삶의 방식을 상상해보고, 노동과 투자로 벌게 될 돈이 그런 삶의 방식을 구현하는데 흘러갈 수 있도록 설계도를 그려봅니다. 이제 먹줄을 튕기고 거푸집을 대고 나면, 힘들기도 하고 재밌기도 한 일상의 노동과 투자의 펌프가 루틴하게 작동하면서 콘크리트를 거푸집 안으로 내뿜어 줍니다.&lt;/p&gt;&lt;p&gt;4. &lt;strong&gt;굳기 전에는 똑같은 콘크리트(돈) 이지만, 거푸집의 모양에 맞게 단단하게 굳어지고 나면 모두 다른 각양각색의 공간과 의미를 만들어 냅니다. '나의 돈은 어떤 모양으로 굳어져서 어떤 공간과 의미를 만들어 낼까'&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;104. 프리드리히 니체&lt;/p&gt;&lt;p&gt;1. 인간은 단순히 살아 있는 존재가 아니라, 자신을 넘어서는 존재다.&lt;/p&gt;&lt;p&gt;2. 오늘의 나를 어제보다 낫게 만드는 일, 그것이 삶의 본질이다. 세상은 너를 정의하려 하고, 너를 규정하려 한다. 그러나 그때마다 저항하라. 네가 내일도 어제와 같은 인간이라면, 그것이야말로 가장 큰 비극이다. 인간은 자신을 초월하지 않으면 곧 썩는다.&lt;/p&gt;&lt;p&gt;105. 돈 쓰는 법. wide funnel, tight filter! (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://m.blog.naver.com/mynameisdj/224063101077"&gt;https://m.blog.naver.com/mynameisdj/224063101077&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 가장 좋은 독서법은 많이 시도하고, 강력한 필터로 걸러내는 것이다.&lt;/p&gt;&lt;p&gt;2. 좋아할지 안 할지는 시도해 보기 전에는 절대 알 수 없다. 그래서 가능한 한 많이 시도해야 하고, 동시에 즉시 거절하는 필터가 필요하다.&lt;/p&gt;&lt;p&gt;3. "당신이 좋아하는 것에는 아낌없이 쓰되, 좋아하지 않는 것에는 무자비하게 잘라내라" 예컨대 옷을 좋아하지만 차에는 관심이 없어서, 부자처럼 옷을 입지만 차는 돈 아끼는 사람처럼 탄다.&lt;/p&gt;&lt;p&gt;4. 여행, 음식, 경험, 스포츠, 의류 뭐든 지금보다 조금 더 써보라. 하지만 행복을 주지 않는다면 즉시 멈춰라. 나쁜 책을 던져버리는 것처럼. 충분히 반복하다 보면, 당신에게 딱 맞는 "이상하지만 특별한 것"을 발견하게 될 것이다. 동시에 불필요한 소비는 잘라내고, 행복을 주는 소비에 돈을 쓸 수 있게 된다.&lt;/p&gt;&lt;p&gt;106. 자기 몸을 돌보는 의무&lt;/p&gt;&lt;p&gt;1. 삶을 바꾸는 과정은 완전히 하나이다. 몸과 마음을 나눠 생각하는 잘못된 착각에 빠진 우리 현대인과 달리, 그리스 현자들은 육체와 영혼의 탁월성을 똑같이 함께 추구했다.&lt;/p&gt;&lt;p&gt;2. "체력과 정신력이 조화롭게 집중되면, 삶은 저 스스로 힘을 얻는다." 단순한 이 사실이야말로 어쩌면 우리가 알아야할 궁극의 인생 지혜인 듯 싶다.&lt;/p&gt;&lt;p&gt;107. 기분 관리를 잘 하는 사람들 특징 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/224070259007"&gt;https://blog.naver.com/travis88/224070259007&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 기분이 태도가 되면 안 된다.&lt;/p&gt;&lt;p&gt;108. 의존하면 망한다 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/224076837754"&gt;https://blog.naver.com/travis88/224076837754&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 롤 모델, 멘토의 장점을 배우고 그것을 자신만의 방식으로 재창조하는 것임. 결국에는 누구나 자신의 것을 만들어야 하니까. 물론 초반에는 복붙 수준으로 배우는 것이 중요하지만.&lt;/p&gt;&lt;p&gt;2. 결국 본질적인 건 '나 자신'의 레벨을 올리는 것. 멘토, 롤모델 전략을 쓴다고 해서 상대에게 나의 모든 것을 의탁해서는 안 됨. 부작용이 너무 큼&lt;/p&gt;&lt;p&gt;109. 행운이란, 준비가 기회를 만나는 것이다. - 세네카&lt;/p&gt;&lt;p&gt;110. 사람을 평가하는 중요한 기준 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/travis88/224111453939"&gt;https://blog.naver.com/travis88/224111453939&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 저 사람은 자신의 말에 책임을 질까?&lt;/p&gt;&lt;p&gt;111. 무엇이든 시작하기 전이 가장 어렵죠.&lt;/p&gt;&lt;p&gt;1. 시작하는 순간 대부분의 두려움은 사라진다는걸. 일단 해보는게 중요하다는 것을요.&lt;/p&gt;&lt;p&gt;2. 시작이 거창할 필요는 없습니다. 중요한 건 시도하고, 다시 시도하는 것 입니다. 아주 작은 발걸음 하나가 새로운 풍경을 열어주기 때문입니다.&lt;/p&gt;&lt;p&gt;3. 두려움은 늘 시작 앞에 있습니다. 그리고 그 시작의 주변에는 귀찮음도 있죠. 하지만 그 시작 뒤에는, 우리가 아직 보지 못한 세계가 기다리고 있다는 걸 잊지 말아야 합니다.&lt;/p&gt;&lt;p&gt;112. 천재 소설가가 말하는 시간을 현명하게 보내는 방법 (ft. 밀란 쿤데라 '느림') (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://www.youtube.com/watch?v=gVxFSNjc58s"&gt;https://www.youtube.com/watch?v=gVxFSNjc58s&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 인간은 무언가를 기억하려 할 때 본능적으로 행동을 늦추지만, 반대로 어떤 일을 빨리 잊고 싶을 때는 발걸음을 재촉합니다. 느림은 내면을 살피고 경험을 온전한 기억으로 엮어내는 과정인 반면, 빠름은 시간의 흐름을 분절시켜 경험을 휘발시키고 망각을 가속화하는 힘을 가집니다.&lt;/p&gt;&lt;p&gt;2. 삶이 앙상해지지 않고 의미 있는 기억들로 채워지기 위해서는, 의도적으로 속도를 늦추고 순간을 음미하는 '느림의 시간'을 회복해야 한다.&lt;/p&gt;&lt;p&gt;113. 삶이라는 멋진 모순, 우연과 노력 사이에서 ('25년 상반기 투자와 생각) (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://blog.naver.com/r123k120/223916981750"&gt;https://blog.naver.com/r123k120/223916981750&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. 무질서에 대한 생각&lt;/p&gt;&lt;p&gt;1. 이 세상은 무질서하고 우리 개인이 이룩한 것들 역시 헐거운 질서의 역학에서 아주 '우연'하게 파생한 산물이라고 느끼는 것이 사실이다.&lt;/p&gt;&lt;p&gt;2. 운명을 움켜쥐려는 것보다 자연스러움 속에서 일상을 의미 있는 것들로 채우고 'Let it be'하는 달관의 자세에 대해 다시 한 번 생각해보면 어떨까.&lt;/p&gt;&lt;p&gt;3. 우리에게 주어진 일상과 자유의 소중함이 얼마나 위대하고, 또 그저 개개인의 노력으로만 얻어진 것이 아님에 대한 생각&lt;/p&gt;&lt;p&gt;2. 모순에 대한 생각&lt;/p&gt;&lt;p&gt;1. 뒤바뀐 인과관계의 오류(Fallacy of reversed casuality). 원인 -&amp;gt; 결과의 관계를 결과 -&amp;gt; 원인으로 혼동하여 사용하는 경우를 일컫는다.&lt;/p&gt;&lt;p&gt;2. 무심과 성의&lt;/p&gt;&lt;p&gt;3. 그냥 하는 것(Just doing it)이 무심과 결을 같이 하지 않나 한다. 사실 그냥 하는 행위를 지속적으로 영위하기 위해서는 지루함을 느끼지 않아야 되며, 업에 대한 몰입과 진정성, 그리고 깊은 애정이 필요하다. 따라서 무심을 이어나가는 이들은 필연 성의를 갖게 될 수 밖에 없을 것이다.&lt;/p&gt;&lt;p&gt;3. 진정성&lt;/p&gt;&lt;p&gt;1. 주위에 투자 또는 다른 영역에서 성공한 이들을 보면 빠짐없이 진정성을 가지고 있음을 보게 된다. 투자에 있어서도 과거에 어떠한 분야에 진정성 있게 몰입하여 성과를 이뤄본 경험이 있는 이들이 더 빛을 발하는 모습을 보게 돼 곤 한다.&lt;/p&gt;&lt;p&gt;4. 운&lt;/p&gt;&lt;p&gt;1. 인생은 B(Birth)와 D(Death) 사이의 C(Choice)라는 잘 폴 사르트의 명언을 좋아하는데, 얼마 전 C(Coincidence)를 넣어야 한다는 지적호기심님의 블로그 글을 봤다. 정말이지, 인생의 선택만큼이나 운과 '우연'은 우리의 삶을 깊숙히 지배하고 있다. 그리고 이것은 무질서와 모슨의 필욘적인 존재와도 연결이 되어있다.&lt;/p&gt;&lt;p&gt;114. 알면 불편하지만, 그래도 아셔야 하는.. 한국 부동산의 자본주의 원리 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://m.slrclub.com/v/free/41426285"&gt;https://m.slrclub.com/v/free/41426285&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;115. "하루하루는 성실하게, 인생 전체는 되는대로" | 영화평론가 이동진 1부 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://www.youtube.com/watch?v=2YLonjnptTw"&gt;https://www.youtube.com/watch?v=2YLonjnptTw&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. &lt;strong&gt;1. 인생관: "하루하루는 성실하게, 인생 전체는 되는대로"&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;1. &lt;strong&gt;의미:&lt;/strong&gt;&amp;nbsp;젊은 시절에는 인생 전체를 성실하게 살면 특정 목적지에 도달할 것이라 믿었지만, 지금은 인간의 힘보다 외부의 힘(운명, 우연)이 훨씬 강함을 인정합니다.&lt;/p&gt;&lt;p&gt;2. &lt;strong&gt;태도:&lt;/strong&gt;&amp;nbsp;인간이 통제할 수 있는 것은 기껏해야 '하루' 정도입니다. 하루하루 성실히 살되, 인생의 거대한 흐름이나 결과는 내가 어쩔 수 없음을 받아들이고 순응하겠다는 뜻입니다.&lt;/p&gt;&lt;p&gt;116. 재능과 꿈에 대하여 (&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://www.youtube.com/watch?v=vGzEXcB2KXw"&gt;https://www.youtube.com/watch?v=vGzEXcB2KXw&lt;/a&gt;)&lt;/p&gt;&lt;p&gt;1. &lt;strong&gt;3. 재능에 대한 새로운 정의&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;- &lt;strong&gt;"재능은 해보지 않은 사람들만이 매달리는 허상이다."&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;- 프로 작가들은 "이 일을 하기 위해 재능이 있는가?"를 묻기보다, "내가 가진 것 중 무엇을 써서 이 일을 해낼까?"를 고민합니다.&lt;/p&gt;&lt;p&gt;- 특정 직업을 갖는 것 자체에는 압도적인 재능이 필요하지 않을 수 있습니다. 재능은 '세계 1등'이나 '올림픽 금메달' 같은 극단의 목표를 이룰 때 필요한 요소일 뿐입니다.&lt;/p&gt;&lt;p&gt;2. &lt;strong&gt;4. 과정 자산의 힘&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;- 작가는 학원 강사, 모델, 라디오 진행 등 다양한 아르바이트 경험이 작가 생활에 모두 도움이 되었다고 말합니다.&lt;/p&gt;&lt;p&gt;- 실패나 거절의 경험도 단순히 버려지는 비용이 아니라, 다음 단계로 나아가기 위한 '과정 자산'으로 해석하고 활용해야 합니다.&lt;/p&gt;</description><pubDate>Mon, 22 Dec 2025 09:02:00 +0900</pubDate><guid>http://blex.me/@mildsalmon/2025%EB%85%84%EB%8F%84%EC%97%90-%EC%9D%BD%EC%97%88%EB%8D%98-%EA%B2%83%EB%93%A4</guid></item><item><title>[책] 안티프레질</title><link>http://blex.me/@mildsalmon/%EC%95%88%ED%8B%B0%ED%94%84%EB%A0%88%EC%A7%88</link><description>&lt;h1&gt;1. 트라이애드(Triad)&lt;/h1&gt;&lt;p&gt;프래질(Fragile), 강건함(Robust), 안티프래질(Antiifragile)을 트라이애드라고 부른다.&lt;/p&gt;&lt;p&gt;나는 이 책을 읽기 전에 안티프래질이 Anti + Fragile이라서 프래질하지 않은 것, 즉 외부 충격에도 깨지지 않는 것이라고 생각했다. 프래질이 유리잔이면 안티프래질은 스테인리스 잔 정도를 생각했다. 그러나 그런 개념이 아니였다. 안티프레질은 외부 충격을 받으면 오히려 더 튼튼해지고 좋아지는 것을 의미한다. 즉 스테인리스 잔은 강건함이고 안티프레질은 평소에는 흐물거리다가 충격을 받으면 단단해지거나 갯수가 늘어나는 잔을 의미한다. (잔으로 비유했기 때문에 더 헷갈려지진다.)&lt;/p&gt;&lt;h1&gt;2. 안티프레질한 상태로 이동하거나 프래질한 시스템 막기&lt;/h1&gt;&lt;h3&gt;2.1. 칠면조의 오류: 프래질의 원인과 탐지&lt;/h3&gt;&lt;p&gt;칠면조는 1000일 동안 먹이를 주인을 보며 &lt;strong&gt;안전하다&lt;/strong&gt;고 믿지만, 1001일(추수감사절)째에 목이 잘린다. 이는 과거부터 쭉 이어진 데이터가 안전하다는 증거로 착각하는 현상이다. (증거의 부재를 부재의 증거로 착각하는 것)&lt;/p&gt;&lt;p&gt;어떤 현상이 일어났다는 증거가 없다고 해서, 그 현상이 일어나지 않을 것이라는 증거가 되지 않는다는 의미다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;증거의 부재&lt;/strong&gt;: 과거에는 AI가 창의적인 활동을 한다는 증거가 없었다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;오류&lt;/strong&gt;: 사람들은 이것을 AI에게는 창의성이 부족하다는 증거로 받아들였다. (창의적인 활동을 하지 않았으니 앞으로도 창의적인 활동을 하지 못할 것이다.)&lt;/p&gt;&lt;p&gt;&lt;strong&gt;실제&lt;/strong&gt;: 생성형 이미지 모델 등으로 가장 활발한 분야가 예술 분야다.&lt;/p&gt;&lt;p&gt;우리의 직관은 과거에 발생한 사건이 미래에도 쭉 이어질 것이라는 선형적인 사고를 한다. 그러나 현실은 비선형으로 움직인다.&lt;/p&gt;&lt;h3&gt;2.2. 비아 네가티바(Via Negativa): 프래질 제거의 기술&lt;/h3&gt;&lt;p&gt;좋은 것을 더하기보다 나쁜 것(부자연스러운 것, 해로운 것)을 제거하는 전략이다.&lt;/p&gt;&lt;p&gt;트라이애드의 왼쪽(프래질)에서 벗어나는 가장 확실한 방법이다. 무엇이 옳은지(안티프레질) 아는 것은 어렵지만, 틀린것(프래질)을 제거하는 것은 훨씬 쉽고 명확하기 때문이다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;프래질한 접근(더하기)&lt;/strong&gt;: 갓생 스케줄의 함정 (일찍 기상하기, 세수하기, 야외에서 러닝하기, 독서하기 등을 촘촘하게 채운 스케줄.)&lt;/p&gt;&lt;p&gt;&lt;strong&gt;비아 네가티바(빼기)&lt;/strong&gt;: 오늘 하지 말아야할 것 정하기 (불필요한 유튜브 보지 않기)&lt;/p&gt;&lt;h3&gt;2.3. 바벨 전략(Barbell Strategy): 안티프레질의 실현 수단&lt;/h3&gt;&lt;p&gt;바벨 전략은 트라이애드의 오른쪽(안티프래질)으로 이동하기 위한 구체적인 실행 방법이다.&lt;/p&gt;&lt;p&gt;어설프게 중간 정도의 위험을 감수하는 것이 아니라, 한쪽에서는 극도로 안전하게, 다른 한쪽에서는 극도로 모험적으로 행동하는 이원적 전략이다. 손실은 제한하고 이익은 무한대로 만드는 구조(자산의 90%를 절대 잃을 일 없는 현금 등 극도의 안전한 곳에 분배하여 하방 리스크를 제거한다. 나머지 10%는 가장 위험하지만 성공하면 수백 배의 이익을 주는 곳에 분배하여 상방 이익을 추구한다.)를 구성한다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;직업 (커리어)&lt;/strong&gt;: 낮에는 안정적인 회사에서 근무하고 밤에는 새로운 공부나 활동 등의 모험을 한다.&lt;/p&gt;&lt;h3&gt;2.4. 승부의 책임(Skin in the Game): 시스템의 균형 유지 장치&lt;/h3&gt;&lt;p&gt;승부의 책임은 전체 시스템이 프래질해지는 것을 막는 윤리적 안전장치이다.&lt;/p&gt;&lt;p&gt;자신의 행동 결과에 대해 이익뿐만 아니라 손실(리스크)도 함께 부담하는 것이다.&lt;/p&gt;&lt;p&gt;승부의 책임이 없으면, 누군가는 이익만 챙기고 손실(프래질)은 남에게 떠넘긴다. 이는 사회 전체를 프래질하게 만드는 주범이다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;의견보다 행동을 확인하라&lt;/strong&gt;: 누군가 어떤 주장을 한다면 "당신의 의견은 무엇입니까?"라고 묻지 말고 "그래서 당신의 포트폴리오에는 무엇이 들어있습니까?"라고 물어야 한다.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;배팅&lt;/strong&gt;: 어떤 사람이 특정 자산(주식 등)이 하락할 것이라고 비판하고 있다면 그 사람은 하락에 배팅하여 자신의 의견에 책임을 져야 한다. 그래야 신뢰할 수 있다.&lt;/p&gt;&lt;h1&gt;3. 결론&lt;/h1&gt;&lt;p&gt;우리는 세상의 불확실성을 제대로 예측할 수 없음에도 안전하다고 착각하여 시스템을 프래질하게 만든다. 이 위험에서 벗어나 안티프래질해지기 위해서는 1. 나쁜 것과 취약한 요소를 먼저 제거 2. 최악의 상황에서도 망하지 않도록 안전을 확보한 뒤 모험을 추구해야 한다. 그리고 사회적으로는 남에게 위험을 떠넘기고 자신은 안전한 곳에 숨는 사람들을 막기 위해 반드시 승부의 책임을 물어야 한다.&lt;/p&gt;</description><pubDate>Sat, 13 Dec 2025 20:11:47 +0900</pubDate><guid>http://blex.me/@mildsalmon/%EC%95%88%ED%8B%B0%ED%94%84%EB%A0%88%EC%A7%88</guid></item><item><title>24년에 읽은 것 정리</title><link>http://blex.me/@mildsalmon/24%EB%85%84%EC%97%90-%EC%9D%BD%EC%9D%80-%EA%B2%83-%EC%A0%95%EB%A6%AC</link><description>&lt;h4 id="a-김지수의-인터스텔라-질병없이-오래-살고-싶"&gt;A. &lt;a href="https://biz.chosun.com/topics/kjs_interstellar/2024/01/13/WGKZE4R5AVAO7H6XXGOTPPTNAU/"&gt;[김지수의 인터스텔라] “질병없이 오래 살고 싶다면… 새해엔 이것만 피하라” 美 최정상 장수의학자의 일침&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;노화와 관련된 기능 저하(허약함, 만성 질환, 기억력 및 거동 능력 저하 등)는 자연스러운 현상이 아니다. 설탕과 녹말을 너무 많이 섭취하고 앉아서 생활하기 때문에 벌어지는 현상이다.&lt;/li&gt;
&lt;li&gt;하루에 12 ~ 16시간 동안 식사를 중단하는 간헐적 단식으로 필요한 모든 이점을 얻을 수 있다. (저녁 6시 ~ 아침 6시, 8시 = 12 ~ 14시간)&lt;/li&gt;
&lt;li&gt;매일 20분씩 걷고 스쿼트나 플랭크 같은 근력 운동을 주 3~4회&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="b-최고의-영양제는-수면"&gt;B. &lt;a href="https://x.com/DrEcsta/status/1756261965998309497"&gt;최고의 영양제는 수면&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;최고의 영양제는 잠이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="c-삶이-더-좋아지는-것에는-편법이-없다"&gt;C. &lt;a href=""&gt;삶이 더 좋아지는 것에는 편법이 없다.&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;기분을 관리하고 삶을 관리하는 방법으로는 의무를 다하는 것 하나만 기억하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="d-일론머스크-5단계"&gt;D. &lt;a href="https://blog.naver.com/gmyhhj/223373134945"&gt;일론머스크 5단계&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;질문하기 (요구사항이 덜 멍청해지고 명확해질 때까지 질문해야 한다.)&lt;/li&gt;
&lt;li&gt;제거 (최대한 프로세스를 제거하는 쪽으로 억지로 노력을 해야 균형이 맞다.)&lt;/li&gt;
&lt;li&gt;단순화&lt;/li&gt;
&lt;li&gt;가속화 (모든 곳에서 언제든 더 빨리 할 수 있는 방법이 있다.)&lt;/li&gt;
&lt;li&gt;자동화 (위 과정을 반복하면 각 단계별로 점검하고 테스트하는게 필요 없어지는 순간이 온다. 자동화할 수 있다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="e-개저씨-되기-싫으면-움직여라이-근육-키우면"&gt;E. &lt;a href="https://www.joongang.co.kr/article/25239085"&gt;'개저씨' 되기 싫으면 움직여라…이 근육 키우면 90대도 거뜬 [마흔공부②]&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;40대에 몸 쓰는 거 싫어하고 머리 쓰는 거 주저하면, 그 이후에 빛을 잃어요. 더 열심히 움직이셔야 합니다.&lt;/li&gt;
&lt;li&gt;스트레스 해소에는 걷기 (대신 걸을 때 휴대폰 보거나 음악 듣지 않기. 내 호흡과 관절의 느낌, 소리를 듣고 풍경을 보면서 그냥 걷기)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="f-돈을-벌어-결국-무엇을-하는데요"&gt;F. &lt;a href="https://blog.naver.com/alex267/223422964547?"&gt;돈을 벌어 결국 무엇을 하는데요?&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;만족 지연을 하지 말고 만족하는 삶을 살아라.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="g-아무도-없는-바다"&gt;G. &lt;a href="https://product.kyobobook.co.kr/detail/S000001966316"&gt;아무도 없는 바다&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;하고 있어라. 그게 무엇이든 지금이 어떤 상황이든, 일단 하고 있어라. 하고 있지 않으면 그 무엇도 되지 않는다. 하고 있으면 하고 있는 사이 무엇이든 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="h-미야자키-하야오의-인생관"&gt;H. &lt;a href="https://blog.naver.com/travis88/223447512899?"&gt;미야자키 하야오의 인생관&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;열심히 산다? 그런 건 당연한거지. 열심히 해도 안 되는 인간들이 모여 사는 곳이 우리 인생이니까요.&lt;/li&gt;
&lt;li&gt;죽을힘을 다해야 겨우 살아남는게 우리 인생입니다. 좋은 날이 쉽게 올거라 기대하지 말고 괴로워도 하루하루 버티면서 열심히 살면 그걸로 되는 겁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="i-the-days-are-long-but-t"&gt;I. &lt;a href="https://blog.samaltman.com/the-days-are-long-but-the-decades-are-short"&gt;The days are long but the decades are short&lt;/a&gt;&lt;/h4&gt;&lt;ol start="12"&gt;
&lt;li&gt;Minimize your own cognitive load from distracting things that don’t really matter.  It’s hard to overstate how important this is, and how bad most people are at it.  Get rid of distractions in your life.  Develop very strong ways to avoid letting crap you don’t like doing pile up and take your mental cycles, especially in your work life.&lt;/li&gt;
&lt;li&gt;Do new things often.  This seems to be really important.  Not only does doing new things seem to slow down the perception of time, increase happiness, and keep life interesting, but it seems to prevent people from calcifying in the ways that they think.  Aim to do something big, new, and risky every year in your personal and professional life.&lt;/li&gt;
&lt;li&gt;The days are long but the decades are short.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="j-내가-이-일을-하는-이유"&gt;J. &lt;a href="https://stibee.com/api/v1.0/emails/share/vbegq9eiAZffbF5I_TIg8yOnexi2KGw"&gt;내가 ‘이 일’을 하는 이유&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;왜? 그 일을 하시나요?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="k-투자한다며-종목-뭐-사냐고-묻는-사원-대리"&gt;K. &lt;a href="https://blog.naver.com/tkos3333/223496963846"&gt;투자한다며 종목 뭐 사냐고 묻는 사원 대리에게 꼰대소리 했더니&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;지금 본인 역량을 키워야 합니다.&lt;/li&gt;
&lt;li&gt;업무를 월급받는 만큼만 하지말고, 하루 8시간 9시간 대충 시간만 때우다 가지말고, 몸은 사원이지만 일은 과장 부장님처럼 하십시오. 그렇게 갈아넣으십시오. 몇년간 결혼할때까지요.&lt;/li&gt;
&lt;li&gt;본인 분야에서 최고가 되어보십시오. 최고가 되지 못하더라도 인생의 한순간은 전력질주를 해보십시오. 지금이 그런 시기입니다! 그런 나이고요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="l-어딘가에-기록된-메모"&gt;L. 어딘가에 기록된 메모&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;삶을 살아가면서 변명은 의미가 없다. 그저 지금 상황을 어떻데 하면 극복할 수 있을지 고민하고 해결하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="m-사격선수-김예지가-말하는-노력"&gt;M. &lt;a href="https://blog.naver.com/travis88/223576490743?"&gt;사격선수 김예지가 말하는 노력&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;남들이 하는만큼만 하는 건 노력이라기 보다는 생활임.&lt;/li&gt;
&lt;li&gt;남들보다 잘 살고는 싶은데 남들보다 더 노력하고 싶진 않다? -&amp;gt; 스트레스만 쌓임.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="n-세상속으로-박용근-천재는-없다httpsbi"&gt;N. &lt;a href="https://biz.heraldcorp.com/article/637752"&gt;[세상속으로 - 박용근] 천재는 없다&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;수 년간 관찰 끝에 내가 내린 결론은 &amp;lt;천재는 없다&amp;gt;였다. 천재라고 생각했던 대부분의 사람들은 자기 일이 너무 좋아서, 하루 하루 최선을 다해 몰입하는 평범한 사람들이었다. 오히려 단순한 암기나 계산 등의 지적 능력은 일반인보다 특별한 게 없었다. 일하는 것이 좋아서, 새벽부터 밤 늦게까지 미친 듯이 빠져있고, 샤워하러 가는 시간도 아까워서 사무실에 샤워실을 만들고, 해외 출장 중 비행기 안에서도 경유지에서도 한 순간도 마음을 놓지 않고 열정적으로 사는 사람들이었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="o-인생의-목표가-없어서-허무한-사람들을-위해"&gt;O. &lt;a href="https://brunch.co.kr/@core-cure/267"&gt;인생의 목표가 없어서 허무한 사람들을 위해&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;인간이 마땅히 삶을 통해서 추구해야 하는 목적은 무엇인가?&lt;/li&gt;
&lt;li&gt;&amp;quot;인생의 목표? 삶의 목적? 그런 건 없다. 오히려 목표를 세워야 한다는 생각 자체가 문제다.&amp;quot;&lt;/li&gt;
&lt;li&gt;&amp;quot;목적&amp;quot;을 찾으려는 노력 대신 &lt;strong&gt;&amp;quot;지금 이 순간에 몰입하며 온전히 살아가는 것&amp;quot;&lt;/strong&gt;을 찾으라고 제안.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="p-대기업-입사하고-워라밸을-버렸더니-바뀐-점"&gt;P. &lt;a href="https://blog.naver.com/tkos3333/223266135632"&gt;대기업 입사하고 워라밸을 버렸더니 바뀐 점&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;한번 내 모든걸 퍼부어서 달릴때가 있는겁니다. 원없이 일에 미쳐보는 시기가 필요합니다. 내 역량을 최고로 끌어올릴 수 있는 시기요! 사회초년생은 지금이 그럴 시기입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="q-하루하루는-성실하게-인생-전체는-되는대로"&gt;Q. &lt;a href="https://blog.naver.com/lhd1371/223621492133?"&gt;하루하루는 성실하게, 인생 전체는 되는대로&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;하루하루는 성실하게 인생 전체는 되는대로&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="r-세상은-운빨이다"&gt;R. &lt;a href="https://blog.naver.com/travis88/223641231122?"&gt;세상은 운빨이다&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;우리는 항상 어떤 행동을 통해서 세상에 어떤 여러가지 씨앗을 뿌리면 되는 거지. '꼭 이걸 해야 한다'라는 집착을 버리는 것이 더 좋을 것 같아요. 어차피 내가 만드는 게 아니기 때문에.&lt;/li&gt;
&lt;li&gt;삶은 우연으로 이루어져 있기 때문에,, 막 살라는 의미는 아니고 우연을 일으킬 수 있는 여러가지 씨앗을 많이 뿌려 놓아야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="s-일사일언-무리하지-않으면-아무것도-이룰-수"&gt;S. &lt;a href="https://n.news.naver.com/article/newspaper/023/0003867331?date=20241031"&gt;[일사일언] 무리하지 않으면 아무것도 이룰 수 없다&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;나는 무엇을 얻고 싶은가? 그래서 나는 그것을 위해 약간의 무리를 감수하고 있는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="t-누구에게나-반드시-필요한-루틴"&gt;T. &lt;a href="https://blog.naver.com/travis88/223654540317?"&gt;누구에게나 반드시 필요한 루틴&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;모든 신체 문제에는 해결법이 있다.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;스트레스 -&amp;gt; 코르티솔 -&amp;gt; 산책&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;통증 -&amp;gt; 엔드로핀 -&amp;gt; 달리기&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;피로 -&amp;gt; 도파민 -&amp;gt; 냉수욕&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;슬픔 -&amp;gt; 세로토닌 -&amp;gt; 과일 섭취&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;분노 -&amp;gt; 세로토닌 -&amp;gt; 음악 감상&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;불면증 -&amp;gt; 멜라토닌 -&amp;gt; 햇볕 쬐기&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;내적 소모 -&amp;gt; 자기 인식 -&amp;gt; 마음 챙김 연습&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;두려움 -&amp;gt; 아드레날린 -&amp;gt; 마음 챙김 호흡&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;나태함 -&amp;gt; 아드레날린 -&amp;gt; 고갱도 짧은 운동&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;미루기 -&amp;gt; 도파민 -&amp;gt; 작은 목표 설정 및 즉시 실행&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="u-냉소주의"&gt;U. &lt;a href="https://blog.naver.com/travis88/223670667499?"&gt;냉소주의&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;어떤 것의 리스크를 점검한다는 것은 그것을 하기 위해 점검하는 겁니다. 냉소주의는 하지 않기 위해서 하는 거에요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="v-자기관리론-1-걱정스런-상황을-해결하는-마"&gt;V. &lt;a href="https://blog.naver.com/lhd1371/223703778108?"&gt;[자기관리론] 1. 걱정스런 상황을 해결하는 마법의 공식&lt;/a&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;제1단계, &amp;quot;스스로에게 일어날 수 있는 최악은 어떤 것인가?&amp;quot; 하고 물어보라.&lt;/li&gt;
&lt;li&gt;제2단계. 필요한 경우 최악을 받아들일 준비를 하라.&lt;/li&gt;
&lt;li&gt;제3단계. 침착하게 최악의 상황을 개선하기 위해 노력하라.&lt;/li&gt;
&lt;/ul&gt;
</description><pubDate>Tue, 31 Dec 2024 23:47:48 +0900</pubDate><guid>http://blex.me/@mildsalmon/24%EB%85%84%EC%97%90-%EC%9D%BD%EC%9D%80-%EA%B2%83-%EC%A0%95%EB%A6%AC</guid></item><item><title>24년 회고를 하겠습니다. 근데 이제 ai를 곁들인</title><link>http://blex.me/@mildsalmon/24%EB%85%84-%ED%9A%8C%EA%B3%A0%EB%A5%BC-%ED%95%98%EA%B2%A0%EC%8A%B5%EB%8B%88%EB%8B%A4-%EA%B7%BC%EB%8D%B0-%EC%9D%B4%EC%A0%9C-ai%EB%A5%BC-%EA%B3%81%EB%93%A4%EC%9D%B8</link><description>&lt;h2 id="0-세-번째-맺음말"&gt;0. 세 번째 맺음말&lt;/h2&gt;&lt;p&gt;벌써 세번째 연말 회고이다. (&lt;a href="https://blex.me/@mildsalmon/10-2022%EB%85%84%EC%97%90-%EB%82%98%EB%8A%94-%EC%96%BC%EB%A7%88%EB%82%98-%EC%84%B1%EC%9E%A5%ED%96%88%EC%9D%84%EA%B9%8C"&gt;2022년 회고&lt;/a&gt;, &lt;a href="https://blex.me/@mildsalmon/2023-%EC%97%B0%EB%A7%90-%ED%9A%8C%EA%B3%A0-%EB%82%98%EC%9D%98-%ED%9A%8C%EA%B3%A0-%EC%9D%BC%EC%A7%80#f-longed-for-%EC%95%9E%EC%9C%BC%EB%A1%9C-%EB%B0%94%EB%9D%BC%EB%8A%94-%EA%B2%83"&gt;2023년 회고&lt;/a&gt;)&lt;br&gt;
시간이 정말 빠르다. 특히 12월달에는 현장 감사 등의 이유로 긴장도 많이 하고 신경도 예민해져서 그런지 시간이 정말 빠르게 지나갔다. 돌아보니 24년은 정말 많은 일들이 있었다.&lt;/p&gt;
&lt;p&gt;나에게 24년은 &lt;code&gt;쉼 없이 달리는 기차처럼, 멈추지 않았지만 창밖 풍경을 놓치지 않으려 했던 해&lt;/code&gt;라고 생각한다.(?) 나 혼자서만 잘한건 아니고 주변 사람들이 많이 도와줬다. 내가 스트레스를 많이 받으면 &lt;code&gt;스트레스 해소 여행 패키지&lt;/code&gt;를 마구 생각하고 준비해준 여자친구, 길게 휴가를 떠날 때면 신경쓰지 않을 수 있게 많이 도와준 리더님과 팀원분들, 운전에 대한 막연한 두려움을 극복할 수 있게 도와준 친구들 등. 돌이켜보니 생각보다 많은 상황에서 관리를 받았다. 많은 부분을 위임하고 도움을 받아보니 필요한 부분에 집중할 수 있어서 도움이 되었고 상당히 좋았다.&lt;/p&gt;
&lt;p&gt;그 외에도 정말 특별한 일들이 많았다. 많은 내용을 담으려다보니 이번 회고는 유독 쉽게 써내려가지 못했다. 오랜만에 쓰는 글이다 보니 단어 하나를 작성하기 위해서 더 많은 고민을 한 것도 한 몫했다. 보통은 &lt;code&gt;Ready, Aim, Fire&lt;/code&gt; 프로세스로 작업을 한다. 그래서 어떤 행동(fire, 회고 작성)을 하기까지의 시간이 오래걸린다. Ready와 Aim 과정이 짧으면 문제가 없겠지만.. 현실은 그렇지 않다. 더 완벽하게 해야지 하면서 시간만 보내게 된다. 그러나 이번 회고는 &lt;code&gt;Ready, Fire, Aim&lt;/code&gt; 프로세스처럼 일단 작성하고 퇴고를 반복하는 방식으로 작성하려고 한다. 세상 모든 일이 다 이와 비슷한 것 같다. 결심하다가 시간을 다 보낸다. (냉탕에서 10분간 입수를 고민했는데 뒤이어 온 할아버지들은 1초만에 전신 입수를 하시는 모습을 보며 든 생각..)&lt;/p&gt;
&lt;p&gt;이번 회고는 23년 회고에서 반성한 내용을 24년에는 어떻게 개선했는지, 24년은 어떤 일들이 있었는지, 25년에는 어떤 목표를 달성할 것인지 등을 적어보려고 한다.&lt;/p&gt;
&lt;h2 id="1-해낸-것들과-놓친-것들"&gt;1. 해낸 것들과 놓친 것들&lt;/h2&gt;&lt;p&gt;23년도에 &lt;code&gt;아쉬웠던 점&lt;/code&gt;이라고 생각한 부분을 24년도에는 전부 잘 해냈다. 그러나 &lt;code&gt;앞으로 바라는 점&lt;/code&gt;이라고 생각한 부분은 대부분 잘 해내지 못했다. 아무래도 시간이 지날수록 하고 싶은 것은 많아지는데 할 수 있는 시간은 줄어든게 원인인 것 같다. (스트레스를 받으면 무의식적으로 유튜브를 켜는 것도 문제이다.)&lt;/p&gt;
&lt;h4 id="a-23년도에-아쉬웠던-점-또는-앞으로-바라는"&gt;A. 23년도에 &lt;code&gt;아쉬웠던 점&lt;/code&gt; 또는 &lt;code&gt;앞으로 바라는 점&lt;/code&gt; 중에 24년도에 &lt;strong&gt;잘 해낸 부분&lt;/strong&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;무의식적으로 운전을 계속 두려워하는 것.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[24년 기록]&lt;/strong&gt; 무주에서 약 500km 운전을 했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[25년 예정]&lt;/strong&gt; 저렴한 중고차를 구매해서 운전을 더 많이 해볼 것&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;찬물샤워를 하려고 했는데, 2~3번밖에 하지 않은 것.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[24년 기록]&lt;/strong&gt; 1주일에 1번은 냉탕에 들어가려고 했으나, 아무리 못가도 1달에 1번 냉탕에 들어갔다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[25년 예정]&lt;/strong&gt; 최소 2주일에 1번은 냉탕에 들어가기&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;패시브 인컴에 대해서 다양하게 생각했지만 실천하지 못한 것.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[24년 기록 - 결과]&lt;/strong&gt; 본격적으로 해외 주식을 시도했고 수익도 많이 났다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[24년 기록 - 반성]&lt;/strong&gt; 아직은 active income의 비율이 더 높은데,, passive income의 비율을 높일 방법을 고민해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[25년 예정]&lt;/strong&gt; 세금을 합법적인 선에서 절세할 방법이 있을지 고민하기.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;임장을 많이 다니지 못한 것.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[24년 기록]&lt;/strong&gt; 여자친구랑 데이트겸 다녀왔다. (아파트를 보다는 주로 동네 분위기를 보고 왔다.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[25년 예정]&lt;/strong&gt; 더 많은 동네를 구경하고 기회가 되면 각 아파트의 인프라 특징을 살펴보기. (아직 부동산을 통해 집에 방문하기는 부담스럽다.)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;금전적으로 부담이 돼서 PT 등록을 망설인 것
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[24년 기록]&lt;/strong&gt; PT, 킥복싱, 기타 등 취미를 배우는데 돈을 계속 쓰면서 나랑 맞는 취미를 찾아가고 있다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[25년 예정]&lt;/strong&gt; 클라이밍이나 수영에 다시 도전해보기.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;책이나 글을 통해 인사이트를 얻는 시간을 고정적으로 만드는 것
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[24년 기록]&lt;/strong&gt; 매일 회사에 7시 즈음 도착해서 9시까지 책을 읽거나 공부를 했다. (퇴근하면 아무것도 안하게 되서 아침에 한다.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[25년 예정]&lt;/strong&gt; 퇴근후 2시간도 따로 빼서 책을 읽거나 공부하기.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;내가 살아가야 하는 이유에 대해 찾기. (왜 성장하고 싶은지, 삶의 목적이 무엇인지)
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[24년 기록]&lt;/strong&gt; 완벽하게 답을 찾지는 못했지만 행복에 관해서 과거와 다르게 생각하기 시작했다. (24년 회고에서 이야기할 예정)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[25년 예정]&lt;/strong&gt; 인생의 목표나 목적이라는 깃발을 많이 꽂아보기. (더 많은 경험을 해보면서 깃발을 만들어야 한다.)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;이번 회사에서의 과업들을 잘 수행하고 관련된 글도 써보는 것.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[24년 기록]&lt;/strong&gt; 블로그에 글을 몇개 작성했고 notion에 포트폴리오로 몇개 작성했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[25년 예정]&lt;/strong&gt; 이력서를 재구성해서 작성해보기.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;나만의 데이터 인프라 구축 프로젝트를 해보는 것.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[24년 기록]&lt;/strong&gt; git repo도 만들고 컨셉 구상도 했는데 코드 작업을 하려는 시기에 회사 업무가 몰려서 코드 작업을 거의 못했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[25년 예정]&lt;/strong&gt; 작게라도 ETL 전 과정을 완성하고 새로운 기술도 도입해보기.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;airflow 강의를 만들기 위해 목차부터 써보는 것. 그리고 airflow 코드도 가끔 파보면서 코드를 통해 학습하는 것
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[24년 기록]&lt;/strong&gt; KDT 기수가 올라갈때마다 airflow 특강 내용을 수정하고 있는데, 특강 내용을 준비하면서 코드를 또 파보고 어떻게 전달하면 이해할 수 있을지 고민했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[25년 예정]&lt;/strong&gt; KDT 5기의 airflow 특강을 진행하기 전에 ppt 보충 하기.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="b-23년도에-아쉬웠던-점-또는-앞으로-바라는"&gt;B. 23년도에 &lt;code&gt;아쉬웠던 점&lt;/code&gt; 또는 &lt;code&gt;앞으로 바라는 점&lt;/code&gt; 중에 24년도에 &lt;strong&gt;잘 해내지 못한 점&lt;/strong&gt;&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;Inflearn에서 Airflow 강의를 만들기
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[24년 기록]&lt;/strong&gt; KDT에서 발표한 내용을 바탕으로 Airflow 강의를 하려고 했는데 여러 이유(회사 업무가 바빠짐, ppt를 새로 만들어야 함, 영상 찍기가...)로 못했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[25년 예정]&lt;/strong&gt; inflearn 전용 airflow ppt를 만들고 기초 강의는 빠르게 찍어서 무료로 뿌리기&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;역마살은 핑계지만, 여러 지역에서 살아보는 것 (가능하면 워케이션 또는 한달 살기도 다녀오기?)
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[24년 기록]&lt;/strong&gt; 여러 지역에서 거주를 해보지 못했다. 다만 정말 많은 지역(강릉, 동해, 태백, 대관령, 무주, 제주, 충주, 송도, 망원동, 동탄, 건대, 김포, 인덕원, 김해, SF)에 방문했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[25년 예정]&lt;/strong&gt; 2개 이상 지역에서 1달 ~ 3달 정도 거주해보기.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;spark를 어느 정도 할 수 있는 사람이 되는 것.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[24년 기록]&lt;/strong&gt; 회사에서 업무를 할때 사용하지 않아서 못했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[25년 예정]&lt;/strong&gt; 지금 당장 spark보다 우선순위 높여서 해야할게 더 많다. (DBT, iceberg..) Databricks를 도입하면 spark를 쓰게될 것 같은데.. 아직은 모르겠다.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;월 5만원 정도의 배당소득을 만들어보는 것.
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[24년 기록]&lt;/strong&gt; 월 $ 0.3 ~ $ 37 배당 소득을 만들었다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[25년 예정]&lt;/strong&gt; 배당에 대한 관심이 떨어져서 배당주를 모으지는 않을 것 같다.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="2-길-위에서-찾은-나"&gt;2. 길 위에서 찾은 나&lt;/h2&gt;&lt;h4 id="a-여행에서-찾아가는-나"&gt;A. 여행에서 찾아가는 나&lt;/h4&gt;&lt;h6 id="a-여행-나를-마주하는-시간"&gt;a. 여행, 나를 마주하는 시간&lt;/h6&gt;&lt;p&gt;여행은 길 위에서 나를 만나는 시간이다. 낯선 장소로 떠난다는 것은 단순히 지리적인 이동을 의미하지 않는다. 그것은 익숙한 일상과의 거리두기, 낯선 환경에서 내가 진정으로 원하는 것을 찾아가는 과정이다. 길을 걸어가는 순간, 나는 조금씩 내가 누구인지, 그리고 어디로 향해야 할지를 깨닫게 된다.&lt;/p&gt;
&lt;p&gt;여행은 단순히 도피가 아니기 때문에 특별하다. 익숙했던 환경에서 벗어나 낯선 곳에서 만나는 모든 풍경, 사람들 그리고 그 속에서 떠오르는 생각들은 내 안에 묻혀 있던 질문들을 끌어올린다. 여행은 그 질문들에 대한 답을 찾아가는 여정이자, 질문 자체를 소중히 여기는 시간이기도 하다.&lt;/p&gt;
&lt;h6 id="b-길-위에서-던지는-질문들"&gt;b. 길 위에서 던지는 질문들&lt;/h6&gt;&lt;p&gt;여행을 떠날 때마다 나에 대한 궁금증이 생긴다. &lt;strong&gt;&amp;quot;나는 정말로 바다를 좋아하고 숲을 싫어할까?&amp;quot;&lt;/strong&gt;, &lt;strong&gt;&amp;quot;내가 선택한 방향이 유일한 정답일까?&amp;quot;&lt;/strong&gt; 이런 질문들은 내가 평소에는 생각하지 못했던 부분을 깨닫게 해준다. 여행은 그 질문들에 대한 완벽한 답을 주지는 않지만, 그 답을 찾는 과정 속에서 나를 더 깊이 이해하게 만든다.&lt;/p&gt;
&lt;p&gt;답을 찾지 못해도 괜찮다. 중요한 것은 질문을 던지고, 그 질문이 나를 움직이게 한다는 점이다. 여행은 그 질문들에 대한 답을 찾아가는 여정이다. 익숙한 풍경에서 벗어나 낯선 도시의 거리, 고요한 산길 그리고 광활한 바다를 마주할 때, 그곳에서 마주하는 것은 단순히 풍경만이 아니다. 나는 여행속에서 내가 모르는 나를 알아가게 된다.&lt;/p&gt;
&lt;h6 id="c-자연-속에서의-깨달음"&gt;c. 자연 속에서의 깨달음&lt;/h6&gt;&lt;p&gt;무주에서 만났던 고요한 밤하늘은 빠르게 흘러가던 도시와는 다르게 여유롭게 느껴졌다. 덜 자극적인 그곳의 일상을 통해 내가 가진 조바심도 되돌아보게 되었다. 밤하늘의 별을 가만히 바라보며 문득 이런 생각이 들었다. &lt;strong&gt;&amp;quot;나는 왜 항상 빨리 가야 한다고 생각했을까?&amp;quot;&lt;/strong&gt; 조바심은 내게서 무엇을 빼앗고 있었는지, 그리고 내가 무엇을 놓치고 있었는지 질문만 가득해진다.&lt;/p&gt;
&lt;p&gt;경포에서 영진까지 강릉 해변을 따라 걸었던 긴 산책은 내 안의 복잡한 생각들을 단순화시켜 주었다. 파도가 밀려왔다 밀려가는 반복 속에서 &lt;strong&gt;&amp;quot;삶의 본질도 비슷하지 않을까?&amp;quot;&lt;/strong&gt;라는 생각을 했다. 복잡한 계획과 목표 대신, 지금 이 순간에 최선을 다하며 치열하게 살아가고 그런 일상을 반복하는 것. 조바심을 내려놓고 순간에 몰입하는 것이 오히려 더 충만한 삶을 만든다는 깨달음이 밀려왔다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;조바심은 내게서 무엇을 빼앗고 있었는지, 그리고 내가 무엇을 놓치고 있었을까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h6 id="d-여행-속에서-발견한-나의-취향"&gt;d. 여행 속에서 발견한 나의 취향&lt;/h6&gt;&lt;p&gt;소살리토에서 자전거를 타고 한적한 거리를 지나며, 나는 내가 좋아하는 지역들의 공통된 특징을 알게 되었다. &lt;strong&gt;나는 사람이 적고 낮은 건물들이 있는 한적한 곳을 좋아한다.&lt;/strong&gt; 그곳에서의 시간은 나를 편안하게 만들었고, 마치 내게 맞는 옷을 입은 것처럼 자연스러웠다.&lt;/p&gt;
&lt;p&gt;그러나 오랜 시간 거주하지 않았기 때문에 이 생각은 환상일 수도 있다. 그래서 25년에는 짧으면 1달에서 3달까지 거주하며 내가 정말로 이 공간들을 좋아하는지 아니면 여행이라는 특수한 상황이 주는 일시적인 설렘인지 알아가려고 한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;나는 사람이 적고 낮은 건물들이 있는 한적한 곳을 좋아한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h6 id="e-여행에서-돌아온-후의-나"&gt;e. 여행에서 돌아온 후의 나&lt;/h6&gt;&lt;p&gt;여행에서 끝에서 언제나 조금 달라진 나를 마주한다. 그 변화는 눈에 보이는 것보다 더 내면적인 경우가 많다. 더 넓어진 시야, 더 가벼워진 마음, 그리고 더 명확해진 방향성. 여행은 나를 더 깊이 이해하게 만들고, 나아가고자 하는 길에 대한 용기를 심어준다.&lt;/p&gt;
&lt;p&gt;여행이 끝난 후에는 항상 공허함이 남는다. 하지만 그 공허함 속에서도 나는 삶을 다시 조정할 수 있는 힘을 얻는다. 나에게 여행은 단순한 탈출이 아니라, 나를 돌아보고 앞으로 나아가게 만드는 원동력이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class="col-3"&gt;
&lt;figcaption&gt; 동해, 무주, 제주, 샌프란, 소살리토, 강릉 &lt;/figcaption&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2024/12/31/2024123110_XiereCHlbssxg8MOfktq.jpeg" src="/resources/media/images/content/2024/12/31/2024123110_XiereCHlbssxg8MOfktq.jpeg.preview.jpg" alt=""&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2024/12/31/2024123110_NVc6GO8g4XMXvmYFuCtt.jpeg" src="/resources/media/images/content/2024/12/31/2024123110_NVc6GO8g4XMXvmYFuCtt.jpeg.preview.jpg" alt=""&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2024/12/31/2024123110_GqsDUeRgLdVSb2FqJmVt.jpeg" src="/resources/media/images/content/2024/12/31/2024123110_GqsDUeRgLdVSb2FqJmVt.jpeg.preview.jpg" alt=""&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2024/12/31/2024123110_9MbiaWovMuoo3MF6O1QB.jpeg" src="/resources/media/images/content/2024/12/31/2024123110_9MbiaWovMuoo3MF6O1QB.jpeg.preview.jpg" alt=""&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2024/12/31/2024123110_fXgOI7lSanmrBTmcbFVb.jpeg" src="/resources/media/images/content/2024/12/31/2024123110_fXgOI7lSanmrBTmcbFVb.jpeg.preview.jpg" alt=""&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2024/12/31/2024123110_F96r5Iqzh3my8DQxIqL8.jpg" src="/resources/media/images/content/2024/12/31/2024123110_F96r5Iqzh3my8DQxIqL8.jpg.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;/figure&gt;&lt;/p&gt;
&lt;h4 id="b-번아웃과-행복"&gt;B. 번아웃과 행복&lt;/h4&gt;&lt;h6 id="a-다시-찾아온-번아웃"&gt;a. 다시 찾아온 번아웃&lt;/h6&gt;&lt;p&gt;아이러니하게도 번아웃은 종종 행복을 추구하다가 시작된다. &lt;strong&gt;&amp;quot;내가 진정으로 원하는 삶을 살고 있는가?&amp;quot;&lt;/strong&gt;라는 질문에 답하기 위해 우리는 더 나은 회사, 더 많은 성취, 더 높은 목표를 향해 끝없이 달린다. 그런데 이 과정에서 나는 행복을 결과로 착각하고, 달성해야 할 목표로만 여기곤 했다.&lt;/p&gt;
&lt;p&gt;올해 초부터 나는 마치 멈추지 않는 열차를 탄 듯 바쁘게 달렸다. 업무는 끝이 보이지 않았고, 매일 새로운 문제들이 쌓였다. 나는 그 문제들을 해결해야 한다는 의무감에 스스로를 몰아붙였고, 결국 번아웃에 빠졌다.&lt;/p&gt;
&lt;h6 id="b-번아웃의-증상"&gt;b. 번아웃의 증상&lt;/h6&gt;&lt;p&gt;번아웃이 찾아오면서 가장 먼저 나 자신에 대한 신뢰가 무너졌다. &lt;strong&gt;&amp;quot;내가 이걸 잘할 수 있을까?&amp;quot;&lt;/strong&gt;라는 의문이 머릿속을 떠나지 않았고, 회사의 끊임없는 요구와 다중 작업의 압박은 나를 더욱 무겁게 했다. 계속 생성되는 티켓들과 변하는 우선순위 속에서 내 머릿속은 소음으로 가득 찼고, 나는 무엇 하나 제대로 해내지 못한다는 느낌에 사로잡혔다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;내가 지금 뭘 하고 있는 거지?&amp;quot;&lt;/strong&gt;라는 질문은 더 이상 내 삶의 방향을 찾기 위한 것이 아니라, 무력감에서 나온 하소연처럼 느껴졌다. 번아웃은 단순히 피로 이상의 것이었다. 내 몸은 무겁고, 마음은 공허했으며, 일상은 무의미하게 느껴졌다.&lt;/p&gt;
&lt;h6 id="c-작은-탈출구에서-찾은-위로"&gt;c. 작은 탈출구에서 찾은 위로&lt;/h6&gt;&lt;p&gt;그러나 번아웃 속에서도 나는 작은 탈출구를 찾으려고 노력했다. 강릉 해변을 따라 걸었던 긴 산책은 나에게 그런 탈출구였다. 파도 소리를 들으며 천천히 걸었던 그 순간, 나는 &lt;strong&gt;&amp;quot;조바심을 내려놓아도 괜찮다&amp;quot;&lt;/strong&gt;는 생각을 처음으로 할 수 있었다. 모든 것을 빠르고 완벽하게 해결하려는 강박에서 벗어나, 단순히 지금 이 순간을 살아가는 법을 배운 것 같았다.&lt;/p&gt;
&lt;p&gt;소살리토에서 자전거를 타고 한적한 거리를 달렸던 경험도 잊을 수 없다. 그곳에서 나는 &lt;strong&gt;&amp;quot;사람이 적고 조용한 곳에서 더 편안함을 느끼는&amp;quot;&lt;/strong&gt; 나만의 취향을 발견했다. 그 깨달음은 행복이 무엇인지에 대한 작은 힌트를 주었다. 행복은 늘 거창하지 않았고, 나의 작은 선택과 발견 속에서 존재했다.&lt;/p&gt;
&lt;p&gt;번아웃은 나에게 행복에 대한 새로운 관점을 제시했다. 번아웃의 시간 동안 나는 행복을 단순히 &lt;strong&gt;&amp;quot;기쁨&amp;quot;&lt;/strong&gt;이나 &lt;strong&gt;&amp;quot;즐거움&amp;quot;&lt;/strong&gt;으로 정의했던 과거의 나를 돌아보았다. 하지만 강릉 해변에서, 소살리토의 거리에서, 그리고 무주의 밤하늘 아래에서 나는 행복이 단순한 감정이 아니라 &lt;strong&gt;&amp;quot;과정 속에서 살아가는 태도&amp;quot;&lt;/strong&gt;라는 사실을 배웠다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;행복은 결과가 아니라 과정에 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h6 id="d-번아웃과-행복-그-공존"&gt;d. 번아웃과 행복, 그 공존&lt;/h6&gt;&lt;p&gt;번아웃과 행복은 마치 나를 밀고 당기는 힘처럼 느껴졌다. 번아웃이 나를 주저앉히면, 행복을 찾으려는 의지가 나를 다시 일으켰다. 이 두 가지는 서로 반대되는 것처럼 보이지만, 결국 함께 존재하며 내 삶에 중요한 교훈을 남겼다.&lt;/p&gt;
&lt;p&gt;번아웃이 없었다면, 나는 아마 행복의 본질에 대해 이토록 깊이 고민하지 않았을 것이다. 번아웃은 나에게 많은 것을 빼앗았지만, 동시에 내가 진정으로 원하는 삶의 방향을 찾게 해줬다.&lt;/p&gt;
&lt;p&gt;올해를 돌아보며 나는 확신할 수 있다. 번아웃 속에서도 행복은 여전히 나를 기다리고 있었다. 작고 소중한 순간들 속에서 나는 번아웃을 넘어설 힘을 얻었고, 그 과정을 통해 조금 더 단단해질 수 있었다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;미래 사건의 결과가 성공적이라면, 그것은 멋진 보너스일 뿐이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="c-회사"&gt;C. 회사&lt;/h4&gt;&lt;p&gt;회사에서는 아래 업무들을 진행하고 정리했다. 일부 항목은 바빠서 정리하지 못해서 주제만 적어뒀다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[Airflow] &lt;a href="https://mildsalmon.notion.site/ETL-Airflow-daily-DB-DL-DB-Clone-116b0fda75c54a679e81c1f3aed46404?pvs=4"&gt;DB -&amp;gt; DL 복제시 Aurora Clone Cluster가 비효율적으로 많이 생성되는 문제 해결&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[Airflow] &lt;a href="https://blex.me/@mildsalmon/data-quality-check%EB%A5%BC-%EC%9C%84%ED%95%9C-assert-runner-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0"&gt;Data Quality Check를 위해 AssertExecutor와 이를 활용한 Operator 개발&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[Ariflow] &lt;a href="https://mildsalmon.notion.site/Airflow-MWAA-Airflow-2-7-3-2-8-2-1b44e433f0b74c44b65ef29b4d6ea6cd?pvs=4"&gt;MWAA의 Airflow 버전 업그레이드 (2.7.3 -&amp;gt; 2.8.2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[Airflow] 코드 문제 없는 Task가 이유없이 죽는 경우에 대한 원인 파악&lt;/li&gt;
&lt;li&gt;[Airflow] ExternalTaskSensor의 execution_date를 동적으로 계산하도록 개발&lt;/li&gt;
&lt;li&gt;[DL] &lt;a href="https://mildsalmon.notion.site/DL-7-Hashing-Migration-5af6ce7873d548979527d092571e49ea?pvs=4"&gt;Hashing이 누락된 7년치 데이터 Migration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[DW] &lt;a href="https://mildsalmon.notion.site/DW-Data-Mart-User-Master-Table-Modeling-d302ac21161146e8899c236500375b2e?pvs=4"&gt;User의 모든 정보를 조회할 수 있는 Data Mart용 User Master Table Modeling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[데이터추출] 국내 금융당국의 요청에 대응하기 위해 데이터 추출 및 자동화 시스템 개발&lt;/li&gt;
&lt;li&gt;[Infra] 실 데이터 테스트를 위한 개인별 분석 환경 구축&lt;/li&gt;
&lt;li&gt;[AML] &lt;a href="https://mildsalmon.notion.site/ETL-Glue-Dow-jones-Watchlist-Feed-cc00068829494a078dfba598ea956fd5?pvs=4"&gt;Dow-jones WLF Feed 데이터 ETL 및 메일링 시스템 개발&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[AML] WLF Simulation을 위한 인프라 구축&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="3-25년의-밑그림"&gt;3. 25년의 밑그림&lt;/h2&gt;&lt;p&gt;25년은 익숙함에서 벗어나 새로운 환경에서 나를 탐구하는 시간이 될 것이다. 낯선 지역에서의 짧지 않은 거주는 내가 놓쳤던 작은 행복과 나만의 취향을 발견하는 기회가 될 것이다. 낮은 건물들이 늘어선 조용한 거리, 시원하게 부딪치는 파도, 쫓기지 않고 하염없이 걷는 산책길에서 나는 내가 진정으로 원하는 삶의 모습을 조금씩 그려나갈 것이다.&lt;/p&gt;
&lt;p&gt;매주 목욕탕에서 피로를 풀고, 한 달에 한 권의 책을 읽으며 사고를 확장하고, 꾸준한 운동으로 나를 단련할 것이다. 이 습관들은 단순히 일상을 유지하는 수단이 아니라, 내 삶을 재정비하고 더 큰 변화를 이끄는 씨앗이 될 것이다.&lt;/p&gt;
&lt;p&gt;새로움과 루틴 속에서 나는 다양한 깃발⛳️을 세울 것이다. 그것은 단순한 성취의 상징이 아니라, 내가 경험 속에서 발견한 가치를 기록하고 앞으로 나아갈 방향을 알려주는 나만의 길잡이가 될 것이다. 실패를 두려워하기보다 새로운 시도 속에서 나의 한계를 넓혀가며, 진정으로 원하는 삶의 모습을 구체화해 나갈 것이다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;나중을 위해 정리해보면..&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자동차를 구매해서 운전을 하며 조금 더 자유롭게 돌아다녀보기.&lt;/li&gt;
&lt;li&gt;다양한 지역(제주, 동해, 태백)에서 거주해보며 그 지역에서 할 수 있는 것을 충분히 즐겨보기.&lt;/li&gt;
&lt;li&gt;나만의 블록체인 데이터 인프라 구축 프로젝트를 완성 해보며 지금까지 해온 것들을 복기하고 업그레이드 해보기.&lt;/li&gt;
&lt;li&gt;부동산을 통해 집에 방문하기는 부담스러우니, 더 많은 동네를 구경하고 기회가 되면 각 아파트의 인프라 특징을 살펴보기.&lt;/li&gt;
&lt;li&gt;클라이밍, 수영, 30km 걷기 등 다양한 운동을 하는 시간 갖기.&lt;/li&gt;
&lt;li&gt;지금까지 한 일들을 정리하고 이력서를 다시 작성해보기.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[미정]&lt;/strong&gt; inflearn 특강 만들기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[루틴]&lt;/strong&gt; 2주에 1회 목욕탕 가서 피로 풀기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[루틴]&lt;/strong&gt; 한달에 책 1권 읽고 정리하기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[루틴]&lt;/strong&gt; 어떤 종류의 운동이든 꾸준히 하기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[루틴]&lt;/strong&gt; 주에 며칠은 하와이안식 음식 먹기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[루틴]&lt;/strong&gt; 출근 전 2시간, 퇴근 후 2시간 책 읽거나 공부하기&lt;/li&gt;
&lt;/ul&gt;
</description><pubDate>Tue, 31 Dec 2024 11:14:42 +0900</pubDate><guid>http://blex.me/@mildsalmon/24%EB%85%84-%ED%9A%8C%EA%B3%A0%EB%A5%BC-%ED%95%98%EA%B2%A0%EC%8A%B5%EB%8B%88%EB%8B%A4-%EA%B7%BC%EB%8D%B0-%EC%9D%B4%EC%A0%9C-ai%EB%A5%BC-%EA%B3%81%EB%93%A4%EC%9D%B8</guid></item><item><title>OOP를 곁들인 Data Quality Check</title><link>http://blex.me/@mildsalmon/data-quality-check%EB%A5%BC-%EC%9C%84%ED%95%9C-assert-runner-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link><description>&lt;h2 id="0-to-do"&gt;0. To-Do&lt;/h2&gt;&lt;p&gt;Data Warehouse에 Data를 적재하기 전후로 필요한 Data가 잘 적재되어 있는지, 적재한 데이터가 잘 적재되었는지를 검증할 필요가 생겼다.&lt;/p&gt;
&lt;p&gt;[사진 1]로 예를 들면 &lt;code&gt;DW Table 9&lt;/code&gt;를 적재하기 전에는 &lt;code&gt;DL Table 1&lt;/code&gt;, &lt;code&gt;DL Table 2&lt;/code&gt; 데이터가 누락되지 않았는지 확인해야 한다. &lt;code&gt;DW Table 9&lt;/code&gt;를 적재한 후에는 잘 적재되었는지 확인해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class="col-1"&gt;
&lt;figcaption&gt;[사진 1] DW에 적재 전후로 Quality Check가 필요한 데이터 예시&lt;/figcaption&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2024/6/23/202462314_gNZzQCoKxgn0uAJDCpfW.png" src="/resources/media/images/content/2024/6/23/202462314_gNZzQCoKxgn0uAJDCpfW.png.preview.jpg" alt=""&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;h2 id="1-배경-지식"&gt;1. 배경 지식&lt;/h2&gt;&lt;h4 id="a-data-architecture"&gt;A. Data Architecture&lt;/h4&gt;&lt;p&gt;우리의 Data Architecture는 [사진 2]처럼 구성되어 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;DB에 저장된 데이터를 수정없이 Data Lake에 저장한다.&lt;/li&gt;
&lt;li&gt;Data Lake의 데이터를 가공 또는 집계하여 Data Warehouse에 적재한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class="col-1"&gt;
&lt;figcaption&gt;[사진 2] Data Architecture (DB -&amp;gt; DL -&amp;gt; DW 복제 흐름)&lt;/figcaption&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2024/6/23/202462314_1eT7TlyV1UQ1tpUBinqc.png" src="/resources/media/images/content/2024/6/23/202462314_1eT7TlyV1UQ1tpUBinqc.png.preview.jpg" alt=""&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;h4 id="b-data-lake-quality-check"&gt;B. Data Lake Quality Check Process&lt;/h4&gt;&lt;p&gt;Data Architecture 중에 &lt;code&gt;DB -&amp;gt; DL&lt;/code&gt;에 예전부터 Data Quality Check를 하고 있었다. (&lt;del&gt;Data Validation이라고 이름지었었는데, 다시 생각해보니 잘못 지은 것 같다.&lt;/del&gt;)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DB -&amp;gt; DL&lt;/code&gt;에 적용되어 있는 Data Quality Check는 두 개의 데이터 소스(DB, DL)로부터 데이터를 가져와서 누락된 데이터가 있는지 검증하는 방식이다.&lt;/p&gt;
&lt;p&gt;따라서 JDBC로 DB와 연결할 수 있고 Glue Data Catalog로 Data Lake를 쿼리할 수 있는 Spark를 사용했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class="col-1"&gt;
&lt;figcaption&gt;[사진 3] DB, DL의 Data Quality Check Process&lt;/figcaption&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2024/6/23/202462321_jCl8WEqY75EieUZplHQt.png" src="/resources/media/images/content/2024/6/23/202462321_jCl8WEqY75EieUZplHQt.png.preview.jpg" alt=""&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;이 방식을 DW Quality Check에 사용하기에는 여러가지 부적절한 부분들이 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;DL Quality Check는 확장할 수 없다. (당시에 주요 Table만 검증하고자 했기에 수백개의 테이블의 Quality Check에는 적합하지 않다.)&lt;/li&gt;
&lt;li&gt;DW Quality Check는 Spark를 사용할 필요가 없다. (Source와 Target이 같은 Metadata store(glue data catalog)를 사용하기 때문에 Spark가 아닌 방법으로도 충분히 해결할 수 있다.)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2-to-be"&gt;2. To-Be&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;DW Quality Check Process를 개발한다.&lt;/li&gt;
&lt;li&gt;지금은 AWS의 Glue Data Catalog를 Metadata store로 사용하지만, 추후 Databricks의 Unity Catalog를 사용할 수도 있기 때문에 이를 고려해서 개발한다.&lt;/li&gt;
&lt;li&gt;사용자는 쿼리라는 개념을 모르더라도 Quality Check를 할 수 있도록 구현한다. (사용자는 Assert Condition만 선언적으로 입력하고 어떻게 처리할지는 시스템에 위임한다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="3-해결-과정"&gt;3. 해결 과정&lt;/h2&gt;&lt;h4 id="a-uml-diagram"&gt;A. UML Diagram&lt;/h4&gt;&lt;p&gt;&lt;figure class="col-1"&gt;
&lt;figcaption&gt;[사진 4] UML Diagram&lt;/figcaption&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2024/6/23/202462315_kMlYnTUXA1m5Qg6YndVP.png" src="/resources/media/images/content/2024/6/23/202462315_kMlYnTUXA1m5Qg6YndVP.png.preview.jpg" alt=""&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;h4 id="b-assertrunner-isp-ocp-di"&gt;B. AssertRunner: ISP, OCP, DIP&lt;/h4&gt;&lt;blockquote&gt;
&lt;p&gt;OOP의 SOLID Principle을 지켜서 확장성 있는 코드를 개발한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;책임과 역할의 분리&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h6 id="a-isp-interface-segregati"&gt;a. ISP (Interface Segregation Principle)&lt;/h6&gt;&lt;blockquote&gt;
&lt;p&gt;여러개의 기능을 가진 하나의 인터페이스보다 하나의 기능을 가진 여러개의 인터페이스가 낫다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Assert를 execute하는 &lt;code&gt;IAssertRunner&lt;/code&gt;와 Assert Data를 build하는 &lt;code&gt;IAssertionDataGenerator&lt;/code&gt;로 인터페이스를 분리하였다. 이로써 인터페이스의 기능별로 역할이 분리되게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class="col-1"&gt;
&lt;figcaption&gt;[사진 5] ISP&lt;/figcaption&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2024/6/23/202462321_c1acdwa1OsPw0E73CU92.png" src="/resources/media/images/content/2024/6/23/202462321_c1acdwa1OsPw0E73CU92.png.preview.jpg" alt=""&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;h6 id="b-ocp-open-closed-princip"&gt;b. OCP (Open Closed Principle)&lt;/h6&gt;&lt;blockquote&gt;
&lt;p&gt;소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;인터페이스를 구현한 클래스를 만들어서 새로운 기능을 구현한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class="col-1"&gt;
&lt;figcaption&gt;[사진 6] OCP&lt;/figcaption&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2024/6/23/202462316_g3YwcujmVqcvUQj09moV.png" src="/resources/media/images/content/2024/6/23/202462316_g3YwcujmVqcvUQj09moV.png.preview.jpg" alt=""&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;h6 id="b-dip-dependency-inversio"&gt;b. DIP (Dependency Inversion Principle)&lt;/h6&gt;&lt;blockquote&gt;
&lt;p&gt;추상화에 의존해야지, 구체화에 의존하면 안된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;인터페이스를 구현한 구체 클래스(&lt;code&gt;AthenaAssertionExecutor&lt;/code&gt;, &lt;code&gt;S3AssertionExecutor&lt;/code&gt;)를 모르더라도 &lt;code&gt;EX-1&lt;/code&gt; 함수처럼 추상화에 의존하는 코드를 작성할 수 있다.&lt;/p&gt;
&lt;p&gt;만약 객체를 생성하고 연관관계를 맺어주는 시스템(e.g. Spring Container)이 있다면, 적절한 구현 객체를 선택해준다. 이를 DI (Dependency Injection)이라고 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# EX-1
def test(assert_runner: IAssertRunner):
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다만, Airflow는 Spring Container와 같은 역할을 하지 못하기 때문에 구현 객체를 직접 생성하고 주입해줘야 한다. 이 말은 구현 객체를 변경하려면 코드를 변경해야 한다는 의미라서 &lt;del&gt;OCP 원칙&lt;/del&gt; DI를 위배하게 된다. (&lt;code&gt;EX-2&lt;/code&gt; 코드를 &lt;code&gt;EX-3&lt;/code&gt; 코드처럼 직접 수정해야 한다.)&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# EX-2
a = AthenaAssertionExecutor()

test(assert_runner = a)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# EX-3
b = S3AssertionExecutor()

test(assert_runner = b)
&lt;/code&gt;&lt;/pre&gt;
&lt;h6 id="c-factory-method-pattern"&gt;c. Factory Method Pattern&lt;/h6&gt;&lt;p&gt;위 방식(부모 클래스에서 인터페이스를 정의하고 자식 클래스에서 인터페이스를 구현하여 구체적인 객체 처리 로직을 수행하)은 Factory Method Pattern이라고 생각할 수 있다.&lt;/p&gt;
&lt;p&gt;Factory Method는 [사진 7]처럼 부모 클래스의 method를 자식 클래스들이 구현하는 방식인데, [사진 6]를 보면 부모 클래스의 method를 자식 클래스에서 구현하기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class="col-1"&gt;
&lt;figcaption&gt;[사진 7] Factory Method Pattern을 적용했을 때의 UML diagram&lt;/figcaption&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2024/6/23/202462318_ucgpZtnUVGuHyuw4AY0m.png" src="/resources/media/images/content/2024/6/23/202462318_ucgpZtnUVGuHyuw4AY0m.png.preview.jpg" alt=""&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;h4 id="c-specsjson-declarative-p"&gt;C. *_specs.json: Declarative Programming&lt;/h4&gt;&lt;blockquote&gt;
&lt;p&gt;사용자가 '무엇을' 해야하는지 명시하고 '어떻게'해야하는지는 시스템이 결정&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;사용자는 코드를 수정하지 않고 원하는 동작을 선언적으로 입력하면 시스템이 어떻게 동작해야할 지를 결정한다.&lt;/p&gt;
&lt;p&gt;이로 인해 사용자와 실제 구현된 코드를 떼어놓을 수 있기 때문에 추상화 레벨이 높아진다는 점과 간결성, 유지보수성 향상 등의 장점이 있다.&lt;/p&gt;
&lt;p&gt;e.g. 사용자는 &lt;code&gt;쿼리&lt;/code&gt;라는 개념을 모르더라도 원하는 Asset 조건을 추가할 수 있다. 시스템은 사용자가 입력한 Assert 조건을 쿼리로 해석해서 결과를 만든다.&lt;/p&gt;
&lt;p&gt;&lt;figure class="col-2"&gt;
&lt;figcaption&gt;[사진 8, 9] 사용자는 초록 박스(.json)와 빨간 박스(dag)만 입력하면 된다.&lt;/figcaption&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2024/6/23/202462321_zT9wYZLrLzpnEqNlZTPn.png" src="/resources/media/images/content/2024/6/23/202462321_zT9wYZLrLzpnEqNlZTPn.png.preview.jpg" alt=""&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2024/6/23/202462321_3usZqyJzSc2iPhyLG2jG.png" src="/resources/media/images/content/2024/6/23/202462321_3usZqyJzSc2iPhyLG2jG.png.preview.jpg" alt=""&gt;
&lt;/figure&gt;&lt;/p&gt;
&lt;h2 id="4-결론"&gt;4. 결론&lt;/h2&gt;&lt;p&gt;내가 직접 설계하고 구현하는 모든 과정이 재미있었다.&lt;/p&gt;
&lt;p&gt;UML Diagram을 그리는 과정은 조금 귀찮았으나 그리고 나니 머릿속에 떠다니던 점들이 연결되는 느낌을 받았다.&lt;/p&gt;
&lt;p&gt;처음에는 &lt;code&gt;IAssertRunner&lt;/code&gt; 쪽만 구현하고 DAG를 생성하는 코드에서 &lt;code&gt;AthenaAssertRunner&lt;/code&gt;객체를 생성해서 주입해주는 방식으로 개발했다. 개발하고 나니 DAG를 생성하는 코드가 너무 더럽고 유지보수하기 힘들 것 같다는 생각이 들었다.&lt;/p&gt;
&lt;p&gt;이 방식보다는 json 파일에 사용자가 원하는 행동을 선언하고 DAG를 생성하는 코드에 문자열만 입력하면 시스템이 알아서 동작하는 방식이 더 유지보수하기 편리하겠다는 생각이 들었다. (이 과정에서 많은 조언을 해주신 리더님께 감사하다.)&lt;/p&gt;
&lt;p&gt;구조를 바꾸려고 생각하고 간략하게 다이어그램을 그렸는데, 지금 그린 다이어그램에서 많은 것들이 생략되었었다. 처음부터 완벽하게 틀을 짜고 구현하고 싶었는데 쉽지 않았다. 그래도 계속 시도하다보면 자연스럽게 초기 설계와 일치하는 코드를 구현하게 될지 않을까? (이게 더 안좋은건가?)&lt;/p&gt;
</description><pubDate>Sun, 23 Jun 2024 21:37:42 +0900</pubDate><guid>http://blex.me/@mildsalmon/data-quality-check%EB%A5%BC-%EC%9C%84%ED%95%9C-assert-runner-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid></item><item><title>함께 자라기</title><link>http://blex.me/@mildsalmon/2024-1-19-%EC%98%A4%EC%A0%84-82828</link><description>&lt;h2 id="0-머리말"&gt;0. 머리말&lt;/h2&gt;&lt;p&gt;기회가 수백, 수천 번 더 있다면, 이번에 잘하냐 못하냐 하는 것은 그렇게 중요하지 않습니다. 더 중요한 것은 지금 자라고 있느냐는 것입니다. 세상에는 한 번보다 수백, 수천 번의 누적 위에서 미래가 서서히 정해지는 경우가 더 많습니다.&lt;/p&gt;
&lt;p&gt;우리는 &lt;code&gt;내가 정말 잘할 수 있을까?&lt;/code&gt; → &lt;code&gt;내가 정말 자랄 수 있을까?&lt;/code&gt; → &lt;code&gt;우리가 정말 함께 자랄 수 있을까?&lt;/code&gt; → &lt;code&gt;우리가 정말 매일매일 함께 자랄 수 있을까?&lt;/code&gt; 흐름으로 생각의 방향이 나아가야 합니다.&lt;/p&gt;
&lt;h2 id="1-자라기"&gt;1. 자라기&lt;/h2&gt;&lt;h2 id="a-자기계발은-복리로-돌아온다"&gt;A. 자기계발은 복리로 돌아온다.&lt;/h2&gt;&lt;p&gt;자기계발은 &lt;code&gt;현재 나에게 무엇을 투자했느냐가 1년, 혹은 2년 후의 나를 결정&lt;/code&gt;한다고 느끼기 때문에 중요하다고 생각한다.&lt;/p&gt;
&lt;p&gt;자기가 습득한 지식이나 능력은 복리로 이자가 붙기 때문에 나중에 엄청난 차이를 만들 것입니다.&lt;/p&gt;
&lt;p&gt;복리를 만들기 위해서는 곱하기를 꾸준하게 해야 합니다.&lt;/p&gt;
&lt;p&gt;따라서 더 빨리 자라고 싶다면 1. 어떻게 이율을 높일 것인가 2. 지속적으로 현명한 투자를 하려면 어떻게 할 것인가를 고민해야 합니다.****&lt;/p&gt;
&lt;h4 id="a-복리의-비밀"&gt;a. 복리의 비밀&lt;/h4&gt;&lt;p&gt;&lt;a href="https://archive.nytimes.com/www.nytimes.com/library/cyber/digicom/1007digicom.html"&gt;Douglas Engelbart의 작업 구분에 대한 이야기&lt;/a&gt;에서 작업을 A, B, C로 구분합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A 작업: 원래 그 조직이 하기로 되어 있는 일을 하는 것. (자동차 공장에서는 자동차를 만드는 것)&lt;/li&gt;
&lt;li&gt;B 작업: A 작업을 개선하는 것 (자동차를 만드는 사이클에서 시간과 품질을 개선하는 것, 시스템을 설계하는 것)&lt;/li&gt;
&lt;li&gt;C 작업: B 작업을 개선하는 것 (개선 사이클 자체의 시간과 품질을 개선하는 것. 개선하는 인프라를 설계하는 것)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;C 작업을 잘 하기 위해서는 내가 만든 결과물을 나의 일부로 만들어서 다음 단계에 보탬이 되도록 이용해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2024/1/19/20241199_TKZiQeafclaE36i1b56o.png" src="/resources/media/images/content/2024/1/19/20241199_TKZiQeafclaE36i1b56o.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.dougengelbart.org/colloquium/session_09/session_09_carlson.html"&gt;지수적 팀&lt;/a&gt;은 자기 자신을 곱해나가는 팀입니다. 팀은 일을 상호 협력적으로 진행합니다. 거기에서 시너지 효과라는 게 나옵니다. 부분의 합보다 전체가 더 크다는 말입니다.&lt;/p&gt;
&lt;p&gt;가용시간을 늘리고, 쓸데없이 낭비되는 시간을 줄이고, 잠자는 시간을 줄이는 것이 더하기적 사고라면, 집단의 지능을 높이는 것은 곱하기적 사고입니다. 집단의 지능을 높이면 모든 지적 활동의 효율이 좋아지기 때문에 전반적인 개선(B 작업)이 일어나고, 특히나 개선 작업을 더 잘하게(C 작업)됩니다.&lt;/p&gt;
&lt;p&gt;나의 A 작업을 개선하려면 1. 어떻게 하면 더하기보다 곱하기를 할 수 있을 것인가? 2. 어떻게 해야 곱하는 비율을 높일 수 있는가? 또는 이자 적용 주기를 짧게 할 수 있는가?를 질문해봐야 합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자신이 이미 갖고 있는 것들을 잘 활용하라.
&lt;ul&gt;
&lt;li&gt;책을 많이 읽기보다 그 지식을 얼마나 어떻게 활용하는지 반성하라.&lt;/li&gt;
&lt;li&gt;이미 습득한 지식, 기술, 경험 등을 서로 연결지어서 시너지 효과가 나게 하고 서로 다른 영역을 넘나드는 것을 수월해지도록 하라.&lt;/li&gt;
&lt;li&gt;새로운 것이 들어오면 이미 갖고 있는 것들과 충돌을 시도하라.&lt;/li&gt;
&lt;li&gt;현재 내가 하는 일이 차후에 밑거름이 될 수 있도록 하라.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;외부 물질을 체화하라.
&lt;ul&gt;
&lt;li&gt;주기적인 외부 자극(컨퍼런스 등)을 받고 자기화하라.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;자신을 개선하는 프로세스에 대해 생각해보라.
&lt;ul&gt;
&lt;li&gt;나의 A 작업을 회고/반성 활동을 주기적으로 하는 프로세스를 만들어라.&lt;/li&gt;
&lt;li&gt;나의 개선하는 과정(B 작업)을 어떻게 하면 개선할 수 있을지 고민하라.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;피드백을 자주 받아라.
&lt;ul&gt;
&lt;li&gt;사이클 타임을 줄여라. (1년 동안 완벽하게 만들기보다 1주일 동안 완성을 1년 동안 반복하자.)&lt;/li&gt;
&lt;li&gt;일찍, 그리고 자주 실패하라. 실패에서 학습하라.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;자신의 능력을 높여주는 도구와 환경을 점진적으로 만들어라.
&lt;ul&gt;
&lt;li&gt;완벽한 도구와 환경을 갖추는 데에 집착해서는 안 된다.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="b-달인이-되는-비결"&gt;B. 달인이 되는 비결&lt;/h2&gt;&lt;p&gt;꾸준한 반복으로 달인이 되려면.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;실력을 개선하려는 &lt;code&gt;동기&lt;/code&gt;가 있어야 한다.&lt;/li&gt;
&lt;li&gt;구체적인 &lt;code&gt;피드백&lt;/code&gt;을 적절한 시기에 받아야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;특정 영역에서 개인이 성취할 수 있는 최고 수준의 퍼포먼스는 경험을 오래한다고 해서 자동으로 얻을 수 있는 것은 아닙니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="c-수십-년-동안-전문가가-안-되는-비결"&gt;C. 수십 년 동안 전문가가 안 되는 비결&lt;/h2&gt;&lt;h4 id="a-전문성-형성에서-타당성과-피드백의-중요성"&gt;a. 전문성 형성에서 타당성과 피드백의 중요성&lt;/h4&gt;&lt;p&gt;직관이 형성되려면 특정 조건이 필요합니다. 특정 조건으로 타당성(validity)과 피드백입니다.&lt;/p&gt;
&lt;p&gt;타당성 조건이 필요하다는 의미는 직관이 적용되는 영역에 어느 정도 인과관계와 규칙성이 존재해야 한다는 겁니다. 예측가능성이라고 말할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;피드백 조건이 필요하다는 의미는 자신이 내린 직관적 판단에 대해 빨리 피드백을 받고 이를 통해 학습할 기회가 주어지는 환경이 갖춰져야 한다는 걸 말합니다.&lt;/p&gt;
&lt;h4 id="b-타당성과-피드백을-높이기"&gt;b. 타당성과 피드백을 높이기&lt;/h4&gt;&lt;p&gt;타당성을 높이려면 변수를 제한하고 실험을 하면서 규칙성과 인과관계를 찾으려는 노력을 하면 됩니다. 피드백을 높이려면 동료나 상사, 고객에게서, 혹은 내가 개발하는 프로그램에서 직접 피드백을 적극적으로 구하면 됩니다.&lt;/p&gt;
&lt;h2 id="d-당신이-제자리걸음인-이유"&gt;D. 당신이 제자리걸음인 이유&lt;/h2&gt;&lt;p&gt;실력을 높이기 위해서는 &lt;code&gt;의도적 수련(Deliberate Pratice)&lt;/code&gt;이 중요합니다. 앞에서 의도적 수련을 효과적으로 하기 위해서는 &lt;code&gt;동기&lt;/code&gt;와 &lt;code&gt;피드백&lt;/code&gt;이 필요하다고 언급했습니다.&lt;/p&gt;
&lt;h4 id="a-의도적-수련의-필수조건-적절한-난이도"&gt;a. 의도적 수련의 필수조건, 적절한 난이도&lt;/h4&gt;&lt;p&gt;의도적 수련이 되려면 나의 실력과 작업의 난이도가 비슷해야 합니다. 미하이 칙센트미하이의 몰입이론과도 일치하는 부분입니다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2024/1/19/20241198_SNYlOtW5ROE3RVaBoKve.png" src="/resources/media/images/content/2024/1/19/20241198_SNYlOtW5ROE3RVaBoKve.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;h4 id="b-실력이-늘지-않는-이유"&gt;b. 실력이 늘지 않는 이유&lt;/h4&gt;&lt;p&gt;의도적 수련의 필수 요건 중 하나가 '적절한 난이도'입니다.&lt;/p&gt;
&lt;p&gt;자신이 업무 시간 중에 불안함이나 지루함을 느끼는 때가 대부분이라면, 실력이 도무지 늘지 않는 환경에 있는 겁니다.&lt;/p&gt;
&lt;p&gt;뛰어난 선수는 자기 기량보다 어려운 기술을 연마하지만 그렇지 못한 선수는 이미 잘하는 걸 더 연습합니다.&lt;/p&gt;
&lt;h4 id="c-제자리걸음에서-벗어나기"&gt;c. 제자리걸음에서 벗어나기&lt;/h4&gt;&lt;p&gt;본인의 하루가 불안하거나 지루한 때가 대부분이라면 이 전략들을 사용해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2024/1/19/20241198_vELoh2Qpp5Q6Bf9g7hzE.png" src="/resources/media/images/content/2024/1/19/20241198_vELoh2Qpp5Q6Bf9g7hzE.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;h4 id="d-지루함을-느끼는-경우-a1-실력-낮추기"&gt;d. 지루함을 느끼는 경우: a1 실력 낮추기&lt;/h4&gt;&lt;p&gt;작업의 난이도는 그대로 두고 실력을 낮추는 전략&lt;/p&gt;
&lt;p&gt;e.g. 마우스 없이 키보드로만 개발, 디버거를 안쓰기, 컴파일을 주기를 늘리기&lt;/p&gt;
&lt;h4 id="e-지루함을-느끼는-경우-a2-난이도-높이기"&gt;e. 지루함을 느끼는 경우: a2 난이도 높이기&lt;/h4&gt;&lt;p&gt;실력을 그대로 두고 난이도를 높이는 전략&lt;/p&gt;
&lt;p&gt;e.g. 하루 만에 개발하면 되는 것을 한 시간 만에 할 수 있는 방법 찾기, 익숙한 작업을 새로운 언어로 하기, 리팩터링을 하기, 자동화 테스트 달기&lt;/p&gt;
&lt;p&gt;남들보다 일을 좀 더 효율적/효과적으로 하기 위해 내가 직접 만들어 쓰는 나만의 도구, 방법을 만드는게 매우 중요합니다. 이를 위해서는 자주 일어나는 반복 패턴(DRY)을 파악하고 분석해야 하며, 부족한 시간에도 짬을 내어 도구를 고안하고 작성해야 합니다.&lt;/p&gt;
&lt;h4 id="f-불안함을-느끼는-경우-b2-실력-높이기"&gt;f. 불안함을 느끼는 경우: b2 실력 높이기&lt;/h4&gt;&lt;p&gt;실력을 높여서 몰입 영역으로 들어가는 전략&lt;/p&gt;
&lt;p&gt;장기적인 접근은 책을 보거나 스터디에 참가하거나 교육을 듣거나 등.&lt;/p&gt;
&lt;p&gt;사회적 접근은 &lt;code&gt;나보다 뛰어난 전문가의 도움을 얻는 것&lt;/code&gt;입니다. 잘하는 사람한테 가서 짝 프로그래밍을 해달라고 부탁하거나 튜토리얼 문서를 따라가는 것도 좋습니다.&lt;/p&gt;
&lt;p&gt;도구적 접근은 &lt;code&gt;다른 도구의 도움을 받는 것&lt;/code&gt;입니다.&lt;/p&gt;
&lt;p&gt;내관적 접근은 &lt;code&gt;비슷한 일을 했던 경험을 머릿속에서 되살려 보는 것&lt;/code&gt;입니다. 그때 그 일을 어떻게 했는지 떠올려 보면서 비유적으로 문제를 해결합니다.&lt;/p&gt;
&lt;h4 id="g-불안함을-느끼는-경우-b2-난이도-낮추기"&gt;g. 불안함을 느끼는 경우: b2 난이도 낮추기&lt;/h4&gt;&lt;p&gt;난이도를 낮춰서 몰입 영역으로 들어가는 전략&lt;/p&gt;
&lt;p&gt;가장 간단하면서 핵심적인 결과물을 목표로 삼는 것이다. (What's The Simplest Thing That Could Possibly Work?)&lt;/p&gt;
&lt;h4 id="h-동적인-균형"&gt;h. 동적인 균형&lt;/h4&gt;&lt;p&gt;유의해야할 점은 자신의 실력이나 작업의 난이도가 계속 변한다는 것입니다.&lt;/p&gt;
&lt;p&gt;이 말은 곧, 지속적으로 자신의 감정 상태를 살피면서 조금 지루한지 불안한지를 알아채고 만약 지루함이나 불안함을 느낀다면 앞의 네 가지 전략을 적절히 사용해야 한다는 겁니다. 이렇게 감정 상태를 살피고 조치를 취하는 사이클을 계속 돌아야 합니다.&lt;/p&gt;
&lt;p&gt;그런 면에서 자기가 지금 어떤 상태인지 살피는 &lt;code&gt;알아차림(mindfulness)&lt;/code&gt;이 꼭 필요합니다.&lt;/p&gt;
&lt;h4 id="i-팀장이-할-수-있는-일"&gt;i. 팀장이 할 수 있는 일&lt;/h4&gt;&lt;p&gt;팀원들의 현재 어떤 상태를 경험하는지 파악하고 적절한 전략을 구사하게 도와줄 수 있습니다.&lt;/p&gt;
&lt;h2 id="e-실수는-예방하는-것이-아니라-관리하는-것이"&gt;E. 실수는 예방하는 것이 아니라 관리하는 것이다&lt;/h2&gt;&lt;h4 id="a-두-가지의-실수-문화"&gt;a. 두 가지의 실수 문화&lt;/h4&gt;&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2024/1/19/20241198_im0azTgeICPMNeU8MM3a.png" src="/resources/media/images/content/2024/1/19/20241198_im0azTgeICPMNeU8MM3a.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;실수 예방은 행동에서 실수로 가는 경로를 차단하려고 합니다. 실수를 저지르지 말라고 요구합니다. 근데, 사실 이는 불가능합니다.&lt;/p&gt;
&lt;p&gt;전문가들은 실수를 조기에 발견하고 빠른 조치를 취합니다. 이를 실수 관리라고 합니다.&lt;/p&gt;
&lt;p&gt;실수 예방 문화에서는 실수를 한 사람을 비난하고, 처벌하고, 따라서 실수를 감추고 그에 대해 논의하기 꺼리며 문제가 생겼을 때 협력도 덜하게 됩니다. 실수에서 배우지 못하게 됩니다.&lt;/p&gt;
&lt;p&gt;실수 관리 문화에서는 실수가 나쁜 결과를 내기 전에 빨리 회복하도록 돕고, 실수를 공개하고, 실수에 대해 서로 이야기하고 거기에서 배우는 분위기가 생깁니다.&lt;/p&gt;
&lt;p&gt;회사 문화가 실수 예방보다 관리에 가까울수록 기업의 혁신 정도가 더 높습니다. 실수가 없으면 학습하지 못합니다. 실수 관리를 하는 문화일수록 학습을 더 잘합니다.&lt;/p&gt;
&lt;h2 id="2-함께"&gt;2. 함께&lt;/h2&gt;&lt;p&gt;사람들은 협력이 중요하다고 합니다. 그래서 프로젝트를 할 때 협력적으로 하자고 합니다. 그러나 실제 모습을 들여다보면 초반에 일을 세밀하게 나누고 선을 긋습니다. 각자 진행하고 나중에 만나서 서로 합쳐봅니다. 그 속을 들여다보면 협력은 거의 없습니다.****&lt;/p&gt;
&lt;h2 id="a-협력을-통한-추상화"&gt;A. 협력을 통한 추상화&lt;/h2&gt;&lt;h4 id="a-커뮤니케이션과-협력"&gt;a. 커뮤니케이션과 협력&lt;/h4&gt;&lt;p&gt;실력이 뛰어난 프로그래머는 보통 정도의 실력을 가진 프로그래머에 비해 커뮤니케이션, 협력 능력이 더 뛰어납니다.&lt;/p&gt;
&lt;p&gt;실력이 뛰어난 프로그래머는 커뮤니케이션과 협력에 더 오랜 시간을 들입니다. 반면 설계나 코딩, 테스팅에 들이는 시간에는 통계적으로 큰 차이가 없었습니다. &lt;a href="https://pubmed.ncbi.nlm.nih.gov/9806013/"&gt;#&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="b-톱니바퀴-실험"&gt;b. 톱니바퀴 실험&lt;/h4&gt;&lt;p&gt;둘이서 협력하면서 작업하면 서로 시각이 다르기 때문에 두 사람의 다른 시각을 연결해 줄 다리가 필요하고, 그 다리에는 필연적으로 추상화의 요소가 있게 됩니다. 서로 다른 것들을 하나로 묶어야 하기 때문입니다. 반면 혼자서 작업할 경우에는 이런 추상화의 필요가 덜합니다.&lt;/p&gt;
&lt;h4 id="c-추상화의-중요성"&gt;c. 추상화의 중요성&lt;/h4&gt;&lt;p&gt;이 &amp;quot;흥미로운 무엇&amp;quot;은 강력합니다. 내가 전에 모르던 것을 배우게 됩니다. 그리고 종종 이것은 프로그래머의 울타리를 넘어서 영향력을 끼치기도 합니다.&lt;/p&gt;
&lt;p&gt;여기에서 말하는 &amp;quot;흥미로운 무엇&amp;quot;이 바로 추상화입니다. 특히 우리가 예상하지도 못하던 추상화로, 말하자면 창발적 추상화라고 할 수 있겠습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;복잡한 현상에 대한 이해를 발전시켜 나갈 때, 인간 지성에서 가장 강력한 도구는 추상화다. 실세계의 특정한 대상체, 상황, 과정 간의 유사성을 인식하는 데에서, 그리고 이러한 유사성에 집중하고, 차이점은 일시적으로 무시하는 결정에서 추상화가 생겨난다. -토니 호어&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;추상화를 높일 수 있는 방법은 &amp;quot;다른 시각을 가진 두 사람이 협력하기&amp;quot; 입니다.&lt;/p&gt;
&lt;h4 id="d-대화하는-프로그래밍"&gt;d. 대화하는 프로그래밍&lt;/h4&gt;&lt;p&gt;짝 프로그래밍은 두 사람이 한 컴퓨터를 사용해 함께 프로그래밍하는 것입니다. 두 사람이라는 구성은 대화를 통해 추상화를 높이게 합니다. 한 컴퓨터라는 구성은 구체화를 통해 검증하게 합니다. 미루고 헤아리는 것이 빈번히 교차합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;주의해서 생각하지 않으면 프로그래밍은 특정 프로그래밍 언어로 명령문을 타이핑해 넣는 것에 지나지 않는다고 생각할 수 있다. -워드 커닝햄&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;미루고 헤아린다.는 표현은 혜강 최한기의 &amp;lt;추측론&amp;gt;에서 빌려온 표현입니다. 미루다고 할 때의 추는 직접적이고 구체적인 경험 헤어린다 할 때의 측은 이 구체성을 토대로 경험하지 못한 것에 대한 이성적이고 추상적인 사고 혜강은 우리가 지식을 넓혀가는 요체가 이 추측의 반복에 있다고 생각했습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;자신이 작성하는 코드의 추상성을 높이고 싶다면 혼자서 고민하지 말고 다른 사람들과 협동하고, 대화하세요. 같이 그림도 그려보고 함께 소스코드를 편집하세요. 인간에게는 다른 인간과 소통하고 협력할 수 있는 놀라운 능력이 있습니다. 대화는 기적입니다.&lt;/p&gt;
&lt;h2 id="b-이것도-모르세요"&gt;B. 이것도 모르세요?&lt;/h2&gt;&lt;h4 id="a-공감하고-이해하려는-대화"&gt;a. 공감하고 이해하려는 대화&lt;/h4&gt;&lt;p&gt;능력이 없는 팀장일수록 '비난'만 합니다.&lt;/p&gt;
&lt;p&gt;훌륭한 팀장이라면 먼저 그 사람의 사고 과정과 전략을 이해하려고 합니다.&lt;/p&gt;
&lt;h2 id="3-애자일"&gt;3. 애자일&lt;/h2&gt;&lt;p&gt;'애자일 소프트웨어 개발 방법론'은 소프트웨어를 개발하는 한 가지 스타일을 일컫습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[협소한 의미의 애자일]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;애자일은 불확실성이 높은 프로젝트에 더 적합합니다. 애자일이 불확실성을 다루는 방식은 좀 더 짧은 주기로 더 일찍부터 피드백을 받고, 더 다양한 사람으로부터 더 자주 그리고 더 일찍 피드백을 받는 것으로 정리할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[광의의 애자일]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;일하는 한 가지 스타일을 넘어 삶을 사는 방식으로 확장해 보는 것이지요.&lt;/p&gt;
&lt;p&gt;학습과 협력이 애자일이 불확실성을 다루는 핵심적인 구동원리입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[학습]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;우리가 이동을 할 때 목표점의 위치가 자주 바뀌거나 우리 위치가 자주 바뀌거나 하는 상황으로 비유해 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;이동하면서 계속 배워나가야 한다는 뜻입니다. 불확실성이 높을수록 학습을 잘해야 하는 것이죠. 새로운 상황에 대해서 계속 배우고 내가 맞춰 나가야 합니다. 따라서 우리의 학습 능력을 향상시킬 수 있다면 우리는 불확실성에 대해 더 잘 대응할 수 있을 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[협력]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;불확실한 상황일수록 리스크가 높은 것이고, 안 좋은 일이 벌어질 확률이 높을 것입니다. 안 좋은 일이 벌어질 확률은 '또는' 조건으로 연결되어 있을 때 더 높아집니다.&lt;/p&gt;
&lt;p&gt;애자일은 서로의 업무를 공유하고 상호 검토하는 협력을 통해 불행한 일을 '또는' 조건에서 '그리고' 조건으로 바꾸게 합니다.&lt;/p&gt;
&lt;p&gt;불확실한 상황에서 예상치 못한 좋은 일이 생길 확률도 있습니다. 한 사람이라도 통찰을 얻으면 그걸 공유해서 전체가 개선되는 것입니다.&lt;/p&gt;
&lt;p&gt;→ 학습과 협력은 불확실성이 큰 상황에서 좋은 대응 전략이 됩니다.&lt;/p&gt;
&lt;h2 id="a-애자일의-씨앗"&gt;A. 애자일의 씨앗&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;고객에게 매일 가치를 전하라.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;고객에게
&lt;ul&gt;
&lt;li&gt;우리의 진짜 고객은 누구인가?&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;매일
&lt;ul&gt;
&lt;li&gt;어떻게 점진적으로 가치를 전할 것인가?&lt;/li&gt;
&lt;li&gt;어떻게 보다 일찍, 그리고 보다 자주 가치를 전할 것인가?&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;가치를
&lt;ul&gt;
&lt;li&gt;무엇이 가치인가?&lt;/li&gt;
&lt;li&gt;지금 우리가 하고 있는 일이 정말 가치를 만드는 일인가?&lt;/li&gt;
&lt;li&gt;지금 가장 높은 가치는 무엇인가?&lt;/li&gt;
&lt;li&gt;비슷한 수준의 가치를 더 값싸게 전달하는 방법은?&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;전하라
&lt;ul&gt;
&lt;li&gt;가치를 우리가 갖고 있지 않고 고객에게 정말 전달하고 있는가?&lt;/li&gt;
&lt;li&gt;고객이 정말 가치를 얻고 있는가?&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;[학습]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;매일&lt;/code&gt;하는 것은 학습의 빈도를 말합니다. 불확실성이 높을수록 빈도가 자주 있어야 합니다.&lt;/p&gt;
&lt;p&gt;목적지가 움직이는 정도가 크다면 잠깐씩 현재 위치와 목적지의 위치를 확인하는 빈도가 잦아야 합니다.&lt;/p&gt;
&lt;p&gt;좋은 학습은 질 높은 피드백에서 옵니다. 진짜 가치를 전달할 때 우리는 진정한 피드백을 받을 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;매일&lt;/code&gt;에는 빈도와 동시에 이른 시점부터 시작한다는 의미가 있습니다. 이러면 학습의 복리 효과를 얻을 수 있게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[협력]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;고객은 넓은 의미로 이해관계자라고 생각합시다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;가치를 전하면&lt;/code&gt; 협력이 쉽습니다. 우선 신뢰가 쌓이게 됩니다. 신뢰가 있을 경우 협력의 비용이 낮아지고 원활해진다는 연구가 많습니다.&lt;/p&gt;
&lt;p&gt;이는 앞의 학습과도 연결되는데 가치를 전달하게 되면 의사소통이 명확하고 구체적이 됩니다.&lt;/p&gt;
&lt;h2 id="b-애자일-도입-성공-요인-분석"&gt;B. 애자일 도입 성공 요인 분석&lt;/h2&gt;&lt;h4 id="a-애자일-도입에-대한-무서운-사실"&gt;a. 애자일 도입에 대한 무서운 사실&lt;/h4&gt;&lt;p&gt;고객참여, 리팩토링, 코딩 후 자동화 단위 테스트 붙이기, 코드 공유가 잘 되어야 프로젝트 성공에 애자일이 도움이 될 수 있습니다.&lt;/p&gt;
&lt;p&gt;많은 조직들이 고객 참여와 코드 공유를 뒤로 미룹니다. 두 가지 모두 &lt;code&gt;사람&lt;/code&gt;이 중심이 되기 때문입니다. 고객 참여는 고객을 설득해야 하고, 코드 공유는 개발자를 설득해야 합니다.&lt;/p&gt;
&lt;p&gt;사람과의 대문과 충돌이 무섭고 두려운 것이죠. 하지만 이런 것들을 제대로 하지 않으면 프로젝트를 서공하기가 더 힘들어집니다.&lt;/p&gt;
&lt;p&gt;애자일 초보팀은 처음에 쉽고 안심이 되는 것에서 시작합니다. 애자일 전문가팀은 무섭고 두렵더라도 중요한 일이라면 그 일을 안하는 리스크를 인식하고 꾸준히 시도합니다.&lt;/p&gt;
&lt;p&gt;두려워도 중요하다면 시도해봐야 하지 않겠는가.&lt;/p&gt;
&lt;h2 id="c-애자일을-애자일스럽게-도입하기"&gt;C. 애자일을 애자일스럽게 도입하기&lt;/h2&gt;&lt;p&gt;도요타가 도요타일 수 있었던 것은 칸반 같은 개별 &lt;code&gt;베스트 프랙티스&lt;/code&gt;가 아니라 그런 실천법들이 생겨날 수 있는 문화적 풍토와 생성적 과정 때문이었습니다.&lt;/p&gt;
&lt;p&gt;Q: 애자일을 진행하는 가운데 가장 빈번히 빚어지는 폐단은 무엇인가?&lt;/p&gt;
&lt;p&gt;A: 애자일은 불확실한 상황에 대한 접근법인데 애자일을 도입할 때 확실성 위에서 진행하려고 한다면 문제가 된다.&lt;/p&gt;
&lt;p&gt;뭘 해야 할지 찾아가는 모습이 애자일입니다.&lt;/p&gt;
&lt;p&gt;이전 경험이 이번에도 정확히 들어 맞는다고 말할 수도 없습니다.&lt;/p&gt;
&lt;p&gt;현명한 전략은 정해진 수순을 따르는 것이 아니라 곁에 있는 사람들과 함께 주변을 탐색하고 조금 나아가고 확인하고를 반복하면서 우리의 현 맥락에 맞는 좋은 전략들을 스스로 만들어 나가는 것이 아닐까 합니다.&lt;/p&gt;
</description><pubDate>Fri, 19 Jan 2024 08:30:08 +0900</pubDate><guid>http://blex.me/@mildsalmon/2024-1-19-%EC%98%A4%EC%A0%84-82828</guid></item><item><title>[2023 연말 회고] 나의 회고 일지</title><link>http://blex.me/@mildsalmon/2023-%EC%97%B0%EB%A7%90-%ED%9A%8C%EA%B3%A0-%EB%82%98%EC%9D%98-%ED%9A%8C%EA%B3%A0-%EC%9D%BC%EC%A7%80</link><description>&lt;p&gt;&lt;del&gt;작년에도 12월 31일에 급하게 연말 회고를 마무리했는데, 올해도 역시나..&lt;/del&gt;&lt;/p&gt;
&lt;h2 id="0-회고를-시작하며"&gt;0. 회고를 시작하며.&lt;/h2&gt;&lt;p&gt;곧 있으면 24년이다. 영원할 것만 같았던 23년도 기억에만 존재하는 순간이 온다. 참 많은 일들이 있었다. 영원할 것만 같았던 매 순간들도 지금 돌아보면 찰나였다. 모든 슬픔과 기쁨의 순간들도 시간의 흐름 속에서 희미해진다. 나에게 23년은 상실의 해였다. 나는 성숙해진 걸까? 아니면 닳고 닳아 무뎌진 걸까? 이 질문에 대한 답은 아래 회고를 통해 판단해 주었으면 한다.&lt;/p&gt;
&lt;p&gt;본격적으로  23년을 회고하기 전에 &lt;a href="https://blex.me/@mildsalmon/10-2022%EB%85%84%EC%97%90-%EB%82%98%EB%8A%94-%EC%96%BC%EB%A7%88%EB%82%98-%EC%84%B1%EC%9E%A5%ED%96%88%EC%9D%84%EA%B9%8C"&gt;2022년 연말에 작성했던 회고&lt;/a&gt;를 되돌아봤다. 과연 나는 22년에 후회하고 반성하면서 개선하고 싶다고 기록한 부분들을 잘 실천했을까? 다행히 연말 회고를 22년도부터 기록했기 때문에 이번에는 좀 더 객관적으로 23년도를 돌아볼 수 있었다.&lt;/p&gt;
&lt;p&gt;목차는 다음처럼 구성했다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;22년도 회고를 통한 23년도 반성&lt;/li&gt;
&lt;li&gt;23년에 대한 회고&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="1-22년도-회고를-통한-23년도-반성"&gt;1. 22년도 회고를 통한 23년도 반성&lt;/h2&gt;&lt;p&gt;22년 회고를 다시 읽어보니 감회가 새로웠다.&lt;/p&gt;
&lt;h4 id="a-the-good"&gt;A. The Good&lt;/h4&gt;&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;The Good
&lt;ul&gt;
&lt;li&gt;좋은 사람들과 좋은 환경이 구성되어 있는 회사에 입사한 것&lt;/li&gt;
&lt;li&gt;사내 지식공유 활동에 불을 붙인 것&lt;/li&gt;
&lt;li&gt;가끔은 여행을 통해 삶에 여유를 준 것&lt;/li&gt;
&lt;li&gt;데이터 인프라를 구축하기 위해 데이터 파이프라인을 만드는 과정에서 기여를 한 것&lt;/li&gt;
&lt;li&gt;개발 외적인 활동에도 참여한 것&lt;/li&gt;
&lt;li&gt;꾸준히 운동을하는 것&lt;/li&gt;
&lt;li&gt;주변에 좋은 사람들을 많이 사귄 것&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://blex.me/@mildsalmon/10-2022%EB%85%84%EC%97%90-%EB%82%98%EB%8A%94-%EC%96%BC%EB%A7%88%EB%82%98-%EC%84%B1%EC%9E%A5%ED%96%88%EC%9D%84%EA%B9%8C#a-the-good"&gt;The Good&lt;/a&gt;에 적힌 많은 부분을 주도적으로 했기 때문에 2023년도에 많은 기회(멘토링, 특강, 이직 등)를 놓치지 않을 수 있었다. 미시세계를 살아가는 나는 잘 살아가는 것인지 항상 궁금했다 (나는 정말 잘하는 걸까? 내가 하는 모든 것들이 최선일까?). 어제와 오늘 그리고 내일만 놓고 보면 나는 대부분 실패를 했고 실수를 반복했다. 그러나 시계열을 2년 정도로 길게 놓고 보니 생각보다 잘 살아왔다는 생각이 든다.&lt;/p&gt;
&lt;p&gt;22년도에 자주 실패하고 가끔 좌절하고 다시 도전하였기에 23년도에 많이 성취하고 덜 두려워하며 더 무모하게 도전할 수 있었다. 실수할까 봐 두려워하고 완벽에 완벽을 기하며 도전조차 하지 않는 어리석은 행동을 하기보다, 실수를 반복할 수밖에 없다는 사실을 인정하고 더 많이 실수하고 더 많이 배울 기회로 삼았다.&lt;/p&gt;
&lt;p&gt;한기용님이 자주 말씀하시던 완벽보다 완성에 초점을 맞추라는 말도 이런 맥락일 것이다. 22년도와 23년도에 많이 실수하고 실패해도 별일 아니란 듯이 말씀해 주시고 격려를 해주신 조우진 팀장님께 다시 한번 감사를 드립니다.&lt;/p&gt;
&lt;h4 id="b-the-bad-the-ugly"&gt;B. The Bad &amp;amp; The Ugly&lt;/h4&gt;&lt;p&gt;22년 회고 중 하지 말아야할 것(The Bad)과 개선하면 좋을 것(The Ugly)들을 적어봤는데, 이 내용에 대해서 본격적으로 회고해보자.&lt;/p&gt;
&lt;p&gt;아래는 22년도에 기록해둔 The Bad와 The Ugly이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;The Bad
&lt;ul&gt;
&lt;li&gt;불확실한 미래에 대해 너무 많이 불안해하고 걱정한 것&lt;/li&gt;
&lt;li&gt;내일까지 데이터 추출해달라는 요청에 너무 많은 스트레스를 받은 것&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;The Ugly
&lt;ul&gt;
&lt;li&gt;개인적인 공부를 소홀히 한 것&lt;/li&gt;
&lt;li&gt;개인 시간을 적극적으로 활용하지 못한 것&lt;/li&gt;
&lt;li&gt;정해둔 소비 한도를 초과한 것 (8개월 중 5개월 정도?)&lt;/li&gt;
&lt;li&gt;귀찮다고 주간 회고를 건너뛴 것&lt;/li&gt;
&lt;li&gt;운전에 대한 너무 큰 두려움 때문에 적극적으로 하지 못한 것&lt;/li&gt;
&lt;li&gt;과거에 많은 도움을 받은 사람 중 일부에게만 고마움을 표현한 것&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;위 내용 중에 개선한 부분과 개선하지 못한 부분은 아래와 같은데, 정리하다 보니 생각보다 많은 부분에서 발전됨을 느꼈다. 22년도에는 외부의 상황에 많이 휘둘렸다면, 23년도에는 불확실한 외부의 상황에도 낙관적인 생각을 하면서 돌파구를 찾았다. 그 외에 많은 부분들을 관리하는 시스템을 적용한 덕분에 작년보다 나아질 수 있었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;23년도에 개선한 부분&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;내일까지 데이터 추출해달라는 요청에 너무 많은 스트레스를 받은 것&lt;/li&gt;
&lt;li&gt;개인적인 공부를 소홀히 한 것&lt;/li&gt;
&lt;li&gt;개인 시간을 적극적으로 활용하지 못한 것&lt;/li&gt;
&lt;li&gt;정해둔 소비 한도를 초과한 것&lt;/li&gt;
&lt;li&gt;귀찮다고 주간 회고를 건너뛴 것&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;23년도에 개선하지 못한 부분&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;불확실한 미래에 대해 너무 많이 불안해하고 걱정한 것&lt;/li&gt;
&lt;li&gt;운전에 대한 너무 큰 두려움 때문에 적극적으로 하지 못한 것&lt;/li&gt;
&lt;li&gt;과거에 많은 도움을 받은 사람 중 일부에게만 고마움을 표현한 것&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h6 id="a-불확실한-미래에-대해-너무-많이-불안해하고"&gt;a. 불확실한 미래에 대해 너무 많이 불안해하고 걱정한 것&lt;/h6&gt;&lt;p&gt;22년도에 어떤 불확실성 때문에 불안해하고 걱정했는지 정확히 기억나지 않는다. 다만, 23년도에는 22년도보다 훨씬 더 거대한 정리해고라는 불확실성을 만났다. 자의 또는 타의로 회사를 떠나가는 동료들. 입사하기 어렵다는 고팍스(Streami)에 당당히 입사하였지만, 회사가 힘들다는 이유로 하루아침에 잘렸던 친구, 형, 누나들. 그날 내 마음의 열정도 함께 잘려 나갔다. 정리해고는 구성원 전체를 번아웃으로 끌고 갔다.&lt;/p&gt;
&lt;p&gt;아직도 정리해고의 모든 순간이 생생하다. 그즈음에 정리해고를 할 것이라는 느낌은 있었다. 그런데 그렇게 갑자기 단번에 진행될 줄은 몰랐다. 일요일 점심 즈음에 연구소 인원들 전원 출근을 하라는 메일이 왔다. 그리고 월요일에 점심을 먹고 오니 연구소장님은  모두와 함께할 수 없어서 유감이라는 말씀을 하셨다. 그리고 정리해고 대상자에게는 전화가 갈 것이라는 말씀을 덧붙이셨다. 그렇게 우리 연구소 인원들은 13시부터 18시까지 머리에 겨눠진 러시안룰렛 총구의 방아쇠가 당겨지기를 하염없이 기다렸다.&lt;/p&gt;
&lt;p&gt;사실 나도 정리해고 대상자인 줄 알았다. 팀장님과 어느 정도 데이터 인프라를 구축한 상황이어서 쓸모를 다 했다고 생각했기 때문이다. 추가로 데이터팀 막내이자 마지막 입사자라는 사실과 더불어 근속기간이 1년이 넘지 않아서 퇴직금을 주지 않아도 된다는 경제성까지 갖춘 나는 정말 매력적이었다. 그래서인지 내가 정리해고 대상자가 아니라는 말은 나를 안심시켰다. 하지만 나와 같은 연도에 입사한 사람 중에 거의 유일하게 살아남았다는 죄책감이 재직기간 내내 마음을 불편하게 만들었다.&lt;/p&gt;
&lt;p&gt;회사는 우리를 언제든지 해고할 수 있다는 선례를 남겼다. 첫 정리해고 이후 꽤 오랜 기간 해고되는 것을 걱정했다. 그래서 힘들고 괴로운 상황을 잊기 위해 오히려 더 미친 듯이 일했다. 멘토링을 포함하면 하루에 11시간에서 12시간은 일을 했던 것 같다. 일에 몰입하면 그 순간만큼은 우울하지 않아서 좋았다. 또한 퇴근하는 매 순간 채용공고를 보며 나에게 부족한 점을 찾으려는 절절한 시간을 보냈다.&lt;/p&gt;
&lt;p&gt;이런 불안과 걱정이 나쁘기만 했다는 생각은 들지 않는다. 걱정이라는 에너지를 긍정적으로 발산하였기에 적극적으로 공부도 하고 여러 가지 기회도 잡을 수 있었으니 말이다. 아쉽지 않다고 하면 거짓말이겠지만, 이 또한 발전을 위한 한 걸음이라고 생각할 수 있다.&lt;/p&gt;
&lt;h6 id="b-운전에-대한-너무-큰-두려움-때문에-적극적"&gt;b. 운전에 대한 너무 큰 두려움 때문에 적극적으로 하지 못한 것&lt;/h6&gt;&lt;p&gt;운전은 여전히 두렵다. 운전이라는 행위가 두려운 것은 아니다. 결은 다르지만, 군대에서 자주포를 조종하는 과정도 재밌었고 승무원들에게 최상의 승차감을 주기 위해 연구하는 과정도 재밌었다. 다만 한 번도 경험해 보지 못한 &lt;code&gt;사고&lt;/code&gt;라는 미지의 경험이 걱정되고 두렵다. 이 내용도 결국 &lt;a href="https://blex.me/@mildsalmon/2023-%EC%97%B0%EB%A7%90-%ED%9A%8C%EA%B3%A0-%EB%82%98%EC%9D%98-%ED%9A%8C%EA%B3%A0-%EC%9D%BC%EC%A7%80#a-%EB%B6%88%ED%99%95%EC%8B%A4%ED%95%9C-%EB%AF%B8%EB%9E%98%EC%97%90-%EB%8C%80%ED%95%B4-%EB%84%88%EB%AC%B4-%EB%A7%8E%EC%9D%B4-%EB%B6%88%EC%95%88%ED%95%B4%ED%95%98%EA%B3%A0"&gt;a. 불확실한 미래에 대해 너무 많이 불안해하고 걱정한 것&lt;/a&gt;의 내용과 연결된다. 미래에 대한 두려움과 걱정, 완벽해야 한다는 강박 때문에 앞으로 나아가질 못했다. 더구나, 여자친구가 운전을 좋아한다는 아주 좋은 핑계도 생겼다. 조금씩이라도 해봐야 하는데, 시도하는 과정이 가장 어렵다.&lt;/p&gt;
&lt;h6 id="c-과거에-많은-도움을-받은-사람-중-일부에게"&gt;c. 과거에 많은 도움을 받은 사람 중 일부에게만 고마움을 표현한 것&lt;/h6&gt;&lt;p&gt;군 시절에 미숙하기도 하고 힘들어하던 내  상황을 나아질 수 있게 많은 도움을 준 전사관님이 있다. 가끔 연락은 하는데 찾아뵙지를 못했다. 24년도에는 꼭 밥이라도 한번 사드려야겠다.&lt;/p&gt;
&lt;p&gt;고팍스에 다닐 때도 많은 사람에게 다양한 도움을 받았는데, &lt;code&gt;별일 없으면, 먼저 연락하지 않는다.&lt;/code&gt;는 말도 안 되는 고집으로 연락을 안 하고 있었다. &lt;code&gt;말하지 않으면 아무도 모른다.&lt;/code&gt;는 사실을 알고 있으면서도 왜 이렇게 생각했을까?. 이제라도 한명 한명 연락을 해봐야겠다.&lt;/p&gt;
&lt;h2 id="2-23년에-대한-회고"&gt;2. 23년에 대한 회고&lt;/h2&gt;&lt;h4 id="a-나의-1년은-어땠을까-비관에서-낙관으로"&gt;A. 나의 1년은 어땠을까? [비관에서 낙관으로]&lt;/h4&gt;&lt;blockquote&gt;
&lt;p&gt;낙관적 = 현재보다 미래가 좋아질 것&lt;/p&gt;
&lt;p&gt;비관적 = 현재보다 미래가 나빠질 것&lt;/p&gt;
&lt;p&gt;우리는 현재가 좋다는 사실을 부정하면서도 낙관적일 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;23년도의 키워드는 &lt;code&gt;상실&lt;/code&gt;, &lt;code&gt;새옹지마&lt;/code&gt;이다. 또한 비관적이던 나를 낙관적으로 만들어준 해이기도 하다. 나는 습관적으로 비관적이었다. &lt;code&gt;어차피 안될 텐데&lt;/code&gt;라는 생각은 나를 쉽게 우울하게 만들었다. 현재가 나쁘다면 미래는 잘해봐야 본전일 것으로 생각했다. 냉소적인 태도는 알게 모르게 내 사고를 잠식하였다. 그러나 몇몇 글들을 통해 냉소가 나의 발전에 큰 도움이 되지 않는다는 것을 알게 되었다. 냉소적이던 나는 조금씩 냉소 대신 낙관을 선택하기 시작했다. 발전을 위해 선택한 낙관은 내 삶에 숨통을 트여주었다.&lt;/p&gt;
&lt;p&gt;낙관주의는 &lt;code&gt;완벽주의로 인한 우울&lt;/code&gt;을 가지고 있던 나를 긍정적인 방향으로 변화시켜 주었다. 완벽주의를 지향하지만, 완벽하지 않은 현실로 인해 좌절하고 우울해지는 빈도가 줄어들었다. &lt;code&gt;뭐 어때, 그럴 수도 있지.&lt;/code&gt;라는 생각이 뿌리를 내린 것처럼 말이다. 생각해 보니 이 생각은 군대에서 많은 도움을 받았던 선임과 전사관님한테 많이 주입받았던 생각이다. 이제서야 내 마음속에 뿌리내리나 보다.&lt;/p&gt;
&lt;p&gt;1년 동안의 내 기분을 그림으로 그려보면 아래 그림과 같다. 참 많은 일들이 있었는데 결과적으로는 좋은 경험들이었다. 스트리미에서 불안정한 멘탈을 가루로 만드는 경험을 했기 때문에 데이터라이즈에서도 문제없이 잘 적응할 수 있었던 것 같다. 생각하기에 따라서 다르겠지만, 힘든 순간이 있었기 때문에 지금의 단단한 내면을 얻을 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/12/30/2023123023_ftpyrDGoklOCKqJTWtqu.png" src="/resources/media/images/content/2023/12/30/2023123023_ftpyrDGoklOCKqJTWtqu.png.preview.jpg" alt=""&gt;
&lt;img class="lazy" data-src="/resources/media/images/content/2023/12/30/2023123023_vqZQRd2pelfdCdNW9MFf.png" src="/resources/media/images/content/2023/12/30/2023123023_vqZQRd2pelfdCdNW9MFf.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;그리고 이제는 빅쏠(더쎈카드) 면접관분이 해주신 어이없는 질문에 담담히 답변할 수 있을 것 같다. 항상 그 자리에서는 치고받고 싸우면서 반박을 못 한다. 시간이 조금 흘러서 화가 사라져야지 이성적이고 합리적인 대답이 떠오른다. 뒤끝이 심한건가?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;빅쏠 면접관: 정리해고를 겪은 소감이 어떠신가요? 나는 직장생활 동안 한 번도 안 겪어봐서 궁금해서 그래요.&lt;/p&gt;
&lt;p&gt;나: 제가 정리해고 대상자는 아니었지만, 가장 힘들었던 시기였습니다. 그렇지만 오히려 그런 시기가 있었기에 더욱 단단해지고 더 많이 성장할 수 있었습니다. 가진 게 없을 때 많이 망가져 봐야 가진 게 많아졌을 때 빨리 회복할 수 있을 테니, 값진 경험이었다고 생각합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;사실 요즘 들어 너무 쉼 없이 달려왔다는 생각이 든다. 대학생 때 단 한 번도 휴학하지 않고 군대도 시기 맞춰서 다녀오고 졸업하자마자 취직도 했다. 딱 한 달만이라도 온전히 온전히 나만을 위해 쉴 수 있는 시간이 있었으면 좋았을 거라는 아쉬움이 든다. 그렇다고 쉬면서 하고 싶은 것들이 명확하게 있는 것도 아니다. 그냥 막연하게 &lt;code&gt;잠깐이라도 쉬면 좋지 않을까?&lt;/code&gt;라는 생각이 든다. 사실 제대로 쉰다는 것이 무엇인지 모르겠다. 나는 왜 그렇게 세상이 정한 선로를 벗어나지 않고 칼같이 맞춰서 달린건지 모르겠다.&lt;/p&gt;
&lt;p&gt;나는 빠른 연생으로 남들보다 1년 먼저 학교에 입학했다. 초등학교 고학년쯤에 어머니한테 &lt;code&gt;나는 왜 친구들보다 한 살 어린 거에요? 나 그냥 1년 쉬면 안 돼요?&lt;/code&gt;라는 질문을 했던 것 같다. 어머니는 언젠가 내가 미끄러졌을 때 남들보다 빠른 1년이 유용할 것이라고 하셨다. 지금 와서 생각해 보면 언젠가는 유용하게 사용할 1년을 아껴두기 위해서 불필요한 낭비 없이 시기를 딱딱 맞춰서 살아온 것 같다. 아이러니하게도 딱딱 맞춰서 살아왔기에 삶의 여유가 무엇인지 모르는 것 같다.&lt;/p&gt;
&lt;p&gt;데이터라이즈의 컬쳐핏 면접을 보면서 CTO 님이 했던 질문과 고팍스의 백엔드 팀장님이 했던 이야기들이 자꾸 머릿속을 맴돈다. &lt;code&gt;나는 왜 성장하고 싶을까?&lt;/code&gt;, &lt;code&gt;회사는 왜 날 뽑아야 하는가?&lt;/code&gt;, &lt;code&gt;내 삶의 목적은 무엇일까?&lt;/code&gt;. 사실 잘 모르겠다. 24년도에는 이 질문들에 대한 답변을 만들어야겠다. 이제는 성장이라고 추상화한 것 아래의 구체를 찾아볼 시간이다. 나는 어떤 구체적인 것을 기대하며 두루뭉술한 추상 속에 숨었을지 알아야겠다.&lt;/p&gt;
&lt;h4 id="b-4l-회고"&gt;B. 4L 회고&lt;/h4&gt;&lt;p&gt;올해는 아래에 설명한 4L 회고 방식으로 진행해볼까 한다. 1년에 한 번 하는 회고인 만큼 다양한 방식으로 진행해보고 가장 좋은 방법은 무엇일지 고민해보려고 한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;4L 회고&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Liked : 좋았던 점은 무엇인가?&lt;/li&gt;
&lt;li&gt;Lacked : 아쉬웠던 점, 부족한 점은 무엇인가?&lt;/li&gt;
&lt;li&gt;Learned : 배운 점은 무엇인가?&lt;/li&gt;
&lt;li&gt;Longed for : 앞으로 바라는 것은 무엇인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4 id="c-liked-좋았던-점"&gt;C. Liked (좋았던 점)&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;평소라면 하지 않았을 것들에 도전한 것. (소개팅)&lt;/li&gt;
&lt;li&gt;아무 생각 없이 유튜브를 보면서 시간을 날리는 중독을 해결한 것&lt;/li&gt;
&lt;li&gt;여자친구와 1년 동안 10번의 여행을 다녀온 것&lt;/li&gt;
&lt;li&gt;3인이지만, 매일 회고를 하는 '시스템'이자 '커뮤니티'를 10월부터 운영하고 있음.&lt;/li&gt;
&lt;li&gt;데이터라이즈에서 다양한 커뮤니케이션 방식을 보고 배울 수 있는 점.&lt;/li&gt;
&lt;li&gt;데이터라이즈에서 에러에 민감한 시스템을 다루면서 생기는 모든 것들을 경험해 볼 수 있음.&lt;/li&gt;
&lt;li&gt;스트리미에서 정말 다양하고 많은 사람들을 만날 수 있었던 것. 데이터라이즈에서도 스페셜리스트이자 제네럴리스트인 분들을 만나서 다양한 경험을 해볼 수 있는 것. 두 직장 다 사람들이 너무 좋다는 것. (교수님 말씀대로 인복이 있는가 보다..)&lt;/li&gt;
&lt;li&gt;먼슬리 모닝 회고를 통해 한 달에 한 번씩 다양한 분야의 사람들과 이야기를 나누며 인사이트를 얻을 수 있는 것.&lt;/li&gt;
&lt;li&gt;막연히 멀리했던 투자를 시작한 것&lt;/li&gt;
&lt;li&gt;액티브 인컴을 극대화시킨 것. (그 덕분에 맥북, 아이패드, 에어팟, 아이폰을 살 수 있었음)&lt;/li&gt;
&lt;li&gt;패시브 인컴을 위해 주식 투자를 하는 것. (다만, 본업에 집중하기 위해 적립식으로만 구매하고 장이 열려있는 시간에는 주식을 안 보려고 노력함)&lt;/li&gt;
&lt;li&gt;복식부기 방식을 사용하는 가계부인 후잉을 사용하여 좀 더 철저하게 가계부를 작성하고 습관으로 만든 것&lt;/li&gt;
&lt;li&gt;스트리미에서는 매 점심을 샐러드, 포케를 먹은 것. 데이터라이즈에서는 가끔 점심으로 먹는 밥의 양을 절반만 먹는 것.&lt;/li&gt;
&lt;li&gt;클라이밍을 취미로 하게 된 것.&lt;/li&gt;
&lt;li&gt;재택이 나쁘지 않다는 경험을 한 것.&lt;/li&gt;
&lt;li&gt;간접적으로 인사이트를 얻을 수 있는 경로를 많이 만들어둔 것. (블로그, 책)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="d-lacked-아쉬웠던-점-부족한-점"&gt;D. Lacked (아쉬웠던 점, 부족한 점)&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;스트리미에서 3년이라는 기간을 채우지 못하고 퇴사한 것.&lt;/li&gt;
&lt;li&gt;퇴사 기념으로 퇴사 회고도 쓰려고 했는데, 쓰지 못했다.&lt;/li&gt;
&lt;li&gt;airflow 강의를 만들려고 했는데, 흐지부지된 것.&lt;/li&gt;
&lt;li&gt;무의식적으로 운전을 계속 두려워하는 것.&lt;/li&gt;
&lt;li&gt;찬물샤워를 하려고 했는데, 2~3번밖에 하지 않은 것.&lt;/li&gt;
&lt;li&gt;패시브 인컴에 대해서 다양하게 생각했지만 실천하지 못한 것. (성장주는 목표했던 만큼 담았으니, 내년에는 배당주 위주로 투자하고 연금저축펀드, IRP, ISA도 공부해서 전략을 세워볼 것)&lt;/li&gt;
&lt;li&gt;임장을 많이 다니지 못한 것. (지금까지는 감으로만 다녔는데, 기준을 세우고 기록을 해야 할 듯함)&lt;/li&gt;
&lt;li&gt;금전적으로 부담이 돼서 PT 등록을 망설인 것&lt;/li&gt;
&lt;li&gt;비트코인을 적립식으로 매수해서 수익을 보다가 도지코인에 몰빵해서 원금을 까먹은 것. (코인은 그만해야겠다고 생각하고 전부 팔아버리자, 비트코인이 5천만원을 향해 달려간 것)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="e-learned-배운-점"&gt;E. Learned (배운 점)&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;조우진 팀장님에게 데이터 '엔지니어'로서의 성장하기 위해서는 어떤 것들을 해야 하는지 배움.&lt;/li&gt;
&lt;li&gt;윤천성 CTO님을 통해 나의 장점에 대해 다시 생각하고 자신감을 얻을 기회가 됨.&lt;/li&gt;
&lt;li&gt;윤승민님께 삶을 바라보는 자세에 대해 배움.&lt;/li&gt;
&lt;li&gt;권형준 팀장님께 나에게 부족한 점(멘탈, 성장의 이유)이 무엇인지 듣고 이를 해결하기 위해 어떤 것을 해야 할지 배움.&lt;/li&gt;
&lt;li&gt;배진오님과 회고 커뮤니티를 만들면서 자동화가 되리라 생각하지 못한 것도 자동화 할 수 있다는 것을 배움.&lt;/li&gt;
&lt;li&gt;한기용님을 통해 Great People Data Engineering Conference에서 발표할 수 있는 기회를 얻음.&lt;/li&gt;
&lt;li&gt;한기용님을 통해 K-Digital Training: 데이터 엔지니어링 데브코스 - 1기 멘토를 할 수 있는 기회를 얻음.&lt;/li&gt;
&lt;li&gt;KDT 과정 1기 5팀, 6팀 분들에게 데이터 인프라에 대한 아키텍처 구조 설명과 면접, 이력서 특강 및 첨삭의 기회를 통해 내가 추상적으로 알고 있던 것들을 구체적으로 알 기회가 됨.&lt;/li&gt;
&lt;li&gt;클라이밍을 배운 것.&lt;/li&gt;
&lt;li&gt;PT를 통해 운동을 배우고 있는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="f-longed-for-앞으로-바라는-것"&gt;F. Longed for (앞으로 바라는 것)&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;이번 회사에서는 해결사가 되는 것&lt;/li&gt;
&lt;li&gt;역마살은 핑계지만, 여러 지역에서 살아보는 것 (가능하면 워케이션 또는 한달 살기도 다녀오기?)&lt;/li&gt;
&lt;li&gt;airflow 강의를 만들기 위해 목차부터 써보는 것. 그리고 airflow 코드도 가끔 파보면서 코드를 통해 학습하는 것&lt;/li&gt;
&lt;li&gt;spark를 어느 정도 할 수 있는 사람이 되는 것.&lt;/li&gt;
&lt;li&gt;월 5만원 정도의 배당소득을 만들어보는 것.&lt;/li&gt;
&lt;li&gt;나만의 데이터 인프라 구축 프로젝트를 해보는 것.&lt;/li&gt;
&lt;li&gt;이번 회사에서의 과업들을 잘 수행하고 관련된 글도 써보는 것.&lt;/li&gt;
&lt;li&gt;책이나 글을 통해 인사이트를 얻는 시간을 고정적으로 만드는 것&lt;/li&gt;
&lt;li&gt;내가 살아가야 하는 이유에 대해 찾기. (왜 성장하고 싶은지, 삶의 목적이 무엇인지, 회사는 날 왜 뽑아야 하는지)&lt;/li&gt;
&lt;/ul&gt;
</description><pubDate>Sun, 31 Dec 2023 23:25:00 +0900</pubDate><guid>http://blex.me/@mildsalmon/2023-%EC%97%B0%EB%A7%90-%ED%9A%8C%EA%B3%A0-%EB%82%98%EC%9D%98-%ED%9A%8C%EA%B3%A0-%EC%9D%BC%EC%A7%80</guid></item><item><title>Diving Into Delta Lake - Unpacking The Transaction Log</title><link>http://blex.me/@mildsalmon/diving-into-delta-lake-unpacking-the-transaction-log</link><description>&lt;blockquote&gt;
&lt;p&gt;Databricks blog에 posting된 글을 정리하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.databricks.com/blog/2019/08/21/diving-into-delta-lake-unpacking-the-transaction-log.html"&gt;https://www.databricks.com/blog/2019/08/21/diving-into-delta-lake-unpacking-the-transaction-log.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;transaction log는 Delta Lake를 이해하기 위한 핵심 개념이다. (transaction log는 ACID transaction, scalable metadata handling, time travel ..etc의 공통 개념이다). 이 글에서는 transaction log가 무엇인지, file level에서는 어떻게 동작하는지, 여러 개의 동시 읽기 및 쓰기 문제에 대한 해결책을 제공하는 방법에 대해 살펴본다.&lt;/p&gt;
&lt;h2 id="1-what-is-the-delta-lake-"&gt;1. What is the Delta Lake Transaction Log?&lt;/h2&gt;&lt;p&gt;Delta Lake transaction log (DeltaLog)는 Delta Lake table이 처음 만들어진 이후 수행된 모든 transaction들이 순서대로 기록된 것이다.&lt;/p&gt;
&lt;h2 id="2-what-is-the-transaction"&gt;2. What is the Transaction Log Used For?&lt;/h2&gt;&lt;h4 id="a-single-source-of-truth"&gt;A. Single Source of Truth&lt;/h4&gt;&lt;p&gt;사용자에게 항상 정확한 데이터 view를 보여주기 위해, Deta Lake transaction log는 사용자가 테이블에 시도하는 모든 변경 사항을 추적하는 중앙 레포지토리(central repository) single source of truth 역할을 한다.&lt;/p&gt;
&lt;p&gt;사용자가 처음으로 Delta Lake 테이블을 읽거나 마지막으로 읽은 이후 수정된 open table에 대해 새로운 쿼리를 실행하면, Spark는 transaction log를 확인하여 테이블에 새로운 transaction이 post되었는지 확인한 다음, end user의 테이블을 새로운 변경사항으로 update한다. 이렇게 하면 사용자의 테이블 버전이 항상 가장 최근 쿼리 시점의 마스터 레코드(모든 transaction log가 적용된 버전)와 synchronized된다.&lt;/p&gt;
&lt;h4 id="b-the-implementation-of-a"&gt;B. The Implementation of Atomicity on Delta Lake&lt;/h4&gt;&lt;p&gt;data lake에서 수행되는 작업(insert, update..)이 완전히 완료되거나 전혀 완료되지 않도록 보장한다. 하드웨어 장애나 소프트웨어 버그로 인해 데이터가 테이블에 부분적으로만 기록되는 문제를 방지한다.&lt;/p&gt;
&lt;p&gt;transaction log는 delta lake가 atomicity를 보장하게하는 매커니즘이다. transaction log에 기록되지 않은 트랜잭션은 발생한 적이 없는 것이나 마찬가지이다. 완전하고 완벽하게 실행된 transaction만 기록하고 이 기록을 single source of truth로 사용한다.&lt;/p&gt;
&lt;h2 id="3-how-doese-the-transacti"&gt;3. How Doese the Transaction Log Work?&lt;/h2&gt;&lt;h4 id="a-breaking-down-transacti"&gt;A. Breaking Down Transaction Into Atomic Commits&lt;/h4&gt;&lt;p&gt;사용자가 테이블을 수정하는 작업(INSERT, UPDATE, DELETE)을 수행할 때마다 Delta Lake는 해당 작업을 아래 작업 중 하나 이상의 작업으로 구성된 일련의 개별 단계로 세분화한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add file: 데이터 파일을 추가한다.&lt;/li&gt;
&lt;li&gt;Remove file: 데이터 파일을 제거한다.&lt;/li&gt;
&lt;li&gt;Update metadata: 테이블의 메타데이터를 업데이트한다. (table's name, schema, partitioning)&lt;/li&gt;
&lt;li&gt;Set transaction: 구조화된 스트리밍 작업이 지정된 ID로 micro-batch를 commit했음을 기록한다.&lt;/li&gt;
&lt;li&gt;Change protocol: Delta Lake transaction log를 최신 software protocol로 전환하여 새로운 기능을 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;Commit info: commit과 관련된 정보, 어떤 작업이 언제 어디서 수행되었는지에 대한 정보를 포함한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그 후 transaction log에 commit이라는 정렬된 원자 단위로 기록된다.&lt;/p&gt;
&lt;h6 id="a-예시"&gt;a. 예시&lt;/h6&gt;&lt;p&gt;user가 table에 새로운 column을 추가하고 데이터를 더 추가하기 위해 transaction을 생성한다. Delta Lake는 해당 transaction을 구성 요소로 나누고, transaction이 완료되면 다음 commit으로 transaction log에 추가한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Update metadata: 새로운 column을 포함하도록 schema를 변경한다.&lt;/li&gt;
&lt;li&gt;Add file: 추가된 각 파일에 대해&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="b-the-delta-lake-transact"&gt;B. The Delta Lake Transaction Log at the File Level&lt;/h4&gt;&lt;p&gt;user가 Delta Lake 테이블을 생성하면 해당 table의 transaction log가 &lt;code&gt;_delta_log&lt;/code&gt; 하위 디렉토리에 자동으로 생성된다. user가 해당 테이블을 변경하면 변경 사항은 transaction log에 순서대로 atomic commit으로 기록된다. 각 commit은 000000.json으로 시작하는 JSON 파일로 기록된다. 테이블을 추가로 변경하면 다음 commit이 000001.json으로 기록되는 식으로 후속 JSON 파일이 생성된다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/11/25/202311258_5wFoobOETuqrhrEAVKYQ.png" src="/resources/media/images/content/2023/11/25/202311258_5wFoobOETuqrhrEAVKYQ.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;h6 id="a-예시-1"&gt;a. 예시&lt;/h6&gt;&lt;p&gt;테이블에 1.parquet, 2.parquet 파일로 레코드를 추가할 수 있다. 이 transaction은 transaction log에 자동으로 추가되고 커밋 000000.json으로 distk에 저장된다.&lt;/p&gt;
&lt;p&gt;그 다음 레코드를 삭제하여 1.parquet, 2.parquet을 삭제하고 새 레코드(3.parquet)를 추가할 수 있다. 이 작업은 000001.json으로 다음 commit에 기록된다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/11/25/202311258_yKRcOiM7eETQfaF4lXsf.png" src="/resources/media/images/content/2023/11/25/202311258_yKRcOiM7eETQfaF4lXsf.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;1.parquet, 2.parquet은 더 이상 Delta lake 테이블의 일부가 아니지만, 궁극적으로 서로를 상쇄했음에도 불구하고 테이블에서 해당 작업이 수행되었기 때문에 transaction log에 추가, 삭제가 기록된다.&lt;/p&gt;
&lt;p&gt;Delta Lake는 테이블을 audit하거나 특정 시점으로 time travel을 하는 경우 정확하게 확인할 수 있도록 atomic commit을 유지한다.&lt;/p&gt;
&lt;p&gt;또한 테이블에서 데이터 파일을 제거했어도 Spark는 Disk에서 파일을 열심히 제거하지 않는다. 사용자는 더 이상 필요하지 않는 파일은 VACUUM을 사용하여 삭제할 수 있다.&lt;/p&gt;
&lt;h4 id="c-quickly-recomputing-sta"&gt;C. Quickly Recomputing State With Checkpoint Files&lt;/h4&gt;&lt;p&gt;transaction log에 여러 번의 commit을 수행한 후, Delta Lake는 동일한 &lt;code&gt;_delta_log&lt;/code&gt; 하위 디렉토리에 Parquet 형식의 checkpoint 파일을 저장한다. Delta Lake는 우수한 읽기 성능을 유지하기 위해 필요에 따라 자동으로 checkpoint를 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/11/25/202311258_1W6mYu29PJtWanl6P5Yw.png" src="/resources/media/images/content/2023/11/25/202311258_1W6mYu29PJtWanl6P5Yw.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;이런 checkpoint 파일은 특정 시점에 테이블의 전체 상태를 spark가 빠르고 쉽게 읽을 수 있는 parquet 형식으로 저장한다. 즉, 이 파일은 Spark 리더가 테이블의 상태를 완전히 재현할 수 있는 일종의 &lt;code&gt;바로가기&lt;/code&gt;를 제공함으로써 Spark가 수천 개의 작고 비효율적인 JSON 파일을 재처리하지 않도록 한다.&lt;/p&gt;
&lt;p&gt;속도를 높이기 위해 Spark는 &lt;code&gt;listFrom&lt;/code&gt;작업을 실행하여 transaction log의 모든 파일을 보고, 최신 checkpoint 파일로 빠르게 건너뛰고, 가장 최근의 checkpoint 파일이 저장된 이후에 이루어진 JSON commit만 처리할 수 있다.&lt;/p&gt;
&lt;p&gt;아래 그림에서 0000007.json 까지 commit을 생성했다고 생각하자. Spark는 이 commit을 통해 가장 최신 버전의 테이블을 memory에 자동으로 cache하여 빠르게 처리한다.&lt;/p&gt;
&lt;p&gt;만약 다른 사용자가 테이블에 새 데이터를 작성하여 000012.json까지 commit을 추가할 수 있다. 이러한 새로운 transaction을 통합하고 테이블의 상태를 업데이트하기 위해 Spark는 &lt;code&gt;listFrom version 7&lt;/code&gt; 작업을 실행하여 테이블의 새로운 변경 사항을 확인한다.&lt;/p&gt;
&lt;p&gt;Spark는 중간의 모든 JSON 파일을 처리하는 대신 commit #10에 있는 테이블의 전체 상태가 포함되어 있는 가장 최근 checkpoint 파일로 건너뛸 수 있다. 이제 Spark는 000011.json, 000012.json의 증분 처리만 수행하면 테이블의 현재 상태를 확보할 수 있다. 그런 다음 Spark는 테이블의 &lt;code&gt;version 12&lt;/code&gt;를 memory에 cache한다. 이 워크플로우를 따라 Delta Lake는 Spark를 사용하여 테이블의 상태를 항상 효율적으로 업데이트할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/11/25/202311258_kJ4TYsqOWxnLOukCG6Jq.png" src="/resources/media/images/content/2023/11/25/202311258_kJ4TYsqOWxnLOukCG6Jq.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;spark는 데이터를 memory 위에 올려서 사용한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="d-dealing-with-multiple-c"&gt;D. Dealing With Multiple Concurrent Reads and Writes&lt;/h4&gt;&lt;p&gt;만약 Delta Lake가 multiple concurrent read와 writes를 하는 경우에는 어떻게 될까?&lt;/p&gt;
&lt;p&gt;Delta Lake는 Apache Spark로 구동되기 때문에 여러 사용자가 한 번에 테이블을 수정할 수 있다. 이러한 상황을 처리하기 위해 Delta Lake는 optimistic concurrency control을 사용한다.&lt;/p&gt;
&lt;h4 id="e-what-is-optimistic-conc"&gt;E. What is Optimistic Concurrency Control?&lt;/h4&gt;&lt;p&gt;Optimistic concurrency control은 여러 사용자가 테이블에 대해 수행한 transaction(changes)이 서로 충돌하지 않고 완료될 수 있닥 가정하는 concurrent transaction을 처리하는 방법이다. petabytes 단위의 데이터를 처리할 때 &lt;code&gt;사용자가 데이터의 다른 부분에서 동시에 작업&lt;/code&gt;할 가능성이 높기 때문에 충돌하지 않는 transaction을 동시에 완료할 수 있다. 그래서 빠르다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;큰 퍼즐을 서로 다른 가장자리에서 맞추는 것처럼&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;optimistic concurrency control을 사용해서 사용자가 동일한 부분을 동시에 수정하려고 시도할 수 있다. Delta Lake에는 이를 위한 protocol이 있다.&lt;/p&gt;
&lt;h4 id="f-solving-conflicts-optim"&gt;F. Solving Conflicts Optimistically&lt;/h4&gt;&lt;p&gt;Delta Lake는 ACID transaction을 제공하기 위해 commit을 어떻게 정렬해야 하는지(db에서 serializability 개념) 파악하고, 두 개 이상의 commit이 동시에 수행되는 경우 어떻게 해야 하는지 결정하는 protocol이 있다. Delta Lake는 mutual exclusion 규칙을 구현하여 이러한 경우를 처리한 다음 충돌이 발생하면 optimisticlly 해결하려고 시도한다. 이 protocol을 통해 Delta Lake는 여러 번의 동시 쓰기 후 테이블의 결과 상태가 서로 분리되어 연속적으로 발생한 경우와 동일하도록 보장하는 ACID isolation원칙을 구현할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;일반적인 프로세스는
&lt;ol&gt;
&lt;li&gt;시작 테이블 버전을 기록한다.&lt;/li&gt;
&lt;li&gt;일기/쓰기를 기록한다.&lt;/li&gt;
&lt;li&gt;커밋을 시도한다.&lt;/li&gt;
&lt;li&gt;다른 사람이 이긴 경우, 읽은 내용이 변경되었는지 확인한다.&lt;/li&gt;
&lt;li&gt;반복한다.&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/11/25/202311258_VFvLK95HU66lzudNlntf.png" src="/resources/media/images/content/2023/11/25/202311258_VFvLK95HU66lzudNlntf.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Delta Lake는 변경전에 읽은 테이블의 시작 테이블 버전(version 0)을 기록한다.&lt;/li&gt;
&lt;li&gt;user 1, 2가 동시에 테이블에 일부 데이터를 추가하려고 시도한다. 여기서 충돌이 발생하는 이유는 다음 커밋 중 하나만 000001.json으로 기록될 수 있기 때문이다.&lt;/li&gt;
&lt;li&gt;Delta Lake는 mutual exclusion라는 개념으로 이 충돌을 처리하는데, 이는 한 명의 사용자만 000001.json을 성공적으로 commit할 수 있다는 것을 의미한다. user 1의 commit은 accept되고 user 2는 reject된다.&lt;/li&gt;
&lt;li&gt;user 2에 대해 Delta Lake는 optimistically 처리하는 것을 선호한다. 테이블에 새로운 커밋이 수행되었는지 확인하고 해당 변경 사항을 반영하도록 테이블을 조용히 업데이트한 다음, 데이터 처리 없이 새로 업데이트된 테이블에 대한 사용자 2의 커밋을 다시 시도하여 000002.json을 성공적으로 commit한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Delta Lake가 optimistically 해결할 수 없는 조정 불가능한 문제가 있는 경우, 유일한 옵션은 오류를 발생시키는 것이다.&lt;/p&gt;
&lt;p&gt;마지막으로, Delta Lake 테이블에서 이루어지는 모든 transaction은 disk에 직접 저장되므로, 이 프로세스는 durability라는 ACID 속성을 충족한다.&lt;/p&gt;
&lt;h2 id="4-other-use-cases"&gt;4. Other Use Cases&lt;/h2&gt;&lt;h4 id="a-time-travel"&gt;A. Time Travel&lt;/h4&gt;&lt;p&gt;모든 테이블은 Delta Lake transaction log에 기록된 모든 commit을 합산한 결과이다. transaction log는 테이블의 원래 상태에서 현재 상태로 이동하는 방법을 정확하게 설명한다.&lt;/p&gt;
&lt;p&gt;따라서 원본 테이블에서 시작하여 그 시점 이전의 commit만 처리함으로써 어느 시점에든 테이블의 상태를 재현할 수 있다. 이 기능을 time travel 또는 data versioning이라고 한다.&lt;/p&gt;
&lt;p&gt;cc. &lt;a href="https://www.databricks.com/blog/2019/02/04/introducing-delta-time-travel-for-large-scale-data-lakes.html"&gt;Introducing Delta Time Travel for Large Scale Data Lakes&lt;/a&gt;, &lt;a href="https://docs.databricks.com/delta/delta-batch.html#query-an-older-snapshot-of-a-table-time-travel"&gt;Delta Lake time travel documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="b-data-lineage-and-debugg"&gt;B. Data lineage and Debugging&lt;/h4&gt;&lt;p&gt;테이블에 적용된 모든 변경 사항에 대한 최종 기록인 Delta Lake transaction log는 사용자에게 governance, audit, compliance purpose에 유용한 검증 가능한 data lineage를 제공한다. 또한, 의도치 않은 변경이나 파이프라인의 버그가 발생했을 때 그 원인이 된 정확한 작업까지 추적하는 데에도 사용할 수 있다. 사용자는 &lt;code&gt;DESCRIBE HISTORY&lt;/code&gt;를 실행하여 변경된 내용과 관련된 메타데이터를 확인할 수 있다.&lt;/p&gt;
</description><pubDate>Sat, 25 Nov 2023 08:52:02 +0900</pubDate><guid>http://blex.me/@mildsalmon/diving-into-delta-lake-unpacking-the-transaction-log</guid></item><item><title>Spark 맛보기 - 4. RDD, DataFrame, DataSet API</title><link>http://blex.me/@mildsalmon/spark-%EB%A7%9B%EB%B3%B4%EA%B8%B0-4-rdd-dataframe-dataset-api</link><description>&lt;h2 id="1-rdd란"&gt;1. RDD란?&lt;/h2&gt;&lt;p&gt;RDD(Resilient Distributed Dataset, 탄력적 분산 데이터셋)는 Apache Spark의 핵심 개념 중 하나로, 분산 환경에서 병렬로 처리될 수 있는 불변의 데이터 컬렉션이다. RDD는 Spark의 데이터 처리와 트랜스포메이션을 수행하는 데 사용되는 기본적인 데이터 구조이다.&lt;/p&gt;
&lt;p&gt;RDD는 Spark의 초기 데이터 추상화 모델이며, 후에 DataFrame과 Dataset과 같은 고수준의 추상화가 도입되었다. DataFrame과 Dataset은 RDD에 비해 더 효율적인 실행 계획을 생성하고 다양한 최적화를 제공하지만, RDD는 저수준 API로서 유용성을 가지고 있다.&lt;/p&gt;
&lt;h4 id="a-rdd의-주요-특징"&gt;A. RDD의 주요 특징&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;불변성 (Immutability)
&lt;ul&gt;
&lt;li&gt;한 번 생성된 RDD는 변경될 수 없다. 데이터를 변형하려면, 새로운 RDD를 생성하는 transformation 연산을 수행해야 한다.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;탄력성 (Resilience)
&lt;ul&gt;
&lt;li&gt;노드 장애가 발생하더라도 RDD의 lineage 정보는 통해 어떻게 변형되어 왔는지에 대한 정보를 가지고 있어서 필요한 경우 원본 데이터로부터 RDD를 재구성할 수 있다.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;분산 처리 (Distributed)
&lt;ul&gt;
&lt;li&gt;데이터는 클러스터 내 여러 노드에 걸쳐 분산되어 저장된다. 이를 통해 대규모 데이터셋을 효율적으로 처리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;RDD는 transformation과 action을 사용하여 작업을 수행한다. Transformation은 새로운 RDD를 생성하지만, 실제 계산은 action이 호출될 때까지 지연된다. (Lazy evaluation)&lt;/p&gt;
&lt;h4 id="b-rdd의-내부적인-특성"&gt;B. RDD의 내부적인 특성&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;의존성 (Dependency)
&lt;ul&gt;
&lt;li&gt;RDD는 다른 RDD로부터 파생될 수 있으며, 이런 관계를 의존성이라고 한다.&lt;/li&gt;
&lt;li&gt;Narrow Dependency는 각 자식 RDD 파티션이 단일 부모 RDD 파티션에만 의존한다. (데이터 셔플링이 필요 없는 경우)&lt;/li&gt;
&lt;li&gt;Wide Dependency는 자식 RDD 파티션이 여러 부모 RDD 파티션에 의존할 수 있다. (데이터 셔플링이 필요하다.)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;파티션 (Partition)
&lt;ul&gt;
&lt;li&gt;RDD 데이터의 물리적 분할을 나타낸다.&lt;/li&gt;
&lt;li&gt;각 파티션은 클러스터의 다른 노드에서 독립적으로 처리될 수 있으며, 이를 통해 병렬 처리가 가능해진다.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;연산 함수 (compute function)
&lt;ul&gt;
&lt;li&gt;RDD에 저장되는 데이터를 Iterator[T] 형태로 만들어주는 compute() 함수이다.&lt;/li&gt;
&lt;li&gt;RDD의 compute() 함수는 RDD의 내부 구현의 일부이며 사용자가 직접적으로 이 함수를 호출할 일은 거의 없다.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="c-rdd의-문제"&gt;C. RDD의 문제&lt;/h4&gt;&lt;blockquote&gt;
&lt;p&gt;아래 문제점들은 Spark의 후속 버전에서 DataFrame과 Dataset과 같은 새로운 데이터 추상화 모델을 도입하게 된 배경이 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;사용자가 연산 함수 안에서 무엇을 하는지 스파크가 알 수 없었다.
&lt;ul&gt;
&lt;li&gt;Spark는 사용자 정의 함수의 내부 로직을 파악할 수 없었고, 이러한 함수에 대한 최적화를 수행하기 어려웠다.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Iterator[T] 데이터 타입이 파이썬 RDD에서 불투명했다.
&lt;ul&gt;
&lt;li&gt;Spark가 데이터의 구조나 타입에 대한 정확한 정보 없이 작업을 수행해야 함을 의미한다.&lt;/li&gt;
&lt;li&gt;동적 타입 언어에서 이 문제가 두드러졌다.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;스파크는 위에서 T로 표시한 타입에 대한 정보가 전혀 없었다.
&lt;ul&gt;
&lt;li&gt;RDD의 데이터는 구조화되지 않은 형태로 취급되어서 데이터 압축 기술을 효과적으로 적용하기 어려웠다.&lt;/li&gt;
&lt;li&gt;정체를 알 수 없는 객체를 바이트 뭉치로 직렬화해 사용하는 수 밖에 없었다.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="2-spark의-구조-확립"&gt;2. Spark의 구조 확립&lt;/h2&gt;&lt;h4 id="a-구조-확립을-위한-핵심-개념들"&gt;A. 구조 확립을 위한 핵심 개념들&lt;/h4&gt;&lt;h6 id="a-일상적인-패턴들을-사용하여-연산을-표현"&gt;a. 일상적인 패턴들을 사용하여 연산을 표현&lt;/h6&gt;&lt;ul&gt;
&lt;li&gt;일상적인 패턴들을 써서 연산을 표현&lt;/li&gt;
&lt;li&gt;필터링, 선택, 집합연산, 집계, 평균, 그룹화 같은 고수준 연산으로 표현되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;-&amp;gt; 명료함과 단순함&lt;/p&gt;
&lt;h6 id="b-지원-언어에서의-api-사용"&gt;b. 지원 언어에서의 API 사용&lt;/h6&gt;&lt;ul&gt;
&lt;li&gt;지원 언어에서의 API 사용이 가능해졌다.&lt;/li&gt;
&lt;li&gt;연산자들은 스파크에게 데이터로 &lt;strong&gt;무엇을&lt;/strong&gt; 작업하고 싶은지, 결과로 무엇을 원하는지 알려준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;-&amp;gt; 실행을 위한 효율적인 플랜 작성이 가능&lt;/p&gt;
&lt;h6 id="c-데이터를-표-형태로-구성"&gt;c. 데이터를 표 형태로 구성&lt;/h6&gt;&lt;ul&gt;
&lt;li&gt;정형화 타입을 써서 데이터를 표 형태로 구성할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="b-핵심적인-장점과-이득"&gt;B. 핵심적인 장점과 이득&lt;/h4&gt;&lt;p&gt;구조를 갖추면 스파크 컴포넌트를 통틀어 더 나은 성능과 공간 효율성 등 많은 이득을 얻을 수 있다.&lt;/p&gt;
&lt;h6 id="a-저수준의-rdd-api-패턴"&gt;a. 저수준의 RDD API 패턴&lt;/h6&gt;&lt;pre&gt;&lt;code class="language-python"&gt;# (name, age) 형태의 튜플로 된 RDD를 생성
dataRDD = sc.parallelize([(&amp;quot;Brooke&amp;quot;, 20), (&amp;quot;Denny&amp;quot;, 31), (&amp;quot;Jules&amp;quot;, 30)])
# 집계와 평균을 위한 람다 표현식과 함께 map, reduceByKey 트랜스포메이션을 사용
agesRDD = (dataRDD
      .map(lambda x: (x[0], (x[1], 1)))
      .reduceByKey(lambda x, y: (x[0] + y[0], x[1] + y[1]))
      .map(lambda x: (x[0], x[1][0]/x[1][1]))
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;스파크에게 &lt;strong&gt;어떻게&lt;/strong&gt; 키를 집계하고 평균 계산을 하는지 작성되었다.&lt;/p&gt;
&lt;p&gt;-&amp;gt; 스파크에게 쿼리를 계산하는 과정을 직접적으로 지시하고 있다.&lt;/p&gt;
&lt;h6 id="b-고수준-dsl-연산자들과-데이터-프레임-a"&gt;b. 고수준 DSL 연산자들과 데이터 프레임 API&lt;/h6&gt;&lt;pre&gt;&lt;code class="language-python"&gt;from pyspark.sql import SparkSession
from pyspark.sql.functions import avg

# SparkSession으로부터 데이터 프레임을 만든다.
spark = (SparkSession
    .builder
    .appName(&amp;quot;AuthorsAges&amp;quot;)
    .getOrCreate()
)

# 데이터 프레임 생성
data_df = spark.createDataFrame([(&amp;quot;Brooke&amp;quot;, 20), (&amp;quot;Denny&amp;quot;, 31), (&amp;quot;Jules&amp;quot;, 30)], [&amp;quot;name&amp;quot;, &amp;quot;age&amp;quot;])
# 동일한 이름으로 그룹화하여 나이별로 계산해 평균
avg_df = data_df.groupBy(&amp;quot;name&amp;quot;).agg(avg(&amp;quot;age&amp;quot;))
avg_df.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;고수준 DSL 연산자들과 데이터 프레임 API를 써서 스파크에게 &lt;strong&gt;무엇을&lt;/strong&gt; 할지 알려준다.&lt;/p&gt;
&lt;p&gt;스파크는 이런 쿼리를 파악해서 사용자의 의도를 이해할 수 있기 때문에 효과적인 실행을 위해 연산들을 최적화하거나 적절하게 재배열할 수 있다.&lt;/p&gt;
&lt;p&gt;스파크의 상위 수준 API는 컴포넌트들과 언어를 통틀어 일관성을 갖고 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-scala"&gt;...

val spark = SparkSession
    .builder
    .appName(&amp;quot;AuthorsAges&amp;quot;)
    .getOrCreate()
val dataDF = spark.createDataFrame(Seq((&amp;quot;Brooke&amp;quot;, 20), (&amp;quot;Denny&amp;quot;, 31))).toDF(&amp;quot;name&amp;quot;, &amp;quot;age&amp;quot;)
val avgDF = dataDF.groupBy(&amp;quot;name&amp;quot;).agg(avg(&amp;quot;age&amp;quot;))
acgDF.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이런 단순성이나 표현력은 상위 수준 구조화 API 위에 구축된 스파크 SQL 엔진 덕분에 가능한 것이다.&lt;/p&gt;
&lt;h2 id="3-dataframe-api"&gt;3. DataFrame API&lt;/h2&gt;&lt;p&gt;Spark DataFrame은 이름 있는 컬럼과 스키마를 가진 분산 인메모리 테이블처럼 동작하며, 각 컬럼은 특정한 데이터 타입을 가질 수 있다.&lt;/p&gt;
&lt;h4 id="a-스파크의-기본-데이터-타입"&gt;A. 스파크의 기본 데이터 타입&lt;/h4&gt;&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/11/12/2023111210_xWftKTlVqkalWNoC3T7G.png" src="/resources/media/images/content/2023/11/12/2023111210_xWftKTlVqkalWNoC3T7G.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/11/12/2023111210_5BZ15Qb0WBSV5m7jwCef.png" src="/resources/media/images/content/2023/11/12/2023111210_5BZ15Qb0WBSV5m7jwCef.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;h4 id="b-스파크의-정형화-타입과-복합-타입"&gt;B. 스파크의 정형화 타입과 복합 타입&lt;/h4&gt;&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/11/12/2023111210_9wszO6OxvXu1h5NWvRMD.png" src="/resources/media/images/content/2023/11/12/2023111210_9wszO6OxvXu1h5NWvRMD.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/11/12/2023111210_ckNM2KFbwaFcJ3XCS0Uh.png" src="/resources/media/images/content/2023/11/12/2023111210_ckNM2KFbwaFcJ3XCS0Uh.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;h4 id="c-스키마와-데이터-프레임-만들기"&gt;C. 스키마와 데이터 프레임 만들기&lt;/h4&gt;&lt;p&gt;Schema는 데이터 프레임을 위해 칼럼 이름과 연관된 데이터 타입을 정의한 것이다. 스키마는 외부 데이터 소스에서 구조화된 데이터를 읽어 들일 때 쓰이게 된다.&lt;/p&gt;
&lt;h6 id="a-스키마를-미리-정의하는-것의-장점"&gt;a. 스키마를 미리 정의하는 것의 장점&lt;/h6&gt;&lt;ul&gt;
&lt;li&gt;스파크가 데이터 타입을 추측해야 하는 책임을 덜어 준다.&lt;/li&gt;
&lt;li&gt;스파크가 스키마를 확정하기 위해 파일의 많은 부분을 읽어들이려고 별도의 job을 만드는 것을 방지한다.
&lt;ul&gt;
&lt;li&gt;데이터 파일이 큰 경우, 이는 시간과 비용이 많이 드는 작업이다.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;데이터가 스키마와 맞지 않는 경우, 조기에 문제를 발견할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h6 id="b-스키마를-정의하는-두-가지-방법"&gt;b. 스키마를 정의하는 두 가지 방법&lt;/h6&gt;&lt;p&gt;스파크는 1. 프로그래밍 스타일, 2. DDL(data definition language)을 사용하여 스키마를 정의한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# 프로그래밍 스타일

from pyspark.sql.types import *

schema = StructType([
    StructField(&amp;quot;author&amp;quot;, StringType(), False),
    StructField(&amp;quot;title&amp;quot;, StringType(), False),
    StructField(&amp;quot;pages&amp;quot;, IntegerType(), False)
])
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# DDL

schema = &amp;quot;author STRING, title STRING, pages INT&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id="d-칼럼과-표현식"&gt;D. 칼럼과 표현식&lt;/h4&gt;&lt;p&gt;스파크가 지원하는 언어들에서 칼럼은 public 메소드를 가진 객체로 표현된다.&lt;/p&gt;
&lt;p&gt;논리식이나 수학 표현식을 칼럼에 사용할 수도 있다.&lt;/p&gt;
&lt;p&gt;스칼라, 자바, 파이썬은 모두 칼럼과 연관된 공개 메서드[1]들을 갖고 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-scala"&gt;blogDF.select(expr(&amp;quot;Hits * 2&amp;quot;)).show(2)
blogDF.select(col(&amp;quot;Hits&amp;quot;) * 2).show(2)
blogsDF.withColumn(&amp;quot;AuthorsId&amp;quot;, (concat(expr(&amp;quot;First&amp;quot;), expr(&amp;quot;Last&amp;quot;), expr(&amp;quot;Id&amp;quot;))))
    .select(col(&amp;quot;AuthorsId&amp;quot;))
    .show(4)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id="e-row"&gt;E. Row&lt;/h4&gt;&lt;p&gt;Row는 스파크의 객체이고 순서가 있는 필드 집합 객체이므로 각 필드를 0부터 시작하는 인덱스로 접근한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;from pyspark.sql import Row

blog_row = Row(6, &amp;quot;Reynold&amp;quot;, &amp;quot;Xin&amp;quot;, &amp;quot;https://tinyurl.6&amp;quot;, 255568, &amp;quot;3/2/2015&amp;quot;)
blog_row[1]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Row 객체들은 빠른 탐색을 위해 데이터 프레임으로 만들어 사용하기도 한다.&lt;/p&gt;
&lt;p&gt;대부분의 경우 파일들은 규모가 크기 때문에 스키마를 미리 지정해 사용하는 것이 훨씬 더 빠르고 효율적인 방법이다.&lt;/p&gt;
&lt;h4 id="f-자주-쓰이는-dataframe-작업들"&gt;F. 자주 쓰이는 DataFrame 작업들&lt;/h4&gt;&lt;h6 id="a-dataframereader"&gt;a. DataFrameReader&lt;/h6&gt;&lt;p&gt;스파크는 데이터 소스에서 데이터 프레임으로 로드하기 위해 DataFrameReader라는 이름의 인터페이스를 제공하며 이는 JSON, CSV, Parquet, Text, AVRO, ORC 포멧을 지원한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;from pyspark.sql.types import *  
from pyspark.sql.functions import *

fire_schema = StructType([StructField('CallNumber', IntegerType(), True),  
                     StructField('UnitID', StringType(), True),  
                     StructField('IncidentNumber', IntegerType(), True),  
                     ...         
                     StructField('ALSUnit', BooleanType(), True),         
                     StructField('CallTypeGroup', StringType(), True),  
                     StructField('NumAlarms', IntegerType(), True),  
                     StructField('UnitType', StringType(), True),  
                     StructField('UnitSequenceInCallDispatch', IntegerType(), True),  
                     StructField('FirePreventionDistrict', StringType(), True),  
                     StructField('SupervisorDistrict', StringType(), True),  
                     StructField('Neighborhood', StringType(), True),  
                     StructField('Location', StringType(), True),  
                     StructField('RowID', StringType(), True),  
                     StructField('Delay', FloatType(), True)])

sf_fire_file = &amp;quot;/Users/mildsalmon/Downloads/LearningSparkV2/databricks-datasets/learning-spark-v2/sf-fire/sf-fire-calls.csv&amp;quot;
fire_df = spark.read.csv(sf_fire_file, header=True, schema=fire_schema)
&lt;/code&gt;&lt;/pre&gt;
&lt;h6 id="b-dataframewriter"&gt;b. DataFrameWriter&lt;/h6&gt;&lt;p&gt;특정 포맷의 데이터 소스에 데이터 프레임의 데이터를 써서 내보내기 위해서는 DataFrameWriter를 쓴다.&lt;/p&gt;
&lt;p&gt;DataFrameWriter도 다양한 데이터 소스[2]를 지원한다. 만약 데이터프레임이 parquet로 쓰여졌다면 스키마는 parquet 메타데이터의 일부로 보존될 수 있다. 이 경우 데이터 프레임으로 읽을 때 수동으로 스키마를 적용할 필요가 없다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# 데이터를 탐색하고 변환한 후 파케이 포맷이나 SQL 테이블로 데이터를 저장한다.

parquet_path = ...
fire_df.write.format(&amp;quot;parquet&amp;quot;).save(parquet_path)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# 하이브 메타스토어에 메타데이터로 등록되는 테이블로 저장할 수 있다.

parquet_table = ...
fire_df.write.format(&amp;quot;parquet&amp;quot;).saveAsTable(parquet_table)
&lt;/code&gt;&lt;/pre&gt;
&lt;h6 id="c-transformation과-action"&gt;c. transformation과 action&lt;/h6&gt;&lt;p&gt;&lt;a href="https://blex.me/@mildsalmon/spark-%EB%A7%9B%EB%B3%B4%EA%B8%B0-3-transformation-action-lazy-evalu"&gt;Spark 맛보기 - 3. transformation, action, lazy evaluation&lt;/a&gt;&lt;/p&gt;
&lt;h6 id="d-projection과-filter"&gt;d. projection과 filter&lt;/h6&gt;&lt;p&gt;spark에서 projection은 &lt;code&gt;select()&lt;/code&gt;로 수행된다. 조건에 맞는 컬럼을 추출한다.&lt;/p&gt;
&lt;p&gt;filter는 &lt;code&gt;filter(), where()&lt;/code&gt;로 표현된다. 조건에 맞는 행을 추출한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;few_fire_df = (
       fire_df
       .select(&amp;quot;IncidentNumber&amp;quot;, &amp;quot;AvailableDtTm&amp;quot;, &amp;quot;CallType&amp;quot;)
       .where(col(&amp;quot;CallType&amp;quot;) != &amp;quot;Medical Incident&amp;quot;)
)
few_fire_df.show(5, truncate=False)

(
 fire_df
 .select(&amp;quot;CallType&amp;quot;)
 .where(col(&amp;quot;CallType&amp;quot;).isNotNull())
 .agg(countDistinct(&amp;quot;CallType&amp;quot;).alias(&amp;quot;DistinctCallTypes&amp;quot;))
 .show()
)

(
 fire_df
 .select(&amp;quot;CallType&amp;quot;)
 .where(col(&amp;quot;CallType&amp;quot;).isNotNull())
 .distinct()
 .show(10, False)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h6 id="e-칼럼의-이름-변경-및-추가-삭제"&gt;e. 칼럼의 이름 변경 및 추가 삭제&lt;/h6&gt;&lt;p&gt;parquet 파일 포맷은 칼럼 이름에 공백이 포함된 것을 금지한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;StructField를 써서 스키마 내에서 원하는 칼럼 이름들을 지정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;데이터 소스의 칼럼 이름을 무시하고 원하는 이름으로 읽어오는 경우.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;fire_schema = StructType([StructField('CallNumber', IntegerType(), True),  
                     StructField('UnitID', StringType(), True),  
             ])
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start="2"&gt;
&lt;li&gt;withColumnRenamed() 함수를 써서 원하는 이름으로 변경한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;데이터 프레임은 변경 불가 방식으로 동작한다. 따라서 withColumnRenamed()는 원본을 유지한 채로 칼럼 이름이 변경된 새로운 데이터 프레임을 받아 오게 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;new_fire_df = fire_df.withColumnRenamed(&amp;quot;Delay&amp;quot;, &amp;quot;ResponseDelayedinMins&amp;quot;)
(
 new_fire_df
 .select(&amp;quot;ResponseDelayedinMins&amp;quot;)
 .where(col(&amp;quot;ResponseDelayedinMins&amp;quot;) &amp;gt; 5)
 .show(5, False)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h6 id="f-집계연산"&gt;f. 집계연산&lt;/h6&gt;&lt;p&gt;자주 혹은 반복적으로 질의할 필요가 있는 규모가 큰 데이터 프레임에서는 캐싱을 해서 이득을 얻을 수도 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;(
 fire_ts_df
 .select(&amp;quot;CallType&amp;quot;)
 .where(col(&amp;quot;CallType&amp;quot;).isNotNull())
 .groupBy(&amp;quot;CallType&amp;quot;)
 .count()
 .orderBy(&amp;quot;count&amp;quot;, ascending=False)
 .show(n=10, truncate=False)
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h6 id="g-그-외-일반적인-데이터-프레임-연산들"&gt;g. 그 외 일반적인 데이터 프레임 연산들&lt;/h6&gt;&lt;p&gt;데이터 프레임 API는 &lt;code&gt;min(), max(), sum(), avg()&lt;/code&gt; 등 통계 함수들을 지원한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;import pyspark.sql.functions as F

(
 fire_ts_df
 .select(
     F.sum(&amp;quot;NumAlarms&amp;quot;),
     F.avg(&amp;quot;ResponseDelayedinMins&amp;quot;),
     F.min(&amp;quot;ResponseDelayedinMins&amp;quot;),
     F.max(&amp;quot;ResponseDelayedinMins&amp;quot;)
 ).show()
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;좀 더 고수준의 요구사항을 만족하려면 &lt;code&gt;stat(), describe(), correlation(), covariance(), sampleBy(), approxQuantile(), frequentItems()&lt;/code&gt;이 있다.&lt;/p&gt;
&lt;p&gt;데이터 프레임의 고차원 API와 DSL 연산자들을 쓰면 쉽게 표현력이 우수한 질의를 구성하고 연결할 수 있다.&lt;/p&gt;
&lt;h2 id="4-dataset-api"&gt;4. DataSet API&lt;/h2&gt;&lt;p&gt;데이터 프레임과 데이터세트 API를 유사한 인터페이스를 갖도록 정형화 API로 일원화했다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/11/12/2023111210_0btTcBlRcdobkKeFtW44.png" src="/resources/media/images/content/2023/11/12/2023111210_0btTcBlRcdobkKeFtW44.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;DataSet는 정적 타입(typed) API와 동적 타입(untyped) API의 특성을 모두 가진다.&lt;/p&gt;
&lt;p&gt;스칼라의 DataFrame은 공용 객체의 모음인 Dataset[Row]의 다른 이름이라고 생각할 수 있으며, Row는 서로 다른 타입의 값을 저장할 수 있는 포괄적 JVM 객체라고 보면 된다.&lt;/p&gt;
&lt;p&gt;DataSet은 스칼라에서 엄격하게 타입이 정해진 JVM 객체의 집합이며, 이 객체는 자바에서는 클래스라고 볼 수 있다.&lt;/p&gt;
&lt;h4 id="a-정적-타입-객체-동적-타입-객체-포괄적인-"&gt;A. 정적 타입 객체, 동적 타입 객체, 포괄적인 Row&lt;/h4&gt;&lt;p&gt;DataSet는 자바와 스칼라에서 통용되고 파이썬과 R에서는 DataFrame만 사용 가능하다. 이는 파이썬과 R이 컴파일 시 타입의 safe를 보장하는 언어가 아니기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/11/12/2023111210_l8Q1Ls4aQ1BPZ3olJ832.png" src="/resources/media/images/content/2023/11/12/2023111210_l8Q1Ls4aQ1BPZ3olJ832.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Row는 스파크의 포괄적 객체 타입이며 인덱스를 사용하여 접근할 수 있으며 다양한 타입의 값들을 담을 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;from pyspark.sql import Row
row = Row(350, True, &amp;quot;Learning Spark 2E&amp;quot;, None)

&amp;gt;&amp;gt;&amp;gt; row
&amp;lt;Row(350, True, 'Learning Spark 2E', None)&amp;gt;
&amp;gt;&amp;gt;&amp;gt; row[0]
350
&amp;gt;&amp;gt;&amp;gt; row[1]
True
&amp;gt;&amp;gt;&amp;gt; row[2]
'Learning Spark 2E'
&amp;gt;&amp;gt;&amp;gt; row[3]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정적 객체들은 JVM에서 실제 자바 클래스나 스칼라 클래스가 된다. 그러므로 데이터세트의 각 아이템들은 곧바로 하나의 JVM 객체가 되어 쓸 수 있다.&lt;/p&gt;
&lt;h4 id="b-데이터세트-생성"&gt;B. 데이터세트 생성&lt;/h4&gt;&lt;p&gt;스칼라에서 DataSet를 만들 때 결과 DataSet가 쓸 스키마를 지정하는 가장 쉬운 방법은 스칼라의 case class를 사용하는 것이다. 자바는 JavaBean 클래스를 쓸 수 있다.&lt;/p&gt;
&lt;h6 id="a-스칼라-케이스-클래스"&gt;a. 스칼라: 케이스 클래스&lt;/h6&gt;&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/11/12/2023111210_l5zvY48VulVSm8SvPSn7.png" src="/resources/media/images/content/2023/11/12/2023111210_l5zvY48VulVSm8SvPSn7.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-scala"&gt;case class DeviceIoTData (battery_level: Long, c02_level: Long,   
    cca2: String, cca3: String, cn: String, device_id: Long,   
    device_name: String, humidity: Long, ip: String, latitude: Double,  
    lcd: String, longitude: Double, scale:String, temp: Long, timestamp: Long)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;케이스 클래스를 정의한 이후에 Dataset[Row]를 Dataset[DeviceIoTData]로 바꾸는 데 사용 가능하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-scala"&gt;val ds = spark.read
    .json(&amp;quot;/databricks-datasets/learning-spark-v2/iot-devices/iot_devices.json&amp;quot;)
    .as[DeviceIoTData]
ds: org.apache.spark.sql.Dataset[DeviceIoTData] = [battery_level...]
ds.show(5, false)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id="c-데이터세트에서-가능한-작업들"&gt;C. 데이터세트에서 가능한 작업들&lt;/h4&gt;&lt;p&gt;트랜스포메이션이나 액션들을 수행할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-scala"&gt;val filterTempDS = ds.filter(d =&amp;gt; d.temp &amp;gt; 30 &amp;amp;&amp;amp; d.humidity &amp;gt; 70)
filterTempDS: org.apache.spark.sql.Dataset[DeviceIoTData] = [battery_level...]
filterTempDS.show(5, false)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DataSet의 함수인 filter()에 인자로 함수를 사용하였다. 시그니처는 filter(func: (T) &amp;gt; Boolean): Dataset[T]이고, 인자로 람다 함수 func: (T) &amp;gt; Boolean을 받는다.&lt;/p&gt;
&lt;p&gt;람다 함수의 인자는 DeviceIoTData의 JVM 객체다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-scala"&gt;case class DeviceTempByCountry(temp: Long, device_name: String, device_id: Long, cca3: String)

val dsTemp = ds  
  .filter(d =&amp;gt; {d.temp &amp;gt; 25}).map(d =&amp;gt; (d.temp, d.device_name, d.device_id, d.cca3))  
  .withColumnRenamed(&amp;quot;_1&amp;quot;, &amp;quot;temp&amp;quot;)  
  .withColumnRenamed(&amp;quot;_2&amp;quot;, &amp;quot;device_name&amp;quot;)  
  .withColumnRenamed(&amp;quot;_3&amp;quot;, &amp;quot;device_id&amp;quot;)  
  .withColumnRenamed(&amp;quot;_4&amp;quot;, &amp;quot;cca3&amp;quot;).as[DeviceTempByCountry]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-scala"&gt;val dsTemp2 = ds
    .select($&amp;quot;temp&amp;quot;, $&amp;quot;device_name&amp;quot;, $&amp;quot;device_id&amp;quot;, $&amp;quot;device_id&amp;quot;, $&amp;quot;cca3&amp;quot;)
    .where(&amp;quot;temp &amp;gt; 25&amp;quot;)
    .as[DeviceTempByCountry]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;데이터세트에서 &lt;code&gt;filter(), map(), groupBy(), select(), take()&lt;/code&gt;등을 사용할 수 있다. DataSet는 함수들의 형태다 컴파일 타임 안전성을 보장한다는 점에서 RDD와 유사하지만 훨씬 읽기 쉬우며 객체지향 프로그래밍 인터페이스를 갖고 있다.&lt;/p&gt;
&lt;p&gt;DataSet가 사용되는 동안은 하부의 스파크 SQL 엔진이 JVM 객체의 생성, 변환, 직렬화, 역직렬화를 담당한다. DataSet 인코더의 도움을 받아 자바의 오프힙 메모리 관리 또한 하게 된다.&lt;/p&gt;
&lt;h2 id="5-데이터-프레임-vs-데이터세트"&gt;5. 데이터 프레임 vs 데이터세트&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;RDD
&lt;ul&gt;
&lt;li&gt;어떻게 하는지&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;DataFrame, DataSet
&lt;ul&gt;
&lt;li&gt;무엇을 해야 하는지&lt;/li&gt;
&lt;li&gt;풍부한 표현과 높은 수준의 추상화 및 DSL 연산&lt;/li&gt;
&lt;li&gt;높은 수준의 표현력, 필터, 맵, 집계, 평균, SQL 질의, 칼럼 지향 접근, 반정형화된 데이터에 대한 관계형 연산 등이 필요&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;DataSet
&lt;ul&gt;
&lt;li&gt;컴파일 타임에 엄격한 타입 체크를 원하면&lt;/li&gt;
&lt;li&gt;Encoder를 써서 프로젝트 텅스텐의 직렬화 능력을 통한 이득을 보고 싶은 경우[^9]&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;DataFrame
&lt;ul&gt;
&lt;li&gt;SQL과 유사한 질의를 쓰는 관계형 연산&lt;/li&gt;
&lt;li&gt;일원화, 코드 최적화, 스파크 컴포넌트들 사이에서의 API 단순화를 원하면&lt;/li&gt;
&lt;li&gt;R 사용자&lt;/li&gt;
&lt;li&gt;파이썬 사용자 (제어권을 좀 더 갖고 싶다면 RDD를 사용)&lt;/li&gt;
&lt;li&gt;공간/속도 효율성을 원하면&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/11/12/2023111210_DIdo6oEDb3itGKsk9lFd.png" src="/resources/media/images/content/2023/11/12/2023111210_DIdo6oEDb3itGKsk9lFd.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;h2 id="a-언제-rdd를-사용하는가"&gt;A. 언제 RDD를 사용하는가&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;RDD를 사용하도록 작성된 서드파티 패키지를 사용&lt;/li&gt;
&lt;li&gt;코드 최적화, 효과적인 공간 사용, 퍼포먼스를 포기할 수 있다&lt;/li&gt;
&lt;li&gt;어떻게 질의를 수행할지 정확하게 지정해 주고 싶다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;데이터세트나 데이터 프레임에서 RDD로 가기 위해서는 API 함수인 df.rdd만 호출하면 된다. DataFrame과 DataSet은 RDD에 기반해서 만들어졌고 전체 단계 코드 생성 중에 최소화된 RDD 코드로 분해된다.&lt;/p&gt;
&lt;h2 id="99-참고-자료"&gt;99. 참고 자료&lt;/h2&gt;&lt;p&gt;[1] &lt;a href="https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.Column.html?highlight=column#pyspark.sql.Column"&gt;https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.Column.html?highlight=column#pyspark.sql.Column&lt;/a&gt;
[2] &lt;a href="https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrameWriter.parquet.html?highlight=dataframewriter"&gt;https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrameWriter.parquet.html?highlight=dataframewriter&lt;/a&gt;&lt;/p&gt;
</description><pubDate>Sun, 12 Nov 2023 10:41:40 +0900</pubDate><guid>http://blex.me/@mildsalmon/spark-%EB%A7%9B%EB%B3%B4%EA%B8%B0-4-rdd-dataframe-dataset-api</guid></item><item><title>Spark 맛보기 - 3. transformation, action, lazy evaluation</title><link>http://blex.me/@mildsalmon/spark-%EB%A7%9B%EB%B3%B4%EA%B8%B0-3-transformation-action-lazy-evalu</link><description>&lt;h2 id="1-transformation"&gt;1. transformation&lt;/h2&gt;&lt;p&gt;Spark의 RDD(Resilient Distributed Dataset)는 불변성(immutability)을 가지고 있다. 불변성은 한 번 생성되면 변경할 수 없는 것을 의미한다. 이로 인해, 데이터를 변경하고 싶을 때 기존의 데이터를 수정하는 것이 아니라 변경된 내용을 가진 새로운 RDD를 생성하게 된다. 이 과정을 transformation이라고 한다.&lt;/p&gt;
&lt;p&gt;모든 transformation은 뒤늦게 평가되는데 이를 lazy evaluation이라고 한다. 즉, transformation은 즉시 계산되지 않고 lineage라 불리는 형태로 기록된다. 기록된 lineage는 실행 계획에서 후반쯤에spark가 확실한 transformation들끼리 재배열하거나 합치거나 해서 더 효율적으로 실행할 수 있도록 최적화한다.&lt;/p&gt;
&lt;h4 id="a-narrow-transformation"&gt;A. narrow transformation&lt;/h4&gt;&lt;blockquote&gt;
&lt;p&gt;하나의 입력 파티션을 연산하여 하나의 결과 파티션을 내놓는 transformation이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;모든 필요한 데이터가 하나의 파티션에 이미 있을 때 수행할 수 있는 transformation이다. 이 연산은 다른 파티션의 데이터를 필요로 하지 않기 때문에 각 파티션은 독립적으로 연산을 수행할 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어 &lt;code&gt;map()&lt;/code&gt;, &lt;code&gt;filter()&lt;/code&gt; 같은 연산이 여기에 해당한다. 이 연산들은 각각의 입력 요소가 독립적으로 처리되며, 결과적으로 네트워크를 통한 데이터의 shuffle이 필요하지 않다.&lt;/p&gt;
&lt;h4 id="b-wide-transformation"&gt;B. wide transformation&lt;/h4&gt;&lt;blockquote&gt;
&lt;p&gt;다른 파티션으로부터 데이터를 읽어 들여서 합치고 디스크에 쓰는 등의 일을 하는 transformation이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;여러 파티션에 걸쳐 있는 데이터가 필요할 때 수행하는 transformation이다. 이 transformation은 종종 shuffle을 발생시키는데, 이는 다른 파티션에 있는 데이터를 재분배하는 과정을 말한다. 데이터를 재분배하는 wide transformation은 narrow transformation에 비해 더 많은 자원을 사용하고 시간이 오래 걸린다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;groupBy()&lt;/code&gt;, &lt;code&gt;reduceByKey()&lt;/code&gt;같은 연산이 wide transformation에 해당한다.&lt;/p&gt;
&lt;h2 id="2-action"&gt;2. action&lt;/h2&gt;&lt;p&gt;하나의 action은 모든 기록된 transformation의 lazy evaluation을 발동시킨다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/11/5/202311519_fjrfUOlqQeUe65DdP3N9.png" src="/resources/media/images/content/2023/11/5/202311519_fjrfUOlqQeUe65DdP3N9.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Spark 연산 중 transformation 연산은 orderBy(), groupBy(), filter(), select(), join()이 있다. action 연산은 show(), take(), count(), collect(), save()가 있다.&lt;/p&gt;
&lt;p&gt;각 연산들이 직관적이라 어떤 동작을 할 것인지 명확해보인다. 그래도 설명을 추가해보면..&lt;/p&gt;
&lt;h4 id="a-transformation-연산"&gt;A. transformation 연산&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;orderBy()&lt;/code&gt;: 데이터를 특정 컬럼의 값에 따라 오름차순 또는 내림차순으로 정렬한다. 이 연산은 wide transformation에 속하며, 데이터의 shuffle을 일으킬 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;groupBy()&lt;/code&gt;: 지정된 컬럼의 값에 기반하여 데이터를 그룹화한다. 그룹화된 결과에 집계 함수를 적용할 수 있다. wide transformation이며 shuffle을 발생시킬 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filter()&lt;/code&gt;: 데이터셋에서 특정 조건을 만족하는 요소만을 선택하여 새로운 데이터셋을 생성한다. narrow transformation이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;select()&lt;/code&gt;: 특정 컬럼만을 선택하여 새로운 데이터프레임을 생성한다. 컬럼을 변형하거나 새로운 칼럼을 만들 때도 사용된다. narrow transformation이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;join()&lt;/code&gt;: 두 데이터셋을 특정 키를 기준으로 결합한다. &lt;code&gt;inner&lt;/code&gt;, &lt;code&gt;outer&lt;/code&gt;, &lt;code&gt;left&lt;/code&gt;, &lt;code&gt;right&lt;/code&gt; 등의 다양한 타입의 조인을 지원한다. wide transformation이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="b-action-연산"&gt;B. action 연산&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;show()&lt;/code&gt;: 데이터프레임의 상위 몇 줄을 출력하여 콘솔에서 확인할 수 있게 한다. 주로 디버깅이나 데이터의 미리보기에 사용된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;take()&lt;/code&gt;: 데이터셋에서 지정된 수의 로우를 반환한다. 결과는 드라이버 프로그램으로 가져오기 때문에 대용량 데이터셋에 이 연산을 사용할 때는 주의가 필요하다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;count()&lt;/code&gt;: 데이터셋에 있는 요소의 수를 반환한다. 전체 데이터셋에 대한 간단한 집계 연산을 수행한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;collect()&lt;/code&gt;: 전체 데이터셋을 드라이버 프로그램으로 가져와서 배열 등의 자료구조에 저장한다. 이 역시 매우 큰 데이터셋에 대해서는 메모리 문제를 일으킬 수 있으므로 주의해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;save()&lt;/code&gt;: 데이터셋을 파일 시스템, 데이터베이스 등에 저장한다. 텍스트, CSV, JSON, Parquet 등 다양한 포맷으로 저장할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="3-lazy-evaluation"&gt;3. Lazy Evaluation&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;Lazy Evaluation은 쿼리 최적화를 가능하게 하고 lineage와 immutability은 장애에 대한 데이터 내구성을 제공한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Spark의 Lazy Evaluation은 Spark가 Transformation 연산을 즉시 수행하지 않고, 실제로 필요한 Action 연산이 호출될 때까지 연산을 지연시키는 처리 방식이다. 이 방식은 전체 데이터 처리 파이프라인을 통째로 최적화할 수 있는 여지를 제공한다.&lt;/p&gt;
&lt;p&gt;Lazy Evaluation의 주요 장점은 효율성, 최적화, 고비용 연산의 최소화가 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;모든 Transformation을 즉시 수행하지 않고 필요한 최종 결과를 얻기 위해 실제로 필요한 연산만 수행하여 불필요한 데이터 처리를 줄여주고 자원 사용을 최적화한다.&lt;/li&gt;
&lt;li&gt;전체 작업을 논리적 실행 계획으로 구성하고, 실행 전에 그 계획을 분석하여 더 효율적인 물리적 실행 계획으로 변환한다.&lt;/li&gt;
&lt;li&gt;Shuffle과 같이 네트워크를 통한 데이터 이동이 필요한 비싼 연산의 수를 줄이거나 피하여 실행 계획을 조정한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="999-참고자료"&gt;999. 참고자료&lt;/h2&gt;&lt;p&gt;줄스 담지, 브룩 웨닉, 타타가타 다스, 데니 리 저/박종영, &amp;quot;러닝 스파크&amp;quot;, 제이펍(2022)&lt;/p&gt;
</description><pubDate>Sun, 05 Nov 2023 19:28:02 +0900</pubDate><guid>http://blex.me/@mildsalmon/spark-%EB%A7%9B%EB%B3%B4%EA%B8%B0-3-transformation-action-lazy-evalu</guid></item><item><title>[requests 라이브러리] 내가 아는 timeout은 너무 추상적이였어..</title><link>http://blex.me/@mildsalmon/requests-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%82%B4%EA%B0%80-%EC%95%84%EB%8A%94-timeout%EC%9D%80-%EB%84%88%EB%AC%B4-%EC%B6%94%EC%83%81%EC%A0%81%EC%9D%B4%EC%98%80%EC%96%B4</link><description>&lt;h2 id="1-python에서의-http-통신-그리고-t"&gt;1. python에서의 HTTP 통신 그리고 timeout&lt;/h2&gt;&lt;p&gt;python에서 HTTP 통신을 하기 위해서 보통 requests 라이브러리를 사용한다.&lt;/p&gt;
&lt;p&gt;Get 요청이든 Post 요청이든 무한정으로 응답을 기다리는 비극을 막기 위해 timeout 파라미터에 값을 넣어주곤 한다.&lt;/p&gt;
&lt;p&gt;이 timeout이 정확히 어떤 순간의 timeout을 의미할까?&lt;/p&gt;
&lt;p&gt;나는 막연하게 client에서 server의 응답을 받기까지의 시간이라고 생각했다.&lt;br&gt;
그러면 아래 코드는 timeout=0.12초를 초과한 0.32초가 걸렸으니 에러가 발생해야 하지 않을까?&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;res = Session().get(&amp;quot;https://google.co.kr&amp;quot;, timeout=0.12)

print(res.elapsed)

# 0:00:00.323224
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기에서 뭔가 이상하다는 것이 느껴졌다.&lt;/p&gt;
&lt;p&gt;그래서 코드를 보려고 한다.&lt;/p&gt;
&lt;p&gt;코드를 봄으로써 파악하고 싶은 것은 2가지다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ConnectionTimeoutError, ReadTimeoutError가 어디서 발생하는가?&lt;/li&gt;
&lt;li&gt;timeout을 초과하는 위 경우(res.elapsed=0.32)에 왜 Error가 발생하지 않았는가?&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2-connectiontimeouterror-"&gt;2. ConnectionTimeoutError, ReadTimeoutError가 어디서 발생하는가?&lt;/h2&gt;&lt;h4 id="a-sessionget"&gt;A. Session().get()&lt;/h4&gt;&lt;p&gt;Session 클래스의 get 메소드를 사용했으므로 그 코드를 따라가봤다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# Session().get()

    def get(self, url, **kwargs):
        r&amp;quot;&amp;quot;&amp;quot;Sends a GET request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
        :rtype: requests.Response
        &amp;quot;&amp;quot;&amp;quot;

        kwargs.setdefault(&amp;quot;allow_redirects&amp;quot;, True)
        return self.request(&amp;quot;GET&amp;quot;, url, **kwargs)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;별거 없다.&lt;/p&gt;
&lt;p&gt;timeout은 kwargs에 패킹되어서 &lt;code&gt;get 메소드&lt;/code&gt;로 넘어오고 &lt;code&gt;self.request()&lt;/code&gt;로 넘어갈 때 언패킹될 것이다.&lt;/p&gt;
&lt;h4 id="b-sessionrequest"&gt;B. Session().request()&lt;/h4&gt;&lt;pre&gt;&lt;code class="language-python"&gt;# Session().request()

    def request(
        self,
        method,
        url,
                ...
        timeout=None,
                ...
    ):
        # Create the Request.
                ...

        # Send the request.
        send_kwargs = {
            &amp;quot;timeout&amp;quot;: timeout,
            &amp;quot;allow_redirects&amp;quot;: allow_redirects,
        }
        send_kwargs.update(settings)
        resp = self.send(prep, **send_kwargs)

        return resp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;쓸데 없는 것들은 다 지우고 timeout만 추적해보려고 위 코드만 남겼다.&lt;/p&gt;
&lt;p&gt;timeout은 send_kwargs 딕셔너리에 value로 들어가고 &lt;code&gt;self.send()&lt;/code&gt; 메소드에 언패킹되어서 전달된다.&lt;/p&gt;
&lt;h4 id="c-sessionsend"&gt;C. Session().send()&lt;/h4&gt;&lt;pre&gt;&lt;code class="language-python"&gt;# Session().send()

    def send(self, request, **kwargs):
        &amp;quot;&amp;quot;&amp;quot;Send a given PreparedRequest.

        :rtype: requests.Response
        &amp;quot;&amp;quot;&amp;quot;
            # Get the appropriate adapter to use
        adapter = self.get_adapter(url=request.url)
                
        # Start time (approximately) of the request
        start = preferred_clock()

        # Send the request
        r = adapter.send(request, **kwargs)

        # Total elapsed time of the request (approximately)
        elapsed = preferred_clock() - start
        r.elapsed = timedelta(seconds=elapsed)

        # Response manipulation hooks
        r = dispatch_hook(&amp;quot;response&amp;quot;, hooks, r, **kwargs)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;또 코드를 필요한 것만 남기고 지우면,&lt;/p&gt;
&lt;p&gt;kwargs를 다시 패킹하고 &lt;code&gt;adapter.send(request, **kwargs)&lt;/code&gt;에서 언패킹하여 전달한다.&lt;br&gt;
그런데 &lt;code&gt;adapter&lt;/code&gt;가 뭘까?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;adapter&lt;/code&gt;는 &lt;code&gt;self.get_adapter(url=request.url)&lt;/code&gt;을 통해서 얻었다.&lt;/p&gt;
&lt;h4 id="d-sessiongetadapter"&gt;D. Session().get_adapter()&lt;/h4&gt;&lt;pre&gt;&lt;code class="language-python"&gt;# Session().get_adapter()

    def get_adapter(self, url):
        &amp;quot;&amp;quot;&amp;quot;
        Returns the appropriate connection adapter for the given URL.

        :rtype: requests.adapters.BaseAdapter
        &amp;quot;&amp;quot;&amp;quot;
        for (prefix, adapter) in self.adapters.items():

            if url.lower().startswith(prefix.lower()):
                return adapter
    ```
    
Session 클래스의 필드로 self.adapters가 존재하고 url prefix에 따라 알맞는 adapter를 반환해주는 로직이다.

### E. Session().__init__()

```python
# Session().__init__()

...
        # Default connection adapters.
        self.adapters = OrderedDict()
        self.mount(&amp;quot;https://&amp;quot;, HTTPAdapter())
        self.mount(&amp;quot;http://&amp;quot;, HTTPAdapter())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;self.adapters&lt;/code&gt;는 위 처럼 prefix, HTTPAdapter가 mount된 Dict이다.&lt;/p&gt;
&lt;p&gt;adpater가 뭔지 알았으니 2-C로 돌아가서 생각해보자.&lt;/p&gt;
&lt;p&gt;사실 adapter를 알고 싶었던 이유는 &lt;code&gt;adpater.send()&lt;/code&gt;를 찾기 위해서였다. (adapter가 BaseAdapter로 추상화되어 있어서 send 메소드의 구현을 한눈에 확인하기 어려웠다.)&lt;/p&gt;
&lt;h4 id="f-httpadaptersend"&gt;F. HTTPAdapter().send()&lt;/h4&gt;&lt;pre&gt;&lt;code class="language-python"&gt;    def send(
        self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
    ):
            ...
        ... conn...
        
        if isinstance(timeout, tuple):
            try:
                connect, read = timeout
                timeout = TimeoutSauce(connect=connect, read=read)
            except ValueError:
                raise ValueError(
                    f&amp;quot;Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, &amp;quot;
                    f&amp;quot;or a single float to set both timeouts to the same value.&amp;quot;
                )
        elif isinstance(timeout, TimeoutSauce):
            pass
        else:
            timeout = TimeoutSauce(connect=timeout, read=timeout)

        try:
            resp = conn.urlopen(
                method=request.method,
                url=url,
                body=request.body,
                headers=request.headers,
                redirect=False,
                assert_same_host=False,
                preload_content=False,
                decode_content=False,
                retries=self.max_retries,
                timeout=timeout,
                chunked=chunked,
            )

        ...

        except MaxRetryError as e:
            if isinstance(e.reason, ConnectTimeoutError):
                # TODO: Remove this in 3.0.0: see #2811
                if not isinstance(e.reason, NewConnectionError):
                    raise ConnectTimeout(e, request=request)

            ...
        
        ...

        ...

        except (_SSLError, _HTTPError) as e:
            ...
    
            elif isinstance(e, ReadTimeoutError):
                raise ReadTimeout(e, request=request)
            
            ...

        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기에 timeout(connection timeout, read timeout)같은 내가 궁금한 것들이 전부 다 있다.&lt;/p&gt;
&lt;p&gt;크게 나누면 아래 두개로 나눌 수 있겠다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;내부적으로 timeout을 connect, read로 쪼개는 부분&lt;/li&gt;
&lt;li&gt;ConnectionTimeoutError, ReadTimeoutError가 발생하는 부분&lt;/li&gt;
&lt;/ol&gt;
&lt;h6 id="a-내부적으로-timeout을-connect-"&gt;a. 내부적으로 timeout을 connect, read로 쪼개는 부분&lt;/h6&gt;&lt;p&gt;1번은 최초에 get 요청을 할때 보내는 파라미터에 timeout을 tuple로 주면 connect, read로 알아서 분리해서 생각하겠다는 의미이다. 만약 tuple이 아닌 값만 입력했다면 connect, read의 값을 동일하게 생각하겠다는 말이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# tuple로 주는 경우
res = Session().get(&amp;quot;https://google.co.kr&amp;quot;, timeout=(0.1, 2))

# 값(int, float ...)으로 주는 경우 (TimeoutSauce 내부 로직을 보니 bool을 제외한 나머지 값들은 캐스팅을 해준다.)
res = Session().get(&amp;quot;https://google.co.kr&amp;quot;, timeout=0.12)
&lt;/code&gt;&lt;/pre&gt;
&lt;h6 id="b-connectiontimeouterror-"&gt;b. ConnectionTimeoutError, ReadTimeoutError가 발생하는 부분&lt;/h6&gt;&lt;p&gt;사실 궁금했던 것은 이 부분이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;conn.urlopen()&lt;/code&gt;로 url을 열고 except나오는 것에 따라 timeout을 걸러주는 방식으로 보인다.&lt;/p&gt;
&lt;p&gt;이제부터 conn.urlopen()으로 가보려고 한다.&lt;/p&gt;
&lt;p&gt;conn은 HTTPConnectionPool 클래스다. (HTTPConnectionPool을 파악하는 것을 이 포스트에서 다루면 한도 끝도 없어질 것 같아서 생략한다.. ㅎ)&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# HTTPConnectionPool().urlopen()

    def urlopen(  # type: ignore[override]
        self,
        method: str,
        url: str,
        ...
        timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
        ...
    ) -&amp;gt; BaseHTTPResponse:
        ...

        conn = None

        ...
        try:
            # Request a connection from the queue.
            timeout_obj = self._get_timeout(timeout)
            conn = self._get_conn(timeout=pool_timeout)

            conn.timeout = timeout_obj.connect_timeout  # type: ignore[assignment]

            # Is this a closed/new connection that requires CONNECT tunnelling?
            if self.proxy is not None and http_tunnel_required and conn.is_closed:
                try:
                    self._prepare_proxy(conn)
                except (BaseSSLError, OSError, SocketTimeout) as e:
                    self._raise_timeout(
                        err=e, url=self.proxy.url, timeout_value=conn.timeout
                    )
                    raise

            # If we're going to release the connection in ``finally:``, then
            # the response doesn't need to know about the connection. Otherwise
            # it will also try to release it and we'll have a double-release
            # mess.
            response_conn = conn if not release_conn else None

            # Make the request on the HTTPConnection object
            response = self._make_request(
                conn,
                method,
                url,
                timeout=timeout_obj,
                body=body,
                headers=headers,
                chunked=chunked,
                retries=retries,
                response_conn=response_conn,
                preload_content=preload_content,
                decode_content=decode_content,
                **response_kw,
            )
            ...

        ...

        except (
            TimeoutError,
            HTTPException,
            OSError,
            ProtocolError,
            BaseSSLError,
            SSLError,
            CertificateError,
            ProxyError,
        ) as e:
            # Discard the connection for these exceptions. It will be
            # replaced during the next _get_conn() call.
            clean_exit = False
            new_e: Exception = e
            ...
        
            if isinstance(
                new_e,
                (
                    OSError,
                    NewConnectionError,
                    TimeoutError,
                    SSLError,
                    HTTPException,
                ),
            ) and (conn and conn.proxy and not conn.has_connected_to_proxy):
                new_e = _wrap_proxy_error(new_e, conn.proxy.scheme)
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;timeout과 관련된 부분만 가져왔는데도, 코드가 길다.&lt;/p&gt;
&lt;p&gt;내 궁금증을 해소할만한 부분만 집어서 정리해보자.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;conn = self._get_conn(timeout=pool_timeout)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;response = self._make_request()&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;핵심만 보자면 2번만 봐도 되는데, 중간에 conn 객체가 어떤 클래스의 객체인지 알기 위해서 1번도 집어봤다.&lt;/p&gt;
&lt;h4 id="g-httpconnectionpoolgetco"&gt;G. HTTPConnectionPool()._get_conn()&lt;/h4&gt;&lt;p&gt;HTTPConnectionPool() 클래스의 _get_conn() 메소드를 따라가면, 얻을 수 있는 것이 HTTPConnection() 클래스의 객체이다.&lt;/p&gt;
&lt;p&gt;(코드를 몇번 타고 넘어가서 객체를 생성하기 때문에, 별도로 코드를 남기지는 않았다.)&lt;/p&gt;
&lt;h4 id="h-httpconnectionpoolmaker"&gt;H. HTTPConnectionPool()._make_request()&lt;/h4&gt;&lt;p&gt;2번을 따라서 메소드를 타고 넘어왔다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;    def _make_request(
        self,
        conn: BaseHTTPConnection,
        method: str,
        url: str,
                ...
        timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,
                ...
    ) -&amp;gt; BaseHTTPResponse:
                ...
        timeout_obj = self._get_timeout(timeout)
        timeout_obj.start_connect()
        conn.timeout = Timeout.resolve_default_timeout(timeout_obj.connect_timeout)

        try:
            # Trigger any extra validation we need to do.
            try:
                self._validate_conn(conn)
            except (SocketTimeout, BaseSSLError) as e:
                self._raise_timeout(err=e, url=url, timeout_value=conn.timeout)
                raise

        # _validate_conn() starts the connection to an HTTPS proxy
        # so we need to wrap errors with 'ProxyError' here too.
        except (
            OSError,
            NewConnectionError,
            TimeoutError,
            BaseSSLError,
            CertificateError,
            SSLError,
        ) as e:
            new_e: Exception = e
            if isinstance(e, (BaseSSLError, CertificateError)):
                new_e = SSLError(e)
            # If the connection didn't successfully connect to it's proxy
            # then there
            if isinstance(
                new_e, (OSError, NewConnectionError, TimeoutError, SSLError)
            ) and (conn and conn.proxy and not conn.has_connected_to_proxy):
                new_e = _wrap_proxy_error(new_e, conn.proxy.scheme)
            raise new_e

        # conn.request() calls http.client.*.request, not the method in
        # urllib3.request. It also calls makefile (recv) on the socket.
        try:
            conn.request(
                method,
                url,
                body=body,
                headers=headers,
                chunked=chunked,
                preload_content=preload_content,
                decode_content=decode_content,
                enforce_content_length=enforce_content_length,
            )

        # We are swallowing BrokenPipeError (errno.EPIPE) since the server is
        # legitimately able to close the connection after sending a valid response.
        # With this behaviour, the received response is still readable.
        except BrokenPipeError:
            pass
        except OSError as e:
            # MacOS/Linux
            # EPROTOTYPE is needed on macOS
            # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/
            if e.errno != errno.EPROTOTYPE:
                raise

        # Reset the timeout for the recv() on the socket
        read_timeout = timeout_obj.read_timeout

        if not conn.is_closed:
            # In Python 3 socket.py will catch EAGAIN and return None when you
            # try and read into the file pointer created by http.client, which
            # instead raises a BadStatusLine exception. Instead of catching
            # the exception and assuming all BadStatusLine exceptions are read
            # timeouts, check for a zero timeout before making the request.
            if read_timeout == 0:
                raise ReadTimeoutError(
                    self, url, f&amp;quot;Read timed out. (read timeout={read_timeout})&amp;quot;
                )
            conn.timeout = read_timeout

        # Receive the response from the server
        try:
            response = conn.getresponse()
        except (BaseSSLError, OSError) as e:
            self._raise_timeout(err=e, url=url, timeout_value=read_timeout)
            raise
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 코드의 대부분은 except 구분으로 에러를 핸들링하는 부분이기 때문에 봐야할 코드가 딱 2줄 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;self._validate_conn(conn)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;conn.request(...)&lt;/code&gt;부터 &lt;code&gt;response = conn.getresponse()&lt;/code&gt;까지&lt;/li&gt;
&lt;/ol&gt;
&lt;h6 id="a-selfvalidateconnconn"&gt;a. self._validate_conn(conn)&lt;/h6&gt;&lt;pre&gt;&lt;code class="language-python"&gt;        conn.timeout = Timeout.resolve_default_timeout(timeout_obj.connect_timeout)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우선 &lt;code&gt;self._validate_conn()&lt;/code&gt;를 하기 전에 &lt;code&gt;conn.timeout&lt;/code&gt;에 connect_timeout을 할당한다.&lt;br&gt;
즉, 위 코드 이후에 호출하는 &lt;code&gt;self._validate_conn(conn)&lt;/code&gt;에서 connect와 관련된 일이 진행될 것이라 생각해볼 수 있다.&lt;/p&gt;
&lt;p&gt;2-G 에서 파악한 HTTPConnection 객체는 is_closed 프로퍼티가 False를 반환한다. 정확히는 HTTPConnection().sock 필드가 None이다.&lt;/p&gt;
&lt;p&gt;왜냐하면, (내가 파악한게 맞다면) HTTPConnection 객체를 생성하고 별도의 작업을 안해주었기 때문이다.&lt;/p&gt;
&lt;p&gt;그래서 &lt;code&gt;conn.connect()&lt;/code&gt;을 호출하여 새로운 커넥션을 연결한다.&lt;/p&gt;
&lt;p&gt;그리고 이 과정에서 HTTPConnection 클래스의 connect 메소드는 내부적으로 &lt;code&gt;self._new_conn()&lt;/code&gt;을 호출한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# HTTPConnection()._new_conn()

                ....
        try:
            sock = connection.create_connection(
                (self._dns_host, self.port),
                self.timeout,
                source_address=self.source_address,
                socket_options=self.socket_options,
            )
                ...
        except SocketTimeout as e:
            raise ConnectTimeoutError(
                self,
                f&amp;quot;Connection to {self.host} timed out. (connect timeout={self.timeout})&amp;quot;,
            ) from e
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기에서 Socket을 연결할 때 Timeout이 발생하면 Connection Timeout이 발생하게 된다. (ConnectionTimeoutError)&lt;/p&gt;
&lt;h6 id="b-connrequest"&gt;b. conn.request(...)&lt;/h6&gt;&lt;p&gt;&lt;code&gt;conn.request()&lt;/code&gt;에서 요청을 전부 던진다.&lt;/p&gt;
&lt;p&gt;그 다음 &lt;code&gt;conn.timeout = read_timeout&lt;/code&gt;에서 read_timeout을 &lt;code&gt;conn.timeout&lt;/code&gt;을 설정한다.&lt;br&gt;
이제 이후에 호출되는 &lt;code&gt;response = conn.getresponse()&lt;/code&gt;는 read와 관련되었다는 것을 유추할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;response = conn.getresponse()&lt;/code&gt; 로 위에서 보낸 요청의 응답을 받는다.&lt;br&gt;
이때 ReadTimeoutError가 발생할 수 있다.&lt;/p&gt;
&lt;p&gt;즉, read timeout은 단순하게 생각해서 &lt;code&gt;서버가 요청을 처리하는 시간 + 서버가 보낸 요청이 클라이언트에 도달하는 시간&lt;/code&gt;이라고 생각할 수 있다. (블러킹된 시간)&lt;/p&gt;
&lt;p&gt;request()와 response()는 socket의 send()와 recv()가 추상화된 형태라고 볼 수 있다. 그래서 코드를 쭉 따라가면 socket 모듈쪽에서 막히는 부분이 생긴다. (구현이 안되어 있다거나..) 이 부분이 구현이 되어 있지 않아도 코드가 동작하는 이유는 CPython으로 구현되어 있기 때문이다.&lt;/p&gt;
&lt;p&gt;좀 더 구체적으로는 request()는 sendall()이라는 메소드에서 막히게 된다. response()는 fp.readline()에서 막히게 된다.&lt;/p&gt;
&lt;p&gt;request와 response가 순차적으로 처리되는데도 &lt;strong&gt;대기&lt;/strong&gt;하는 작업이 없는 이유는 send()와 recv()가 내부적으로 blocking으로 구현되었기 때문이다.&lt;/p&gt;
&lt;h2 id="3-timeout을-초과하는-위-경우resel"&gt;3. timeout을 초과하는 위 경우(res.elapsed=0.32)에 왜 Error가 발생하지 않았는가?&lt;/h2&gt;&lt;p&gt;이건 정확하게는 잘 모르겠다. 다만 코드를 보니까 여러 가능성을 추론해볼 수 있겠다.&lt;/p&gt;
&lt;h4 id="a-elapsed가-측정되는-위치를-생각해보면"&gt;A. elapsed가 측정되는 위치를 생각해보면..&lt;/h4&gt;&lt;pre&gt;&lt;code class="language-python"&gt;        # Total elapsed time of the request (approximately)
        elapsed = preferred_clock() - start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;elapsed는 connect와 read가 진행되는 상황을 전부 포함해서 측정한 시간이다.&lt;/p&gt;
&lt;p&gt;그래서 최악의 경우 connect timeout 시간을 가득 채우고 read timeout 시간을 가득채울 경우 &lt;code&gt;Session().get(&amp;quot;https://google.co.kr&amp;quot;, timeout=0.12)&lt;/code&gt;는 0.24가 나올 수 있다.&lt;/p&gt;
&lt;p&gt;-&amp;gt; 그런데, 실제로는 0.32가 나왔다.&lt;/p&gt;
&lt;h4 id="b-read-timeout이-패킷별로-적용된다"&gt;B. read timeout이 패킷별로 적용된다?&lt;/h4&gt;&lt;p&gt;만약에, read timeout이 패킷별로 적용된다고 생각해보자.&lt;/p&gt;
&lt;p&gt;그러면 0.1초 걸리는 패킷이 3개라면 충분히 가능하다고 생각할 수 있는 시나리오가 된다.&lt;/p&gt;
&lt;p&gt;-&amp;gt; 그런데, 버퍼관련된 코드는 찾았는데, 패킷마다 read timeout이 별도로 적용되는 코드는 찾지 못했다..&lt;/p&gt;
&lt;h4 id="c-진실은"&gt;C. 진실은?&lt;/h4&gt;&lt;p&gt;와이어샤크를 통해 다 뜯어보면 알 수 있지 않을까? ㅎ&lt;/p&gt;
</description><pubDate>Sat, 28 Oct 2023 22:01:34 +0900</pubDate><guid>http://blex.me/@mildsalmon/requests-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%82%B4%EA%B0%80-%EC%95%84%EB%8A%94-timeout%EC%9D%80-%EB%84%88%EB%AC%B4-%EC%B6%94%EC%83%81%EC%A0%81%EC%9D%B4%EC%98%80%EC%96%B4</guid></item><item><title>Spark 맛보기 - 2. 스파크 애플리케이션 개념</title><link>http://blex.me/@mildsalmon/spark-%EB%A7%9B%EB%B3%B4%EA%B8%B0-2-%EC%8A%A4%ED%8C%8C%ED%81%AC-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B0%9C%EB%85%90</link><description>&lt;h2 id="1-스파크-연산이-실행되기까지"&gt;1. 스파크 연산이 실행되기까지&lt;/h2&gt;&lt;p&gt;스파크 연산들은 작업으로 표현된다. 작업들은 태스크라고 불리는 저수준 RDD 바이트 코드로 변환되며 실행을 위해 스파크의 executor들에 분산된다.&lt;/p&gt;
&lt;p&gt;상위 수준 정형화 API로 표현된 모든 연산은 저수준으로 최적화되고 생성되는 RDD 작업으로 분해된 다음, executor들의 JVM을 위한 스칼라 바이트코드로 변환된다. 생성된 RDD 작업 코드들은 사용자들은 볼 수 없으며 사용자가 접근 가능한 RDD API와도 다르다.&lt;/p&gt;
&lt;h2 id="2-스파크-애플리케이션-개념의-이해"&gt;2. 스파크 애플리케이션 개념의 이해&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;애플리케이션
&lt;ul&gt;
&lt;li&gt;API를 써서 스파크 위에서 돌아가는 사용자 프로그램&lt;/li&gt;
&lt;li&gt;드라이버 프로그램과 클러스터의 실행기로 이루어진다.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;SparkSession
&lt;ul&gt;
&lt;li&gt;스파크 코어 기능들과 상호 작용할 수 있는 진입점을 제공하며 그 API로 프로그래밍을 할 수 있게 해주는 객체&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;job
&lt;ul&gt;
&lt;li&gt;스파크 action에 대한 응답으로 생성되는 여러 task로 이루어진 병렬 연산&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;stage
&lt;ul&gt;
&lt;li&gt;각 job은 stage라 불리는 서로 의존성을 가지는 다수의 태스크 모음으로 나뉜다.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;task
&lt;ul&gt;
&lt;li&gt;스파크 executor로 보내지는 작업 실행의 가장 기본적인 단위&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="a-스파크-애플리케이션과-sparksessio"&gt;A. 스파크 애플리케이션과 SparkSession&lt;/h4&gt;&lt;p&gt;모든 스파크 애플리케이션의 핵심에는 스파크 드라이버 프로그램이 있으며, 이 드라이버는 SparkSession 객체를 만든다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/10/21/2023102117_PYWmzOrTsIZZYLLgq6GC.png" src="/resources/media/images/content/2023/10/21/2023102117_PYWmzOrTsIZZYLLgq6GC.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;SparkSession 객체를 만들었으면 이를 통해 스파크 연산을 수행하는 API를 써서 프로그래밍이 가능하다.&lt;/p&gt;
&lt;h4 id="b-스파크-job"&gt;B. 스파크 job&lt;/h4&gt;&lt;p&gt;드라이버는 스파크 애플리케이션을 하나 이상의 스파크 job으로 변환한다. 각 job은 DAG로 변환된다. 이것이 스파크의 실행 계획이 된다.&lt;/p&gt;
&lt;p&gt;DAG 그래프에서 각각의 노드는 하나 이상의 스파크 스테이지에 해당한다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/10/22/2023102215_48RPPPKNZZEI1bDJmwkR.png" src="/resources/media/images/content/2023/10/22/2023102215_48RPPPKNZZEI1bDJmwkR.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;h4 id="c-스파크-stage"&gt;C. 스파크 stage&lt;/h4&gt;&lt;p&gt;스파크 연산은 여러 스테이지로 나뉘어야 한다.&lt;/p&gt;
&lt;p&gt;스파크 executor끼리의 데이터 전송이 이루어지는 연산 범위 경계 위에서 스테이지가 결정되기도 한다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/10/22/2023102215_GdRCQJXWDnfIcM7eVsio.png" src="/resources/media/images/content/2023/10/22/2023102215_GdRCQJXWDnfIcM7eVsio.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;h4 id="d-스파크-task"&gt;D. 스파크 task&lt;/h4&gt;&lt;p&gt;각 stage는 최소 실행 단위이며 스파크 executor들 위에서 연합 실행되는 스파크 task들이 이루어진다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/10/22/2023102215_GgxELBdvVK5YgFFWIFqW.png" src="/resources/media/images/content/2023/10/22/2023102215_GgxELBdvVK5YgFFWIFqW.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;h2 id="99-위-내용-중에-추가-설명이-필요한-부분-"&gt;99. 위 내용 중에 추가 설명이 필요한 부분 (알쓸신잡)&lt;/h2&gt;&lt;h4 id="a-sparkcontext와-sparksess"&gt;A. SparkContext와 SparkSession&lt;/h4&gt;&lt;h6 id="a-sparkcontext"&gt;a. SparkContext&lt;/h6&gt;&lt;p&gt;SparkContext는 Spark 애플리케이션에서 가장 중요한 진입점이다. 클라이언트 프로그램과 Spark 클러스터 간의 연결을 설정하며, 다양한 Spark 기능을 초기화하고 구성한다.&lt;/p&gt;
&lt;h6 id="b-sparksession"&gt;b. SparkSession&lt;/h6&gt;&lt;p&gt;Spark 2.0 이후로, SparkSession은 SparkContext, SQLContext, HiveContext 등을 하나로 통합한 단일 진입점으로 소개된다. 이는 DataFrame과 DataSet API, SQL 쿼리, Hive 쿼리 등을 쉽게 처리할 수 있도록 도와준다.&lt;/p&gt;
&lt;h2 id="999-참고자료"&gt;999. 참고자료&lt;/h2&gt;&lt;p&gt;줄스 담지, 브룩 웨닉, 타타가타 다스, 데니 리 저/박종영, &amp;quot;러닝 스파크&amp;quot;, 제이펍(2022)&lt;/p&gt;
</description><pubDate>Sun, 22 Oct 2023 15:51:28 +0900</pubDate><guid>http://blex.me/@mildsalmon/spark-%EB%A7%9B%EB%B3%B4%EA%B8%B0-2-%EC%8A%A4%ED%8C%8C%ED%81%AC-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B0%9C%EB%85%90</guid></item><item><title>Spark 맛보기 - 1. Spark란?</title><link>http://blex.me/@mildsalmon/spark-%EB%A7%9B%EB%B3%B4%EA%B8%B0-spark%EB%9E%80</link><description>&lt;h2 id="1-spark가-무엇일까"&gt;1. Spark가 무엇일까?&lt;/h2&gt;&lt;p&gt;Apache Spark는 대규모 분산 데이터 처리를 하기 위해 설계된 통합형 엔진이다.
Spark는 중간 연상을 위해 메모리 저장소를 지원하며 디스크를 사용하는 MapReduce보다 훨씬 빠르게 동작한다.&lt;/p&gt;
&lt;p&gt;핵심 라이브러리는 MLlib, Spark SQL, Spark Streaming, GraphX가 있다. 모든 것에 대해 설명할 자신은 없어서 이번에는 Spark와 Spark SQL에 대해 공부한 내용을 정리하려고 한다. 나중에 Spark Core에 대해 더 많이 알게 되면 그 부분은 따로 글을 작성하려고 한다. Streaming은 나..중에..&lt;/p&gt;
&lt;p&gt;스파크 설계 철학의 핵심 특성은 속도, 사용 편의성, 모듈성, 확장성이다. 이 철학들은 MapReduce에서 겪은 단점들을 개선하려는 과정에서 나온 특성들이다.&lt;/p&gt;
&lt;h4 id="a-속도"&gt;A. 속도&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;하드웨어의 발전으로 인해 가격 및 CPU와 메모리의 성능이 향상&lt;/li&gt;
&lt;li&gt;스파크는 질의 연산을 DAG로 구성&lt;/li&gt;
&lt;li&gt;물리적 실행 엔진인 Tungsten은 whole-stage code generation 기법을 써서 실행을 위한 간결한 코드를 생성해 낸다.&lt;/li&gt;
&lt;li&gt;모든 중간 결과는 메모리에 유지되며, 디스크 I/O를 제한적으로 사용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="b-사용-편리성"&gt;B. 사용 편리성&lt;/h4&gt;&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/10/21/2023102117_cnSFuT17QujWYolZx5Ka.png" src="/resources/media/images/content/2023/10/21/2023102117_cnSFuT17QujWYolZx5Ka.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;고수준 데이터 추상화 계층(DataFrame, DataSet) 아래에 유연한 분산 데이터 세트(Resilient Distributed Dataset, RDD)라 불리는 논리 자료구조를 구축하여 단순성을 실현하였다.&lt;/li&gt;
&lt;li&gt;Operation의 종류로서 Transformation과 Action의 집합과 단순한 프로그래밍 모델을 제공함으로써 각자 편한 언어로 빅데이터 애플리케이션을 만들 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="c-모듈성"&gt;C. 모듈성&lt;/h4&gt;&lt;p&gt;스파크 연산은 다양한 타입의 워크로드에 적용 가능.&lt;/p&gt;
&lt;p&gt;하나의 Spark Application을 작성함으로써 모든 것이 실행 가능해진다.&lt;/p&gt;
&lt;p&gt;Spark를 쓴다면 자신의 워크로드를 처리하기 위한 하나의 통합된 처리 엔진을 갖게 된다.&lt;/p&gt;
&lt;h4 id="d-확장성"&gt;D. 확장성&lt;/h4&gt;&lt;p&gt;Spark는 저장보다는 빠른 병렬 연산 엔진에 초점이 맞추어져 있다.&lt;/p&gt;
&lt;p&gt;Spark가 수 많은 데이터 소스(Hadoop, Cassandra ...)에서 데이터를 읽어들여 메모리에서 처리 가능하다.&lt;/p&gt;
&lt;h2 id="2-통합된-분석"&gt;2. 통합된 분석&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;빅데이터 처리를 위한 통합 엔진&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apache Spark: A Unified Engine For Big Data Processing&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4 id="a-단일화된-스택으로의-아파치-컴포넌트"&gt;A. 단일화된 스택으로의 아파치 컴포넌트&lt;/h4&gt;&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/10/21/2023102117_QjY9T97kAOlG2mBATMYT.png" src="/resources/media/images/content/2023/10/21/2023102117_QjY9T97kAOlG2mBATMYT.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;컴포넌트(Spark SQL, MLlib, Spark Streaming, GraphX)는 스파크의 중심 장애 대응 엔진과는 별도로 존재한다. API를 써서 스파크 애플리케이션을 만들면 스파크 코어 엔진이 적절한 DAG로 변환해 실행하게 된다. 어떤 언어로 스파크 코드를 작성해 정형화 API를 사용하더라도 실제 코드는 고도로 경량화된 바이트코드로 변환되어 클러스터 전체에 나뉘어 워커 노드의 JVM에서 실행된다.&lt;/p&gt;
&lt;h4 id="b-아파치-스파크의-분산-실행"&gt;B. 아파치 스파크의 분산 실행&lt;/h4&gt;&lt;p&gt;스파크의 분산 아키텍쳐 위에서 모든 컴포넌트들이 같이 동작하면서 서로 통신하는지, 어떤 식으로 배포가 가능한지 알아둘 필요가 있다.&lt;/p&gt;
&lt;p&gt;스파크 아키텍쳐를 넓은 범위에서 보면, 하나의 &lt;strong&gt;스파크 애플리케이션&lt;/strong&gt;은 스파크 클러스터의 병렬 작업들을 조율하는 하나의 &lt;strong&gt;드라이버&lt;/strong&gt; 프로그램으로 이루어진다. 드라이버는 &lt;strong&gt;SparkSession 객체&lt;/strong&gt;를 통해 클러스터의 분산 컴포넌트(spark executor, cluster manager)들에 접근한다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/10/21/2023102117_PYWmzOrTsIZZYLLgq6GC.png" src="/resources/media/images/content/2023/10/21/2023102117_PYWmzOrTsIZZYLLgq6GC.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;h6 id="a-스파크-드라이버"&gt;a. 스파크 드라이버&lt;/h6&gt;&lt;p&gt;SparkSession 객체를 초기화하는 책임을 가진 스파크 애플리케이션의 일부&lt;/p&gt;
&lt;p&gt;클러스터 매니저와 통신하며 spark executor들을 위해 필요한 자원(cpu, memory)을 요청하고 모든 스파크 작업을 DAG 연산 형태로 변환하고 스케줄링하며 각 실행 단위를 태스크로 나누어 spark executor들에게 분배해준다.&lt;/p&gt;
&lt;p&gt;자원이 할당되면 driver는 executor와 직접 통신한다.&lt;/p&gt;
&lt;h6 id="b-sparksession"&gt;b. SparkSession&lt;/h6&gt;&lt;p&gt;SparkSession은 모든 스파크 연산과 데이터에 대한 통합 연결 채널이 되었다.&lt;/p&gt;
&lt;h6 id="c-cluster-manager"&gt;c. Cluster manager&lt;/h6&gt;&lt;p&gt;자원을 관리 및 할당하는 책임을 지닌다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;standalone Cluster Manager&lt;/li&gt;
&lt;li&gt;YARN&lt;/li&gt;
&lt;li&gt;Mesos&lt;/li&gt;
&lt;li&gt;Kubernetes&lt;/li&gt;
&lt;/ul&gt;
&lt;h6 id="d-spark-executor"&gt;d. Spark Executor&lt;/h6&gt;&lt;p&gt;클러스터의 각 워커 노드에서 동작한다. 드라이버 프로그램과 통신하며 워커에서 태스크를 실행하는 역할을 한다.&lt;/p&gt;
&lt;p&gt;대부분의 배포 모드에서 노드당 하나의 executor만 실행된다.&lt;/p&gt;
&lt;h6 id="e-배포-모드"&gt;e. 배포 모드&lt;/h6&gt;&lt;p&gt;스파크는 여러 다른 환경에서 다른 설정으로 돌아갈 수 있도록 다양한 배포 모드를 지원한다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/10/21/2023102117_22EJLWhl256jlbm9633y.png" src="/resources/media/images/content/2023/10/21/2023102117_22EJLWhl256jlbm9633y.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;h6 id="f-분산-데이터와-파티션"&gt;f. 분산 데이터와 파티션&lt;/h6&gt;&lt;p&gt;물리 데이터는 파티션이 되어 저장소 전체에 분산된다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/10/21/2023102117_tCUkEFB3x7ewbwPcDdXL.png" src="/resources/media/images/content/2023/10/21/2023102117_tCUkEFB3x7ewbwPcDdXL.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;스파크는 각 파티션을 고수준에서 논리적인 데이터 추상화, 즉 메모리의 데이터 프레임 객체로 바라본다. Spark Executor는 가능하면 데이터 지역성을 고려하여 네트워크에서 가장 가까운 파티션을 읽어 들이도록 태스크를 할당한다.&lt;/p&gt;
&lt;p&gt;파티셔닝은 효과적인 병렬 처리를 가능하게 해 준다. 각 executor가 쓰는 CPU 코어는 작업해야 하는 데이터의 파티션에 할당되어 네트워크 사용을 최소화할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/10/21/2023102117_FrUcFG0LbINFdtTnsWq1.png" src="/resources/media/images/content/2023/10/21/2023102117_FrUcFG0LbINFdtTnsWq1.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-python"&gt;# .txt 파일을 DataFrame으로 읽고 8개의 파티션으로 나눈다.
# 각 executor는 하나 이상의 파티션을 메모리로 읽어 들이게 된다.
log_df = spark.read.text('/Users/mildsalmon/requirements.txt').repartition(8)
print(log_df.rdd.getNumPartitions())

# 8
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="99-위-내용-중에-추가-설명이-필요한-부분-"&gt;99. 위 내용 중에 추가 설명이 필요한 부분 (알쓸신잡)&lt;/h2&gt;&lt;h4 id="a-mapreduce란"&gt;A. MapReduce란?&lt;/h4&gt;&lt;p&gt;MapReduce는 구글의 &lt;strong&gt;MapReduce: Simplified Data Processing on Large Clusters&lt;/strong&gt; 논문을 바탕으로 만들어진 분산 컴퓨팅 프레임워크이다.&lt;/p&gt;
&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/10/21/2023102117_QHnDiHbBxn0JCsaVD6Ns.png" src="/resources/media/images/content/2023/10/21/2023102117_QHnDiHbBxn0JCsaVD6Ns.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;특징을 정리하면 아래와 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;데이터를 여러 노드에 분산하여 병렬로 처리하는 분산 컴퓨팅 모델을 사용&lt;/li&gt;
&lt;li&gt;map, reduce 함수를 개발자가 작성&lt;/li&gt;
&lt;li&gt;map과 reduce 중간 결과를 디스크에 작성한다는 특징이 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;시대가 발전됨에 따라 위 특징 중 일부는 자연스럽게 단점이 되었다. 메모리 양이 증가되어서 느리고 비용이 많이 드는 디스크 io를 대체할 수 있게 되었다. map과 reduce 함수를 작성해야한다는 것도 개발 복잡성을 늘린다는 단점으로 작용했다.&lt;/p&gt;
&lt;p&gt;Spark는 MapReduce의 비효율적인 부분을 개선해야겠다는 생각으로 만들어졌다.&lt;/p&gt;
&lt;h4 id="b-대규모-분산-데이터-처리란"&gt;B. 대규모 분산 데이터 처리란?&lt;/h4&gt;&lt;p&gt;대용량의 데이터를 여러대의 컴퓨터에 나눠서 처리하는 것을 말한다.&lt;/p&gt;
&lt;p&gt;굳이 여러대의 컴퓨터까지 갈 필요 없이 1개의 프로세스에 데이터를 올려두고 멀티 스레딩으로 처리할 수도 있지 않느냐고 반문할 수도 있다. 그런데 1개의 프로세스, 1대의 컴퓨터에 다 올라가지 못하는 데이터인 경우에도 해당 방법이 유효할지 생각해봐야 한다.&lt;/p&gt;
&lt;h2 id="999-참고자료"&gt;999. 참고자료&lt;/h2&gt;&lt;p&gt;줄스 담지, 브룩 웨닉, 타타가타 다스, 데니 리 저/박종영, &amp;quot;러닝 스파크&amp;quot;, 제이펍(2022)&lt;/p&gt;
</description><pubDate>Sat, 21 Oct 2023 17:50:49 +0900</pubDate><guid>http://blex.me/@mildsalmon/spark-%EB%A7%9B%EB%B3%B4%EA%B8%B0-spark%EB%9E%80</guid></item><item><title>[서평] 세상 끝의 카페</title><link>http://blex.me/@mildsalmon/%EC%84%9C%ED%8F%89-%EC%84%B8%EC%83%81-%EB%81%9D%EC%9D%98-%EC%B9%B4%ED%8E%98</link><description>&lt;p&gt;&lt;img class="lazy" data-src="/resources/media/images/content/2023/9/16/202391620_1d3esu1dg7hkxQ4MPVPs.png" src="/resources/media/images/content/2023/9/16/202391620_1d3esu1dg7hkxQ4MPVPs.png.preview.jpg" alt=""&gt;&lt;/p&gt;
&lt;h2 id="서평"&gt;서평&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;당신은 왜 여기 있습니까?
죽음이 두렵습니까?
충만한 삶을 살고 있습니까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 책을 다 읽고 나니 연금술사 책이 떠오른다. 방향을 잃었다고 느낄 즈음 읽었던 책인데, 이 책도 비슷하게 방향을 제시해주고 있다. 사실 인생이라는 게 가끔 현타가 오면 삶을 헤매기 마련이다. 그리고 내가 옳다고 생각한 방향으로 계속 나아가더라도 이 방향이 맞는지에 대한 물음이 떠오르곤 한다. 이 책을 읽는다고 방향이 명확해지지는 않지만, 생각할 거리는 충분히 만들어준다.&lt;/p&gt;
&lt;p&gt;사람이 편협해지지 않으려면 가끔은 새로운 시각으로 세상을 바라보는 것도 필요하다. 익숙한 출근길 대신 한 번도 가보지 않았던 길로 출근을 하면 새로운 아이디어가 떠오른다는 이야기처럼 말이다. 탐험과 고통보다는 익숙함과 쾌락을 추구하는 모습을 보면 우리는 편한 것을 추구하도록 설계되어 있는 것 같다. 그런데 편한 것에서 어떤 새로운 아이디어와 기회를 얻을 수 있을까? 새로운 것을 추구한다면 새로운 곳으로 가야하지 않을까?&lt;/p&gt;
&lt;p&gt;익숙함은 남들이 정해준 것일지도 모른다. 남들이 작성해둔 가이드 문서를 보고 따라하니 잘 되더라. 그래서 그 가이드 문서를 보고 더 간략한 버전의 가이드 문서를 작성한다. 핵심 원리는 알지도 못한 채로.&lt;/p&gt;
&lt;p&gt;만족스러운 삶은 무엇일까? 나는 어떤 행위, 환경에 만족을 할까? 나는 어떤 고통을 견딜 수 있을까?&lt;/p&gt;
&lt;p&gt;열심히 사는 것은 전염이 된다. 반대도 마찬가지다. 그러므로 내 주변에 열심히 하는 사람들로 채우자. 내가 원하는 방향에 있는 사람들로.&lt;/p&gt;
&lt;p&gt;매일 조금씩 내가 진정으로 원하는 일을 해야한다. 나는 데이터 엔지니어 관련된 공부가 좋다. 그래서 아직은 여가시간에 데이터 엔지니어 관련된 공부를 할 것이다. 그리고 잘 정리해서 강의로 풀어보고 싶다.&lt;/p&gt;
</description><pubDate>Sat, 16 Sep 2023 20:04:02 +0900</pubDate><guid>http://blex.me/@mildsalmon/%EC%84%9C%ED%8F%89-%EC%84%B8%EC%83%81-%EB%81%9D%EC%9D%98-%EC%B9%B4%ED%8E%98</guid></item></channel></rss>