<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>please_give_me_electro-brain_for_genious</title>
    <link>https://cdaosldk.tistory.com/</link>
    <description>안녕하세요, 개발 공부하며 정리한 부분 남기고 있습니다.

https://github.com/cdaosldk</description>
    <language>ko</language>
    <pubDate>Thu, 2 Jul 2026 11:24:51 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>wannaDevelopIt</managingEditor>
    <image>
      <title>please_give_me_electro-brain_for_genious</title>
      <url>https://tistory1.daumcdn.net/tistory/5707490/attach/d0d7da49f49049c791505db4930ed683</url>
      <link>https://cdaosldk.tistory.com</link>
    </image>
    <item>
      <title>RDB 설정 : SQL Mode</title>
      <link>https://cdaosldk.tistory.com/382</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;RDB에서 DB 서버가 SQL 구문을 어떻게 해석하고, 유효성 검사의 척도를 지정해줄수 있는 방법이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 많이 사용하는 mode&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) STRICT_TRANS_TABELS&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 타입이 맞지 않거나 범위를 벗어난 값을 입력할 때 에러를 발생시킨다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 꺼진 경우 : 숫자가 데이터 타입의 최대치를 벗어난 경우, 최대치만큼만 저장하고 경고한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&amp;gt; 해당 모드를 켜야 DB가 값 입력을 거부하고 rollback 한다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) ONLY_FULL_GROUP_BY&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Group by 절을 사용할 때 선택목록에 집계 함수나 Group by에 포함되지 않은 컬럼이 있는 경우 쿼리를 거부한다. SQL 표준 준수를 위한 모드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) NO_ZERO_DATE / NO_ZERO_IN_DATE&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0000-00-00 같은 유효하지 않은 날짜의 입력 허용 여부를 결정하는 모드. 일반적으로 금지하는 추세&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) ANSI_QUOTES&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 따옴표(&quot;)를 문자열 리터럴이 아닌 테이블이나 컬럼명 같은 식별자로 인식하게 한다. SQL 표준 준수를 위한 모드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 관련 쿼리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 툴에서 해당 DB에 설정된 모드를 보기 위한 쿼리&lt;/p&gt;
&lt;pre id=&quot;code_1775425868095&quot; class=&quot;autoit&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;SELECT @@sql_mode;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 세션에서 모드를 변경하는 쿼리&lt;/p&gt;
&lt;pre id=&quot;code_1775425910759&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SET SESSION sql_mode = '설정하고 싶은 모드';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;세션에서 변경하는 경우 그 세션이 종료되면 원래대로 변경된다. 영구적으로 변경을 원하는 경우 해당 DB 설정파일(my.cnf 등)을 수정해야 한다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;+ 클라우드 DB 같은 PAAS를 사용하는 경우 해당 서비스에서 모드를 수정하는 페이지를 제공하고 있으므로 그 곳에서 수정해야 한다&lt;/b&gt;&lt;/p&gt;</description>
      <category>RDB ️</category>
      <author>wannaDevelopIt</author>
      <guid isPermaLink="true">https://cdaosldk.tistory.com/382</guid>
      <comments>https://cdaosldk.tistory.com/382#entry382comment</comments>
      <pubDate>Mon, 6 Apr 2026 06:54:18 +0900</pubDate>
    </item>
    <item>
      <title>03. 26 TIL : 도커의 편리함 깨닫기</title>
      <link>https://cdaosldk.tistory.com/381</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;쉘 스크립트를 사용해 배포하는 과정에서 온갖 시행착오를 겪었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 불편하고 신경쓸게 한 두개가 아니더라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 리눅스에서 매번 권한설정하기 : sudo 지옥&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) VPC마다 다른 환경과 경로에 맞춰 설정해야 한다(그나마 단일 VPC 환경에서 작업해서 이 정도지, 여러 개로 늘리는 순간 개 헬일듯)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 실행 명령어에 환경 변수 설정이 빠졌는지를 매번 확인해야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 도커는 도커파일 또는 compose.yaml에 적어두면 신경쓸 필요가 없다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 모든 실행 환경에 필요한 언어 등을 직접 설치해야함 : &lt;b&gt;말해 뭐해, 최악&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커의 단점은..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지의 용량이 크다는 것?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;러닝 커브가 좀 있다는 것 정도인 것 같다. 정말 빡센 경험이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 이런 경험을 해야하는 이유가 있다. 왜 좋다고 하는지를 몸소 깨달을 수 있다.&lt;/p&gt;</description>
      <category>TIL, WIL/TIL </category>
      <author>wannaDevelopIt</author>
      <guid isPermaLink="true">https://cdaosldk.tistory.com/381</guid>
      <comments>https://cdaosldk.tistory.com/381#entry381comment</comments>
      <pubDate>Fri, 27 Mar 2026 06:02:30 +0900</pubDate>
    </item>
    <item>
      <title>LLM 서비스를 위한 컨텍스트 관리 w. redis</title>
      <link>https://cdaosldk.tistory.com/380</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;FastApi 프레임워크를 기반으로 한 LLM 서비스를 구현하는 도중, LLM에 쿼리를 보내는 과정에 있어,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨텍스트(대화 문맥, 흐름 ~ 여기서는 LLM이 기존에 사용자와 대화한 내용을 기억하기 위해 필요하다) 관리의 필요성&lt;/b&gt;을 느끼게 되었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 왜 Redis를 사용해야 할까?&lt;br /&gt;1. 빠른 조회 속도 : Redis는 메모리 기반 데이터베이스로서, ms 단위 응답을 보장하기 때문에, 가볍고 빠르게 조회가 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. TTL : Redis 자체적으로 제공하는 TTL 기능을 통해, 이력에 대해 일정 주기로 삭제를 진행해, 이력에 대한 용량 부담을 덜 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. (추후 구현 해볼 예정) 벡터 DB 역할에 보다 특화되어, RAG를 위한 시맨틱 캐싱에 활용된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 비교해볼만한 다른 DB&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) PostgreSQL : 이력 데이터의 영구 저장과 벡터 DB 활용이 가능. 트랜젝션의 원자성을 보장한다. Redis보다 읽기/ 쓰기가 느리다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) MongoDB : JSON 구조의 데이터를 그대로 저장할 때 유연하게 저장 가능. Redis와 비교해 지연시간이 발생한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FAST API 기반 서비스에서 구현하기&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;0. main.py&lt;/h2&gt;
