<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발자 김현수</title>
    <link>https://kimhyun5u.tistory.com/</link>
    <description>안녕하세요</description>
    <language>ko</language>
    <pubDate>Wed, 6 May 2026 12:31:17 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>kimhyun5u</managingEditor>
    <item>
      <title>동시성 제어에 관하여</title>
      <link>https://kimhyun5u.tistory.com/21</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우아한 테크 캠프에서 팀 프로젝트를 진행하면서 주문 시스템을 최적화 하기 위해서 많은 고민을 했다. 그 중 재고의 정합성을 유지하면서 속도를 빠르게 하고자 하였는데, 기존 비관적 락을 사용하면 같은 메뉴에 대한 요청이 계속 대기하면서 성능 저하가 발생했습니다. 그래서 원자적 연산을 적용해 DB의 락을 제거하고 레디스로 재고 관리를 위임하면서 2-3배의 성능 개선을 이뤄냈다.&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-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkwYE1/btsJxYwUucD/UwaVdUEk0ZLDMR4nGazFCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkwYE1/btsJxYwUucD/UwaVdUEk0ZLDMR4nGazFCk/img.png&quot; data-alt=&quot;한빛 미디어 &amp;amp;lt;동시성 프로그래밍&amp;amp;gt;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkwYE1/btsJxYwUucD/UwaVdUEk0ZLDMR4nGazFCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkwYE1%2FbtsJxYwUucD%2FUwaVdUEk0ZLDMR4nGazFCk%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;300&quot; height=&quot;385&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;385&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;한빛 미디어 &amp;lt;동시성 프로그래밍&amp;gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동기 처리&lt;/h3&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;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;mermaid&quot;&gt;sequenceDiagram
participant 고객A
participant 고객B
participant 웹서버
participant 데이터베이스
Note over 데이터베이스: 초기 재고: 10개
par 고객A의 주문
고객A-&gt;&gt;웹서버: 상품 5개 주문
웹서버-&gt;&gt;데이터베이스: 재고 확인 (10개 남음)
웹서버-&gt;&gt;데이터베이스: 재고 감소 (5개)
데이터베이스--&gt;&gt;웹서버: 주문 성공
웹서버--&gt;&gt;고객A: 주문 완료
and 고객B의 주문
고객B-&gt;&gt;웹서버: 상품 8개 주문
웹서버-&gt;&gt;데이터베이스: 재고 확인 (10개 남음)
웹서버-&gt;&gt;데이터베이스: 재고 감소 (8개)
데이터베이스--&gt;&gt;웹서버: 주문 성공
웹서버--&gt;&gt;고객B: 주문 완료
end
Note over 데이터베이스: 최종 재고: -3개 (오류 상태)&lt;/div&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중 원자적 연산(Atomic Operation) 에 대해서 알아보자.&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;원자적 연산&lt;/h3&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 class=&quot;mermaid&quot;&gt;sequenceDiagram
participant 스레드A
participant 메모리
participant 스레드B
Note over 메모리: 초기값: 10
par 스레드A의 연산
스레드A-&gt;&gt;메모리: 값 읽기 (10)
스레드A-&gt;&gt;스레드A: 10 + 1 계산
스레드A-&gt;&gt;메모리: 11 저장
and 스레드B의 연산
스레드B-&gt;&gt;메모리: 값 읽기 (10)
스레드B-&gt;&gt;스레드B: 10 + 1 계산
스레드B-&gt;&gt;메모리: 11 저장
end
Note over 메모리: 최종값: 11 (예상: 12)&lt;/div&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;원자적 연산을 구현하는 방법은 Compare And Set(CAS), Test And Set(TAS), Load-Link/Store-Conditional 등 여러 가지가 있다.&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;CAS(Compare And Set)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CAS는 값을 연산 전 값과 현재 메모리의 값을 비교해 같을 경우에만 연산이 진행되도록 하는 방법이다. 아래와 같은 의사 코드로 구현할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1725951857330&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# CAS 의사 코드
function CAS(address: *int, expected: int, new_value: int) -&amp;gt; bool:
    atomic {
        if *address == expected:
            *address = new_value
            return true
        else:
            return false
    }

function atomic_increment(address: *int) -&amp;gt; int:
    while true:
        old_value = *address
        new_value = old_value + 1
        if CAS(address, old_value, new_value):
            return new_value&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TAS(Test And Set)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TAS는 직접적으로 원자적 연산을 지원하지는 않지만 TAS를 활용해 원자적 연산을 구현할 수 있다. TAS는 해당 값이 True이면 True를 반환하고 False이면 True로 변환하고 False를 반환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1725951880375&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# TAS 의사코드
function TestAndSet(address: *bool) -&amp;gt; bool:
    atomic {
        old_value = *address
        *address = true
        return old_value
    }
    
    
function atomic_operation():
    while TestAndSet(&amp;amp;lock):
        // 스핀
    
    // 임계 영역 시작
    // 여기서 원하는 연산 수행
    shared_value = some_calculation(shared_value)
    // 임계 영역 끝
    
    lock = false  // 락 해제&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;h4 data-ke-size=&quot;size20&quot;&gt;Load-Link/Store-Conditional&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LL(Load-Link)는 메모리에서 값을 읽을 때 베타적으로 수행할 수 있도록 지정하는 명령이다. SC(Store-Conditional)을 통해 다른 CPU 가 LL 로 지정한 메모리로의 쓰기가 발생하지 않았을 때만 값을 저장하고 실패할 경우 다시 읽기와 쓰기를 수행한다. 이론적으로 CAS 보다 성능이 좋고 CAS에서 발생할 수 있는 &lt;a href=&quot;https://en.wikipedia.org/wiki/ABA_problem&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ABA문제&lt;/a&gt;를 방지할 수 있는 장점이 있지만, x86 아키텍쳐에서는 지원하지 않아 하드웨어에 종속되는 단점을 가지고 있기 때문에 CAS를 더 많이 사용한다.&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;&lt;span style=&quot;background-color: #fffffe; color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;원자적 연산을 통한 동시성 제어의 이점&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fffffe; color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;원자적 연산을 통한 동시성 제어의 이점은 기존 원시적 락 방식에 비해 짧은 락 지속 시간&lt;span style=&quot;background-color: #fffffe; color: #000000; text-align: start;&quot;&gt;(보통 단위 연산 명령 실행 기간)&lt;/span&gt;과 작은 범위의 장점을 가지고 있어 큰 장점을 가지고 있다.&amp;nbsp;&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: #fffffe; color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;다음은 레디스를 활용한 원자적 연산 방식과 DB 락을 활용한 방식의 차이를 보여주는 시퀀스 다이어그램이다.&lt;/span&gt;&lt;/p&gt;
&lt;div class=&quot;mermaid&quot;&gt;sequenceDiagram
participant 고객A
participant 고객B
participant 웹서버
participant Redis
participant DB
Note over DB: 초기 재고: 10개
rect rgb(200, 230, 200)
Note right of 고객A: Redis 활용 원자적 연산
par 고객A의 주문
고객A-&gt;&gt;웹서버: 상품 5개 주문
웹서버-&gt;&gt;Redis: DECRBY stock 5
Redis--&gt;&gt;웹서버: 새 재고 값 (5)
웹서버-&gt;&gt;DB: 주문 정보 저장 (비동기)
웹서버--&gt;&gt;고객A: 주문 완료
and 고객B의 주문
고객B-&gt;&gt;웹서버: 상품 8개 주문
웹서버-&gt;&gt;Redis: DECRBY stock 8
Redis--&gt;&gt;웹서버: 새 재고 값 (-3)
웹서버--&gt;&gt;고객B: 재고 부족
end
end
Note over Redis: 최종 재고: 5개 (정확한 값)
Note over DB: 최종 재고: 5개 (정확하지만 처리 지연)
rect rgb(230, 200, 200)
Note right of 고객A: 기존 DB 락 방식
고객A-&gt;&gt;웹서버: 상품 5개 주문
웹서버-&gt;&gt;DB: 락 획득 시도
DB--&gt;&gt;웹서버: 락 획득
웹서버-&gt;&gt;DB: 재고 확인 및 갱신
DB--&gt;&gt;웹서버: 갱신 완료
웹서버-&gt;&gt;DB: 락 해제
웹서버--&gt;&gt;고객A: 주문 완료
Note over 고객B: 대기...
고객B-&gt;&gt;웹서버: 상품 8개 주문
웹서버-&gt;&gt;DB: 락 획득 시도 (대기)
DB--&gt;&gt;웹서버: 락 획득
웹서버-&gt;&gt;DB: 재고 확인 및 갱신
DB--&gt;&gt;웹서버: 재고 부족
웹서버-&gt;&gt;DB: 락 해제
웹서버--&gt;&gt;고객B: 재고 부족
end
Note over DB: 최종 재고: 5개 (실시간 처리)&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;맺음말&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책을 읽고 추상적으로 알고 있었던 내용을 조금 구체적으로 알 수 있었던 것 같다. 아직 책을 다 읽지는 않아서 끝까지 읽어보고 내용을 더 수정해봐야겠다.&lt;/p&gt;

