<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>즐겁게 살고 싶은 개발자</title>
    <link>https://2dongdong.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 4 Jul 2026 10:41:43 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>평범한 개발자...</managingEditor>
    <item>
      <title>어느날&amp;nbsp;오픈코드에서 클로드가 갑자기 막혀서 간단한 오픈소스를 만들어봤다</title>
      <link>https://2dongdong.tistory.com/81</link>
      <description>&lt;h3&gt;클로드 인증이 실패하다&lt;/h3&gt;
&lt;p&gt;지난 3월 여느 때처럼 AI 코딩 에이전트를 돌리고 있었습니다. 도구로는 opencode/oh-my-opencode(현재는 oh-my-openagent)를, 오케스트레이터 모델로는 클로드 오퍼스를 쓰고 있었습니다.&lt;/p&gt;
&lt;p&gt;그날도 일어나자마자 자기 전에 돌려둔 작업부터 확인했습니다. 작업은 끝나 있었고, 이어서 다음 작업을 시키려는데 갑자기 클로드가 안 됐습니다. 클로드 서버가 워낙 자주 터지니까 좀 쉬었다가 다시 확인했는데, 여전히 그대로였습니다.&lt;/p&gt;
&lt;p&gt;처음엔 opencode 버전 문제인 줄 알았는데, 확인해보니 아니었습니다. 뭔가 이상해서 opencode 깃허브 이슈에 들어가 보니, 저와 똑같은 증상을 겪는 이슈가 올라와 있었습니다.&lt;/p&gt;
&lt;p&gt;이슈를 올린 사람이 직접 원인을 분석해서 플러그인을 새로 만들어 공유한 것도 보였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1940&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgJj9u/dJMcaf1mpEY/A2qK3kA7ugE4qDV8PtFaa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgJj9u/dJMcaf1mpEY/A2qK3kA7ugE4qDV8PtFaa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgJj9u/dJMcaf1mpEY/A2qK3kA7ugE4qDV8PtFaa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgJj9u%2FdJMcaf1mpEY%2FA2qK3kA7ugE4qDV8PtFaa1%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;1940&quot; height=&quot;500&quot; data-origin-width=&quot;1940&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;AI가 멈춰 있는 시간이 아까웠던 터라, 우선 그 플러그인부터 설치해봤는데 안 됐습니다. 그래서 문제를 해결하고 싶은 마음에 이슈를 제대로 들여다보기 시작했습니다.&lt;/p&gt;
&lt;h3&gt;버그가 아니라 정책 문제&lt;/h3&gt;
&lt;p&gt;그 플러그인을 만든 사람의 말로는, 실제로는 토큰을 교환하는 단계에서 막힌 거였고, opencode가 보내는 User-Agent가 클로드 코드와 달라 429가 난다는 분석이었습니다. 이유는 모르겠지만 제 환경에선 동작하지 않았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1860&quot; data-origin-height=&quot;1122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjw3mn/dJMcaiKxqiV/zrkvbSf581ofDwHUVCnHK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjw3mn/dJMcaiKxqiV/zrkvbSf581ofDwHUVCnHK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjw3mn/dJMcaiKxqiV/zrkvbSf581ofDwHUVCnHK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbjw3mn%2FdJMcaiKxqiV%2FzrkvbSf581ofDwHUVCnHK0%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;1860&quot; height=&quot;1122&quot; data-origin-width=&quot;1860&quot; data-origin-height=&quot;1122&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;결국 버그가 아니라 클로드 정책 문제였습니다. Anthropic이 서드파티 OAuth 클라이언트를 토큰 단계에서 막은 거였고, opencode도 법적 요청을 받아 클로드 OAuth 지원을 제거했습니다(커밋 &amp;quot;Anthropic legal requests&amp;quot;). 이후 Anthropic은 약관에서도 클로드 구독 인증을 클로드 코드와 claude.ai 밖에서 쓰는 걸 금지했습니다.&lt;/p&gt;
&lt;p&gt;헤더를 맞춰 로그인에 성공한 사람도 있었지만, 어차피 정책으로 막아둔 길을 우회하는 방법이었습니다.&lt;/p&gt;
&lt;h3&gt;npm으로 받은 인증 도구를 믿어도 될까&lt;/h3&gt;
&lt;p&gt;이때 이슈는 플러그인 춘추전국시대였습니다. 저를 포함하여 하루 이틀만에 &amp;#39;내가 만든 게 된다&amp;#39;는 댓글만 여러 개였습니다. 마침 경고하는 댓글도 하나 올라왔습니다. 급하게 인증 플러그인을 설치하다 보면, 누가 npm 패키지에 악성 코드를 심어도 제대로 확인하지 못하고 깔게 된다는 얘기였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1874&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJzf8S/dJMcabkr6L0/rfHmIvfkN8euzJTcaXec0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJzf8S/dJMcabkr6L0/rfHmIvfkN8euzJTcaXec0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJzf8S/dJMcabkr6L0/rfHmIvfkN8euzJTcaXec0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJzf8S%2FdJMcabkr6L0%2FrfHmIvfkN8euzJTcaXec0K%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;1874&quot; height=&quot;426&quot; data-origin-width=&quot;1874&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;저 또한 누군가 이제 막 만든 인증 토큰을 다루는 도구를 쓰는 건 조심스러웠습니다. 그래서 npm 플러그인 말고 다른 방법은 없을까 고민하게 됐습니다.&lt;/p&gt;
&lt;h3&gt;토큰을 옮기는 작은 스크립트&lt;/h3&gt;
&lt;p&gt;이슈 댓글에서 힌트를 발견했습니다. 클로드 코드가 쓰는 토큰을 꺼내 기존 opencode 클로드 인증 플러그인이 사용하고 있는 &lt;code&gt;auth.json&lt;/code&gt;에 넣으면 동작한다는 거였습니다. 생각해보니 막힌 건 토큰 교환 API뿐이라, 이미 받아둔 토큰을 그대로 쓰면 됐습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1868&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2oxuv/dJMcadbmvBF/5DrohL285IVk6J18pmOIRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2oxuv/dJMcadbmvBF/5DrohL285IVk6J18pmOIRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2oxuv/dJMcadbmvBF/5DrohL285IVk6J18pmOIRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2oxuv%2FdJMcadbmvBF%2F5DrohL285IVk6J18pmOIRK%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;1868&quot; height=&quot;614&quot; data-origin-width=&quot;1868&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;그리핀마틴도 클로드 코드 토큰을 활용한 플러그인을 만들었는데, 확인해보니 npm 설치 방식이어서 저는 스크립트로 만들어 공유하기로 했습니다.&lt;/p&gt;
&lt;p&gt;먼저 나온 그리핀마틴 기존 내장 플러그인 방식을 대체하는 npm 방식이었지만, 저는 스크립트로 만들어 공유하기로 했습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;클로드 코드 인증 정보를 읽는다 (macOS Keychain 또는 ~/.claude/.credentials.json)
필드 이름을 opencode 형식에 맞게 바꾼다 (accessToken → access, expiresAt → expires …)
auth.json의 anthropic 항목에 써넣는다&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;{
  &amp;quot;anthropic&amp;quot;: {
    &amp;quot;type&amp;quot;: &amp;quot;oauth&amp;quot;,
    &amp;quot;access&amp;quot;: &amp;quot;...&amp;quot;,
    &amp;quot;refresh&amp;quot;: &amp;quot;...&amp;quot;,
    &amp;quot;expires&amp;quot;: 1770000000000
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;근데 이 방식은 곧 제거될 예정인 opencode 내장 플러그인을 사용하는 거라 한계가 있었습니다. 그래도 대표 플러그인이 자리잡기 전까지 쓸 임시방편으로는 충분하다는 생각이 들었습니다.&lt;/p&gt;
&lt;h3&gt;마무리&lt;/h3&gt;
&lt;p&gt;단순히 한 번 복사하면 끝나는 스크립트는 아니었습니다. 토큰이 만료되면 다시 동기화해야 했고, 플랫폼마다 credentials 위치와 스케줄러도 달랐습니다. 노트북은 macOS, 회사 서버는 Linux, 회사 컴퓨터는 Windows라 세 환경을 직접 쓰면서 하나씩 고쳤습니다. 공개한 뒤로는 이슈로 버그를 올려주는 분도 있었고, macOS PATH 문제나 Linux 스케줄러를 직접 고쳐 PR을 보내준 분도 있었습니다.&lt;/p&gt;
&lt;p&gt;위 버그를 수정해서 공유했을때 많은 이모지 반응이 있어서 놀랐었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4UjBg/dJMb997WnKN/jftSkiL8NGTh1jca2MnvM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4UjBg/dJMb997WnKN/jftSkiL8NGTh1jca2MnvM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4UjBg/dJMb997WnKN/jftSkiL8NGTh1jca2MnvM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4UjBg%2FdJMb997WnKN%2FjftSkiL8NGTh1jca2MnvM1%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;1852&quot; height=&quot;852&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;852&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;다른 사람들의 아이디어에서 시작한 작은 스크립트였는데, 생각보다 깃허브 스타가 많이 붙어서 신기했습니다. 누군가 댓글에서 제 프로젝트를 언급해준 게 홍보가 됐던 것 같습니다.&lt;/p&gt;
&lt;p&gt;초반에는 현재 자리잡은 플러그인인 그리핀마틴과 비슷하게 가다가, 금세 스타 개수가 벌어졌습니다. 그리고 1.3.0에서 opencode 내장 플러그인이 빠지면서, 제 오픈소스도 막을 내렸습니다. ㅎㅎ&lt;/p&gt;
&lt;p&gt;그래도 다른 사람이 제 프로젝트에 버그를 올리고 고쳐주는 걸 처음 겪어봐서 좋았습니다.&lt;/p&gt;</description>
      <author>평범한 개발자...</author>
      <guid isPermaLink="true">https://2dongdong.tistory.com/81</guid>
      <comments>https://2dongdong.tistory.com/81#entry81comment</comments>
      <pubDate>Fri, 26 Jun 2026 09:13:31 +0900</pubDate>
    </item>
    <item>
      <title>[핫딜트래커] 핫딜 제목 데이터로 파인튜닝 해보기</title>
      <link>https://2dongdong.tistory.com/80</link>
      <description>&lt;h3&gt;파인튜닝을 적용한 서비스를 만들어보고 싶었다&lt;/h3&gt;
&lt;p&gt;오픈 모델을 파인튜닝해서 실제 서비스 기능으로 써보고 싶었습니다.&lt;br&gt;저는 알구몬 같은 핫딜 사이트를 자주 이용하는데, 핫딜 글을 보다가 불편한 점이 있어서 여기에 파인튜닝을 붙여볼 수 있겠다는 생각이 들었습니다.&lt;/p&gt;
&lt;p&gt;핫딜을 볼 때 가장 궁금했던 건 &amp;quot;이 가격이 진짜 싼가?&amp;quot;였습니다.&lt;/p&gt;
&lt;p&gt;커뮤니티 핫딜 글에는 진짜 괜찮은 딜도 있지만, 업자나 바이럴 글이 섞여 있어서 애매한 딜도 꽤 많습니다.&lt;br&gt;특히 음료수, 생수, 화장지, 물티슈처럼 캔, 팩, 롤, 매수 단위가 붙는 상품은 용량과 수량 조합이 제각각이라, 가격만 봐서는 싼지 아닌지 판단하기 어려웠습니다.&lt;/p&gt;
&lt;p&gt;이런 단위 상품들은 파인튜닝한 소형 LLM으로 단위 가격을 뽑아 데이터로 쌓아두면, 그 데이터로 핫딜인지 쉽게 구분하는 서비스를 만들 수 있겠다 싶었습니다.&lt;/p&gt;
&lt;h3&gt;핫딜 제목에서 어떤 값을 봐야 할까&lt;/h3&gt;
&lt;p&gt;보통 핫딜 게시글의 제목은 아래와 같은 포맷으로 올라옵니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[네이버] 코카콜라 제로 355ml 24캔 (15,900원/무료)&lt;/li&gt;
&lt;li&gt;[11번가] 크리넥스 3겹 화장지 30m 30롤 (24,900원)&lt;/li&gt;
&lt;li&gt;[쿠팡] LG 그램 16인치 노트북 16Z90S (1,490,000원/무료)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여기서 LLM의 역할은 두 가지입니다.&lt;/p&gt;
&lt;p&gt;하나는 단가 계산에 쓸 값을 제목에서 뽑는 일입니다.&lt;br&gt;어디까지가 상품명인지, 행사 문구는 빼야 하는지, 500ml 20캔 같은 숫자가 단위 수치인지 수량인지를 추출합니다.&lt;/p&gt;
&lt;p&gt;다른 하나는 서비스에서 필터링 기능을 위해 카테고리를 분류하는 것입니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;th&gt;왜 필요한가&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;상품명&lt;/td&gt;
&lt;td&gt;코카콜라 제로&lt;/td&gt;
&lt;td&gt;같은 상품인지 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;상품 유형&lt;/td&gt;
&lt;td&gt;용량, 무게, 개수, 묶음&lt;/td&gt;
&lt;td&gt;어떤 방식으로 단가를 계산할지 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;단위 수치&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;500ml, 100g처럼 계산에 쓰는 숫자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;단위&lt;/td&gt;
&lt;td&gt;ml, g, 개, 롤&lt;/td&gt;
&lt;td&gt;가격을 나눌 기준&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;묶음 수량&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;전체 용량이나 총 개수 계산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;카테고리&lt;/td&gt;
&lt;td&gt;식품, 생활용품&lt;/td&gt;
&lt;td&gt;검색 범위 줄이기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;세부 카테고리&lt;/td&gt;
&lt;td&gt;음료, 화장지&lt;/td&gt;
&lt;td&gt;비슷한 상품 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;핫딜 정답 데이터셋 만들기&lt;/h3&gt;
&lt;p&gt;크롤링해서 데이터를 수집하는 과정은 스킵하겠습니다.&lt;/p&gt;
&lt;p&gt;수집된 데이터는 클로드코드를 이용해서 일차적으로 파싱하고, 결과를 직접 검수했습니다.&lt;/p&gt;
&lt;p&gt;먼저 수집한 핫딜 제목들을 보고, 어떤 형태의 데이터가 많이 나오는지 분포를 정리했습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;단품형: 노트북, 선풍기 (개당 판매)&lt;/li&gt;
&lt;li&gt;용량형: 500ml, 1.5L&lt;/li&gt;
&lt;li&gt;중량형: 100g, 2kg&lt;/li&gt;
&lt;li&gt;수량형: 24캔, 30롤, 20개입&lt;/li&gt;
&lt;li&gt;모델명과 숫자가 섞인 상품&lt;/li&gt;
&lt;li&gt;쿠폰, 무료배송, 행사 문구가 섞인 제목&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그 다음 제목을 JSON 필드로 나누는 작업을 진행했습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &amp;quot;title&amp;quot;: &amp;quot;코카콜라 제로 500ml 24개&amp;quot;,
  &amp;quot;product_type&amp;quot;: &amp;quot;volume&amp;quot;,
  &amp;quot;product_name&amp;quot;: &amp;quot;코카콜라 제로&amp;quot;,
  &amp;quot;unit_value&amp;quot;: 500,
  &amp;quot;unit&amp;quot;: &amp;quot;ml&amp;quot;,
  &amp;quot;quantity&amp;quot;: 24,
  &amp;quot;category&amp;quot;: &amp;quot;식품&amp;quot;,
  &amp;quot;subcategory&amp;quot;: &amp;quot;음료&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;모델 정하기&lt;/h3&gt;
&lt;p&gt;현재 제 파인튜닝 환경은 RTX 5090입니다. 그래서 이 장비에서 무난하게 학습할 수 있는 소형 모델을 후보로 봤습니다.&lt;/p&gt;
&lt;p&gt;조건은 아래와 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RTX 5090 32GB에서 QLoRA 학습이 가능할 것&lt;/li&gt;
&lt;li&gt;한국어 또는 다국어 제목을 어느 정도 처리할 수 있을 것&lt;/li&gt;
&lt;li&gt;Unsloth, PEFT 같은 파인튜닝 도구에서 다루기 어렵지 않을 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 기준으로 7B~14B 정도의 모델을 후보로 잡았습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Solar Mini&lt;/li&gt;
&lt;li&gt;Qwen3 8B&lt;/li&gt;
&lt;li&gt;Qwen3 14B&lt;/li&gt;
&lt;li&gt;SKT A.X&lt;/li&gt;
&lt;li&gt;EXAONE&lt;/li&gt;
&lt;li&gt;EEVE-Korean&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;하나만 골라 파인튜닝하기보다는, 후보가 다 소형 모델이고 학습 데이터도 적어서 Unsloth 같은 최적화된 라이브러리를 쓰면 금방 학습이 끝나기 때문에, 모든 모델을 전부 학습하고 같은 테스트셋에서 비교했습니다.&lt;/p&gt;
&lt;h3&gt;파인튜닝 결과&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;train set: 4,830 rows
test set: 1,188 rows
schema: product_type, product_name, unit_value, unit, quantity, category, subcategory
training: QLoRA
evaluation: temperature=0&lt;/code&gt;&lt;/pre&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;모델&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;모두 맞춘 비율&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;상품명&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;단위 수치&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;단위&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;수량&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;카테고리&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;세부 카테고리&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;EEVE-Korean LoRA&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;70.1%&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;90.5&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;96.3&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;96.6&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;93.9&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;93.7&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;82.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EXAONE LoRA&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;65.6%&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;83.8&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;96.1&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;96.7&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;94.2&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;96.1&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;85.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SKT A.X LoRA&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;64.6%&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;83.6&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;94.4&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;95.2&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;93.9&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;95.8&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;85.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3 14B LoRA&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;64.4%&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;84.7&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;94.5&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;95.2&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;93.3&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;95.9&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;85.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Solar Mini LoRA&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;63.7%&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;84.3&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;94.3&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;95.0&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;93.7&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;96.0&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;84.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3 8B LoRA&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;62.7%&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;83.2&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;94.3&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;94.8&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;93.8&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;95.5&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;83.8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;파인튜닝 결과 각 모델들의 단위, 수량, 카테고리 점수는 다 비슷했고, 상품명만 EEVE-Korean이 유독 높았습니다.&lt;/p&gt;
&lt;p&gt;다른 항목이 다 비슷하다 보니, 상품명에서 점수가 높았던 EEVE-Korean이 모두 맞춘 비율도 가장 높게 나온 것 같습니다.&lt;/p&gt;
&lt;h3&gt;결론&lt;/h3&gt;
&lt;p&gt;핫딜트레커 서비스에서는 카테고리보다 상품명, 스펙이 중요했고, 그 기준에서 EEVE-Korean을 선택하는 게 가장 적합하다고 생각했습니다.&lt;br&gt;카테고리 점수가 낮긴 하지만, 이는 학습 데이터가 쌓일수록 어느 정도 보완될 것이라 봤습니다.&lt;/p&gt;
&lt;p&gt;다음 글에서는 서비스를 며칠 운영하며 쌓인 데이터로, 모델이 약한 부분과 자주 틀리는 케이스를 찾아 개선해본 과정을 정리해보겠습니다&lt;/p&gt;</description>
      <author>평범한 개발자...</author>
      <guid isPermaLink="true">https://2dongdong.tistory.com/80</guid>
      <comments>https://2dongdong.tistory.com/80#entry80comment</comments>
      <pubDate>Wed, 17 Jun 2026 15:25:08 +0900</pubDate>
    </item>
    <item>
      <title>[Spring boot] @Transactional은 어떻게 동작할까? (+ 트랜잭션이 동작하지 않는 이유)</title>
      <link>https://2dongdong.tistory.com/77</link>
      <description>&lt;p&gt;여러분은 Spring 프레임워크를 사용하면서 @Transactional 어노테이션을 자주 사용해봤을 것입니다. 하지만 정확히 어떻게 동작하는지 그 원리를 제대로 이해하고 있나요?&lt;/p&gt;
&lt;p&gt;최근 기술 면접에서 다음과 같은 질문을 받았습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;하나의 클래스에 두 개의 메소드가 있다고 가정합니다. 둘 중 하나의 메소드에만 @Transactional 어노테이션이 선언되어 있습니다. 그런데 선언되지 않은 메소드에서 선언된 메소드를 호출하면 트랜잭션이 동작하지 않습니다. 그 이유는 무엇인가요?&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;코드로 보면 다음과 같습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class OrderService {

    // @Transactional이 없는 메서드
    public void processAndCreateOrder(Order order) {
        validateOrder(order);
        createOrder(order); // 같은 클래스 안에서 호출
    }

    @Transactional
    public void createOrder(Order order) {
        orderRepository.save(order);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;processAndCreateOrder&lt;/code&gt;가 &lt;code&gt;createOrder&lt;/code&gt;를 호출하니 트랜잭션이 적용될 것 같지만, 실제로는 적용되지 않습니다. 그 이유가 이 글의 핵심입니다.&lt;/p&gt;
&lt;p&gt;처음에는 트랜잭션 전파(propagation)와 관련된 질문이라고 생각했지만, 면접관의 의도는 그게 아니었습니다. 결국 제대로 답하지 못했고, 트랜잭션 어노테이션이 어떻게 동작하는지 원리부터 설명해 보라는 추가 질문을 받았습니다.&lt;/p&gt;
&lt;p&gt;솔직히 프록시 객체를 통해 실행된다는 점 정도만 알고 있을 뿐, 자세한 원리는 설명하지 못했습니다. 개발자로서 TDD, DDD, 클린 코드, 아키텍처, JPA 등 다양한 개념을 공부해 왔지만, 정작 가장 자주 사용하는 어노테이션의 원리를 제대로 몰랐다는 사실이 조금 부끄러웠습니다.&lt;/p&gt;
&lt;p&gt;그래서 이번 기회를 통해 Spring @Transactional 어노테이션의 원리와, 자주 놓치기 쉬운 함정들을 정리해 보고자 합니다.&lt;/p&gt;
&lt;h2&gt;1. Spring AOP와 동적 프록시의 동작 원리&lt;/h2&gt;
&lt;p&gt;Spring의 AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)는 핵심 비즈니스 로직과 부가 기능(트랜잭션, 로깅 등)을 명확히 분리하여 관리할 수 있도록 해주는 기술입니다. Spring은 프록시 객체를 이용해 부가 기능을 메소드 호출 전후에 주입합니다.&lt;/p&gt;
&lt;p&gt;즉, 메소드 호출이 발생하면 프록시 객체가 호출을 중간에서 가로채어 부가 기능을 추가하는 형태입니다.&lt;/p&gt;
&lt;p&gt;그런데 이 프록시는 두 가지 방식으로 만들어집니다. 어떤 방식이 쓰이는지가 뒤에 나올 함정과도 연결되니 짚고 넘어가겠습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JDK Dynamic Proxy&lt;/strong&gt;: 대상 클래스가 인터페이스를 구현하고 있을 때 사용됩니다. 인터페이스를 기반으로 프록시를 만듭니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CGLIB Proxy&lt;/strong&gt;: 인터페이스가 없을 때 사용됩니다. 대상 클래스를 상속받아 프록시를 만듭니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;원래 Spring은 인터페이스가 있으면 JDK, 없으면 CGLIB을 쓰는 방식이었지만, Spring Boot 2.0부터는 기본적으로 CGLIB을 사용합니다. 상속 기반이다 보니, 뒤에서 이야기할 &lt;code&gt;final&lt;/code&gt; 관련 제약도 여기서 생깁니다.&lt;/p&gt;
&lt;h2&gt;2. @Transactional 어노테이션 적용 원리&lt;/h2&gt;
&lt;p&gt;Spring은 @Transactional을 발견하면 다음 구성 요소를 빈으로 등록합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;BeanFactoryTransactionAttributeSourceAdvisor&lt;/strong&gt;: 어떤 메서드에 트랜잭션을 적용할지 판단하는 Advisor입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TransactionInterceptor (Advice)&lt;/strong&gt;: 실제 트랜잭션 로직(시작, 커밋, 롤백)을 수행하는 핵심입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;프록시 객체가 메서드를 호출하면, 어드바이저 체인을 거쳐 TransactionInterceptor가 동작하며 트랜잭션이 관리됩니다.&lt;/p&gt;
&lt;p&gt;TransactionInterceptor가 하는 일을 조금 더 풀어보면 이렇습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TD
    S[&amp;quot;트랜잭션 시작 (begin)&amp;quot;] --&amp;gt; C[&amp;quot;target 메서드 실행&amp;quot;]
    C --&amp;gt;|정상 반환| CM[&amp;quot;커밋 (commit)&amp;quot;]
    C --&amp;gt;|&amp;quot;RuntimeException / Error&amp;quot;| RB[&amp;quot;롤백 (rollback)&amp;quot;]
    C --&amp;gt;|&amp;quot;체크 예외 (IOException 등)&amp;quot;| CM2[&amp;quot;커밋 — 함정 주의&amp;quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 눈여겨볼 점이 하나 있습니다. 트랜잭션이 롤백되는 것은 기본적으로 &lt;code&gt;RuntimeException&lt;/code&gt;이나 &lt;code&gt;Error&lt;/code&gt;가 발생했을 때뿐입니다. 이 부분은 함정과 직결되니 아래에서 다시 다루겠습니다.&lt;/p&gt;
&lt;h2&gt;3. 클래스 내부 호출 시 트랜잭션이 동작하지 않는 이유&lt;/h2&gt;
&lt;p&gt;이제 면접에서 받았던 질문의 답입니다.&lt;/p&gt;
&lt;p&gt;하나의 클래스 내부에서 메서드를 직접 호출하면, 프록시 객체를 거치지 않고 원본 객체(this)를 직접 호출합니다. 프록시를 거치지 않으니 AOP의 어드바이저 체인도 타지 않고, 결국 트랜잭션도 적용되지 않습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;외부 호출&lt;/strong&gt;: 프록시 → TransactionInterceptor → 원본 객체 (트랜잭션 적용)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;내부 호출&lt;/strong&gt;: 원본 객체(this) 직접 호출 (트랜잭션 미적용)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TD
    subgraph ext[&amp;quot;외부 호출 — 트랜잭션 O&amp;quot;]
        A[호출자] --&amp;gt; P[프록시 객체]
        P --&amp;gt; I[&amp;quot;TransactionInterceptor (begin tx)&amp;quot;]
        I --&amp;gt; M1[&amp;quot;원본 객체.createOrder()&amp;quot;]
    end
    subgraph self[&amp;quot;내부 호출 — 트랜잭션 X&amp;quot;]
        M2[&amp;quot;원본 객체.processAndCreateOrder()&amp;quot;] --&amp;gt;|&amp;quot;this.createOrder()&amp;quot;| M3[&amp;quot;원본 객체.createOrder() (프록시 미경유)&amp;quot;]
    end&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;즉, &lt;code&gt;@Transactional&lt;/code&gt;이 붙어 있어도 같은 클래스 안에서 &lt;code&gt;this&lt;/code&gt;로 부르면 아무 소용이 없습니다.&lt;/p&gt;
&lt;p&gt;덧붙이면, 내부 호출은 트랜잭션 적용뿐 아니라 propagation에도 영향을 줍니다. &lt;code&gt;REQUIRES_NEW&lt;/code&gt;를 붙인 메서드를 내부 호출로 부르면, 프록시를 거치지 않으니 새 트랜잭션은 만들어지지 않습니다.&lt;/p&gt;
&lt;h2&gt;4. 그 외 자주 놓치는 함정&lt;/h2&gt;
&lt;p&gt;내부 호출 말고도, 프록시 기반이라는 특성 때문에 조용히 트랜잭션이 안 걸리는 경우가 몇 가지 더 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1) private 메서드에는 적용되지 않습니다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;프록시가 가로챌 수 있는 메서드에만 트랜잭션이 적용되기 때문에, &lt;code&gt;private&lt;/code&gt; 메서드에 &lt;code&gt;@Transactional&lt;/code&gt;을 붙여도 조용히 무시됩니다. 에러도 나지 않아서 오히려 더 위험합니다.&lt;/p&gt;
&lt;p&gt;예전에는 &lt;code&gt;public&lt;/code&gt; 메서드에만 적용됐지만, Spring 6.0(Spring Boot 3.0)부터는 클래스 기반 프록시(CGLIB)에서 &lt;code&gt;protected&lt;/code&gt;, &lt;code&gt;package-private&lt;/code&gt; 메서드도 지원됩니다. 다만 &lt;code&gt;private&lt;/code&gt;은 여전히 제외입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2) 체크 예외는 기본적으로 롤백되지 않습니다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;많이들 놓치는 부분입니다. &lt;code&gt;@Transactional&lt;/code&gt;은 기본적으로 &lt;code&gt;RuntimeException&lt;/code&gt;과 &lt;code&gt;Error&lt;/code&gt;에만 롤백합니다. &lt;code&gt;IOException&lt;/code&gt; 같은 체크 예외가 터지면 롤백되지 않고 그대로 커밋됩니다. 체크 예외에도 롤백이 필요하면 직접 명시해야 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
    orderRepository.save(order);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3) final 클래스/메서드는 프록시를 만들 수 없습니다&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CGLIB은 대상 클래스를 상속해서 프록시를 만드는데, &lt;code&gt;final&lt;/code&gt; 클래스나 &lt;code&gt;final&lt;/code&gt; 메서드는 상속과 오버라이드가 불가능합니다. 그래서 프록시가 만들어지지 않고 트랜잭션도 적용되지 않습니다.&lt;/p&gt;
&lt;h2&gt;5. 해결 방법&lt;/h2&gt;
&lt;p&gt;내부 호출 문제를 푸는 방법은 여러 가지가 있는데, 저는 아래 순서로 고려하는 것을 권합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1) 트랜잭션 메서드를 다른 빈으로 분리 (권장)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;가장 깔끔하고 근본적인 방법입니다. 트랜잭션이 필요한 메서드를 별도 클래스로 옮기면, 호출이 자연스럽게 프록시를 거치게 됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class OrderService {

    private final OrderProcessor orderProcessor;

    public void processAndCreateOrder(Order order) {
        validateOrder(order);
        orderProcessor.createOrder(order); // 다른 빈 → 프록시 경유
    }
}