&lt;pre id=&quot;code_1770783759332&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@asynccontextmanager
async def lifespan(app: FastAPI):
    pool = aioredis.ConnectionPool.from_url(REDIS_URL, decode_responses=True)
    _redis = aioredis.Redis(connection_pool=pool)
    app.state.redis_client = _redis

    try:
        prompt_res = await api_client.get_prompt_list()

        if prompt_res:
            for prompt in prompt_res:
                role_key = f&quot;{prompt['serviceType']}:role&quot;
                await _redis.set(role_key, prompt['promptRole'])

                text_key = f&quot;{prompt['serviceType']}:text&quot;
                await _redis.set(text_key, prompt['promptText'])

        yield

    finally:
        await _redis.close()
        await pool.disconnect()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 DB에 저장되어 있는 프롬프트의 값을 가져와 redis에 저장한 후, 사용자 요청 시에 redis의 프롬프트를 가져와 사용하기 위한 초기 세팅 코드다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@asynccontextmanager&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스의 생명주기에 동기화된 로직을 정의하기 위한 어노테이션이다. 이를 통해 서비스 기동시 해당 로직을 실행하고, 종료시 실행할 코드도 추가할 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;app = FastAPI(
	lifespan= lifespan
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 사용 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. context에 활용&lt;/h2&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;class class:
    def __init__(self, redis_client, api_client):
        self.redis_client = redis_client
        self.ttl = SESSION_TTL
        self.max_history = MAX_HISTORY&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1-0) 프롬프트&lt;/p&gt;