&lt;script src=&quot;https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js&quot;&gt;&lt;/script&gt;
&lt;script&gt;mermaid.initialize({startOnLoad:true});&lt;/script&gt;</description>
      <category>개발</category>
      <author>kimhyun5u</author>
      <guid isPermaLink="true">https://kimhyun5u.tistory.com/21</guid>
      <comments>https://kimhyun5u.tistory.com/21#entry21comment</comments>
      <pubDate>Tue, 10 Sep 2024 17:29:46 +0900</pubDate>
    </item>
    <item>
      <title>주문 시스템 성능 개선기</title>
      <link>https://kimhyun5u.tistory.com/20</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n6CjN/btsJp45zpCH/bKeVnWlGfb68S53dsTD0D1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n6CjN/btsJp45zpCH/bKeVnWlGfb68S53dsTD0D1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n6CjN/btsJp45zpCH/bKeVnWlGfb68S53dsTD0D1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn6CjN%2FbtsJp45zpCH%2FbKeVnWlGfb68S53dsTD0D1%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;390&quot; height=&quot;267&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;개요&lt;/h1&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;h1&gt;접근 방식&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희는 클라우드 자원으로 ec2 t3.small과 rds t3.micro를 지원받았습니다. 그렇기 때문에 한정된 자원에서 성능향상을 이루기 위해서 현재 가장 부하가 많은 곳을 개선하려고 노력했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 가장 많은 병목이 발생하는 부분은 DB라고 생각했기 떄문에 DB 락을 중심으로 문제를 접근했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSWk6g/btsJp9eAl6Y/Rcs88YrF7OAus6UpLXdYa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSWk6g/btsJp9eAl6Y/Rcs88YrF7OAus6UpLXdYa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSWk6g/btsJp9eAl6Y/Rcs88YrF7OAus6UpLXdYa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSWk6g%2FbtsJp9eAl6Y%2FRcs88YrF7OAus6UpLXdYa1%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;1130&quot; height=&quot;418&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;우리가 시도한 방법들&lt;/h1&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;하지만, 다음과 같은 상황에서 트랜잭션이 오래 락을 점유하고 있으면 다음 트랜잭션들이 DB 커넥션을 계속 차지해 DB 커넥션이 낭비될 수 있다는 단점이 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUh4v7/btsJoDVCPOD/fFmkaFsoEjzEHlcaZ0VAb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUh4v7/btsJoDVCPOD/fFmkaFsoEjzEHlcaZ0VAb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUh4v7/btsJoDVCPOD/fFmkaFsoEjzEHlcaZ0VAb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUh4v7%2FbtsJoDVCPOD%2FfFmkaFsoEjzEHlcaZ0VAb1%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;1170&quot; height=&quot;530&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;메뉴에 버전을 두어 조회 시점의 버전보다 늘은 경우 재시도를 하도록 하는 로직을 적용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 오히려 WAS에서는 재시도 하면서 DB I/O가 증가하고 DB에서는 실패할 쿼리들이 연결되어 오히려 DB 커넥션을 낭비하게 됩니다. 성능도 오히려 비관락보다 안좋았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brWGEA/btsJpqurh3K/Tlrvi92GlFroEdo7S5srWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brWGEA/btsJpqurh3K/Tlrvi92GlFroEdo7S5srWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brWGEA/btsJpqurh3K/Tlrvi92GlFroEdo7S5srWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrWGEA%2FbtsJpqurh3K%2FTlrvi92GlFroEdo7S5srWK%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;1208&quot; height=&quot;530&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 저희는 여러번 DB를 접근하지 않고 다른 곳에서 아예 락을 관리해보자고 생각합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;분산락&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 'Redis를 통해 락을 관리하면 DB 커넥션을 효율적이게 쓸 수 있지 않을까?' 라는 생각으로 분산락을 적용하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 분산 서버가 아닌 상황에서 분산락을 적용할 이유가 거의 없지만 장바구니에서 redis를 사용하기도 하고 DB 커넥션을 최적화할 수 있다고 생각해서 적용해봤습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NC6cY/btsJqaYUurW/GKuxdvK2YeLuzje6bMEsF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NC6cY/btsJqaYUurW/GKuxdvK2YeLuzje6bMEsF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NC6cY/btsJqaYUurW/GKuxdvK2YeLuzje6bMEsF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNC6cY%2FbtsJqaYUurW%2FGKuxdvK2YeLuzje6bMEsF1%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;1172&quot; height=&quot;530&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;다음과 같이 재고를 캐싱해서 redis 에서 재고를 관리하고 일정 기간마다 DB에 값을 맞춰 주는 방식을 적용해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재고를 감소시킬 때 Redis 에서 제공하는 원자적 연산으로 한번에 감소시키는 방식을 사용했습니다. 이런 구조를 적용하면 재고를 감소할 때 경쟁 상태가 발생하지 않으며 DB I/O 횟수도 크게 감소시킬 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ9pzG/btsJqflrNdu/N17iPIkWyGrOzjF3u7dzE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ9pzG/btsJqflrNdu/N17iPIkWyGrOzjF3u7dzE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ9pzG/btsJqflrNdu/N17iPIkWyGrOzjF3u7dzE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ9pzG%2FbtsJqflrNdu%2FN17iPIkWyGrOzjF3u7dzE1%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;1174&quot; height=&quot;700&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;700&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식은 성능은 향상시킬 수 있었지만 DB 정합성에 대한 문제가 있을 수도 있습니다. 때문에 Redis 가 장애가 났을 때 DB에 싱크를 맞출 수 있도록 적절한 조치가 필요할 수도 있습니다.&lt;/p&gt;