@Service
public class OrderProcessor {

    private final OrderRepository orderRepository;

    @Transactional
    public void createOrder(Order order) {
        orderRepository.save(order);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2) 자기 자신을 주입받아 사용&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;구조를 바꾸기 어렵다면, 스프링 컨텍스트를 통해 자기 자신(프록시)을 주입받아 호출할 수 있습니다. &lt;code&gt;@Lazy&lt;/code&gt;를 붙이는 이유는 자기 자신을 주입할 때 생기는 순환 참조 문제를 피하기 위해서입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class OrderService {

    private final OrderRepository orderRepository;

    @Autowired
    @Lazy
    private OrderService self;

    @Transactional
    public void createOrder(Order order) {
        orderRepository.save(order);
    }

    public void processAndCreateOrder(Order order) {
        validateOrder(order);
        self.createOrder(order); // 프록시를 통해 호출
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3) 그 외&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TransactionTemplate&lt;/code&gt;으로 트랜잭션을 코드에서 직접 여는 방법&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AopContext.currentProxy()&lt;/code&gt;로 현재 프록시를 얻는 방법 (&lt;code&gt;@EnableAspectJAutoProxy(exposeProxy = true)&lt;/code&gt; 필요)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;상황에 따라 쓸 수 있지만, 개인적으로는 가능하면 1번처럼 설계로 푸는 쪽을 선호합니다.&lt;/p&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;지금까지 Spring @Transactional의 동작 원리와, 내부 호출을 비롯해 자주 놓치는 함정들을 살펴봤습니다.&lt;/p&gt;
&lt;p&gt;돌이켜보면 그때 저는 문제 자체를 제대로 이해하지 못했습니다. 매일 쓰던 어노테이션인데, 정작 어떻게 동작하는지는 모르고 있었던 것입니다. 이번에 정리하면서 그 부분을 처음으로 제대로 들여다봤습니다.&lt;/p&gt;</description>
      <category>Java/Spring</category>
      <author>평범한 개발자...</author>
      <guid isPermaLink="true">https://2dongdong.tistory.com/77</guid>
      <comments>https://2dongdong.tistory.com/77#entry77comment</comments>
      <pubDate>Thu, 7 Oct 2021 16:44:09 +0900</pubDate>
    </item>
    <item>
      <title>REST, RESTful, REST API에 대한 오해와 진실</title>
      <link>https://2dongdong.tistory.com/76</link>
      <description>&lt;h1&gt;서론&lt;/h1&gt;
&lt;p&gt;백엔들 개발자라면 누구나 한 번쯤은 본 유명한 발표자료입니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://slides.com/eungjun/rest&quot;&gt;그런 REST API로 괜찮은가&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;발표에서의 핵심내용은 아래와 같습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;오늘날 대부분의 &amp;quot;REST API&amp;quot;는 사실 REST를 따르지 않고 있다.&lt;br&gt;REST의 제약조건을 만족하지 않고 있기 때문이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;해당 발표로, 우리가 잘못 알고있는 상식들을 바로잡는데 크게 기여했다고 생각합니다.&lt;/p&gt;
&lt;p&gt;그러나 아직까지도 각종 블로그 포스팅글에 REST, RESTful, REST API, RESTful API 등의 용어들이 마치 &lt;code&gt;웹 서비스용 API&lt;/code&gt; 라는 하나의 뜻으로 정의되어 남용 및 혼용되는 경우를 많이 봅니다. &lt;/p&gt;
&lt;p&gt;뭐, 대충 뜻만 통하면 되는거 아닌가...? 라고 생각이 들다가도, &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/77561943-ec73-4107-8b49-21edcf08543e/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;REST의 창시자인 Roy Fielding이 개인 블로그를 통해, REST API라 불리는 것들이 REST 규약을 위반하고 있으니, 규약을 지키거나 다른 단어를 선택하라고 말합니다.&lt;/p&gt;
&lt;p&gt;고로 창시자의 뜻을 이어받아, 우리가 알고있는 REST에 대한 잘못된 상식 이를 바로 잡기위한 글을 써보려고 합니다.&lt;/p&gt;
&lt;p&gt;레츠고!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://thumbs.gfycat.com/ObeseThisBarasinga-size_restricted.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;대표적인 오해 6가지&lt;/h1&gt;
&lt;h2&gt;1. REST는 웹 서비스 API 정의를 위한 아키텍처이다.&lt;/h2&gt;
&lt;p&gt;REST는 웹 서비스 API를 정의할 때 &lt;code&gt;이해하기 쉽고 사용하기 쉬운 URL을 만드는 규칙&lt;/code&gt;이 아닙니다.&lt;/p&gt;
&lt;p&gt;놀랍게도 REST의 개념이 처음 등장한 Roy Fielding의 &lt;a href=&quot;https://www.ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation_2up.pdf&quot;&gt;논문&lt;/a&gt;은 2000년에 출간되었고, 이 시대에는 우리가 흔히 알고있는 웹 서비스 API가 존재하지 않았습니다.&lt;/p&gt;
&lt;p&gt;Roy Fielding은 HTTP/1.0 (1991&lt;del&gt;1995), HTTP/1.1 (1995&lt;/del&gt;1999)의 주요 저자 중 한명이고,&lt;br&gt;수십 년의 변화를 견딜 수 있고, 독립적으로 진화하는 시스템을 만들기 위해 REST를 설계했다고 합니다.&lt;/p&gt;
&lt;p&gt;자세한 내용은 &lt;a href=&quot;https://www.infoq.com/articles/roy-fielding-on-versioning/&quot;&gt;인터뷰&lt;/a&gt;를 통해 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;REST의 정확한 정의는 &lt;code&gt;분산형 하이퍼미디어 시스템을 위한 아키텍처 스타일&lt;/code&gt;이라고 논문에 나와있습니다. REST 아키텍처가 잘 적용된 대표적인 예시는, 우리 모두가 잘 알고있는 &lt;code&gt;웹사이트&lt;/code&gt; 입니다.&lt;/p&gt;
&lt;h2&gt;2. REST는 HTTP 표준을 기반으로 구현한다.&lt;/h2&gt;
&lt;p&gt;REST는 어떤 구현체가 아니라, 아키텍처 스타일입니다. 그리고 REST는 특정 네트워크 프로토콜에 국한되지 않습니다. &lt;/p&gt;
&lt;p&gt;물론 REST가 HTTP 문제점을 해결하기 위해 만든 아키텍처이고, 실제로 REST의 규약들은 웹 환경에 최적화 되어있습니다. 그러나 웹은 REST 스타일 아키텍처가 잘 적용된 하나의 예시이고, 핵심은 분산 하이퍼 미디어 시스템에 있다고 논문 일부에 설명되어있습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The modern Web is one instance of a REST-style architecture. Although Web-based&lt;br&gt;applications can include access to other styles of interaction, the central focus of its&lt;br&gt;protocol and performance concerns is distributed hypermedia&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;3. REST는 HTTP 프로토콜을 활용하는 아키텍처이다.&lt;/h2&gt;
&lt;p&gt;2번과 비슷한 내용입니다. REST는 프로토콜과 관련이 없는 아키텍처이지만, 제약조건 때문에 헷갈릴 수 있습니다.&lt;/p&gt;
&lt;p&gt;REST의 제약조건을 살펴보면 아래와 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Client–server architecture&lt;/li&gt;
&lt;li&gt;Statelessness&lt;/li&gt;
&lt;li&gt;Cacheability&lt;/li&gt;
&lt;li&gt;Layered system&lt;/li&gt;
&lt;li&gt;Code on demand (optional)&lt;/li&gt;
&lt;li&gt;Uniform interface&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;제약조건의 자세한 내용은 &lt;a href=&quot;https://en.wikipedia.org/wiki/Representational_state_transfer&quot;&gt;위키피디아&lt;/a&gt;나 &lt;a href=&quot;https://www.ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation_2up.pdf&quot;&gt;논문&lt;/a&gt; 챕터 5.1부터 확인할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;HTTP에 대해 공부를 하신 분들이라면 &lt;code&gt;이거 HTTP에 대한 내용이 아닌가?&lt;/code&gt; 라는 생각이 드실텐데, 그 이유가 바로 HTTP는 REST 아키텍처 스타일이 반영된 프로토콜이기 때문입니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt; REST는 HTTP/1.0 → HTTP/1.1로 넘어오면서 반영됐습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;HTTP와 REST의 관계?&lt;/h3&gt;
&lt;p&gt;그렇다고해서 HTTP가 REST라는 이야기가 아닙니다. HTTP는 REST 아키텍처 스타일이 반영되었기 때문에, HTTP를 이용하여 REST 제약조건이 잘 지켜진 RESTful 시스템을 쉽게 만들 수 있다는 것이 핵심입니다.&lt;/p&gt;
&lt;p&gt;다른 네트워크 프로토콜을 이용하여 REST 아키텍처를 따르는 시스템을 만든다면 그 또한 RESTful 시스템입니다.&lt;/p&gt;
&lt;h2&gt;4. REST와 RESTful은 같은 말이다.&lt;/h2&gt;
&lt;p&gt;RESTful은 공식적인 단어도, REST의 창시자가 만든 단어도 아닙니다. (논문에 RESTful이란 단어는 등장하지 않습니다.)&lt;/p&gt;
&lt;p&gt;다만, REST의 제약조건을 모두 따르는 시스템을 RESTful, 혹은 RESTful 시스템이라고 전 세계적으로 무언의 약속을 한 것같습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;앞에서도 언급한 &lt;code&gt;웹사이트&lt;/code&gt; 는 REST의 제약조건을 모두 따르기 때문에 RESTful 이라 할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;5. REST API는 HTTP 메서드를 사용하여 검색, 생성, 업데이트, 삭제한다.&lt;/h2&gt;
&lt;p&gt;반복하지만 REST는 특정 네트워크 프로토콜에 국한되는 개념이 아닙니다.&lt;/p&gt;
&lt;p&gt;REST API는 &lt;code&gt;REST의 제약조건을 따르는 API 이다.&lt;/code&gt; 정도로 나타낼 수 있고, &lt;code&gt;HTTP 메서드를 사용하여 검색, 생성, 업데이트, 삭제한다.&lt;/code&gt; 라는 내용은 단순히 &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7231&quot;&gt;HTTP RPC&lt;/a&gt;의 일부입니다.&lt;/p&gt;
&lt;h2&gt;6. /customer 또는 /cusrtomer/1과 같은 리소스에 대해 알기쉬운 URL를 정의하는 것이 REST 제약 조건에 해당하며 RESTful API라 부른다.&lt;/h2&gt;
&lt;p&gt;REST와 관련이 없는 내용입니다. 아마 이런 내용이 널리 퍼지게된 이유 중 하나가 &lt;a href=&quot;https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md&quot;&gt;마이크로소프트 REST API 가이드 라인&lt;/a&gt; 때문이지 않을까 싶네요.&lt;/p&gt;
&lt;p&gt;7장에 아래와 같은 내용이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/c5758d59-75ec-4216-9b7b-904de524e303/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;인간은 URL을 쉽게 읽고 구성할 수 있어야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;REST 가이드 라인에 이런 내용이 있으니, 해당 내용이 REST의 제약 조건이라고 착각하게 만들었나봅니다.&lt;/p&gt;
&lt;h1&gt;REST를 이해하기 어려운 이유&lt;/h1&gt;
&lt;h2&gt;1. REST라는 단어가 남용되고 있다.&lt;/h2&gt;
&lt;p&gt;우리가 처음 접하게 되는 REST API, RESTful API라는 용어는 대부분 &lt;code&gt;웹 서비스 API 정의를 위한 아키텍처&lt;/code&gt; 정도로 생각합니다.&lt;/p&gt;
&lt;p&gt;수 많은 블로그에 나와있는 내용들을 보면서, REST는 HTTP 메소드와 알기 쉬운 URL을 통해 쉽고 편하게 사용할 수 있는 사용자 API를 만들기 위한 아키텍처 혹은 제약사항 정도로 이해하게 됩니다.&lt;/p&gt;
&lt;h2&gt;2. REST라는 단어를 직역하여 이해하려고 한다.&lt;/h2&gt;
&lt;p&gt;REST는 Representational state transfer의 줄임말이며, 번역기나 사전을 통해 직역하면 &lt;code&gt;대표 상태 이전&lt;/code&gt; 혹은 &lt;code&gt;표현 상태 이전&lt;/code&gt; 인데, REST의 제약사항을 보았을때 &lt;code&gt;표현 상태 전송&lt;/code&gt;이 더 어울리는것 같습니다.&lt;/p&gt;
&lt;p&gt;REST를 &lt;code&gt;웹 서비스 API 정의를 위한 아키텍처&lt;/code&gt;로 알고있는 상태에서 REST의 풀네임의 직역 및 정의를 읽게되다면 서로 매칭이 안되니까, 무슨 말인지 이해못하는게 당연하지 않나 생각됩니다.&lt;/p&gt;
&lt;h1&gt;REST는 왜 만들어졌는가?&lt;/h1&gt;
&lt;p&gt;REST를 왜 만들었는지 그 의도와 해결하기 위한 고민을 알게되면 이해하기 쉽습니다.&lt;/p&gt;
&lt;p&gt;Roy Fielding은 수십 년의 변화를 견딜 수 있는 시스템을 만들고 싶어 하였고, 그 시스템을 만들기 위해 끊임없는 질문과 고민을 하였습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;웹을 손상시키지 않고 HTTP를 어떻게 발전시킬 수 있을까?&lt;/li&gt;
&lt;li&gt;시대가 바뀌면서 확장성 및 일관성 문제는 어떻게 해결할 수 있을까?&lt;/li&gt;
&lt;li&gt;Web과 같은 분산 하이퍼미디어 시스템이 생겨난다면 서로 어떻게 연결해야 할까?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이런 고민끝에 만들어진게 REST이고, REST가 반영된 프로토콜이 HTTP이며, 이 HTTP를 이용하여 웹이 서로 통신합니다. 그래서 우리는 여전히 최신 웹브라우저를 통해 80~90년대에 만들어진 웹사이트에 접속할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://thoughtcatalog.com/jeremy-london/2018/09/oldest-websites-on-the-internet/&quot;&gt;인터넷에서 가장 오래된 웹사이트 15개&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;REST를 꼭 알아야할까?&lt;/h1&gt;
&lt;p&gt;Roy Fielding의 PPT &lt;a href=&quot;https://www.slideshare.net/AEMHub2014/rest-in-aem-by-roy-fielding&quot;&gt;발표&lt;/a&gt; 자료 마지막에 나와있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/7fc13e04-b5d6-4bab-9e17-eed8bedb3c9b/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;시스템 전체를 제어할 수 있다고 생각하거나, 진화에 관심이 없다면, REST에 대해 논쟁하는 데 시간을 낭비하지 마세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;라고 합니다.&lt;/p&gt;
&lt;h1&gt;진정한 REST API에 대해 알고싶다.&lt;/h1&gt;
&lt;p&gt;Roy Fielding 개인 블로그 글 (&lt;a href=&quot;https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven&quot;&gt;REST API는 하이퍼텍스트 기반이어야 합니다.&lt;/a&gt;)&lt;br&gt;을 통해 사람들이 자주 위반하는 룰 6가지 정도를 설명합니다.&lt;/p&gt;
&lt;p&gt;그리고 REST, REST API, RESTful API에 관한 사람들의 질문에 대한 답글 또한 최대한 길게 풀어서 설명합니다.&lt;/p&gt;
&lt;p&gt;제가 설명하는것 보단, 해당 글을 번역해서 쭉 읽는게 훨씬 더 도움이 될 것 같습니다.&lt;/p&gt;
&lt;h1&gt;결론&lt;/h1&gt;
&lt;p&gt;공부하면서 REST가 무엇인지 정확하게 알게되었습니다. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/7ee28080-d447-40b8-a21a-bf3d6242c038/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;API를 개발하는 백엔드 개발자로써, REST는 저랑 상관 없는 단어라는 것을 알게되었습니다.&lt;/p&gt;
&lt;p&gt;그리고 수 십년을 내다본 Roy Fielding이란 인물을 매우 존경하게 되었습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;[참고]&lt;br&gt;&lt;a href=&quot;https://www.infoq.com/articles/roy-fielding-on-versioning/&quot;&gt;https://www.infoq.com/articles/roy-fielding-on-versioning/&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://deview.kr/2017/schedule/212&quot;&gt;https://deview.kr/2017/schedule/212&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;http://slides.com/eungjun/rest#/69&quot;&gt;http://slides.com/eungjun/rest#/69&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://twobithistory.org/2020/06/28/rest.html#fnref:6&quot;&gt;https://twobithistory.org/2020/06/28/rest.html#fnref:6&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://www.ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation_2up.pdf&quot;&gt;https://www.ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation_2up.pdf&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://stackoverflow.com/questions/2190836/what-is-the-difference-between-http-and-rest&quot;&gt;https://stackoverflow.com/questions/2190836/what-is-the-difference-between-http-and-rest&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://news.ycombinator.com/item?id=7201871&quot;&gt;https://news.ycombinator.com/item?id=7201871&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Representational_state_transfer&quot;&gt;https://en.wikipedia.org/wiki/Representational_state_transfer&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven&quot;&gt;https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://shoark7.github.io/programming/knowledge/what-is-rest&quot;&gt;https://shoark7.github.io/programming/knowledge/what-is-rest&lt;/a&gt;&lt;/p&gt;</description>
      <category>Server</category>
      <author>평범한 개발자...</author>
      <guid isPermaLink="true">https://2dongdong.tistory.com/76</guid>
      <comments>https://2dongdong.tistory.com/76#entry76comment</comments>
      <pubDate>Tue, 5 Oct 2021 10:38:28 +0900</pubDate>
    </item>
    <item>
      <title>GitLab CI/CD + SSH 공개키를 이용한 자동배포</title>
      <link>https://2dongdong.tistory.com/75</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Start&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gitlab + AWS 연동에 관한 글을 참 많지만.. 저처럼 IDC 서버를 사용하는 사람들에겐 해당하지 않는 내용이라..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료도 많지 않고, 설명이 부실한 글들이 많아서 직접 정리해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무작정 따라하는것 보다, 공개키를 이용한 SSH 접속의 전체적인 흐름을 이해하고 읽는 것이 훨씬 더 도움됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@lehdqlsl/SSH-%EA%B3%B5%EA%B0%9C%ED%82%A4-%EC%95%94%ED%98%B8%ED%99%94-%EB%B0%A9%EC%8B%9D-%EC%A0%91%EC%86%8D-%EC%9B%90%EB%A6%AC-i7rrv4de&quot;&gt;SSH 공개키 인증을 사용하여 접속하기&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Process&lt;/h3&gt;
&lt;ol 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;패스워드 입력 없이 SSH 접속하기 위함.&lt;/li&gt;
&lt;li&gt;패스워드 대신 공개키 및 비밀키 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Gitlab Variables 등록
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포할 서버의 IP 혹은 도메인 주소 (노출돼도 상관없으면 스크립트에 직접 넣어도 됩니다.)&lt;/li&gt;
&lt;li&gt;SSH 비밀키 (생성한 키의 비밀키)&lt;/li&gt;
&lt;li&gt;(옵션) 배포 서버의 SSH 공개키 (주의: 생성한 키의 공개키가 아님)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;known_hosts로 등록하기 위함&lt;/li&gt;
&lt;li&gt;서버의 SSH 공개키는 &lt;code&gt;/etc/ssh&lt;/code&gt; 경로에 있으며, ssh 설치 시 생성됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;배포 서버에 공개키 등록 (생성한 키의 공개키)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;~.ssh/authorized_keys 파일에 등록.&lt;/li&gt;
&lt;li&gt;SSH 클라이언트 접속 시, 등록되어있는 공개키의 해당하는 비밀키를 가지고 있다면 접속을 허용해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Gitlab 스크립트 작성&lt;/li&gt;
&lt;li&gt;자동 배포 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 클라이언트 키쌍 생성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ssh-keygen 명령어를 이용하여 키쌍을 생성합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Windows 10이나 대부분 리눅스는 기본적으로 설치가 되어있는데, 없으면 설치하면 됩니다.&lt;/li&gt;
&lt;li&gt;cmd 창에 &lt;code&gt;ssh-keygen&lt;/code&gt; 명령어를 입력하고, 엔터를 쭉쭉 눌러주면 C:Users\계정\.ssh\ 경로에 키쌍이 생성됩니다.&lt;/li&gt;
&lt;li&gt;ssh-keygen 명령어의 기본 값은 아래와 같습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RSA 3072 비트 (SHA256)&lt;/li&gt;
&lt;li&gt;키 생성 경로: ~.ssh\
&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;비밀키 (id_rsa)&lt;/li&gt;
&lt;li&gt;공개키 (id_rsa.pub)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;키에 패스워드를 지정하지 않음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/2b084e1d-e78b-49a8-966d-09888ee35ad5/image-20210913171744638.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Gitlab Variables 등록&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Settings -&amp;gt; CI/CI -&amp;gt; Variables -&amp;gt; Add variable&lt;/li&gt;
&lt;li&gt;저는 이미 변수를 등록해놓은 상태입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/a2a3923a-79ce-401e-8a37-87d029fef82f/image-20210913172658544.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ol 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;Key
&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;/li&gt;
&lt;li&gt;Value
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아까 생성한 비밀키(id_rsa)를 붙여넣습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Type
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;File로 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/811be768-1d06-43fd-be59-76b9aa86781e/image-20210913172819444.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; start=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 IP or 도메인 등록&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/d18f3e92-e9a5-4649-8584-c78750332f84/image-20210913173439368.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; start=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(Option) 배포 서버의 SSH 공개키 등록&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;서버에 접속하여, /etc/ssh/ssh_host_*.pub 파일들을 확인합니다.&lt;/li&gt;
&lt;li&gt;보통 ssh-server를 설치하면 3개의 키쌍이 생성되는데, 이 중 아무 공개키를 사용해도 무관합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/b30aa5dc-4eb8-477c-bfa0-bade89a206c2/image.png&quot; alt=&quot;&quot; /&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/3d4eb8e9-aa47-443f-b2b1-8b0e77c09e76/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래와 같이 변수로 등록해줍니다. (서버 IP주소 입력 후 한칸 띄어야 합니다.)&lt;/li&gt;
&lt;li&gt;만약 포트가 22번이 아니라면 &lt;code&gt;[IP주소]:포트&lt;/code&gt; 형태로 입력합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) [111.111.111.111]:2243 ssh-ed25519 AAAA.........JJJ&lt;br /&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/65938299-b54e-4d1c-9948-f88ce503ca6c/image.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&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;서버에 처음 SSH 접속 시 아래와 같은 메시지를 많이 보셨을겁니다.&lt;br /&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/2fb8fa28-58df-45b6-9367-ee621566b56a/image.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/1b51adb1-c5b1-4d5d-9430-6cc376cd8bd5/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로컬에 서버의 키가 등록 되어있지 않은데, 이 서버를 신뢰할 수 있다면 로컬에 서버키를 추가 후 SSH 접속을 하고, 그렇지 않다면 접속하지 않습니다.&lt;/li&gt;
&lt;li&gt;키를 추가하게 되면 다음부터 이 서버에 접속할 때 마다, 서버에 대한 인증을 위해 로컬에 저장되어있는 키를 이용하여 인증하는 과정을 거칩니다.&lt;/li&gt;
&lt;li&gt;이런 과정을 통해 중간자 공격을 예방하여 안전하게 SSH 접속이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Gitlab 변수로 등록하는 이유&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;SSH 클라이언트를 사용하는 우리 입장에서는, 엔터를 치거나 마우스 클릭으로 등록을 하면 됩니다.&lt;/li&gt;
&lt;li&gt;그러나, 빌드 할 때마다 매번 새로운 도커 이미지가 생성되고 콘솔에서 조작이 불가능한 Gitlab에서는 스크립트를 통해 서버키를 미리 등록함으로써 해당 과정을 생략할 수 있습니다.&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;공개키 등록 대신에, ssh 설정으로 이 서버키 등록 과정을 생략하고 SSH 접속을 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;바로 Host Key Checking을 비활성화 하는 것인데요,&lt;/li&gt;
&lt;li&gt;이 옵션을 추가하게 되면, 처음 접속할 때 공개키 등록 없이 바로 SSH 접속이 가능하게 되지만, 중간자 공격에 취약할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 배포 서버에 공개키 등록&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;키쌍으로 생성한 공개키를 서버의 ~/.ssh/authorized_keys 파일에 등록합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;많이 실수하는 부분인데, SSH로 접속할 계정의 홈디렉토리/.ssh 폴더입니다.&lt;/li&gt;
&lt;li&gt;일반 유저계정으로 SSH 접속할건데, /root/.ssh/authorized_keys 파일을 수정하면 안됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;공개키 등록 방법
&lt;ol 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;vi와 같은 편집기를 통해 공개키 복사 및 붙여넣기 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ssh-copy-id 명령어 이용
&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;윈도우는 ssh-copy-id 명령어가 없기 때문에, 다른 명령어로 대체합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type $env:공개키위치 | ssh 계정@IP &quot;cat &amp;gt;&amp;gt; .ssh/authorized_keys&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;만약 Windows에서 키쌍을 생성했다면 공개키의 경로는 &lt;code&gt;C:\Users\계정\.ssh\id_rsa.pub&lt;/code&gt; 입니다.&lt;/li&gt;
&lt;li&gt;위 명령어를 수행하면 공개키값을 변수에 담아서 SSH 접속 후, .ssh/authorized_keys에 붙여 넣게됩니다.&lt;/li&gt;
&lt;li&gt;서버에 접속 후 authorized_keys을 확인해보면 공개키값이 잘 들어가있는 것을 확인 할수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;리눅스계열
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리눅스는 간단합니다. &lt;code&gt;ssh-copy-id 계정@IP&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;해당 명령어를 입력하면 ~/.ssh 폴더에 있는 공개키를 해당 서버에 등록합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 스크립트 작성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트 최상위 폴더에 .gitlab-ci.yml 파일을 생성합니다.&lt;/li&gt;
&lt;li&gt;아래 스크립트 내용 붙여넣습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;stages:
    - build
    - deploy

image: java:8-jdk # jdk8 도커 이미지 사용 (배포환경에 따라 변경할 것)

cache:
    paths: # .gradle 캐시 적용
        - .gradle/wrapper
        - .gradle/caches

build:
    stage: build
    before_script:
        - chmod +x ./gradlew # gradlew 실행권한 부여 (초기 권한 666 [-rw-rw-rw-])
    script:
        - ./gradlew build
    artifacts: 
        paths:
            - build/libs/*.jar    # build의 결과물인 jar파일을 artifacts로 지정. 
        expire_in: 1 week    # 1주일 동안 보관

deploy-to-server:
    stage: deploy
    before_script:
        - mkdir -p ~/.ssh
        - eval $(ssh-agent -s) # ssh-agent 백그라운드 실행

        ###############################################
        # 공개키 등록을 안했다면 Host Key Checking 비활성화 옵션 추가 (중간자 공격 위험)       
        # - '[[ -f /.dockerenv ]] &amp;amp;&amp;amp; echo -e &quot;Host *\n\tStrictHostKeyChecking no\n\n&quot; &amp;gt;&amp;gt; ~/.ssh/config'    

        # 공개키 등록을 했다면 known_hosts 등록 및 권한 변경
        # - echo &quot;$SSH_KNOWN_HOSTS&quot; &amp;gt;&amp;gt; ~/.ssh/known_hosts
        # - chmod 644 ~/.ssh/known_hosts
        ###############################################

        - chmod 600 &quot;$SSH_KEY&quot; # 개인키 파일 권한변경 
        - ssh-add &quot;$SSH_KEY&quot;   # SSH 개인키 추가
    script:
        # SSH를 이용한 원격 파일 업로드
        - scp build/libs/*.jar 계정명@&quot;$DEPLOY_SERVER_IP&quot;:~/BUILD_PATH/build.jar
        # 원격 스크립트 실행
        - ssh 계정명@&quot;$DEPLOY_SERVER_IP&quot; /BUILD_PATH/start.sh&lt;/code&gt;&lt;/pre&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;단순히 기존 프로세스가 있으면 kill 후 재실행, 없으면 그냥 실행합니다.
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;#!/bin/sh
cd ~/BUILD_PATH
PID=$(ps -ef|grep build.jar|grep -v grep|awk '{print $2}')
if [ &quot;$PID&quot; == &quot;&quot; ]; then
  echo &quot;no process exist&quot;
else
  echo &quot;process id (${PID}) killed&quot;
  kill -9 ${PID}
fi
echo &quot;Program Start&quot;
nohup java -jar build.jar 1 &amp;gt; /dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 자동 배포 확인&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Gitlab으로 Push 하여 CI/CD를 동작시킵니다.&lt;br /&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/b0718633-7e5a-4ca0-a4c4-db6e75fc00d3/image.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;li&gt;별다른 문제 없이 성공하였다면, 서버에 접속하여 프로세스가 동작하는지 확인합니다.&lt;br /&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/866bae04-ff79-4bbb-ac29-fd9fc1654628/image.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Server</category>
      <author>평범한 개발자...</author>
      <guid isPermaLink="true">https://2dongdong.tistory.com/75</guid>
      <comments>https://2dongdong.tistory.com/75#entry75comment</comments>
      <pubDate>Tue, 14 Sep 2021 15:20:47 +0900</pubDate>
    </item>
    <item>
      <title>MariaDB에는 'innodb buffer pool instances' 변수 설정이 없다...?</title>
      <link>https://2dongdong.tistory.com/74</link>
      <description>&lt;p&gt;최근 innodb buffer pool 설정 관련해서 찾아보다가 알게된 사실입니다.&lt;/p&gt;
&lt;p&gt;MriaDB 10.5 변경사항에 innoDB 성능 항샹 내용 중 &lt;code&gt;Multiple buffer pool Remove&lt;/code&gt;가 있습니다.&lt;br&gt;상식적으로 생각해봤을때 멀티로 동작하는게 무조건 성능이 좋다고 생각이 드는데, 지운 이유가 무엇일까요? 궁금해서 조금 찾아봤습니다.&lt;br&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/09ad5be3-b798-45f7-88d3-2cd4fd809855/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;innodb_buffer_pool_instances 변수 설명을 확인해보니, 10.5.1부터 해당 변수의 값이 비활성화되고 10.6.0부터 제거되었습니다.&lt;br&gt;제거된 이유는 단 한줄로 표시하였네요.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;버퍼 풀을 분할하는 본래의 이유가 사라졌다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/8cf54fa5-b15e-4732-9e79-5036e39be230/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;왜인지 이유는 자세하게 설명을 안해주네요.. 궁금하게 참...&lt;br&gt;먼저 multiple buffer pool을 사용하는 이유 부터 알아보면&lt;/p&gt;
&lt;h3&gt;multiple buffer pool을 사용 이유&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;MySQL 공식 홈페이지에 잘 설명되어있습니다. (&lt;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_instances&quot;&gt;원문&lt;/a&gt;)&lt;br&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/a8ab24f2-392d-4034-8519-ecbebf249b86/image.png&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;li&gt;*&lt;em&gt;다른 스레드가 캐시된 페이지를 읽고 쓸 때 경합을 줄임으로써 동시성을 향상시킬 수 있다. *&lt;/em&gt;가 핵심.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이유만 들어보면 엄청난 성능 향상이 있을것 같은데... 왜 없앴을까요??&lt;/p&gt;
&lt;h3&gt;지라 이슈 제기&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;지라 이슈 제기: &lt;a href=&quot;https://jira.mariadb.org/browse/MDEV-15058&quot;&gt;Remove multiple InnoDB buffer pool instances&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;요약&lt;ul&gt;
&lt;li&gt;MySQL 5.5.5 버전부터 multiple buffer pools 도입 이후 &lt;a href=&quot;https://github.com/mysql/mysql-server/commit/ce6109ebfdedfdf185e391a0c97dc6d33867ed78&quot;&gt;뮤텍스 대신 rw-locks를 사용하도록 MySQL 5.6.2에 수정&lt;/a&gt; 과 같은 버퍼 풀간 뮤텍스 경합을 줄이기 위해 다수의 수정이 있었음&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jira.mariadb.org/browse/MDEV-15016&quot;&gt;multiple page cleaner threads use a lot of CPU on idle server&lt;/a&gt; 문제 검토 중 multiple buffer pools이 정말 성능향상에 도움이 되는지 궁금 &lt;/li&gt;
&lt;li&gt;그리고 뮤텍스 관련 &lt;a href=&quot;https://jira.mariadb.org/browse/MDEV-15053%5D&quot;&gt;MySQL 8.0.0 기능 추가&lt;/a&gt;되면서 MariaDB도  비슷한 작업을 수행해야한다고 이슈 제기.&lt;ul&gt;
&lt;li&gt;그러나 multiple buffer pools 과 page cleaners를 지원하기 위해 모든 코드를 지우는 것을 고려해야 한다 생각하고,&lt;/li&gt;
&lt;li&gt;향후 multiple buffer pools이 필요할 경우, 처음부터 잘 설계해야된다고 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;결론은 single instance buffer pools과 multiple buffer pools의 성능을 비교해서, 단일 인스턴스 버퍼 풀이어도 성능 저하가 없다면 코드를 단순화 해야된다고 주장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;다시 한번 정리 (뇌피셜로 분석한 제 생각입니다...)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;multiple buffer pools 도입 이후 버퍼 풀간 뮤텍스 경합을 줄이기 위해 다수의 수정으로 코드가 복잡해져있는 상태에서&lt;/li&gt;
&lt;li&gt;MySQL의 Mutex 분할 기능을 도입하기엔  설계 문제로 불가능하고 판단,&lt;/li&gt;
&lt;li&gt;multiple buffer pools로 인해 생기는 성능 이슈도 있으니&lt;/li&gt;
&lt;li&gt;single buffer pools과 multi buffer pools의 성능의 큰 차이가 없다면 기존 코드를 제거하여 single buffer pools로 가자!&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;2년간 이어진 기나긴 테스트...&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;지라 댓글 히스토리를 쭉 보니까, 거의 2년에 걸쳐서 테스트는 진행되었고 그 과정이 쉽지 않았다는게 느껴집니다.&lt;/li&gt;
&lt;li&gt;single buffer pools 에서 생기는 병목현상을 다른 방향으로 해결하기 위해 여러 수정이 있었고,&lt;/li&gt;
&lt;li&gt;결국 single buffer pools으로도 multiple 만큼 성능을 발휘하도록 만들었다고 하네요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/575715ec-503b-4f2c-b493-413e98d24a01/image-20210831141408538.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;MySQL의 innodb buffer pool instances는?&lt;/h3&gt;
&lt;p&gt;percona의 CTO가 innodb buffer pool instances 관련 테스트 글을 작성하였습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://www.percona.com/blog/2020/08/13/how-many-innodb_buffer_pool_instances-do-you-need-in-mysql-8&quot;&gt;How Many innodb_buffer_pool_instances Do You Need in MySQL 8?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.percona.com/blog/2020/08/14/part-two-how-many-innodb_buffer_pool_instances-do-you-need-in-mysql-8-with-a-cpu-bound-workload/&quot;&gt;Part Two: How Many innodb_buffer_pool_instances Do You Need in MySQL 8 With a CPU-Bound Workload?&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;innodb_buffer_pool_instances 라는 매개변수가 어떤 값으로 설정해야 좋은지 나쁜지 애매모호하여 다양한 시나리오에서 테스트를 진행했다고 합니다.&lt;ul&gt;
&lt;li&gt;서버 사양은 cpu core 80개, 메모리 188GB&lt;/li&gt;
&lt;li&gt;innodb_buffer_pool_instances를 1, 2, 4, 8, 16, 32, 64 로 설정하여 테스트 진행&lt;/li&gt;
&lt;li&gt;첫 테스트는 innodb_buffer_pool_size를 25GB로, 두번째 테스트는 140GB로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;해당글의 결론은 아래와 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;높은 처리량과 적은 변동성을 고려하였을 때 64가 최선의 선택이지만, 그렇다고 무작정 이 값을 추천할 수 없다. (하드웨어 및 부하량에 따라 다름)&lt;/li&gt;
&lt;li&gt;1-4는 처리량에 대한 변동성이 크기 때문에 8부터 테스트를 진행하여 최적의 값을 찾는걸 추천.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;나의 생각&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;설정 값에 대한 정답은 없다. 하드웨어 사양 및 환경에 따라 다 다르고, 다양한 설정값으로 밴치마크를 하여 직접 비교해보는 것이 정답인듯 합니다.&lt;/li&gt;
&lt;li&gt;시간이 된다면 MariaDB 10.6 vs MySQL8의 innodb_buffer_pool_instances 관련 성능 비교글을 작성해보고 싶네요.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Server/Database</category>
      <author>평범한 개발자...</author>
      <guid isPermaLink="true">https://2dongdong.tistory.com/74</guid>
      <comments>https://2dongdong.tistory.com/74#entry74comment</comments>
      <pubDate>Mon, 13 Sep 2021 09:15:53 +0900</pubDate>
    </item>
    <item>
      <title>SSH 공개키 인증을 사용하여 접속하기</title>
      <link>https://2dongdong.tistory.com/73</link>
      <description>&lt;h1&gt;1. 공개키 암호화란?&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;공개 키 암호 방식은 암호 방식의 한 종류로 사전에 비밀 키를 나눠가지지 않은 사용자들이 안전하게 통신할 수 있도록 한다.&lt;br&gt;공개 키 암호 방식에서는 공개 키와 비밀 키가 존재하며, 공개 키는 누구나 알 수 있지만 그에 대응하는 비밀 키는 키의 소유자만이 알 수 있어야 한다.&lt;br&gt;공개 키는 보안 타협 없이 공개적으로 배포가 가능하다.&lt;br&gt;공개 키 암호를 구성하는 알고리즘은 대칭 키 암호 방식과 비교하여 비대칭 암호라고 부르기도 한다.&lt;br&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EA%B3%B5%EA%B0%9C_%ED%82%A4_%EC%95%94%ED%98%B8_%EB%B0%A9%EC%8B%9D&quot;&gt;위키백과&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;공개키-개인키(비밀키)가 한 쌍으로 이루어져 있는 키 쌍이라 합니다.&lt;br&gt;공개키는 누구나 가질 수 있고, 개인키는 개인이나 중요한 사람만이 가지고 있습니다.&lt;/p&gt;
&lt;p&gt;이런 특성을 이용하여 공개키 방식으로 암호화 및 인증에 사용됩니다.&lt;/p&gt;
&lt;p&gt;메시지를 공개키로 암호화하면 개인키를 가지고 있는 사람만이 메시지를 복호화하여 메시지를 확인 할 수 있습니다. 개인키를 탈취당하지 않는 이상, 암호화된 메시지가 유출되어도 다른 사람이 절대 읽을 수 없습니다.&lt;/p&gt;
&lt;h1&gt;2. SSH 공개키 접속&lt;/h1&gt;
&lt;p&gt;SSH는 기본적으로 사전에 설정된 패스워드를 입력하여 접속하지만, 공개키 암호화에 사용되는 &lt;strong&gt;키 쌍&lt;/strong&gt;을 가지고 접속할 수 있는 기능을 제공합니다.&lt;/p&gt;
&lt;p&gt;사전에 자신의 공개키를 서버에 나눠주고, SSH 접속 시 개인키를 이용하여 사용자를 증명하는 원리로 로그인하게 됩니다.&lt;/p&gt;
&lt;p&gt;아래는 ssh 접속 과정을 나타낸 시퀀스 다이어그램 입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/e3c60ea2-9716-452b-8b7f-1bbbed626347/%EA%B7%B8%EB%A6%BC1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h1&gt;3. SSH 공개키 접속 방법&lt;/h1&gt;
&lt;p&gt;3.1 및 3.2는 공개키를 이용한 SSH 접속 방법을 설명하기 앞서 기본적으로 알아야할 사전지식입니다.&lt;/p&gt;
&lt;h2&gt;3.1 SSH 클라이언트 (사용자)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;클라이언트 키 쌍&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ssh-keygen&lt;/strong&gt; 명령어를 이용하여 공개키 및 개인키 생성&lt;/li&gt;
&lt;li&gt;키 쌍을 생성하게 되면 기본적으로 아래 경로에 생성됨&lt;ul&gt;
&lt;li&gt;~/.ssh/&lt;strong&gt;id_rsa&lt;/strong&gt; (개인키)&lt;/li&gt;
&lt;li&gt;~/.ssh/&lt;strong&gt;id_ras.pub&lt;/strong&gt; (공개키)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서버의 authorized_keys 파일에 생성된 사용자 공개키를 제공해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;~/.ssh/&lt;strong&gt;known_hosts&lt;/strong&gt; 파일&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사용자가 처음 서버에 로그인 할 때, 연결을 진행하면 서버의 공개키가 해당 파일에 기록됨&lt;h2&gt;3.2 SSH 서버 (호스트)&lt;/h2&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;서버 키 쌍&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서버에서 openssh-server를 설치하는 과정에 자동 생성&lt;br&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/a24c9fd1-46a1-440e-adeb-0a817432e951/image.png&quot; alt=&quot;&quot;&gt; &lt;ul&gt;
&lt;li&gt;/etc/ssh/&lt;strong&gt;ssh_host_ras_key&lt;/strong&gt; (개인키)&lt;/li&gt;
&lt;li&gt;/etc/ssh/&lt;strong&gt;ssh_host_ras_key.pub&lt;/strong&gt; (공개키)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클라이언트가 서버에 처음 연결 시도 시 해당 공개키를 클라이언트에게 제공. 클라이언트는 &lt;strong&gt;known_hosts&lt;/strong&gt; 파일에 서버의 공개키를 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;~/.ssh/&lt;strong&gt;authorized_keys&lt;/strong&gt; 파일&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;해당 파일에 로그인할 클라이언트의 공개키가 기록되어있어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3.3 SSH 공개키 접속&lt;/h2&gt;
&lt;h3&gt;3.3.1 클라이언트 키 쌍 생성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;아래 명령어를 이용하여 RSA 알고리즘 키 쌍 생성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$ ssh-keygen -t rsa&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;별 다른 설정이 필요하지 않으므로, 엔터를 계속 누르면 현재 로그인되어있는 사용자의 홈디렉토리 .ssh 폴더 밑에 키 쌍이 생성 됨&lt;br&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/a1081ead-cac1-451b-8611-4bda700d39b2/image.png&quot; alt=&quot;&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3.2 공개키 전송&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;리눅스계열에서는 공개키를 복사하는 명령어 &lt;strong&gt;ssh-copy-id&lt;/strong&gt; 를 사용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;$ ssh-copy-id USER@remote-host&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;예) $ ssh-copy-id &lt;a href=&quot;mailto:lee@192.168.0.15&quot;&gt;lee@192.168.0.15&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;-i 옵션을 사용하지 않으면, 현재 로그인 되어있는 계정의 .ssh/id_rsa.pub 파일을 전송 (기본값)&lt;/li&gt;
&lt;li&gt;공개키 파일이 다른 경로에 있다면 -i 옵션을 이용하여 별도의 경로를 지정해야 함&lt;ul&gt;
&lt;li&gt;$ ssh-copy-id -i /home/test/key/key.pub &lt;a href=&quot;mailto:lee@192.168.0.15&quot;&gt;lee@192.168.0.15&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;윈도우에서는 &lt;strong&gt;ssh&lt;/strong&gt;와 &lt;strong&gt;cat&lt;/strong&gt;을 이용한 명령어 조합을 이용 &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;type $env:공개키경로 | ssh USER@remote-host &amp;quot;cat &amp;gt;&amp;gt; .ssh/authorized_keys&amp;quot;&lt;/li&gt;
&lt;li&gt;예) type $env:C:\Users\LDB.ssh\id_rsa.pub | ssh &lt;a href=&quot;mailto:lee@192.168.0.15&quot;&gt;lee@192.168.0.15&lt;/a&gt;  &amp;quot;cat &amp;gt;&amp;gt; .ssh/authorized_keys&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3.3 로그인&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;공개키 방식으로 SSH 접속 시 -i 옵션 생략 시, 아래 경로에 있는 개인키로 접속을 시도&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;윈도우: USER_NAME\.ssh\id_rsa&lt;/li&gt;
&lt;li&gt;리눅스: USER_NAME/.ssh/id_rsa&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;만약 개인키의 위치가 다르거나 AWS의 pem 파일인 경우 -i 옵션 이용&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;$ssh -i KEY_PATH\key.pem USER@remote-host&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$ssh -i KEY_PATH\id_rsa USER@remote-host&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3.4 키 파일 권한 문제 (Permissions are too open)&lt;/h3&gt;
&lt;p&gt;개인키를 windows &amp;lt;-&amp;gt; linux간 FTP로 복사하다가 발생하는 권한 문제입니다.&lt;br&gt;ssh에 사용되는 각 종 파일들 (설정파일,공개키,개인키 등)은 보안적인 이슈로 인해 아래 사진과 같이 권한이 정해져있습니다.&lt;br&gt;&lt;img src=&quot;https://images.velog.io/images/lehdqlsl/post/dd201cdb-817e-48ee-aa7a-bafe974b8dd4/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;위 사진을 보면 Mandatory Permission 이라고 정해진 권한이 있는데, 개인키 파일과 설정파일은 꼭 600으로 설정이 되어있어야 한다는 뜻입니다. (600은 소유자만 읽고 쓸 수 있는 권한)&lt;br&gt;만약 600이 아니라면 chmod 명령어를 통해 권한을 수정하면 됩니다.&lt;/p&gt;
&lt;p&gt;만약 윈도우에서 발생했다면 해당 파일에 대한 상속 권한을 비활성화 시키고, 현재 윈도우 계정만 개인키 파일에 접근할 수 있도록 설정을 변경해줍니다. &lt;/p&gt;
&lt;p&gt;자세한 방법은 &lt;a href=&quot;https://techsoda.net/windows10-pem-file-permission-settings/&quot;&gt;윈도우10 SSH 접속시 PEM 파일 퍼미션 에러 해결방법&lt;/a&gt;  글을 참고하시면 됩니다.&lt;/p&gt;
&lt;p&gt;[참고]&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://serverfault.com/questions/935666/ssh-authentication-sequence-and-key-files-explain&quot;&gt;https://serverfault.com/questions/935666/ssh-authentication-sequence-and-key-files-explain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://superuser.com/questions/215504/permissions-on-private-key-in-ssh-folder&quot;&gt;https://superuser.com/questions/215504/permissions-on-private-key-in-ssh-folder&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Server</category>
      <author>평범한 개발자...</author>
      <guid isPermaLink="true">https://2dongdong.tistory.com/73</guid>
      <comments>https://2dongdong.tistory.com/73#entry73comment</comments>
      <pubDate>Mon, 13 Sep 2021 09:14:47 +0900</pubDate>
    </item>
    <item>
      <title>[Mysql/MariaDB] 밀리세컨드 저장이 안되는 문제</title>
      <link>https://2dongdong.tistory.com/71</link>
      <description>&lt;p&gt;밀리세컨드 저장이 안되는 근본적인 이유는 하나입니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;데이터베이스 버전 문제&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MySQL은 5.6.4부터,&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MariaDB는 5.3부터 &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;밀리세컨드/마이크로세컨드 단위를 지원합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;MariaDB의 경우, 호환성을 위해 10.1.2 버전부터 MySQL과 동일한 포맷 및 변수를 사용합니다.&lt;b&gt;&lt;br /&gt;&lt;a href=&quot;https://mariadb.com/kb/en/server-system-variables/#mysql56_temporal_format&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mariadb.com/kb/en/server-system-variables/#mysql56_temporal_format&lt;/a&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;그러나, 버전이 지원함에도 불구하고 Spring boot 설정 문제로 인하여 밀리세컨드 저장이 안되는 경우가 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 경우에는, DB에 직접 쿼리를 실행하면 밀리세컨드가 저장 되지만,&lt;/p&gt;
&lt;p&gt;JDBC를 통해서 저장하게되면 밀리세컨드가 저장되지 않는 아주 난감한 상황게 빠지게 되죠.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;저장이 안되는 이유는 크게 2가지 입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Dialect 설정 문제&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p&gt;잘못된 Dialect 설정으로 해당 기능을 사용하지 못하는 경우입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예를들어, 데이터베이스 버전이 MySQL 8.0인데,&lt;/p&gt;
&lt;p&gt;아래와 같이 MySQL5(5.5)로 설정하게되면 ORM에서 MySQL 5.5 버전을 기준으로 쿼리를 작성하게 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1616903279500&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;5.5버전에서는&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;마이크로세컨드&lt;/span&gt;를 지원하지 않으니 당연히 저장이 안되겠죠&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자세한 내용은 아래 글을 참고하시면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://2dongdong.tistory.com/66&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;[Spring boot] JPA Dialect(방언) 설정에 관하여&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Connector 문제&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p&gt;데이터베이스는 MariaDB인데 MySQL Connector 사용하는 경우입니다.&lt;/p&gt;
&lt;p&gt;(&lt;span style=&quot;color: #333333;&quot;&gt;MySQL을 사용하는데 MariaDB Connector를 사용하는 경우는 아마 없겠죠..?)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;MySQL과 MariaDB는 서로 호환이 되기 때문에 Connector를 크게 신경쓰지 않는 분들이 계실텐데요.&lt;/p&gt;
&lt;p&gt;실제로 동작시켜보면 MySQL Connector를 사용할 경우, &lt;span&gt;MariaDB 10.4.7을 MySQL 5.5.5로 감지하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lf0FJ/btq09kkZCQ4/e1wmXsh5gy7cR3mxG1kzNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lf0FJ/btq09kkZCQ4/e1wmXsh5gy7cR3mxG1kzNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lf0FJ/btq09kkZCQ4/e1wmXsh5gy7cR3mxG1kzNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flf0FJ%2Fbtq09kkZCQ4%2Fe1wmXsh5gy7cR3mxG1kzNk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;3. 결론&lt;/span&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p&gt;초급 혹은 신입개발자가 혼자서 초기 프로젝트를 구성하다보면,&lt;/p&gt;
&lt;p&gt;Spring boot 설정파일이나 pom 또는 gradle 코드를 구글링하여 그대로 쓰는 경우가 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;설정이나 라이브러리는 하나하나 공부해서 직접 설정하는것이 실력향상에 도움도되고&lt;/p&gt;
&lt;p&gt;오류가 발생했을때 원인을 찾는것이 훨씬 쉬워질 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[참고]&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://issues.alfresco.com/jira/browse/MNT-17613&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;issues.alfresco.com/jira/browse/MNT-17613&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://mariadb.com/kb/en/changes-improvements-in-mariadb-53/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mariadb.com/kb/en/changes-improvements-in-mariadb-53/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://mariadb.com/kb/en/microseconds-in-mariadb/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mariadb.com/kb/en/microseconds-in-mariadb/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Server/Database</category>
      <author>평범한 개발자...</author>
      <guid isPermaLink="true">https://2dongdong.tistory.com/71</guid>
      <comments>https://2dongdong.tistory.com/71#entry71comment</comments>
      <pubDate>Sun, 28 Mar 2021 13:11:29 +0900</pubDate>
    </item>
    <item>
      <title>2020년 회고</title>
      <link>https://2dongdong.tistory.com/70</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://2dongdong.tistory.com/14?category=730705&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2dongdong.tistory.com/14?category=730705&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1610267445801&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;2020년 목표&quot; data-og-description=&quot;훈련소 다녀오고 여행갔다가 한 주 보내니 벌써 1월의 절반이 지나갔네요. 2020년도 목표를&amp;nbsp;따로&amp;nbsp;다이어리에 적긴 했지만, 블로그를 제대로 운영해보고자 글을 남겨봅니다. 남들 처럼 이쁘게 꾸&quot; data-og-host=&quot;2dongdong.tistory.com&quot; data-og-source-url=&quot;https://2dongdong.tistory.com/14?category=730705&quot; data-og-url=&quot;https://2dongdong.tistory.com/14&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bwpka3/hyIS7Ilsf4/56L8mIDymirafjrkLomrl0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bqIAj5/hyISZDxonN/NVqKekS4BoAkgnSdTtYtjK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://2dongdong.tistory.com/14?category=730705&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://2dongdong.tistory.com/14?category=730705&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bwpka3/hyIS7Ilsf4/56L8mIDymirafjrkLomrl0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bqIAj5/hyISZDxonN/NVqKekS4BoAkgnSdTtYtjK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;2020년 목표&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;훈련소 다녀오고 여행갔다가 한 주 보내니 벌써 1월의 절반이 지나갔네요. 2020년도 목표를&amp;nbsp;따로&amp;nbsp;다이어리에 적긴 했지만, 블로그를 제대로 운영해보고자 글을 남겨봅니다. 남들 처럼 이쁘게 꾸&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;2dongdong.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 운동&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주 3회를 목표로 하였으며, 코로나 때문에 중간중간 끊기긴 했지만 지금 까지 꾸준히 진행 중이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;711&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/74Det/btqSZydqltb/67Puylgd3KJ7eTPjKOgep1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/74Det/btqSZydqltb/67Puylgd3KJ7eTPjKOgep1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/74Det/btqSZydqltb/67Puylgd3KJ7eTPjKOgep1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F74Det%2FbtqSZydqltb%2F67Puylgd3KJ7eTPjKOgep1%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;711&quot; data-ke-mobilestyle=&quot;widthContent&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;재작년 12월 훈련소를 다녀온 이후,&amp;nbsp;&lt;/p&gt;
&lt;p&gt;골격근량 3.7kg 증가, 체지방량 4.7kg 감소했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;되돌아보면 몸이 정말 많이 바뀌었다. 왜소했던게 큰 컴플랙스 였는데, 덩치가 많이 커져서 뿌듯하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 공부&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 영어회화는 완전한 실패로 끝났다. 처음 한, 두달 열심히 했지만 동기부여가 생기지 않아 깔끔하게 포기!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 책읽기는 정말 자기전 하루에 한 장 읽는 것을 목표로 해도, 실천하지 못했다. 자기전에 자꾸 유튜브를 보게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 유튜브를 삭제해야하나 싶다! 아으~~!&lt;/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;- 카메라 공부는 틈틈히 했다. 책을 사서 참고 해보기도 하고, 인터넷 글도 많이 읽었다. 2019년도에는 카메라 공부, 2020년도는 사진찍는법에 대해 많이 배웠다. 연말에는 영상찍는법을 벼락치기로 배워서 여자친구 언니 결혼식 영상을 찍고 편집하여 하나의 작품을 만들었다.&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; . Vue.js 입문책: 완독&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; . 스프링부트와 AWS로 혼자 구현하는 웹 서비스: 완독&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; . RealMySQL: 필요한 부분만 읽는 중. index, partitioning을 주로 읽은 듯&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; . 디자인패턴 책 3권: 1권으로는 이해가안가서 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;위 공부를 통해 얻은 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Vue.js로 만든 간단한 웹서비스 (Front 보단 Back에 더 집중..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;. gitlab+ aws S3 + CodeDeploy를 연동해서 테스트 자동화, 배포 자동화 시스템을 구현했다.&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;2. 테이블 만들 때, 쿼리를 작성할 때 인덱스부터 고민한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 대용량처리를 어떻게 하는지 구체적으로 알고 싶었는데, 가려운 부분이 해소됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;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;4. 처음부터 디자인패턴을 설계해서 코드 짜는 레벨은 아니지만, 코드를 짜다가 반복되는 부분이 생기거나, 나중에 기능이 추가되거나 유지보수를 고려해서 기존코드의 수정을 최소화 시키는 방법 또는 아에 독립적으로 분리시켜서 어떻게 패턴화 할지 많이 고민하게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 경험한 것 혹은 이룬 것&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 꾸준히 운동하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 공부하기 (꾸준히는 안함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 눈썹 문신 (진작 했어야함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 국내여행 (전주,공주,삼남길,청주,춘천,소요산)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 아울렛 쇼핑에 눈을 떴다. (다신 백화점 안간다...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 턱걸이 최대 10개 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 커플링을 맞췄다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 다양한 데이트 (사격, 스포츠몬스터, 출사, 비건데이트, 커플여행)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. 결혼식 영상 찍기+편집 (뜻깊은 선물을 줘서 기분이 좋다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. 카러플 레전드 티어 달성?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11. 후지필름 카메라를 사고 인생샷을 많이 찍은 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 시원하게! 플랙스하게 지른 것&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 후지필름 카메라: 2020년 한 해의 추억을 이 카메라로 담았다. 너무 잘 샀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 자브라 75T: QCY시리즈 쓰다가 큰맘먹고 지른 것. 정말 잘 쓰고있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 갤럭시S20: 말도안되는 스팩에 정말 잘 만든 핸드폰...&amp;nbsp; 많이 안팔려서 안타까울 뿐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 지오지아 롱패딩: 따뜻하고 옷 자체는 좋은데, 백화점에서 산게 가장 큰 실수. 너무 비싸게주고 샀다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 아울렛 가서 산것들: 정장, 코트, 바지, 셔츠.. 등등 눈돌아가서 막 산거같다. 합해서 100만원 나온듯.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 2070 Super + 32인치 144hz QHD 모니터: 게임하는데 몰입감이 한 층 더해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 마샬 스탠모어2 스피커: &lt;span style=&quot;color: #333333;&quot;&gt;음질 좋고 파워 빵빵한 스피커. &lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;직구로 싸게 샀다.&amp;nbsp; &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;span style=&quot;color: #333333;&quot;&gt;평소에&lt;span&gt;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 결론&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;메모장에 써야할 글과 주제를 수십개 적어놨는데, 줄지않고 늘어나기만한다. 마이너스 요소임..&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;2020년을 점수로 평가하자면 10점 만점에 7점!&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;2021년도를 완벽하게 보낼거라 생각하지 않는다. 딱 1점만 올려서 8점의 해를 보내보자.&lt;/p&gt;</description>
      <category>Life/일상</category>
      <author>평범한 개발자...</author>
      <guid isPermaLink="true">https://2dongdong.tistory.com/70</guid>
      <comments>https://2dongdong.tistory.com/70#entry70comment</comments>
      <pubDate>Sun, 10 Jan 2021 18:53:55 +0900</pubDate>
    </item>
    <item>
      <title>[Vue.js] - HTML5 WebSocket(웹 소켓) 소스코드 및 데모 사이트</title>
      <link>https://2dongdong.tistory.com/67</link>
      <description>&lt;p&gt;웹소켓 클라이언트가 필요해서 bootstrap+vuejs로 간단하게 만들어봤습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;jQuery에서 vuejs로 넘어오면서 익숙하지 않은게 힘들었는데, 조금씩 익숙해지니 개발하는데 훨씬 편리함을 느끼네요.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;웹소켓 또한 이벤트핸들러와 메소드로 간단히 만들 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;핵심 부분은 아래가 전부입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1605253082027&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ...
  //소켓 연결
  this.socket = new WebSocket(this.address)
  ...
  this.socket.onopen = 소켓 연결 이벤트 헨들러
  ...
  this.socket.onerror = 소켓 에러 이벤트 헨들러
  ...
  this.socket.onmessage = 데이터 수신 이벤트 헨들러
  ...
  this.socket.onclose = 소켓 해제 이벤트 헨들러
  ...
  // 소켓 데이터 전송
  this.socket.send(&quot;메시지&quot;) 
  ...
  // 소켓 종료
  this.socket.close()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래는 자바 스크립트 전체 소스코드 입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1605250709003&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  methods: {
    connect () {
      if (this.socket === undefined || this.socket.readyState === 3) {
        this.socket = new WebSocket(this.address)
        this.socket.onopen = () =&amp;gt; {
          this.logs.push({ type: 'INFO', msg: 'CONNECTED' })
          this.disabled = false
        }
        this.socket.onerror = () =&amp;gt; {
          this.logs.push({ type: 'ERROR', msg: 'ERROR:' })
        }
        this.socket.onmessage = ({ data }) =&amp;gt; {
          this.logs.push({ type: 'RECV', msg: 'RECV:' + data })
        }
        this.socket.onclose = (msg) =&amp;gt; {
          this.logs.push({ type: 'ERROR', msg: 'Closed (Code: ' + msg.code + ', Message: ' + msg.reason + ')' })
        }
      }
    },
    sendMessage () {
      if (this.selected === 'plain') {
        this.logs.push({ type: 'SENT', msg: 'SENT:' + this.message })
        this.socket.send(this.message)
      } else if (this.selected === 'json') {
        this.logs.push({ type: 'SENT', msg: 'SENT:' + JSON.stringify(this.json) })
        this.socket.send(JSON.stringify(this.json))
      }
    },
    disconnect () {
      if (this.socket.readyState === 1) {
        this.socket.close()
        this.logs.push({ type: 'INFO', msg: 'DISCONNECTED' })
        this.disabled = true
      }
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Vue를 포함한 모든 소스 코드는 깃허브에 있습니다.&lt;/p&gt;
&lt;p&gt;깃허브 주소:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/lehdqlsl/vue-websocket-client&quot;&gt;github.com/lehdqlsl/vue-websocket-client&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceqPCY/btqNoRO2w3w/kfxrwn5pDeM7k5mP56jXLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceqPCY/btqNoRO2w3w/kfxrwn5pDeM7k5mP56jXLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceqPCY/btqNoRO2w3w/kfxrwn5pDeM7k5mP56jXLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceqPCY%2FbtqNoRO2w3w%2Fkfxrwn5pDeM7k5mP56jXLK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;&lt;span style=&quot;color: #333333;&quot;&gt;실제 서버에 올려서 구동시킨 데모 화면입니다. 아래 URL에 누르면 해당 웹페이지로 접속 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;접속주소:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;http://vue-websocket.ga/&quot;&gt;vue-websocket.ga/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 Location 부분에 아래 에코서버에 연결하여 간단하게 테스트해 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://echo.websocket.org&quot;&gt;wss://echo.websocket.org&lt;/a&gt;&lt;/p&gt;</description>
      <category>Web/Vue.js</category>
      <author>평범한 개발자...</author>
      <guid isPermaLink="true">https://2dongdong.tistory.com/67</guid>
      <comments>https://2dongdong.tistory.com/67#entry67comment</comments>
      <pubDate>Fri, 13 Nov 2020 16:43:03 +0900</pubDate>
    </item>
  </channel>
</rss>