&lt;pre class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;async def get_prompt(self, service_type: str, prompt_key: str) -&amp;gt; str:
    return await self.redis_client.get(f&quot;{service_type}:{prompt_key}&quot;) or &quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 클래스에 redis를 연결해둔 다음, 밑에서 필요할때마다 호출하는 방식이다&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1-1) 컨텍스트 관리&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;async def get_context_history(self, conversation_id: str) -&amp;gt; List[Any]:
    await self._ensure_redis_loaded(conversation_id)

    chat_key = f&quot;{conversation_id}&quot;
    raw_data = await self.redis_client.lrange(chat_key, 0, -1)

    if not raw_data:
        return []

    messages = self._parse_history(raw_data)

    return messages&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;pre class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;sync def _ensure_redis_loaded(self, conversation_id: str) -&amp;gt; bool:
    &quot;&quot;&quot;Redis에 데이터가 없으면 RDB에서 로드 + 캐싱. 데이터 존재 여부 반환&quot;&quot;&quot;
    chat_key = f&quot;{conversation_id}&quot;
    exists = await self.redis_client.llen(chat_key) &amp;gt; 0

    if exists:
        return True

    rdb_history = await self._fetch_history_from_rdb(conversation_id)
    if not rdb_history:
        return False

    await self._cache_history_to_redis(chat_key, rdb_history)
    return True&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부에서 컨텍스트를 조회하고 내부에서 컨텍스트 조회 및 캐싱하는 로직으로, _ensure_redis_loaded에 각 대화의 ID를 파라미터로 해 redis에 컨텍스트가 저장되어 있는지를 조회하고, 없는 경우엔 장기 저장된 DB에서 불러와 history의 연속성을 가져올 수 있게 구현했다&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def build_user_prompt(self, context_history: List[dict], query: str) -&amp;gt; str:
    if not context_history:
        return query

    context_lines = []
    for msg in context_history:
        role = msg.get(&quot;role&quot;, &quot;user&quot;)
        content = msg.get(&quot;content&quot;, &quot;&quot;)

        label = &quot;사용자&quot; if role == &quot;user&quot; else &quot;어시스턴트&quot;
        context_lines.append(f&quot;{label}: {content}&quot;)

    context_text = &quot;\n&quot;.join(context_lines)
    return f&quot;[이전 대화]\n{context_text}\n\n[현재 입력]\n{query}&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 가져온 컨텍스트와 새로 입력된 query를 가지고 사용자와 연속된 대화를 이어갈 수 있도록 만들어주는 로직이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 통해 컨텍스트 관리를 빠르게 처리할 수 있다&lt;/p&gt;</description>
      <category>NoSQL ️</category>
      <author>wannaDevelopIt</author>
      <guid isPermaLink="true">https://cdaosldk.tistory.com/380</guid>
      <comments>https://cdaosldk.tistory.com/380#entry380comment</comments>
      <pubDate>Wed, 11 Feb 2026 19:58:30 +0900</pubDate>
    </item>
    <item>
      <title>LLM에 보낼 파일 전처리를 위한 Python 라이브러리 : markitdown</title>
      <link>https://cdaosldk.tistory.com/379</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리 깃허브&lt;br /&gt;&lt;a href=&quot;https://github.com/microsoft/markitdown&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/microsoft/markitdown&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770710141297&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - microsoft/markitdown: Python tool for converting files and office documents to Markdown.&quot; data-og-description=&quot;Python tool for converting files and office documents to Markdown. - microsoft/markitdown&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/microsoft/markitdown&quot; data-og-url=&quot;https://github.com/microsoft/markitdown&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bTbiYh/dJMb9c9tMtI/0wnHxaAHEw2neMsFLZ64k1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/eWrDn/dJMb9jOiQIf/h6m4FkUxq9NC0hamosKxEK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/microsoft/markitdown&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/microsoft/markitdown&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bTbiYh/dJMb9c9tMtI/0wnHxaAHEw2neMsFLZ64k1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/eWrDn/dJMb9jOiQIf/h6m4FkUxq9NC0hamosKxEK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - microsoft/markitdown: Python tool for converting files and office documents to Markdown.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Python tool for converting files and office documents to Markdown. - microsoft/markitdown&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;LLM에게 파일을 읽게 하고자 할때, 기존에는 파일 형식에 따른 제약 사항이 매우 많았다. pandas나 기타 많은 라이브러리를 사용하더라도, 이들은 지정된 파일 형식에만 대응이 가능해 개발자가 고민해야되는 부분이 많았다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 활용 가능한 라이브러리가 markitdown이다. 전처리 작업 수행 시 파일 형식에 구애받지 않고 모든 지원가능한 파일(PPT, XLSX, 음성 파일도 지원한다고 한다)을 mark down 형식으로 변환시켜 LLM이 해당 데이터에 대한 처리를 수월하게 할 수 있도록 만들어준다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 사용하기 위해서는, 파이썬 3.10+ 이상 버전부터 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용을 위해 pip install부터 수행한다&lt;/p&gt;
&lt;pre id=&quot;code_1770711192670&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install markitdown&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기본 형태로, 이렇게만 설치해도 xlsx를 지원한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;*PDF 등, 기본 지원이 아닌 파일의 경우&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770711234097&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install markitdown[pdf]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;등의 구체적인 지정을 통한 추가 설치가 필요하다&lt;br /&gt;&lt;br /&gt;이를 해결하는 방법은&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1770711273932&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install markitdown[all]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로 설치하면 markitdown의 모든 지원 파일을 한번에 사용가능하다(라이브러리의 용량 차이는 발생하게 된다)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설치하고 나면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1770711449896&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;md_converter = MarkItDown()