&lt;h1&gt;성능 비교&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;성능 테스트는 Locust 로 진행하였고 User 는 1만명 ramp up 은 100으로 두었습니다. 시나리오는 한 매장에 모든 메뉴를 여러 사용자가 주문하는 방식으로 경쟁 상태가 많이 발생하도록 구성했습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능은 캐시가 응답속도 면에서 2-3배 빠른 향상을 보였습니다. 처음 테스트를 진행할 때는 장애 대비가 되어 있지 않아 DB 싱크를 매 순간마다 해줘서 성능의 차이가 없었지만 DB 싱크를 따로 스케줄링을 돌려 처리하니 큰 성능 향상을 보이는 것을 알 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Redis 장애에 대한 대비가 되어 있지 않았지만 데모 데이 피드백을 받고 한번 적용을 해보았습니다.)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비관락&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;# Requests&lt;/th&gt;
&lt;th&gt;# Fails&lt;/th&gt;
&lt;th&gt;Average (ms)&lt;/th&gt;
&lt;th&gt;Min (ms)&lt;/th&gt;
&lt;th&gt;Max (ms)&lt;/th&gt;
&lt;th&gt;Average size (bytes)&lt;/th&gt;
&lt;th&gt;RPS&lt;/th&gt;
&lt;th&gt;Failures/s&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/orders&lt;/td&gt;
&lt;td&gt;13486&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;17538.89&lt;/td&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;50876&lt;/td&gt;
&lt;td&gt;38.18&lt;/td&gt;
&lt;td&gt;30.92&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;50%ile (ms)&lt;/th&gt;
&lt;th&gt;60%ile (ms)&lt;/th&gt;
&lt;th&gt;70%ile (ms)&lt;/th&gt;
&lt;th&gt;80%ile (ms)&lt;/th&gt;
&lt;th&gt;90%ile (ms)&lt;/th&gt;
&lt;th&gt;95%ile (ms)&lt;/th&gt;
&lt;th&gt;99%ile (ms)&lt;/th&gt;
&lt;th&gt;100%ile (ms)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/orders&lt;/td&gt;
&lt;td&gt;14000&lt;/td&gt;
&lt;td&gt;16000&lt;/td&gt;
&lt;td&gt;19000&lt;/td&gt;
&lt;td&gt;31000&lt;/td&gt;
&lt;td&gt;39000&lt;/td&gt;
&lt;td&gt;43000&lt;/td&gt;
&lt;td&gt;49000&lt;/td&gt;
&lt;td&gt;51000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;재고 캐싱&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;# Requests&lt;/th&gt;
&lt;th&gt;# Fails&lt;/th&gt;
&lt;th&gt;Average (ms)&lt;/th&gt;
&lt;th&gt;Min (ms)&lt;/th&gt;
&lt;th&gt;Max (ms)&lt;/th&gt;
&lt;th&gt;Average size (bytes)&lt;/th&gt;
&lt;th&gt;RPS&lt;/th&gt;
&lt;th&gt;Failures/s&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/orders&lt;/td&gt;
&lt;td&gt;26718&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;8715.08&lt;/td&gt;
&lt;td&gt;479&lt;/td&gt;
&lt;td&gt;18227&lt;/td&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;48.95&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;50%ile (ms)&lt;/th&gt;
&lt;th&gt;60%ile (ms)&lt;/th&gt;
&lt;th&gt;70%ile (ms)&lt;/th&gt;
&lt;th&gt;80%ile (ms)&lt;/th&gt;
&lt;th&gt;90%ile (ms)&lt;/th&gt;
&lt;th&gt;95%ile (ms)&lt;/th&gt;
&lt;th&gt;99%ile (ms)&lt;/th&gt;
&lt;th&gt;100%ile (ms)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;/orders&lt;/td&gt;
&lt;td&gt;8400&lt;/td&gt;
&lt;td&gt;8900&lt;/td&gt;
&lt;td&gt;9900&lt;/td&gt;
&lt;td&gt;11000&lt;/td&gt;
&lt;td&gt;14000&lt;/td&gt;
&lt;td&gt;15000&lt;/td&gt;
&lt;td&gt;17000&lt;/td&gt;
&lt;td&gt;18000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;결론&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐싱을 활용하는 방식은 DB에서 실시간으로 재고 수량의 정합성이 맞춰지진 않지만 큰 성능 향상을 보여 주문이 많이 몰리는 상황에서는 좋은 구조가 될 수 있을 것 같습니다.&lt;/p&gt;
&lt;h1&gt;회고&lt;/h1&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;&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;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;++ 캐시 웜업&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;저희 서비스는 주문을 하기 위해서는 카트를 담는 행위가 선행되어야 하기 때문에 카트에 담을 때 재고를 redis에 동기화 해주었습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;서버를 처음 실행할 때에 모든 메뉴를 동기화하는 것은 750만개의 데이터를 모두 담는 것은 서버에 큰 부하가 있을 것 같아 패스했습니다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발</category>
      <category>redis</category>
      <category>우아한테크캠프</category>
      <author>kimhyun5u</author>
      <guid isPermaLink="true">https://kimhyun5u.tistory.com/20</guid>
      <comments>https://kimhyun5u.tistory.com/20#entry20comment</comments>
      <pubDate>Tue, 3 Sep 2024 18:45:46 +0900</pubDate>
    </item>
    <item>
      <title>CSV Driver SELECT 요청 개선기</title>
      <link>https://kimhyun5u.tistory.com/19</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;i&gt;★&lt;/i&gt;이 글은 강승훈 님의 의견을 반영해 수정되었습니다.★&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;b&gt;단어수 : 391개 읽는 시간: 2분&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z6GkH/btsISVCgZ1V/PSOvLJ7LVntmih9UIPgQm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z6GkH/btsISVCgZ1V/PSOvLJ7LVntmih9UIPgQm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z6GkH/btsISVCgZ1V/PSOvLJ7LVntmih9UIPgQm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz6GkH%2FbtsISVCgZ1V%2FPSOvLJ7LVntmih9UIPgQm0%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;390&quot; height=&quot;267&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;u&gt;&lt;i&gt;&lt;b&gt;미리 보기&lt;/b&gt;&lt;/i&gt;&lt;/u&gt;&lt;br /&gt;- CSV 파일로 DB를 구현했다. &lt;br /&gt;- 마지막에 있는 데이터를 찾으려면 모든 데이터를 탐색해야한다. &lt;br /&gt;- 탐색 시간을 최적화 해 1900배 감소시켰다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WAS 미션 마지막에 CSV 파일을 활용하여 DB 를 만드는 미션이 있었습니다. 당시에 구현할 시간이 별로 없어서 단순하게 파일의 끝에 새로운 데이터를 추가하고 모든 라인을 탐색하면서 파일을 찾도록 구현했습니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그러다가 데모하면서 마스터님이 1GB 파일이 있을 때는 어떻게 탐색할 거냐고 물어보셨습니다. 그때 저는 별로 생각을 안하고 있었는데 너무 느릴 거 같다고 생각이 들었습니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터가 많으면 얼마나 성능이 안좋을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번 제가 구현한 코드로 1억개의 데이터(약 4GB)를 넣고 마지막 레코드를 찾아보았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2399&quot; data-origin-height=&quot;1366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nMIbV/btsIVi3yxU7/0yQALiPsiC1Y2LLzQk6iK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nMIbV/btsIVi3yxU7/0yQALiPsiC1Y2LLzQk6iK1/img.png&quot; data-alt=&quot;개선 전 소요 시간&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nMIbV/btsIVi3yxU7/0yQALiPsiC1Y2LLzQk6iK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnMIbV%2FbtsIVi3yxU7%2F0yQALiPsiC1Y2LLzQk6iK1%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;2399&quot; height=&quot;1366&quot; data-origin-width=&quot;2399&quot; data-origin-height=&quot;1366&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개선 전 소요 시간&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 약 19초가 걸렸습니다.&lt;br /&gt;이 문제를 조금은 해결해보겠습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;현대 데이터베이스는 인덱스를 활용해서 문제를 해결하지만 저는 데이터를 id 기반으로 범위를 나눠서 저장하고 불러오는 방식으로 접근해보았습니다. 이러한 방식을 &lt;s&gt;샤딩&lt;/s&gt;수평 파티셔닝, &lt;s&gt;범위 기반의 샤딩&lt;/s&gt;이라고 부르는데요. 이렇게 하면 아무리 큰 id 값을 찾는다고해도 모두 비슷한 탐색 시간이 소요됩니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;s&gt;샤딩 &lt;/s&gt;수평 파티셔닝&amp;nbsp;크기를 정해서 그 이상의 데이터가 들어오면 다른 csv 파일에 저장하도록 만들었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oRwDV/btsIS7CDKEZ/81FPJgdNJuwWOYqGkmLuP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oRwDV/btsIS7CDKEZ/81FPJgdNJuwWOYqGkmLuP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oRwDV/btsIS7CDKEZ/81FPJgdNJuwWOYqGkmLuP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoRwDV%2FbtsIS7CDKEZ%2F81FPJgdNJuwWOYqGkmLuP0%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;449&quot; height=&quot;445&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;709&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 테이블 별 샤딩의 정보를 가지고 있는 ShardingInfo 객체를 만들어 이를 관리하도록 합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;CREATE 나 INSERT 연산 시 마지막 id 값을 기록합니다.&lt;br /&gt;이러한 값들을 sharding_info.csv 에 기록하고 메모리 위에도 Map 구조로 가지고 있습니다.&lt;br /&gt;서버 실행 시 sharding_info.csv 에서 값을 읽어와 초기화 해줍니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;저는 1000 개를 기준으로 데이터를 관리하도록 수정했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxPwwS/btsITjizCz2/b8Jt41Kxla7t8uLcDg7eFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxPwwS/btsITjizCz2/b8Jt41Kxla7t8uLcDg7eFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxPwwS/btsITjizCz2/b8Jt41Kxla7t8uLcDg7eFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxPwwS%2FbtsITjizCz2%2Fb8Jt41Kxla7t8uLcDg7eFK%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;606&quot; height=&quot;480&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://github.com/kimhyun5u/java-was/commit/47fc17adf3aeff00ef7d0c28ea62a52602746151&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;변경 코드 링크 보기&lt;/span&gt;&lt;/a&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;개선 후 1억번째 데이터를 찾는 시간은 10.44 ms 입니다&amp;nbsp;  &lt;br /&gt;이전 방식보다는 확연히 빨라진 모습입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2435&quot; data-origin-height=&quot;1360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dP3Fqc/btsITUCHCpl/PK1gW9KXPmMJCtuQC7VhqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dP3Fqc/btsITUCHCpl/PK1gW9KXPmMJCtuQC7VhqK/img.png&quot; data-alt=&quot;개선 후 소요 시간&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dP3Fqc/btsITUCHCpl/PK1gW9KXPmMJCtuQC7VhqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdP3Fqc%2FbtsITUCHCpl%2FPK1gW9KXPmMJCtuQC7VhqK%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;2435&quot; height=&quot;1360&quot; data-origin-width=&quot;2435&quot; data-origin-height=&quot;1360&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개선 후 소요 시간&lt;/figcaption&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;샤딩의 범위에 따라서 성능이 차이가 날 수 밖에 없기 때문에 여러 조건을 맞춰서 설정하는 것이 중요합니다.&lt;br /&gt;그리고 여러 csv 파일을 만들면서 문제가 발생했는데 멀티 스레딩 환경에서 파일을 동시에 접근하는 문제가 발생했습니다.&lt;br /&gt;또한, id 값을 제외한 조건문을 걸면 연산 속도가 이전과 똑같습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;여러 문제가 남아있지만 다음에 천천히 해결해보겠습니다..  &lt;/p&gt;</description>
      <category>개발</category>
      <author>kimhyun5u</author>
      <guid isPermaLink="true">https://kimhyun5u.tistory.com/19</guid>
      <comments>https://kimhyun5u.tistory.com/19#entry19comment</comments>
      <pubDate>Mon, 5 Aug 2024 00:28:46 +0900</pubDate>
    </item>
    <item>
      <title>내가 만든 was, Maven에 올렸지</title>
      <link>https://kimhyun5u.tistory.com/18</link>
      <description>&lt;h1&gt;개요&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우아한 테크 캠프를 진행하면서 was를 직접 개발하는 경험을 했습니다. 3주 동안 고생하며 만든 제 was에 정이 들었는지 실제 maven 서버에 등록하고 싶은 마음이 생겨서 바로 어떻게 올리고 싶었는데 몇 년 전과 비교해서 달라진 게 많고 생각보다 자료를 찾기 어려워서 이틀 동안 매달려서 겨우 성공했습니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분들은 쉽고 빠르게 성공할 수 있도록 저의 과정을 자세히 설명해 드리겠습니다.&lt;/p&gt;
