<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>꾸준함이 진리다</title>
    <link>https://simgee.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 27 Aug 2026 10:47:13 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>황심지</managingEditor>
    <image>
      <title>꾸준함이 진리다</title>
      <url>https://tistory1.daumcdn.net/tistory/4420203/attach/f6f9f4cc05394f2aa6805ed4fb88063e</url>
      <link>https://simgee.tistory.com</link>
    </image>
    <item>
      <title>6초에서 0.2초로: 레거시 API 성능 개선 이야기</title>
      <link>https://simgee.tistory.com/73</link>
      <description>&lt;p&gt;서비스를 운영하다 보면 늘 예상치 못한 문제가 튀어나오곤 합니다. 이번엔 &lt;strong&gt;문서 및 카테고리 이동 기능&lt;/strong&gt;이 발목을 잡았습니다.&lt;/p&gt;
&lt;p&gt;실제로 고객들이 자주 사용하는 기능이여서, 해당 기능으로 고객들은 작성해놓은 문서를 새로운 카테고리나, 폴더등으로 옮기곤 합니다. 그런데 고객들의 사용 빈도만큼이나 &lt;strong&gt;&amp;quot;너무 느려요&amp;quot;&lt;/strong&gt;, &lt;strong&gt;&amp;quot;이동기능 로딩만 5초 넘게 걸려요&amp;quot;&lt;/strong&gt; 같은 불만도 많았습니다.&lt;/p&gt;
&lt;p&gt;그래서 실제로 테스트를 해보니 응답 시간이 &lt;strong&gt;평균 6초&lt;/strong&gt;에 달했습니다. 사용자는 6초 동안 화면에서 로딩 메시지를 봐야 하고, 작업 도중 페이지를 닫아버리는 경우도 있었습니다.&lt;/p&gt;
&lt;p&gt;따라서 해당 기능에 대해 어떤 부분에서 성능 문제가 발생했는지, 파악하고 개선하자는 목표로 작업을 진행했습니다.&lt;/p&gt;
&lt;h2&gt;6초의 로딩 시간, 문제는 반복되는 save()호출이었다&lt;/h2&gt;
&lt;p&gt;문제의 원인을 파악하기 위해 해당 코드 부분을 살펴보니 아래와 같은 로직이 작성되어 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;개선 전 코드&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;for document in documents:
    document.position = new_position
    document.save()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 코드는 각 문서를 하나씩 업데이트하고, 매번 save() 메서드를 호출합니다.&lt;br&gt;save() 메서드는 내부적으로 데이터베이스와 통신하기 때문에 호출할 때마다 &lt;strong&gt;데이터베이스 쿼리&lt;/strong&gt;가 발생합니다.&lt;br&gt;즉, 문서가 많아질수록 요청 속도가 기하급수적으로 느려지는 구조였던 거죠.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예를 들어&lt;/strong&gt; 문서가 95개라면, 데이터베이스에 &lt;strong&gt;95번의 쿼리&lt;/strong&gt;가 발생합니다.&lt;/p&gt;
&lt;p&gt;이건 단순히 서버 리소스를 낭비하는 것뿐 아니라, 응답 시간을 길게 만드는 주범이었습니다.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;해결 방법: bulk_update로 쿼리 최적화&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;문서를 하나씩 저장하는 대신, Django에서 제공하는 &lt;code&gt;bulk_update()&lt;/code&gt; 메서드를 사용해 데이터를 한 번에 업데이트하도록 코드를 변경했습니다.&lt;/p&gt;
&lt;p&gt;bulk_update는 한 번의 데이터베이스 쿼리로 여러 개의 데이터를 업데이트할 수 있어 다수의 데이터를 업데이트 해야할 때 성능 향상에 유용 합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;개선 후 코드&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from django.db import transaction

# 모든 문서의 position 값을 한꺼번에 변경
for document in documents:
    document.position = new_position