def methodA(file: UploadFile) -&amp;gt;
	file_content = await file.read()
    file_stream = io.BytesIO(file_content)

	respsonse = md_converter.convert_stream(file_stream, file_extension=file_extension)
	markdown_text = conversion_res.text_content&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 코드를 통해 markdown으로 변환된 파일을 볼 수 있다.&lt;br /&gt;이후 LLM 호출부터는 자유롭게 진행하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 LLM 호출목적이 아니더라도 다양한 전처리에 활용가능할 듯 하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발공부/파이썬 </category>
      <author>wannaDevelopIt</author>
      <guid isPermaLink="true">https://cdaosldk.tistory.com/379</guid>
      <comments>https://cdaosldk.tistory.com/379#entry379comment</comments>
      <pubDate>Tue, 10 Feb 2026 17:18:22 +0900</pubDate>
    </item>
    <item>
      <title>@blocknote 호환 컨텐츠 자동 번역 기능 구현</title>
      <link>https://cdaosldk.tistory.com/378</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;BlockNote(React를 위해 구축된 오픈소스 기반의&lt;span&gt;&amp;nbsp;&lt;/span&gt;블록 스타일 텍스트 에디터)를 사용하고 있는 상황이었다&lt;br /&gt;링크 :&amp;nbsp;&lt;a href=&quot;https://www.blocknotejs.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.blocknotejs.org/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번역 기능을 구현해야 했는데, 기존 브라우저에서 제공하는 번역 기능이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;blockNote의 텍스트 블록을 텍스트로 취급하지 않아 번역이 되지 않는 문제가 있었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 블로그 작성 시 다국어 번역본을 자동으로 제공하기 위해 해당 에디터의 구조를 분석하고 번역 수행 로직을 구현했다&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;0. translation 진행 전 호출 위치 선정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항에 따라 블로그 게시물을 생성이 완료된 후 번역 대상 컨텐츠에 대한 업데이트(번역 컨텐츠는 별도의 컬럼으로 관리한다)를 수행하고, 본 컨텐츠를 업데이트하는 경우사용자의 필요에 따른 번역 API를 호출하는 것으로 했다. 이는 간단한 flag 분기 처리를 통해 구분했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 전처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업을 수행하는 동안 JsonObject와 JsonArray를 정말 많이 활용했다(정리한 내용 : &lt;a href=&quot;https://cdaosldk.tistory.com/373&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://cdaosldk.tistory.com/373&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 타입으로 입력된 데이터를 번역에 적합하게 전처리했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 블록 단위로 구성된 텍스트 데이터의 구조를 이해하는게 매우 중요했다. 해당 에디터의 문서를 참조했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.blocknotejs.org/docs/foundations/document-structure&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.blocknotejs.org/docs/foundations/document-structure&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768375542499&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;BlockNote - Document Structure&quot; data-og-description=&quot;Learn how documents (the content of the rich text editor) are structured to make the most out of BlockNote.&quot; data-og-host=&quot;www.blocknotejs.org&quot; data-og-source-url=&quot;https://www.blocknotejs.org/docs/foundations/document-structure&quot; data-og-url=&quot;https://www.blocknotejs.org/docs/foundations/document-structure&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/nHOZI/dJMb9fZoGaH/FUzZQLEHSE31i62ejO91H1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bMFR74/dJMb9kTWlOD/34zQbhmDV1s4pCcmFnSTak/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.blocknotejs.org/docs/foundations/document-structure&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.blocknotejs.org/docs/foundations/document-structure&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/nHOZI/dJMb9fZoGaH/FUzZQLEHSE31i62ejO91H1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bMFR74/dJMb9kTWlOD/34zQbhmDV1s4pCcmFnSTak/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;BlockNote - Document Structure&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how documents (the content of the rich text editor) are structured to make the most out of BlockNote.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.blocknotejs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록의 기본 구조는 다음과 같다 :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) id : 해당 블록의 아이디를 의미. 해당 값이 없으면 스타일을 유지한 텍스트 번역이 불가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) type : 블록의 유형, 테이블(표)인지, 링크인지, 일반 텍스트인지 등을 알려준다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) props : 블록의 특징을 정리해둔 키/값 집합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) content : 해당 블록의 텍스트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) children : 블록 안에 다른 블록(인라인 블록)의 경우 이 안에서 지금 설명된 것과 같은 블록 객체가 들어있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 블록이 배열 형태로 저장되기 때문에, 이 배열을 먼저 JsonArray로 파싱했다&lt;/p&gt;
&lt;pre id=&quot;code_1768376316378&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;....