&lt;h1&gt;올리기 전에 알고 가면 좋은 것&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Maven Central Repository 란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Maven Central Repository는 Java 개발자들이 가장 많이 사용하는 공개 아티팩트 저장소입니다. 이 저장소는 수많은 오픈 소스 Java 라이브러리, 프레임워크, 그리고 도구들을 호스팅 하고 있습니다. Sonatype이 운영하며, Apache Maven 프로젝트의 공식 저장소로 인정받고 있습니다.&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;pom.xml(Maven) 또는 build.gralde(gradle)을 통한 의존성 선언&lt;/li&gt;
&lt;li&gt;설정된 repository에서 선언된 의존성의 프로젝트를 다운로드한다.&lt;/li&gt;
&lt;li&gt;로컬 저장소(~/. m2/repository)에 캐싱해 재사용할 수 있도록 한다.&lt;/li&gt;
&lt;li&gt;클래스 패스에 자동으로 추가되어 코드에서 바로 사용할 수 있다~&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 조금 알면 좋다고 생각하는 부분은 외부 프로젝트를 가져오는 repository의 정보는 ~/. m2/settings.xml에 저장되어 있습니다. 설정을 수정하면 다른 repository에 있는 정보를 가져올 수 있습니다! 그래서 내부망에서 repository를 구축하면 라이브러리를 쉽게 공유할 수 있도록 할 수 있죠 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 repository 정보를 수정할 수 있는 mirror tag입니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;&amp;lt;settings&amp;gt;
  ...
  &amp;lt;mirrors&amp;gt;
    &amp;lt;mirror&amp;gt;
      &amp;lt;id&amp;gt;custom-central&amp;lt;/id&amp;gt;
      &amp;lt;name&amp;gt;Custom Central Repository&amp;lt;/name&amp;gt;
      &amp;lt;url&amp;gt;https://your-custom-repo-url.com/repo&amp;lt;/url&amp;gt;
      &amp;lt;mirrorOf&amp;gt;central&amp;lt;/mirrorOf&amp;gt;
    &amp;lt;/mirror&amp;gt;
  &amp;lt;/mirrors&amp;gt;
  ...
&amp;lt;/settings&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Maven Central Repository에 올려보기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Maven에 프로젝트를 올리는 방법에 대해서 설명하겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SonaType Central 가입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Maven Central Repository를 운영하는 SonaType에 가입을 진행해야 합니다.&lt;br /&gt;먼저 &lt;a href=&quot;https://central.sonatype.com/&quot;&gt;여기에서&lt;/a&gt; 회원가입을 진행합니다. Github를 통해 가입하는 걸 추천드립니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3248&quot; data-origin-height=&quot;1956&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PLmqT/btsIGjQVufZ/uknIVCrhA13JpY1InLAZBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PLmqT/btsIGjQVufZ/uknIVCrhA13JpY1InLAZBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PLmqT/btsIGjQVufZ/uknIVCrhA13JpY1InLAZBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPLmqT%2FbtsIGjQVufZ%2FuknIVCrhA13JpY1InLAZBK%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;3248&quot; height=&quot;1956&quot; data-origin-width=&quot;3248&quot; data-origin-height=&quot;1956&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Namespace 등록&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 Namespace 등록입니다. 이 단계는 2가지의 경우가 존재하는 데 사용하고 있는 도메인을 통한 방법과 github 같은 code hosting service 서비스를 이용하는 방법이 있습니다. 로그인 후 오른쪽 상단의 publish -&amp;gt; add namespace를 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3248&quot; data-origin-height=&quot;1956&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqO2zG/btsIHar3xwU/TCxCwD1KtNzgqrVwR6avmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqO2zG/btsIHar3xwU/TCxCwD1KtNzgqrVwR6avmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqO2zG/btsIHar3xwU/TCxCwD1KtNzgqrVwR6avmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqO2zG%2FbtsIHar3xwU%2FTCxCwD1KtNzgqrVwR6avmk%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;3248&quot; height=&quot;1956&quot; data-origin-width=&quot;3248&quot; data-origin-height=&quot;1956&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;code hosting service&lt;br /&gt;만약 github를 통해 회원가입을 했고 username.github.io를 등록했다면 자동으로 namespace가 등록이 되는 것 같습니다. 그런 분들은 그냥 사용하시면 되고 만약 없다면 github.com/username/제공받은-인증-키 레포를 만들고 인증을 진행합니다.(이 레포는 인증 후 삭제해도 됩니다.)&lt;/li&gt;
&lt;li&gt;개인 도메인&lt;br /&gt;개인 도메인을 사용하신다면 custom record를 생성한 후 Type을 TXT 그리고 data를 제공받은 인증 키로 등록하면 끝입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;소스 업로드하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스를 업로드하는 방식은 다양하게 존재합니다. 그러나 저는 쉽지 않았는데요 sonatype은 Gradle을 이용한 업로드 방식을 여러 개 소개합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3248&quot; data-origin-height=&quot;1956&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bInVEP/btsIG3T7vEn/65eA5Khcad27yqjyrmGeq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bInVEP/btsIG3T7vEn/65eA5Khcad27yqjyrmGeq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bInVEP/btsIG3T7vEn/65eA5Khcad27yqjyrmGeq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbInVEP%2FbtsIG3T7vEn%2F65eA5Khcad27yqjyrmGeq0%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;3248&quot; height=&quot;1956&quot; data-origin-width=&quot;3248&quot; data-origin-height=&quot;1956&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 상단에 있는 방식이 제가 첫 번째로 시도한 jreleaser인데요... 현재 github 인증 버그가 있는데 실제로 이슈로도 올라와 있고 방법을 찾으려 해도 쉽지 않아서 포기했습니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 제가 시도한 방법은 vanniktech/gradle-maven-publish-plugin입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;gpg 서명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명에 앞서 먼저 Central Repository에 올리기 위해서는 GPG로 서명해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gpg 가 없다면 다음 &lt;a href=&quot;https://gnupg.org/download/&quot;&gt;https://gnupg.org/download/&lt;/a&gt; 사이트 또는 패키지 매니저를 통해 다운로드를 진행합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;gpg 키 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 명령어를 통해 키를 생성합니다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;gpg --gen-key&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름, 이메일, 코멘트 순으로 설정을 진행합니다.&lt;br /&gt;그 후 passphrase 입력하면 끝입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;gpg 키 확인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 명령어를 통해 생성된 키를 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;gpg --list-keys&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[keyboxd]
---------
pub   rsa4096 2024-07-20 [SC] [expires: 2025-07-20]
      발급받은_키ID
