<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>세모난 야구공</title>
    <link>https://olt13mm.tistory.com/</link>
    <description>직접 재현하고 검증한 개발 기록입니다. 데이터베이스, 웹, 아키텍처에서 만난 문제의 원인과 해결 과정을 정리합니다.</description>
    <language>ko</language>
    <pubDate>Mon, 7 Sep 2026 13:24:59 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>세모난 야구공</managingEditor>
    <item>
      <title>문의</title>
      <link>https://olt13mm.tistory.com/pages/contact</link>
      <description>&lt;p&gt;블로그와 관련된 문의는 아래 방법으로 보내 주시기 바랍니다.&lt;/p&gt;
&lt;h2&gt;이메일&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;mailto:olt13mm@gmail.com&quot;&gt;olt13mm@gmail.com&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;가능한 한 확인하는 대로 답장드리지만, 상황에 따라 회신이 늦어질 수 있는 점 양해 부탁드립니다.&lt;/p&gt;
&lt;h2&gt;이런 내용을 보내 주세요&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;내용 오류 제보&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;글에 사실과 다른 내용이나 더 이상 유효하지 않은 정보가 있으면 알려 주시기 바랍니다. 어떤 글의 어느 부분인지와 함께 근거가 되는 자료를 보내 주시면 확인이 빠릅니다. 확인 후 본문을 수정하고 변경한 내용을 밝힙니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;질문&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;글에 나온 내용 중 이해가 어려운 부분이나 재현이 되지 않는 상황을 문의하실 수 있습니다. 사용 중인 버전과 실행 환경, 실제 오류 메시지를 함께 보내 주시면 답변에 도움이 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;인용 및 저작권&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;본문이나 직접 제작한 도식을 사용하실 때는 출처와 원문 링크를 표기해 주시기 바랍니다. 그 밖의 사용 범위는 문의해 주시면 회신드립니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;제휴 및 광고 문의&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;제안 내용과 조건을 구체적으로 적어 보내 주시기 바랍니다.&lt;/p&gt;
&lt;h2&gt;댓글과 방명록&lt;/h2&gt;
&lt;p&gt;각 글의 댓글이나 &lt;a href=&quot;/guestbook&quot;&gt;방명록&lt;/a&gt;을 이용하셔도 됩니다. 공개된 공간이므로 개인정보나 민감한 내용은 남기지 않도록 유의해 주시기 바랍니다.&lt;/p&gt;
&lt;h2&gt;개인정보 관련&lt;/h2&gt;
&lt;p&gt;개인정보 처리와 삭제 요청은 &lt;a href=&quot;/pages/privacy&quot;&gt;개인정보처리방침&lt;/a&gt;을 확인하신 뒤 같은 이메일로 문의해 주시기 바랍니다.&lt;/p&gt;</description>
      <author>세모난 야구공</author>
      <guid isPermaLink="true">https://olt13mm.tistory.com/pages/contact</guid>
      <pubDate>Thu, 3 Sep 2026 13:27:37 +0900</pubDate>
    </item>
    <item>
      <title>소개</title>
      <link>https://olt13mm.tistory.com/pages/about</link>
      <description>&lt;p&gt;세모난 야구공은 개발하면서 직접 부딪힌 문제와 그 해결 과정을 기록하는 블로그입니다.&lt;/p&gt;