JsonElement element = JsonParser.parseString(jsonString);
    if (element.isJsonNull()) {
        return new JsonArray();
    }
    if (element.isJsonPrimitive()) {
        String realJsonContent = element.getAsString();
        JsonElement arrayElement = JsonParser.parseString(realJsonContent);

...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 로직을 통해 파싱한 후, 핵심 전처리 로직을 수행한다&lt;/p&gt;
&lt;pre id=&quot;code_1768376639977&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private void addTextPropertiesFromContent(... {
...

String currentIndex = parentIndex.isEmpty() ? String.valueOf(i) : parentIndex + &quot;_&quot; + i;

...
if (contentItem.has(TEXT_FIELD) &amp;amp;&amp;amp; !contentItem.get(TEXT_FIELD).getAsString().isEmpty()) {
	translationItem.addProperty(TEXT_FIELD + &quot;_&quot; + currentIndex, contentItem.get(TEXT_FIELD).getAsString());
} else if (&quot;link&quot;.equals(contentItem.get(TYPE_FIELD).getAsString())) {
    JsonElement linkContentElement = contentItem.get(CONTENT_FIELD);
    if (linkContentElement != null &amp;amp;&amp;amp; linkContentElement.isJsonArray()) {
        // 링크 내부의 텍스트 처리 로직 재사용
        addTextPropertiesFromContent(linkContentElement.getAsJsonArray(), translationItem, currentIndex);
    }
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심적인 로직은 인덱스와 함께 배열에 담는 것이다. 가장 상위엔 상위 블럭의 id가 있고, 그 밑에 &quot;text_0_1&quot; 형태로 값을 담는다.&lt;br /&gt;부모 인덱스와 자식 인덱스 개념이 로직에 반영되어 있어야한다. link 등의 경우, 내부의 다른 블록으로 들어가야 하므로 메서드의 재귀호출로 이를 구현헀다&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 번역&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPT를 활용한 번역을 수행했으며, 내부 로직에 따라 오류나 잘못된 결과를 반환한 경우, 재시도하는 로직을 구현헀다. 추후 시멘틱 캐싱의 개념을 더 이해하고 도입할 수 있다면, 토큰 비용을 절감할 수 있을것으로 기대한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 후처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;text_0_1&quot; 등의 키에 매핑되어 번역된 결과물을, 원본 blcoknote 배열에 매핑을 해야한다. 애초에 불필요한 원본 배열을 번역 로직에 추가하지 않기 위해 전처리 로직이 필요했다. 큰 흐름은 전처리 로직의 역순으로 볼 수 볼 수 있다. 그렇게 번역까지 진행된 blocknote 배열은 원본 데이터와 함께 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 조회&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 클라이언트의 헤더에서 어떤 언어로 접속한지 보내는 값에 따라, SELECT 쿼리에서 이를 반영한 CHOOSE 문법을 통해 해당 데이터를 조회하는 것으로 번역 작업은 완료된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;마치며.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조화된 데이터, 특히 그 구조가 반복되는 데이터의 처리를 다루며 배우게 된 점이 많다. 반복 작업에 대해 캡슐화를 통한 재귀 호출을 구현하게 된 점이 좋았고, 처리 성능을 최적화하는게 매우 중요하다는 것을 다시 깨달았다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>자바☕</category>
      <author>wannaDevelopIt</author>
      <guid isPermaLink="true">https://cdaosldk.tistory.com/378</guid>
      <comments>https://cdaosldk.tistory.com/378#entry378comment</comments>
      <pubDate>Wed, 14 Jan 2026 18:29:06 +0900</pubDate>
    </item>
    <item>
      <title>CICD : Github Action + AWS IAM + AWS S3 + AWS EC2 + AWS Code Deploy</title>
      <link>https://cdaosldk.tistory.com/377</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;0. 사전&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술 스택 : Github Action, AWS EC2, AWS Code Deploy, AWS S3, Docker&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. appspec.yml&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;permission을 작성할 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-object 부분에 경로를 정확하게 작성해야 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-owner, group의 경우 EC2 내부의 사용자, 그룹을 의미하고 별도로 생성하지 않는 한 ubuntu EC2의 경우 ubuntu가 기본값이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 설정하는 방법&lt;/p&gt;
&lt;pre id=&quot;code_1765417633093&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo adduser 사용할사용자이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* (ubuntu) 사용자 이름에 대문자를 사용하고 싶은 경우:&lt;/p&gt;
&lt;pre id=&quot;code_1765417763782&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo adduser --force-badname 사용할사용자이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*After Install 설정을 통해 deploy.sh를 지정하여 code deploy가 해당 쉘 스크립트를 시작하도록 지정한다&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. workflow&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 도커 허브에 접속&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private docker repository에 접근하기 위해서는 로그인 정보가 필요하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 올리고자 하는 이미지 빌드 및 푸시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지로 올리려는 서비스의 경로를 지정한다. 또한 푸시하는 경로를 정확하게 지정해 pull에서 오류가 없게 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. AWS 접속&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 로그인 방식과 OIDC 방식이 있으며, 보안상으로 OIDC(쉽게 이해한 바로는 일회용 키 발급) 방식이 더 안전하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IAM ROLE에서 ARN을 사용해 키 발급 역할을 만들고, AWS IAM 사용자에게 해당 역할을 부여하면 OIDC 방식의 AWS 접속이 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. .env 파일(Spring에서 application.properties 또는 application.yml와 동일 개념) 생성 :&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경변수와 설정이 담긴 파일을 workflow에서 생성하는 방식도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 별도로 해당 파일을 관리할 필요가 없어 유지보수에 효율적이다. 다만 실제 값이 파일에 남은 채로 EC2에도 업로드 되기 떄문에 다음과 같은 보안이 필수다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 값을 git hub secret에 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) .env 파일을 암호화 : 추천 방식 - GPG&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;+ 런타임에 주입하는 방식도 가능하며, 대표적으로 AWS Secret Manager가 있다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 도커 접근용 파일 생성 .deploy_secrets&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커에서 이미지를 내려받기 위해 해당 파일에 로그인 정보를 담는다. 이 파일에 대한 암호화도 권장되며, 또한 위와 같이 런타임 주입도 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 배포 아티팩트 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 작업을 통해 만들어진 파일 및 appspec.yml, 배포 스크립트, docker.yml을 모아 아티팩트로 생성한다. S3에 아티팩트를 업로드에 관리하게 하며 유지보수 효율을 증가시킬수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. Code deploy에 작업 요청&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. AWS&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포를 위해 작업할 내용&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) IAM&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용자를 생성하고 해당 사용자에 필요한 역할을 부여해야 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 정책을 부여해 AWS 내부 서비스 접근 권한을 설정해야 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ID 제공업체 설정을 통해 ARN 값을 설정해야 한다&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) S3&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Github secret에 설정한 버킷 이름과 동일한 버킷을 생성해두어야 한다&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3) EC2&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-1) 탄력적 IP 부여를 통해 DNS 세팅을 준비한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-2) 키 페어 관리를 통해 SSH 접속 보안을 강화한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-3) IAM의 Code deploy 역할을 부여해 code deploy 작업이 가능하게 설정한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-4) 보안 그룹을 설정하고 인바운드 규칙을 통해 방화벽 허용 포트를 설정한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-5) docker와 AWS Code Deploy agent가 설치한다&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. Code deploy&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 배포 전략 설정&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 배포 그룹 생성 : ARN 역할 부여 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어플리케이션 생성 후 해당 어플리케이션을 배포 그룹에 종속시킨다&lt;/p&gt;</description>
      <category>인프라</category>
      <author>wannaDevelopIt</author>
      <guid isPermaLink="true">https://cdaosldk.tistory.com/377</guid>
      <comments>https://cdaosldk.tistory.com/377#entry377comment</comments>
      <pubDate>Fri, 12 Dec 2025 15:36:14 +0900</pubDate>
    </item>
    <item>
      <title>단일 인덱스 vs 복합 인덱스 vs 인덱스 머지(Index Merge)</title>
      <link>https://cdaosldk.tistory.com/376</link>
      <description>&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단순&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;장점 :&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;관리&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;편함&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;쓰기&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;성능&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;향상&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0000ff;&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저장공간&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;절약&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단점 :&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 단일 인덱스만으로 이뤄진 여러 검색 조건의 조회 쿼리 수행 시&lt;/span&gt;&lt;span&gt; 하나의 인덱스만 사용하거나 인덱스 머지를 수행&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 정렬이&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;필요한&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;경우&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;별도의&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정렬&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;수행해야 한다&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 복합 인덱스 : 생성 순서와 사용 컬럼이 정해진 여러 인덱스 키를 가진 하나의 인덱스&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;장점 :&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 해당&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;순서에&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;맞는&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;전체&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;또는&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;일부&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;검색을&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;수행할&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;경우, 단일 인덱스와 인덱스 머지보다 빠른 성능으로 조회 가능&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 커버링&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;인덱스&lt;/span&gt; : &lt;span style=&quot;color: #000000;&quot;&gt;검색조건이 모두&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;복합&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스인&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;경우&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;테이블을&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;읽지&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;않고&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스만&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;읽고&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;반환하기 때문에&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;성능&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;향상&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;생성&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;순서와&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;쿼리&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;정렬&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;순서가&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;일치할&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;경우&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;별도의&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;정렬&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;작업을&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;수행할&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;필요&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;없음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단점&lt;/span&gt; :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 순서에&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;맞지않는&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;검색조건은&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;인덱스를&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;타지&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;못함&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ex) 복합 인덱스 a : 컬럼 b, 컬럼 c, 컬럼 d로 구성되어 있을때, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컬럼 b, b - c, b - c - d는 가능하나 c - d 또는 d 등의 순서가 맞지 않는 경우 인덱스를 타지 않는다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;키의&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;크기가&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;커지므로&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;쓰기&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;성능&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;저하 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-저장 공간 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;용량&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;증가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. &lt;span style=&quot;color: #000000;&quot;&gt;인덱스&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;머지&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;종류 :&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 인덱스&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;교집합&lt;/span&gt; : &lt;span style=&quot;color: #000000;&quot;&gt;인덱스&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;사용하는&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;검색조건이&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;서로&lt;/span&gt; &lt;span style=&quot;color: #800000;&quot;&gt;AND&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;경우&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-&amp;gt; 단일&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;인덱스로&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;조회를 2&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;번&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;이상&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;수행&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;후&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;교집합&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;작업 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) 인덱스&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;합집합&lt;/span&gt; : &lt;span style=&quot;color: #800000;&quot;&gt;OR&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;인&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;경우&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;조회&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;수&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;합집합&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;장점&lt;/span&gt; :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 저장공간&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;절약&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #800000;&quot;&gt;- OR&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;조건&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;쿼리에서&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;풀스캔을&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;피할&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;수&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;있다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 새로운&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;복합&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;인덱스를&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;생성할&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;필요가&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;없다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단점&lt;/span&gt; :&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 병합&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;오버헤드&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;발생 : 교집합 또는 합집합을 수행하며 추가 메모리 사용 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;복합&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;인덱스보다&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;성능이&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;느리다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 머지 사용 여부를 옵티마이저가&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;선택할 때&lt;/span&gt;, &lt;span style=&quot;color: #000000;&quot;&gt;풀스캔이&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;더&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;빠르다고&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;판단하면&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;풀스캔으로&lt;/span&gt; &lt;span style=&quot;color: #000000;&quot;&gt;수행한다(머지가 항상 사용되지 않는다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-&amp;gt; 쿼리에 옵티마이저 힌트, 또는 설정을 통해 인덱스 머지를 강제할 수 있다&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>RDB ️</category>
      <author>wannaDevelopIt</author>
      <guid isPermaLink="true">https://cdaosldk.tistory.com/376</guid>
      <comments>https://cdaosldk.tistory.com/376#entry376comment</comments>
      <pubDate>Tue, 25 Nov 2025 10:59:53 +0900</pubDate>
    </item>
    <item>
      <title>11.25 TIL : Mybatis 조건문 관련</title>
      <link>https://cdaosldk.tistory.com/375</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;동적 쿼리를 작성할때 많이 사용하는 패턴으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;if test= 조건&amp;gt;&amp;lt;/if&amp;gt;에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;if test =&quot;a != null and a != ''&quot;를 사용하곤 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 조건문에 값 0이 들어갈 경우, mybatis는 이를 false로 판단한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 해당 조건문이 생략된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할것&lt;/p&gt;</description>
      <category>TIL, WIL/TIL </category>
      <author>wannaDevelopIt</author>
      <guid isPermaLink="true">https://cdaosldk.tistory.com/375</guid>
      <comments>https://cdaosldk.tistory.com/375#entry375comment</comments>
      <pubDate>Tue, 25 Nov 2025 09:05:26 +0900</pubDate>
    </item>
    <item>
      <title>10.21 TIL : SQL 필터를 통한 데이터 정제</title>
      <link>https://cdaosldk.tistory.com/374</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;DB에서 varchar로 저장된 json String에 대해 한글인 경우의 데이터를 추출해야 했다. 다만, 한글이어도 상관없는 키와 한글 데이터가 같이 섞여있기 때문에, 이를 제외하는 것이 중요했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완성된 쿼리 :&lt;/p&gt;