uid           [ultimate] 이름 &amp;lt;이메일&amp;gt;
sub   rsa4096 2024-07-20 [E] [expires: 2025-07-20]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pub 옆에 발급받은-키 ID 가 생성된 키의 ID입니다.&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;br /&gt;주로 keyserver.ubuntu.com, keys.openpgp.org, pgp.mit.edu 에 전송하고 모두 전송해 놓는 게 좋다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 명령어를 통해 키 서버에 전송합니다.&lt;/p&gt;
&lt;pre class=&quot;brainfuck&quot;&gt;&lt;code&gt;gpg --keyserver keyserver.ubuntu.com --send-keys 발급받은_키ID&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 잘 올라갔는지 다음과 같이 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;brainfuck&quot;&gt;&lt;code&gt;gpg --keyserver keyserver.ubuntu.com --recv-keys 발급받은_키ID&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;POM 설정하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 거의 끝났습니다. 다음으로 프로젝트와 함께 게시될 pom을 설정해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 gradle을 사용해서 gradle 기반으로 설명하겠습니다.&lt;/p&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;  import com.vanniktech.maven.publish.SonatypeHost

  plugins {
    ... 기존 설정
    id &quot;com.vanniktech.maven.publish&quot; version &quot;0.29.0&quot;
    Id 'signing'
  }
  ... 기존 설정

  signing { // 배포할 프로젝트에 서명을 추가하는 코드
      useGpgCmd() // 시스템 CLI 를 통해 서명 진행
      sign publishing.publications // publishing.publications 에 정의된 모든 publication에 서명을 추가
  }

  mavenPublishing {

    // publishToMavenCentral(SonatypeHost.DEFAULT)
    // publishToMavenCentral(SonatypeHost.S01) // https://s01.oss.sonatype.org 에 올리고 싶을 떄
    publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) // https://central.sonatype.com/ 에 올리고 싶을 떄

    coordinates(&quot;com.example.mylibrary&quot;, &quot;library-name&quot;, &quot;1.0.3-SNAPSHOT&quot;) // groupId(namespace), artifactId, version(현재 central.sonatype.com 에는 SNAPSHOT을 올릴 수 없음)

    pom { // pom 에 나타날 정보
      name = &quot;My Library&quot; // 프로젝트 이름
      description = &quot;A description of what my library does.&quot; // 설명
      inceptionYear = &quot;2020&quot; // 시작년도
      url = &quot;https://github.com/username/mylibrary/&quot; // 설명 사이트
      licenses { // 라이센스 정보
        license {
          name = &quot;The Apache License, Version 2.0&quot;
          url = &quot;http://www.apache.org/licenses/LICENSE-2.0.txt&quot;
          distribution = &quot;http://www.apache.org/licenses/LICENSE-2.0.txt&quot;
        }
      }
      developers { // 개발자 정보
        developer {
          id = &quot;username&quot;
          name = &quot;User Name&quot;
          url = &quot;https://github.com/username/&quot;
        }
      }
      scm { // 사용자들이 소스를 찾을 수 있도록 github 등록
        url = &quot;https://github.com/username/mylibrary/&quot;
        connection = &quot;scm:git:git://github.com/username/mylibrary.git&quot;
        developerConnection = &quot;scm:git:ssh://git@github.com/username/mylibrary.git&quot;
      }
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 build.gradle을 수정합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SonaType 정보 입력하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 View Account 에 들어가서&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2024-07-20 오후 10.47.11.png&quot; data-origin-width=&quot;2041&quot; data-origin-height=&quot;639&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q5URE/btsIGDO8WgS/D6JKQK9sHLo8Q4JGvlutsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q5URE/btsIGDO8WgS/D6JKQK9sHLo8Q4JGvlutsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q5URE/btsIGDO8WgS/D6JKQK9sHLo8Q4JGvlutsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ5URE%2FbtsIGDO8WgS%2FD6JKQK9sHLo8Q4JGvlutsK%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;2041&quot; height=&quot;639&quot; data-filename=&quot;edited_스크린샷 2024-07-20 오후 10.47.11.png&quot; data-origin-width=&quot;2041&quot; data-origin-height=&quot;639&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Generate User Token 을 통해 SonaType의 User Token을 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2024-07-20 오후 10.48.14.png&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/do61e7/btsIGIJwgMb/5GBYDqF9ecftRztzj5DFlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/do61e7/btsIGIJwgMb/5GBYDqF9ecftRztzj5DFlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/do61e7/btsIGIJwgMb/5GBYDqF9ecftRztzj5DFlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdo61e7%2FbtsIGIJwgMb%2F5GBYDqF9ecftRztzj5DFlK%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;3000&quot; height=&quot;451&quot; data-filename=&quot;edited_스크린샷 2024-07-20 오후 10.48.14.png&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 ~/. gralde/gradle.properties 에 다음과 같이 추가합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;**주의** 프로젝트 내의 .gradle 폴더가 아닌 user.home(~/) 밑의 .gradle 입니다!!&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;mavenCentralUsername=생성된_username_token
mavenCentralPassword=생성된_password_token&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이제 끝&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 gradle 명령어를 입력하면 passpharse 를 입력하는 창이 나오고 등록했던 패스워드를 입력하면 완료입니다!!&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;./gradlew publishAllPublicationsToMavenCentralRepository&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드가 성공했다면 다시 central.sonatype.com/publishing으로 돌아가면 Deployments에 배포되고 있는 프로젝트를 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;1514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d11VbC/btsIGYZJkdp/Ux7aIBvEb6L6FkBnsZLOEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d11VbC/btsIGYZJkdp/Ux7aIBvEb6L6FkBnsZLOEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d11VbC/btsIGYZJkdp/Ux7aIBvEb6L6FkBnsZLOEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd11VbC%2FbtsIGYZJkdp%2FUx7aIBvEb6L6FkBnsZLOEk%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;3000&quot; height=&quot;1514&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;1514&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2024-07-20 오후 5.57.46.png&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;1514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbgZqq/btsIHbdrWPs/xKx0t3wJ0FKndItLmqvJUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbgZqq/btsIHbdrWPs/xKx0t3wJ0FKndItLmqvJUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbgZqq/btsIHbdrWPs/xKx0t3wJ0FKndItLmqvJUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbgZqq%2FbtsIHbdrWPs%2FxKx0t3wJ0FKndItLmqvJUk%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;3000&quot; height=&quot;1514&quot; data-filename=&quot;edited_스크린샷 2024-07-20 오후 5.57.46.png&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;1514&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 repo1.maven.org/maven2/(groupId. 단위로 / 입력)/(articeId)에서 정상적으로 업로드된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-20 오후 7.49.16.png&quot; data-origin-width=&quot;3248&quot; data-origin-height=&quot;1964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KQObP/btsIHUPFtBF/poYDlfqt8N347HtwLqFhkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KQObP/btsIHUPFtBF/poYDlfqt8N347HtwLqFhkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KQObP/btsIHUPFtBF/poYDlfqt8N347HtwLqFhkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKQObP%2FbtsIHUPFtBF%2FpoYDlfqt8N347HtwLqFhkk%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;3248&quot; height=&quot;1964&quot; data-filename=&quot;스크린샷 2024-07-20 오후 7.49.16.png&quot; data-origin-width=&quot;3248&quot; data-origin-height=&quot;1964&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;여기까지가 저의 이틀 동안의 삽질의 결과입니다..ㅎㅎ 여러분은 쉽고 간단하게 프로젝트를 maven에 업로드하길 바랍니다!!&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://vanniktech.github.io/gradle-maven-publish-plugin/central/#configuring-maven-central&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://vanniktech.github.io/gradle-maven-publish-plugin/central/#configuring-maven-central&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발</category>
      <author>kimhyun5u</author>
      <guid isPermaLink="true">https://kimhyun5u.tistory.com/18</guid>
      <comments>https://kimhyun5u.tistory.com/18#entry18comment</comments>
      <pubDate>Sat, 20 Jul 2024 19:52:46 +0900</pubDate>
    </item>
    <item>
      <title>Java 프로그램 바이트 코드를 분석해보자</title>
      <link>https://kimhyun5u.tistory.com/17</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 배운 JVM의 동작을 간단히 이해해 보고자 Java 프로그램을 간단히 작성해 바이트 코드를 분석해보았다.&lt;/p&gt;
&lt;h1&gt;코드&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 다음은 작성한 코드다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class SimpleProgram {
    public static void main(String[] args) {
        int a = 5;
        int b = 3;
        int result = add(a, b);
        System.out.println(&quot;Result: &quot; + result);
    }

    public static int add(int x, int y) {
        return x + y;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;바이트 코드&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;javac SimpleProgram.java | javap -c SimpleProgram&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 실행한 결과다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Compiled from &quot;SimpleProgram.java&quot;
public class SimpleProgram {
  public SimpleProgram();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object.&quot;&amp;lt;init&amp;gt;&quot;:()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: iconst_5
         1: istore_1
         2: iconst_3
         3: istore_2
         4: iload_1
         5: iload_2
         6: invokestatic  #7                  // Method add:(II)I
         9: istore_3
        10: getstatic     #13                 // Field java/lang/System.out:Ljava/io/PrintStream;
        13: iload_3
        14: invokedynamic #19,  0             // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
        19: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        22: return
      LineNumberTable:
        line 3: 0
        line 4: 2
        line 5: 4
        line 6: 10
        line 7: 22

  public static int add(int, int);
    descriptor: (II)I
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_0
         1: iload_1
         2: iadd
         3: ireturn
      LineNumberTable:
        line 10: 0
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main 메소드 안 코드를 한 줄씩 분석해보았다.&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;0: iconst_5&lt;/b&gt;&lt;br /&gt;: 상수 5를 스택에 푸시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;1: istore_1&lt;/b&gt;&lt;br /&gt;: 스택의 값을 1번째 지역변수에 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;2: iconst_3&lt;/b&gt;&lt;br /&gt;: 상수 3을 스택에 푸시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;3: istore_2&lt;/b&gt;&lt;br /&gt;: 스택의 값을 2번째 지역변수에 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;4: iload_1&lt;/b&gt;&lt;br /&gt;&lt;b&gt;5: iload_2&lt;/b&gt;&lt;br /&gt;: 1, 2번째 지역변수를 스택에 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;6: invokestatic #7 // Method add:(II)I&lt;/b&gt;&lt;br /&gt;: add 메소드 실행 하면서 새로운 스택 프레임 생성 및 매개변수 pop 후 새로운 스택 프레임에 복사&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;0: iload_0&lt;/b&gt;&lt;br /&gt;&lt;b&gt;1: iload_1&lt;/b&gt;&lt;br /&gt;: 1, 2번째 지역번수를 스택에 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;2: iadd&lt;/b&gt;&lt;br /&gt;: 스택의 2개 값을 더함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;3: ireturn&lt;/b&gt;&lt;br /&gt;: 정수 결과값 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;9: istore_3&lt;/b&gt;&lt;br /&gt;: 반환된 값을 3번째 지역변수에 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;10: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;&lt;/b&gt;&lt;br /&gt;: System.out 객체를 스택에 푸시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;13: iload_3&lt;/b&gt;&lt;br /&gt;: 3번째 지역변수를 스택에 푸시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;14: invokedynamic #19, 0 // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;&lt;/b&gt;&lt;br /&gt;: #0 부트스트랩 메서드를 실행 문자열을 합치고 스택에 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;19: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/String;)V&lt;/b&gt;&lt;br /&gt;: 스택에서 값을 받아 println 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;22: return&lt;/b&gt;&lt;br /&gt;: 프로그램 종료&lt;/p&gt;
&lt;h1&gt;주요 특징&lt;/h1&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;JVM은 스택 기반으로 연산을 진행함&lt;/li&gt;
&lt;li&gt;지역 변수는 인덱스로 접근&lt;/li&gt;
&lt;li&gt;invokestatic, invokedynamic, invokevirtual 을 이용해 메서드를 실행함&lt;/li&gt;
&lt;li&gt;iconst_, iadd 같은 정수 특화 명령어들이 사용됨&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발</category>
      <author>kimhyun5u</author>
      <guid isPermaLink="true">https://kimhyun5u.tistory.com/17</guid>
      <comments>https://kimhyun5u.tistory.com/17#entry17comment</comments>
      <pubDate>Wed, 10 Jul 2024 21:13:21 +0900</pubDate>
    </item>
    <item>
      <title>ServerSocket의 내부 동작 살펴보기: TCP 연결 설정의 로우레벨 이해</title>
      <link>https://kimhyun5u.tistory.com/16</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EoSmd/btsIsf7451F/DAOyh3sFyUZWPlmLgQRIMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EoSmd/btsIsf7451F/DAOyh3sFyUZWPlmLgQRIMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EoSmd/btsIsf7451F/DAOyh3sFyUZWPlmLgQRIMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEoSmd%2FbtsIsf7451F%2FDAOyh3sFyUZWPlmLgQRIMK%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;390&quot; height=&quot;267&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;267&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;WAS 미션을 진행하며 생긴 ServerSocket의 동작 원리에 대한 궁금증을 해결하기 위해 내부 구조를 확인해보았습니다.&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;궁금증&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 요청을 처리하는 중 /index.html을 접속했을 때 멀티 스레딩을 구현하지 않아도 다수의 요청이 동시에 들어오는 것을 모두 처리하는 것에 의문이 들었습니다.&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;SeverSocket을 생성하면 발생하는 일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;new ServerSocket(port)를 하면 먼저 해당 port에 서버소켓을 바인드 시스템 콜을 호출합니다. 그 다음 백로그에 크기만 큼 SYN와 accept 큐의 합계를 제한합니다. 클라이언트가 서버에 요청을 보내면 SYN 큐로 들어가고 3-way handshaking 가 완료되면 accept 큐에 들어가 서버의 accept를 기다립니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2726&quot; data-origin-height=&quot;1084&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lsgHT/btsIrxORkxy/FnkUmDUOot4d5DKqYAtnM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lsgHT/btsIrxORkxy/FnkUmDUOot4d5DKqYAtnM0/img.png&quot; data-alt=&quot;SeverSocket을 생성하면 발생하는 일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lsgHT/btsIrxORkxy/FnkUmDUOot4d5DKqYAtnM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlsgHT%2FbtsIrxORkxy%2FFnkUmDUOot4d5DKqYAtnM0%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;2726&quot; height=&quot;1084&quot; data-origin-width=&quot;2726&quot; data-origin-height=&quot;1084&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SeverSocket을 생성하면 발생하는 일&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;accept()&amp;nbsp;를&amp;nbsp;하면&amp;nbsp;발생하는&amp;nbsp;일&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ServerSocket.accept() 를 실행하면 accept 큐에 있는 conn 을 꺼내와 clientSocket을 반환합니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;하지만&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백로그를 통해 여러 요청을 처리할 수 있지만 메모리와 CPU 같은 시스템 리소스를 고려하지 않으면 나쁜 성능을 보일 수도 있습니다.&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;따라서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ServerSocket의 내부 동작을 공부하면서&amp;nbsp; WAS가 어떻게 HTTP request를 처리할 수 있는지 알 수 있었습니다. 또한, 시스템의 환경을 고려해 설정을 최적화한다면 효율적인 서버를 구성할 수 있다는 것을 알게되었습니다.&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;혹시라도 틀린 내용이나 추가적인 내용이 있다면 댓글로 알려주세요 :3&lt;/p&gt;</description>
      <category>개발</category>
      <category>ServerSocket</category>
      <category>우아한테크캠프</category>
      <author>kimhyun5u</author>
      <guid isPermaLink="true">https://kimhyun5u.tistory.com/16</guid>
      <comments>https://kimhyun5u.tistory.com/16#entry16comment</comments>
      <pubDate>Mon, 8 Jul 2024 20:21:29 +0900</pubDate>
    </item>
    <item>
      <title>Buffered I/O Steram vs I/O Stream: 커널 영역 접근과 성능 비교</title>
      <link>https://kimhyun5u.tistory.com/15</link>
      <description>&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baevvZ/btsIrPaEaDd/sos5IKvjErU8zSReyKBc30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baevvZ/btsIrPaEaDd/sos5IKvjErU8zSReyKBc30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baevvZ/btsIrPaEaDd/sos5IKvjErU8zSReyKBc30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaevvZ%2FbtsIrPaEaDd%2Fsos5IKvjErU8zSReyKBc30%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;390&quot; height=&quot;267&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가며&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;저번 주 우아한테크캠프 미션을 진행하면서 WAS를 구현하고 있는데, 그룹 리뷰에서 I/O Stream 보다 Buffered I/O Stream 을 사용하는 게 더 좋다는 피드백을 받아서 Buffered I/O Stream과 일반 I/O Stream의 차이점이 궁금해졌습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;Buffered&amp;nbsp;I/O&amp;nbsp;Steram&amp;nbsp;vs&amp;nbsp;I/O&amp;nbsp;Stream&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;여러분은 Java에서 파일을 읽고 쓸 때 어떤 방식을 사용하시나요? 저는 InputStream 은 BufferedInputStream 으로 OutputStream은 그냥 OutputStream으로 구현했습니다. 이유는 InputStream은 BufferedInputStream을 사용해서 받는 경우를 많이 봤지만 OutputStream은 잘 못봤기 때문이었습니다. 하지만, 그냥 I/O Stream과 Buffered I/O Stream은 큰 차이가 있습니다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;I/O Stream은 데이터를 바이트 단위로 읽고 쓰는 기본적인 방식입니다. 하지만, 매 바이트 마다 커널 영역에 접근해 비싼 Context Swtiching 비용을 지불해야합니다. &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Buffered I/O Stream은 내부 메모리에 버퍼를 두어 데이터를 모았다가 한 번에 읽거나 쓰는 방식입니다. 버퍼 크기만큼 모았다가 커널 영역에 접근하기 때문에 커널 접근 횟수가 상대적으로 적습니다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;예를 들어 1MB 파일을 읽는다면 일반 I/O 는 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1,049,000 번 접근하게 되지만, 버퍼의 크기가 8KB인 Buffered I/O는 128번만 접근하게 됩니다.&amp;nbsp;&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제 POST /create 요청의 response 성능 비교&lt;/span&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 실제로도 그러한 성능 차이가 발생하는지 궁금하여 WAS 미션 중 POST /create 요청에 대한 처리 성능을 비교해봤습니다.&lt;/span&gt;&lt;br&gt;해당 응답은 약 360B의 크기를 가지고 있습니다.&lt;br&gt;&lt;u&gt;&lt;i&gt;&lt;b&gt;Buffered I/O Stream은 기본 버퍼 크기(8KB)로 하였습니다.&lt;/b&gt;&lt;/i&gt;&lt;/u&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;결과는 약 33배의 차이가 났습니다. 이는 입력 세팅을 제와한 write 시간의 비교입니다. 따라서 커널 영역 접근 감소에 따른 성능 향상으로 볼 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DLsGy/btsIrcjR7hM/Hypk1WpbJuSgSs9RKZzG3k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DLsGy/btsIrcjR7hM/Hypk1WpbJuSgSs9RKZzG3k/img.jpg&quot; data-alt=&quot;Buffered IO Stream vs IO Stream&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DLsGy/btsIrcjR7hM/Hypk1WpbJuSgSs9RKZzG3k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDLsGy%2FbtsIrcjR7hM%2FHypk1WpbJuSgSs9RKZzG3k%2Fimg.jpg&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;495&quot; height=&quot;259&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Buffered IO Stream vs IO Stream&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;하지만&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 상황에서는 Buffered I/O Stream이 거의 대부분 더 좋은 성능을 보여줍니다. 하지만, 버퍼를 위한 추가적인 메모리를 사용하고 버퍼의 크기가 작으면 더 많이 커널 영역에 접근하므로 제한된 상황에서는 메모리 사용량과 실제 사용 케이스를 고려할 필요는 있습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;따라서&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 I/O Stream의 동작 원리와 내부 구조를 이해한다면 더 효율적으로 HTTP request, response를 처리할 수 있다는 것을 알았습니다. 혼자 공부했다면 이렇게 자세히 공부할 수 없었을 것 같습니다. 우아한테크캠프를 통해 다양한 사람들과 기술에 대한 고민을 할 수 있어서 더 깊이 공부하고 빠르게 성장할 수 있는 것 같습니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>java</category>
      <category>우아한테크캠프</category>
      <author>kimhyun5u</author>
      <guid isPermaLink="true">https://kimhyun5u.tistory.com/15</guid>
      <comments>https://kimhyun5u.tistory.com/15#entry15comment</comments>
      <pubDate>Mon, 8 Jul 2024 19:58:43 +0900</pubDate>
    </item>
    <item>
      <title>[LiveStreamingOnWebRTC] 스트리밍 서비스를 WebRTC로 구현해보기</title>
      <link>https://kimhyun5u.tistory.com/13</link>
      <description>&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;HLS를 사용하는 기존 스트리밍 서비스의 지연 시간(20s)을 해결하기 위해 WebRTC를 활용해 스트리밍 서비스를 구현해봄.&lt;/p&gt;
&lt;h2&gt;구조&lt;/h2&gt;
&lt;h3&gt;P2P&lt;/h3&gt;
&lt;p&gt;
    &lt;img src=&quot;https://kimhyun5u.github.io/assets/img/p2p.png&quot; alt&gt;
    &lt;em&gt;Live Streaming On WebRTC P2P 구조&lt;/em&gt;
&lt;/p&gt;

&lt;/center&gt;
현재 시그널링 서버와 클라이언트로 구성되어 스트리머가 시청자 1개당 1개의 PeerConnection을 생성하는 구조로 구성되어있다. P2P 통신으로 구성되기 때문에 많은 시청자가 접속할 때 스트리머에서 큰 부하가 있다.

&lt;h3&gt;SFU&lt;/h3&gt;
&lt;center&gt;

&lt;p&gt;
    &lt;img src=&quot;https://kimhyun5u.github.io/assets/img/%08sfu.png&quot; alt&gt;
    &lt;em&gt;Live Streaming On WebRTC sfu 구조&lt;/em&gt;
&lt;/p&gt;

&lt;/center&gt;

&lt;p&gt;그래서 위와 같은 구조를 통해 스트리머는 한개의 미디어 스트림을 서버로 보내고 서버와 클라이언트가 PeerConnection을 맺는 방식으로 구성한다.&lt;/p&gt;
&lt;p&gt;그 결과 스트리머의 과도한 부하는 막을 수 있지만 서버의 부담이 조금 커진다.&lt;/p&gt;
&lt;h2&gt;계획&lt;/h2&gt;
&lt;p&gt;현재 구조를 SFU로 변경하고 서버의 부하를 최소화 할 수 있도록 미디어 Encoding 등 공부를 진행&lt;/p&gt;
&lt;h2&gt;코드&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kimhyun5u/Live-Streaming-On-WebRTC&quot;&gt;https://github.com/kimhyun5u/Live-Streaming-On-WebRTC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발</category>
      <author>kimhyun5u</author>
      <guid isPermaLink="true">https://kimhyun5u.tistory.com/13</guid>
      <comments>https://kimhyun5u.tistory.com/13#entry13comment</comments>
      <pubDate>Thu, 2 May 2024 22:00:28 +0900</pubDate>
    </item>
    <item>
      <title>WebRTC 찍어먹기</title>
      <link>https://kimhyun5u.tistory.com/12</link>
      <description>&lt;h2&gt;WebRTC란?&lt;/h2&gt;
&lt;p&gt;WebRTC는 중간자 없이 브라우저 간 미디어 스트림과 데이터 교환을 지원하는 기술이다. 우리가 많이 접하는 서비스로는 Zoom, &lt;a href=&quot;https://webrtchacks.com/facetime-finally-faces-webrtc-implementation-deep-dive/&quot;&gt;Facetime&lt;/a&gt;이 있고 &lt;a href=&quot;https://www.youtube.com/watch?v=8jryUH6xmjU&quot;&gt;게임 리모트 플레이&lt;/a&gt;에도 사용된다.&lt;/p&gt;
&lt;h2&gt;WebRTC의 장점&lt;/h2&gt;
&lt;center&gt;

&lt;p&gt;&lt;img src=&quot;https://www.wowza.com/wp-content/uploads/latency-continuum-2021-with-protocols-no-title-1110x540-1.webp&quot; alt=&quot;image&quot;&gt; &lt;em&gt;&lt;a href=&quot;https://www.wowza.com/low-latency&quot;&gt;source&lt;/a&gt;: wowza&lt;/em&gt;&lt;/p&gt;
&lt;/center&gt;

&lt;p&gt;WebRTC는 P2P 통신이기 때문에 매우 적은 지연 시간을 보여준다. 또한 WebRTC는 브라우저 위에서 동작할 수 있기 때문에 다양한 OS 환경에서 쉽게 사용할 수 있다.&lt;/p&gt;
&lt;h2&gt;WebRTC의 단점&lt;/h2&gt;
&lt;h3&gt;제한된 사용성&lt;/h3&gt;
&lt;p&gt;하지만 WebRTC에는 다양한 단점이 존재하는데 첫째로 화상통화를 위해 만들어졌기 때문에 그외의 모든 작업에서 문제가 발생한다. WebRTC는 최고의 실시간성을 보장하기 위해 너무 많은 화질 열화를 감수해야한다. 이 부분은 하드코딩 되어 있어 조절이 불가능하다.&lt;/p&gt;
&lt;h3&gt;어려운 연결&lt;/h3&gt;
&lt;p&gt;애플리케이션 관점에서 ICE handshake는 너무 복잡하다. 또한 P2P를 제한하는 네트워크에서는 사용이 불가능하다.&lt;/p&gt;
&lt;h2&gt;WebRTC를 대체할 기술&lt;/h2&gt;
&lt;h3&gt;WebTransport&lt;/h3&gt;
&lt;p&gt;이러한 문제점을 해결해 나온 기술이 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebTransport&quot;&gt;WebTransport&lt;/a&gt;이다.(아직 사파리에서 지원 안함.)&lt;br&gt;TCP가 아닌 QUIC 위에서 동작하고 순서를 보장하는 스트림을 지원한다.&lt;/p&gt;
&lt;h2&gt;그럼에도&lt;/h2&gt;
&lt;p&gt;WebRTC는 더 높은 호환성을 가지고 있고 실시간성을 보장해야하는 서비스에서는 많이 사용된다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/WebRTC_API&quot;&gt;https://developer.mozilla.org/ko/docs/Web/API/WebRTC_API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/WebRTC_API/Protocols&quot;&gt;https://developer.mozilla.org/ko/docs/Web/API/WebRTC_API/Protocols&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://quic.video/&quot;&gt;https://quic.video/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발</category>
      <author>kimhyun5u</author>
      <guid isPermaLink="true">https://kimhyun5u.tistory.com/12</guid>
      <comments>https://kimhyun5u.tistory.com/12#entry12comment</comments>
      <pubDate>Wed, 1 May 2024 15:48:44 +0900</pubDate>
    </item>
    <item>
      <title>소프트웨어마에스트로 14기 수료 후기</title>
      <link>https://kimhyun5u.tistory.com/11</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwAnTM/btsCf63tlxc/y6zOoGx4bD7u4rQXFkIfGk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwAnTM/btsCf63tlxc/y6zOoGx4bD7u4rQXFkIfGk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwAnTM/btsCf63tlxc/y6zOoGx4bD7u4rQXFkIfGk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwAnTM%2FbtsCf63tlxc%2Fy6zOoGx4bD7u4rQXFkIfGk%2Fimg.jpg&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;460&quot; height=&quot;613&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2023년 12월 14일 소프트웨어마에스트로 14기의 정식 활동이 모두 끝났다.&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;사실 활동 자체는 11월 30일에 끝났긴 했지만, 수료를 하니까 더 실감이 나는 것 같다. 8개월의 시간이 훅 지나간 느낌이다. 특히 9월, 10월은 너무 빠르게 지나갔다. 3인으로 상용 가능한 서비스를 만들기에는 조금 벅찬 기간이었지만 나름 노력해서 잘 마무리한 것 같다. 인증이 되진 않았지만, 스스로 많이 성장할 수 있는 계기가 된 것 같다.&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;나는 소마에서 네트워킹, 개발 능력 향상을 목표를 했다. 개발 능력 향상은 대용량 서버를 위한 캐시 적용과 TCP 리눅스 최적화를 경험하면서 향상된 느낌이지만, 네트워킹은 소마 컨퍼런스 발표 등 다양한 활동에 참여했지만, 센터 자체에 많이 가지 않아서 그런지 큰 수확은 없었다. 그래도 링크드인으로 다양한 사람들과 일촌을 맺었긴 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3t0Jd/btsCgYRqIDS/YpX9p7vkZGkXvtKUn4Zfvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3t0Jd/btsCgYRqIDS/YpX9p7vkZGkXvtKUn4Zfvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3t0Jd/btsCgYRqIDS/YpX9p7vkZGkXvtKUn4Zfvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3t0Jd%2FbtsCgYRqIDS%2FYpX9p7vkZGkXvtKUn4Zfvk%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;1314&quot; height=&quot;704&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;704&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아쉬운 점도 남았다. 일단 실제 서비스를 위한 기능이 너무 많았기 때문에 모든 기능을 구현하지 못한 점이 아쉬웠다. 최소 기능만 도출해 MVP를 구성했는데 실제 사용하기에는 부족함이 많았다. 피드백을 통해 버전업을 계속 진행했지만 그럼에도 활동 기간 내에는 시간이 부족해 아쉬웠다. 그리고 멘토님들을 제대로 활용하지 못한 점이다. 멘토님들은 각 분야별로 전문가분들이시다. 그렇기 때문에 각 부분에 대해 집중적으로 케어를 받을 수 있었지만, 그 당시에는 개발과 운영에 너무 치여 살아서 이를 최대한 활용하지 못했던 것 같다.&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;소마는 본 과정 이후에도 다양한 지원을 받는 고도화 과정을 이수할 수 있다. 하지만, 나는 고도화 과정을 선택하지 않았다. 가장 큰 이유는 내가 원하는 도메인의 개발을 진행하고 싶어서이다. 나는 작년 11월 전역 후 미디어 서버 개발자가 되고자 결심했다. 하지만, 캡스톤, 소마 등 프로젝트를 진행할 때 좀 더 대중적이고 범용적인 개발을 위해 양보를 하면서 1년 동안 한 번도 미디어 관련 프로젝트를 진행하지 못했다. 물론, 내가 따로 진행하면 되지 않느냐 라고 할 수 있지만, 많은 활동을 하다보니 너무 힘들었다. 그래서 이제 미디어 관련 프로젝트를 정말로 하기 위해 고도화 과정에 참여하지 않았다.&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>잡담</category>
      <author>kimhyun5u</author>
      <guid isPermaLink="true">https://kimhyun5u.tistory.com/11</guid>
      <comments>https://kimhyun5u.tistory.com/11#entry11comment</comments>
      <pubDate>Tue, 19 Dec 2023 11:29:07 +0900</pubDate>
    </item>
  </channel>
</rss>