# bulk_update로 한 번에 저장
with transaction.atomic():
    Document.objects.bulk_update(documents, [&amp;#39;position&amp;#39;])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 코드는 문서를 반복하며 필요한 값만 변경한 뒤, &lt;strong&gt;한 번의 쿼리&lt;/strong&gt;로 저장합니다.&lt;br&gt;문서가 95개든 1,000개든 한 번의 쿼리로 처리되기 때문에 성능이 크게 개선됩니다.&lt;/p&gt;
&lt;h2&gt;결과는? 속도가 30배 빨라졌습니다.&lt;/h2&gt;
&lt;p&gt;코드를 수정한 후 테스트를 진행해보았습니다.&lt;br&gt;결과는 정말 놀라웠습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;테스트 환경 : 스테이징(실제 서비스 환경과 동일한, 인프라를 구축한 환경)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;개선 전&lt;/strong&gt;: 평균 응답 시간 &lt;strong&gt;6초&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;개선 후&lt;/strong&gt;: 평균 응답 시간 &lt;strong&gt;0.2초&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;성능 향상&lt;/strong&gt;: 약 &lt;strong&gt;30배&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이제 고객들이 긴 로딩 메시지를 볼 필요 없이 빠르게 작업을 이어갈 수 있게 되었고, 서버 자원의 낭비도 크게 줄어들었습니다.&lt;/p&gt;
&lt;h2&gt;정리하며: 배운 점&lt;/h2&gt;
&lt;p&gt;이 경험을 통해 한 가지 확실히 배운 점은, &lt;strong&gt;“데이터베이스와의 통신은 꼭 필요한 만큼만 하자”&lt;/strong&gt; 라는 기본 원칙입니다.&lt;/p&gt;
&lt;p&gt;단순히 반복문 내에서 저장 메서드를 호출하는 코드가 성능에 얼마나 큰 영향을 미칠 수 있는지 실무에서 직접 체감한 사례였어요.&lt;/p&gt;
&lt;p&gt;특히, Django ORM을 사용한다면 bulk_update, bulk_create 같은 쿼리 최적화 기능을 적극적으로 활용하는 것이 중요하다는 것을 깨달았습니다.&lt;/p&gt;</description>
      <category>프로그래밍</category>
      <author>황심지</author>
      <guid isPermaLink="true">https://simgee.tistory.com/73</guid>
      <comments>https://simgee.tistory.com/73#entry73comment</comments>
      <pubDate>Sat, 14 Dec 2024 17:04:17 +0900</pubDate>
    </item>
    <item>
      <title>번역 API 개선기: 번역 가능 언어 추가 요청을 받으며 배운 점</title>
      <link>https://simgee.tistory.com/72</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;업무를 하다 보면 늘 새로운 문제를 만납니다. 이번에는 번역 관련 API에서 번역 가능 언어를 추가해 달라는 요청이 왔습니다. 처음에는 단순히 번역 가능 언어 코드를 추가하고 끝낼 작업처럼 보였지만, 막상 작업을 시작하면서 기존 코드에서 숨겨진 문제를 발견하게 되었습니다. 그리고 그 문제를 개선하는 과정에서 많은 것을 배웠습니다. 이번 글에서는 그 경험을 나누고자 합니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;번역 가능 언어 목록에 새로운 언어를 추가해 주세요&amp;rdquo;라는 요청을 받았습니다. 요청 자체는 간단했지만, 테스트를 진행하던 중 의외의 문제가 드러났습니다. 번역 API를 호출할 때 번역 처리 시간이 너무 오래 걸렸던 것입니다. &lt;b&gt;약 4,500자&lt;/b&gt;의 텍스트를 번역하는 데 &lt;b&gt;17초&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;ldquo;왜 이렇게 오래 걸리지?&amp;rdquo; 하는 의문에서 코드를 살펴보니 문제는 바로 &lt;b&gt;for-loop&lt;/b&gt; 안에서 매번 번역 API로 HTTP 요청을 보내고 있다는 것이었습니다.&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;2. 문제의 원인: 반복적인 API 호출&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733234367428&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from google.cloud import translate_v2 as translate

def translate_text(text):
    translate_client = translate.Client()
    lines = text.split(&quot;\n&quot;)  # 개행 문자 기준으로 텍스트 분리
    translated_lines = []

    for line in lines:
        if line.strip():  # 빈 줄은 건너뜀
            response = translate_client.translate(line, target_language=&quot;ko&quot;)
            translated_lines.append(response[&quot;translatedText&quot;])

    return &quot;\n&quot;.join(translated_lines)&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;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;text.split(&quot;\n&quot;)&lt;/span&gt;로 개행 문자를 기준으로 텍스트를 분리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;각 줄을 번역 API에 개별 요청(&lt;span&gt;translate_client.translate&lt;/span&gt;)합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&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;b&gt;&amp;rdquo;\n&amp;rdquo;&lt;/b&gt;(개행 문자) 기준으로 분리한 뒤, 각 줄을 번역 API에 개별적으로 요청하고 있었습니다. 즉, 줄이 많아질수록 API 요청 횟수가 기하급수적으로 늘어나고, 이로 인해 전체 처리 시간이 길어졌던 겁니다.&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;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;4,500자 기준으로 개행 문자가 많을수록 HTTP 요청 횟수가 늘어남.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;API 호출은 네트워크 레이턴시와 처리 시간이 포함되기 때문에, 반복 호출은 성능을 크게 저하시킴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&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;b&gt;3. 개선 방법: API 문서를 참고해 새로운 구조로 변경&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;구글 클라우드 번역 API의 &lt;a href=&quot;https://cloud.google.com/python/docs/reference/translate/latest/google.cloud.translate_v2.client.Client#google_cloud_translate_v2_client_Client_translate&quot;&gt;공식 문서&lt;/a&gt;에서 &lt;span&gt;translate&lt;/span&gt; 메서드가 **문자열(str)**뿐 아니라 &lt;b&gt;리스트(list)&lt;/b&gt; 형태로도 요청할 수 있다는 점을 확인했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1733235076850&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;translate(
    values,
    target_language=None,
    format_=None,
    source_language=None,
    customization_ids=(),
    model=None,
)&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;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.4884%;&quot;&gt;&lt;span style=&quot;background-color: #f1f3f4; color: #202124; text-align: left;&quot;&gt;values&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 81.5116%;&quot;&gt;str or list&lt;br /&gt;&lt;span&gt;String or list of strings to translate.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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: #333333; text-align: start;&quot;&gt;즉, 개행 문자로 나뉜 텍스트를 한 번에 리스트 형태로 API에 전달하면, 응답 또한 번역된 결과를 담은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&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;&lt;b&gt;4. 개선 코드: 한 번의 요청으로 처리&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;구글 번역 API는 긴 텍스트를 한 번에 처리할 수 있기 때문에, 텍스트 전체를 API로 전달하도록 코드를 수정했습니다. 개선된 코드는 아래와 같습니다:&lt;/p&gt;
&lt;pre id=&quot;code_1733234471490&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from google.cloud import translate_v2 as translate

def translate_text_optimized(text):
    translate_client = translate.Client()

    # API 요청을 한 번만 수행
    splitted_text = text.split(&quot;\n&quot;)
    response = translate_client.translate(splitted_text, target_language=&quot;ko&quot;)
	
    # ... 그외 처리

    return translated_text&lt;/code&gt;&lt;/pre&gt;
&lt;div id=&quot;__endic_crx__&quot;&gt;
&lt;div class=&quot;css-diqpy0&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;span&gt;text&lt;/span&gt; 전체를 API로 전달하여 한 번의 요청으로 번역을 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;불필요한 for-loop와 여러 HTTP 요청을 제거했습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;개선 전:&lt;/b&gt; &lt;b&gt;4,500자&lt;/b&gt; 기준 번역 처리 시간 &lt;b&gt;약 17초&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;/span&gt;&amp;bull;&lt;span&gt; &lt;/span&gt;&lt;b&gt;개선 후:&lt;/b&gt; 같은 텍스트를 번역하는 데 &lt;b&gt;약 0.9초 (1초 이내로 단축)&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;이 작업을 통해 요청 횟수를 대폭 줄이고, API의 성능을 최대한 활용할 수 있었습니다.&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;마무리하며&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;작은 문제처럼 보였던 비효율적인 구조가 전체 번역 프로세스의 성능을 좌우한다는 점을 이번 경험을 통해 실감했습니다. 특히 API의 문서를 잘 읽고 활용하면 단순한 최적화 작업에도 큰 성과를 낼 수 있다는 것을 배웠습니다. 앞으로도 문서를 꼼꼼히 살피며 개선할 수 있는 부분을 찾아갈 계획입니다.&lt;/p&gt;</description>
      <category>프로그래밍</category>
      <author>황심지</author>
      <guid isPermaLink="true">https://simgee.tistory.com/72</guid>
      <comments>https://simgee.tistory.com/72#entry72comment</comments>
      <pubDate>Tue, 3 Dec 2024 23:18:23 +0900</pubDate>
    </item>
    <item>
      <title>정기 실행 쿼리 최적화</title>
      <link>https://simgee.tistory.com/71</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;APM 툴에서 정기적으로 실행되는 쿼리가 평균 25초 정도 소요되어 성능 개선이 필요했습니다. 이 쿼리는 30분마다 실행되는 배치 작업에 사용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;문제점&lt;/h4&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기존 쿼리는 다음과 같은 문제점을 가지고 있었습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Using filesort와 Using temporary가 실행 계획에 포함됨.&lt;/li&gt;
&lt;li&gt;reg_date 칼럼에 인덱스를 걸어도 정렬 처리에서 인덱스를 사용하지 못함.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시&lt;/p&gt;
&lt;pre id=&quot;code_1716363521135&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT
  ...(필요한 컬럼들)
FROM
  A
  INNER JOIN  ON 
  INNER JOIN  ON 
  INNER JOIN  ON 
  INNER JOIN  ON 
WHERE
  조건문 1
  AND 조건문 2
ORDER BY
  `some_table`.`reg_date` ASC;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;주요 원인&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Using filesort: ORDER BY 처리가 인덱스를 사용하지 못할 때 발생하며, 조회된 레코드를 정렬용 메모리 버퍼에 복사해 정렬합니다.&lt;/li&gt;
&lt;li&gt;Using temporary: 임시 테이블을 사용하여 결과를 정렬합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;해결 방안&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;created_at에 인덱스를 추가해 ORDER BY 절에서 인덱스를 사용할 수 있도록 처리:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그러나 WHERE 절에서 created_at을 사용하지 않기 때문에 인덱스가 정상적으로 작동하지 않음.&lt;/li&gt;
&lt;li&gt;비즈니스 로직상 WHERE 절에 created_at을 추가할 수 없으므로 이 방법은 사용 불가.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ORDER BY 구문을 삭제하고 애플리케이션 로직에서 정렬 처리:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 로직상 ORDER BY를 통한 정렬이 중요하지 않기에 애플리케이션 로직을 변경해서 처리함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;최적화 후 쿼리 실행 시간&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;AS-IS&lt;/b&gt;: 약 29초 소요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TO-BE&lt;/b&gt;: 약 19초 소요 (약 10초 감소)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;최적화된 쿼리 예시&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;최적화된 쿼리는 ORDER BY 구문을 제거하고, 애플리케이션에서 정렬을 처리하도록 변경했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1716363569037&quot; class=&quot;sql&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;SELECT
  ...(필요한 컬럼들)
FROM
  A
  INNER JOIN  ON 
  INNER JOIN  ON 
  INNER JOIN  ON 
  INNER JOIN  ON 
WHERE
  조건문 1
  AND 조건문 2;&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY 구문 한줄을 삭제함으로써, 쿼리 실행시간을 약 10초 정도 줄일 수 있었습니다. 이 과정에서 해당 쿼리에 대한 실행계획을 이해하고 튜닝을 진행하는것 뿐만아니라 해당 쿼리의 각 부분이 실제로 유용하게 쓰이고 있는지 불필요한 정렬이 발생하고 있는건 아닌지와 같은 부분을 토대로 튜닝을 진행할 수 있었습니다.&lt;/p&gt;</description>
      <author>황심지</author>
      <guid isPermaLink="true">https://simgee.tistory.com/71</guid>
      <comments>https://simgee.tistory.com/71#entry71comment</comments>
      <pubDate>Wed, 22 May 2024 16:41:20 +0900</pubDate>
    </item>
    <item>
      <title>시스템 로그 페이지 조회 성능 최적화</title>
      <link>https://simgee.tistory.com/70</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;개발 과정에서 성능 최적화는 매우 중요한 과제 중 하나입니다. 특히, 대용량 데이터 처리 시, 데이터 조회 성능은 사용자 경험에 직접적인 영향을 미칩니다. 본 포스트에서는 시스템 로그 페이지 조회 성능을 개선한 사례를 공유하고자 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;라이브 환경에서 시스템 로그 페이지 조회 시, 약 8.10초가 소요되는 문제가 발생했습니다. 조회 대상 데이터는 약 76,040건이었습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;문제 분석&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ListView를 사용하는 클래스에서 전체 시스템 로그 데이터 갯수를 조회하는 과정에서 성능 저하가 발생했습니다. 특히, get_context_data 메서드에서 len(self.get_queryset())을 호출할 때 전체 테이블 데이터에 대한 조회 쿼리가 발생했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1712195818951&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def get_context_data(self, **kwargs):
    ...
    context['objects_count'] = len(self.get_queryset())
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;데이터베이스 로그를 확인해보니, limit 없이 &lt;/span&gt;system_log&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&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;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;len(self.get_queryset()) 대신 context['paginator'].count를 사용하는 방식으로 변경하여, Paginator 클래스의 count 속성을 사용했습니다. 이 방식은 전체 데이터 수를 조회할 때 데이터베이스에 쿼리를 날리지 않고, Paginator가 이미 계산한 결과를 사용합니다.&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;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;또한, get_queryset()&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt; 메서드에서 queryset에 대한 반복 처리 시, 쿼리셋이 평가되어 전체 데이터 조회 쿼리가 발생했습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1712200224438&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def get_queryset(self):
    ...
    for query in queryset:
        query.convert_log_data()
    return queryset&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;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;이를 &lt;/span&gt;context&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;의 &lt;/span&gt;object_list&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;를 사용해 처리하도록 변경함으로써, ListView에서 지정한 &lt;/span&gt;paginated_by&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt; 값만큼만 처리되도록 개선했습니다. 이는 불필요한 전체 데이터 조회를 방지하고 성능을 향상시킵니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;개선 결과&lt;/h2&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;개선 전&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;소요 시간&lt;/b&gt;: 약 1분&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;개선 후&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;소요 시간&lt;/b&gt;: 0.7 밀리초&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 최적화 과정을 통해 데이터 조회 성능이 크게 향상되었습니다. Django의 Paginator 클래스와 쿼리셋의 평가 시점을 이해하고 적절히 활용함으로써, 대규모 데이터 처리 시 성능을 개선할 수 있음을 확인했습니다.&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.djangoproject.com/en/5.0/ref/models/querysets/&quot;&gt;Django QuerySets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.djangoproject.com/en/5.0/topics/db/optimization/&quot;&gt;Django DB Optimization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.djangoproject.com/en/5.0/ref/models/querysets/#when-querysets-are-evaluated&quot;&gt;Django QuerySets Evaluation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.djangoproject.com/en/5.0/ref/paginator/&quot;&gt;Django Paginator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍</category>
      <category>Django</category>
      <category>django orm</category>
      <category>python</category>
      <author>황심지</author>
      <guid isPermaLink="true">https://simgee.tistory.com/70</guid>
      <comments>https://simgee.tistory.com/70#entry70comment</comments>
      <pubDate>Thu, 4 Apr 2024 12:12:03 +0900</pubDate>
    </item>
    <item>
      <title>주니어 개발자가 말아주는 간식 구매 요청 자동화</title>
      <link>https://simgee.tistory.com/69</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 기술&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Slack API&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack API를 사용하여 간단한 명령어, 예를 들어 &lt;code&gt;/간식구매요청&lt;/code&gt; 과 같은 것들을 처리합니다. 이는 사용자가 Slack을 통해 쉽게 요청을 할 수 있게 해줍니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AWS Lambda&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS Lambda는 이러한 Slack 명령어에 대한 반응을 처리하는 서버의 역할을 합니다. 우리는 많은 종류의 요청을 처리할 필요가 없기 때문에, 간단하고 효율적인 AWS Lambda를 선택했습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AWS API Gateway&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS ApiGateway는 Lambda와 연결되어, 사용자의 요청을 Lambda로 전달하는 역할을 합니다. 이를 통해 사용자의 요청을 쉽게 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로, 구글 스프레드 시트를 조작하거나 웹에서 정보를 수집하기 위해 파이썬의 gspread 패키지와 bs4라는 도구를 사용했습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자동화 하게 된 계기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;1424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvHukm/btsD4TU35ui/qStXSonpzUfVxqUcH25TT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvHukm/btsD4TU35ui/qStXSonpzUfVxqUcH25TT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvHukm/btsD4TU35ui/qStXSonpzUfVxqUcH25TT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvHukm%2FbtsD4TU35ui%2FqStXSonpzUfVxqUcH25TT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;265&quot; height=&quot;413&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;1424&quot;/&gt;&lt;/span&gt;&lt;/figure&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;h3 data-ke-size=&quot;size23&quot;&gt;간식 구매요청 자동화 아키텍쳐&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btMoj5/btsD4HtGDOM/nRAr5b5y2CjHx83Z3k2cf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btMoj5/btsD4HtGDOM/nRAr5b5y2CjHx83Z3k2cf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btMoj5/btsD4HtGDOM/nRAr5b5y2CjHx83Z3k2cf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtMoj5%2FbtsD4HtGDOM%2FnRAr5b5y2CjHx83Z3k2cf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;592&quot; height=&quot;108&quot; data-origin-width=&quot;1141&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 &quot;/간식구매요청&quot; 명령어를 입력하면, Slack API가 이를 감지하고 설정된 API 주소로 요청을 보냅니다. 이 주소는 API Gateway에 설정되어 있으며, Gateway는 이 요청을 받아 Lambda 함수를 실행시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lambda 함수는 요청을 처리하고 결과를 Slack으로 다시 보냅니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;간식 구매요청 자동화 과정에서 만난 문제&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;API Gateway로 사용자가 요청한 &lt;code&gt;/간식구매요청&lt;/code&gt; 에 해당하는 커맨드값을 가져오는 과정에서, 요청 데이터를 제대로 처리하지 못하는 문제가 발생.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제 파악&lt;/b&gt;&lt;br /&gt; Slack Slash Command 공식문서를 살펴보니 다음과 같은 내용이 있었습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;token=gIkuvaNzQIHg97ATvDxqgjtO
&amp;amp;team_id=T0001
&amp;amp;team_domain=example
&amp;amp;enterprise_id=E0001
&amp;amp;enterprise_name=Globular%20Construct%20Inc
&amp;amp;channel_id=C2147483705
&amp;amp;channel_name=test
&amp;amp;user_id=U2147483697
&amp;amp;user_name=Steve
&amp;amp;command=/weather
&amp;amp;text=94070
&amp;amp;response_url=https://hooks.slack.com/commands/1234/5678
&amp;amp;trigger_id=13345224609.738474920.8088930838d88f008e0
&amp;amp;api_app_id=A123456&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;This data will be sent with a &lt;code&gt;Content-type&lt;/code&gt; header set as &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/간식구매요청 명령어로 요청을 보내면 해당 요청을 처리하는 URL로 위와 같은 x-www-form-urlencoded형식의 form data형식이 보내지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 데이터 형식을 API Gateway에서 Lambda로 처리할 때, 매핑 템플릿 이라는 걸 설정해줘야 합니다&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_매핑 템플릿_이란 &lt;a href=&quot;https://velocity.apache.org/engine/devel/vtl-reference.html&quot;&gt;Velocity Template Language(VTL)&lt;/a&gt;로 표현된 스크립트로, &lt;a href=&quot;http://goessner.net/articles/JsonPath/&quot;&gt;JSONPath&lt;/a&gt;를 사용하여 페이로드에 적용됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매핑 템플릿을 설정해주게 되면 원하는 데이터를&lt;/p&gt;
&lt;pre class=&quot;vbnet&quot;&gt;&lt;code&gt;def lambda_handler(event,context):
    #TODO implement

    option = event['text']
    command = event['command']
    response_url = event['response_url']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 식으로 사용할 수 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Slack Command의 짧은 응답 시간제한으로 인한 &quot;operation_timeout&quot;이 발생하는 문제&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1203&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bafcIj/btsDZ2NhNpc/0E0ktWUPmXlADgrllfk8ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bafcIj/btsDZ2NhNpc/0E0ktWUPmXlADgrllfk8ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bafcIj/btsDZ2NhNpc/0E0ktWUPmXlADgrllfk8ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbafcIj%2FbtsDZ2NhNpc%2F0E0ktWUPmXlADgrllfk8ek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1203&quot; height=&quot;493&quot; data-origin-width=&quot;1203&quot; data-origin-height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위 그림은 기존에 구현했던 워크플로우 입니다. 문제점은 슬랙에서는 요청을 보낸 후 3초 이내에 응답을 보내주지 않으면 에러를 반환하게 됩니다. &lt;a href=&quot;https://api.slack.com/interactivity/slash-commands#:~:text=This%20confirmation%20must,ignore%20the%20request&quot;&gt;https://api.slack.com/interactivity/slash-commands#:~:text=This%20confirmation%20must,ignore%20the%20request&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;operation_timeout이 반환되지만 실제 응답은 정상적으로 처리되었기에, 사용자 입장에서 혼란이 생길 수 있다 판단에 해당 문제를 해결하고자 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2922&quot; data-origin-height=&quot;990&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhiE8X/btsD1lMiJHB/RnqY86aHvyPe75C1Xz1zCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhiE8X/btsD1lMiJHB/RnqY86aHvyPe75C1Xz1zCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhiE8X/btsD1lMiJHB/RnqY86aHvyPe75C1Xz1zCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhiE8X%2FbtsD1lMiJHB%2FRnqY86aHvyPe75C1Xz1zCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2922&quot; height=&quot;990&quot; data-origin-width=&quot;2922&quot; data-origin-height=&quot;990&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이, API Gateway 요청이 들어오면 1번 함수에서 실제 작업이 진행될 2번 람다 함수를 실행 시킵니다. 그 후 바로 slack으로 응답을 반환시켜 해당 문제를 해결 했습니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;736&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vDZLu/btsD5xRS2aQ/QnsR5kBHQZtohC41y7aM0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vDZLu/btsD5xRS2aQ/QnsR5kBHQZtohC41y7aM0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vDZLu/btsD5xRS2aQ/QnsR5kBHQZtohC41y7aM0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvDZLu%2FbtsD5xRS2aQ%2FQnsR5kBHQZtohC41y7aM0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1410&quot; height=&quot;736&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;736&quot;/&gt;&lt;/span&gt;&lt;/figure&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;간단한 자동화 과정이지만, 이번 작업한 내용을 토대로 추후에 새로운 기능들을 계속해서 추가하고 기존 기능을 고도화 하는등의 작업을 진행하며 Slack Command를 활용한 ChatOps를 만들어보려 합니다.&lt;/p&gt;</description>
      <category>프로그래밍/파이썬</category>
      <category>AWS</category>
      <category>chatops</category>
      <category>python</category>
      <category>자동화</category>
      <author>황심지</author>
      <guid isPermaLink="true">https://simgee.tistory.com/69</guid>
      <comments>https://simgee.tistory.com/69#entry69comment</comments>
      <pubDate>Sat, 27 Jan 2024 15:07:21 +0900</pubDate>
    </item>
    <item>
      <title>2023년, 나의 한 해를 돌아보며: 성장과 변화의 시간</title>
      <link>https://simgee.tistory.com/68</link>
      <description>&lt;h4&gt;서론:&lt;/h4&gt;
&lt;p&gt;2023년을 시작하며 전 직장을 퇴사하고 본격적으로 학습에 집중하기로 마음먹은 시기였습니다. F-LAB 멘토링 과정을 병행하며 백엔드 개발자로서 큰 성장을 이룰 수 있었습니다. (이와 관련한 자세한 내용은 제가 작성한 &amp;#39;F-LAB 후기&amp;#39; 글을 참고해주세요.)&lt;/p&gt;
&lt;p&gt;또한 2023년은 성장뿐만 아니라, 인간 관계, 자기 사랑, 운동 등 육체적, 정신적 측면에서 의미 있는 한 해였습니다.&lt;/p&gt;
&lt;h4&gt;1. F-Lab 자바 백엔드 과정에서 멘토로서의 역할 수행:&lt;/h4&gt;
&lt;p&gt;F-LAB 자바 백엔드 과정을 수료하며 많은 것을 배울 수 있었습니다. 자바 백엔드에 국한된 지식뿐만 아니라, 어떤 언어나 프레임워크에도 적용할 수 있는 폭넓은 지식을 얻었습니다. 현업에서의 다양한 고민도 함께 학습할 수 있었고, 기술적 성장뿐만 아니라 대인 관계 측면에서도 멘토님의 도움을 받아 성장할 수 있었습니다.&lt;/p&gt;
&lt;h4&gt;2.  중요한건 꺾이지 않는 마음&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z8JwQ/btsCUACPPvi/hOz8jvPIoCeVuTMGIUhXp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z8JwQ/btsCUACPPvi/hOz8jvPIoCeVuTMGIUhXp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z8JwQ/btsCUACPPvi/hOz8jvPIoCeVuTMGIUhXp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz8JwQ%2FbtsCUACPPvi%2FhOz8jvPIoCeVuTMGIUhXp1%2Fimg.png&quot; width=&quot;100%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;저는 395번의 지원과 10회 이상의 면접을 거치며 몸과 마음이 많이 지쳤습니다. 그럼에도 불구하고, 끝까지 꾸준히 도전하며 한 번쯤은 원하는 회사에서 일할 수 있지 않을까 하는 희망을 가지고 준비했습니다. 그 결과, 사진과 같이 최종 합격의 기쁨을 맛볼 수 있었고, 현재는 그 회사에서 근무하고 있습니다.&lt;/p&gt;
&lt;p&gt;이 글을 읽는 여러분들도 취업 준비 과정에서 여러 번의 실패를 경험할 수 있지만, 그때마다 자신을 잘 다스리며 &amp;#39;나는 결국 잘 될 것이다&amp;#39;라는 긍정적인 마음가짐으로 도전을 이어가면 결국 성공할 수 있습니다.&lt;/p&gt;
&lt;h4&gt;4. 눈떠보니 파이썬과 Django 사용중?!&lt;/h4&gt;
&lt;p&gt;F-LAB에서 자바 백엔드 멘토링 과정을 수료하며 주로 Java와 Spring을 공부했습니다. 하지만 멘토님의 조언대로 기술이나 언어에 집중하기보다는 원론적인 부분에 집중하여 학습하면 추후 다른 언어나 프레임워크에도 빠르게 적응할 수 있다는 것을 깨달았습니다.&lt;/p&gt;
&lt;p&gt;이러한 학습 방법 덕분에 현재는 Python과 Django를 사용하는 회사에 빠르게 적응하고 업무를 수행하고 있습니다. 테스트 코드 추가, 레거시 코드 리팩토링 등을 진행하며 기존의 업무 방식을 개선하는 데 일조하고 있습니다.&lt;/p&gt;
&lt;h4&gt;5. Hello World != Real World&lt;/h4&gt;
&lt;p&gt;학습 단계에서의 토이 프로젝트와 실무는 정말 다릅니다. 실무에서 경험하는 데이터베이스 테이블의 양, 서버 인프라 구조, 클라우드 서비스의 사용 등은 토이 프로젝트에서는 경험하기 어려운 부분들입니다.&lt;/p&gt;
&lt;p&gt;또한, 객체지향 프로그래밍에 대한 이해를 더욱 심화할 수 있었으며, 실무에서는 이론적인 지식과 함께 실제 적용에 대한 타협과 결정을 내려야 하는 다양한 상황을 직면하게 됩니다.&lt;/p&gt;
&lt;h4&gt;6. 2024년엔 어쩌지?&lt;/h4&gt;
&lt;p&gt;2024년에는 두가지 관점에서 목표를 생각해봤습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;학습&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;업무와 관련된 네트워킹, 커뮤니티 참여 또는 세미나 등에 적극적으로 참여하면서, 실무에 계신 다른개발자분들은 어떻게 업무를 하고 계신지 등등 많은 정보를 얻으려 합니다&lt;/li&gt;
&lt;li&gt;학습 진행(회사 실무와 관련된 부분들을 중점적으로 학습할 예정입니다.)&lt;ul&gt;
&lt;li&gt;리팩토링 책과 레거시 코드 활용 방법 책을 통해 레거시 코드 개편시, 어떻게 접근하는게 좋을지 학습과 더불어 실제 실무 코드에 적용을 해나가볼 생각입니다.&lt;/li&gt;
&lt;li&gt;소프트웨어 아키텍쳐 설계에 대해 학습을 진행함으로써, 코드 작성뿐만 아니라 아키텍쳐 구성에도 학습을 진행할 예정입니다&lt;/li&gt;
&lt;li&gt;쿠버네티스 및, AWS 공부&lt;/li&gt;
&lt;li&gt;Effective Python 학습&lt;/li&gt;
&lt;li&gt;F-LAB에서 진행했던, 사이드 프로젝트 Python과 Django로 포팅&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;취미&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;주말 서퍼 해보기&lt;/li&gt;
&lt;li&gt;스노보드 타러가기&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;마무리하며&lt;/h4&gt;
&lt;p&gt;2023년이 마무리되고, 2024년이 시작되었습니다 작년에 아쉬었던 일들 이루지 못한일들 이젠 다 잊어버리시고 지금 현재에 할일들 하나하나 잘 마무리하고, 이 글을 읽으시는분들 모두 원하시는 일을 해낼 수 있을꺼라 생각합니다. 긴 글 읽어주셔서 감사합니다! 다음글로 찾아뵙겠습니다 (꾸벅)&lt;/p&gt;
&lt;p&gt;더 궁금하신 내용이 있으시면 댓글로 남겨주세요!&lt;/p&gt;</description>
      <category>프로그래밍/회고</category>
      <category>1년회고록</category>
      <category>2023년회고</category>
      <category>개인성장</category>
      <category>개인회고록</category>
      <category>나의이야기</category>
      <category>목표달성</category>
      <category>새해준비</category>
      <category>성장일기</category>
      <category>한해결산</category>
      <author>황심지</author>
      <guid isPermaLink="true">https://simgee.tistory.com/68</guid>
      <comments>https://simgee.tistory.com/68#entry68comment</comments>
      <pubDate>Mon, 1 Jan 2024 21:21:43 +0900</pubDate>
    </item>
    <item>
      <title>HTML 텍스트 처리과정에서 발생하는 공백발생문제해결</title>
      <link>https://simgee.tistory.com/67</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 회사에서 HTML 기반 텍스트 편집기를 사용하던 중, 텍스트에 색상을 적용했을 때 발생하는 불필요한 공백 문제를 경험했습니다. 이는 특히 텍스트 편집기의 특정 플러그인(텍스트 color 등)을 사용할 때 두드러졌습니다. 이러한 문제는 일반적으로 HTML 편집기의 내부 구현이나 CSS 스타일링과 관련이 있을 수 있습니다. 그렇기에 저 또한 처음에는 CSS를 통해 이 문제를 해결하려 했으나, 이 방법은 긴 텍스트에서 레이아웃이 깨지는 새로운 문제를 야기했습니다. 이를 통해 문제의 원인이 HTML이나 CSS 자체가 아니라, 원본 텍스트에 삽입된 불필요한 공백에서 비롯되었음을 파악했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 방법을 찾기 위해 다양한 디버깅 과정을 거쳤고, 특히 문자열 인코딩 함수의 사용 방법에 주목했습니다. 해당 함수에서 position argument를 잘못 사용함으로써 예기치 않은 동작이 발생해 버그가 생긴 것을 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 다음과 같은 코드였습니다.&lt;/p&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;try:
            return self.soup.encode('utf-8','ignore').decode('utf-8') or ''
        except Error:
            return self._get_text(self.soup)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상치 못한 동작의 원인은 encode 함수에 ignore 인자를 잘못 전달한 것이었습니다. encode 함수는 문자열을 지정된 인코딩으로 변환할 때 사용되며, errors 매개변수는 인코딩 과정에서 발생하는 에러를 어떻게 처리할지 결정합니다. 여기서 ignore 옵션을 사용하면 인코딩할 수 없는 문자를 무시하지만, 이 경우 잘못된 위치에 인자를 넣어 문제가 발생했습니다.&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;(method) def encode(
    encoding: str = &quot;utf-8&quot;,
    indent_level: int | None = None,
    formatter: str | Formatter = &quot;minimal&quot;,
    errors: str = &quot;xmlcharrefreplace&quot;
) -&amp;gt; bytes&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 beautifulSoup 4.12.2 버전 기준으로 작성되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return self.soup.encode('utf-8','ignore').decode('utf-8') or '' 코드에서 두번째 인자값에 위치한 ignore가, errors에 해당하는게 아닌, indent_level에 해당하게 되었고, 그 이후 encode함수 처리과정에서 예기치 않은 동작이 발생해 버그가 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 encode 함수의 사용 방법을 수정함으로써 문제를 해결할 수 있었습니다. 코드를 &lt;code&gt;self.soup.encode('utf-8', errors='ignore').decode('utf-8')&lt;/code&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;또한 파이썬을 사용하는 유저라면, positional Argument보다, Keyword Argument사용을 통해 위와 같은 일을 방지하는게 좋을 것 같다고 생각이 들었습니다. 감사합니다&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://m.blog.naver.com/youndok/222299302794&quot;&gt;https://m.blog.naver.com/youndok/222299302794&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://beautiful-soup-4.readthedocs.io/en/latest/index.html?highlight=encode#output-encoding&quot;&gt;https://beautiful-soup-4.readthedocs.io/en/latest/index.html?highlight=encode#output-encoding&lt;/a&gt;&lt;/p&gt;</description>
      <category>프로그래밍/파이썬</category>
      <category>beautifulsoup</category>
      <category>keyword argument</category>
      <category>position argument</category>
      <category>파이썬</category>
      <author>황심지</author>
      <guid isPermaLink="true">https://simgee.tistory.com/67</guid>
      <comments>https://simgee.tistory.com/67#entry67comment</comments>
      <pubDate>Fri, 15 Dec 2023 17:16:20 +0900</pubDate>
    </item>
    <item>
      <title>F-Lab 자바 백엔드 코스 멘토링 후기</title>
      <link>https://simgee.tistory.com/65</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3evsL/btsBldaZw9X/fy0Q66O3YgKIgEEmkOThX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3evsL/btsBldaZw9X/fy0Q66O3YgKIgEEmkOThX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3evsL/btsBldaZw9X/fy0Q66O3YgKIgEEmkOThX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3evsL%2FbtsBldaZw9X%2Ffy0Q66O3YgKIgEEmkOThX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;302&quot; height=&quot;126&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 2022년 10월부터 2023년 1월까지 F-Lab 자바 백엔드 멘토링 과정을 수료한 simgee입니다 :)&lt;br /&gt;이번글을 통해 F-Lab 멘토링을 통해 깨달은 점과 공유드리고 싶은 내용에 대해 작성해보려 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;경험과 성장&lt;/h2&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;그렇다면 어떤 부분에서 성장을 느꼈는가? 라는게 가장 궁금하실 텐데 다른 무엇보다 제가 작성하는 코드와, 아키텍처적 의사결정 사용 기술에 대한 고민 등 여러 부분에서 저의 생각을 녹여낼 수 있는 능력을 기를 수 있었습니다. 예를 들어 인증과 관련된 부분으로 세션방식과 JWT방식 중 어떤 것을 택할지, 현재 프로젝트 상황에 맞춰 생각한 후에 선택해 진행할 수 있었습니다. 그리고 내가 작성한 코드에 대해서도 멘토님의 세세한 피드백을 통해 다시 한번 작성한 코드에 대해 어떤 의도를 담고 코드를 작성했는지(소위 말하는 무지성 코딩)를 생각할 수 있었습니다.&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;프로젝트 이전에 학습하는 과정에서는 단순히 hello world를 타이핑 하고, 학습하는 것이 아닌 자바 메모리 구조나, HTTP에 대한 깊은 이해, 등등 깊이 있게 이해하는 과정을 거치게 되었습니다. 저 같은 경우에는 필요한 CS지식이나 자바, 프레임워크에 대한 지식을 하나하나 공부하기보다는 프로젝트를 진행하면서 필요한 지식에 대해 깊이 파고들다 보면 CS적인 부분과 맞닥뜨리게 되는 경험을 할 수 있었습니다. 이러한 과정을 토대로 현재 실무에서도 깊이 있게 생각하고, 제가 작성한 코드에 대해 다시 한번 생각할 수 있는 기회를 가질 수 있었습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;F-Lab 나만의 팁&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저같은 경우에는 에프랩 과정 중 프로젝트를 진행하면서, 다른 분들이 작성하신 프로젝트도 참고하고, 유명한 기업들의 예시들을 참고하며 프로젝트를 진행했습니다. 그중 굿즈포유 개인 프로젝트를 진행할 당시에 예외 계층 구조에 대한 고민을 할 때, AWS SDK의 예외 계층 구조를 보고 참고해 굿즈포유에도 적용해 보는 등의 경험을 했습니다.&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;
&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;h2 data-ke-size=&quot;size26&quot;&gt;감사의 말&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 저는 스타트업 기업에 취업을 해, 백엔드 개발자로서 일하고 있습니다. 사용하는 언어와 프레임워크가 자바 스프링이 아닌 파이썬과 장고이지만, 결국 F-Lab에서 공부한 컴퓨터 공학 근본적인 지식들을 토대로 업무에 빠르게 적응할 수 있었으며, 멘토링과정에서 멘토님이 주신 회사생활에 대한 팁이나, 앞으로 개발자로서의 자세등을 생각하며 업무를 진행하고 있습니다.&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;F-Lab 팀과 멘토에게 감사의 마음을 표현하며, 멘토링 프로그램으로부터 얻은 소중한 경험에 대한 감사의 말을 덧붙입니다. 에프랩 과정을 신청하는 분들께 이 글이 도움이 되기를 바랍니다. 감사합니다&lt;/p&gt;</description>
      <category>프로그래밍</category>
      <category>F-Lab</category>
      <category>F-Lab 후기</category>
      <category>에프랩</category>
      <category>에프랩 후기</category>
      <category>에프랩 후기 자바 백엔드</category>
      <category>에프랩 후기 자바 백엔드 부트캠프</category>
      <author>황심지</author>
      <guid isPermaLink="true">https://simgee.tistory.com/65</guid>
      <comments>https://simgee.tistory.com/65#entry65comment</comments>
      <pubDate>Sun, 3 Dec 2023 16:35:47 +0900</pubDate>
    </item>
    <item>
      <title>MSW?</title>
      <link>https://simgee.tistory.com/64</link>
      <description>&lt;h2 data-pm-slice=&quot;0 0 []&quot; data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드와 백엔드를 병렬로 개발할 때, 백엔드 API가 아직 완성되지 않아 프론트엔드 개발이 진행되기 어려운 상황이 발생할 수도 있습니다. 이럴 때 MSW(Mock-Service-Worker)를 활용하면 프론트엔드 개발을 용이하게 할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MSW란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSW는 프론트엔드에서 가상의 서버를 생성하여 실제 백엔드와의 통신을 모방하게 해주는 라이브러리입니다. 이를 통해 백엔드가 아직 개발 중일 때도 프론트엔드 개발을 원활하게 진행할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트에서 MSW를 적용한 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 회원가입 시, 이메일 중복여부를 체크해야합니다. 이때 기존에 존재하는 이메일인지 체크하기 위해서는 백엔드 서버 API쪽에 요청을 보내 확인을 해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSW를 이용해 이런 상황에서 백엔드쪽 응답을 Mocking 처리했습니다.&lt;/p&gt;
&lt;div data-width-type=&quot;pixel&quot; data-width=&quot;601&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림처럼 프론트엔드와 백엔드가 통신하는 부분이 Mocking으로 처리되기에, 백엔드와의 결합이 없어져&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 서버의 작업 여부와 상관없이 병렬적으로 작업을 진행할 수 있게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdyIOW/btsAOVoGQc8/hki5FWoz5g9mEJZKI7wgkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdyIOW/btsAOVoGQc8/hki5FWoz5g9mEJZKI7wgkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdyIOW/btsAOVoGQc8/hki5FWoz5g9mEJZKI7wgkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdyIOW%2FbtsAOVoGQc8%2Fhki5FWoz5g9mEJZKI7wgkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;601&quot; height=&quot;418&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>프로그래밍</category>
      <category>msw</category>
      <category>react</category>
      <author>황심지</author>
      <guid isPermaLink="true">https://simgee.tistory.com/64</guid>
      <comments>https://simgee.tistory.com/64#entry64comment</comments>
      <pubDate>Thu, 23 Nov 2023 18:10:15 +0900</pubDate>
    </item>
    <item>
      <title>무중단 배포</title>
      <link>https://simgee.tistory.com/63</link>
      <description>&lt;h3 data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size23&quot;&gt;무중단 배포 왜 해야할까?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 운영중인, 서비스의 신 기능 추가로 인해 버전이 업그레이드 되고 새로운 파일을 배포해야할 때, 다운 타임이 발생하게 됩니다&lt;/li&gt;
&lt;li&gt;다운타임이 발생하는 배포 방식을 중단 배포라고 하며, 이 시간동안은 유저가 서비스를 이용할 수 없기에 유저에게 지속적으로 서비스를 제공하기 위해 무중단 배포 방식을 고려해봐야 합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;무중단 배포&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무중단 배포는 서비스가 중단되지 않은 상태(zero-downtime)로, 새로운 버전을 사용자들에게 배포하는 것을 의미합니다&lt;/li&gt;
&lt;li&gt;무중단 배포를 하기 위해서는 최소 서버가 2대 이상 확보되어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;무중단 배포의 종류&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;롤링(Rolling) 배포&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 운영중인 서비스의 트래픽을 점진적으로 구버전에서 새로운 버전으로 옮기는 방식 입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;점진적으로 트래픽을 옮기는 방식은 크게 2가지 방식이 있습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;방식 1&lt;/h4&gt;
&lt;div data-width-type=&quot;pixel&quot; data-width=&quot;720&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;797e41c0-a01b-4b24-b0f5-1a10565ca033.gif&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/znjF9/btsz9kDCBZX/Pr3pK7aVlMkTuNloSjuJK0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/znjF9/btsz9kDCBZX/Pr3pK7aVlMkTuNloSjuJK0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/znjF9/btsz9kDCBZX/Pr3pK7aVlMkTuNloSjuJK0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/znjF9/btsz9kDCBZX/Pr3pK7aVlMkTuNloSjuJK0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;405&quot; data-filename=&quot;797e41c0-a01b-4b24-b0f5-1a10565ca033.gif&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인스턴스를 하나 추가하고, 새로운 버전을 실행한다.&lt;/li&gt;
&lt;li&gt;로드 밸런서에 새로운 인스턴스를 연결하고, 기존 구버전 어플리케이션이 실행되는 인스턴스를 하나 줄인다&lt;/li&gt;
&lt;li&gt;그 후, 나머지 인스턴스도 버전을 변경한다&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 개수를 유연하게 조절할 수 있는 클라우드 기반 환경에 적합할 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;방식 2&lt;/h4&gt;
&lt;div data-width-type=&quot;pixel&quot; data-width=&quot;720&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.gif&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mMuyF/btsz9WPXxUE/EQICJH9lazAWod1TQJy8qK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mMuyF/btsz9WPXxUE/EQICJH9lazAWod1TQJy8qK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mMuyF/btsz9WPXxUE/EQICJH9lazAWod1TQJy8qK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/mMuyF/btsz9WPXxUE/EQICJH9lazAWod1TQJy8qK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;405&quot; data-filename=&quot;2.gif&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;V1이 실행되고 있는 서버 하나를 로드밸런서에서 떼어낸다.&lt;/li&gt;
&lt;li&gt;이 상태에서 해당 서버의 어플리케이션을 V2로 교체한다&lt;/li&gt;
&lt;li&gt;이 과정을 반복해 모든 서버를 새 버전으로 교체한다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방식2의 경우 클라우드 환경이 아닌, 온프레미스 환경에서도 사용할 수 있을것이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;롤링 방식의 장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;롤링 배포 방식은 k8s, elastic beanstalk와 같은 많은 오케스트레이션 도구에서 지원하여 간편하게 구축할 수 있다.&lt;/li&gt;
&lt;li&gt;많은 서버 자원을 확보하지 않아도 무중단 배포가 가능하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;롤링 방식의 단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;방식 2와 같은 경우, 배포 도중 서비스 중인 인스턴스 수가 줄어들게 되므로 그에 따른 서버가 부담하는 트래픽의 양이 늘어 날 수 있기에 주의해야 한다&lt;/li&gt;
&lt;li&gt;또한 구버전과 신버전의 어플리케이션이 동시에 서비스되기 때문에 호환성 문제가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Blue/Green 배포&lt;/h2&gt;
&lt;div data-width-type=&quot;pixel&quot; data-width=&quot;720&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blue-green.gif&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z95N6/btsz9j5LCb6/RtMOGPnAkCTBc4MxJEPYQ0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z95N6/btsz9j5LCb6/RtMOGPnAkCTBc4MxJEPYQ0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z95N6/btsz9j5LCb6/RtMOGPnAkCTBc4MxJEPYQ0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/z95N6/btsz9j5LCb6/RtMOGPnAkCTBc4MxJEPYQ0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;405&quot; data-filename=&quot;blue-green.gif&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트래픽을 한번에 구버전에서 신버전으로 옮기는 방법이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 운영중인 서비스의 환경을 Blue라고 부르고, 새롭게 배포할 환경을 Green이라 부른다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배포 진행 방식&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Blue와 Green 환경을 동시에 구성해둔다.&lt;/li&gt;
&lt;li&gt;배포 시점에 로드 밸런서가 트래픽을 Blue에서 Green으로 일제히 전환 시킨다.&lt;/li&gt;
&lt;li&gt;Green버전 배포가 성공적으로 완료 되었고, 문제가 없다고 판단시 Blue 서버를 제거할 수 있다&lt;br /&gt;혹은 다음 배포를 위해 유지해둘 수도 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Blue/Green 방식의 장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;롤링 배포와 달리 한번에 새로운 버전으로 옮기기 때문에 호환성 문제가 발생하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Blue/Green 방식의 단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Blue Green 환경을 구성해야 하기에, 실제 운영에 필요한 서버 리소스 대비 2배의 리소스를 확보해야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라우드 환경에서는 필요없는 인스턴스를 제거하거나, 배포가 완료된 Blue환경을 제거하면 되지만&lt;br /&gt;온프레미스 방식이라면 운영비용 부담이 클 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;카나리(Canary) 배포&lt;/h2&gt;
&lt;div data-width-type=&quot;pixel&quot; data-width=&quot;720&quot; data-layout=&quot;center&quot; data-node-type=&quot;mediaSingle&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;canary.gif&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YqgAa/btsAaEVumIy/EZIpjVljdUkPBEYMfPOQ9k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YqgAa/btsAaEVumIy/EZIpjVljdUkPBEYMfPOQ9k/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YqgAa/btsAaEVumIy/EZIpjVljdUkPBEYMfPOQ9k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/YqgAa/btsAaEVumIy/EZIpjVljdUkPBEYMfPOQ9k/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;405&quot; data-filename=&quot;canary.gif&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카나리는 롤링 배포 방식과 비슷하지만, 새로운 버전에 대한 오류를 조기에 감지하는 것이 다르다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;카나리 배포 방식&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;소수 인원에 대해서만 트래픽을 새로운 버전에 옮겨둔 상태에서 서비스를 운영한다&lt;/li&gt;
&lt;li&gt;새로운 버전에 이상이 없다고 판단하였을 경우에만 모든 트래픽을 신규 버전으로 옮긴다
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이때, 트래픽을 새로운 버전으로 옮기는 기준은 정해진 규칙(특정 유저 등)혹은 랜덤이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;카나리 배포방식의 장점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 버전으로 인한 위험을 최소화 할 수 있다.&lt;/li&gt;
&lt;li&gt;소수 인원에 대해서만 트래픽을 옮기는 특성으로 인해 A/B 테스트를 진행하기에도 적합한 방식이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;카나리 배포방식의 단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;롤링 배포와마찬가지로 신/구 버전의 애플리케이션이 동시에 존재하므로 호환성 문제가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://hudi.blog/zero-downtime-deployment/&quot;&gt;https://hudi.blog/zero-downtime-deployment/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로그래밍/Infra</category>
      <category>deploy</category>
      <category>무중단 배포</category>
      <category>배포 방식</category>
      <author>황심지</author>
      <guid isPermaLink="true">https://simgee.tistory.com/63</guid>
      <comments>https://simgee.tistory.com/63#entry63comment</comments>
      <pubDate>Sun, 12 Nov 2023 14:51:32 +0900</pubDate>
    </item>
  </channel>
</rss>