&lt;pre id=&quot;code_1761034226932&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;WITH W1 AS (
    -- 1. 본 테이블에서 한글이 포함된 모든 데이터 조회
    SELECT
        sequence,
        content
    FROM
        board b
    WHERE
        b.content RLIKE '[가-힣]'
),
W2 AS (
    SELECT
    	sequence,
    	REGEXP_SUBSTR(
        	b.content
        	-- 패턴: &quot;key&quot;:&quot; 뒤에 오며, 한글을 포함하고, 닫는 따옴표 직전까지의 내용을 캡처
        	'(?&amp;lt;=&quot;key&quot;:&quot;)([^&quot;]*[가-힣]+[^&quot;]*)' 
    	) AS Extracted_Korean_Content,
    	b.content
	FROM
    	board b
	WHERE
    	-- content에 &quot;key&quot; 속성 값 중 한글이 포함된 블록이 없는 행을 조회
    	b.content NOT REGEXP '(&quot;key1&quot;:&quot;|&quot;key2&quot;:&quot;|&quot;key3&quot;:&quot;|&quot;key4&quot;:&quot;)[^&quot;]*[가-힣]+'
)
SELECT
W1.*, W2.Extracted_Korean_Content
FROM W1 INNER JOIN W2 ON W1.sequence = W2.sequence
ORDER BY W1.sequence DESC&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;*REGXP_SUBSTR() 메서드에서 패턴이 있는 값을 추출한다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;W1 테이블은 한글이 포함된 모든 데이터를 조회하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;W2 테이블에서 특정 키 뒤에 한글이 오는 경우를 제외 한 후&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이너 조인을 통해 내가 원하는 키에 한글이 있는 경우만 추출할 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TIL, WIL/TIL </category>
      <author>wannaDevelopIt</author>
      <guid isPermaLink="true">https://cdaosldk.tistory.com/374</guid>
      <comments>https://cdaosldk.tistory.com/374#entry374comment</comments>
      <pubDate>Wed, 22 Oct 2025 06:51:28 +0900</pubDate>
    </item>
    <item>
      <title>09. 25 TIL : Gson 라이브러리의 JsonObject 객체 활용 시 주의할 점</title>
      <link>https://cdaosldk.tistory.com/373</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;0. 문제 상황&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;-&amp;gt; JsonObject에서 String인 값을 toString() 메서드를 활용해 값을 참조했지만, 왜인지 \&quot;A\&quot; 의 형태로 문자열 안에 따옴표까지 추가된 형태로 데이터를 반환하고 있었다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 공부한 내용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JsonObject.get(memberName).toString() 메서드를 활용할 때 주의할 사항&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본인 Object의 toString() 메서드는&lt;/p&gt;
