<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jihoon-gh.log</title>
        <link>https://velog.io/</link>
        <description>인간은 노력하는 한 방황한다</description>
        <lastBuildDate>Sun, 19 Oct 2025 06:53:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jihoon-gh.log</title>
            <url>https://images.velog.io/images/jihoon-gh/profile/361ec944-a20f-44c2-8edf-3ce845127010/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jihoon-gh.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jihoon-gh" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[현업 장애 사례 (1) - Elastic Search]]></title>
            <link>https://velog.io/@jihoon-gh/%ED%98%84%EC%97%85-%EC%9E%A5%EC%95%A0-%EC%82%AC%EB%A1%80-1-Elastic-Search</link>
            <guid>https://velog.io/@jihoon-gh/%ED%98%84%EC%97%85-%EC%9E%A5%EC%95%A0-%EC%82%AC%EB%A1%80-1-Elastic-Search</guid>
            <pubDate>Sun, 19 Oct 2025 06:53:44 GMT</pubDate>
            <description><![CDATA[<p>현업에서 장애가 발생했던 상황을 간단히 공유하려고 한다.
시리즈로 계획중이나.. 그렇게 많이 발생하지는 않는 환경이라 몇 편이 될지..
<del>개발을 잘 해서가 아니라 그 정도 트래픽이 안나온다</del></p>
<hr>
<h3 id="elastic-searchopensearch-장애-사례">Elastic Search(OpenSearch) 장애 사례</h3>
<h4 id="장애-내용">장애 내용</h4>
<p>OpenSearch의 샤드 배치가 안됐다. </p>
<h4 id="장애-원인">장애 원인</h4>
<p>사실 주니어 개발자였고, ES 관련된 부분은 많이 찾아보지 않아서 여러모로 확인에 애를 먹고 있었다.
사용하는것과 관리하는것은 다른 부분이기에 그러한 설정 부분과 공식문서 등을 유심히 찾아본 결과
클러스터에 <strong>&#39;각 노드에 설정 가능한 최대 샤드 갯수&#39;</strong>를 설정할 수 있다는 사실을 알게 되었다.</p>
<pre><code class="language-yaml"> cluster.max_shards_per_node </code></pre>
<p>해당 옵션의 default 는 1000개인데, 알고보니 개발환경 OpenSearch는 이미 1000개가 배치되어 있었다.</p>
<h4 id="장애-해결-방법">장애 해결 방법</h4>
<p>해결 방안은 두 가지다</p>
<ul>
<li>가장 쉬운 방법 - OpenSearch 클러스터에 노드를 추가한다. (비용 증가, 안정성 확보)</li>
<li>옵션 조정을 통해 노드당 샤드 최대 갯수를 증가시킨다. (비용 동결, 안정성 하락)</li>
</ul>
<p>현업에서는 모든 부분에 있어 다양한 요소를 두고 판단한다.
당연히 계속 확장하면 좋지만 그 비용도 우리가 내야한다.
<del>특히 상황이 어려울수록 클라우드 비용 절감은 중요한 요소이다</del>
그러나 우리는 회의 끝에, 노드를 증가시키는 의사 결정을 했고, 이를 통해 안정성을 확보했다.</p>
<hr>
<h4 id="느낀점">느낀점</h4>
<p>단순히 ES의 사용 뿐 아니라 인프라 관리 및 설정 등에 대해서도 중요성을 체감할 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Data Quality (3) - Great Expectations 실습]]></title>
            <link>https://velog.io/@jihoon-gh/Data-Quality-3-Great-Expectations-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@jihoon-gh/Data-Quality-3-Great-Expectations-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Sat, 18 Oct 2025 06:33:04 GMT</pubDate>
            <description><![CDATA[<p>저번에 Great Expectations의 기초적인 요소들에 대해 학습을 했다.
해당 내용을 실제 코드로 옮겨보고자 한다.
환경은 모두가 다를 수 있고, 이렇게 되는구나를 느끼면 될 것 같다.</p>
<blockquote>
<p>필자는 Jupyter Notebook, pyspark, great expectation을 사용하였다.
dataset은 
<a href="https://github.com/databricks/LearningSparkV2/tree/master/databricks-datasets/learning-spark-v2/flights">https://github.com/databricks/LearningSparkV2/tree/master/databricks-datasets/learning-spark-v2/flights</a>
의 departuredelays.csv를 사용하였다. </p>
</blockquote>
<hr>
<blockquote>
<ol>
<li>import </li>
</ol>
</blockquote>
<pre><code class="language-python">from pyspark.sql import SparkSession
import great_expectations as gx</code></pre>
<hr>
<blockquote>
<ol start="2">
<li>일단 SparkSession 만들고 dataframe 생성</li>
</ol>
</blockquote>
<pre><code class="language-python">spark = SparkSession.builder.appName(&quot;test_app&quot;).getOrCreate()
df = spark.read.option(&quot;header&quot;, &quot;true&quot;).option(&quot;inferSchema&quot;, &quot;true&quot;).csv(&quot;departuredelays.csv&quot;)
# inferSchema 옵션을 true로 할 경우 처리 시간이 오래 걸린다. 현업에서는 스키마 따로 정의하자

---
&gt; 3. great expectaion 시작. data context 생성
```python
context = gx.get_context(mode=&quot;ephemeral&quot;)
# 간단한 테스트니까 ephemeral 도 충분하다.</code></pre>
<blockquote>
<ol start="4">
<li>datasource 생성</li>
</ol>
</blockquote>
<pre><code class="language-python">data_source = context.data_sources.add_spark(name=&quot;test_source&quot;)
# source는 저번에 말했듯 datalake 같은 저장소에 대한 것이다</code></pre>
<blockquote>
<ol start="5">
<li>data asset 생성</li>
</ol>
</blockquote>
<pre><code class="language-python">data_asset = data_source.add_dataframe_asset(name=&quot;test_asset&quot;)
# asset은 곧 dataset 같은 단위라고 보면 된다.</code></pre>
<blockquote>
<ol start="6">
<li>batch_definition 생성</li>
</ol>
</blockquote>
<pre><code class="language-python">batch_def = data_asset.add_batch_definition_whole_dataframe(&quot;test_batch&quot;)
# batch는 위의 data asset, 즉 dataset이 실제로는 매우 클 때가 있는데 (수백 Gb ~ Tb 단위)
# 이를 한 번에 처리하기 어려우므로 파티셔닝해서 처리할 때 쓰인다.
# 우리는 예시이므로 asset 전체를 batch로 가져가는 add_batch_definition_whole_dataframe 를 사용했다.</code></pre>
<blockquote>
<ol start="7">
<li>expectation 생성</li>
</ol>
</blockquote>
<pre><code class="language-python">expectations_distance =  gx.expectations.ExpectColumnValuesToBeBetween(column=&quot;distance&quot;, max_value=5000, min_value=10)
# 여기서부터는 dataset에 따라, 조직의 요구사항에 따라 dataset quality check 요소를 expectation으로 만든다.
# 나는 항공기의 출발지,도착지,거리, 지연 등을 담은 데이터셋이므로 거리에 대해 체크했다.</code></pre>
<blockquote>
<ol start="8">
<li>expectation suite 생성 및 expectation 추가</li>
</ol>
</blockquote>
<pre><code class="language-python">expectation_suite_ref = gx.ExpectationSuite(name=&quot;test_suite&quot;)
expectation_suite = context.suites.add(expectation_suite_ref)
expectation_suite.add_expectation(expectations_distance)
# expectation_suite를 먼저 만들어주고, 이 후에 expectation을 suite에 추가한다.</code></pre>
<blockquote>
<ol start="9">
<li>validation 생성</li>
</ol>
</blockquote>
<pre><code class="language-python">validation = gx.ValidationDefinition(data=batch_def, suite=expectation_suite, name=&quot;test_valid&quot;)
# 이제 실제로 수행할 validation에 우리의 작업 단위와 평가 기준 등을 담아준다.</code></pre>
<blockquote>
<ol start="10">
<li>validation 수행</li>
</ol>
</blockquote>
<pre><code class="language-python">batch_params = {&quot;dataframe&quot;: df}
result = validation.run(batch_parameters=batch_params)
# dataframe을 사용한다고 정의해주고, 이를 실행해서 결과를 얻는다.</code></pre>
<blockquote>
<ol start="11">
<li>result 확인</li>
</ol>
</blockquote>
<pre><code class="language-python">print(result)
# 확인 결과는 아래와 같다.
{
  &quot;success&quot;: true,
  &quot;results&quot;: [
    {
      &quot;success&quot;: true,
      &quot;expectation_config&quot;: {
        &quot;type&quot;: &quot;expect_column_values_to_be_between&quot;,
        &quot;kwargs&quot;: {
          &quot;batch_id&quot;: &quot;test_source-test_asset&quot;,
          &quot;column&quot;: &quot;distance&quot;,
          &quot;min_value&quot;: 10.0,
          &quot;max_value&quot;: 5000.0
        },
        &quot;meta&quot;: {},
        &quot;id&quot;: &quot;b692dcb0-1813-4e82-91ce-8c045572d107&quot;,
        &quot;severity&quot;: &quot;critical&quot;
      },
      &quot;result&quot;: {
        &quot;element_count&quot;: 1391578,
        &quot;unexpected_count&quot;: 0,
        .
        .
        .
#성공적으로 수행되었음을 확인했다. 아래의 결과중 중요한 부분은
# success: expectation을 만족하지 못하면 false
# 그 이후 우리가 정의한 expectation type을 확인하고, element_count와 unexpected_count를 통해
# 실패케이스를 확인하고 카운팅하고 한다.</code></pre>
<hr>
<p>실패 케이스도 쉽게 만들 수 있으나, 여러분들이 해보시길 권장한다.
이렇게 가볍게 spark와 great_expectation을 연동하여 test를 해봤다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Data Quality (2) - Great Expectations 개요]]></title>
            <link>https://velog.io/@jihoon-gh/Data-Quality-2-Great-Expectations-%EA%B0%9C%EC%9A%94</link>
            <guid>https://velog.io/@jihoon-gh/Data-Quality-2-Great-Expectations-%EA%B0%9C%EC%9A%94</guid>
            <pubDate>Wed, 15 Oct 2025 12:38:05 GMT</pubDate>
            <description><![CDATA[<p>Data Quality 관련 도구에는 다양한 것들이 있다.
그 중에서도, 가장 큰 커뮤니티를 가진 도구인 Great Expectations에 대해 알아보겠다.</p>
<p><img src="https://velog.velcdn.com/images/jihoon-gh/post/4241588f-8fba-47ab-83f5-ee0141f8f3e6/image.png" alt=""></p>
<p><a href="https://github.com/great-expectations/great_expectations">공식 깃허브</a>, 대략 10.9k star면 훌륭한 것 같다.</p>
<p>gx-core와 gx cloud가 존재한다. gx-core의 경우 Apache-2.0 license 이다.</p>
<p>기본적으로는 python 기반 품질 관리 도구이고, 다양한 data engineering 도구들과 연동할 수 있다.
필자는 spark를 주로 사용하므로 이에 대해 같이 사용해 볼 것이다.</p>
<p>보통은 datalake에서 그 이후 warehouse, mart 및 ML/DL 모델 등 다양한 곳으로 데이터가 흘러갈텐데, 이 때 이 흘러갈 data의 quality를 검증한다고 생각하면 된다.</p>
<hr>
<h4 id="기본적인-구조">기본적인 구조</h4>
<ul>
<li>Data Context: 프로젝트의 전체 설정과 메타데이터 관리. 3가지 타입 존재<ul>
<li>Ephemeral: In-Memory, 메타데이터를 영속적으로 저장하지는 않음. PoC 용도</li>
<li>FilesystemData: 가장 기본적인 형태. 메타데이터를 filesystem에 저장</li>
<li>Cloud: 위에서 언급한 gx cloud를 사용할 때 사용 가능</li>
</ul>
</li>
<li>Data Source: 데이터가 저장되어 있는곳. Databricks, RDBMS, Spark, Pandas 등</li>
<li>Data Asset: Source에 저장된 dataset을 의미한다고 보면 됨. 즉 우리의 quality testing 대상</li>
<li>Batch Definition: Asset을 이제 어떻게 처리할것인가에 대한 설정. batch size 등 </li>
<li>Expectation Suite: 우리가 정의할 Expectations(데이터 품질 규칙) 집합</li>
<li>Expectation: 데이터에 대한 품질 규칙. 예를 들어<ul>
<li>col1은 not null 이어야 함</li>
<li>col2는 정수값인데 max=100, min=0 범위 안에 있어야 함. 등</li>
</ul>
</li>
<li>Validation: 위의 Batch Definition과 Excetation Suite를 사용해서, 즉 정의된 batch 대상에 해당 suite 적용</li>
</ul>
<hr>
<p>3편에서 실제 spark와 gx-core의 연동을 다뤄보겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Data Quality (1) - 정의와 속성]]></title>
            <link>https://velog.io/@jihoon-gh/Data-Quality-1-%EC%A0%95%EC%9D%98%EC%99%80-%EC%9A%94%EC%86%8C</link>
            <guid>https://velog.io/@jihoon-gh/Data-Quality-1-%EC%A0%95%EC%9D%98%EC%99%80-%EC%9A%94%EC%86%8C</guid>
            <pubDate>Tue, 14 Oct 2025 12:54:03 GMT</pubDate>
            <description><![CDATA[<h3 id="data-quality란">Data Quality란</h3>
<p>데이터 품질</p>
<hr>
<h3 id="data-quality가-중요한-이유">Data Quality가 중요한 이유</h3>
<ul>
<li>Data를 대시보드 등을 구성하고 의사결정에 사용하는데 데이터가 잘못됐다면?</li>
<li>Data Quality의 이슈로 인해 ML/DL 모델 학습에 문제가 생긴다면?</li>
</ul>
<p>이런 이슈를 Data Quality Management를 통해 방지하고자 함</p>
<hr>
<h3 id="data-quality-속성">Data Quality 속성</h3>
<ul>
<li><p><strong>정확성 (Accuracy)</strong></p>
<ul>
<li>데이터에서 잘못된 값, 오타, 왜곡된 정보와 같은 오류를 제거</li>
<li>정확성을 측정하고 개선하기 위한 방법<ul>
<li>검증 절차(Validation checks)</li>
<li>신뢰할 수 있는 출처로 교차 검증(Cross-referencing with trusted sources)</li>
<li>정기적인 감사(Regular audits)</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>완전성 (Completeness)</strong></p>
<ul>
<li>데이터셋에 모든 필수 데이터가 존재하고 사용 가능한 정도</li>
<li>모든 필요한 속성이 완전히 채워져 있어야 함</li>
<li>필수 속성 (Required attributes)<ul>
<li>데이터셋이 완전하다고 간주되려면 반드시 값이 입력되어야 함</li>
</ul>
</li>
<li>선택 속성 (Optional attributes)<ul>
<li>필수는 아니지만 추가적인 인사이트를 제공</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>일관성 (Consistency)</strong></p>
<ul>
<li>데이터의 일관성. 이를 보장하기 위해 아래와 같은 방법들 활용</li>
<li>동기화 프로세스 (Synchronization processes)<ul>
<li>한 시스템에서 이루어진 업데이트가 다른 모든 시스템에도 정확히 반영</li>
</ul>
</li>
<li>검증 규칙 (Validation rules)<ul>
<li>시스템 간 불일치(discrepancy)를 감지하고 해결할 수 있도록 지원합니다.</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>적시성 (Timeliness)</strong></p>
<ul>
<li>데이터가 필요한 시점에 이용 가능하고 최신 상태인지에 대한 척도<ul>
<li>적시에 데이터를 제공함으로써 가장 최신·관련성 높은 정보를 기반으로 의사결정 가능</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>유효성 (Validity)</strong></p>
<ul>
<li>데이터셋의 값이 정의된 규칙, 표준 및 기대치에 부합하는 정도</li>
<li>데이터가 사전에 정해진 기준(타입, 형식,범위, 제약 조건 등)을 준수하도록 보장</li>
</ul>
</li>
<li><p><strong>고유성 (Uniqueness)</strong></p>
<ul>
<li>각 레코드가 중복되지 않고 고유함</li>
<li>이를 보장하기 위해 다음과 같은 방법을 사용<ul>
<li>엄격한 검증 규칙 적용(Strict validation rules)</li>
<li>중복 제거 프로세스(Deduplication processes) 활용</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="data-quality-관련-도구">Data Quality 관련 도구</h3>
<ul>
<li>Great Expectations (가장 유명한 도구)</li>
<li>Soda (좀 더 sql 친화적?이라고 알고있음)</li>
<li>Deequ (아마존이 개발함)</li>
</ul>
<p>다음편에서는 Great Expectaion(중에서 gx-core)의 기본 개념 및 구성 등에 대해 다루겠음</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Elastic Search 정리(3)]]></title>
            <link>https://velog.io/@jihoon-gh/Elastic-Search-%EC%A0%95%EB%A6%AC3</link>
            <guid>https://velog.io/@jihoon-gh/Elastic-Search-%EC%A0%95%EB%A6%AC3</guid>
            <pubDate>Fri, 08 Nov 2024 07:29:32 GMT</pubDate>
            <description><![CDATA[<h3 id="cat-api">CAT API</h3>
<ul>
<li>Compact and Aligned Text API</li>
</ul>
<h3 id="가장-많이-사용되는-api-4개">가장 많이 사용되는 API 4개</h3>
<ul>
<li>_cat/health<ul>
<li>클러스터의 전반적인 상태확인 API</li>
<li>상태 종류<ul>
<li>green : 프라이머리, 레플리캬 사드 모두 정상적으로 배치</li>
<li>yellow : 프라이머리 정상, 레플리카 비정상 -&gt; 검색 성능 악영향</li>
<li>red : 프라이머리와 레플리카 둘 다 비정상</li>
</ul>
</li>
</ul>
</li>
<li>_cat/nodes<ul>
<li>기본적인 노드 정보 확인 API</li>
<li>h 옵션을 통해 header 지정 -&gt; 원하는 정보 filtering</li>
</ul>
</li>
<li>_cat/indices<ul>
<li>인덱스 상태 확인 API</li>
<li>인덱스 프라이머리 샤드 &amp; 레플리카 샤드 갯수 확인</li>
<li>이상 상태 인덱스 확인</li>
</ul>
</li>
<li>_cat/shards<ul>
<li>샤드 상태 확인</li>
</ul>
</li>
</ul>
<blockquote>
<p>?v 하면 사람이 볼 수 있는 형태(verbose)</p>
</blockquote>
<hr>
<h3 id="elastic-search-주요-지표">Elastic Search 주요 지표</h3>
<p>** 모니터링 도구 선정 ** </p>
<ul>
<li>AWS OpenSearch : AWS CloudWatch</li>
<li>ElasticSearch : Kibana..
그 외 promtheus도 고려할 만 함</li>
</ul>
<h3 id="어떤-지표를-모니터링-할-것인가">어떤 지표를 모니터링 할 것인가</h3>
<ul>
<li>** 알람을 받아서 처리해야 할 지표 **<ul>
<li>CPU Usage : 노드가 CPU를 얼마나 많이 사용하고 있는가 - 50% 이상</li>
<li>Disk Usage : 노드가 얼마나 많은 문서를 저장하고 있는가 - 70% 이상</li>
<li>Load : 노드가 얼마나 많은 CPU 및 Disk 연산을 처리하는가 (부하) - cpu 갯수 따라서</li>
<li>JVM Heap : JVM 메모리 사용량 - 꺾이는 등 정상적인 GC 상태를 관측 - 85% 이상</li>
<li>Threads : 처리량을 넘어가는 색인 / 검색 요청 존재 여부 - Rejected Threads 발생시</li>
<li>** 문제 원인 분석에 사용되는 지표 **<ul>
<li>Memory Usage : 노드에 설치되어 있는 물리적 메모리 사용량 (JVM Heap과 별개)</li>
<li>GC Duration : gc 소요시간</li>
<li>GC Rate : Old/Young GC의 발생 주기 파악</li>
<li>Disk I/O : 노드에 발생하는 디스킈 연산의 지연 시간</li>
<li>Latency : 검색과 색인 요청에 소요되는 시간</li>
<li>Rate : 검색과 색인 요청이 인입되는 양</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[ElasticSearch 정리(2)]]></title>
            <link>https://velog.io/@jihoon-gh/ElasticSearch-%EC%A0%95%EB%A6%AC2</link>
            <guid>https://velog.io/@jihoon-gh/ElasticSearch-%EC%A0%95%EB%A6%AC2</guid>
            <pubDate>Thu, 07 Nov 2024 02:31:17 GMT</pubDate>
            <description><![CDATA[<h3 id="색인indexing">색인(indexing)</h3>
<ul>
<li>문서를 분석하고 저장하는 과정</li>
</ul>
<h3 id="간단한-색인-과정">간단한 색인 과정</h3>
<ul>
<li>인덱스 존재 여부 판단 (없으면 생성)</li>
<li>매핑 정보 존재 여부 판단 (없으면 동적매핑)</li>
<li>매핑 올바른지 판단(type등 문제 있을경우 에러 발생)</li>
<li>inverted index 생성</li>
<li>프라이머리 샤드에 저장</li>
<li>레플리카 샤드에 복사</li>
</ul>
<h3 id="색인-성능과-샤드-갯수">색인 성능과 샤드 갯수</h3>
<ul>
<li>색인 성능을 위해서는 클러스터로서의 이점을 살리고 있는지 확인</li>
<li>클러스터의 이점을 살리기 위해서는 적절한 노드 갯수, 성능 및 샤드 갯수 중요</li>
<li>처음부터 완벽하게 샤드를 배치할 수는 없음<ul>
<li>점차 최적화해야됨</li>
</ul>
</li>
<li>샤드 갯수를 최적화한 후, 노드에 대한 Scale-up &amp; Scale-out<br>


</li>
</ul>
<h4 id="inverted-index--문자열을-분석한-결과를-저장하고-있는-구조체">inverted index : 문자열을 분석한 결과를 저장하고 있는 구조체</h4>
<hr>
<h3 id="간단한-검색-과정">간단한 검색 과정</h3>
<ul>
<li>검색어 분석 (analyzer 적용해서 토큰 생성)</li>
<li>inverted index 검색 (생성된 토큰을 inverted index에서 검색)</li>
<li>검색 결과 표시</li>
</ul>
<h3 id="analyzer의-토큰-생성-과정">Analyzer의 토큰 생성 과정</h3>
<ul>
<li>문자열 입력</li>
<li>character filter : 특수문자 제거 등 필터링 과정</li>
<li>tokenizer : 공백 등 특정 기준을 바탕으로 tokenize</li>
<li>token filter : 대소문자 처리 등 추가 필터링</li>
</ul>
<blockquote>
<h4 id="주의-사항">주의 사항</h4>
</blockquote>
<ul>
<li>색인 : primary shard에서만 가능</li>
<li>검색 : primary &amp; replica 둘 다 가능</li>
</ul>
<hr>
<h3 id="text와-keyword">text와 keyword</h3>
<ul>
<li>text : 전문 검색(Full-text search)을 위해 토큰 생성<ul>
<li>ex) &quot;Hello world&quot; 에 대해 &quot;Hello&quot;, &quot;World&quot;로 두 개 토큰 생성</li>
</ul>
</li>
<li>keyword : Exact Matching<ul>
<li>ex) &quot;Hello world&quot; 에 대해 &quot;Hello world&quot;라는 하나의 토큰 생성</li>
<li>쪼개는 과정이 없어 색인 속도가 빠름</li>
</ul>
</li>
</ul>
<p>문자열 필드는 동적 매핑되면 keyword, text 타입 두 개 모두 생성</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ElasticSearch 정리(1)]]></title>
            <link>https://velog.io/@jihoon-gh/Elastic-Search-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jihoon-gh/Elastic-Search-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 06 Nov 2024 08:11:42 GMT</pubDate>
            <description><![CDATA[<h2 id="elastic-search">Elastic Search</h2>
<ul>
<li>lucene 기반 오픈소스 검색 엔진
(json 기반 문서 저장, 검색 및 분석)</li>
</ul>
<h3 id="elastic-search-특징">Elastic Search 특징</h3>
<ul>
<li>near real time 검색</li>
<li>클러스터 구성<ul>
<li>한 대 이상의 노드로 클러스터 구성 - 부하 분산, 장애 대응</li>
</ul>
</li>
<li>동적 스키마 생성 - 입력 데이터에 대해 스키마 생성해 줌<ul>
<li>정적 스키마도 지정 가능</li>
</ul>
</li>
<li>RestAPI 기반 인터페이스</li>
</ul>
<h3 id="클러스터와-노드">클러스터와 노드</h3>
<ul>
<li>클러스터 : 노드 여러개 모여서 이룬 군집</li>
<li>노드 종류<ul>
<li>마스터 노드 : <ul>
<li>클러스터 상태, 메타데이터 관리</li>
<li>마스터 노드와 마스터 후보 노드로 구성</li>
</ul>
</li>
<li>데이터 노드 : 문서 색인 및 검색 처리 </li>
<li>코디네이팅 노드 : 검색 처리</li>
<li>인제스트 노드 : 색인(저장)되는 문서의 데이터 전처리</li>
</ul>
</li>
<li>클러스터이므로 어떤 노드에 어떤 요청을 해도 응답은 동일</li>
<li>하지만 더 최적화된 작업을 할 수 있게(본인 역할에 충실하게) 구성해야 함</li>
</ul>
<h3 id="인덱스와-샤드">인덱스와 샤드</h3>
<ul>
<li><p>인덱스 : 문서가 저장되는 논리적인 공간</p>
<ul>
<li>인덱스 설계 (하나의 큰 인덱스 vs 여러개로 쪼갠 인덱스) 중요<ul>
<li>하나의 큰 인덱스 : 관리 리소스 적게 발생 but 쿼리와 문서가 복잡해질 가능성</li>
<li>여러개로 쪼갠 인덱스 : 각각의 경우에 대해 최적화된 쿼리 but 관리 어려움 증대</li>
</ul>
</li>
<li>추천 -&gt; 하나의 인덱스로 단순하게 시작하고 경우에 따라 쪼개기</li>
</ul>
</li>
<li><p>샤드 : 인덱스에 색인되는 문서가 저장되는 공간</p>
<ul>
<li><p>하나의 인덱스는 반드시 하나 이상의 샤드 갖음</p>
</li>
<li><p>샤드 종류</p>
<ul>
<li><p>프라이머리 샤드 : 문서 저장되는 원본 샤드, 색인 및 검색 성능에 모두 영향</p>
</li>
<li><p>레플리카 샤드 : 프라이머리 샤드의 복제 샤드로써 검색에 영향</p>
<pre><code>프라이머리 샤드에 문제가 생기면 프라이머리 샤드로 승격함</code></pre></li>
<li><p>문서들은 모든 샤드에 고르게 저장됨</p>
</li>
<li><p>인덱스 생성 이후 프라이머리 샤드 갯수는 변경 불가 - 신중하게 설정</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[외워서 끝내는 네트워크 핵심이론 - 기초 완강]]></title>
            <link>https://velog.io/@jihoon-gh/%EC%99%B8%EC%9B%8C%EC%84%9C-%EB%81%9D%EB%82%B4%EB%8A%94-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%95%B5%EC%8B%AC%EC%9D%B4%EB%A1%A0-%EA%B8%B0%EC%B4%88-%EC%99%84%EA%B0%95</link>
            <guid>https://velog.io/@jihoon-gh/%EC%99%B8%EC%9B%8C%EC%84%9C-%EB%81%9D%EB%82%B4%EB%8A%94-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%95%B5%EC%8B%AC%EC%9D%B4%EB%A1%A0-%EA%B8%B0%EC%B4%88-%EC%99%84%EA%B0%95</guid>
            <pubDate>Sat, 12 Oct 2024 12:20:52 GMT</pubDate>
            <description><![CDATA[<p>CS의 경우에는 꾸준히 하지 않으면 완전히는 아니어도 좀 기억이 안나고 하는 부분이 있어서
웬만하면 꾸준히 Remind 하는것이 중요하다고 생각하는데
네트워크를 어떻게 할까, 어떤 서적을 살까 하다가
추석에 진행한 인프런 할인에서 강의를 사서 들어보았다.</p>
<p>전반적으로 네트워크 흐름을 생각하기에 좋은 강의였다.
디테일하게 채우긴 쉽지 않지만, 각 Layer 마다 특징적인 부분들 등에 대해서는
잘 학습할 수 있는 좋은 강의였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[토비님의 클린 스프링을 보고]]></title>
            <link>https://velog.io/@jihoon-gh/%ED%86%A0%EB%B9%84%EB%8B%98%EC%9D%98-%ED%81%B4%EB%A6%B0-%EC%8A%A4%ED%94%84%EB%A7%81%EC%9D%84-%EB%B3%B4%EA%B3%A0</link>
            <guid>https://velog.io/@jihoon-gh/%ED%86%A0%EB%B9%84%EB%8B%98%EC%9D%98-%ED%81%B4%EB%A6%B0-%EC%8A%A4%ED%94%84%EB%A7%81%EC%9D%84-%EB%B3%B4%EA%B3%A0</guid>
            <pubDate>Wed, 09 Oct 2024 14:29:56 GMT</pubDate>
            <description><![CDATA[<p>오늘은 인프콘 2024 영상을 보다가 <a href="https://www.youtube.com/watch?v=d3krJ4el8Hg">토비님의 클린 스프링</a> 영상을 봤다.</p>
<p>영상에서의 핵심은 결국</p>
<h3 id="clean-code-that-works">Clean code that works</h3>
<p>였던 것 같다. </p>
<p>사실 회사에서의 업무 자체도 클린 코드랑 거리가 멀기도 하고, 그 가치를 알아주는 사람이 많이 없어서
여러모로 아쉬움을 느끼고 있기는 하다.</p>
<p>그래도 그 가치를 나 혼자라도 지켜가야지 싶기는 하다.</p>
<p>강의에서 다뤘던 테스트코드 관련 부분도 인상깊다.
테스트코드를 짜면서 개발하는게 분명 초반에는 생산성에서 약간 이슈가 될 수 있는데
이 부분을 어떻게 해결하는가? 에 대한 명쾌한 해답</p>
<h3 id="테스트-코드를-최대한-빨리-짜자">테스트 코드를 최대한 빨리 짜자!</h3>
<p>뭔가 굉장히 심플하고 웃음이 나지만, 이것보다 명쾌한 해답이 없을 것 같다.
테스트 코드를 짜는 능력을 더 키워야겠다.</p>
<p>이 두 내용이 가장 인상깊었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[테스트 작성 관련 내용 정리]]></title>
            <link>https://velog.io/@jihoon-gh/%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%91%EC%84%B1-%EA%B4%80%EB%A0%A8-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jihoon-gh/%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9E%91%EC%84%B1-%EA%B4%80%EB%A0%A8-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 16 Sep 2024 09:49:34 GMT</pubDate>
            <description><![CDATA[<ol>
<li><p>한 문단에 한 주제</p>
<blockquote>
<ul>
<li>if, for 등 논리 구조가 들어갈 경우 여러 주제가 포함될 확률이 높고, test를 읽는 사람이 자연스럽게 읽기 힘듦
(생각을 하면서 읽어야 됨)</li>
<li>테스트가 @DisplayName에서 한 문장으로 설명될 수 있는가?</li>
</ul>
</blockquote>
</li>
<li><p>완벽하게 제어하기</p>
<blockquote>
<ul>
<li>제어할 수 없는 변수(ex, LocalDateTime.now()) 는 상위 클래스에서 주입받는걸로 변경</li>
<li>외부 시스템에 대해 Mocking 처리를 하고 테스트 구성을 하자</li>
</ul>
</blockquote>
</li>
<li><p>테스트 환경의 독립성 보장</p>
<blockquote>
<ul>
<li>테스트가 깨져도 when, then 절에서 깨져야 하는데, 테스트 구성을 하는 given 절에서 실패하면 논리적으로 맞지 않음</li>
</ul>
</blockquote>
</li>
<li><p>각 테스트 사이의 독립성 보장</p>
<blockquote>
<ul>
<li>언제 수행되었든 항상 같은 결과 보장</li>
<li>테스트 수행 환경을 항상 동일하게</li>
<li>공유자원 사용 배제</li>
</ul>
</blockquote>
</li>
<li><p>한 눈에 들어오는 Text Fixture
Text Fixture -&gt; 테스트를 위해 원하는 상태로 고정시킨 객체</p>
<blockquote>
<ul>
<li>setUp 같은 함수 사용하면 모든 Test에 영향을 미침</li>
<li>BeforeEach 등 함수는 각 테스트 입장에서 해당 메서드 내부를 모르고, 수정해도 아무 변화가 없다면 사용 가능</li>
</ul>
</blockquote>
</li>
<li><p>Text Fixture 클렌징</p>
<blockquote>
<ul>
<li>deleteAll과 deleteAllInBatch의 차이
 -&gt; deleteAll은 전체를 Select해서 건 당 delete로 지워줘서 쿼리가 길어짐
 -&gt; 다만 매핑된 다른 테이블도 다 건 당 delete로 지줌
 ** -&gt; deleteAll과 deleteAllInBatch의 성능 차이가 발생**   </li>
<li>지울때도 순서 따라서 잘 지워야 함 -&gt; 외래키 제약 조건 방지 </li>
</ul>
</blockquote>
</li>
<li><p>ParamiterizedTest</p>
<blockquote>
<ul>
<li>@ParamiterizedTest를 통해 여러 case에 대해 test를 한 번에 수행</li>
</ul>
</blockquote>
</li>
</ol>
<p>예시코드</p>
<pre><code class="language-java">@DisplayName(&quot;상품 타입이 재고 타입인지 체크한다.&quot;)
@CsvSource({&quot;HANDMADE,false&quot;, &quot;BOTTLE,true&quot;, &quot;BAKERY,true&quot;})
@ParameterizedTest
void containsStockType3(ProductType productType, boolean expected){
    //given
    //when
    boolean result = ProductType.containsStockType(productType);
    //then
    assertThat(result).isEqualTo(expected);
}</code></pre>
<ol start="8">
<li>DynamicTest<blockquote>
<ul>
<li>@TestFactory를 사용하고, 아래에서 여러 행동을 결합하여 특정 시나리오대로 테스트 진행</li>
</ul>
</blockquote>
</li>
</ol>
<p>예시코드</p>
<pre><code class="language-java">@DisplayName(&quot;재고 차감 시나리오&quot;)
@TestFactory
Collection&lt;DynamicTest&gt; stockDeductionDynamicTest(){

    //given
    Stock stock = Stock.create(&quot;001&quot;, 1);

    return List.of(
             DynamicTest.dynamicTest(&quot;재고를 주어진 개수만큼 차감할 수 있다.&quot;, () -&gt; {
                //given
                int quantity = 1;

                //when
                stock.deductQuantity(quantity);

                //then
                assertThat(stock.getQuantity()).isZero();
            }),
            DynamicTest.dynamicTest(&quot;재고보다 많은 수의 수량으로 차감 시도하는 경우 예외가 발생한다.&quot;, () -&gt; {
                //given
                int quantity = 1;

                //when //then
                assertThatThrownBy(() -&gt; stock.deductQuantity(quantity))
                        .isInstanceOf(IllegalArgumentException.class)
                        .hasMessage(&quot;차감할 재고 수량이 없습니다.&quot;);
            })
    );
}</code></pre>
<ol start="9">
<li><p>테스트 환경 통일</p>
<blockquote>
<ul>
<li>ActiveProfile 차이 등으로 인해 테스트시 SpringBoot 서버가 새로 뜸. 이는 자원 낭비</li>
<li>환경을 통합하는 부모 클래스를 만들고, 이를 상속하게 하면서 이러한 테스트 시 서버 재가동 줄일 수 있음</li>
<li>Mock의 경우에는 또 다른 환경이므로 이에 대핸 구분 필요 (Mock을 사용하는 class끼리만 통합)</li>
<li>Presentation 계층의 경우 @SpringBootTest가 아닌 @WebMvcTest 사용하므로 통합 어려움, 같은 계층끼리만 통합</li>
</ul>
</blockquote>
</li>
<li><p>private 메서드의 테스트</p>
<blockquote>
<ul>
<li>할 필요 없음</li>
<li>외부(호출하는 입장)에서는 private method는 알 필요가 없음</li>
<li>private method를 사용하는 쪽에서 test를 진행하면 자연럽게 해당 private method의 검증도 되는 구조</li>
<li>계속 이러한 필요가 생각난다면, 오히려 클래스의 책임 분리에 대해 생각할 떄 일지도..</li>
</ul>
</blockquote>
</li>
<li><p>production에서는 필요 없고 test에만 필요한 코드라면?</p>
<blockquote>
<ul>
<li>만들어도 되지만 보수적으로 접근</li>
<li>정말 꼭 필요한 경우에만 작성하자</li>
</ul>
</blockquote>
</li>
</ol>
<p>츨처 - <a href="https://www.inflearn.com/course/practical-testing-%EC%8B%A4%EC%9A%A9%EC%A0%81%EC%9D%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9D%B4%EB%93%9C/dashboard">https://www.inflearn.com/course/practical-testing-%EC%8B%A4%EC%9A%A9%EC%A0%81%EC%9D%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9D%B4%EB%93%9C/dashboard</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Mock과 Stub의 차이]]></title>
            <link>https://velog.io/@jihoon-gh/Mock%EA%B3%BC-Stub%EC%9D%98-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@jihoon-gh/Mock%EA%B3%BC-Stub%EC%9D%98-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Fri, 13 Sep 2024 13:14:17 GMT</pubDate>
            <description><![CDATA[<p><a href="https://martinfowler.com/articles/mocksArentStubs.html">Mocks aren&#39;t Stubs</a></p>
<ul>
<li><p>Stub
상태 검증</p>
</li>
<li><p>Mock
행위 검증</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Apache Spark study(3)]]></title>
            <link>https://velog.io/@jihoon-gh/Apache-Spark-study3</link>
            <guid>https://velog.io/@jihoon-gh/Apache-Spark-study3</guid>
            <pubDate>Fri, 29 Dec 2023 05:30:40 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jihoon-gh/post/aa72bf78-897d-4230-bfd1-d3fb1f89569d/image.png" alt=""></p>
<hr>
<h2 id="spark-rdd">Spark RDD</h2>
<p>RDD는 Resilient Distributed Dataset의 줄임말로 분산 데이터 모델이다.
RDD는 <strong>의존성, 파티션, 연산 함수</strong>라는 세 가지 특성이 있다.</p>
<h3 id="1-의존성">1. 의존성</h3>
<p>어떤 입력을 필요로 하고 현재의 RDD가 어떻게 만들어지는지 스파크에게 가르쳐 주는 것을 의미
결과를 새로 만들어야 할 경우 스파크는 의존성 정보를 참고하고 연산을 다시 반복하여 RDD 다시 만듦
이를 통해 RDD에 유연성을 부여함</p>
<h3 id="2-파티션">2. 파티션</h3>
<p>스파크에게 작업을 나눠서 이그제큐터들에게 분산해 파티션별로 병렬 연산을 할 수 있게 함
이를 통해, 작업의 효율성 증진시킴</p>
<h3 id="3-연산-함수">3. 연산 함수</h3>
<p>RDD에 저장되는 데이터를 Iterator[T]형태로 만들어 주는 연산 함수를 가지고 있음</p>
<hr>
<h2 id="스파크의-구조-확립">스파크의 구조 확립</h2>
<p>위의 스파크의 핵심 특성들에는 연산 함수 및 연산식의 불투명성 등과 같은 문제가 있었음</p>
<p>이를 해결하기 위해, 스파크 2.x부터 여러 개념을 도입함</p>
<ul>
<li>데이터 분석을 통해 찾은 일상적인 패턴들을 사용하여 연산을 표현(필터링, 선택, 집합연산 등)</li>
<li>DSL에서 일반적인 연산 집합 사용</li>
<li>지원하는 정형화 데이터 타입을 사용하여 데이터를 표 형태로 구성할 수 있게 됨</li>
</ul>
<p>이렇게, 스파크는 <strong>구조 확립을 통해 더 나은 성능, 공간 효율성 및 명료한 표현 등 이득을 얻음</strong>
ex) 스파크에게 직접 연산을 명령하기 vs 무엇을 할지(작업 자체)를 명령하기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Apache Spark study (2)]]></title>
            <link>https://velog.io/@jihoon-gh/Apache-Spark-study-2</link>
            <guid>https://velog.io/@jihoon-gh/Apache-Spark-study-2</guid>
            <pubDate>Tue, 26 Dec 2023 12:15:15 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jihoon-gh/post/d69910e2-239a-4ba3-b9a6-abd0d6aadaf6/image.png" alt=""></p>
<h3 id="spark에서의-분산-데이터와-파티션">Spark에서의 분산 데이터와 파티션</h3>
<p>스파크에서 실제 물리적인 데이터는 HDFS(HaDoop File System)나 클라우드 저장소에 존재하는 파티션이 되어 저장소 전체에 분산된다.
데이터가 파티션이 되어 물리적으로 분산되며, 스파크는 각 파티션을 <strong>메모리의 데이터 프레임 객체</strong>로 바라본다.
각 스파크 이그제큐터는 데이터 지역성을 고려하여 네트워크에서 가장 가까운 파티션을 읽도록 태스크를 할당한다.
이러한 파티셔닝을 통해, 네트워크 사용을 최소화하고 효과적인 병렬 처리를 가능하게 해준다.</p>
<hr>
<h3 id="스파크-어플리케이션-작업-단위별-설명">스파크 어플리케이션 작업 단위별 설명</h3>
<ul>
<li>태스크 : 스파크 이그제큐터로 보내지는 작업 실행의 가장 기본적인 단위</li>
<li>스테이지 : 서로 의존성을 가지는 다수의 태스크 모음</li>
<li>잡(job) : 스파크 액션에 대한 응답으로 생성되는 병렬 연산. 스테이지의 모음.</li>
</ul>
<hr>
<h3 id="스파크-연산의-종류">스파크 연산의 종류</h3>
<ul>
<li><h4 id="트랜스포메이션transformation">트랜스포메이션(transformation)</h4>
<p>이미 불변성의 특징을 가진 원본 데이터를 수정하지 않고 하나의 스파크 데이터 프레임을 새로운 데이터 프레임으로 변형하는 연산 (ex: select(), filter() 등)
트랜스포메이션의 결과는 즉시 계산되는게 아니라 계보(lineage)라 불리는 형태로 기록됨(지연평가)
기록된 리니지는 실행 계획의 후반에 확실한 트랜스포메이션들끼리 재배열 및 결합 등을 통해 최적화</p>
</li>
<li><h4 id="액션action">액션(action)</h4>
<p>모든 기록된 트랜스포메이션의 지연 평가를 발동시킴. 즉 액션 a가 호출되면 그 전까지 기록된 모든 트랜스포메이션이 실제로 실행되며 데이터에 접근함
(ex, show(), take() 등)</p>
</li>
</ul>
<blockquote>
<p>지연 평가를 통해 쿼리의 최적화를 가능하게 하며, 리니지와 데이터 불변성을 통해 데이터 내구성 제공</p>
</blockquote>
<hr>
<h3 id="스파크-연산---트랜스포메이션의-종류">스파크 연산 - 트랜스포메이션의 종류</h3>
<ul>
<li><p>좁은 의존성
하나의 입력 파티션을 연산하여 하나의 결과 파티션을 내놓는 트랜스포메이션</p>
</li>
<li><p>넓은 의존성
groupBy()나 orderBy()를 쓸 경우, 다른 파티션으로부터 데이터를 읽어 들이고 디스크에 쓰는 작업 등</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Apache Spark study (1)]]></title>
            <link>https://velog.io/@jihoon-gh/Apache-Spark-study-1</link>
            <guid>https://velog.io/@jihoon-gh/Apache-Spark-study-1</guid>
            <pubDate>Sat, 23 Dec 2023 12:00:06 GMT</pubDate>
            <description><![CDATA[<h3 id="apache-spark란">Apache Spark란?</h3>
<p><img src="https://velog.velcdn.com/images/jihoon-gh/post/8aaacce5-1231-4eb1-afc5-cf6c653db464/image.png" alt="">아파치 스파크는 데이터 센터나 클라우드에서 대규모 분산 데이터 처리를 하기 위해 설계된 통합형 엔진임</p>
<hr>
<h2 id="apache-spark의-핵심-특징-4가지">Apache Spark의 핵심 특징 4가지</h2>
<h3 id="1-속도">1. 속도</h3>
<ul>
<li>중간 연산을 위한 메모리 저장소를 지원하여 Hadoop Map-Reduce보다 빠름</li>
<li>또한, 질의 연산을 DAG(Directed Acyclic Graph)로 구성하고, DAG 스케쥴러 및 쿼리 최적화 모듈은 효율적인 연산 그래프를 만들고, 클러스터의 워커 노드에서 병렬 수행될 수 있게 함</li>
<li>물리엔진 텅스텐(Tungsten)은 whole-stage code generation 기법을 사용하여 실행을 위한 간결한 코드를 생성함</li>
<li>디스크 I/O의 제한적 사용</li>
</ul>
<h3 id="2-사용-편리성">2. 사용 편리성</h3>
<ul>
<li>스파크는 데이터 프레임이나 데이터세트 같은 고수준 데이터 추상화 계층 아래 유연한 분산 데이터 세트 <strong>RDD</strong>(Resilent Distributed Dataset)이라는 자료구조 구축. 단순성 실현함</li>
<li>연산(Operation)의 종류로 트랜스포메이션(Transformation)과 액션(Action) 두 가지만 가지고 프로그래밍 할 수 있게 해서 단순성 실현</li>
</ul>
<h3 id="3-모듈성">3. 모듈성</h3>
<ul>
<li>스파크 연산은 다양한 타입의 워크로드에 적용 가능하며, 지원하는 프로그래밍 언어(Scala, Python, Java, R)로 표현 가능</li>
<li>통합 라이브러리와 여러 컴포넌트들을 제공하고 이를 하나의 엔진 안에서 연동할 수 있음</li>
</ul>
<h3 id="4-확장성">4. 확장성</h3>
<ul>
<li>스파크는 저장보다는 빠른 병렬 연산 엔진에 초점이 맞추어져 있음. (이 점이 저장과 연산을 모두 포함하는 Apache Hadoop과의 차이) 이는 스파크가 수많은 데이터 소스에서 데이터를 읽고, 이를 메모리에서 처리 가능하다는 의미</li>
</ul>
<h3 id="그-외-특징">그 외 특징</h3>
<ul>
<li>스파크는 JVM 위에서 동작. 즉 어떤 언어로 작성하든 실제 코드는 고도로 경량화된 바이트코드로 변환됨</li>
<li>스파크 SQL은 ANSI SQL 2003 표준과 호환. </li>
<li>데이터 과학을 위한 MLlib 존재</li>
<li>그래프를 조작하고 그래프 병렬 연산을 수행하게 해주는 GraphX 라이브러리 존재</li>
</ul>
<hr>
<h3 id="아파치-스파크의-구성">아파치 스파크의 구성</h3>
<ul>
<li>하나의 스파크 애플리케이션은 하나의 드라이버 프로그램으로 구성. 이 드라이버는 Spark Driver임. Spark Driver는 Spark Session을 생성하고,이것이 스파크 클러스터의 분산 컴포넌트들에 접근함</li>
</ul>
<br>

<p><strong>스파크 드라이버(Spark Driver)</strong> :</p>
<ul>
<li>Spark Session 객체 초기화</li>
<li>Cluser Manager와 통신하며 스파크 이그제큐터들을 위해 필요한 자원 요청</li>
<li>모든 스파크 작업을 DAG 연산 형태로 변환 및 스케줄링하여 각 실행 단위를 태스크로 나눔</li>
<li>나눈 태스크들을 스파크 이그제큐터에 분배</li>
</ul>
<br>

<p><strong>스파크 세션(Spark Session)</strong> : </p>
<ul>
<li>모든 스파크 기능을 한 군데에서 접근할 수 있는 시작점 제공</li>
</ul>
<br>

<p><strong>클러스터 매니저(Cluser Manager)</strong> : </p>
<ul>
<li>스파크 애플리케이션이 실행되는 클러스터에서 자원을 관리 및 할당하는 책임
현재 네 종류의 클러스터 매니저(standalne, hadoop YARN, Mesos, K8s) 지원</li>
</ul>
<br>

<p><strong>스파크 이규제큐터(Spark Executor)</strong> : </p>
<ul>
<li>클러스터의 각 워커 노드에서 동작. 드라이버의 프로그램과 통신하며 워커에서 태스크를 실행하는 역할 
(대부분 배포 모드에서 노드 당 하나의 이그제큐터만 실행)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[이벤트 소싱 패턴]]></title>
            <link>https://velog.io/@jihoon-gh/Event-Sourcing-Pattern</link>
            <guid>https://velog.io/@jihoon-gh/Event-Sourcing-Pattern</guid>
            <pubDate>Sat, 18 Nov 2023 09:38:40 GMT</pubDate>
            <description><![CDATA[<h3 id="event-sourcing-pattern">Event Sourcing Pattern</h3>
<p>일반적인 어플리케이션에서 
데이터베이스의 데이터는 비즈니스 상황을 반영함.
CRUD가 지속적으로 일어나고, 데이터의 변경이 일어날 것이며 이렇게 데이터가 변경될 경우 이전 데이터는 백업이 있고 하겠지만 비즈니스 상 크게 중요하지 않음(현재의 데이터가 중요하니까)</p>
<p>하지만, 특정 몇 몇 경우에는 이전의 상황이 분명히 필요함
(ex: 은행 잔고의 경우 x일 전에 y원만큼 변경되어 z원의 잔고 존재라는 정보가 중요할것임)</p>
<p>이 때, 필요한것이 <strong>Event Sourcing Pattern</strong></p>
<hr>
<p>이벤트소싱 패턴은 
현재 상태는 저장하지 않고 이벤트만 저장함
각 이벤트는 시스템 내 특정 엔티티에 대한 변경 사항과 사실 정보로 구성됨</p>
<p>이벤트의 가장 큰 특징은 <strong>불변성(immutable)</strong>
데이터에 대한 VCS라고 생각하면 유사</p>
<hr>
<p>이벤트 소싱 패턴의 장점</p>
<ul>
<li>시각화</li>
<li>감사</li>
<li>검증</li>
<li>높은 쓰기 성능</li>
</ul>
<hr>
<p>이벤트 소싱 패턴에서 이벤트를 저장하고 표현하는 방식</p>
<ul>
<li>데이터베이스에서 각 이벤트를 개별 레코드로 저장</li>
<li>메세지 브로커를 통한 이벤트 저장</li>
<li><blockquote>
<p> 대부분의 DB와 달리 다량의 이벤트를 처리하는데 최적화</p>
</blockquote>
</li>
<li><blockquote>
<p> 이벤트 스트림에서 복잡한 쿼리를 처리하기는 힘듦</p>
</blockquote>
</li>
</ul>
<hr>
<p>맨 위의 은행 잔고 예시에서, 이벤트 소싱 패턴을 적용했다면?
잔고를 저장하는게 아니라 거래 내역을 저장하여 이를 통해 계산하는 방식으로 데이터를 얻어냄</p>
<p>이벤트 소싱 패턴에서 모든 이벤트를 저장해놨다가, 특정 시점에서의 값을 구하기위해 
처음부터 이벤트를 쭉 읽어내려오며 계산하는것은 사실 좀 비효율적임</p>
<p>이를 해결하기 위한 방법들이 존재</p>
<ul>
<li>특정 시점의 snapshot 만들기</li>
<li><blockquote>
<p>스냅샷 이후부터 계산하면 됨</p>
</blockquote>
</li>
<li>CQRS 패턴 적용</li>
<li><blockquote>
<p>명령과 쿼리를 분리하여 쿼리 서비스에게 조회만 시키면서 조회 성능을 향상시킴. 
(업계에서 많이 쓰이는 방식, 물론 최종적 일관성만 보장된다는 단점이 있기 때문에 주의해야 함)</p>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[손상 방지 계층 패턴]]></title>
            <link>https://velog.io/@jihoon-gh/%EC%86%90%EC%83%81-%EB%B0%A9%EC%A7%80-%EA%B3%84%EC%B8%B5-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@jihoon-gh/%EC%86%90%EC%83%81-%EB%B0%A9%EC%A7%80-%EA%B3%84%EC%B8%B5-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Fri, 17 Nov 2023 14:47:18 GMT</pubDate>
            <description><![CDATA[<h3 id="anti-corruption-adapterlayer-pattern">Anti-Corruption Adapter/Layer Pattern</h3>
<hr>
<p>마이그레이션 상황
오래된 모놀리식 어플리케이션을 마이크로서비스로 마이그레이션하려 함</p>
<p>보통 이런 상황에서는</p>
<ul>
<li>시스템 및 아키텍쳐 최신화</li>
<li>어플리케이션 현대화</li>
</ul>
<p>이 둘 모두를 한꺼번에 하려고 할 것임</p>
<p>하지만 마이그레이션을 진행한다고 해서 모든 개발을 중단할 순 없음. 
즉 운영중인 서비스는 계속 고도화 및 확장 등이 일어날것임</p>
<p>이렇게 될 경우, 하나씩 마이크로서비스를 만들어가면서 현재 돌아가는 모놀리식 어플리케이션과 연동시킬거임 (마이크로서비스가 전부 모놀리식 어플리케이션을 대체할 때 까지)</p>
<p>하지만, 한번에 완전히 전환하지 않으면 마이크로서비스들이 저 현대화 전 레거시 어플리케이션에 의존하게됨(API, Data 등에 대해서)
이러면 어플리케이션 현대화의 의미가 퇴색됨</p>
<hr>
<p>이러한 상황을 방지하기 위해
<strong>손상 방지 계층 패턴</strong>이 존재함</p>
<p>이 패턴은 이전 시스템과 새 시스템의 어댑터 기능을 하는 서비스를 통해 새로 작성한 마이크로서비스와 모놀리식(레거시) 어플리케이션이 직접 연결되는것을 방지함</p>
<p>서로 통신을 할 때 무조건 손상 방지 계층을 지나게 함으로써, 이 둘 간 의존성을 없에게 되는게 핵심</p>
<hr>
<p>일반적으론 마이그레이션 완료 후에 어댑터를 없엘거임
하지만 마이그레이션 이후에도 레거시 코드는 남아있을 수 있음.
(완전히 레거시 코드를 제거할 수 없는 상황도 존재할 수 있음)</p>
<p>이런 상황에서, 손상 방지 계층을 남긴다면, 이를 통해 레거시 시스템과 새로운 시스템이 통신하게 하여 새로운 시스템의 손상을 방지함</p>
<hr>
<p>만능은 아님. 생각해 볼 요소들이 있음</p>
<ol>
<li><p>손상 방지 어댑터 또한 개발, 테스트 ,배포되어야 하는 서비스임
개발해야 할 요소가 증가함</p>
</li>
<li><p>손상 방지 계층의 존재 자체가 통신에 하나의 계층이 추가되는것을 의미
이는 즉 추가적 오버헤드의 발생을 의미함</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[사이드카 패턴 & 앰배서더 패턴]]></title>
            <link>https://velog.io/@jihoon-gh/Sidecar-pattern-Ambassador-pattern</link>
            <guid>https://velog.io/@jihoon-gh/Sidecar-pattern-Ambassador-pattern</guid>
            <pubDate>Fri, 17 Nov 2023 14:21:00 GMT</pubDate>
            <description><![CDATA[<h2 id="sidecar-pattern">Sidecar pattern</h2>
<p>모든 서비스에는 핵심 기능이 있음(서비스의 존재 이유)
ex) 고객 서비스 : 고객 정보 저장
ex) 결제 서비스 : 결제 내역 저장 및 결제 처리 등</p>
<p>하지만, 각 서비는 핵심 기능 외에 다른 기능도 해야함
예를 들면 metric을 모니터링 서비스에 전송한다던지..
구성 파일을 가져와 파싱하고 비지니스 로직에 적용한다던지..</p>
<br>
MSA에서는 각기 다른 프로그래밍 언어를 사용할 때가 있는데
이럴 때는 같은 기능을 구현하기 위해 각기 언어에 맞는 다른 Library를 사용해야 함 (확장성이 낮음)

<p>동일한 라이브러리를 다른 언어로 구현하는것은 상호 호환성과 일관성을 떨어뜨림 (데이터 유형 등 차이로 다양한 문제 발생 가능)</p>
<p>이러한 문제를 해결하는것이 <strong>Sidecar pattern</strong></p>
<p>애플리케이션에 필요한 추가 기능을 메인 애플리케잇녀과 동일한 서버에 개별 프로세스 (혹은 컨테이너)로 실행
사이드카 프로세스와 메인 프로세스를 분리하는것(같은 서버에서)</p>
<p>개별 프로세스로 사이드카 프로세스를 만들면, 하나의 언어로 사이드카를 만들고 이를 재활용할 수 있음</p>
<p>핵심 서비스의 구현 및 테스트에 집중할 수 있음
(공통 기능 및 기타 기능을 사이드카에 몰아놨으니까)</p>
<br>

<h3 id="ambassador특사-pattern">Ambassador(특사) pattern</h3>
<p>ambassador pattern은 sidecar pattern의 special case임</p>
<p>ex)서비스를 대신해 모든 네트워크 활동을 전송하는 사이드카
이 경우 서비스 책임 범위 밖으로 복잡한 네트워크 통신 로직을 빼낼 수 있음</p>
<p>-&gt; 이렇게하면 진짜 핵심 비즈니스 로직만 애플리케이션 프로세스에 남게 됨</p>
<p>모든 서비스의 통신이 같은 위치에 있는 특사 사이드카에서 이루어지므로 쉽게 네트워크 통신 실행 및 분산 추적 가능</p>
<blockquote>
<p>결국 핵심은 공통 기능과 핵심 기능의 분리를 코드 레벨이 아닌 
아키텍쳐 레벨에서 구현한 것이라 볼 수 있을 것 같다.
(약간 Spring AOP 생각난다.)</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[CQRS pattern]]></title>
            <link>https://velog.io/@jihoon-gh/CQRS-pattern</link>
            <guid>https://velog.io/@jihoon-gh/CQRS-pattern</guid>
            <pubDate>Sat, 11 Nov 2023 13:45:25 GMT</pubDate>
            <description><![CDATA[<h3 id="cqrs---명령과-쿼리의-책임-분리">CQRS - 명령과 쿼리의 책임 분리</h3>
<h4 id="command-and-query-responsibility-segregation">Command and Query Responsibility Segregation</h4>
<p>데이터를 데이타베이스에 저장하는 일반적 시스템은
데이터에 대한 작업을 두 유형으로 나눔</p>
<ul>
<li><p>명령 -&gt; DB의 데이터를 변경시킴(추가, 수정 및 삭제)
(DB에 레코드 삽입, 업데이트 및 삭제 등 변경 발생)</p>
</li>
<li><p>쿼리 -&gt; DB의 데이터를 조회함
(DB 데이터의 변경 없음)</p>
</li>
</ul>
<p>CQRS는 시스템의 명령 파트와 쿼리 파트를 명확히 분리함
서비스를 (1)명령 서비스, (2) 쿼리 서비스 라는 두 가지 서비스로 분리하고, 각자 DB를 가지게 함. 
이를 통해, 쿼리 서비스(조회)를 고성능으로 유지할 수 있음.</p>
<blockquote>
<p>새로운 비즈니스 로직이 추가되어도 명령 서비스에만 변경이 일어나고, 쿼리 서비스는 변경되지 않으므로 테스트하거나 배포할 필요가 없음</p>
</blockquote>
<h3 id="cqrs-pattern을-적용할-시-장점">CQRS Pattern을 적용할 시 장점:</h3>
<ul>
<li><strong>시스템을 명령, 쿼리 둘 모두에 대해 최적화 할 수 있다.</strong></li>
<li><blockquote>
<p>그러므로, 읽기와 쓰기 모두 빈번한 시스템에서 특히 중요하다</p>
</blockquote>
</li>
<li><strong>높은 확장성을 가지게 된다.</strong><br>

</li>
</ul>
<h3 id="cqrs-pattern-적용-시-발생할-수-있는-문제점">CQRS Pattern 적용 시 발생할 수 있는 문제점</h3>
<ul>
<li><strong>최종적 일관성만 보장됨. 엄격한 일관성이 필요할 경우 적합하지 않음</strong></li>
<li><blockquote>
<p>작업 과정 중간에 명령 서비스 DB와 쿼리 서비스 DB가 다른 상황이 존재할 수 있음</p>
</blockquote>
</li>
<li><strong>시스템에 오버헤드와 복잡성을 추가시킴</strong></li>
<li><blockquote>
<p>CQRS를 통해 얻는 성능상의 이점이 이러한 부분보다 큰지 검증 후 사용</p>
</blockquote>
</li>
</ul>
<p>중요한 부분은, 명령 서비스에서 데이터가 수정되었을 때, 수정된 데이터를 어떻게 쿼리 서비스에 전파하여 동기화시킬 것인지
이를 위해서, 명령 파트에 데이터 변형 요청이 발생할 때 마다 쿼리 서비스가 이를 수신하게 함</br></p>
<br>

<p><strong>동기화 방법</strong></p>
<ul>
<li><p><strong>명령 서비스와 쿼리 서비스의 사이에 메세지 브로커 두기</strong></p>
</li>
<li><blockquote>
<p>트랜잭셔널 아웃박스 패턴을 적용하여 단일 트랜잭션에서 DB와 메세지 브로커 모두에 메세지 전달 </p>
</blockquote>
</li>
<li><p><strong>서비스형 함수(function as a service) 사용하기</strong></p>
</li>
<li><blockquote>
<p>서비스형 함수를 통해 명령 서비스 DB의 변경을 감지</p>
</blockquote>
</li>
</ul>
<br>



]]></description>
        </item>
        <item>
            <title><![CDATA[Materialized View pattern]]></title>
            <link>https://velog.io/@jihoon-gh/Materialized-View-pattern</link>
            <guid>https://velog.io/@jihoon-gh/Materialized-View-pattern</guid>
            <pubDate>Thu, 09 Nov 2023 08:40:34 GMT</pubDate>
            <description><![CDATA[<h2 id="구체화된-뷰materialized-view">구체화된 뷰(Materialized View)</h2>
<p>데이터 집약적인 어플리케이션이 가지는 문제들이 있음</p>
<ol>
<li><p>성능문제
여러 테이블이나 여러 데이터베이스간 걸쳐있는 복잡한 쿼리를 실행할 경우 시간도 오래 걸리게 됨</p>
</li>
<li><p>효율성과 비용
대용량 데이터 셋을 읽거나 종합 및 변환하는 복잡한 쿼리를 반복적으로 실행하면 쿼리 실행에 들어가는 리소스 낭비가 심함 </p>
</li>
</ol>
<p>이를 해결하기 위해 <strong>구체화된 뷰(Materialized View)</strong> pattern 사용</p>
<p>구체화된 뷰 패턴은 읽기 전용 테이블을 생성하여, 최적화하려는 쿼리의 결과값을 미리 채워넣음</p>
<h3 id="고려사항">고려사항</h3>
<ul>
<li><p>구체화된 뷰 테이블이 필요로하는 추가 공간을 고려해야 함</p>
<p>  -&gt; 공간과 시간의 trade-off. 평범하지만 cloud 환경에서는 비용 문제를 생각해야함</p>
</li>
<li><p>구체화된 뷰를 어디에 저장하는가?</p>
<p>  -&gt; 원본 데이터가 있는 DB의 개별 테이블에 저장하는게 일반적
  -&gt; In-memory Cache 에 저장할 수도 있음(Redis 등)</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Transactional Outbox 패턴]]></title>
            <link>https://velog.io/@jihoon-gh/Transactional-Outbox-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@jihoon-gh/Transactional-Outbox-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Thu, 09 Nov 2023 08:16:26 GMT</pubDate>
            <description><![CDATA[<h2 id="transactional-outbox">Transactional Outbox:</h2>
<p>Message broker에게 메세지를 주는 대신, DB에 Outbox table을 두고 여기에 새 record로써 메세지를 추가하게 됨
<strong>(중요 사항: 이 작업은 단일 데이터베이스 트랜잭션에서 수행됨 -&gt; atomicity가 보장됨)</strong></p>
<p>이 후, Message Relay라는 서비스를 둠. 이 서비스는 Outbox table에 record가 추가됐을 경우, 이를 가져와서 Message broker에게 전송한 후, table에서 해당 record를 삭제함</p>
<h3 id="이-패턴의-문제점">이 패턴의 문제점</h3>
<ul>
<li><p><strong>이벤트 중복의 가능성</strong>: Message Relay(Sender)가 Outbox table에서 추가된 record 형태의 메세지를 읽고 이를 Message broker에 보낸 상태에서, record를 삭제하기 전에 재시작된다면 다시 record를 Message broker에 보낼 수 있고, 이는 이벤트의 중복이 됨 <strong>(At Least Once Delivery Semantics)</strong></p>
<p>  -&gt; Message Broker 이후 Scheduling Service를 통해 멱등 조작을 실현하면 됨
  -&gt; record 마다 고유한 id를 통해 중복 여부를 검사함</p>
</li>
</ul>
  </br>
- 이벤트 순서 문제: 이벤트의 순서가 중요할 수 있다. 비즈니스 로직에 따라 순서대로 이벤트를 처리할 수 있게 해야함 (ex: 회원가입 후 회원 탈퇴 순서에서, 탈퇴부터 처리하고 가입시키면? 요구와 다르다.)

<pre><code>-&gt; 시퀀스 id를 record에 부여하고, 이 순서대로 해결</code></pre>]]></description>
        </item>
    </channel>
</rss>