&lt;h2&gt;이 블로그가 다루는 것&lt;/h2&gt;
&lt;p&gt;검색해서 나오는 설명을 그대로 옮기지 않고, 직접 재현하고 확인한 내용을 씁니다. 오류 메시지 하나를 해결하더라도 왜 그런 결과가 나왔는지, 어떤 조건에서 달라지는지를 함께 남깁니다.&lt;/p&gt;
&lt;p&gt;주로 다음 주제를 다룹니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;데이터베이스&lt;/strong&gt;: MySQL과 Redis에서 만난 트랜잭션, 인덱스, 문자셋, 데드락 문제&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JavaScript·React·웹&lt;/strong&gt;: 언어 동작 원리, 렌더링 전략, 인증과 쿠키, 웹 접근성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CS·아키텍처&lt;/strong&gt;: 캐시, 가비지 컬렉션, 메시지 큐, CQRS, 멀티 테넌시&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;개발 도구&lt;/strong&gt;: Git 복구와 worktree, Node.js 환경 구성, 포매터 팀 적용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;알고리즘·성능&lt;/strong&gt;: 풀이 과정과 반례, 복잡도, 성능 측정 방법&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 활용&lt;/strong&gt;: AI 도구를 업무에 적용하며 결과를 검증하는 절차&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;업무 자동화&lt;/strong&gt;: MCP와 외부 API 연동, 권한 설계&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;라이선스·개발 경험&lt;/strong&gt;: 오픈소스 라이선스 확인 기록과 비전공 개발자로서의 경험&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;글을 쓸 때 지키는 것&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;직접 확인한 것만 씁니다.&lt;/strong&gt; 실행해 보지 않은 내용은 단정하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;공식 문서로 재검증합니다.&lt;/strong&gt; 버전에 따라 달라지는 내용은 버전과 확인한 날짜를 함께 적습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;틀렸던 과정도 남깁니다.&lt;/strong&gt; 처음 시도한 방법이 왜 실패했는지가 결과만큼 중요할 때가 많습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이미지는 근거일 때만 넣습니다.&lt;/strong&gt; 실행 결과나 설정 화면처럼 설명을 확인시켜 주는 경우에만 사용하고, 장식용 이미지는 넣지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;글쓴이&lt;/h2&gt;
&lt;p&gt;비전공으로 개발을 시작해 부트캠프를 거쳐 현업에서 일하고 있습니다. 배운 것을 정리하다 보면 이해가 얕은 부분이 드러나서, 확인하고 다시 쓰는 과정을 반복하고 있습니다.&lt;/p&gt;
&lt;h2&gt;정정과 제보&lt;/h2&gt;
&lt;p&gt;내용에 오류가 있거나 더 정확한 자료를 알고 계시면 알려 주시기 바랍니다. 확인 후 본문을 수정하고 변경한 내용을 밝힙니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이메일: &lt;a href=&quot;mailto:olt13mm@gmail.com&quot;&gt;olt13mm@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;각 글의 댓글&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;인용과 저작권&lt;/h2&gt;
&lt;p&gt;본문과 직접 제작한 도식의 저작권은 글쓴이에게 있습니다. 인용하실 때는 출처와 원문 링크를 함께 표기해 주시기 바랍니다. 외부 자료를 인용한 부분은 본문에 출처를 밝혔습니다.&lt;/p&gt;</description>
      <author>세모난 야구공</author>
      <guid isPermaLink="true">https://olt13mm.tistory.com/pages/about</guid>
      <pubDate>Thu, 3 Sep 2026 13:26:56 +0900</pubDate>
    </item>
    <item>
      <title>개인정보처리방침</title>
      <link>https://olt13mm.tistory.com/pages/privacy</link>
      <description>&lt;p&gt;이 개인정보처리방침은 세모난 야구공(&lt;a href=&quot;https://olt13mm.tistory.com&quot;&gt;https://olt13mm.tistory.com&lt;/a&gt;, 이하 &amp;quot;본 블로그&amp;quot;)에 적용됩니다. 본 블로그는 카카오의 티스토리 서비스를 이용해 운영합니다.&lt;/p&gt;
&lt;h2&gt;수집하는 정보&lt;/h2&gt;
&lt;p&gt;본 블로그는 방문자에게 회원 가입을 요구하지 않으며 이름, 연락처 같은 개인정보를 직접 입력받지 않습니다. 다만 아래 정보가 자동으로 수집되거나 서비스 이용 과정에서 남을 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;접속 기록: 방문 일시, 방문한 페이지, 유입 경로, 브라우저와 기기 정보&lt;/li&gt;
&lt;li&gt;쿠키: 광고 게재와 방문 분석을 위한 식별 값&lt;/li&gt;
&lt;li&gt;댓글과 방명록: 작성자가 직접 입력한 닉네임, 비밀번호, 작성 내용&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;댓글과 방명록에 개인을 식별할 수 있는 정보를 남기지 않도록 유의해 주시기 바랍니다.&lt;/p&gt;
&lt;h2&gt;쿠키 사용&lt;/h2&gt;
&lt;p&gt;쿠키는 웹사이트가 방문자의 브라우저에 저장하는 작은 텍스트 파일입니다. 본 블로그는 광고 게재와 방문 통계 확인을 위해 쿠키를 사용합니다.&lt;/p&gt;
&lt;p&gt;브라우저 설정에서 쿠키 저장을 거부하거나 삭제할 수 있습니다. 다만 쿠키를 차단하면 일부 기능이 정상적으로 동작하지 않을 수 있습니다.&lt;/p&gt;
&lt;h2&gt;제3자 광고&lt;/h2&gt;
&lt;p&gt;본 블로그는 Google에서 제공하는 애드센스 광고를 게재합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google을 포함한 제3자 광고 사업자는 쿠키를 사용해 이용자의 이전 방문 기록을 바탕으로 광고를 게재할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Google이 광고 쿠키를 사용함으로써 Google과 그 파트너는 본 블로그나 다른 사이트 방문 기록을 바탕으로 광고를 제공할 수 있습니다.&lt;/li&gt;
&lt;li&gt;이용자는 &lt;a href=&quot;https://adssettings.google.com&quot;&gt;Google 광고 설정&lt;/a&gt;에서 개인 맞춤 광고를 사용 중지할 수 있습니다.&lt;/li&gt;
&lt;li&gt;제3자 광고 사업자의 쿠키 사용을 사용 중지하려면 &lt;a href=&quot;https://www.aboutads.info/choices/&quot;&gt;aboutads.info&lt;/a&gt;를 참고하시기 바랍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;광고 사업자가 수집하는 정보의 처리 방식은 해당 사업자의 정책을 따릅니다. 자세한 내용은 &lt;a href=&quot;https://policies.google.com/privacy&quot;&gt;Google 개인정보처리방침&lt;/a&gt;에서 확인할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;방문 분석&lt;/h2&gt;
&lt;p&gt;본 블로그는 방문 현황을 파악하기 위해 Google Analytics를 사용합니다. Google Analytics는 쿠키를 사용해 방문 기록을 수집하며, 이 정보는 개인을 특정하지 않는 형태로 집계됩니다.&lt;/p&gt;
&lt;p&gt;Google Analytics의 데이터 수집을 원하지 않는 경우 &lt;a href=&quot;https://tools.google.com/dlpage/gaoptout&quot;&gt;Google Analytics 차단 브라우저 부가 기능&lt;/a&gt;을 설치할 수 있습니다.&lt;/p&gt;
&lt;p&gt;또한 티스토리 서비스 자체의 방문 통계 기능이 함께 동작합니다.&lt;/p&gt;
&lt;h2&gt;정보의 보관과 이용&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;자동으로 수집된 접속 기록과 통계 정보는 블로그 운영과 콘텐츠 개선 목적으로만 사용합니다.&lt;/li&gt;
&lt;li&gt;본 블로그 운영자는 방문자의 개인정보를 별도로 저장하거나 제3자에게 판매하지 않습니다.&lt;/li&gt;
&lt;li&gt;댓글과 방명록은 작성자가 삭제하거나 운영자에게 삭제를 요청할 때까지 보관됩니다.&lt;/li&gt;
&lt;li&gt;티스토리 플랫폼이 수집하고 보관하는 정보는 &lt;a href=&quot;https://www.kakao.com/policy/privacy&quot;&gt;카카오 개인정보처리방침&lt;/a&gt;을 따릅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;외부 링크&lt;/h2&gt;
&lt;p&gt;본 블로그의 글에는 공식 문서나 참고 자료로 연결되는 외부 링크가 포함됩니다. 링크된 사이트의 개인정보 처리에 대해서는 본 블로그가 책임지지 않으며, 해당 사이트의 정책을 확인하시기 바랍니다.&lt;/p&gt;
&lt;h2&gt;아동의 개인정보&lt;/h2&gt;
&lt;p&gt;본 블로그는 만 14세 미만 아동을 대상으로 하지 않으며 아동의 개인정보를 의도적으로 수집하지 않습니다.&lt;/p&gt;
&lt;h2&gt;문의&lt;/h2&gt;
&lt;p&gt;개인정보 처리에 관한 문의나 삭제 요청은 아래로 연락해 주시기 바랍니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이메일: &lt;a href=&quot;mailto:olt13mm@gmail.com&quot;&gt;olt13mm@gmail.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;변경 이력&lt;/h2&gt;
&lt;p&gt;본 방침의 내용이 변경되는 경우 이 페이지에 반영하고 시행일을 갱신합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;시행일: 2026년 9월 3일&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>세모난 야구공</author>
      <guid isPermaLink="true">https://olt13mm.tistory.com/pages/privacy</guid>
      <pubDate>Thu, 3 Sep 2026 13:25:57 +0900</pubDate>
    </item>
    <item>
      <title>GEO: AI가 인용하기 좋은 문서의 조건</title>
      <link>https://olt13mm.tistory.com/2208</link>
      <description>&lt;p&gt;검색 환경이 조금씩 바뀌고 있습니다.&lt;/p&gt;
&lt;p&gt;검색 결과의 링크를 하나씩 여는 대신 AI가 여러 자료를 묶어 만든 답부터 확인하는 사람이 늘었습니다.&lt;/p&gt;
&lt;p&gt;예를 들면 이런 질문입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;“민감성 피부에 좋은 선크림 추천해줘”&lt;/li&gt;
&lt;li&gt;“RAG 아키텍처 베스트 프랙티스 알려줘”&lt;/li&gt;
&lt;li&gt;“Next.js SEO 잘하는 방법 알려줘”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이런 질문에 대해 AI 검색 시스템은 여러 문서를 참고해 하나의 답변을 만듭니다. 다만 여기서 조심해야 할 점이 있습니다. 모든 AI 검색 시스템이 같은 방식으로 동작하는 것은 아닙니다. Google AI Overview, ChatGPT, Perplexity, Gemini는 각각 검색·출처 선택·답변 생성 방식이 다를 수 있습니다.&lt;/p&gt;
&lt;p&gt;따라서 “AI가 잘 인용하는 문서”라는 표현부터 두 가지 근거로 나눠 봐야 합니다.&lt;/p&gt;
&lt;p&gt;하나는 &lt;strong&gt;Google이 공식적으로 권장하는 AI 검색 최적화 방향&lt;/strong&gt;입니다. 다른 하나는 &lt;strong&gt;GEO 관련 연구나 업계에서 관찰한 AI 검색 엔진의 인용 경향&lt;/strong&gt;입니다. 이 둘을 섞어 말하면 그럴듯해 보이지만, 실제로는 과장된 결론으로 이어지기 쉽습니다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 실제로 봐야 할 부분과 조심해야 할 부분을 정리해보겠습니다. Google은 생성형 AI 검색 기능을 위한 별도의 새로운 요령보다 기존 검색 품질 원칙과 사람 중심 콘텐츠를 계속 강조하고 있습니다. (&lt;a href=&quot;https://developers.google.com/search/docs/fundamentals/ai-optimization-guide&quot;&gt;Google for Developers&lt;/a&gt;)&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGx7v5/dJMcafA8Y5H/kH6UkNRMKgFBqT25EnzNkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGx7v5/dJMcafA8Y5H/kH6UkNRMKgFBqT25EnzNkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGx7v5/dJMcafA8Y5H/kH6UkNRMKgFBqT25EnzNkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGx7v5%2FdJMcafA8Y5H%2FkH6UkNRMKgFBqT25EnzNkK%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; alt=&quot;접근 가능한 원문과 명확한 근거가 검색과 검증 및 인용으로 이어지는 구조&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;520&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;먼저 구분해야 할 것&lt;/h2&gt;
&lt;p&gt;GEO라는 말이 자주 쓰이면서 여러 최적화 방법이 함께 이야기되고 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;문단을 아주 짧게 쪼개야 한다는 주장이 있습니다.&lt;/li&gt;
&lt;li&gt;AI가 읽기 좋게 모든 문단을 독립적으로 써야 한다는 주장이 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;llms.txt&lt;/code&gt;를 만들어야 한다는 주장이 있습니다.&lt;/li&gt;
&lt;li&gt;AI 검색용 schema.org 마크업을 따로 추가해야 한다는 주장이 있습니다.&lt;/li&gt;
&lt;li&gt;키워드 변형 페이지를 많이 만들어야 한다는 주장이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;하지만 이 중 일부는 Google 공식 가이드와 거리가 있습니다.&lt;/p&gt;
&lt;p&gt;Google의 입장은 비교적 단순합니다. AI 검색을 위해 완전히 새로운 방식으로 사이트를 다시 만들 필요는 없습니다. 검색 엔진이 접근할 수 있고, 사용자가 만족할 만한 고유한 콘텐츠를 제공하는 것이 여전히 중요합니다. (&lt;a href=&quot;https://developers.google.com/search/docs/fundamentals/ai-optimization-guide&quot;&gt;Google for Developers&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;즉, AI 검색 최적화는 기존 SEO와 완전히 다른 기술이라기보다, 기존 원칙이 AI 검색 환경에서도 이어지는 것으로 보는 편이 더 정확합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;오해 1. 문서를 무조건 잘게 쪼개야 한다&lt;/h2&gt;
&lt;p&gt;AI가 문서를 chunk 단위로 처리한다는 이유로, 문단을 아주 짧게 나누고 각 문단을 독립적인 설명처럼 써야 한다는 이야기가 있습니다.&lt;/p&gt;
&lt;p&gt;하지만 Google 검색 기준에서는 콘텐츠를 AI를 위해 인위적으로 잘게 나눌 필요는 없습니다. Google은 한 페이지 안에 여러 주제가 있어도 문맥을 이해할 수 있으며, 사용자의 질문과 관련된 부분을 찾아낼 수 있다고 설명합니다. (&lt;a href=&quot;https://developers.google.com/search/docs/fundamentals/ai-optimization-guide&quot;&gt;Google for Developers&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;다만 여기서 한 가지는 구분해야 합니다.&lt;/p&gt;
&lt;p&gt;Google 검색 시스템에서 “문서를 억지로 쪼갤 필요가 없다”는 말과, 일반적인 RAG 시스템에서 “chunking이 중요하지 않다”는 말은 다릅니다.&lt;/p&gt;
&lt;p&gt;RAG 시스템을 직접 설계할 때는 chunk 크기, overlap, metadata, embedding 전략이 중요합니다. 하지만 블로그 글이나 웹 문서를 작성할 때 AI 검색을 의식해서 문장을 부자연스럽게 쪼갤 필요는 없습니다.&lt;/p&gt;
&lt;p&gt;확인할 기준은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;사람이 읽기에 자연스럽고, 문맥이 잘 이어지며, 필요한 정보를 찾기 쉬운 구조면 충분합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;오해 2. AI 검색용 schema.org를 따로 만들어야 한다&lt;/h2&gt;
&lt;p&gt;구조화 데이터가 AI 검색 노출에 결정적이라는 주장도 자주 보입니다. 이 말은 절반만 맞습니다.&lt;/p&gt;
&lt;p&gt;schema.org는 웹페이지의 정보를 검색 엔진과 여러 애플리케이션이 이해하기 쉽게 표현하기 위한 공통 vocabulary입니다. JSON-LD, Microdata, RDFa 같은 방식으로 사용할 수 있습니다. (&lt;a href=&quot;https://schema.org/&quot;&gt;Schema.org&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;따라서 구조화 데이터 자체는 여전히 의미가 있습니다. 제품, 리뷰, FAQ, 조직 정보, 글 작성자, breadcrumb 등을 명확히 표현하는 데 도움을 줄 수 있습니다.&lt;/p&gt;
&lt;p&gt;하지만 Google은 생성형 AI 검색을 위해 특별한 schema.org 마크업을 추가해야 한다고 말하지 않습니다. 즉, “AI 전용 schema”가 따로 필요한 것은 아닙니다. (&lt;a href=&quot;https://developers.google.com/search/docs/fundamentals/ai-optimization-guide&quot;&gt;Google for Developers&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;정리하면 이렇습니다.&lt;/p&gt;
&lt;p&gt;구조화 데이터는 기존 SEO 관점에서 유효합니다. 검색 엔진이 페이지의 의미를 이해하는 데 도움을 줄 수 있습니다. 하지만 AI 검색 노출만을 목적으로 새로운 schema를 과하게 추가하는 것은 우선순위가 높지 않습니다.&lt;/p&gt;
&lt;p&gt;이미 필요한 구조화 데이터를 잘 쓰고 있다면 유지하면 됩니다. 아직 없다면 Product, Article, Organization, BreadcrumbList처럼 실제 페이지 성격에 맞는 것부터 적용하는 편이 좋습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;오해 3. llms.txt만 만들면 AI가 더 잘 인용한다&lt;/h2&gt;
&lt;p&gt;llms.txt를 만들면 AI가 사이트를 더 잘 읽고 인용할 것이라는 이야기도 있습니다.&lt;/p&gt;
&lt;p&gt;하지만 Google 기준에서는 llms.txt가 필수 요소는 아닙니다. AI 검색 노출을 위해 별도로 llms.txt를 만들어야 한다고 보기는 어렵습니다. (&lt;a href=&quot;https://developers.google.com/search/docs/fundamentals/ai-optimization-guide&quot;&gt;Google for Developers&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt;보다 먼저 확인할 것은 검색 엔진이 페이지에 접근할 수 있는지입니다. &lt;code&gt;robots.txt&lt;/code&gt;로 막혀 있거나 중요한 내용이 최종 HTML에 나타나지 않고 &lt;code&gt;noindex&lt;/code&gt;가 설정돼 있다면 일반 검색부터 문제가 됩니다. &lt;code&gt;nosnippet&lt;/code&gt; 설정, 페이지 속도, 본문을 가리는 광고와 화면 요소도 기존 검색과 사용자 경험의 관점에서 확인해야 합니다. 특별한 파일 하나가 이 기본 조건을 대신하지는 않습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;문서의 근거와 판단 기준을 드러냅니다&lt;/h2&gt;
&lt;h3&gt;1. 어디서나 볼 수 있는 내용이 아니어야 합니다&lt;/h3&gt;
&lt;p&gt;Google이 반복해서 강조하는 방향은 사람에게 도움이 되는 고유한 콘텐츠입니다. 단순히 이미 있는 정보를 다시 요약한 글보다는, 작성자의 경험과 관점이 들어간 글이 더 가치 있습니다. (&lt;a href=&quot;https://developers.google.com/search/docs/fundamentals/ai-optimization-guide&quot;&gt;Google for Developers&lt;/a&gt;)&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;Next.js에서 SEO를 잘하려면 메타 태그를 설정하고 sitemap을 만들어야 합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;틀린 말은 아니지만, 어디서나 볼 수 있는 내용입니다.&lt;/p&gt;
&lt;p&gt;직접 문제를 해결한 글이라면 사용한 데이터와 측정 조건, 원인으로 판단한 근거, 변경 전후 결과를 함께 적을 수 있습니다. 실제 측정값이 없다면 숫자나 성과를 만들어 넣지 말고 확인하지 못한 범위를 밝혀야 합니다. 설명한 주제뿐 아니라 작성자가 직접 확인한 내용과 외부 자료에서 가져온 내용을 구분하는 것이 중요합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;2. 외부에서 언급되는 것도 중요합니다&lt;/h3&gt;
&lt;p&gt;GEO 관련 arXiv 논문에서는 ChatGPT, Perplexity, Gemini 같은 AI 검색 엔진이 브랜드가 직접 작성한 콘텐츠보다 제3자 출처, 즉 earned media를 더 자주 활용하는 경향이 관찰되었다고 설명합니다. (&lt;a href=&quot;https://arxiv.org/abs/2509.08919&quot;&gt;arXiv&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;여기서 주의할 점은 있습니다.&lt;/p&gt;
&lt;p&gt;이 결과를 모든 AI 검색 시스템의 절대적인 규칙처럼 받아들이면 안 됩니다. 특정 실험 환경, 특정 시점, 특정 검색 엔진을 대상으로 한 연구 결과이기 때문입니다.&lt;/p&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;업계 리포트 인용&lt;/li&gt;
&lt;li&gt;신뢰도 있는 리뷰 플랫폼 관리&lt;/li&gt;
&lt;li&gt;고객 사례 공개&lt;/li&gt;
&lt;li&gt;전문가 기고&lt;/li&gt;
&lt;li&gt;비교 콘텐츠에서의 언급 확보&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI 검색에서 “내가 나를 좋다고 말하는 것”보다 “다른 신뢰할 만한 곳에서 나를 언급하는 것”이 더 강한 신호가 될 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;3. 비교와 판단 기준이 있어야 합니다&lt;/h3&gt;
&lt;p&gt;AI 검색 결과에 활용되기 쉬운 문서는 단순한 정의만 나열하지 않습니다. 사용자가 판단할 수 있는 기준을 제공합니다.&lt;/p&gt;
&lt;p&gt;예를 들어 “SSR과 CSR의 차이”를 설명한다면 단순히 개념만 쓰는 것보다 아래처럼 판단 기준을 함께 제시하는 편이 좋습니다.&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;th&gt;적합한 경우&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;SSR&lt;/td&gt;
&lt;td&gt;초기 HTML 제공에 유리합니다&lt;/td&gt;
&lt;td&gt;서버 비용이 증가할 수 있습니다&lt;/td&gt;
&lt;td&gt;SEO가 중요한 페이지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSR&lt;/td&gt;
&lt;td&gt;인터랙션 구현이 자유롭습니다&lt;/td&gt;
&lt;td&gt;초기 로딩과 SEO에 불리할 수 있습니다&lt;/td&gt;
&lt;td&gt;로그인 후 대시보드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSG&lt;/td&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;p&gt;이런 구조는 사람이 읽기에도 좋고, AI가 답변을 만들 때도 활용하기 쉽습니다.&lt;/p&gt;
&lt;p&gt;다만 “AI가 표를 좋아한다”고 단정할 필요는 없습니다. 더 정확히 말하면, 비교표·선택 기준·트레이드오프 정리는 정보의 의미를 분명하게 만들어줍니다. 그래서 검색 시스템이나 AI 답변 생성 과정에서 재사용되기 좋은 형태가 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;4. 결론이 분명해야 합니다&lt;/h3&gt;
&lt;p&gt;좋은 문서는 정보를 많이 담는 것에서 끝나지 않습니다. 읽고 나서 무엇을 선택해야 하는지 알 수 있어야 합니다.&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;Prisma와 Drizzle은 모두 TypeScript 환경에서 사용할 수 있는 ORM입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&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;빠른 개발과 안정적인 마이그레이션 관리가 중요하다면 Prisma가 적합합니다. SQL 제어권과 가벼운 런타임을 더 중요하게 본다면 Drizzle이 더 나은 선택이 될 수 있습니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;AI 검색은 사용자의 질문에 대한 답을 구성해야 합니다. 이때 단순 정보보다 판단 기준, 조건, 예외, 한계가 있는 문서가 더 좋은 재료가 될 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Google과 다른 AI 검색은 다르게 봐야 합니다&lt;/h2&gt;
&lt;p&gt;Google AI Overview와 ChatGPT, Perplexity, Gemini의 검색형 답변을 하나의 기준으로 평가해서는 안 됩니다.&lt;/p&gt;
&lt;p&gt;Google은 기존 검색 시스템과 강하게 연결되어 있습니다. 반면 ChatGPT나 Perplexity는 질문 시점의 웹 검색, 자체 retrieval, 선택된 출처 기반 답변 생성 방식이 더 두드러질 수 있습니다.&lt;/p&gt;
&lt;p&gt;GEO 논문에서도 AI 검색 서비스마다 출처 다양성, 최신성, 언어별 안정성, 질문 표현에 대한 민감도가 다르게 나타난다고 설명합니다. (&lt;a href=&quot;https://arxiv.org/abs/2509.08919&quot;&gt;arXiv&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;따라서 “AI 검색에 효과가 있다”는 주장을 읽을 때에는 어느 서비스에서 시험했는지, 실험 결과인지 업계 경험담인지, 공식 문서인지 작성자의 해석인지 구분해야 합니다. 이 조건이 빠진 결과를 다른 검색 서비스에도 그대로 적용하면 효과를 과장하기 쉽습니다.&lt;/p&gt;
&lt;p&gt;이 글에도 한계가 있습니다. 동일한 문서를 여러 AI 검색 서비스에 등록해 인용 전후를 측정한 자체 실험 결과는 없습니다. 아래 내용은 Google 공식 안내와 공개 연구를 구분해 정리한 것으로, 특정 형식이 실제 인용률을 높인다는 보장은 아닙니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;이 글에서 확인할 수 있는 범위&lt;/h2&gt;
&lt;p&gt;Google의 공식 안내에서 확인되는 방향은 기존 검색 최적화의 기본을 유지하는 것입니다. 검색 엔진이 본문에 접근할 수 있게 하고, 사람에게 유용한 원문을 제공하며, 구조화 데이터는 실제 페이지 내용과 일치하게 사용해야 합니다. 생성형 검색만을 위한 특별한 스키마나 &lt;code&gt;llms.txt&lt;/code&gt;가 인용을 보장한다는 근거는 확인되지 않습니다.&lt;/p&gt;
&lt;p&gt;공개 연구는 제3자 언급이나 판단 기준이 있는 문서가 활용될 가능성을 살펴볼 단서를 제공하지만, 서비스와 실험 조건이 달라 결과를 일반화할 수 없습니다. 이 글 역시 동일한 문서를 여러 서비스에서 시험해 인용률을 측정하지 않았습니다. 따라서 특정 형식을 성공 공식으로 제시하기보다 공식 안내, 연구 결과, 작성자의 해석을 구분해 읽는 기준을 제공하는 데 범위를 둡니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;참고자료&lt;/h2&gt;
&lt;h3&gt;공식 문서&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/search/docs/fundamentals/ai-optimization-guide&quot;&gt;Google Search Central, &lt;em&gt;Guide to optimizing for generative AI features on Google Search&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://schema.org/&quot;&gt;schema.org 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;연구 자료&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2509.08919&quot;&gt;Mahe Chen, Xiaoxuan Wang, Kaiwen Chen, Nick Koudas, &lt;em&gt;Generative Engine Optimization: How to Dominate AI Search&lt;/em&gt;, arXiv&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고 아티클&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.digidop.com/blog/structured-data-secret-weapon-seo&quot;&gt;Digidop, &lt;em&gt;Structured data: SEO and GEO optimization for AI&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AI 활용</category>
      <category>AI Overview 인용</category>
      <category>ai 검색 최적화</category>
      <category>GEO</category>
      <category>llms.txt</category>
      <author>세모난 야구공</author>
      <guid isPermaLink="true">https://olt13mm.tistory.com/2208</guid>
      <comments>https://olt13mm.tistory.com/2208#entry2208comment</comments>
      <pubDate>Thu, 28 May 2026 00:30:41 +0900</pubDate>
    </item>
    <item>
      <title>AI 시대 개발자의 검증 가능한 학습 루프</title>
      <link>https://olt13mm.tistory.com/2205</link>
      <description>&lt;p&gt;AI는 모르는 개념을 요약하고 예제 코드를 만드는 시간을 크게 줄여 줍니다. 그러나 답을 빨리 얻었다고 해서 그 내용을 이해하거나 실무에서 재현할 수 있는 것은 아닙니다. 그럴듯하지만 틀린 설명을 그대로 기억하거나, 동작 원리를 모른 채 생성된 코드를 붙여 넣는다면 학습 속도만큼 오류가 쌓이는 속도도 빨라집니다.&lt;/p&gt;
&lt;p&gt;이 글에서는 개발자가 AI를 활용하면서도 결과를 스스로 검증할 수 있도록 &lt;strong&gt;흡수(Absorb) → 적용(Apply) → 검증(Verify) → 공유(Share)&lt;/strong&gt;의 네 단계로 학습 과정을 구성합니다. 이 명칭과 순서는 특정 학자의 이론을 그대로 옮긴 것이 아니라, 경험을 통한 학습과 짧은 피드백 주기라는 일반 원칙을 개발 작업에 맞게 재구성한 실무용 모델입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;675&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8knhZ/dJMcablhs7S/fh4PKDJHtDGEYEdfKzxJM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8knhZ/dJMcablhs7S/fh4PKDJHtDGEYEdfKzxJM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8knhZ/dJMcablhs7S/fh4PKDJHtDGEYEdfKzxJM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8knhZ%2FdJMcablhs7S%2Ffh4PKDJHtDGEYEdfKzxJM1%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; alt=&quot;흡수·적용·검증·공유가 순환하고 검증 단계에서 공식 문서와 테스트를 확인하는 학습 루프&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;675&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;675&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;읽은 내용을 직접 확인할 수 있어야 합니다&lt;/h2&gt;
&lt;p&gt;학습의 완료 기준을 “문서를 읽었습니다” 또는 “AI의 설명을 이해했습니다”로 두면 실제 능력을 확인하기 어렵습니다. 다음 세 가지를 만족하면 학습 결과를 좀 더 객관적으로 확인할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자신의 말로 핵심 개념과 선택 이유를 설명할 수 있습니다.&lt;/li&gt;
&lt;li&gt;새로운 입력이나 조건에서도 같은 결과를 재현할 수 있습니다.&lt;/li&gt;
&lt;li&gt;공식 문서, 실행 결과, 테스트 또는 반례로 핵심 주장을 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;빠른 학습은 이 기준을 생략하는 것이 아니라, 확인 가능한 작은 단위로 범위를 줄여 네 단계를 자주 반복하는 과정입니다. 한 번에 프레임워크 전체를 이해하려고 하기보다 “폼 데이터를 서버에서 검증하고 오류를 표시합니다”처럼 관찰 가능한 목표 하나를 선택하는 편이 좋습니다.&lt;/p&gt;
&lt;h2&gt;1단계: 흡수 범위를 질문 하나로 제한합니다&lt;/h2&gt;
&lt;p&gt;흡수 단계에서는 자료를 많이 모으기보다 이번 루프에서 답할 질문을 정합니다. 좋은 질문은 결과를 확인할 수 있을 만큼 구체적입니다.&lt;/p&gt;
&lt;p&gt;나쁜 목표는 “캐시를 공부합니다”처럼 범위가 넓습니다. 좋은 목표는 “동일한 GET 요청이 반복될 때 브라우저 캐시가 응답을 재사용하는 조건을 확인합니다”처럼 대상과 조건이 드러납니다.&lt;/p&gt;
&lt;p&gt;자료를 찾을 때에는 다음 순서를 권장합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;먼저 공식 문서에서 용어의 정의와 현재 지원 범위를 확인합니다.&lt;/li&gt;
&lt;li&gt;공식 예제에서 가장 작은 실행 단위를 찾습니다.&lt;/li&gt;
&lt;li&gt;이해가 어려운 부분만 AI에게 다른 표현이나 비교 예시로 설명해 달라고 요청합니다.&lt;/li&gt;
&lt;li&gt;AI가 제시한 옵션명, 버전, 기본값은 다시 공식 문서에서 확인합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 단계의 시간은 20~30분으로 제한하는 편이 좋습니다. 시간이 끝났는데도 질문이 너무 크다면 자료를 더 찾기보다 질문을 더 작게 나눕니다.&lt;/p&gt;
&lt;h2&gt;2단계: 설명을 작은 산출물로 바꿉니다&lt;/h2&gt;
&lt;p&gt;적용 단계에서는 읽은 내용을 실행 가능한 결과로 바꿉니다. 결과물은 완성된 서비스일 필요가 없습니다. 재현 가능한 코드, 테스트 하나, 요청과 응답 기록, 작은 다이어그램이면 충분합니다.&lt;/p&gt;
&lt;p&gt;예를 들어 새로운 API의 오류 처리를 학습한다면 다음 조건을 가진 최소 예제를 만듭니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;성공 입력과 실패 입력을 각각 하나씩 준비합니다.&lt;/li&gt;
&lt;li&gt;예상 상태 코드와 실제 상태 코드를 기록합니다.&lt;/li&gt;
&lt;li&gt;오류가 발생한 위치를 확인할 로그를 추가합니다.&lt;/li&gt;
&lt;li&gt;실행 명령과 필요한 버전을 &lt;code&gt;README&lt;/code&gt;에 남깁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI가 코드를 만들었다면 바로 실행하기 전에 각 줄이 필요한 이유를 주석 없이 설명해 봅니다. 설명할 수 없는 줄은 삭제했을 때 어떤 변화가 생기는지 확인합니다. 이렇게 하면 동작하는 코드와 이해한 코드를 구분할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;3단계: 공식 자료·테스트·반례로 검증합니다&lt;/h2&gt;
&lt;p&gt;검증은 이 학습 루프의 중심입니다. 적용 결과가 한 번 동작했다는 사실만으로 일반적인 결론을 내리면 안 됩니다. 다음 세 방향에서 결과를 확인합니다.&lt;/p&gt;
&lt;h3&gt;공식 자료와 일치하는지 확인합니다&lt;/h3&gt;
&lt;p&gt;메서드 이름, 지원 버전, 기본 동작, 더 이상 권장되지 않는 기능을 공식 문서에서 확인합니다. 블로그와 AI 답변은 탐색에 유용하지만 최종 근거로 삼기에는 작성 시점과 환경이 불분명할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;자동화된 테스트나 반복 실행으로 확인합니다&lt;/h3&gt;
&lt;p&gt;같은 절차를 다시 실행했을 때 같은 결과가 나오는지 확인합니다. 가능하다면 정상 입력, 경계 입력, 실패 입력을 테스트로 남깁니다. 테스트가 어렵다면 터미널 명령과 실제 출력이라도 기록합니다.&lt;/p&gt;
&lt;h3&gt;반례를 의도적으로 만듭니다&lt;/h3&gt;
&lt;p&gt;“이 설명이 틀렸다면 어떤 입력에서 드러날까?”라고 질문합니다. 네트워크가 끊긴 경우, 값이 비어 있는 경우, 권한이 없는 경우, 버전이 다른 경우를 넣어 봅니다. 반례를 통과하지 못했다면 실패가 아니라 적용 범위를 발견한 것입니다.&lt;/p&gt;
&lt;p&gt;검증 결과는 다음 세 상태 중 하나로 표시합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;확인됨&lt;/strong&gt;은 공식 자료와 실행 결과가 일치하는 상태입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;조건부 확인&lt;/strong&gt;은 특정 버전이나 조건에서만 재현된 상태입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;미확인&lt;/strong&gt;은 출처나 재현 절차가 부족해 결론을 보류한 상태입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4단계: 다른 사람이 재현할 수 있게 공유합니다&lt;/h2&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;사용한 버전, 환경, 공식 자료 링크를 적습니다.&lt;/li&gt;
&lt;li&gt;최소 예제와 실행 명령을 적습니다.&lt;/li&gt;
&lt;li&gt;예상과 달랐던 결과 및 수정 과정을 적습니다.&lt;/li&gt;
&lt;li&gt;아직 확인하지 못한 질문을 분리해서 적습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;오류까지 남기면 기록의 가치가 높아집니다. 성공한 최종 코드만으로는 어떤 오해를 수정했는지 알 수 없기 때문입니다.&lt;/p&gt;
&lt;h2&gt;90분 동안 한 번의 루프를 실행하는 방법입니다&lt;/h2&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;0~20분&lt;/td&gt;
&lt;td&gt;흡수&lt;/td&gt;
&lt;td&gt;런타임의 공식 환경 변수 문서와 질문 한 개를 정리합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20~50분&lt;/td&gt;
&lt;td&gt;적용&lt;/td&gt;
&lt;td&gt;필수 변수를 읽고 누락 시 종료하는 최소 예제를 만듭니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50~75분&lt;/td&gt;
&lt;td&gt;검증&lt;/td&gt;
&lt;td&gt;정상·누락·빈 문자열 입력을 실행하고 결과를 기록합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;75~90분&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;p&gt;90분 안에 끝나지 않는다면 학습 능력이 부족한 것이 아니라 범위가 큰 것일 수 있습니다. “여러 환경을 위한 설정 체계를 만듭니다”를 “필수 변수 하나의 누락을 감지합니다”로 줄이면 다음 루프를 시작할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;제가 남기는 학습 기록의 항목&lt;/h2&gt;
&lt;p&gt;다음 템플릿은 AI 답변을 받아 적는 노트가 아니라, 판단과 검증 과정을 남기는 기록입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 이번 질문
- 무엇을 확인하려고 합니까?
- 이번에는 무엇을 다루지 않습니까?

## 사전 가설
- 실행 전에는 어떤 결과를 예상합니까?

## 근거
- 공식 문서와 버전은 무엇입니까?
- AI가 제안했지만 아직 확인하지 않은 내용은 무엇입니까?

## 최소 실험
- 실행 명령은 무엇입니까?
- 정상·경계·실패 입력은 무엇입니까?

## 실제 결과
- 예상과 같은 결과는 무엇입니까?
- 예상과 다른 결과는 무엇입니까?

## 판정
- 확인됨 / 조건부 확인 / 미확인 중 어디에 해당합니까?

## 다음 루프
- 다음에 확인할 질문 하나는 무엇입니까?&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;AI에게는 답보다 검증 재료를 요청합니다&lt;/h2&gt;
&lt;p&gt;AI를 사용할 때 “정답을 알려 주세요”라고만 요청하면 결론을 수동적으로 받아들이기 쉽습니다. 다음처럼 검증 가능한 재료를 함께 요청하는 편이 좋습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;이 개념을 현재 공식 용어에 맞게 설명해 주세요.
제가 직접 확인할 수 있는 최소 예제와 실패 사례를 각각 제시해 주세요.
버전이나 실행 환경에 따라 달라지는 부분은 별도로 표시해 주세요.
확신할 수 없는 내용은 추측하지 말고 미확인이라고 표시해 주세요.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;AI가 출처 링크를 제시하더라도 실제 페이지가 존재하고 해당 주장을 뒷받침하는지 직접 확인합니다. 인용문처럼 보이는 문장은 원문과 위치를 확인할 수 없으면 직접 인용으로 사용하지 않습니다.&lt;/p&gt;
&lt;h2&gt;자주 실패하는 패턴과 수정 방법입니다&lt;/h2&gt;
&lt;h3&gt;자료만 계속 수집합니다&lt;/h3&gt;
&lt;p&gt;북마크와 요약은 늘어나지만 산출물이 없다면 흡수 단계가 지나치게 큽니다. 20분이 지나면 예제 하나를 실행하도록 종료 조건을 정합니다.&lt;/p&gt;
&lt;h3&gt;첫 성공을 일반적인 원리로 착각합니다&lt;/h3&gt;
&lt;p&gt;정상 입력 하나가 동작했다면 실패 입력과 다른 조건을 추가합니다. 결론에는 확인한 버전과 범위를 함께 씁니다.&lt;/p&gt;
&lt;h3&gt;AI의 코드를 이해하지 않고 보관합니다&lt;/h3&gt;
&lt;p&gt;코드를 빈 파일에서 다시 작성하거나 각 선택의 이유를 설명합니다. 재현하지 못하면 아직 학습 완료로 표시하지 않습니다.&lt;/p&gt;
&lt;h3&gt;공유 글을 완벽하게 만들다가 루프가 멈춥니다&lt;/h3&gt;
&lt;p&gt;먼저 질문, 실행 명령, 결과, 남은 의문만 기록합니다. 문장 다듬기와 시각 자료는 별도의 편집 단계에서 보완합니다.&lt;/p&gt;
&lt;h2&gt;일주일마다 과정 지표를 확인합니다&lt;/h2&gt;
&lt;p&gt;학습 시간을 늘리는 것보다 루프가 실제로 닫혔는지 확인합니다. 다음 지표를 일주일 단위로 기록하면 병목을 찾을 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;시작한 질문 중 네 단계를 모두 마친 비율을 확인합니다.&lt;/li&gt;
&lt;li&gt;공식 자료 또는 테스트로 확인한 주장 수를 기록합니다.&lt;/li&gt;
&lt;li&gt;실패 사례를 통해 수정한 가설 수를 기록합니다.&lt;/li&gt;
&lt;li&gt;일주일 뒤 빈 화면에서 재현한 결과 수를 기록합니다.&lt;/li&gt;
&lt;li&gt;다음 작업에서 다시 사용한 기록 수를 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;숫자를 경쟁 점수로 사용하지는 않습니다. 흡수 단계에서 계속 멈춘다면 범위를 줄이고, 검증 단계가 비어 있다면 테스트와 반례에 시간을 더 배분하는 진단 도구로 사용합니다.&lt;/p&gt;
&lt;h2&gt;마치며&lt;/h2&gt;
&lt;p&gt;AI 시대의 학습 경쟁력은 답을 가장 빨리 복사하는 능력이 아닙니다. 작은 질문을 만들고, 실행 가능한 결과로 바꾸고, 근거와 반례로 확인한 뒤, 다시 사용할 수 있게 기록하는 능력입니다.&lt;/p&gt;
&lt;p&gt;처음에는 90분 안에 확인할 수 있는 질문 하나로 시작합니다. 네 단계를 마친 기록 한 개가 확인하지 않은 요약 여러 개보다 다음 작업에 더 유용합니다.&lt;/p&gt;</description>
      <category>AI 활용</category>
      <category>AI 시대 학습 방법</category>
      <category>ai 학습법</category>
      <category>AI 활용 학습법</category>
      <category>Learning Loop</category>
      <category>개발자 공부 방법</category>
      <category>러닝 루프</category>
      <category>사실 검증</category>
      <category>학습 기록</category>
      <author>세모난 야구공</author>
      <guid isPermaLink="true">https://olt13mm.tistory.com/2205</guid>
      <comments>https://olt13mm.tistory.com/2205#entry2205comment</comments>
      <pubDate>Sun, 3 May 2026 16:07:51 +0900</pubDate>
    </item>
    <item>
      <title>데이터 분석용 AI 도구 비교 방법</title>
      <link>https://olt13mm.tistory.com/2202</link>
      <description>&lt;p&gt;“SQL은 A, 탐색적 분석은 B, 멀티미디어는 C가 최고입니다”라는 표는 편하지만 오래가지 않습니다. 제품의 모델과 기능이 바뀌고, 같은 제품도 요금제·워크스페이스 정책·도구 활성화 여부에 따라 할 수 있는 일이 달라지기 때문입니다. 무엇보다 공개 벤치마크 점수가 내 회사의 누락값, 한글 열 이름, 복잡한 조인 규칙을 잘 처리한다는 보장은 없습니다.&lt;/p&gt;
&lt;p&gt;이 글은 ChatGPT·Claude·Gemini의 순위를 정하지 않습니다. 같은 데이터, 같은 지시문, 같은 정답표를 사용해 &lt;strong&gt;내 업무에 맞는 도구를 직접 고르는 절차&lt;/strong&gt;를 제공합니다. 기능 설명과 링크는 2026년 9월 2일에 공식 문서로 확인했으며, 실제 사용 전에는 계정 화면과 최신 정책을 다시 확인해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;675&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gm4ax/dJMcaiklaYJ/ZUDhXxrAJ4uf6Kdf0Lcct1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gm4ax/dJMcaiklaYJ/ZUDhXxrAJ4uf6Kdf0Lcct1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gm4ax/dJMcaiklaYJ/ZUDhXxrAJ4uf6Kdf0Lcct1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGm4ax%2FdJMcaiklaYJ%2FZUDhXxrAJ4uf6Kdf0Lcct1%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; alt=&quot;같은 데이터로 세 AI 도구를 블라인드 평가하는 흐름&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;675&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;675&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;먼저 비교 조건을 고정합니다&lt;/h2&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&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;/tr&gt;
&lt;tr&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;/tr&gt;
&lt;tr&gt;
&lt;td&gt;입력 파일&lt;/td&gt;
&lt;td&gt;동일한 해시를 가진 CSV와 데이터 사전을 사용합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&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;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;민감한 고객 정보나 운영 데이터는 곧바로 업로드하지 않습니다. 조직의 데이터 처리 계약, 보존 정책, 학습 사용 설정, 접근 통제와 국외 이전 조건을 먼저 확인합니다. 검증용 데이터는 식별자를 제거하거나 합성 데이터로 만듭니다.&lt;/p&gt;
&lt;h2&gt;작은 정답 데이터세트를 만듭니다&lt;/h2&gt;
&lt;p&gt;도구를 평가할 때는 답을 모르는 대형 파일보다 답을 아는 작은 파일이 유용합니다. 예를 들어 주문 데이터 200행을 만들고 다음 함정을 의도적으로 넣습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;주문 번호 중복 3건을 넣고 중복 판정 기준을 문서화합니다.&lt;/li&gt;
&lt;li&gt;날짜 형식을 &lt;code&gt;YYYY-MM-DD&lt;/code&gt;, &lt;code&gt;YYYY/MM/DD&lt;/code&gt;, 빈 값으로 섞습니다.&lt;/li&gt;
&lt;li&gt;취소 주문과 환불 주문을 별도 상태로 둡니다.&lt;/li&gt;
&lt;li&gt;금액 열에 쉼표가 포함된 문자열과 숫자를 섞습니다.&lt;/li&gt;
&lt;li&gt;고객 표에는 있으나 주문 표에는 없는 키와 그 반대 키를 넣습니다.&lt;/li&gt;
&lt;li&gt;집계 결과 5개를 사람이 계산해 &lt;code&gt;answer-key.md&lt;/code&gt;에 기록합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;원본 파일은 수정하지 않고 &lt;code&gt;input/&lt;/code&gt;, 생성된 코드와 정제 데이터는 &lt;code&gt;output/&lt;/code&gt;, 채점 결과는 &lt;code&gt;evaluation/&lt;/code&gt;에 분리합니다. 이렇게 해야 다른 사람이 같은 분석을 다시 실행할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;다섯 과제로 평가합니다&lt;/h2&gt;
&lt;h3&gt;1. 데이터 품질을 진단합니다&lt;/h3&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;두 파일을 수정하지 말고 구조와 품질만 진단합니다. 각 열의 추정 의미와 자료형, 누락률, 중복 후보, 범위 밖 값을 표로 작성합니다. 확실하지 않은 해석은 가정으로 표시합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;정답표에 심어 둔 문제를 몇 개 찾았는지, 존재하지 않는 문제를 만들어 내지는 않았는지 채점합니다.&lt;/p&gt;
&lt;h3&gt;2. 조인과 정제를 수행합니다&lt;/h3&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;후보 키의 고유성을 확인하고 안전한 조인 방식을 제안합니다. 조인 전후 행 수와 왼쪽 전용·오른쪽 전용 키 수를 보고합니다. 누락값을 임의로 채우지 않으며, 모든 변환을 코드로 남깁니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;3. SQL을 작성하고 실행 결과를 검증합니다&lt;/h3&gt;
&lt;p&gt;스키마, 사용하는 SQL 방언, 기대 결과를 함께 제공합니다. 문법만 그럴듯한 SQL이 아니라 중복 조인으로 매출이 부풀려지는지까지 확인합니다.&lt;/p&gt;
&lt;h3&gt;4. EDA와 차트를 만듭니다&lt;/h3&gt;
&lt;p&gt;차트의 아름다움보다 질문과의 연결을 봅니다. 축 단위, 표본 수, 제외 규칙, 이상치 처리와 불확실성이 함께 표시되어야 합니다. 상관관계를 인과관계로 표현하면 감점합니다.&lt;/p&gt;
&lt;h3&gt;5. 재현 가능한 보고서를 만듭니다&lt;/h3&gt;
&lt;p&gt;최종 답만 받지 않고 실행 코드, 사용한 파일명, 변환 순서, 생성물 목록, 한계를 요구합니다. 새 세션에서 코드만 실행해 같은 핵심 수치가 나오는지 확인합니다.&lt;/p&gt;
&lt;h2&gt;100점 채점표를 사용합니다&lt;/h2&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&gt;확인 방법&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;수치 정확성&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;30점&lt;/td&gt;
&lt;td&gt;정답표의 집계값·행 수·누락률과 비교합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;재현성&lt;/td&gt;
&lt;td align=&quot;right&quot;&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 align=&quot;right&quot;&gt;15점&lt;/td&gt;
&lt;td&gt;원본을 덮어쓰거나 값을 임의 생성했는지 확인합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;근거와 한계&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;15점&lt;/td&gt;
&lt;td&gt;가정, 제외 규칙, 불확실성을 구분했는지 봅니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실행 가능성&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;10점&lt;/td&gt;
&lt;td&gt;SQL·코드·파일이 실제로 열리고 실행되는지 봅니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;작업 효율&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;10점&lt;/td&gt;
&lt;td&gt;수정 횟수와 검토 시간을 함께 기록합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;결과물에서 제품명을 가린 뒤 가능하면 두 사람이 독립 채점합니다. 점수 차이가 크면 “좋아 보입니다” 같은 주관적 기준이 섞였는지 논의하고 평가 규칙을 보완합니다.&lt;/p&gt;
&lt;h2&gt;세 제품의 공식 기능은 이렇게만 확인합니다&lt;/h2&gt;
&lt;p&gt;기능은 선택의 출발점이지 성능 순위가 아닙니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ChatGPT Work&lt;/strong&gt;의 공식 활용 문서는 파일을 점검하고, 누락·중복·조인 문제를 확인하며, 차트와 재사용 가능한 보고서·노트북을 만드는 흐름을 설명합니다. 원본을 보존하고 가정과 불확실성을 밝히는 절차도 권장합니다. 계정에서 실제로 제공되는 도구와 연결 소스는 직접 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claude&lt;/strong&gt;의 공식 지원 문서는 업로드 파일과 코드 실행을 이용한 분석 및 파일 생성을 설명합니다. 지원 형식, 용량, 네트워크 접근과 조직 정책은 최신 계정 조건을 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gemini API&lt;/strong&gt;의 공식 문서는 코드 실행 도구를 이용해 계산하고 그래프를 만드는 방법을 설명합니다. API 기능과 소비자용 Gemini 앱 기능을 같은 것으로 간주하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;따라서 “실시간 데이터에 강합니다”라는 말도 그대로 채점하지 않습니다. 웹 검색인지, 승인된 데이터 연결인지, 사용자가 올린 최신 파일인지 구분하고 출처 URL과 조회 시각을 결과에 남기게 합니다.&lt;/p&gt;
&lt;h2&gt;같은 지시문으로 결과를 받습니다&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;당신은 분석 결과보다 검증 가능성을 우선하는 데이터 분석가입니다.

1. 원본 파일은 변경하지 않습니다.
2. 먼저 파일·열·후보 키·누락·중복을 진단합니다.
3. 불명확한 열의 의미를 추측하지 말고 질문 또는 가정으로 표시합니다.
4. 모든 정제와 집계를 다시 실행할 수 있는 코드로 남깁니다.
5. 조인 전후 행 수와 미매칭 건수를 보고합니다.
6. 핵심 수치는 독립적인 두 방법으로 교차 확인합니다.
7. 결과, 근거, 가정, 한계, 생성 파일 목록을 분리해 보고합니다.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;같은 프롬프트를 쓰되 도구별 문법을 유도하는 추가 지시는 넣지 않습니다. 한 도구에만 여러 차례 힌트를 주었다면 그 횟수와 시간을 비용으로 기록합니다.&lt;/p&gt;
&lt;h2&gt;평가 결과 기록 양식입니다&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;## 실행 정보
- 제품/모델:
- 계정/요금제:
- 실행 날짜:
- 허용 도구:
- 입력 파일 SHA-256:

## 결과
- 정확성: /30
- 재현성: /20
- 데이터 보존: /15
- 근거와 한계: /15
- 실행 가능성: /10
- 작업 효율: /10

## 오류 기록
- 틀린 수치:
- 재실행 실패:
- 필요한 힌트:
- 보안·정책 제약:&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;선택은 업무 단위로 내립니다&lt;/h2&gt;
&lt;p&gt;한 제품을 조직 전체의 영구 승자로 정할 필요는 없습니다. 반복 SQL, 작은 CSV 탐색, PDF와 표의 결합, 정기 보고서처럼 작업 유형별로 점수를 보관합니다. 정확성이 비슷하면 검토 시간, 권한 관리, 기존 저장소 연결, 결과물 형식과 비용을 비교합니다.&lt;/p&gt;
&lt;p&gt;모델이나 제품 기능이 바뀌면 이전 점수에 만료일을 붙입니다. 분기마다 또는 중요한 모델 변경 뒤에 같은 고정 데이터세트를 다시 실행하면 홍보 문구가 아니라 자신의 기록으로 선택할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;비교 결과를 공개할 때 빠뜨리지 않을 내용&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;입력 데이터의 사용 권한과 개인정보 처리 기준을 확인합니다.&lt;/li&gt;
&lt;li&gt;제품명, 모델명, 계정 조건, 실행일을 기록합니다.&lt;/li&gt;
&lt;li&gt;원본과 결과물을 분리하고 입력 파일 해시를 남깁니다.&lt;/li&gt;
&lt;li&gt;정답표는 실행 전에 만들고 평가 중에는 바꾸지 않습니다.&lt;/li&gt;
&lt;li&gt;생성된 SQL과 코드를 실제 환경에서 실행합니다.&lt;/li&gt;
&lt;li&gt;핵심 수치를 사람이 표본 검사하거나 다른 계산으로 교차 검증합니다.&lt;/li&gt;
&lt;li&gt;차트에 단위, 표본 수, 제외 규칙을 표시합니다.&lt;/li&gt;
&lt;li&gt;모델 변경 후 결과가 유지되는지 정기적으로 다시 평가합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;참고한 공식 문서입니다&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.chatgpt.com/ko-KR/use-cases/datasets-and-reports&quot;&gt;OpenAI Learn: 데이터세트를 분석하고 보고서 전달하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.claude.com/en/articles/12111783-create-and-edit-files-with-claude&quot;&gt;Anthropic Support: Create and edit files with Claude&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.claude.com/en/articles/8241126-upload-files-to-claude&quot;&gt;Anthropic Support: Upload files to Claude&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ai.google.dev/gemini-api/docs/code-execution&quot;&gt;Google AI for Developers: Code execution&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>AI 활용</category>
      <category>AI</category>
      <category>EDA</category>
      <category>데이터 분석</category>
      <category>재현성</category>
      <category>평가</category>
      <author>세모난 야구공</author>
      <guid isPermaLink="true">https://olt13mm.tistory.com/2202</guid>
      <comments>https://olt13mm.tistory.com/2202#entry2202comment</comments>
      <pubDate>Tue, 3 Mar 2026 22:06:14 +0900</pubDate>
    </item>
    <item>
      <title>Pexels API 사용과 이미지 라이선스 주의사항</title>
      <link>https://olt13mm.tistory.com/2201</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 2026년 9월 2일 Pexels 공식 API 문서, 라이선스, 이용약관을 기준으로 확인했습니다. 라이선스와 API 조건은 바뀔 수 있으므로 서비스에 적용하기 전 원문을 다시 확인합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;검색 요청 전에 알아둘 내용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pexels API를 사용하면 사진과 동영상을 키워드로 검색해 서비스에 표시할 수 있습니다. 요청의 &lt;code&gt;Authorization&lt;/code&gt; 헤더에 API 키를 넣어 인증하며, 키는 브라우저 코드에 포함하지 않고 자신의 서버에서 관리하는 편이 안전합니다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;GET https://api.pexels.com/v1/search?query=workspace&amp;amp;per_page=12
Authorization: YOUR_PEXELS_API_KEY&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 &amp;lsquo;Pexels 이미지는 무료입니다&amp;rsquo;라는 한 문장만으로 운영 조건을 설명할 수는 없습니다. 일반 Pexels 라이선스와 API 이용 지침을 함께 확인해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반 라이선스는 사진과 동영상을 개인&amp;middot;상업 목적으로 무료 사용할 수 있고 저작자 표기를 필수로 요구하지 않습니다.&lt;/li&gt;
&lt;li&gt;API 문서는 Pexels로 연결되는 눈에 잘 띄는 링크를 요구하고, 가능하면 사진가를 표기해 해당 Pexels 페이지로 연결하도록 안내합니다.&lt;/li&gt;
&lt;li&gt;Pexels의 핵심 기능을 복제하거나 콘텐츠를 사실상 원본 그대로 재판매&amp;middot;재배포하는 서비스는 허용되지 않을 수 있습니다.&lt;/li&gt;
&lt;li&gt;기본 API 제한은 공식 문서상 시간당 200회, 월 20,000회이며 우회 시도는 계정 접근 중단으로 이어질 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;675&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYivXH/dJMcaheIp8G/g3hoVjsDxkuOtnOL27Ijak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYivXH/dJMcaheIp8G/g3hoVjsDxkuOtnOL27Ijak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYivXH/dJMcaheIp8G/g3hoVjsDxkuOtnOL27Ijak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYivXH%2FdJMcaheIp8G%2Fg3hoVjsDxkuOtnOL27Ijak%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; alt=&quot;브라우저와 자체 백엔드, Pexels API 사이에서 API 키를 서버에 두고 이미지와 출처 링크를 돌려주는 호출 구조&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;675&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;675&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Pexels API와 라이선스를 분리해 이해합니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pexels 라이선스는 콘텐츠를 어떤 방식으로 사용할 수 있는지 설명합니다. API 문서는 프로그램으로 콘텐츠를 검색하고 표시할 때 지켜야 할 인증, 호출 제한, 링크와 표기 방식을 추가로 설명합니다. API를 사용한다면 둘 중 하나만 읽지 않습니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&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;Pexels License&lt;/td&gt;
&lt;td&gt;사진&amp;middot;동영상의 허용 용도와 금지 사례를 확인합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API Documentation&lt;/td&gt;
&lt;td&gt;인증, 엔드포인트, 쿼터, 링크와 사진가 표기 지침을 확인합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terms of Service&lt;/td&gt;
&lt;td&gt;standalone 배포, 서비스 복제 등 상세 제한을 확인합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무료라는 표현은 가격이 없다는 뜻이지 아무 조건 없이 원본 콘텐츠를 재배포할 수 있다는 뜻이 아닙니다. 특히 이미지 모음, 배경화면, 스톡 이미지 검색처럼 Pexels 자체의 핵심 기능과 경쟁할 수 있는 서비스를 만들 때는 약관을 세밀하게 검토합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1단계: API 키를 발급합니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pexels 계정으로 로그인한 뒤 공식 API 페이지에서 키 발급 절차를 진행합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pexels.com/api/&quot;&gt;Pexels Image &amp;amp; Video API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pexels.com/api/documentation/&quot;&gt;Pexels API documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 화면은 발급 당시 기준입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 로그인 후 Developer 페이지로 이동합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;1158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vaB66/dJMcadgPa1g/tPVl7D9fHNcSjtzl0B8Km0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vaB66/dJMcadgPa1g/tPVl7D9fHNcSjtzl0B8Km0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vaB66/dJMcadgPa1g/tPVl7D9fHNcSjtzl0B8Km0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvaB66%2FdJMcadgPa1g%2FtPVl7D9fHNcSjtzl0B8Km0%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; alt=&quot;Pexels 로그인 후 Developer 메뉴로 이동한 화면&quot; loading=&quot;lazy&quot; width=&quot;2082&quot; height=&quot;1158&quot; data-origin-width=&quot;2082&quot; data-origin-height=&quot;1158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Image &amp;amp; Video API 메뉴를 선택합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;1180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xDYqA/dJMb99S1sHa/lew0LsaddkBJD8iK9C8WO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xDYqA/dJMb99S1sHa/lew0LsaddkBJD8iK9C8WO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xDYqA/dJMb99S1sHa/lew0LsaddkBJD8iK9C8WO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxDYqA%2FdJMb99S1sHa%2Flew0LsaddkBJD8iK9C8WO1%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; alt=&quot;Pexels Developer 페이지에서 Image and Video API를 선택한 화면&quot; loading=&quot;lazy&quot; width=&quot;1005&quot; height=&quot;1180&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;1180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Your API Key 버튼을 눌러 키를 확인합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;997&quot; data-origin-height=&quot;1191&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byVALa/dJMcacPLjNd/HkgGG6S1C1PUwswODDmSKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byVALa/dJMcacPLjNd/HkgGG6S1C1PUwswODDmSKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byVALa/dJMcacPLjNd/HkgGG6S1C1PUwswODDmSKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyVALa%2FdJMcacPLjNd%2FHkgGG6S1C1PUwswODDmSKK%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; alt=&quot;Pexels에서 발급된 API 키가 표시된 화면&quot; loading=&quot;lazy&quot; width=&quot;997&quot; height=&quot;1191&quot; data-origin-width=&quot;997&quot; data-origin-height=&quot;1191&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발급 화면의 버튼 이름과 승인 절차는 바뀔 수 있습니다. 화면을 그대로 외우기보다 공식 API 페이지에서 현재 안내를 따릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키를 받은 뒤 프로젝트 루트의 로컬 환경 파일에 저장할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;PEXELS_API_KEY=발급받은_키를_입력합니다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 키가 들어 있는 파일은 Git에 포함하지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;.env
.env.local&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀원이 필요한 변수 이름을 알 수 있도록 값이 비어 있는 예시 파일을 별도로 둡니다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;# .env.example
PEXELS_API_KEY=&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 노출된 키는 Git 기록에서 문자열만 숨기는 것으로 끝내지 않습니다. Pexels 계정에서 해당 키를 교체할 수 있는지 확인하고, 배포 환경의 비밀 값도 새 키로 갱신합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;브라우저가 아니라 서버에서 호출합니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 프론트엔드 코드는 빌드 결과나 개발자 도구에서 API 키가 보일 수 있으므로 피합니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// 사용하지 않는 예시입니다.
fetch(&quot;https://api.pexels.com/v1/search?query=office&quot;, {
  headers: {
    Authorization: &quot;실제_API_KEY&quot;,
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 브라우저는 자신의 백엔드에 검색어를 전달하고, 백엔드가 Pexels API를 호출합니다. 이 구조는 키를 서버에 보관할 수 있고, 검색어 검증&amp;middot;캐시&amp;middot;호출 제한&amp;middot;오류 처리를 한곳에서 적용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;브라우저
  &amp;rarr; 자신의 /api/photos 엔드포인트
    &amp;rarr; 서버의 PEXELS_API_KEY로 Pexels 호출
      &amp;rarr; 필요한 사진 메타데이터만 브라우저에 반환합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 프록시를 두더라도 Pexels의 쿼터를 우회하거나 콘텐츠 출처를 숨기기 위한 용도로 사용하지 않습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Node.js 서버 검색 예제를 구현합니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제는 Node.js 18 이상에서 사용할 수 있는 전역 &lt;code&gt;fetch&lt;/code&gt;를 사용합니다. 검색어를 &lt;code&gt;URLSearchParams&lt;/code&gt;로 인코딩하고, 응답 상태와 쿼터 헤더를 확인한 뒤 필요한 필드만 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;qml&quot;&gt;&lt;code&gt;const PEXELS_SEARCH_URL = &quot;https://api.pexels.com/v1/search&quot;;

export async function searchPexelsPhotos({
  query,
  page = 1,
  perPage = 12,
}) {
  if (typeof query !== &quot;string&quot; || query.trim().length &amp;lt; 2) {
    throw new Error(&quot;검색어는 두 글자 이상 입력합니다.&quot;);
  }

  const safePage = Math.max(1, Number(page) || 1);
  const safePerPage = Math.min(80, Math.max(1, Number(perPage) || 12));

  const params = new URLSearchParams({
    query: query.trim(),
    page: String(safePage),
    per_page: String(safePerPage),
  });

  const response = await fetch(`${PEXELS_SEARCH_URL}?${params}`, {
    headers: {
      Authorization: process.env.PEXELS_API_KEY,
    },
    signal: AbortSignal.timeout(8_000),
  });

  if (response.status === 429) {
    throw new Error(&quot;Pexels API 호출 한도를 초과했습니다.&quot;);
  }

  if (!response.ok) {
    const body = await response.text();
    throw new Error(
      `Pexels API 오류: ${response.status} ${body.slice(0, 200)}`,
    );
  }

  const data = await response.json();

  return {
    page: data.page,
    perPage: data.per_page,
    totalResults: data.total_results,
    nextPage: data.next_page ?? null,
    quota: {
      limit: response.headers.get(&quot;x-ratelimit-limit&quot;),
      remaining: response.headers.get(&quot;x-ratelimit-remaining&quot;),
      reset: response.headers.get(&quot;x-ratelimit-reset&quot;),
    },
    photos: data.photos.map((photo) =&amp;gt; ({
      id: photo.id,
      width: photo.width,
      height: photo.height,
      alt: photo.alt,
      photographer: photo.photographer,
      photographerUrl: photo.photographer_url,
      pexelsUrl: photo.url,
      src: {
        medium: photo.src.medium,
        large: photo.src.large,
      },
    })),
  };
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;per_page&lt;/code&gt;의 허용 범위와 응답 필드는 변경될 수 있으므로 공식 문서의 현재 정의를 확인합니다. 예제에서 상한을 두는 목적은 한 화면에 지나치게 많은 결과를 요청하지 않도록 애플리케이션 정책을 적용하는 것입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Express 라우트로 브라우저에 제공합니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음처럼 자신의 API 라우트를 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;import express from &quot;express&quot;;
import { searchPexelsPhotos } from &quot;./pexels.js&quot;;

const app = express();

app.get(&quot;/api/photos&quot;, async (req, res) =&amp;gt; {
  try {
    const result = await searchPexelsPhotos({
      query: req.query.q,
      page: req.query.page,
      perPage: 12,
    });

    res.set(&quot;Cache-Control&quot;, &quot;public, max-age=300&quot;);
    res.json(result);
  } catch (error) {
    const isRateLimit = error.message.includes(&quot;호출 한도&quot;);
    res.status(isRateLimit ? 429 : 502).json({
      error: isRateLimit
        ? &quot;이미지 검색 요청이 많습니다. 잠시 뒤 다시 시도합니다.&quot;
        : &quot;이미지 검색을 완료하지 못했습니다.&quot;,
    });
  }
});

app.listen(3000);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&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;자신의 API 라우트에 사용자별 요청 제한을 적용합니다.&lt;/li&gt;
&lt;li&gt;동일 검색어와 페이지 결과를 짧게 캐시합니다.&lt;/li&gt;
&lt;li&gt;허용할 검색어 길이와 문자 범위를 검증합니다.&lt;/li&gt;
&lt;li&gt;Pexels의 상세 오류 본문을 브라우저에 그대로 노출하지 않습니다.&lt;/li&gt;
&lt;li&gt;로그에 &lt;code&gt;Authorization&lt;/code&gt; 헤더나 환경 변수 값을 기록하지 않습니다.&lt;/li&gt;
&lt;li&gt;타임아웃과 재시도 횟수를 제한합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이미지와 출처 링크를 함께 표시합니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 응답에는 사진가 이름, 사진가의 Pexels URL, 사진 상세 페이지 URL이 포함됩니다. 이 값을 버리지 않고 UI에 전달합니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;figure&amp;gt;
  &amp;lt;a href=&quot;PEXELS_PHOTO_URL&quot;&amp;gt;
    &amp;lt;img src=&quot;PHOTO_SRC&quot; alt=&quot;API 응답과 문맥에 맞게 검토한 대체 텍스트&quot; /&amp;gt;
  &amp;lt;/a&amp;gt;
  &amp;lt;figcaption&amp;gt;
    &amp;lt;a href=&quot;PHOTOGRAPHER_URL&quot;&amp;gt;사진가 이름&amp;lt;/a&amp;gt;의 사진 &amp;middot;
    &amp;lt;a href=&quot;PEXELS_PHOTO_URL&quot;&amp;gt;Pexels에서 보기&amp;lt;/a&amp;gt;
  &amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지의 푸터나 이미지 목록 주변에는 Pexels 제공 콘텐츠임을 알 수 있는 링크도 배치합니다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;&amp;lt;a href=&quot;https://www.pexels.com&quot;&amp;gt;Photos provided by Pexels&amp;lt;/a&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pexels 일반 라이선스는 저작자 표기를 의무로 요구하지 않지만, API 문서는 Pexels로 돌아가는 눈에 잘 띄는 링크를 요구하고 가능하면 사진가 표기를 안내합니다. 따라서 API 기반 서비스에서는 위와 같이 Pexels 및 사진가 페이지 링크를 제품 요구사항으로 구현하는 편이 안전합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;alt&lt;/code&gt; 속성에는 사진가 이름이나 &amp;lsquo;Pexels 이미지&amp;rsquo;만 반복하지 않습니다. 실제 화면에서 이미지가 전달하는 의미를 설명하고, 장식용 이미지라면 빈 대체 텍스트를 검토합니다. API의 &lt;code&gt;alt&lt;/code&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;사진 검색 응답에는 현재 페이지와 다음 페이지 정보가 포함될 수 있습니다. 다음 페이지 URL을 그대로 클라이언트에 전달해 브라우저가 Pexels를 직접 호출하게 만들지 않습니다. 자신의 페이지 번호만 받아 서버가 새 요청을 구성합니다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;const nextPage = result.nextPage ? result.page + 1 : null;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무한 스크롤에서는 사용자가 화면을 내릴 때마다 중복 요청이 발생하기 쉽습니다. 요청 중 상태를 두고, 같은 검색어&amp;middot;페이지 조합을 다시 호출하지 않으며, 검색어가 바뀌면 이전 요청을 취소합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿼터 헤더와 429 오류를 처리합니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pexels 공식 API 문서가 안내하는 기본 제한은 시간당 200회와 월 20,000회입니다. 더 높은 제한이 필요하면 사용 사례와 표시 방식을 준비해 Pexels에 요청합니다. 여러 키를 만들거나 프록시를 바꿔 제한을 우회하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공한 &lt;code&gt;2xx&lt;/code&gt; 응답에는 다음 월간 쿼터 헤더가 포함됩니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&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;code&gt;X-Ratelimit-Limit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;월간 전체 요청 한도를 나타냅니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X-Ratelimit-Remaining&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;월간 남은 요청 수를 나타냅니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X-Ratelimit-Reset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;월간 기간이 초기화되는 Unix 시각을 나타냅니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에 따르면 이 헤더는 성공 응답에만 포함되며 &lt;code&gt;429 Too Many Requests&lt;/code&gt; 응답에는 포함되지 않습니다. 따라서 429가 발생한 뒤 reset 헤더를 읽어 복구 시간을 계산하는 코드에 의존하지 않습니다. 성공 응답에서 관측한 값과 자체 사용량을 모니터링합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재시도는 모든 오류에 즉시 적용하지 않습니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&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;code&gt;401&lt;/code&gt; 또는 &lt;code&gt;403&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;키, 권한, 이용 조건을 확인하며 자동 반복 호출하지 않습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;429&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;요청을 중단하고 캐시 결과나 안내 메시지를 사용합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;5xx&lt;/code&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;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;라이선스와 이용 조건을 확인합니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pexels 콘텐츠를 API로 표시하기 전에 다음 항목을 검토합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 서비스 화면에 Pexels로 연결되는 눈에 잘 띄는 링크를 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 가능하면 사진가 이름을 표시하고 해당 Pexels 페이지로 연결합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 사진 상세 URL과 사진가 URL을 API 응답에서 보존합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 인물이나 브랜드가 포함된 콘텐츠를 모욕적&amp;middot;오해를 부르는 방식으로 사용하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 사진 속 인물이나 브랜드가 제품을 보증한다고 암시하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 원본 콘텐츠를 사실상 그대로 판매하거나 재배포하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; Pexels의 핵심 검색&amp;middot;스톡&amp;middot;배경화면 기능을 복제하는 서비스가 아닌지 검토합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 적용 시점의 Pexels License와 Terms of Service 원문을 다시 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지에 인물, 상표, 건축물, 작품이 포함되어 있으면 Pexels 라이선스만으로 모든 초상권&amp;middot;상표권&amp;middot;재산권 문제가 자동 해결된다고 단정하지 않습니다. 광고, 의료, 정치, 민감한 주제처럼 맥락에 따라 오해가 커질 수 있는 용도는 별도로 검토합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자주 발생하는 오류를 해결합니다&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;브라우저에서 401 오류가 발생합니다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 키를 프론트엔드에 추가하는 방식으로 해결하지 않습니다. 서버 환경 변수에 키가 실제로 설정되었는지, 서버 요청의 &lt;code&gt;Authorization&lt;/code&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;code&gt;.env&lt;/code&gt; 파일은 보통 배포 서버에 자동으로 전달되지 않습니다. 호스팅 서비스의 비밀 또는 환경 변수 설정에 &lt;code&gt;PEXELS_API_KEY&lt;/code&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;검색어를 입력할 때마다 요청한다면 디바운스를 적용하고, 동일한 검색어&amp;middot;페이지 결과를 캐시합니다. 화면이 다시 렌더링될 때 중복 호출되지 않는지도 확인합니다.&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;code&gt;width&lt;/code&gt;와 &lt;code&gt;height&lt;/code&gt;를 사용해 표시 비율을 미리 확보합니다. 화면 크기에 맞는 &lt;code&gt;src&lt;/code&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;웹에서 직접 내려받은 콘텐츠의 일반 라이선스와 API를 통한 통합 지침을 구분합니다. API 기반 화면이라면 공식 API 문서에 맞춰 Pexels 링크와 가능한 사진가 표기를 구현합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서비스에 적용하기 전에 확인합니다&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; API 키를 서버 비밀 값으로 보관합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; &lt;code&gt;.env&lt;/code&gt;와 실제 키가 Git에 포함되지 않는지 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 검색어와 페이지 값을 서버에서 검증합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 타임아웃, 캐시, 자신의 라우트 요청 제한을 적용합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 성공 응답의 월간 쿼터 헤더를 모니터링합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 429 발생 시 반복 호출을 멈추고 사용자 메시지를 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; Pexels와 사진가 링크를 UI에 유지합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 이미지 문맥과 대체 텍스트를 사람이 검토합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; 정책 검증일과 적용한 약관 버전을 기록합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;검색 성공 뒤에 챙길 일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pexels API 통합의 핵심은 검색 요청 하나를 성공시키는 것이 아닙니다. API 키를 서버에서 보호하고, 검색어와 호출량을 통제하며, 이미지와 함께 Pexels 및 사진가 링크를 제공해야 합니다. 무료 콘텐츠라는 이유로 라이선스와 API 조건을 생략하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 환경에서는 성공 응답의 쿼터 헤더를 기록하고 429를 정상적인 장애 시나리오로 처리합니다. 서비스가 Pexels의 핵심 기능을 복제하거나 원본 콘텐츠를 standalone 형태로 배포하지 않는지도 출시 전에 확인합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;공식 참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pexels.com/api/documentation/&quot;&gt;Pexels API documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pexels.com/license/&quot;&gt;Pexels License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.pexels.com/terms-of-service/&quot;&gt;Pexels Terms of Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://help.pexels.com/hc/en-us/articles/900005880463-What-are-the-Terms-and-Conditions&quot;&gt;What are the Terms and Conditions?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://help.pexels.com/hc/en-us/articles/360042295174-What-is-the-license-of-the-photos-and-videos-on-Pexels&quot;&gt;What is the license of the photos and videos on Pexels?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>업무 자동화</category>
      <category>API</category>
      <category>Javascript</category>
      <category>Pexels</category>
      <category>라이선스</category>
      <category>이미지</category>
      <author>세모난 야구공</author>
      <guid isPermaLink="true">https://olt13mm.tistory.com/2201</guid>
      <comments>https://olt13mm.tistory.com/2201#entry2201comment</comments>
      <pubDate>Mon, 2 Mar 2026 21:50:05 +0900</pubDate>
    </item>
    <item>
      <title>Jira MCP 사용 가이드와 안전한 권한 설계</title>
      <link>https://olt13mm.tistory.com/2200</link>
      <description>&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;이 글의 명령과 정책은 2026년 9월 2일 Atlassian 공식 문서를 기준으로 확인했습니다. MCP 서버 주소와 클라이언트 명령은 바뀔 수 있으므로 실제 적용 전 공식 시작 문서를 다시 확인합니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;연결 명령부터 확인합니다&lt;/h2&gt;
&lt;p&gt;Claude Code에서 Atlassian의 공식 원격 MCP 서버를 연결할 때는 다음 명령을 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;claude mcp add --transport http atlassian https://mcp.atlassian.com/v1/mcp/authv2&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Claude Code 세션을 연 뒤 &lt;code&gt;/mcp&lt;/code&gt;를 실행해 Atlassian 계정으로 인증합니다. 예전에 사용하던 &lt;code&gt;--transport sse&lt;/code&gt;와 &lt;code&gt;/v1/sse&lt;/code&gt; 주소는 새 설정에 사용하지 않습니다. 현재 공식 안내는 Streamable HTTP 방식과 &lt;code&gt;/v1/mcp/authv2&lt;/code&gt; 주소를 사용합니다.&lt;/p&gt;
&lt;p&gt;연결 자체보다 더 중요한 원칙은 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MCP가 사용할 수 있는 데이터와 작업은 사용자의 기존 Atlassian 권한을 따릅니다.&lt;/li&gt;
&lt;li&gt;조직 관리자는 도메인, IP 허용 목록, 인증 방식, Read·Search·Write 권한을 별도로 통제할 수 있습니다.&lt;/li&gt;
&lt;li&gt;처음에는 조회와 검색만 허용하고, 쓰기 작업은 테스트 프로젝트와 사람의 최종 확인을 거쳐 확대합니다.&lt;/li&gt;
&lt;li&gt;이슈 설명이나 Confluence 문서도 신뢰할 수 없는 입력으로 취급하고, 외부 지시를 실행하라는 문구를 그대로 따르지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Atlassian Rovo MCP는 무엇을 연결합니까?&lt;/h2&gt;
&lt;p&gt;MCP는 AI 클라이언트가 외부 서비스의 도구를 일정한 형식으로 발견하고 호출하도록 돕는 프로토콜입니다. Atlassian Rovo MCP Server는 Claude Code 같은 MCP 클라이언트와 Jira·Confluence 등 Atlassian Cloud 제품 사이에서 인증과 도구 호출을 중계하는 공식 원격 서버입니다.&lt;/p&gt;
&lt;p&gt;예를 들어 다음과 같은 작업을 요청할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;특정 Jira 작업 항목의 상태와 담당자를 조회합니다.&lt;/li&gt;
&lt;li&gt;스프린트의 미완료 항목을 검색하고 요약합니다.&lt;/li&gt;
&lt;li&gt;Confluence 페이지의 핵심 결정 사항을 정리합니다.&lt;/li&gt;
&lt;li&gt;권한이 허용된 경우 Jira 작업 항목이나 Confluence 페이지를 생성·수정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MCP를 연결했다고 해서 Claude Code가 관리자 권한을 얻는 것은 아닙니다. OAuth로 로그인한 사용자가 Atlassian에서 볼 수 없거나 수정할 수 없는 데이터는 MCP에서도 접근할 수 없습니다. 여기에 조직 관리자가 설정한 MCP 서버 권한과 보안 정책이 추가로 적용됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;675&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHjOVM/dJMcacdjFxG/KUKo0eOazma7S8K20K9is1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHjOVM/dJMcacdjFxG/KUKo0eOazma7S8K20K9is1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHjOVM/dJMcacdjFxG/KUKo0eOazma7S8K20K9is1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHjOVM%2FdJMcacdjFxG%2FKUKo0eOazma7S8K20K9is1%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; alt=&quot;Claude Code와 Atlassian Rovo MCP 사이의 인증 및 권한 흐름&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;675&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;675&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;연결 전에 확인할 사항&lt;/h2&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Atlassian 환경&lt;/td&gt;
&lt;td&gt;Jira 또는 Confluence가 Atlassian Cloud에서 제공되는지 확인합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용자 권한&lt;/td&gt;
&lt;td&gt;대상 사이트와 프로젝트를 실제 브라우저에서 조회할 수 있는지 확인합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;MCP 명령을 지원하는 최신 Claude Code를 사용합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;브라우저&lt;/td&gt;
&lt;td&gt;대화형 OAuth 2.1 동의 화면을 열 수 있는 환경인지 확인합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;조직 정책&lt;/td&gt;
&lt;td&gt;Rovo MCP Server와 사용하려는 클라이언트 도메인이 허용되어 있는지 관리자에게 확인합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;네트워크 정책&lt;/td&gt;
&lt;td&gt;조직 IP 허용 목록이 있다면 현재 접속 위치가 허용되어 있는지 확인합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;회사 계정에서는 사용자가 명령을 정확히 실행해도 조직 정책 때문에 인증이나 도구 호출이 차단될 수 있습니다. 이 경우 설정을 우회하려고 하지 말고 관리자와 허용 범위를 확인합니다.&lt;/p&gt;
&lt;h2&gt;1단계: 공식 MCP 서버를 등록합니다&lt;/h2&gt;
&lt;p&gt;터미널에서 다음 명령을 실행합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;claude mcp add --transport http atlassian https://mcp.atlassian.com/v1/mcp/authv2&lt;/code&gt;&lt;/pre&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mcp add&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Claude Code에 MCP 서버 설정을 추가합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--transport http&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Streamable HTTP 전송 방식을 사용합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;atlassian&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;로컬에서 사용할 서버 별칭을 지정합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/v1/mcp/authv2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Atlassian이 현재 시작 문서에서 안내하는 원격 MCP 주소입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;기존 글이나 오래된 설정에 아래 주소가 남아 있다면 새 공식 설정으로 교체합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://mcp.atlassian.com/v1/sse&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;/v1/sse&lt;/code&gt;는 예전 HTTP+SSE 방식의 주소입니다. 전송 방식과 인증 주소를 섞으면 연결 실패나 재인증 문제가 생길 수 있습니다.&lt;/p&gt;
&lt;h2&gt;2단계: OAuth 인증을 완료합니다&lt;/h2&gt;
&lt;p&gt;Claude Code 세션을 시작하고 다음 슬래시 명령을 실행합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/mcp&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;등록한 &lt;code&gt;atlassian&lt;/code&gt; 서버를 선택하고 인증을 시작합니다. 브라우저에 Atlassian 로그인과 OAuth 2.1 동의 화면이 표시되면 다음 내용을 확인합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;로그인한 Atlassian 계정이 업무용으로 의도한 계정인지 확인합니다.&lt;/li&gt;
&lt;li&gt;연결 대상 Atlassian 사이트가 맞는지 확인합니다.&lt;/li&gt;
&lt;li&gt;요청하는 접근 범위를 읽고 승인합니다.&lt;/li&gt;
&lt;li&gt;브라우저 인증이 끝나면 Claude Code로 돌아와 연결 상태를 확인합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;대화형 사용에는 OAuth 2.1이 기본이자 권장 방식입니다. 별도의 API 토큰이 항상 필요한 것은 아닙니다. 다만 Atlassian 공식 문서에 따르면 조직 관리자가 허용한 경우 CI/CD, 봇, 백엔드 서비스처럼 사용자가 브라우저 앞에 없는 비대화형 환경에서는 개인 API 토큰 또는 서비스 계정 키를 사용할 수 있습니다. 대화형 OAuth와 자동화용 토큰을 같은 운영 절차로 취급하지 않습니다.&lt;/p&gt;
&lt;h2&gt;3단계: 등록 상태와 최소 조회를 확인합니다&lt;/h2&gt;
&lt;p&gt;터미널에서 등록 목록을 확인합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;claude mcp list&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;표시 형식은 Claude Code 버전에 따라 달라질 수 있으므로 특정 출력 문자열과 완전히 같은지만 검사하지 않습니다. &lt;code&gt;atlassian&lt;/code&gt; 서버가 등록되어 있고 연결 또는 인증 상태가 정상인지 확인합니다.&lt;/p&gt;
&lt;p&gt;첫 요청은 쓰기가 없는 작은 조회로 시작합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;내가 접근할 수 있는 Atlassian 사이트를 보여주고, 데이터를 변경하지 마세요.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;그다음 자신이 이미 알고 있는 테스트 이슈를 조회합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PROJ-123의 제목, 상태, 담당자만 조회하세요. 댓글을 추가하거나 상태를 변경하지 마세요.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;조회 결과는 Jira 웹 화면과 비교합니다. 다른 사이트의 동명 프로젝트를 선택했거나 오래된 이슈를 조회하지 않았는지 확인합니다.&lt;/p&gt;
&lt;h2&gt;읽기·검색·쓰기 권한을 나눕니다&lt;/h2&gt;
&lt;p&gt;Atlassian 조직 관리자는 Rovo MCP Server의 권한을 Read, Search, Write 의도별로 허용하거나 차단할 수 있습니다. 제품별 세부 설정도 가능하므로 Jira 조회는 허용하면서 Confluence 쓰기는 차단하는 식으로 운영할 수 있습니다.&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;Read&lt;/td&gt;
&lt;td&gt;작업 항목이나 페이지의 내용을 읽습니다.&lt;/td&gt;
&lt;td&gt;필요한 제품과 프로젝트 범위에서 허용합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search&lt;/td&gt;
&lt;td&gt;Jira·Confluence 등의 데이터를 검색합니다.&lt;/td&gt;
&lt;td&gt;검색 대상에 민감 정보가 있는지 확인한 뒤 허용합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write&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;p&gt;관리자 설정만으로 모든 범위가 결정되지는 않습니다. 도구 호출에는 사용자의 기존 Jira·Confluence 권한도 함께 적용됩니다. 따라서 다음 두 조건이 모두 충족되어야 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;사용자의 기존 Atlassian 권한
            +
조직의 Rovo MCP 권한과 보안 정책
            =
실제로 허용되는 도구 호출&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;조직 보안 정책에서 확인할 네 가지 축&lt;/h2&gt;
&lt;h3&gt;1. 허용 도메인을 확인합니다&lt;/h3&gt;
&lt;p&gt;조직 관리자는 어떤 AI 도구와 도메인이 OAuth 연결을 시작할 수 있는지 통제할 수 있습니다. 승인되지 않은 클라이언트나 콜백 주소라면 OAuth 동의 화면이 표시된 뒤에도 연결이 차단될 수 있습니다.&lt;/p&gt;
&lt;h3&gt;2. IP 허용 목록을 확인합니다&lt;/h3&gt;
&lt;p&gt;Atlassian 조직에서 IP 허용 목록을 사용하면 MCP 도구 호출에도 같은 네트워크 제한이 적용될 수 있습니다. 인증은 성공했지만 실제 조회에서 IP 권한 오류가 발생한다면 현재 사무실·VPN·원격 근무 네트워크가 허용 범위에 포함되는지 확인합니다.&lt;/p&gt;
&lt;h3&gt;3. 인증 방식을 구분합니다&lt;/h3&gt;
&lt;p&gt;사용자가 직접 상호작용하는 Claude Code 연결은 OAuth 2.1을 우선합니다. 자동화 계정이 필요한 경우에는 조직이 API 토큰 인증을 허용하는지 확인하고, 토큰을 비밀 저장소에 보관하며, 만료·회전·폐기 절차를 정합니다.&lt;/p&gt;
&lt;h3&gt;4. 제품별 Read·Search·Write를 검토합니다&lt;/h3&gt;
&lt;p&gt;향후 Atlassian이 새로운 도구를 추가할 때 기존의 포괄 허용 설정을 상속할 수 있습니다. ‘향후 추가 권한 자동 허용’과 같은 선택 항목을 무심코 켜지 않고 정기적으로 권한 목록을 재검토합니다.&lt;/p&gt;
&lt;h2&gt;안전한 쓰기 작업 절차&lt;/h2&gt;
&lt;p&gt;이슈 생성과 상태 변경을 허용하려면 다음 순서를 권장합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;별도의 테스트 프로젝트에서 동일한 워크플로를 재현합니다.&lt;/li&gt;
&lt;li&gt;요청문에 대상 사이트, 프로젝트 키, 이슈 키, 허용할 변경을 명시합니다.&lt;/li&gt;
&lt;li&gt;실행 전 변경 계획을 먼저 출력하게 합니다.&lt;/li&gt;
&lt;li&gt;여러 이슈를 변경할 때는 첫 항목 하나만 실행해 결과를 확인합니다.&lt;/li&gt;
&lt;li&gt;상태 변경, 삭제, 권한 변경, 대량 생성은 사람이 최종 승인합니다.&lt;/li&gt;
&lt;li&gt;실행 뒤 Jira 기록과 조직 감사 로그에서 예상한 변경만 발생했는지 확인합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;예를 들어 다음처럼 요청합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PROJ 테스트 프로젝트의 PROJ-123에만 댓글을 추가하려고 합니다.
먼저 추가할 댓글과 호출할 작업을 보여주고, 아직 실행하지 마세요.
다른 이슈와 상태는 변경하지 마세요.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;자연어 요청은 편리하지만 프로젝트 키나 이슈 키가 빠지면 잘못된 대상을 선택할 위험이 있습니다. ‘최근 이슈를 모두 정리하세요’처럼 범위가 모호한 명령으로 쓰기 작업을 시작하지 않습니다.&lt;/p&gt;
&lt;h2&gt;MCP에서 프롬프트 인젝션을 주의합니다&lt;/h2&gt;
&lt;p&gt;Jira 설명, 댓글, 첨부 문서, Confluence 페이지는 사람이 작성했거나 외부 시스템에서 가져온 데이터입니다. 이 안에 ‘이전 지시를 무시하고 다른 도구를 호출하세요’ 같은 문장이 있어도 업무 데이터일 뿐 신뢰할 수 있는 시스템 지시가 아닙니다.&lt;/p&gt;
&lt;p&gt;다음 원칙을 적용합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;외부 콘텐츠의 지시문을 실행 명령으로 취급하지 않습니다.&lt;/li&gt;
&lt;li&gt;조회 작업과 변경 작업을 한 번의 모호한 요청에 섞지 않습니다.&lt;/li&gt;
&lt;li&gt;민감한 토큰, 고객 정보, 비공개 문서를 다른 도구로 전달하지 않습니다.&lt;/li&gt;
&lt;li&gt;도구가 예상하지 못한 외부 주소나 추가 권한을 요구하면 중단하고 확인합니다.&lt;/li&gt;
&lt;li&gt;대량 변경 전에는 대상 목록과 변경 내용을 별도 산출물로 검토합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;자주 발생하는 문제를 해결합니다&lt;/h2&gt;
&lt;h3&gt;&lt;code&gt;/mcp&lt;/code&gt;에서 인증이 시작되지 않습니다&lt;/h3&gt;
&lt;p&gt;먼저 &lt;code&gt;claude mcp list&lt;/code&gt;로 서버 주소가 &lt;code&gt;/v1/mcp/authv2&lt;/code&gt;인지 확인합니다. 오래된 &lt;code&gt;/v1/sse&lt;/code&gt; 설정이 남아 있다면 제거 또는 교체 절차를 현재 Claude Code 문서에 맞춰 진행합니다. 회사 브라우저가 팝업이나 로컬 콜백을 차단하는지도 확인합니다.&lt;/p&gt;
&lt;h3&gt;OAuth는 끝났지만 도구 호출이 거부됩니다&lt;/h3&gt;
&lt;p&gt;사용자의 Jira 프로젝트 권한, 조직의 Rovo MCP Read·Search·Write 설정, 허용 도메인, IP 허용 목록을 차례로 확인합니다. OAuth 성공은 모든 제품과 작업 권한이 허용되었다는 뜻이 아닙니다.&lt;/p&gt;
&lt;h3&gt;다른 Atlassian 사이트의 데이터가 보입니다&lt;/h3&gt;
&lt;p&gt;여러 Atlassian 사이트에 접근할 수 있는 계정이라면 요청에 사이트 이름이나 URL과 프로젝트 키를 함께 명시합니다. 첫 조회에서 접근 가능한 사이트 목록을 확인하고 대상 &lt;code&gt;cloudId&lt;/code&gt; 또는 사이트가 무엇인지 검증합니다.&lt;/p&gt;
&lt;h3&gt;쓰기 도구가 보이지 않습니다&lt;/h3&gt;
&lt;p&gt;조직 관리자가 Write 권한을 차단했거나 사용자의 Jira 권한이 부족할 수 있습니다. 보안 설정을 우회하지 말고 업무 목적과 필요한 최소 작업을 정리해 관리자에게 요청합니다.&lt;/p&gt;
&lt;h2&gt;운영 전에 마지막으로 확인합니다&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 공식 시작 문서에서 서버 주소와 명령을 다시 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 업무에 필요한 최소 권한의 계정으로 OAuth 인증을 진행합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 첫 테스트는 사이트 목록과 단일 이슈 조회로 제한합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 관리자 설정에서 제품별 Read·Search·Write 범위를 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 허용 도메인과 IP 허용 목록을 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 쓰기 기능은 테스트 프로젝트에서 먼저 검증합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 대량 변경과 고위험 변경은 사람의 실행 전 승인을 받습니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 감사 로그와 Jira 변경 기록을 정기적으로 검토합니다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&gt; 퇴사·역할 변경·프로젝트 종료 시 연결과 토큰을 폐기합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;연결한 뒤가 더 중요합니다&lt;/h2&gt;
&lt;p&gt;Claude Code와 Atlassian Rovo MCP를 연결하는 명령은 간단하지만, 안전한 운영은 인증 이후에 결정됩니다. 현재는 Streamable HTTP와 &lt;code&gt;/v1/mcp/authv2&lt;/code&gt;를 사용하고, 대화형 사용에는 OAuth 2.1을 우선합니다. 사용자의 기존 권한과 조직의 Rovo MCP 정책을 함께 최소화하며, 조회에서 시작해 쓰기로 단계적으로 확대합니다.&lt;/p&gt;
&lt;p&gt;특히 MCP가 읽는 Jira와 Confluence 내용도 신뢰할 수 없는 입력일 수 있다는 점을 기억합니다. 대상과 변경 범위를 명시하고, 실행 계획을 먼저 검토하며, 고위험 작업은 사람이 최종 확인하는 절차를 유지합니다.&lt;/p&gt;
&lt;h2&gt;공식 참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://support.atlassian.com/atlassian-rovo-mcp-server/docs/getting-started-with-the-atlassian-remote-mcp-server/&quot;&gt;Getting started with the Atlassian Rovo MCP Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.atlassian.com/atlassian-rovo-mcp-server/docs/authentication-and-authorization/&quot;&gt;Authentication and authorization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.atlassian.com/security-and-access-policies/docs/Configure-Atlassian-Rovo-MCP-server-permission/&quot;&gt;Configure Atlassian Rovo MCP server permission&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.atlassian.com/security-and-access-policies/docs/understand-atlassian-rovo-mcp-server/&quot;&gt;Understand Atlassian Rovo MCP server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.atlassian.com/atlassian-rovo-mcp-server/docs/supported-tools/&quot;&gt;Supported tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.atlassian.com/blog/company-news/mcp-risk-awareness&quot;&gt;MCP Clients: Understanding the potential security risks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>업무 자동화</category>
      <category>Atlassian</category>
      <category>claude code</category>
      <category>jira</category>
      <category>MCP</category>
      <category>oAuth</category>
      <author>세모난 야구공</author>
      <guid isPermaLink="true">https://olt13mm.tistory.com/2200</guid>
      <comments>https://olt13mm.tistory.com/2200#entry2200comment</comments>
      <pubDate>Sat, 13 Dec 2025 14:08:00 +0900</pubDate>
    </item>
    <item>
      <title>Git Worktree 실전 운영 가이드</title>
      <link>https://olt13mm.tistory.com/2198</link>
      <description>&lt;p&gt;기능 개발 중 긴 테스트를 실행하면서 다른 브랜치의 코드를 검토하거나, 작업 도중 긴급 수정이 들어오면 브랜치 전환 때문에 흐름이 끊깁니다. &lt;code&gt;git worktree&lt;/code&gt;를 사용하면 하나의 저장소에 연결된 작업 디렉터리를 여러 개 만들어 서로 다른 브랜치를 동시에 체크아웃할 수 있습니다.&lt;/p&gt;
&lt;p&gt;Worktree마다 작업 파일과 &lt;code&gt;HEAD&lt;/code&gt;가 분리되지만 Git 객체와 저장소 메타데이터의 일부는 공유합니다. 따라서 저장소를 여러 번 clone하는 것보다 디스크를 덜 쓰면서 병렬 작업 환경을 만들 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEBD6X/dJMcaazSAt5/m9VxLcfGu8yRrhPkO2Jbf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEBD6X/dJMcaazSAt5/m9VxLcfGu8yRrhPkO2Jbf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEBD6X/dJMcaazSAt5/m9VxLcfGu8yRrhPkO2Jbf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEBD6X%2FdJMcaazSAt5%2Fm9VxLcfGu8yRrhPkO2Jbf1%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; alt=&quot;Git 객체를 공유하면서 브랜치별 작업 디렉터리와 HEAD를 분리하는 Worktree 구조&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;520&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;기본 개념&lt;/h2&gt;
&lt;p&gt;처음 clone하거나 &lt;code&gt;git init&lt;/code&gt;한 디렉터리를 main worktree라고 합니다. &lt;code&gt;git worktree add&lt;/code&gt;로 만든 디렉터리는 linked worktree입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;project/                   main worktree
├── .git/
└── src/

worktrees/
├── feature-login/         linked worktree
└── hotfix-payment/        linked worktree&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;각 디렉터리는 독립된 작업 파일과 인덱스를 가지므로 한쪽의 미커밋 수정이 다른 쪽 파일을 바꾸지 않습니다. 다만 같은 저장소의 브랜치, 태그, 커밋 객체, 원격 설정을 공유한다는 점은 기억해야 합니다.&lt;/p&gt;
&lt;h2&gt;기존 브랜치로 Worktree 만들기&lt;/h2&gt;
&lt;p&gt;먼저 현재 연결 상태를 확인합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git worktree list&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이미 존재하는 &lt;code&gt;feature/login&lt;/code&gt; 브랜치를 별도 디렉터리에 체크아웃합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git worktree add ../worktrees/feature-login feature/login&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;생성된 디렉터리로 이동하면 바로 해당 브랜치에서 작업할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd ../worktrees/feature-login
git status&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;새 브랜치를 만들면서 추가하기&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;git fetch origin
git worktree add -b hotfix/payment ../worktrees/hotfix-payment origin/main&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 명령은 &lt;code&gt;origin/main&lt;/code&gt;을 시작점으로 &lt;code&gt;hotfix/payment&lt;/code&gt; 로컬 브랜치를 만들고 새 worktree에 체크아웃합니다.&lt;/p&gt;
&lt;p&gt;원격 브랜치를 추적하는 로컬 브랜치가 이미 있다면 &lt;code&gt;-b&lt;/code&gt;를 다시 사용하면 안 됩니다. 브랜치 존재 여부를 먼저 확인합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git branch --all --list &amp;#39;*hotfix/payment*&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;코드 리뷰용 detached Worktree&lt;/h2&gt;
&lt;p&gt;코드를 수정하거나 브랜치를 유지할 필요 없이 특정 커밋을 확인하려면 detached 상태로 만들 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git fetch origin
git worktree add --detach ../worktrees/review-123 origin/pull/123/head&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;호스팅 서비스마다 pull request 참조 이름이 다를 수 있으므로 실제 ref를 확인해야 합니다. 단순히 특정 커밋 SHA를 전달할 수도 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git worktree add --detach ../worktrees/review-a1b2c3 a1b2c3&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;detached HEAD에서 만든 커밋은 브랜치가 자동으로 가리키지 않습니다. 수정 내용을 남겨야 한다면 커밋 전에 새 브랜치를 만듭니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git switch -c review/fix&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;같은 브랜치를 동시에 체크아웃할 수 없는 이유&lt;/h2&gt;
&lt;p&gt;Git은 일반적으로 동일 브랜치가 여러 worktree에서 동시에 체크아웃되는 것을 막습니다. 두 디렉터리에서 같은 브랜치 포인터와 인덱스를 독립적으로 변경하면 작업 상태를 이해하기 어려워지기 때문입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fatal: &amp;#39;feature/login&amp;#39; is already checked out at &amp;#39;.../feature-login&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 오류가 나오면 해당 브랜치를 이미 사용하는 worktree를 찾습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git worktree list --porcelain&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;다른 브랜치를 만들거나 기존 worktree에서 작업해야 합니다. 강제 옵션으로 보호 장치를 우회하기보다 작업 구조를 바로잡는 편이 안전합니다.&lt;/p&gt;
&lt;h2&gt;Worktree 제거하기&lt;/h2&gt;
&lt;p&gt;먼저 수정 상태를 확인합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git -C ../worktrees/feature-login status --short&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;필요한 변경을 커밋하거나 별도로 보존한 뒤 저장소 명령으로 제거합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git worktree remove ../worktrees/feature-login&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;파일 탐색기나 &lt;code&gt;rm -rf&lt;/code&gt;로 디렉터리만 지우면 Git의 worktree 관리 정보가 남을 수 있습니다. 디렉터리를 이미 수동 삭제했다면 다음 명령으로 유효하지 않은 관리 정보를 정리합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git worktree prune --dry-run
git worktree prune&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;--dry-run&lt;/code&gt;으로 제거 대상을 먼저 확인하는 것이 좋습니다.&lt;/p&gt;
&lt;h2&gt;이동과 잠금&lt;/h2&gt;
&lt;p&gt;Worktree 경로를 Git 명령으로 이동할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git worktree move ../worktrees/feature-login ../worktrees/feature-auth&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;외장 디스크처럼 일시적으로 접근할 수 없는 경로는 자동 정리 대상이 되지 않도록 잠글 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git worktree lock --reason &amp;quot;external SSD&amp;quot; ../external/project-review
git worktree unlock ../external/project-review&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;잠금은 작업 파일을 암호화하거나 다른 사용자의 접근을 막는 보안 기능이 아니라, Git의 관리 정보가 prune되는 것을 막는 표시입니다.&lt;/p&gt;
&lt;h2&gt;의존성과 빌드 결과는 별도다&lt;/h2&gt;
&lt;p&gt;Git 객체를 공유해도 &lt;code&gt;node_modules&lt;/code&gt;, 빌드 결과, 로컬 DB, 환경변수 파일은 worktree별 작업 디렉터리에 존재합니다. 다음 문제를 미리 고려해야 합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;worktree마다 의존성을 설치할 것인가?&lt;/li&gt;
&lt;li&gt;패키지 매니저의 전역 저장소를 공유할 수 있는가?&lt;/li&gt;
&lt;li&gt;동일 포트를 사용하는 개발 서버를 동시에 띄우는가?&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.env&lt;/code&gt;와 로컬 인증 정보가 각 브랜치에 맞는가?&lt;/li&gt;
&lt;li&gt;Docker Compose 프로젝트 이름과 볼륨이 충돌하지 않는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예를 들어 두 Spring Boot 애플리케이션을 동시에 실행하면 Git과 무관하게 8080 포트 충돌이 발생할 수 있습니다. 각 worktree의 개발 포트를 다르게 설정하거나 한 번에 하나만 실행해야 합니다.&lt;/p&gt;
&lt;h2&gt;안전한 운영 규칙&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;worktree는 저장소 밖의 전용 상위 폴더에 둡니다.&lt;/li&gt;
&lt;li&gt;폴더명과 브랜치명의 대응 규칙을 팀에서 통일합니다.&lt;/li&gt;
&lt;li&gt;제거 전 &lt;code&gt;git status --short&lt;/code&gt;로 미커밋 변경을 확인합니다.&lt;/li&gt;
&lt;li&gt;디렉터리 직접 삭제 대신 &lt;code&gt;git worktree remove&lt;/code&gt;를 사용합니다.&lt;/li&gt;
&lt;li&gt;자동화 스크립트는 경로를 명시적으로 검증하고 광범위한 삭제 명령을 피합니다.&lt;/li&gt;
&lt;li&gt;정기적으로 &lt;code&gt;git worktree list&lt;/code&gt;로 사용하지 않는 환경을 확인합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Worktree와 clone을 선택하는 기준&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&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;worktree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;커밋 객체 중복 저장을 줄이고 싶음&lt;/td&gt;
&lt;td&gt;worktree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;완전히 다른 Git 설정·remote·객체 수명 필요&lt;/td&gt;
&lt;td&gt;별도 clone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;저장소 손상이나 실수의 영향도 분리 필요&lt;/td&gt;
&lt;td&gt;별도 clone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI처럼 매번 깨끗한 독립 환경 필요&lt;/td&gt;
&lt;td&gt;별도 clone 또는 임시 checkout&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Git Worktree는 단순히 브랜치를 여러 창에 열어두는 기능이 아닙니다. 작업 파일과 인덱스는 분리하면서 커밋 객체는 공유하는 구조입니다. 이 경계를 이해하고 생성·제거 명령을 사용하면 긴 테스트, 코드 리뷰, 긴급 수정 사이를 오갈 때 불필요한 stash와 중간 커밋을 줄일 수 있습니다.&lt;/p&gt;
&lt;h2&gt;공식 참고자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-worktree&quot;&gt;Git: git-worktree&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/gitrepository-layout#Documentation/gitrepository-layout.txt-worktrees&quot;&gt;Git: Worktrees&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발 도구</category>
      <category>git worktree</category>
      <category>worktree add</category>
      <category>worktree 제거</category>
      <category>브랜치 동시 작업</category>
      <author>세모난 야구공</author>
      <guid isPermaLink="true">https://olt13mm.tistory.com/2198</guid>
      <comments>https://olt13mm.tistory.com/2198#entry2198comment</comments>
      <pubDate>Sun, 30 Nov 2025 23:05:48 +0900</pubDate>
    </item>
    <item>
      <title>JavaScript Map과 WeakMap의 차이와 사용 기준</title>
      <link>https://olt13mm.tistory.com/2190</link>
      <description>&lt;p&gt;객체에 부가 정보를 붙일 때 &lt;code&gt;Map&lt;/code&gt;과 &lt;code&gt;WeakMap&lt;/code&gt; 중 무엇을 골라야 하는지 궁금해 두 자료구조의 참조 방식과 확인 가능한 동작을 비교했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wX5ob/dJMcahyXCsb/feOFpZHsnjKH2kOKZzRQDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wX5ob/dJMcahyXCsb/feOFpZHsnjKH2kOKZzRQDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wX5ob/dJMcahyXCsb/feOFpZHsnjKH2kOKZzRQDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwX5ob%2FdJMcahyXCsb%2FfeOFpZHsnjKH2kOKZzRQDK%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; alt=&quot;Map은 다양한 키를 강하게 참조하고 순회할 수 있지만 WeakMap은 객체 키를 약하게 참조하며 순회할 수 없는 차이&quot; loading=&quot;lazy&quot; width=&quot;1000&quot; height=&quot;500&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3&gt;0. 공통점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;둘 다 &lt;strong&gt;객체(Object)&lt;/strong&gt; 처럼 key-value 쌍으로 데이터를 저장합니다.&lt;/li&gt;
&lt;li&gt;둘 다 객체를 key로 사용할 수 있습니다. &lt;code&gt;Map&lt;/code&gt;은 원시값도 허용하지만 &lt;code&gt;WeakMap&lt;/code&gt;의 키는 객체 또는 등록되지 않은 Symbol로 제한됩니다.&lt;/li&gt;
&lt;li&gt;둘 다 get, set, delete, has 메서드를 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;1. Map의 특징&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Map&lt;/code&gt;은 ES6에서 추가된 자료구조로, 기존 객체(Object)의 한계를 보완하기 위해 만들어졌습니다.&lt;/p&gt;
&lt;p&gt;객체는 키로 문자열이나 심볼만 사용할 수 있지만, &lt;code&gt;Map&lt;/code&gt;은 모든 값을 키로 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;즉, 객체, 함수, 숫자, 문자열 등 어떤 타입이든 키로 활용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const map = new Map();
const objKey = { id: 1 };

map.set(objKey, &amp;#39;User&amp;#39;);
map.set(&amp;#39;role&amp;#39;, &amp;#39;Admin&amp;#39;);

console.log(map.get(objKey)); // &amp;#39;User&amp;#39;
console.log(map.get(&amp;#39;role&amp;#39;)); // &amp;#39;Admin&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;주요 특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;키로 모든 자료형을 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;삽입 순서를 유지하며, 순회가 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;map.size&lt;/code&gt;로 크기를 확인할 수 있습니다.&lt;/li&gt;
&lt;li&gt;내부적으로 &lt;strong&gt;강한 참조(strong reference)&lt;/strong&gt; 를 유지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;성능을 O(1)로 단정하면 안 되는 이유&lt;/h3&gt;
&lt;p&gt;ECMAScript 사양은 &lt;code&gt;Map&lt;/code&gt;이 평균적으로 컬렉션 요소 수에 대해 sublinear한 접근 시간을 제공하도록 요구하지만 특정 해시 테이블 구현이나 항상 O(1)인 성능을 강제하지는 않습니다. 엔진은 해시 테이블이나 트리 등 요구조건을 만족하는 다른 구조를 사용할 수 있습니다. 실제 성능이 중요하면 대상 런타임과 데이터로 측정해야 합니다.&lt;/p&gt;
&lt;p&gt;엔진은 키에 대한 강한 참조를 유지하기 때문에, 키로 사용된 객체가 외부에서 제거되어도 &lt;code&gt;Map&lt;/code&gt; 내부에는 남아 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let obj = { id: 1 };
const map = new Map();
map.set(obj, &amp;#39;data&amp;#39;);

obj = null;
console.log(map.size); // 1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 경우 &lt;code&gt;obj&lt;/code&gt; 변수의 참조는 사라졌지만 &lt;code&gt;Map&lt;/code&gt;이 같은 객체를 키로 계속 참조합니다. 해당 항목이 더 필요하지 않다면 &lt;code&gt;delete()&lt;/code&gt;나 &lt;code&gt;clear()&lt;/code&gt;로 제거해야 합니다. 다만 &lt;code&gt;Map&lt;/code&gt;을 쓴다는 이유만으로 메모리 누수가 생기는 것은 아닙니다. 항목의 수명과 제거 시점을 정하지 않은 채 계속 추가할 때 문제가 됩니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. WeakMap의 개념과 특징&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;WeakMap&lt;/code&gt;은 키 객체를 강하게 붙잡지 않습니다. 키 객체를 가리키는 다른 참조가 사라지면 해당 객체와 연결된 항목도 나중에 가비지 컬렉션 대상이 될 수 있습니다. 수집 시점은 엔진이 결정하므로 코드에서 그 시점을 관찰하거나 즉시 정리됐다고 가정해서는 안 됩니다. 이런 성질은 특정 객체의 수명에 맞춰 부가 정보를 보관할 때 유용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let obj = { id: 1 };
const weakMap = new WeakMap();

weakMap.set(obj, &amp;#39;User&amp;#39;);
obj = null; // 외부 참조 제거
// 이후 GC가 발생하면 자동으로 해제됩니다.&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;주요 특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;키는 &lt;strong&gt;객체 또는 등록되지 않은 Symbol&lt;/strong&gt;이어야 합니다. 문자열·숫자·&lt;code&gt;Symbol.for()&lt;/code&gt;로 만든 등록 Symbol은 사용할 수 없습니다.&lt;/li&gt;
&lt;li&gt;순회(&lt;code&gt;forEach&lt;/code&gt;, &lt;code&gt;for...of&lt;/code&gt;)가 불가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;size&lt;/code&gt; 속성이 없습니다.&lt;/li&gt;
&lt;li&gt;약한 참조를 사용하기 때문에 GC가 자유롭게 객체를 해제할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;GC와 ephemeron 관계&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;WeakMap&lt;/code&gt;을 단순히 내부에 &lt;code&gt;WeakRef&lt;/code&gt;를 저장한 구조라고 설명하면 정확하지 않습니다. 핵심은 키에 대한 도달 가능성이 값에서 다시 키로 이어지는 순환까지 고려하는 ephemeron 의미입니다. 애플리케이션은 이 내부 방식을 전제로 코드를 작성하지 말고, 키가 다른 곳에서 도달 불가능해지면 해당 항목이 GC를 방해하지 않는다는 보장만 사용해야 합니다.&lt;/p&gt;
&lt;p&gt;GC는 키 객체가 다른 곳에서 더 이상 도달 가능하지 않다고 판단하면 WeakMap의 연결된 값도 회수할 수 있습니다.&lt;/p&gt;
&lt;p&gt;정확한 GC 시점은 정해져 있지 않으며 개발자가 WeakMap을 통해 감지할 수 없습니다.&lt;/p&gt;
&lt;p&gt;이 때문에 WeakMap은 순회나 크기 확인 기능을 지원하지 않습니다.&lt;/p&gt;
&lt;p&gt;GC 타이밍에 따라 데이터가 갑자기 사라질 수 있기 때문입니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. Map과 WeakMap 비교&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;Map&lt;/th&gt;
&lt;th&gt;WeakMap&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;객체 또는 등록되지 않은 Symbol&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;GC 관여&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;code&gt;for...of&lt;/code&gt; , &lt;code&gt;forEach&lt;/code&gt; )&lt;/td&gt;
&lt;td&gt;불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;size 속성&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;객체 기반 임시 데이터, 캐시, private 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;Map&lt;/code&gt;은 명시적인 데이터 관리가 필요한 경우에 적합하며,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;WeakMap&lt;/code&gt;은 객체의 생명주기와 데이터의 수명을 일치시킬 때 적합합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. 사용 시점과 예시&lt;/h2&gt;
&lt;h3&gt;Map을 사용하는 경우&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;명확하게 관리되는 데이터가 필요할 때&lt;/li&gt;
&lt;li&gt;원시값을 키로 사용할 때&lt;/li&gt;
&lt;li&gt;순회, 크기 확인, 로깅 등이 필요한 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;const routeMap = new Map([
  [&amp;#39;/home&amp;#39;, &amp;#39;HomePage&amp;#39;],
  [&amp;#39;/login&amp;#39;, &amp;#39;LoginPage&amp;#39;],
]);

for (const [path, page] of routeMap) {
  console.log(`${path} -&amp;gt; ${page}`);
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 경우 &lt;code&gt;Map&lt;/code&gt;은 일반적인 키-값 매핑 구조로 동작하며,&lt;/p&gt;
&lt;p&gt;순회가 가능하고 데이터 크기를 쉽게 확인할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;WeakMap을 사용하는 경우&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;객체의 생명주기와 함께 데이터가 사라져야 할 때&lt;/li&gt;
&lt;li&gt;외부 객체에 메타데이터를 연결하되 수명을 늘리고 싶지 않을 때&lt;/li&gt;
&lt;li&gt;메모리 누수를 방지해야 할 때&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;const cache = new WeakMap();

function getUserData(user) {
  if (!cache.has(user)) {
    cache.set(user, fetchFromDB(user.id));
  }
  return cache.get(user);
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;위 예제에서 &lt;code&gt;user&lt;/code&gt; 객체를 가리키는 다른 강한 참조가 모두 사라지면, 해당 객체와 연결된 항목은 가비지 컬렉션 대상이 될 수 있습니다. 언제 수집되는지는 엔진이 결정합니다.&lt;/p&gt;
&lt;p&gt;이 패턴은 동일한 &lt;code&gt;user&lt;/code&gt; 객체 인스턴스를 다시 전달할 때만 같은 캐시를 찾습니다. 같은 ID를 가진 새 객체는 다른 키입니다. 또한 TTL, 최대 크기, 전체 무효화, 캐시 통계가 필요하면 순회할 수 없는 WeakMap보다 명시적으로 관리하는 Map이나 전용 캐시가 적합합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. 메모리와 사용 목적을 함께 보고 선택합니다&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Map&lt;/code&gt;은 모든 종류의 키를 저장할 수 있고 항목을 순회하거나 개수를 확인할 수 있습니다. 설정값, 라우팅 테이블처럼 항목의 목록과 수명을 코드에서 관리해야 하는 데이터에 알맞습니다. 사용이 끝난 항목은 &lt;code&gt;delete()&lt;/code&gt;로 지우고 전체를 비울 때는 &lt;code&gt;clear()&lt;/code&gt;를 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;WeakMap&lt;/code&gt;은 객체 또는 등록되지 않은 심볼만 키로 받고 항목을 순회할 수 없습니다. 외부에서 받은 DOM 노드나 요청 객체에 부가 정보를 연결하면서, 그 키 때문에 객체의 수명이 늘어나지 않게 하려는 경우에 검토합니다. 성능이 더 빠르기 때문이 아니라 참조 방식이 사용 목적과 맞기 때문에 선택하는 자료구조입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Express 요청별 캐시 예시
const requestCache = new WeakMap();

function middleware(req, res, next) {
  if (!requestCache.has(req)) {
    requestCache.set(req, new Map());
  }
  next();
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;요청 처리가 끝났다는 사실만으로 &lt;code&gt;req&lt;/code&gt; 객체가 즉시 사라지는 것은 아닙니다. 요청을 보관하는 로그, 비동기 작업, 이벤트 리스너처럼 다른 강한 참조가 없어야 수집 대상이 될 수 있습니다. 이 조건을 충족하더라도 실제 수집 시점은 알 수 없습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Map&lt;/th&gt;
&lt;th&gt;WeakMap&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;객체 또는 등록되지 않은 Symbol&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;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;캐시, private 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;항목 제거&lt;/td&gt;
&lt;td&gt;&lt;code&gt;delete()&lt;/code&gt;와 &lt;code&gt;clear()&lt;/code&gt;를 사용할 수 있음&lt;/td&gt;
&lt;td&gt;키 객체의 수명에 연결되며 &lt;code&gt;clear()&lt;/code&gt;를 제공하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;선택할 때는 키의 종류, 항목 순회 여부, 키를 누가 얼마나 오래 참조하는지를 함께 확인합니다.&lt;/p&gt;
&lt;h2&gt;직접 확인할 수 있는 것과 없는 것&lt;/h2&gt;
&lt;p&gt;다음 코드는 API 차이는 확인할 수 있지만 GC가 정확히 언제 일어나는지는 증명하지 못합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const map = new Map();
const weakMap = new WeakMap();
const key = {};

map.set(key, &amp;#39;map value&amp;#39;);
weakMap.set(key, &amp;#39;weak value&amp;#39;);

console.log(map.size);       // 1
console.log([...map]);       // [[key, &amp;#39;map value&amp;#39;]]
console.log(weakMap.has(key)); // true

// weakMap.size       // undefined
// [...weakMap]       // TypeError: not iterable&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;개발자 도구의 강제 GC나 메모리 사용량 한 번만으로 WeakMap 항목 제거를 단정하지 않는 것이 좋습니다. 엔진 최적화와 다른 참조가 결과에 영향을 줄 수 있습니다.&lt;/p&gt;
&lt;h2&gt;공식 참고자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map&quot;&gt;MDN: Map&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap&quot;&gt;MDN: WeakMap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tc39.es/ecma262/multipage/keyed-collections.html&quot;&gt;ECMAScript: Keyed Collections&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>JavaScript&amp;middot;React&amp;middot;웹</category>
      <category>javascript map</category>
      <category>Map WeakMap 차이</category>
      <category>WeakMap</category>
      <category>약한 참조 가비지 컬렉션</category>
      <author>세모난 야구공</author>
      <guid isPermaLink="true">https://olt13mm.tistory.com/2190</guid>
      <comments>https://olt13mm.tistory.com/2190#entry2190comment</comments>
      <pubDate>Mon, 13 Oct 2025 22:57:33 +0900</pubDate>
    </item>
  </channel>
</rss>