&lt;pre id=&quot;code_1758782363097&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public String toString() {
    return getClass().getName() + &quot;@&quot; + Integer.toHexString(hashCode());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로직을 가지고 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 로직을 통해 호출할 경우, 해당 객체의 &quot;주소&quot;를 문자열로 반환받을 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 다르게 JsonObject의 경우,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 객체인 JsonElement에서 재정의한 toString() 메서드를 사용한다&lt;/p&gt;
&lt;pre id=&quot;code_1758782579900&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
  public String toString() {
    try {
      StringWriter stringWriter = new StringWriter();
      JsonWriter jsonWriter = new JsonWriter(stringWriter);
      jsonWriter.setLenient(true);
      Streams.write(this, jsonWriter);
      return stringWriter.toString();
    } catch (IOException e) {
      throw new AssertionError(e);
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) StringWriter 클래스인 stringWriter 객체 생성 : java.io 라이브러리의 클래스로, 내부에서 StringBuffer 클래스를 사용한 데이터 쓰기 작업을 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) JsonWriter : stringWriter 객체를 매개변수로 한 새로운 JsonWrter 객체를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1758782992398&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  public JsonWriter(Writer out) {
    this.out = Objects.requireNonNull(out, &quot;out == null&quot;);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; null 체크를 수행한 후 해당 객체를 주입한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) jsonWriter.setLenient(true) :&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 객체가 json 표준 양식을 엄격하게 준수할 것인지 여부를 설정한다. true로 설정되므로, 형식에 맞지않는 데이터가 입력될 경우 JsonParseException 등의 오류가 발생한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) Streams.write(this, jsonWriter) :&lt;/p&gt;
&lt;pre id=&quot;code_1758790008448&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void write(JsonElement element, JsonWriter writer) throws IOException {
    TypeAdapters.JSON_ELEMENT.write(writer, element);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 메서드 안에서 호출하는 write 메서드는 추상 메서드로,&lt;br /&gt;TypeAdapters 클래스에서 재정의하고 있다&lt;/p&gt;
&lt;pre id=&quot;code_1758790244350&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override public void write(JsonWriter out, JsonElement value) throws IOException {
      if (value == null || value.isJsonNull()) {
        out.nullValue();
      } else if (value.isJsonPrimitive()) {
        JsonPrimitive primitive = value.getAsJsonPrimitive();
        if (primitive.isNumber()) {
          out.value(primitive.getAsNumber());
        } else if (primitive.isBoolean()) {
          out.value(primitive.getAsBoolean());
        } else {
          out.value(primitive.getAsString());
        }

      } else if (value.isJsonArray()) {
        out.beginArray();
        for (JsonElement e : value.getAsJsonArray()) {
          write(out, e);
        }
        out.endArray();

      } else if (value.isJsonObject()) {
        out.beginObject();
        for (Map.Entry&amp;lt;String, JsonElement&amp;gt; e : value.getAsJsonObject().entrySet()) {
          out.name(e.getKey());
          write(out, e.getValue());
        }
        out.endObject();

      } else {
        throw new IllegalArgumentException(&quot;Couldn't write &quot; + value.getClass());
      }
    }
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JsonObject의 경우, 기존 JsonElement의 entrySet()를 순환하면서 jsonWriter 객체를 활용 value(JsonElement)의 key는 out.name() 메서드로, value는 write() 재귀 호출로 올바르게 JSON 문자열로 변환하는 과정을 통해 직렬화한다&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;-&amp;gt; ** 이 과정에서 jsonWriter 객체는 JSON 표준규격을 준수해야 하므로, 기존 데이터에 따옴표를 추가한다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉, JsonObejct.get(memberName).toString()은 따옴표를 한번 더 씌우는 것이다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에, 원하는 값을 위해선&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JsonObejct.get(memberName).getAsString(); 메서드를 사용해야 한다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TIL, WIL/TIL </category>
      <author>wannaDevelopIt</author>
      <guid isPermaLink="true">https://cdaosldk.tistory.com/373</guid>
      <comments>https://cdaosldk.tistory.com/373#entry373comment</comments>
      <pubDate>Thu, 25 Sep 2025 18:00:50 +0900</pubDate>
    </item>
  </channel>
</rss>