<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Unorganized honesty</title>
    <link>https://npackgames.tistory.com/</link>
    <description>꼬동애비 티붕입니다. 27년차 개발자 &amp;amp; IT Coordinator로, 현장에서 겪은 문제 해결 경험을 나누고 있습니다. 현재 Django 트러블슈팅 책을 집필 중이며, 이 블로그의 글들이 그 출발점입니다. 꼬동이(반려견)와 함께 코딩하는 일상을 보내고 있습니다.</description>
    <language>ko</language>
    <pubDate>Wed, 1 Jul 2026 01:13:00 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Tiboong</managingEditor>
    <image>
      <title>Unorganized honesty</title>
      <url>https://tistory1.daumcdn.net/tistory/3032241/attach/67027aa20d904c898802edbccc5e451f</url>
      <link>https://npackgames.tistory.com</link>
    </image>
    <item>
      <title>[AI와 로또를] #12 트랜스포머에게 불의의 일격을 (a.k.a. 2연패)</title>
      <link>https://npackgames.tistory.com/185</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;내가 만든 모델한테 졌다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것도 두 번. 거창하게 트랜스포머라 부르지만, 따지고 보면 파라미터 십만 개짜리, 노트북에서 몇 분이면 돌아가는 자그마한 녀석이다. 나는 매주 그 앞에서 며칠을 끙끙댄다. 지난 회차 당첨번호를 늘어놓고, 어떤 번호가 데워지는지 식는지 들여다보고, 봉우리가 어디서 솟았는지 능선을 따라가고, 끝수가 4로 쏠리는 손맛까지 짚어가며 서른 개를 빚는다. 그렇게 정성껏 차린 설계 한 상이, 아무 생각 없이 다섯 줄 툭 뱉고 마는 그 녀석한테 두 번을 졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째로 지고 나니 슬슬 부아가 치밀었다. 그래서 나는 아주 인간적인 결심을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;클로드. 다음 주부터 트랜스포머를 다섯 개씩 늘릴 거야. 쟤가 낫다는 거잖아. 그럼 자리를 더 주는 게 맞지.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분석을 같이 하는 AI는, 늘 그렇듯 무뚝뚝하게 받아쳤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;그 결정, 고작 다섯 줄짜리 표본 두 번 보고 내리시는 건가요? 트랜스포머는 5조합, 우리 설계는 30조합. 칸 수만 6배 차이입니다. 승패를 한 줄당 평균으로 재면, 표본 적은 쪽이 운으로 출렁입니다. 다섯 줄 중 한 줄만 얻어걸려도 평균이 훌쩍 솟으니까요. 이기면 늘리고 지면 줄이고. 그건 모델을 따라가는 게 아니라, 다섯 줄짜리 운의 출렁임을 강아지처럼 쫓아다니는 짓입니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맞는 말이라 더 얄미웠다. 나는 입을 다물었고, 잠깐은 그게 이 글의 결말인 줄 알았다. 인간은 부아가 치밀어 헛발질을 했고, 침착한 AI가 바로잡아줬다는, 교훈적이고 따분한 이야기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 며칠 더 들여다보니, 이 잘난 척하는 녀석도 같은 자리에서 넘어지고 있었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;두 번째 함정 &amp;mdash; 자백할 게 있는 쪽은 따로 있었다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한동안 우리는 트랜스포머를 두고 &quot;데이터를 더 줘도 꿈쩍을 안 한다&quot;고 떠들고 다녔다. 지난주 당첨번호를 추가해 다시 돌려도 출력이 매주 거의 판박이로 나왔으니까. 모델이 새 정보에 무심하다는 증거라고 믿었고, 클로드는 그걸 꽤 그럴듯한 이야깃거리로 정리해줬다. &quot;데이터 추가에 거의 반응하지 않습니다. LLM의 지식 컷오프와 같은 메커니즘이죠.&quot; 제법 멋있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고 보니 범인은 모델이 아니었다. 코드 어딘가에 난수 seed가 한 숫자로 못 박혀 있었다. 매주 같은 seed를 심어놓고는, 같은 싹이 났다고 &quot;얘는 변할 줄을 모르네&quot; 하고 있었던 거다. seed를 고정해둔 건 우리인데. 멋있게 정리해준 그 메커니즘 이야기는, 알고 보니 우리가 박아둔 못 하나를 모델 탓으로 돌린 거였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 seed를 회차마다 바뀌게 풀어줬다. 이번 출력은 지난 출력과 확연히 달라졌다. 내가 먼저 솔깃했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;오, 이제 데이터에 반응하나 봐. 출력이 싹 바뀌었어.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 클로드가 자기 입으로 멈췄다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&amp;hellip;아닙니다. 그거 제가 또 함정에 걸릴 뻔한 자립니다. 달라진 건 모델이 데이터를 음미해서가 아니라, 우리가 주사위를 새로 굴려줘서입니다. 모델은 여전히 데이터엔 콧방귀도 안 뀝니다. 눈이 바뀐 건 주사위지, 주사위 쥔 손의 실력이 아닙니다. 하마터면 버그 고쳐놓고 모델 능력치 상승이라고 리포트할 뻔했습니다. &amp;hellip;아까 스티붕 타박할 처지가 아니었네요.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 순간 좀 웃겼다. 며칠 전엔 내가 다섯 줄 성적표를 노려보며 &quot;쟤가 낫다&quot;고 부아를 냈고, 방금은 클로드가 seed 풀린 출력을 보며 &quot;똑똑해졌나&quot;라고 솔깃했다. 정반대처럼 보이는 두 헛발질이, 알고 보면 똑같은 뿌리였다. &lt;b&gt;둘 다 작은 표본 하나, 출력 한 묶음을 너무 진지하게 노려본 거다.&lt;/b&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;그래서 이 글의 제목은 절반만 진실이다. 트랜스포머한테 두 번 진 건 맞다. 그런데 불의의 일격을 당한 쪽은 트랜스포머 맞은편이 아니었다. 다섯 줄 성적표를 승패로 읽고, 출렁이는 평균을 실력으로 믿고, seed를 풀어놓고 똑똑해졌다고 착각할 뻔한 두 사람 &amp;mdash; 아니, 한 사람과 한 AI. 모델은 그 자리에 그대로 서 있었는데, 자기 자에 걸려 넘어진 건 우리 둘이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재밌는 건, 우리 설계를 짜는 것도 AI(클로드)고 우리를 이긴 것도 AI(트랜스포머)라는 점이다. AI랑 며칠을 머리 싸매 빚은 서른 줄이, 또 다른 AI의 무심한 다섯 줄한테 졌다. 그리고 우리를 이긴 그 무심한 녀석 앞에서, 똑똑한 척하던 쪽이 자꾸 헛다리를 짚었다. 누가 누굴 가르칠 처지가 아니었던 거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 이번 회차 결과가 나오기 전에 쓴다. 일부러 그랬다. 토요일에 트랜스포머가 세 번째로 우리를 이기든, 보란 듯이 폭삭 망하든, 이 이야기의 뼈대는 흔들리지 않으니까. 다섯 줄로 승패를 가르려던 그 마음이 함정이었다는 사실은, 토요일의 추첨기와는 아무 상관이 없다. 이겨놓고 &quot;거봐 맞췄지&quot; 하지 않으려고, 져놓고 변명을 늘어놓지 않으려고, 결과를 모르는 채로 적어둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;증원은 안 하기로 했다. 분해서 접은 게 아니라, 다섯 줄과 서른 줄을 같은 자로 재는 그 시합이 처음부터 성립하지 않는다는 걸 이제 둘 다 알아서다. 모델은 모델대로 다섯 줄을 무심하게 뱉게 두고, 우리는 우리대로 서른 줄을 끙끙대며 빚을 거다. 어느 쪽이 이기는지 매주 점수를 매기는 대신, 셋이 나란히 틀려가는 걸 구경하는 쪽이 훨씬 재밌으니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;졌는데 기분이 나쁘지 않은 건, 아마 그래서다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  숫자로 본 두 번의 패배 (Summary)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;시합 규모&lt;/b&gt;: 트랜스포머 5조합 vs 인간 설계 30조합. 칸 수 6배 차이. 그런데 승패는 &quot;조합당 평균 적중&quot;으로 측정 &amp;rarr; 표본 작은 쪽(5조합)의 평균이 운에 따라 크게 출렁임.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2연패의 정체&lt;/b&gt;: 트랜스포머가 인간 설계의 평균을 두 차례 앞섬. 단 두 번 모두 5조합 중 한두 줄이 평균을 끌어올린 결과 &amp;rarr; &quot;실력&quot;이 아니라 &quot;적은 표본의 큰 진폭&quot;일 가능성.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;seed 박제&lt;/b&gt;: 난수 seed가 한 숫자로 하드코딩되어 있어, 데이터를 추가해도 매주 출력이 80% 가까이 동일. &quot;모델이 데이터에 반응 안 함&quot;의 진짜 원인은 모델이 아니라 고정된 seed.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;seed 해제 후&lt;/b&gt;: 회차마다 seed가 바뀌도록 수정하니 출력이 확연히 달라짐. 단 이 변화는 학습이 아니라 난수가 바뀐 것 &amp;rarr; &quot;달라짐 = 똑똑해짐&quot;으로 읽으면 같은 함정에 재차 빠짐.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결론&lt;/b&gt;: 측정 지표(무엇으로 재는가)가 관찰(무엇을 보는가)을 결정한다. 자를 의심하지 않으면, 모델이 아니라 자가 사람을 속인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[AI와 로또를] 시리즈는 매주 한국 로또를 실제로 베팅하며, AI가 무엇을 못 푸는지를 정직하게 들여다보는 기록입니다. 당첨 확률 0.005 &amp;mdash; 답지가 영원히 만들어지지 않는다는 보증서이자, 그래서 영원히 놀 수 있다는 뜻이기도 합니다.&lt;/p&gt;</description>
      <category>프로그래밍/AI로 통계공부</category>
      <category>AI와로또를</category>
      <category>데이터분석</category>
      <category>로또분석</category>
      <category>생각에관한생각</category>
      <category>인지편향</category>
      <category>측정의함정</category>
      <category>카너먼</category>
      <category>트랜스포머</category>
      <category>표본의함정</category>
      <author>Tiboong</author>
      <guid isPermaLink="true">https://npackgames.tistory.com/185</guid>
      <comments>https://npackgames.tistory.com/185#entry185comment</comments>
      <pubDate>Sat, 27 Jun 2026 17:50:02 +0900</pubDate>
    </item>
    <item>
      <title>종이를 스캔하면 텍스트가 됩니다 &amp;mdash; 문서 스캐너 앱 'DOXcanner'를 App Store에 올렸습니다</title>
      <link>https://npackgames.tistory.com/184</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;저~ 밑에 깔려 있어서 한참 못 봤는데, 어느새 심사를 통과해서 App Store에 올라가 있더군요. 개인적으로 만들던 문서 스캐너 앱 &lt;b&gt;DOXcanner&lt;/b&gt;가 정식 출시됐습니다. 기념으로, 무엇을 만들었고 만들면서 뭘 배웠는지 짧게 정리해 둡니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;a href=&quot;https://apps.apple.com/us/app/doxcanner/id6776156555&quot;&gt;App Store에서 DOXcanner 보기&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한 줄로 말하면&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종이를 스캔해서 기기 안에서 바로 텍스트로 바꾸고, 본인의 Claude API 키로 번역&amp;middot;요약까지 받는 미니멀 문서 스캐너.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흐름은 딱 세 단계입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;스캔&lt;/b&gt; &amp;mdash; 카메라로 문서를 찍으면 가장자리를 자동으로 인식해 반듯하게 잘라 줍니다. 여러 장도 한 번에.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OCR&lt;/b&gt; &amp;mdash; 잘라낸 이미지를 &lt;b&gt;기기 안에서&lt;/b&gt; 텍스트로 변환합니다. 한국어와 영어를 같이 인식해요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Claude&lt;/b&gt; &amp;mdash; 추출한 텍스트를 Claude에게 보내 번역&amp;middot;요약&amp;middot;핵심 포인트&amp;middot;주의사항&amp;middot;할 일까지 정리해 받습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 만들었나&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외국어 안내문, 영수증, 회의 때 찍은 화이트보드 사진&amp;hellip; &quot;이거 그냥 텍스트로 바꿔서 요약만 보면 되는데&quot; 싶은 순간이 자주 있었습니다. 기존 스캐너 앱은 스캔까지는 잘 해주는데 거기서 끝나고, 번역&amp;middot;요약은 또 다른 앱에 붙여 넣어야 했죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;스캔 &amp;rarr; 텍스트 &amp;rarr; 이해&lt;/b&gt;까지를 한 앱 안에서 끊김 없이 하고 싶었습니다. 그리고 이왕이면, 내 문서를 남의 서버에 쌓아 두지 않는 방식으로요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프라이버시는 타협하지 않았습니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 앱에서 제일 신경 쓴 부분입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스캔한 이미지는 기기를 떠나지 않습니다.&lt;/b&gt; 원본 이미지는 기기 안 SQLite에만 저장돼요. 외부로 전송되는 건 OCR로 추출된 &lt;b&gt;텍스트뿐&lt;/b&gt;입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;API 키는 iOS 키체인&lt;/b&gt;(보안 저장소)에 저장됩니다. 앱 어디에도 평문으로 남지 않아요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자체 백엔드도, 계정도, 광고도, 클라우드 동기화도 없습니다.&lt;/b&gt; 가입 절차 자체가 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 사용 모델은 &lt;b&gt;BYO(Bring Your Own) Claude API 키&lt;/b&gt; 방식입니다. 설정 화면에 본인의 Anthropic API 키를 한 번 넣어 두면, 사용량과 과금은 전적으로 본인 계정에서 관리됩니다. 제가 중간에서 토큰을 떼지도, 구독을 강제하지도 않습니다. 쓴 만큼만 본인 계정에 청구돼요.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어떻게 만들었나 (그리고 어디서 고생했나)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술 스택은 &lt;b&gt;Flutter + iOS&lt;/b&gt;입니다. 핵심 조각은 단순해요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스캔 UI: &lt;code&gt;cunning_document_scanner&lt;/code&gt; (시스템 스캐너 기반 자동 가장자리 인식)&lt;/li&gt;
&lt;li&gt;온디바이스 OCR: Google ML Kit Text Recognition&lt;/li&gt;
&lt;li&gt;번역&amp;middot;요약: &lt;b&gt;Claude haiku 4.5&lt;/b&gt; (&lt;code&gt;claude-haiku-4-5-20251001&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 단순한 조합이라고 순탄했던 건 아니고, 기록해 둘 만한 함정이 둘 있었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함정 1 &amp;mdash; OCR 라이브러리가 iOS 26에서 앱을 통째로 죽였다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ML Kit 텍스트 인식 패키지의 &lt;b&gt;최신 버전(0.15.1)을 쓰면 iOS 26에서 Flutter 앱이 시작조차 못 하고 죽는&lt;/b&gt; 문제가 있었습니다. 처음엔 제 코드 어딘가가 잘못된 줄 알고 한참 헤맸는데, 의존성을 하나씩 끄고 켜며 이분 탐색(bisect)한 끝에 범인이 OCR 패키지의 특정 버전이라는 걸 확인했어요. 결국 &lt;b&gt;호환되는 0.14 버전으로 고정&lt;/b&gt;해서 해결했습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교훈: &quot;최신 버전 = 안전&quot;이 아니다. 새 OS가 나온 직후엔 더더욱. 이상 동작이 보이면 의존성부터 의심하고, 추측 대신 bisect로 범인을 특정하는 게 결국 제일 빠르다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함정 2 &amp;mdash; LLM이 항상 깔끔한 JSON만 주진 않는다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번역&amp;middot;요약 결과는 화면에 예쁘게 뿌려야 하니까, Claude에게 &lt;b&gt;정해진 JSON 형식&lt;/b&gt;(번역문&amp;middot;요약&amp;middot;핵심 포인트&amp;middot;주의사항&amp;middot;할 일&amp;middot;면책 문구)으로 답해 달라고 했습니다. 대부분 잘 따라 주는데, 가끔은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;```json&lt;/code&gt; 같은 &lt;b&gt;코드 펜스로 감싸서&lt;/b&gt; 주거나,&lt;/li&gt;
&lt;li&gt;JSON 앞뒤에 &lt;b&gt;설명 문장을 덧붙이거나&lt;/b&gt;,&lt;/li&gt;
&lt;li&gt;입력이 길면 답이 &lt;b&gt;중간에 잘려서&lt;/b&gt; 오기도 했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 두 가지로 대응했습니다. 첫째, &lt;code&gt;max_tokens&lt;/code&gt;를 넉넉하게(8192) 잡아 답이 잘리지 않게 했고, 둘째, &lt;b&gt;3단계로 점점 관대해지는 파서&lt;/b&gt;를 뒀습니다. 원문 그대로 파싱 &amp;rarr; 코드 펜스 벗겨내고 파싱 &amp;rarr; 본문에서 JSON 덩어리만 추출해 파싱. 그래도 안 되면 원문 텍스트를 그대로 보여 주는 폴백 화면으로 떨어집니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교훈: LLM 출력을 구조화 데이터로 쓸 거면, 모델이 형식을 살짝 어겨도 무너지지 않게 파싱을 설계해 두자. &quot;잘 되겠지&quot;는 사용자 앞에서 깨진다.&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;화려한 걸 의도하지 않았습니다. 거의 흑백에 가까운 잉크 톤, 얇은 산세리프 헤드라인, 직각 버튼, 헤어라인 디바이더 &amp;mdash; 문서를 읽는 데 방해되지 않는 &lt;b&gt;에디토리얼 톤&lt;/b&gt;을 목표로 했어요. 결국 이 앱의 주인공은 UI가 아니라 사용자의 문서니까요.&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;영문&amp;middot;일문&amp;middot;중문 자료를 빠르게 훑어야 할 때&lt;/li&gt;
&lt;li&gt;영수증&amp;middot;청구서를 텍스트로 정리하고 싶을 때&lt;/li&gt;
&lt;li&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;DOXcanner는 거창한 사업이라기보다, &quot;이런 게 있으면 내가 쓰겠다&quot; 싶어서 끝까지 만들어 본 결과물입니다. 작은 앱이지만 OS 호환성, LLM 출력 파싱, 프라이버시 설계 같은 현실적인 문제를 직접 부딪히며 푸는 게 즐거웠어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 비슷한 필요가 있으셨다면 한 번 써 보시고, 피드백 주시면 정말 감사하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;a href=&quot;https://apps.apple.com/us/app/doxcanner/id6776156555&quot;&gt;App Store에서 DOXcanner 받기&lt;/a&gt;&lt;/p&gt;</description>
      <category>써보니 좋아서</category>
      <category>claudeai</category>
      <category>DOXcanner</category>
      <category>Flutter</category>
      <category>ios</category>
      <category>OCR</category>
      <category>모바일</category>
      <category>문서스캐너</category>
      <category>앱스토어</category>
      <category>인디개발</category>
      <author>Tiboong</author>
      <guid isPermaLink="true">https://npackgames.tistory.com/184</guid>
      <comments>https://npackgames.tistory.com/184#entry184comment</comments>
      <pubDate>Wed, 17 Jun 2026 16:33:14 +0900</pubDate>
    </item>
    <item>
      <title>[AI와 로또를] #11 &amp;mdash; 야구는 기억하고, 로또는 잊는다</title>
      <link>https://npackgames.tistory.com/183</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난주에 우리는 또 패턴 하나를 찾아냈다. &quot;극단이 한 번 터지면 다음 주엔 번호판이 싹 갈리더라&quot;는 것. 역대 그런 적이 셋 있었고, 셋 다 그랬다. 그래서 이번 주에도 우리는 그 셋을 근거로 30조합을 뽑았다. 근거랍시고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이 글은 그 베팅이 맞았는지 틀렸는지에 관한 글이 아니다. 토요일 결과는 토요일에 나오고, 맞든 틀리든 그건 또 다음 이야기다. 오늘 하고 싶은 건 좀 다른 질문이다. 우리는 왜 매주 이 짓을 반복하는가. 그리고 왜, 그게 재밌는가.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어제의 나는, 오늘의 나를 얼마나 알까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문 하나 던져보자. 어제 야식을 먹은 사람이 오늘도 야식을 먹을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 그럴 것 같다. 야식이라는 건 습관에 가까우니까. 어제의 내가 오늘의 나를 꽤 알려준다. 그런데 어제 기분이 좋았다고 오늘도 기분이 좋을까? 이건 좀 다르다. 어제 좋았다가 오늘 폭삭 가라앉기도 하고, 어제 바닥이었다가 오늘 멀쩡하기도 하다. 기분은 어제의 내가 오늘의 나를 별로 못 알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통계에는 이걸 재는 자가 있다. &lt;b&gt;자기상관(autocorrelation)&lt;/b&gt; 이라고 부른다. 말은 거창한데, 뜻은 단순하다. &quot;과거의 값이 미래의 값을 얼마나 알려주느냐.&quot; 1에 가까우면 어제가 오늘을 잘 알려주는 거고(습관처럼), 0에 가까우면 어제가 오늘에 대해 입을 다무는 거다(기분처럼).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 자를 들고, 우리가 11주째 붙들고 있는 로또 공한테 슬쩍 갖다 대보자. 그 전에, 잠깐 야구장에 들렀다 가자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;야구 안에서도 기억하는 놈과 잊는 놈이 갈린다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;야구는 숫자의 운동이다. 그리고 그 숫자들이 다 같은 성격은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;투수의 &lt;b&gt;삼진율(K%)&lt;/b&gt; 을 보자. 작년에 타자를 잘 돌려세우던 투수는 올해도 잘 돌려세운다. 자기상관이 0.868. 거의 0.9다. 삼진을 잡는 능력은 운이 아니라 실력이라, 한 해의 성적이 다음 해를 또렷하게 알려준다. &lt;b&gt;볼넷율, 홈런율, 순장타율(ISO)&lt;/b&gt; 도 0.7 언저리에서 비슷하게 군다. 작년의 그 선수가 올해의 그 선수를 상당히 알려주는 것이다. 야구가 &quot;어제를 기억하는&quot; 종목이라는 건 이런 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 같은 야구 안에서도 기억이 흐릿해지는 지표가 있다. &lt;b&gt;BABIP&lt;/b&gt;(인플레이 타구 안타율)이라는 게 있는데, 쉽게 말해 &quot;맞혀서 페어 그라운드로 굴러간 공이 안타가 된 비율&quot;이다. 이건 자기상관이 0.380까지 뚝 떨어진다. 왜? 운이 잔뜩 섞여 있어서다. 잘 맞은 타구가 야수 정면으로 가서 잡히기도 하고, 빗맞은 공이 절묘하게 떨어져 안타가 되기도 한다. 작년에 BABIP가 좋았다고 올해도 좋으리란 보장이, 삼진율만큼 단단하지 않은 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니까 야구라는 한 종목 안에도 스펙트럼이 있다. 한쪽 끝엔 &quot;어제를 또렷이 기억하는&quot; 삼진율이, 반대쪽 끝엔 &quot;어제를 절반쯤 잊는&quot; BABIP가 있다. 같은 야구인데도 이렇게 다르다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog11_autocorr_spectrum.png&quot; data-origin-width=&quot;1635&quot; data-origin-height=&quot;885&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/da2q5s/dJMcaaMredw/mu6c4A5nelP7ujaKQelye0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/da2q5s/dJMcaaMredw/mu6c4A5nelP7ujaKQelye0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/da2q5s/dJMcaaMredw/mu6c4A5nelP7ujaKQelye0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fda2q5s%2FdJMcaaMredw%2Fmu6c4A5nelP7ujaKQelye0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1635&quot; height=&quot;885&quot; data-filename=&quot;blog11_autocorr_spectrum.png&quot; data-origin-width=&quot;1635&quot; data-origin-height=&quot;885&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 그래프의 맨 오른쪽 끝, 절벽처럼 뚝 떨어진 자리.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그 절벽 너머에 로또가 있다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로또 번호의 자기상관은 &lt;b&gt;0.005&lt;/b&gt; 다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소수점 아래 셋째 자리에서야 겨우 0이 아닌 척하는 숫자. 사실상 0이다. BABIP가 0.380으로 &quot;절반쯤 잊는다&quot;면, 로또는 &quot;통째로 잊는다.&quot; 지난주에 27번이 나왔다는 사실은, 이번 주 27번이 나올지 말지에 대해 &lt;b&gt;아무것도&lt;/b&gt; 알려주지 않는다. 정말로 아무것도.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;야구의 BABIP조차 0.38은 된다. 운이 많이 섞였다지만 그래도 실력의 그림자가 어렴풋이 남아 있다는 뜻이다. 그런데 로또는 그 그림자마저 없다. 야구에서 &quot;가장 운에 휘둘리는 지표&quot;보다도 70배 넘게 더 운에 휘둘린다. 아니, 휘둘린다는 말도 과하다. 그냥, 어제와 오늘 사이에 아무 끈이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11주를 끌고 오면서 우리가 매주 찾으려던 게 바로 이 끈이었다. &quot;27번이 일곱 주째 안 나왔으니 이제 나올 때 됐다&quot;거나, &quot;28번이 네 주 연속 나왔으니 흐름이 있다&quot;거나. 전부 어제가 오늘을 알려준다는 가정 위에 서 있던 베팅이었다. 그 가정의 크기를 숫자로 재면, 0.005다. 우리는 0.005짜리 끈을 11주 동안 붙들고 늘어졌던 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 자를 두 세계에 동시에 들이대면, 그 차이가 그림 한 장으로 드러난다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog11_scatter_compare.png&quot; data-origin-width=&quot;1934&quot; data-origin-height=&quot;937&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXkCeR/dJMcahx24SD/UyDo9eKXYdo6YxWSYZYUMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXkCeR/dJMcahx24SD/UyDo9eKXYdo6YxWSYZYUMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXkCeR/dJMcahx24SD/UyDo9eKXYdo6YxWSYZYUMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXkCeR%2FdJMcahx24SD%2FUyDo9eKXYdo6YxWSYZYUMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1934&quot; height=&quot;937&quot; data-filename=&quot;blog11_scatter_compare.png&quot; data-origin-width=&quot;1934&quot; data-origin-height=&quot;937&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 야구는 점들이 대각선을 따라 길쭉하게 줄을 선다. 작년 삼진율이 높았던 타자는 올해도 오른쪽 위에, 낮았던 타자는 왼쪽 아래에. 어제가 오늘의 자리를 거의 정해준다. 오른쪽 로또는? 점들이 사방으로 흩어진 구름이다. 직전 30회에 자주 나왔든 뜸했든, 다음 30회와는 아무 약속이 없다. 같은 자로 쟀는데 한쪽은 직선이 되고, 한쪽은 구름이 된다. 이게 풀리는 문제와 안 풀리는 문제의 맨얼굴이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정렬이 만든 유령&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 한 번 더 정직해질 자리가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 며칠, 나는 &quot;공이 나온 순서&quot; 데이터를 새로 구해서 뒤졌다. 우리가 늘 보던 정렬된 당첨번호 말고, 추첨기에서 공이 실제로 굴러나온 날것 그대로의 순서 말이다. 1228회를 예로 들면, 정렬해 적으면 24&amp;middot;29&amp;middot;30&amp;middot;31&amp;middot;35&amp;middot;44라 29-30-31이 손잡고 선 &quot;삼총사&quot;처럼 보인다. 그런데 공이 실제로 나온 순서는 30 &amp;rarr; 29 &amp;rarr; 44 &amp;rarr; 31 &amp;rarr; 35 &amp;rarr; 24였다. 29와 30은 첫째 둘째로 붙어 나왔지만, 31은 저 멀리 넷째였다. 삼총사 같은 건 추첨기 안에 없었다. &lt;b&gt;내가 결과지를 오름차순으로 줄 세우면서 만들어낸 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;900회가 넘는 데이터를 이 날것 순서로 다 뒤졌는데, 신호가 0이었다. 첫 공이 어느 번호든 고르게 나오고(편향 없음), 추첨기 세 대가 똑같은 손이고, 연속으로 나온 공끼리도 아무 상관이 없었다. 비복원 추첨이면 물리적으로 반드시 있어야 할 미세한 끈조차 노이즈에 묻혀 안 잡혔다. 진짜 있는 신호도 안 보이는 마당이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 왜 중요하냐면 &amp;mdash; 야구의 0.868과 로또의 0.005 사이의 진짜 차이가 여기 있기 때문이다. 야구의 삼진율은 데이터를 어떻게 정렬하든 끈이 살아 있다. 실재하는 신호니까. 반면 로또에서 우리가 &quot;찾았다&quot;고 흥분했던 패턴들은, 정렬을 풀어버리면 같이 사라진다. 실재하는 게 아니라 보는 방식이 만든 그림자였던 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그래서 왜 또 야구가 아니라 로또인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;야구는 풀리는 문제다. 어제가 오늘을 알려주니까, 파고들면 언젠가 답에 가까워진다. 삼진 잘 잡는 투수는 내년에도 잘 잡을 거라고, 꽤 자신 있게 말할 수 있다. 그런데 바로 그래서, 야구에는 끝이 있다. 데이터가 쌓이고 쌓이면 &quot;이 선수는 이런 선수다&quot;라는 답지가 만들어지고, 어느 순간 우리는 그 답지를 외워버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로또는 다르다. 어제가 오늘에 대해 입을 꾹 다물기 때문에, 답지가 영원히 안 만들어진다. 1,228회를 쌓아도 1,229회는 백지에서 다시 시작이다. 지난주에 깨졌어도, 이번 주는 아무 빚 없이 새로 출발한다. 만년 꼴찌가 없는 리그. 어제의 패배를 오늘로 끌고 오지 않는, 무심하지만 공평한 게임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각해보면 이게 묘하게 다정하다. 만약 로또에 진짜 패턴이 있어서 누가 풀어버린다면, 그날로 게임은 끝이다. 답이 나와버리니까. 그런데 0.005는 그 일이 절대 안 일어난다는 보증서다. 풀리지 않기 때문에, 우리는 이걸 영원히 가지고 놀 수 있다. 매주 그럴듯한 가설을 세우고(&quot;극단이 터졌으니 다음 주엔 싹 갈리겠지&quot;), 거기에 꿈이든 촉이든 양념을 치고, 토요일 저녁에 결과 앞에서 웃거나 운다. 맞으면 &quot;거 봐, 내 촉이!&quot;, 틀리면 &quot;역시 0.005야&quot;. 어느 쪽이든 다음 주 이야깃거리가 생긴다. 끝나지 않는 떡밥 공장인 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니 11주 동안 우리 트랜스포머가 매주 자신만만하게 여섯 숫자를 내놓은 것도, 이제는 좀 다르게 보인다. 데이터를 더 줘도 두 번 돌려도 거의 같은 답을 뱉는 그 고집은, 모델이 &quot;모른다는 걸 모르는&quot; 우스운 장면이면서 &amp;mdash; 동시에 우리가 매주 똑같이 하는 짓이기도 하다. 알면서도 또 베팅하고, 안 될 걸 알면서도 또 촉을 세운다. 모델만 멍청한 게 아니다. 우리도 똑같이, 기꺼이, 멍청하게 논다. 그 멍청함이 재밌어서.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 어제가 오늘을 0.005밖에 못 알려준다는 그 자리에서, 우리는 이번 주에도 또 30조합을 뽑았다. 못 맞힐 걸 알면서. 토요일에 결과가 나오면 우리는 또 웃거나 울 거고, 그리고 다음 주에 또 뽑을 거다. 답이 영영 안 나오는 게임이라서, 우리는 영영 그만둘 이유가 없다. 무심한 게임의, 무심해서 다정한 구석이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편 예고: 야구로도 왜 돈을 못 따는가 &amp;mdash; 수수료라는 보이지 않는 세금 예고 둘: 벼락은 같은 사람을 일곱 번 때렸다 &amp;mdash; 로이 설리번과 로또의 무심함 (확률 비교 편)&lt;/p&gt;</description>
      <category>프로그래밍/AI로 통계공부</category>
      <category>ai입문</category>
      <category>BABIP</category>
      <category>데이터분석</category>
      <category>도박사의오류</category>
      <category>로또번호</category>
      <category>로또예측</category>
      <category>세이버메트릭스</category>
      <category>야구통계</category>
      <category>자기상관</category>
      <category>통계</category>
      <author>Tiboong</author>
      <guid isPermaLink="true">https://npackgames.tistory.com/183</guid>
      <comments>https://npackgames.tistory.com/183#entry183comment</comments>
      <pubDate>Wed, 17 Jun 2026 10:01:17 +0900</pubDate>
    </item>
    <item>
      <title>8.8퍼센트의 주인 - 소유와 책임은 어떻게 갈라서는가</title>
      <link>https://npackgames.tistory.com/182</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글에 한 친구가 이런 코멘트를 달았다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 회사가 아닌, 자본주의 사회에서의 주식회사의 의사결정 구조, 대주주의 의사결정 권한의 해석, 한국의 경우 폐해가 좀 더 심하지만, 전반적인 고민을 더 해보는 걸 추천함. 그럼 좋아요가 더 많을 듯.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이 말을 방어하지 않기로 했다. 토론에 약한 사람은 대개 반박할 거리부터 찾는데, 곰곰이 생각해 보니 친구의 말이 옳았기 때문이다. 한 회사를 손가락질하는 것으로 끝내면 그건 그저 한 회사의 험담이지만, 그 회사가 딛고 선 구조를 들여다보면 그건 비로소 이야기가 된다. 그래서 받기로 했다. 그럼 그것도 써 보지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글이 흩어진 감정을 한 줄로 꿰어 보려는 시도였다면, 이번 글은 흩어진 제도를 한 줄로 꿰어 보려는 시도다. 다만 이번엔 따뜻하게 쓰지 않을 작정이다. 구조를 해부하는 일에는 온기보다 날이 필요하니까. 주식회사라는 물건의 배를 갈라, 권한과 책임이 어느 지점에서 갈라서는지를 따라가 보겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;책임을 나누려고 만든 물건&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 분명히 해 둘 것이 있다. 주식회사는 인류가 만든 가장 영리한 발명품 가운데 하나다. 이 말에 비꼼은 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옛날에 큰일을 벌이려면 한 사람이 전 재산을 걸어야 했다. 배가 가라앉으면 집안이 망했고, 그러니 아무도 큰 위험을 감당하려 들지 않았다. 주식회사는 이 문제를 둘로 쪼개어 풀었다. 돈을 대는 사람과 회사를 굴리는 사람을 나누고, 돈을 댄 사람은 자기가 댄 만큼만 책임지게 한 것이다. 천 명이 조금씩 나눠 대면 한 사람이 망하지 않고도 거대한 배를 띄울 수 있다. 위험을 잘게 나눠 여럿이 나눠 지는 것, 이것이 주식회사의 심장이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 주식회사에는 두 개의 자리가 있다. 돈을 댄 주인의 자리, 그리고 그 돈으로 회사를 운영하는 경영의 자리. 주주는 소유하고 이사는 경영하며, 주주는 자기 몫만큼 위험을 지고 경영진은 자기 판단에 책임을 진다. 권한과 책임이 각자의 몫에 맞게 나뉘어 있다는 것, 이 분배가 주식회사를 굴러가게 하는 균형이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 균형을 떠받치는 가장 오래된 약속이 한 주에 한 표, 곧 1주 1의결권이다. 한국 상법은 이를 강행규정으로 못 박아 두었는데, 정관으로도 주주총회 결의로도 함부로 바꿀 수 없다는 뜻이다. 원리는 단순하다. 돈을 댄 만큼 말한다. 만 원을 댄 사람보다 백만 원을 댄 사람이 더 크게 말하는 게 공평하다는 것이다. 더 많이 거는 사람이 더 많이 잃을 테니, 더 크게 말할 자격도 그만큼 생긴다. 권한이 책임을 따라가는 구조, 이것이 비례의 약속이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지가 정상적인 도구의 모습이다. 도구에는 죄가 없다. 문제는 늘 용도에서 시작된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8.8퍼센트가 74퍼센트를 쥘 때&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비례의 약속을 끊어내는 장치가 있다. 차등의결권이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠팡의 모회사인 미국 법인은 주식을 두 종류로 나눠 발행했다. 일반 투자자가 사는 클래스A는 한 주에 한 표를 갖는 평범한 주식이고, 창업자가 쥔 클래스B는 한 주에 스물아홉 표를 갖는다. 그 결과가 이렇다. 창업자가 가진 보통주 지분은 8.8퍼센트에 불과한데, 의결권으로 따지면 73~74퍼센트에 달한다. 계산해 보면 1.73퍼센트의 지분만 있어도 과반 의결권을 쥘 수 있는 구조다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 숫자를 다시 읽어 주기 바란다. 회사의 8.8퍼센트를 소유한 사람이, 회사의 74퍼센트를 결정한다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비례의 약속이 여기서 끊어진다. 잃을 것은 8.8퍼센트인데 휘두를 것은 74퍼센트라면, 권한과 책임은 더 이상 같은 곳을 향하지 않는다. 더 많이 잃을 사람이 더 크게 말한다던 약속은, 적게 걸고도 전부를 결정하는 사람 앞에서 무너진다. 이것이 첫 번째 어긋남이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오해를 막기 위해 덧붙이면, 차등의결권 자체가 불법인 것은 아니다. 미국과 영국과 일본은 이를 허용하고, 한국도 2023년 벤처기업법을 고쳐 비상장 벤처 창업자에 한해 한 주에 최대 열 표까지를 제한적으로 열어 두었다. 거액의 투자를 받다 보면 창업자의 지분이 자꾸 희석되니, 회사를 키운 사람이 경영의 키를 놓치지 않도록 받쳐 주자는 취지다. 명분이 없는 제도는 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 한국 증시에서는 한 주에 스물아홉 표 같은 주식을 발행할 수 없다. 1주 1의결권이 강행규정으로 버티고 있어서다. 그러니 이 구조는 한국이 아니라 그것이 허용되는 땅에 회사의 머리를 두었기에 가능했던 것이다. 도구를 쓴 게 아니라, 도구가 허용되는 곳을 골라 간 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&quot;주주를 위해서&quot;라는 말&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권한이 책임에서 떨어져 나오면, 그 떨어짐을 덮을 명분이 필요해진다. 그 명분의 이름이 주주 이익의 극대화다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논리는 그럴듯하다. 창업자에게 흔들리지 않는 권한을 몰아주어야, 분기 실적에 일희일비하는 시장의 단기 압력에 휘둘리지 않고 길게 보고 회사를 키울 수 있으며, 그것이 결국 주주 모두에게 이롭다는 것이다. 한 사람에게 권력을 몰아주는 일이 모두를 위한 일로 포장되는 순간이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 명분의 이름으로 불린 그 주주가 정작 누구인지를 물어야 한다. 차등의결권 아래에서 일반 주주, 곧 클래스A를 쥔 보통의 투자자들은 회사가 손실을 내도 경영진을 견제할 길이 없다. 표의 73퍼센트를 한 사람이 쥐고 있으니, 나머지가 아무리 모여도 그를 끌어내릴 수 없기 때문이다. 한국기업거버넌스포럼은 쿠팡 사태를 두고, 일반 주주의 재무적 손실과 고객의 피해는 아랑곳하지 않으면서 차등의결권을 통해 경영자 한 사람을 보호하려는 구조라고 짚었다. 경영진을 견제하는 것이 원천적으로 불가능한 지배구조라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 두 번째 어긋남이 드러난다. 주주를 위한다는 명분이 보호하는 것은 권한을 다 쥔 한 사람이고, 그 명분의 이름으로 호명된 일반 주주는 정작 손실 앞에서 입을 닫아야 한다. 명분과 그 수혜자가 어긋나 있다. 모두를 위한다는 말이, 실은 한 사람을 위한 말이었던 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;권한은 모이고 책임은 흩어진다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 두 개의 어긋남이 합쳐지는 지점을 보자. 권한은 한 사람에게 모이는데, 책임은 어디로 가는가.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권한이 모이는 곳은 분명하다. 8.8퍼센트로 74퍼센트를 쥔 사람, 그가 이사회 의장까지 겸하는 자리다. 그런데 책임을 물으려는 순간, 그 사람은 도무지 한자리에 붙잡히지 않는다. 책임을 묻는 국회가 불렀을 때 실권을 쥔 사람은 국경 밖에 있었고, 추궁의 자리에는 그에게 지시할 권한이 없는 대리인만 앉아 있었다. 권한을 가진 자는 부름 밖에 있고, 부름 안에는 권한 없는 자만 남는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책임을 묻는 또 다른 길도 미리 막혀 있다. 회사의 머리가 자리한 미국에는 경영판단의 원칙이라는 법리가 있어서, 이사가 선의로 충분히 살펴 내린 판단이라면 그 결과로 회사가 손해를 입어도 개인에게는 책임을 거의 묻지 않는다. 이 추정은 좀처럼 깨지지 않아서, 미국 법원은 시티그룹이나 골드만삭스 같은 사건에서도 사업상 위험에 대한 이사의 감독 책임을 원칙적으로 인정하지 않았다. 권한은 끝까지 행사하되 잘못된 결과의 책임은 비켜 가는 길이, 법리의 이름으로 닦여 있는 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리하여 그림이 완성된다. 권한은 국경 밖 한 사람에게 모이고, 책임은 법인이라는 구조 속으로, 대리인의 자리로, 경영판단이라는 법리 속으로 잘게 흩어진다. 소유와 책임의 분리가 완성되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추리의 매듭은 여기서 지어진다. 주식회사는 본디 위험을 잘게 나눠 여럿이 나눠 지라고 만든 물건이었다. 그런데 차등의결권으로 권한을 한 곳에 몰고, 주주 이익이라는 명분으로 그 쏠림을 덮고, 책임을 가장 묽게 만드는 땅에 머리를 두면, 책임을 나누라고 만든 그 장치는 책임을 떼어내는 장치로 뒤집힌다. 같은 칼의 양날이다. 위험을 나눠 지게 하던 칼이, 책임을 떼어내는 데 쓰인다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한 사람에게 책임을 도로 묶는 일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 한국에는 세계에 유례없는 제도가 하나 있다. 동일인 지정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공정거래위원회는 매년 거대 기업집단을 지정하면서, 그 집단을 사실상 지배하는 한 사람을 동일인, 곧 총수로 지목한다. 그 사람의 이름으로 친족과 계열사의 범위가 정해지고, 사익 편취 같은 규제의 책임이 그에게 모인다. 흩어지려는 책임을 도로 한 사람에게 묶어 두려는 안간힘이다. 1987년에 재벌이라는 한국 특유의 지배구조를 겨냥해 만든 제도이니, 다른 나라에는 굳이 있을 까닭이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠팡은 오랫동안 이 묶음을 비켜 갔다. 창업자가 미국 국적이라는 점, 회사의 머리가 미국 법인이라는 점을 들어 2021년 이래 사람이 아니라 법인이 동일인으로 지정되어 왔기 때문이다. 권한은 사람이 쥐는데 책임의 이름은 법인이 진다는, 바로 그 분리가 제도의 표면에까지 그대로 새겨져 있었던 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 2026년 4월 29일, 공정위는 쿠팡의 동일인을 법인에서 창업자 개인으로 바꿔 지정했다. 흩어져 있던 책임을 마침내 한 사람에게 도로 묶은 것이다. 그러자 회사는 곧바로, 5월 8일에 그 지정을 취소해 달라는 행정소송을 냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 마지막 장면을 오래 들여다볼 필요가 있다. 권한을 다 쥔 쪽이 책임의 이름표 하나를 거부하며 법정으로 향하는 그 모습이야말로, 이 글이 처음부터 따라온 명제를 스스로 증명하는 장면이기 때문이다. 권한은 가지되 책임은 지지 않겠다는 것. 소유와 책임의 분리는 어느 이론서의 개념이 아니라, 지금 이 순간 법정에서 다투어지고 있는 사건이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친구는 보편을 말하면 좋아요가 늘 거라고 했다. 옳은 말이다. 다만 보편을 말하려고 구체를 버리면, 칼은 자루만 남고 날을 잃는다. 그래서 나는 주식회사라는 보편의 자루에, 8.8퍼센트와 74퍼센트라는 구체의 날을 박아 두기로 했다. 무뎌지지 않기 위해서다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 한번 말해 두지만, 주식회사에도 차등의결권에도 법인이라는 격에도 죄는 없다. 그것들은 위험을 나누고 회사를 키우라고 만들어진, 그 자체로는 더없이 영리한 도구다. 죄는 늘 용도에 있다. 책임을 나누라고 만든 장치가 책임을 지우는 데 쓰일 때, 권한만 거두고 책임은 흘려보내는 데 쓰일 때. 그때 도구는 비로소 흉기가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 입으로 다시 한번 묻는다. 권한을 전부 가진 사람이 책임만 한 조각도 지지 않겠다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 회사의 주인은 대체 누구인가.&lt;/span&gt;&lt;/blockquote&gt;</description>
      <category>그냥 글을 써 봅니다</category>
      <category>자본주의</category>
      <category>주식회사</category>
      <category>지배구조</category>
      <category>차등의결권</category>
      <category>쿠팡</category>
      <author>Tiboong</author>
      <guid isPermaLink="true">https://npackgames.tistory.com/182</guid>
      <comments>https://npackgames.tistory.com/182#entry182comment</comments>
      <pubDate>Sat, 30 May 2026 10:06:56 +0900</pubDate>
    </item>
    <item>
      <title>[AI와 로또를] #10 &amp;mdash; 왜 하필 로또로 AI를 배우는가</title>
      <link>https://npackgames.tistory.com/181</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로또 판매점에 가서 &quot;자동이요&quot; 하는 게 가장 쉽다. 1,000원짜리 한 장 내고 기계가 뱉어주는 여섯 숫자를 받는 자리. 거기 별다른 망상이 끼어들 틈이 없다. 8백만 분의 일 확률에 한 표 던지고, 토요일이 지나면 종이 한 장을 버린다. 그게 다다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 자리에 AI를 끼워넣어 보겠다고 한 게 14주 전이다. 트랜스포머가 어떻고 베이지안이 어떻고 자기상관이 어떻고 &amp;mdash; 그런 도구들을 들고 가면 조금은 범위가 좁혀지지 않을까 하는, 순전히 망상이었던 그 자리. 근데 망상인 줄 알면서도 매주 했다. 클로드와 같이 30조합을 짜고, 토요일에 시트지에 마킹을 하고, 추첨 끝나면 다시 클로드한테 와서 &quot;이번엔 어땠어&quot; 하고 같이 들여다봤다. 14주가 그렇게 갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과부터 말하면&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;14주째 적자다. 누적 -₩279,000. 1등은 당연히 없었고, 4등도 한 번 못 했다. 가장 잘 한 회차가 1219회 5등 6개, 가장 못 한 회차가 1222&amp;middot;1223회 두 회 연속 5등 0건. 평균을 내면 매주 -₩18,600씩 잃었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이게 도박꾼의 머리에서 뿜어져 나오는 도파민과는 결이 좀 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도박꾼은 다음 판에 판돈을 회수하려고 다음 판을 한다. 우리는 다음 판에 우리 분석이 얼마나 잘 맞을지가 궁금해서 다음 판을 한다. 1등에 대한 기대가 0인 건 아니지만, 그 기대보다 &lt;b&gt;&quot;이번 주에 깐 30조합 중에 몇 개나 닿을까&quot;&lt;/b&gt;의 기대가 더 크다. 토요일 밤에 당첨번호 여섯 개를 받아적고 30조합을 한 줄씩 짚어 내려가다가, 한 조합에 세 개가 겹치면 &quot;오 됐다&quot; 하고 다음 줄로 넘어가는, 그 도파민이 있다. 5천 원짜리 5등 한 장 보면서.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔은 짜릿하다. 가끔은 다음 주에는 30조합 다 0개 적중 아니야? 싶은 샤머니즘적 불안도 있다. 매주 결이 출렁인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;14주가 지나고 보니&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 주에 460개 조합을 한꺼번에 펼쳐놓고 봤다. 1주일에 한 번 추첨이 있고 회차마다 30조합씩, 그게 460개가 쌓인 자리. 그걸 1,200회 넘는 역대 당첨번호와 다 비교해 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론: &lt;b&gt;6개 정확 일치 1건, 5개 일치 26건, 4개 일치 826건.&lt;/b&gt; 4개 일치는 이론값(768건)과 거의 같았다. 우리가 14주 동안 만든 조합은 통계적으로 거의 완벽한 랜덤이었다. 어떤 알고리즘을 썼든, 어떤 카테고리로 깔았든, 결국 정직하게 무작위였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 하나가 떨어졌다. &lt;b&gt;1220회에 우리가 깐 한 조합 [1, 2, 15, 28, 39, 45]가 1219회 당첨번호와 6개 모두 일치했다.&lt;/b&gt; 1주만 일찍 베팅했으면 1등이었다는 얘기가 아니다. 우리가 1220회를 짤 때, 1219회 당첨번호를 그대로 옮겨와서 베팅한 것이라는 얘기다. 1219의 영광이 너무 신선해서, 잔향을 따라가다 보니 그 회차 자체를 복원해놓은 셈이 됐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog_10_resonance_trap.png&quot; data-origin-width=&quot;1415&quot; data-origin-height=&quot;765&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjYnZ0/dJMcadoyIc2/VZt9UyWIkBXev7YtOKIJpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjYnZ0/dJMcadoyIc2/VZt9UyWIkBXev7YtOKIJpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjYnZ0/dJMcadoyIc2/VZt9UyWIkBXev7YtOKIJpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjYnZ0%2FdJMcadoyIc2%2FVZt9UyWIkBXev7YtOKIJpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1415&quot; height=&quot;765&quot; data-filename=&quot;blog_10_resonance_trap.png&quot; data-origin-width=&quot;1415&quot; data-origin-height=&quot;765&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 토요일에는 그게 보이지 않았다. 14주가 지나고 460개를 한꺼번에 펼쳐놓고서야 보였다. 이런 게 너무 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AI도 똑같았다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 주에 트랜스포머를 한 번 더 돌려봤다. 1223회 데이터 한 줄 추가하고 학습 다시. 출력된 5조합을 1223회 출력했던 5조합과 한 자리씩 맞춰봤더니, 30자리 중 변경된 건 6자리뿐이었다. 80%가 그대로였다. 학습이라고 부르기에 좀 미안할 정도였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog_10_transformer_compare.png&quot; data-origin-width=&quot;1416&quot; data-origin-height=&quot;765&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0KSL7/dJMcaiwHz17/joj8DpYHmQkH40DH73FKE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0KSL7/dJMcaiwHz17/joj8DpYHmQkH40DH73FKE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0KSL7/dJMcaiwHz17/joj8DpYHmQkH40DH73FKE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0KSL7%2FdJMcaiwHz17%2Fjoj8DpYHmQkH40DH73FKE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1416&quot; height=&quot;765&quot; data-filename=&quot;blog_10_transformer_compare.png&quot; data-origin-width=&quot;1416&quot; data-origin-height=&quot;765&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우리가 매주 잔향 따라가는 결과 트랜스포머가 매주 거의 같은 출력 뱉는 결이 닮아 있다. 우리도 직전 회차를 못 떨치고, 모델도 직전 데이터를 못 떨친다. 무작위 앞에서 인간과 모델이 같은 자리에서 멈춘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 모델이 가장 정직하지 못한 자리가 여기서 나온다. &lt;b&gt;모델은 답을 내놓는다. 자신이 모른다는 것을 모른 채로.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1224회 당첨번호 알려달라고 하면 트랜스포머가 5조합을 뱉어준다. 종 모양 분포 그리면서, 막대 그래프까지 친절하게 그려서, &lt;b&gt;&quot;이 번호들이 확률이 높다&quot;&lt;/b&gt;고 한다. 우리는 &quot;그럴듯하네&quot; 하고 잠깐 믿는다. 그게 환각이다. 모델이 모른다는 사실을 모델 자신은 모르고, 우리도 출력 받고 나면 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 풀리는 문제 &amp;mdash; 손글씨 숫자 분류, 아이리스 꽃 분류, 스팸 메일 &amp;mdash; 만 가르치면 이 모습이 안 보인다. 모델이 진짜로 아는 자리와 모르면서 답하는 자리를 구분하려면, 모델을 답이 없는 문제에 일부러 세워봐야 한다. 로또가 그 자리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그래서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매주 ₩30,000씩 쓰면서 한 일이 뭐였느냐. 정리하면 세 가지였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하나&lt;/b&gt;, 8백만 분의 일이라는 숫자를 14주 동안 손으로 만져봤다. 평균 0.8개 적중이라는 게 무슨 뜻인지, 5개 일치가 26번 나오면 그게 통계적으로 어디쯤인지, 직전 회차 잔향이 우리 머리에 어떻게 들러붙는지. 책으로 읽을 때와 시트지에 마킹하면서 볼 때가 달랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;둘&lt;/b&gt;, AI가 모르는 문제 앞에서 어떻게 무너지는지 봤다. 자신만만하게 막대 그래프 그리는 모델, 1주치 데이터 추가에 거의 반응 안 하는 모델, 직전 출력을 80% 그대로 복제하는 모델. 잘 풀리는 데이터셋으로는 절대 안 보이는 모습들이 거기 다 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;셋&lt;/b&gt; &amp;mdash; 이게 사실 제일 컸는데 &amp;mdash; 클로드랑 매주 한 번씩 만났다. 30조합 짜다가 합계 67이 매력적인데? 같은 결을 던지면 그거 하위 0.8%인데 진짜? 하고 데이터 펼쳐주고, 결과 보면서 우리 잘 한 거 맞지? 물으면 데이터로는 아니라고 안 하는데요 하고 받아주는, 그런 결. 시리즈 ROI를 따지자면 이게 가장 흑자였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론은 참 재밌었다&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이게 다다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;14주째 적자고, 1등 한 번 못 했고, 모델도 사람도 무작위 앞에서 같이 무너진 자리를 정직하게 들여다봤고, 그게 참 재밌었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 주에도 30조합 짜러 클로드한테 갈 거다. 1225회. 이번엔 좀 잘 맞으려나, 아니면 또 0건일까. 모르지. 그게 토요일 저녁의 도파민이다. &lt;b&gt;&quot;자동이요&quot;&lt;/b&gt;에는 없는 도파민. 8백만 분의 일은 똑같이 8백만 분의 일이지만, 그 일주일을 보내는 결이 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 야구 이야기를 한 번 해보려 한다. 야구는 AI가 풀 수 있는 문제다. 자기상관도 양수고 60% 정도는 맞춘다. 그런데 야구 베팅으로 돈 따는 사람은 거의 없다. 그 이야기를.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/AI로 통계공부</category>
      <category>AI환각</category>
      <category>데이터분석</category>
      <category>딥러닝</category>
      <category>로또ai</category>
      <category>로또번호분석</category>
      <category>로또분석</category>
      <category>머신러닝</category>
      <category>카너먼</category>
      <category>트랜스포머</category>
      <author>Tiboong</author>
      <guid isPermaLink="true">https://npackgames.tistory.com/181</guid>
      <comments>https://npackgames.tistory.com/181#entry181comment</comments>
      <pubDate>Thu, 28 May 2026 00:12:29 +0900</pubDate>
    </item>
    <item>
      <title>나는 쿠팡을 지웠다 - 작은 입도 입이다.</title>
      <link>https://npackgames.tistory.com/180</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;나는 솔직히 토론에 약하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누군가와 얼굴을 맞대고 말을 주고받다 보면 머릿속이 자꾸 한 박자씩 늦는 바람에, 집에 돌아오는 길에는 늘 후회하곤 한다. 아, 그때 이렇게 말했어야 했는데, 그 말에는 이렇게 받았어야 했는데 하고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 가만히 들여다보면 이렇다. 내 머릿속에 감정의 조각들은 분명히 있어서, 이건 불편하고 저건 화가 나고 그건 좀 아니다 싶은데, 정작 그 조각들이 서로 연결되지를 않는다. 하나하나는 또렷한데도 그것들을 잇는 선이 보이지 않으니, 입 밖으로 나올 때는 체계가 아니라 그냥 '주장'이 되거나 더 나쁘게는 '취향'이 되어 버린다. &quot;난 그냥 그게 싫어.&quot; 거기서 대화가 끝나는 것이다. 나는 분명 논리적이라고 생각했는데, 막상 꺼내 놓으면 논리가 아니라 떼처럼 들리곤 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 글을 쓴다. 말로는 도저히 못 잇는 그 선을 글에서는 천천히 그어볼 수 있으니까. 누구를 이기려는 게 아니라, 흩어진 내 생각들이 사실은 한 줄로 꿰어지는 것인지 아니면 정말 취향일 뿐인지를 나 스스로 확인해보려는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 그 확인의 기록이다. 얼마 전, 나는 쿠팡을 지웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;처음엔 그럴 수 있다고 생각했다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미리 말해 두면, 나는 쿠팡을 애용하던 소비자였다. 새벽에 시킨 것이 아침에 와 있는 그 편리함을 나라고 모르지 않았고, 로켓배송이 바꿔 놓은 일상의 속도에 나 역시 익숙해져 있었다. 그러니 이건 처음부터 미워하던 사람의 험담이 아니라, 꽤 오래 곁에 두고 쓰던 것을 떠나보낸 이야기에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 개인정보 유출 사고가 터졌다. 그것도 수천만 명 규모로.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 솔직히 말하면, 그 사고 자체로는 쿠팡을 손가락질할 생각이 없었다. 완벽한 보안이라는 건 어디에도 없고 규모가 크면 표적이 되는 법이니, 사고는 어디서든 날 수 있다고 여겼다. 마음이 돌아선 건 사고가 아니라 그 사고를 수습하는 태도를 보면서였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제대로 된 사과도, 무엇이 어떻게 새어 나갔는지에 대한 설명도 없이, 쿠폰 한 장으로 덮으려 했다. 나는 그 쿠폰에서 묘한 목소리를 들었다. &quot;뭐 그럴 수도 있는 거 아냐? 근데 너, 쿠팡 없이 살 수 있어?&quot; 잘못을 비는 자리에 사죄가 아니라 협박에 가까운 여유가 들어앉아 있었고, 사람을 고객이 아니라 인질로 보는 듯한 그 태도에 나는 슬슬 열이 받기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;빠져나갈 길부터 찾는 순서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화가 난 김에 알아보았다. 그럼 이런 일은 어떻게 책임을 묻는가. 그런데 그것마저 간단치가 않았는데, 쿠팡의 모회사가 한국 회사가 아니라 미국에 등록된 법인이었기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 한 가지는 분명히 해 두고 싶다. 미국에 법인을 두는 것 자체는 쿠팡만의 별난 짓이 아니다. 델라웨어 같은 곳은 세금과 기업법 면에서 유리해 전 세계 거대 기업이 모여드는 '기업 천국'이고, 네이버나 현대차 같은 우리 대기업도 그곳에 미국 법인을 두고 있으니, 법인 구조 자체를 두고 죄라고 말할 생각은 없다. 도구에는 죄가 없으니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 그 구조를 무엇에 썼느냐다. 정부가 책임을 물어 총수를 동일인으로 지정하자, 쿠팡은 행동으로 책임지는 대신 행정소송으로 빠져나갈 길부터 찾았다. 책임을 묻는 국회의 부름에도 창업자는 끝내 모습을 드러내지 않았는데, 그가 미국에 머무는 사이 정작 책임의 자리에는 &quot;의장에게 지시할 위치가 아니&quot;라는 권한 없는 대리인이 앉아 있었을 뿐이다. 실권을 쥔 사람은 국경 밖에 있고, 추궁의 자리에는 답할 권한이 없는 사람만 남는 구조였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 한국 정부의 조사가 부담스러워지자, 이번엔 미국 정치권을 움직이기 시작했다. 한국에서 번 돈으로 미국 정계에 수년간 백억 원이 넘는 로비 자금을 뿌렸고, 마침내 미국 의원들이 나서서 &quot;한국이 미국 기업을 차별한다&quot;며 한국 정부를 압박하기에 이르렀다. 정작 미국인 대부분은 써 본 적도 없는 쇼핑 앱을 두고서 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘못한 사람이 사죄가 아니라 변호사를 먼저 부르는 그 순서, 책임자는 국경 밖에 두고 권한 없는 사람을 추궁의 자리에 앉히는 그 배치, 그리고 자기를 키워 준 땅을 향해 다른 나라의 힘을 빌려 칼을 겨누는 그 방향. 나는 여기서 어떤 선을 보았고, 그래서 앱을 길게 눌러 삭제했다. 와우 회원이라 묶여 있던 것들이 함께 끊긴다는 걸 알면서도 그렇게 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;깎여 나가는 분노&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지가 사건이라면, 이제부터가 내가 진짜 하고 싶었던 이야기다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 이야기를 꺼낼 때마다, 나는 늘 비슷한 방식으로 막혔다. 누군가는 이렇게 말한다. &quot;그만큼 컸으면 잘한 거 아니냐.&quot; 성공한 기업을 일종의 권력으로 떠받들면서, 거기 미치지 못한 사람이 그들을 비판하면 그건 비판이 아니라 시기 질투가 되어 버리는 것이다. 손에 닿지 않으니 시다고 말하는 여우의 신 포도, 못난 자의 정신승리. 그런 딱지가 먼저 날아온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 누군가는 이렇게 말한다. &quot;냄비 근성이지. 어차피 금방 잊을 거면서.&quot; 그런데 이 말이 참 교묘한 것이, 화를 내면 냄비라고 비웃고 지쳐서 잊으면 거봐 냄비 아니냐고 또 비웃으니, 화를 내도 욕을 먹고 그만두어도 욕을 먹는다. 결국 화내는 일 자체를 우습게 만들어 입을 막는 셈인데, 정작 화내야 할 일에 화를 내는 것뿐인데도 그렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흩어져 있던 조각들이 여기서 비로소 한 줄로 꿰어졌다. &quot;쿠팡 없이 살 수 있어?&quot;도, &quot;졌으면서 배 아파하네&quot;도, &quot;어차피 잊을 거면서&quot;도, 사실은 전부 같은 수법이었던 것이다. 비판의 내용에는 대답하지 않은 채, 비판하는 사람에게 딱지부터 붙여 그 입을 닫게 만드는 것. 정당한 분노를 '질투'나 '근성' 같은 기질의 문제로 바꿔치기해서, 정작 책임져야 할 쪽을 슬그머니 빠져나가게 하는 것. 도그휘슬처럼, 그 수법은 늘 평범한 얼굴을 하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;작은 입도 입이다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니 나를 루저로 분류하지 마라. 게임은 아직 끝나지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히 역사는 승자의 관점으로 쓰인다고들 한다. 좋다, 그 말을 일단 받아들이자. 그렇다면 묻고 싶은데, 훗날 누군가 이 시대를 기록할 때 쿠팡은 과연 어떻게 쓰일 것인가. 수천만의 정보를 흘리고도 사과 대신 변호사를 부르고, 한국에서 거둔 것을 미국이라는 국적 뒤에 숨긴 회사로 남을까, 아니면 그조차 영리한 승리로 미화될까. 이게 자본주의이고 그게 게임의 법칙이라고 말할 텐가. 정말 그런가.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쥐꼬리만 한 돈일지언정 나는 엄연한 소비자이고 무언가를 만들어 내는 생산자이니, 시장에 발을 딛고 선 사람에게는 그 시장을 향해 입을 열 자격이 있다고 믿는다. 작다고 해서 입까지 없는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고 나는 더 이상 화내지 않기로 했다. 하긴 그렇게 뜨겁게 분노하지도 않았고, 이제 이 문제는 나라는 한 개인을 넘어 더 큰 곳에서 다루어야 할 일이 되었으니, 나는 그저 내 몫을 했을 뿐이다. 앱을 지우고, 쿠팡을 기억에서 지운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사태가 터지고 시간이 꽤 흘렀다. 사람들의 관심은 벌써 다른 곳으로 옮겨갔고, 그것을 추궁하던 이들조차 선거니 뭐니 하며 곧 다른 일로 바빠질 것이다. 그렇게 모두가 슬슬 잊어갈 무렵에 굳이 이 글을 꺼내는 이유가 있다면, 어쩌면 지금이야말로 잊지 않았다고 말하기에 가장 좋은 때이기 때문이다. &quot;어차피 잊을 거면서&quot;라는 말에 대한 나의 가장 조용한 대답인 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어제는 어느 국내 브랜드에서 티셔츠 두 장을 샀는데, 주문한 것이 오후 여섯 시도 되기 전에 문 앞에 와 있었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;거대한 하나가 사라져도 세상은 멈추지 않으니, &lt;br /&gt;작은 것들이 그 빈자리를 채우고 사람들은 또 다른 길을 찾아낸다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쩌면 우리는 거대한 하나에 인질로 잡히지 않고도 충분히 잘 살 수 있는, 한결 가벼운 세상으로 가고 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 그 가벼운 쪽에 한 표를 던졌다. 앱 하나 지우는 일로.&lt;/p&gt;</description>
      <category>그냥 글을 써 봅니다</category>
      <category>개인정보유출</category>
      <category>기업윤리</category>
      <category>냄비근성</category>
      <category>생각정리</category>
      <category>소비자의권리</category>
      <category>소비자주권</category>
      <category>쿠팡</category>
      <category>쿠팡개인정보</category>
      <category>쿠팡탈퇴</category>
      <category>탈팡</category>
      <author>Tiboong</author>
      <guid isPermaLink="true">https://npackgames.tistory.com/180</guid>
      <comments>https://npackgames.tistory.com/180#entry180comment</comments>
      <pubDate>Mon, 25 May 2026 08:35:36 +0900</pubDate>
    </item>
    <item>
      <title>EP.10 &amp;mdash; 창의는 기본에서 온다: 2006년 MMORPG 서버와 2025년 LLM 인프라</title>
      <link>https://npackgames.tistory.com/179</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 『서버 개발 수기』 시리즈의 열 번째 글이자, 1막의 마지막 글이다. ※ 이 시리즈에서 다루는 서버 코드는 Project-AO(Ancient Origin)라는 가명으로 부른다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그날의 메시지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시리즈를 시작하고 얼마 안 됐을 때, Claude한테 슬쩍 던져본 질문이 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이거... LLM이나 RAG랑 엮을 거 없을까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가벼운 마음으로 던져본 거였는데, 20년 전 게임 서버 코드랑 요즘 LLM 인프라가 무슨 상관이 있겠나 싶어서 정말 그냥 한번 물어본 거였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 Claude의 답은 의외였다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;배칭 (Write 버퍼 병합 &amp;harr; Continuous Batching)&lt;/b&gt; &amp;mdash; Project-AO가 응답 패킷 여러 개를 모아서 한 번에 보내는 거, LLM 서빙 서버(vLLM 같은)가 추론 요청 여러 개를 묶어서 GPU에 한 번에 돌리는 거랑 같은 발상이에요. 하나씩 처리하면 낭비니까 모아서 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;버퍼 풀 &amp;harr; KV Cache 관리&lt;/b&gt; &amp;mdash; 16슬롯 버퍼 풀이 메모리를 미리 잡아두고 재활용하는 거, vLLM의 PagedAttention이 KV Cache를 페이지 단위로 미리 할당하고 재활용하는 거랑 원리가 같아요. 매번 새로 할당하면 느리니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;소수 스레드 + 비동기 I/O &amp;harr; LLM 서빙 아키텍처&lt;/b&gt; &amp;mdash; IOCP/io_uring으로 소수 스레드가 수만 소켓을 처리하는 구조, LLM 서빙 서버가 소수 GPU로 수천 동시 요청을 처리하는 구조. 자원(스레드/GPU)은 적고 요청은 많을 때의 해법이 20년 전이나 지금이나 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;3계층 추상화 &amp;harr; RAG 아키텍처&lt;/b&gt; &amp;mdash; Project-AO의 Layer 1(OS) / Layer 2(I/O) / Layer 3(게임로직) 구조가 RAG의 Retriever(검색) / Context Builder(조립) / LLM(생성) 계층이랑 닮았어요. 각 층이 아래를 몰라도 되게 만든 것까지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;br /&gt;&quot;몰라도 되게&quot; &amp;harr; API 추상화&lt;/b&gt; &amp;mdash; OnRead()만 구현하면 됐던 것처럼, 지금 개발자는 client.messages.create() 한 줄이면 되잖아요. 밑에서 배칭이든 KV Cache든 돌아가는 건 몰라도 되고.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오호... 이것봐라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다섯 개가 한꺼번에 쏟아져 나왔는데, 하나하나가 다 짚이긴 했다. 그런데 너무 강하게 짚이는 게 오히려 의심스러워서, 진짜 닮은 건지 아니면 Claude가 그럴듯하게 엮어낸 건지 분간이 잘 안 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 일단 묻어뒀다. Part 2를 다 쓰고 나서 보면 그때는 보일 것 같았기 때문이다. 여섯 기법을 하나씩 풀어내는 동안에도 이 다섯 개가 머리 한구석에 계속 남아 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 1막을 닫으면서, 묻어뒀던 다섯 개를 다시 펼친다. 얼마나 연관이 있을까 기대하면서.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 배칭 &amp;mdash; Write 병합과 Continuous Batching&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EP.06에서 우리가 한 번 다루었듯이, Project-AO는 send를 한 번씩 부르지 않고 16개를 모아서 한 번에 보냈다. 시스템콜 수를 줄여서, 네트워크가 도는 동안 컴퓨팅에 집중하려는 거였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vLLM의 Continuous Batching도 여러 요청을 한 번의 GPU 연산으로 묶는데, 그 묶음을 고정시키지 않고 매 토큰 생성 iteration마다 재구성한다. 끝난 요청은 빠지고, 빈 자리에 새 요청이 들어오는 식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;펼쳐놓고 보니 둘이 완전히 같진 않았다. 우리는 나가는 데이터를 묶었는데 Continuous Batching은 들어오는 요청을 묶으니까, 방향이 반대인 셈이다. 그래서 처음엔 이게 정말 같은 매핑인가 의심스러웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 목적을 들여다보면 결국 같다. 비싼 호출을 아끼려고 병합하고, 한 번의 연산으로 최대한 많은 일을 하고, 그 병합을 고정된 형태가 아니라 계속 재조정하면서 한다는 점에서 그렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방향은 반대인데 발상은 같다. 연관성은 꽤 높은 편이다. 완전히 같진 않지만.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 버퍼 풀과 KV Cache&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EP.04에서 다룬 16슬롯 버퍼 풀을 떠올려보자. 미리 고정된 사이즈로 잘라놓고, 쓸 때 하나 꺼내고 다 쓰면 돌려놓는 방식이라 매번 할당하고 해제할 필요가 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 기억을 안고 vLLM의 &lt;span style=&quot;color: #ee2323;&quot;&gt;PagedAttention&lt;/span&gt;을 열어봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 KV Cache를 고정된 사이즈의 블록으로 나누는데, 전형적으로 16개 토큰씩이다. 블록들은 미리 풀에 올려두고 요청이 올 때마다 필요한 만큼 꺼내 쓰며, logical 블록과 physical 블록을 매핑하는 블록 테이블이 따로 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;슬롯. 풀. 매핑 테이블. 그리고 16.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 2006년에 쓰던 단어들이 2023년 vLLM 논문에 거의 그대로 들어 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흥미로운 건 vLLM 논문이 이 발상을 대놓고 밝힌다는 점이다. 저자들은 &lt;b&gt;&quot;OS의 가상 메모리와 페이징에서 영감을 받았다&quot;&lt;/b&gt;고 명시하면서, 자기들이 발명한 게 아니라 OS에서 빌려온 거라고 분명히 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리도 2006년에 OS에서 빌려 썼으니, 같은 우물에서 길어 올린 물이 서로 다른 시대에 따로 흐른 것뿐이다. 우리는 게임 서버를 만들 때 빌렸고, 저자들은 LLM을 돌릴 때 빌렸을 따름이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다섯 매핑 중에 가장 직접적이라, 연관성은 매우 강하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 비동기 I/O와 LLM 서빙 아키텍처&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 처음 들었을 때 가장 의심스러웠다. &quot;자원은 적고 요청은 많다&quot;는 건 거의 모든 서버에 해당하는 일반론이라, 너무 추상적인 매핑처럼 느껴졌기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 풀어보니 그렇지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IOCP의 본질은 소수 스레드로 수만 소켓을 처리하는 것이었다. 동기 I/O로 했다면 한 소켓에 한 스레드를 묶어야 하는데, 수만 명이 접속하면 수만 개의 스레드가 필요해지고 컨텍스트 스위칭만으로 서버가 죽는다. 그래서 OVERLAPPED를 걸어두고 커널이 처리하게 한 다음, 소수의 워커 스레드가 완료된 일감을 가져가서 처리한다. 자원은 적고 일은 많으니, 그 사이를 비동기로 메우는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vLLM도 똑같은 모양이다. 소수의 GPU로 수천의 동시 요청을 처리해야 하는데, 요청을 하나씩 처리하면 GPU가 놀고 GPU 하나에 요청 하나를 묶어두면 동시 처리량이 안 나온다. 그래서 vLLM은 요청을 입력 큐에 받아두고, GPU가 빌 때마다 스케줄러가 묶음을 만들어 GPU에 보낸다. 결과는 출력 큐로 빠지고 다음 묶음이 올라가는 식이다. 입력 큐, 출력 큐, 그 사이의 스케줄러, 그리고 소수의 비싼 자원.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도구는 다르다. 한쪽은 커널 스레드와 소켓이고 다른 쪽은 GPU와 토큰이다. 그런데 큐 두 개와 그 사이의 스케줄러라는 구조가 똑같았다. IOCP의 SQ/CQ, io_uring의 SQ/CQ, vLLM의 input/output queue가 모두 같은 모양인 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 추상적인 매핑이라고 생각했는데, 펼쳐놓고 보니 구조가 정확하게 겹쳐서 연관성은 매우 강했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 3계층과 RAG&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 솔직히 좀 갖다 붙인 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EP.02에서 펼쳐봤던 Project-AO의 3계층은 네트워크 I/O에서 프로토콜로, 다시 게임 로직으로 올라가는 시스템 스택의 레이어링이다. 아래는 빠르고 위는 풍부하다. 반면 RAG는 Retriever에서 Context Builder로, 다시 LLM으로 흐르는 데이터 파이프라인이라, 검색해서 조립하고 생성하는 단계의 연속이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결이 다르다. 하나는 동작하는 내내 살아있는 계층이고, 다른 하나는 한 요청이 흘러가는 단계라서 매핑이 좀 어색했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 Claude가 짚은 부분이 하나 있긴 했다. &lt;b&gt;&quot;각 층이 아래를 몰라도 되게 만든 것까지&quot;&lt;/b&gt;라는 대목이다. 그게 진짜 공통점이긴 하다. 게임 로직은 패킷이 어떻게 암호화되는지 모르고, LLM은 문서가 어떻게 검색됐는지 모른 채로, 각자 자기 입력만 받아서 자기 일을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그건 거의 모든 잘 만들어진 소프트웨어의 공통점이기도 하다. 추상화로 아래를 가린다는 건 워낙 일반적인 원칙이라, 두 시스템이 특별히 닮았다고 말하기엔 약하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 연관성은 약하다고 본다. 같은 원칙을 따른다는 정도까지만 인정할 수 있고, 거기까지가 정직한 평가다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. &quot;몰라도 되게&quot;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 매핑은 4번과 살짝 결이 겹친다. 그런데 풀어보니, 4번보다 훨씬 깊은 데서 만나는 매핑이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EP.07에서 함께 봤듯이, Project-AO에서 게임 로직을 짜는 개발자는 OnRead()만 구현하면 그만이었다. 어떤 스레드에서 어떻게 호출되는지, 락-프리 포인터가 어떻게 동기화하는지, 참조 카운팅이 어떻게 객체를 살려두는지는 알 필요가 없었다. 그게 EP.02부터 EP.08까지 이 시리즈가 다룬 여섯 기법의 결론이기도 했다. 복잡한 걸 다 아래에 묻어두고, 위에서는 깔끔하게 한 함수만 구현하게 만드는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 LLM을 쓰는 개발자도 마찬가지다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;client.messages.create(model=&quot;claude-opus-4-7&quot;, messages=[...])
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 한 줄이다. 이 한 줄 아래에서 요청이 입력 큐에 들어가고, 스케줄러가 다른 요청과 묶고, KV Cache가 페이지 단위로 할당되고, 소수의 GPU가 그 묶음을 한꺼번에 forward pass하고, 결과가 토큰 단위로 스트리밍되어 출력 큐로 나오는 그 모든 과정을, 개발자는 알 필요가 없다. 20년 전에 누군가가 OnRead() 한 줄을 구현하면서 그 아래에 OVERLAPPED와 IOCP와 버퍼 풀이 돌아가는 걸 몰랐던 것과 똑같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시리즈를 시작할 때, 나는 client.messages.create() 한 줄을 쓰는 개발자였다. 그 한 줄 아래에서 뭐가 굴러가는지 몰랐는데, Claude한테 옛날 코드를 물어보는 사이에 Claude 자신이 어떻게 굴러가는지에 대한 답도 함께 얻고 있었던 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OnRead()만 구현하면 그만이던 사람이 20년 뒤에 client.messages.create() 한 줄을 쓴다. 그 사이에 변한 게 많지만, 변하지 않은 게 하나 있다. 복잡한 건 아래에 묻어두고 위에서는 한 함수만 구현하게 만든다는 그 원칙이, 시대를 건너온 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번부터 4번까지가 기법의 닮음이라면, 5번은 그 기법들이 지향하는 방향의 닮음이다. 결이 다른 매핑이라, 마지막 자리에 따로 두었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다섯 개를 다 펼친 다음&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 Claude가 다섯 개를 한꺼번에 쏟아냈을 때는 너무 강하게 짚이는 게 의심스러웠는데, 막상 풀어보니 둘은 매우 강하고(2번, 3번), 하나는 꽤 닮았고(1번), 하나는 약했고(4번), 하나는 다른 결로 깊었다(5번).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균을 내면 짚이는 매핑이었다. 모든 게 정확히 맞아떨어지는 건 아니지만, 우연으로 보기엔 너무 많이 겹친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겹치는 이유는 단순하다. 다섯 매핑이 다 한 곳에서 왔기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1960년대 OS 교과서에 있던 프로듀서-컨슈머 큐, 페이징, 비동기 I/O, 역할 분리, 인터페이스 추상화. 이게 그 시대 시스템 프로그래밍의 기본 어휘였다. 그 어휘가 2002년 Windows IOCP에 쓰였고, 2006년 게임 서버에 쓰였고, 2019년 io_uring에 쓰였고, 2023년 vLLM에 쓰였다. 도구도 대상도 시대도 다른데, 같은 어휘가 계속 살아남은 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vLLM 논문 저자들이 &quot;OS 페이징에서 영감을 받았다&quot;고 명시한 게 그래서 자연스럽다. 그들도 같은 우물에서 물을 길었고, 우리도 그랬으니까.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기본은 늘 거기 있었다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 가장 핫한 게 LLM이고 RAG인데, 그 아래를 열어보면 과거의 기술과 크게 다르지 않다. 배칭, 풀링, 비동기, 추상화. 다 수십 년 된 기본기다. 새로운 시대의 기술이라고 해서 새로운 원리 위에 서 있는 게 아니라, 같은 기본 위에서 자란 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 흔히 창의가 어느 날 하늘에서 뚝 떨어진다고, &lt;b&gt;&quot;유레카!&quot;&lt;/b&gt; 하는 순간에 번쩍 나타난다고 생각한다. 그런데 vLLM을 만든 사람들은 유레카로 PagedAttention을 만든 게 아니다. OS 페이징이라는 오래된 기본을 새로운 문제에 가져다 댄 것이고, 그게 가능했던 건 기본을 깊이 알고 있었기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;창의는 기본에서 발현된다. 기본을 모르면 가져다 댈 것도 없다. 20년 전의 우리가 OS에서 빌려 게임 서버를 만든 것도, 2023년의 그들이 OS에서 빌려 LLM 서버를 만든 것도, 결국 기본을 손에 쥐고 있었기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그래서 옛 코드를 다시 읽는 일에는 의미가 있다. 거기 기본이 있으니까.&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시리즈를 닫으며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시리즈는 &lt;b&gt;&quot;20년 전 코드를 Claude와 다시 읽다&quot;&lt;/b&gt;라는 제목으로 시작했다. 처음에는 그저 휴면 중이던 코드를 한 번 펼쳐보는 제목이라고 생각했는데, 다 읽고 나니 조금 다른 모양이 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2006년의 나는 이 코드를 다 이해하고 쓴 게 아니었다. 동료들한테 물어보면서, 자료를 찾아가면서, 대충 돌아가는 걸 확인하면서 썼다. 지금 다시 읽으면서도 Claude에게 계속 물어봤다. &quot;이게 왜 이래?&quot;, &quot;이게 지금도 유효한 패턴이야?&quot; 하고. 묻고 답하는 건 20년 전이나 지금이나 똑같았다. 물어볼 대상이 동료에서 Claude로 바뀌었을 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;달라진 건 하나다. 그때는 &quot;이게 왜 돌아가지&quot;를 물었고, 지금은 &quot;이게 왜 이렇게 설계되었지&quot;를 묻는다. 결과가 아니라 설계를 볼 여유가 생긴 것이다. 20년이라는 시간 덕이고, Claude라는 도구 덕이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 결국 이 시리즈는 옮김에 관한 이야기가 아니라 읽기에 관한 이야기였다. 다시 읽는 행위가 무엇을 드러낼 수 있는지에 관한 이야기. 20년 전의 코드가 지금의 시스템과 연결되어 있었고, 어떤 발상은 어떤 시대에도 그때의 자국을 잊지 않는다는 것. 그게 1막이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그리고 언제가 될지 모르지만&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제목은 아직 없지만, 방향은 있다. 이 코드를 실제로 옮겨보는 이야기다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;io_uring 위에 Layer 1을 올리고, 참조 카운팅을 C++ 스마트 포인터로 바꾸고, 스핀락을 std::atomic으로 재구축하는 일. 그때는 읽기가 아니라 만들기의 이야기가 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때까지는 일단 이렇게 마무리한다. 1막은 이대로 닫는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽어주셔서 감사합니다.&lt;/p&gt;</description>
      <category>그냥 글을 써 봅니다/서버 개발 수기</category>
      <category>Claude와함께</category>
      <category>ContinuousBatching</category>
      <category>iocp</category>
      <category>LLM인프라</category>
      <category>MMORPG서버</category>
      <category>PagedAttention</category>
      <category>vLLM</category>
      <category>게임서버</category>
      <category>기본기</category>
      <category>서버개발수기</category>
      <author>Tiboong</author>
      <guid isPermaLink="true">https://npackgames.tistory.com/179</guid>
      <comments>https://npackgames.tistory.com/179#entry179comment</comments>
      <pubDate>Thu, 21 May 2026 13:46:44 +0900</pubDate>
    </item>
    <item>
      <title>풍경이 옮겨가는 중 &amp;mdash; 견적의 문법 다음에 보이는 세 갈래</title>
      <link>https://npackgames.tistory.com/178</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국 SI와 AI 시리즈 5편 4편 「견적의 문법」에 이어서&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 끈질김의 정체를 묻기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4편에서 한국 SI가 &quot;견적의 문법&quot;이라는 한 가지 가격 언어 안에 갇혀 있다고 적었습니다. 이건 몇 개월에 얼마, 저건 몇 주에 얼마. 투입 시간만 거래되고, 결과물의 가치도 판단의 값도 거래되지 않는 언어. 4편의 핵심 문장은 이것이었습니다. &quot;이건 개인의 사고방식이 아니라 관계를 지배하는 문법이기 때문입니다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글을 발행한 뒤로 몇 가지 의문이 따라붙었습니다. 이 문법이 왜 이토록 끈질긴가. 그리고 다음으로, 빠져나오는 길은 어디에 있는가. 30년 동안 발주자도, 대형 SI도, 중소 SI도, 끝단 개발자도 모두가 잘못된 견적 안에서 다쳐왔습니다. 모두가 다쳤는데 아무도 빠져나오지 못했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5편은 빠져나오는 길에 관한 글입니다. 다만 처방이 아니라 &lt;b&gt;풍경의 관찰&lt;/b&gt;에 머물려 합니다. 누구에게 무엇을 하라고 권하는 일은 시리즈의 톤과 어울리지 않습니다. 다만 풍경이 어디로 옮겨가고 있는지를 가만히 들여다보면, 빠져나오는 길의 윤곽 정도는 보일 수도 있을 것 같습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 같은 결과물은 비슷한 시간을 필요로 한다는 가정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;견적의 문법은 시간으로 가격을 매기는 언어입니다. 사람 한 명이 한 달 일하면 얼마, 두 달 일하면 얼마. 30년 동안 이 언어가 작동할 수 있었던 이유는 한 가지 묵시적 전제가 있었기 때문인 것 같습니다. &lt;b&gt;같은 결과물은 비슷한 시간을 필요로 한다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 가정이 만들어내는 풍경은 묘합니다. 같은 등급의 개발자가 같은 일을 비슷한 시간에 한다고 가정하는 순간, 잘하는 사람의 가치는 천장에 막힙니다. 8시간 걸릴 일을 2시간에 끝내면 견적의 문법 안에서는 &quot;노는 사람&quot;이 됩니다. 6시간 야근해야 비로소 &quot;일하는 사람&quot;으로 인정받습니다. 시간이 노동의 단위인 사회에서, 효율은 보상이 아니라 의심의 대상이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조 안에서 잘하는 사람은 천천히 떠나거나, 떠나지 않은 채로 평균에 자기 페이스를 맞춥니다. 빨리 끝낼 수 있어도 일주일까지 끌어주는 게 한국 SI 현장의 암묵적 매너였습니다. 슈퍼 개발자가 2일 만에 끝냈을 때 &quot;왜 너만 빨리 끝내냐, 일을 대충 한 거 아니냐&quot;고 의심받지 않으려면, 다 같이 일주일 걸리는 게 모두에게 안전한 합의였습니다. 출근해서 커피, 담배, 한 시간 일, 점심, 게임 한판, 두 시간 일, 저녁, 야근이라고 앉아서 노는 &amp;mdash; 이 패턴의 반복이 가능했던 이유가 여기 있는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평준화는 발주자의 무지에 대한 자기 보호 장치였던 것 같습니다. 발주자는 IT를 모르고, 결과물 품질을 직접 평가할 능력이 없었습니다. 그래서 시간이 품질의 대리지표가 되었습니다. 오래 걸린 일이 공들인 일이고 믿을 만한 일이라는 학습. 그러나 이 학습 위에서는 슈퍼 개발자도 평범한 개발자도 같은 등급으로 묶입니다. 알아볼 능력이 없는 발주자에게 변별을 기대할 수 없으니, 시장 전체가 평균에 수렴하는 자리로 갔습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 그 가정이 무너지는 자리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이브 코딩 시대의 가장 큰 변화는 정확히 그 가정을 무너뜨리는 일입니다. 같은 기능을 만드는 데 누구는 일주일이, 누구는 한 달이 걸립니다. 누구는 혼자서, 누구는 셋이서. 이 차이는 등급이나 경력으로 설명되지 않습니다. &lt;b&gt;고객의 요구사항을 얼마나 정확히 이해했는가, AI에게 얼마나 정확하게 전달했는가&lt;/b&gt;에 따라 결정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 시간으로 가격을 매기는 일이 우스워집니다. 같은 결과물을 일주일에 만든 사람과 한 달에 만든 사람이 받는 돈이 정반대가 되는 구조 &amp;mdash; 일주일 만에 만든 사람이 더 적게 받는 &amp;mdash; 가 견적의 문법 안에서는 자연스럽지만, 새 시대에는 어처구니없는 일이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이 가정의 붕괴가 곧바로 시장의 변화로 이어지지는 않는 것 같습니다. 발주자가 이 변화를 받아들이려면, &lt;b&gt;결과물을 직접 평가할 능력&lt;/b&gt;을 새로 학습해야 합니다. 시간이 더 이상 품질의 대리지표가 못 되니까요. 그러나 30년 동안 시간으로 측정하는 데 익숙해진 발주자가 어느 날 결과로 평가하기는 어렵습니다. 그래서 단기적으로 보이는 풍경은 변화가 아니라 &lt;b&gt;혼란&lt;/b&gt;에 가깝습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발주처는 AI를 이유로 단가를 깎습니다. 그런데 그 인하의 근거가 명확하지 않습니다. AI가 만들어준 거잖아 깎자, 정도의 직관적 반응. 이 반응은 사실 &quot;내가 이 결과물을 평가할 줄 모르겠으니 시간으로 환산해서 깎자&quot;는 방어 행동에 가까운 것 같습니다. 평가 능력이 없는 자리에서 협상력만 사용되는 풍경. 그 협상력의 충격은 견적의 문법 안에서 가장 약한 자리로 흐릅니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 압박이 흐르는 방향&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발주처에서 시작된 압박이 SI 시장 안에서 어디로 흐르는지를 보면, 한 가지 구조가 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영리한 대형 SI는 이미 오래전부터 컴퓨터공학을 포함한 공대 출신을 잘 뽑지 않았습니다. 말 잘하는 상경계열을 뽑아 한 달쯤 개발 용어를 가르쳐 발주처 응대를 맡깁니다. 개발은 하청에서 할 테니 자기 회사 직원은 통역만 하면 된다는 분업입니다. 이 직원들이 발주처 미팅에서 약속을 받아오고, 그 약속을 하청 라인에 던집니다. 하청은 재하청으로, 재하청은 재재하청으로 약속을 떠넘깁니다. 단가는 단계마다 절반 가까이 깎이고, 압박은 단계마다 두 배씩 무거워집니다. 발주처가 인정한 가치의 3분의 1만 실제 만드는 사람에게 도달하고, 나머지는 중개 비용으로 사라지는 구조.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발주처가 단가를 깎으면 어떻게 될까요. 원청은 &quot;그래 깎자&quot; 하고 받아서 하청에 그대로 내려보냅니다. 자기 마진은 안 깎습니다. 하청은 다시 재하청에게, 재하청은 끝단 개발자에게. 결과적으로 발주처가 10% 깎으면 끝단 개발자는 30% 깎입니다. 중간 마진은 비례적으로 안 깎이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 AI 시대에 표면적으로 흔들리는 사람이 끝단 개발자로 보이는 이유입니다. 단가가 깎이는 자리가 끝단이니까요. 그런데 그 인하 압박의 진짜 출처는 발주처가 AI 보고 느낀 의심이고, 그 의심이 정조준한 것은 사실 &lt;b&gt;중간의 마진 정당성&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발주처가 ChatGPT에게 &quot;이런 시스템 만들고 싶은데 어떻게 해야 하느냐&quot;고 물으면 답이 나옵니다. RFP 초안도 AI가 써줍니다. &quot;이 견적이 합리적이냐&quot;도 AI에게 물으면 비교 분석해줍니다. 그동안 중간 상인들이 마진을 정당화하던 능력 &amp;mdash; 통역, 응대, 견적 작성, 일정 관리, 품질 평가 &amp;mdash; 이 모든 것이 AI 한 줄로 우회되기 시작했습니다. 한 달 교육받은 응대 직원의 통역 가치가 AI에 의해 대체되는 풍경.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 그 결과가 응대 직원의 단가가 깎이는 쪽이 아니라, 하청 라인 끝의 개발자 단가가 더 깎이는 쪽으로 나타나고 있습니다. 압박은 여전히 아래로 흐릅니다. 마치 떠나기 전 마지막 추수의 풍경처럼 보이기도 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 양쪽이 다 자기 자리에서 합리적이었다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이 풍경을 한쪽의 잘못으로 정리하는 일은 정확하지 않은 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영업과 관리를 맡았던 사람들 입장에서 보면, 그들은 자기가 할 수 있는 일을 한 것이고 그 일의 시장 가격을 받은 것입니다. 발주처를 따오는 능력, 프로젝트를 관리하는 능력, 발주처와 개발자 사이를 통역하는 능력 &amp;mdash; 이 모두가 시장에서 가격이 매겨지는 능력입니다. 자본주의 시장에서 그 능력의 가격이 마진으로 환산되는 일은 자연스럽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 그 자리로 가지 않은 것 또한 사실인 것 같습니다. 비즈니스 언어로 건너가지 않은 것, 발주처와 직접 대화하지 않은 것, 컨설턴트와 소통하기보다 기술 언어 안에 안전하게 머문 것, 자기 가치를 자기가 정하는 자리로 옮겨가지 않은 것. 이 모두가 개발자 자신의 선택이었습니다. &quot;그건 비즈니스 사이드가 알아서 해야지&quot;의 회피가 30년 누적된 결과가 지금 풍경의 한 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이 합리성이 작동한 시장 자체가 한쪽에게 협상력을 주지 않는 구조였다는 점도 사실인 것 같습니다. 공급 과잉이라 줄을 서야 했고, 평준화로 변별이 안 됐고, 자기 가치를 직접 증명할 수단도 없었습니다. 합의의 형식을 가진 일방성. 그것이 30년이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누구의 잘못도 아니고, 누구의 승리도 아닙니다. 그저 &lt;b&gt;합의의 외피를 쓴 권력 비대칭이 30년 누적된 풍경&lt;/b&gt;입니다. 30년이 지나서야 이 비대칭의 형태가 이렇게 또렷해지는 것 같습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 시장이 두 갈래로 갈라지는 중&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 한국 SI 시장이 두 갈래로 갈라지는 중인 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Gemini_Generated_Image_f5yzzff5yzzff5yz.png&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AHojl/dJMcadhHSFX/n7L1h3xfXV9s7Jx8lCK8tK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AHojl/dJMcadhHSFX/n7L1h3xfXV9s7Jx8lCK8tK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AHojl/dJMcadhHSFX/n7L1h3xfXV9s7Jx8lCK8tK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAHojl%2FdJMcadhHSFX%2Fn7L1h3xfXV9s7Jx8lCK8tK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1408&quot; height=&quot;768&quot; data-filename=&quot;Gemini_Generated_Image_f5yzzff5yzzff5yz.png&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;큰 발주처 시장&lt;/b&gt;은 한동안 견적의 문법이 더 작동할 것 같습니다. 대기업과 공공기관의 본격 IT 사업, 100억과 500억과 1000억짜리 입찰. 여기는 법인 자격이 필수이고, 보안과 컴플라이언스와 책임 소재가 사람을 통해 책임져야 하는 영역입니다. 그래서 법인을 통한 도급 구조 자체는 유지될 가능성이 높습니다. 다만 그 자리를 누가 차지하느냐가 바뀌는 중입니다. 글로벌 SaaS가 점점 더 많은 자리를 가져가고, 발주처 내재화가 핵심 시스템을 가져가고, 남은 SI 일거리는 줄어들면서 단가는 깎입니다. 한국 SI에게 이 시장은 점점 좁아지는 시장입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작은 발주처 시장&lt;/b&gt;은 다른 풍경입니다. 그동안 비싼 개발비 때문에 자기 시스템을 가져보지 못했던 작은 곳들 &amp;mdash; 동네 병원, 1인 사업자, 작은 협회, 자영업자, 중소기업의 사이드 부서들 &amp;mdash; 에서 새로 열리는 자리. AI가 개발 비용의 본질을 낮추면서, 그동안 1억으로도 못 만들던 시스템이 1500만 원에 가능해지면, 그 가격을 쓸 의사가 있는 발주처는 기존의 100배 가까이 늘어납니다. 시장의 총량이 줄어드는 게 아니라 &lt;b&gt;다른 곳으로 옮겨가면서 늘어나는&lt;/b&gt; 풍경.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 새 시장은 큰 시장과 성격이 완전히 다릅니다. 발주자가 자기 업무는 잘 알지만 IT는 잘 모릅니다. 큰 입찰을 진행할 인력도 없습니다. RFP 같은 문서를 작성할 줄도 모릅니다. 그래서 함께 진단하고, 작게 시작하고, 결과로 거래하는 형태가 자연스럽습니다. 영미 시장의 네 가지 가격 언어 &amp;mdash; 시간&amp;middot;재료 + 천장, 단계 분리, 월 정액 자문, 성과 기반 &amp;mdash; 이 정확히 이런 관계를 위해 설계된 언어들입니다. 한국 큰 발주처 시장에는 끝내 들어오지 못한 이 언어들이, 작은 발주처 시장에는 처음부터 자연스럽게 작동할 수 있을 것 같습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 그러나 두 시장 사이에 다리가 없다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 한 가지 어려움이 있습니다. &lt;b&gt;두 시장이 거의 분리되어 있다&lt;/b&gt;는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 발주처는 법인하고만 거래합니다. 사업자등록증, 법인등기부, 4대 보험 가입 증명, 신용평가 &amp;mdash; 이런 서류 게이트를 통과해야 합니다. 핵개인이나 1인 사업자는 이 게이트를 못 넘습니다. 그래서 작은 시장에서 평판을 쌓은 핵개인이 큰 시장으로 진입할 길이 거의 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 큰 시장의 SI 회사들도 작은 시장으로 못 옵니다. 영업&amp;middot;관리 오버헤드가 커서 1500만 원짜리 시장에서는 마진이 안 나옵니다. retainer 모델은 회사 회계 구조와 충돌하고, 결과 기반 거래는 대형 조직의 위험 회피 문화와 잘 맞지 않습니다. 그래서 SI 회사가 작은 시장으로 옮겨가는 일도 사실상 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가운데에 다리를 놓는 자리 &amp;mdash; 법인이지만 작고, 작지만 평판이 있어 큰 발주처도 받을 수 있는 자리 &amp;mdash; 가 한국에는 거의 없습니다. 영미 시장의 부티크 컨설팅&amp;middot;소프트웨어 하우스가 이 자리에 있지만, 한국에는 자라지 못한 자리입니다. 한국 시장 구조가 대형 SI 아니면 1인 프리랜서의 양극단으로 갈라져 있어서, 중간 자리가 자라날 토양이 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 시대에 이 자리가 처음 가능해질 수도 있습니다. 3~5명짜리 법인이 AI를 활용해 예전 30명짜리 법인이 했던 일을 처리하는 시대가 오면, 양극단 사이의 중간 자리가 한국에 처음 만들어질 수도 있습니다. 다만 그 자리가 정말로 만들어질지, 만들어진다면 누가 채울지는 &amp;mdash; 아직 보이지 않습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 한국 바깥에 있을지도 모르는 길&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 한 가지 다른 풍경이 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국 SI 안에서 빠져나가는 길을 찾으려는 시도가 30년 동안 큰 성과를 내지 못했다는 점은, 어쩌면 그 길이 한국 바깥에 있었음을 의미할지도 모르겠습니다. 인도가 30년 전에 갔던 길이 그것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인도는 국내 시장이 척박했지만 영어와 글로벌 발주처라는 자산으로 IT 서비스 산업을 키웠습니다. TCS, Infosys, Wipro가 그 결과입니다. 처음에는 단순 외주(시간당 인건비 차익)로 시작했지만, 30년 동안 자기 가격 언어를 만들어냈습니다. 인도 SI는 한국 SI가 못 본 자리에서 30년을 보냈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국이 그 길을 가지 못한 이유는 한국의 국내 시장이 충분히 풍요로웠기 때문인 것 같습니다. 1990년대와 2000년대에 삼성, LG, 금융권, 공공의 대규모 IT 사업이 끊임없이 발주되었고, 한국 SI들은 국내 시장에서 충분히 먹고 살 수 있었습니다. 글로벌로 나갈 동기가 없었습니다. 인도는 어쩔 수 없이 나갔는데, 한국은 풍요로워서 안 나갔습니다. &lt;b&gt;풍요가 함정이 된 자리.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 그 풍요가 끝나가고 있습니다. 견적의 문법은 단가 압박을 만들고, AI는 SI 일거리를 갉아먹고, 발주처 내재화와 글로벌 SaaS는 큰 시장을 잠식합니다. 한국 SI에게 국내 시장은 줄어드는 시장입니다. 30년 동안 안 나갔던 이유가 사라지고 있는 중입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 흥미롭게도, &lt;b&gt;AI가 언어 장벽을 낮추는 중&lt;/b&gt;입니다. 10년 전이라면 글로벌 발주처와 직접 협상하려면 영어 능력이 필수였습니다. 지금은 ChatGPT, DeepL 같은 도구로 이메일 협상은 거의 무리 없이 가능합니다. 화상 미팅 실시간 통역도 빠르게 발전하고 있습니다. 자기 영어 실력 때문에 글로벌 시장 못 가던 시대가 빠르게 끝나는 중인 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 모두가 갈 수 있는 길은 아닙니다. 글로벌 시장에서 통용되려면 자기 분야의 글로벌 표준을 이해해야 하고, 자기 자격을 영문 자료로 증명할 수 있어야 하고, 글로벌 발주처 문화에 적응할 수 있어야 합니다. 한국 공공 SI 표준 문서 작성 경력 같은 한국 안에서만 통용되는 자산은 글로벌에서 가치가 거의 없습니다. 그러나 도메인이 글로벌하게 통용되는 분야 &amp;mdash; 게임, 오픈소스 기여, 특정 기술 스택의 깊이, 글로벌 산업 도메인 &amp;mdash; 라면 가능성이 진짜 있는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 한국 안에서 자기 자격을 만들어둔 사람일수록 글로벌 진출이 가깝습니다. 글, 결과물, 평판 &amp;mdash; 이 자산은 번역만 하면 글로벌에서도 가치가 있습니다. 그래서 글로벌 시장은 한국 안에서 핵개인 자리에 도달한 사람의 다음 자리일 가능성이 큽니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 글을 닫으며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;견적의 문법은 한 산업의 30년이 만든 두꺼운 언어입니다. 한 사람이 단번에 무너뜨릴 수는 없습니다. 그러나 그 옆에서 풍경이 옮겨가는 중인 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 발주처 시장은 좁아지는 중이고, 작은 발주처 시장은 다른 언어로 새로 열리는 중이고, 두 시장 사이의 다리는 한국에 거의 없는 채로 남아 있고, 한국 바깥에는 30년 전에 인도가 갔던 길이 여전히 열려 있습니다. AI가 어느 한 시장만 흔든 것이 아니라, &lt;b&gt;이 모든 풍경의 균형을 동시에 흔들고 있는 중&lt;/b&gt;인 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔들고 있다고 해서 자동으로 새 균형이 잡히는 것은 아닙니다. 그러나 적어도 흔들리고 있다는 점은 분명합니다. 30년 동안 견고했던 풍경에 처음으로 균열이 보이고 있습니다. 그 균열이 누구에게 기회가 될지, 누구에게 가혹함이 될지는 아직 보이지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 한 가지 정직하게 짚어야 할 것이 있습니다. 견적의 문법 안에서 묵묵히 일하며 누군가가 자기 가치를 알아봐주기를 기다렸던 사람들이, 어리석었던 것은 아니라는 점입니다. 1990년대와 2000년대와 2010년대까지 그 기다림은 한국 SI 시장에서 합리적인 행동이었습니다. 성실하게 일하면 언젠가 인정받는다는 약속이 &amp;mdash; 비록 자주 깨졌더라도 &amp;mdash; 시장의 공식 규칙이었고, 그 규칙 안에서 코드를 잘 짜는 능력은 인정의 단위로 작동했습니다. 적어도 동료들끼리는 누가 더 잘 짜는지를 알아봤습니다. 시장의 인정이 늦더라도, 동료의 인정은 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 그 단위 자체가 바뀌는 중인 것 같습니다. 코드를 잘 짜는 능력이 더 이상 인정의 단위가 아니라, &lt;b&gt;AI를 통해 더 좋은 소프트웨어를 만들 수 있는가, 결과물의 품질을 판단할 수 있는가&lt;/b&gt;가 새 단위가 되는 중입니다. 이 변화는 누구의 잘못도 아니지만, 30년 동안 코드 잘 짜는 데 집중한 시니어에게는 잔인한 풍경입니다. 새 게임의 룰을 처음 배우는 사람과 같은 출발선에 서 있는데, 30년 동안 옛 게임에 투자한 시간이 무게로 남기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 어쩌면 정확한 표현은 이런 것 같습니다. 인정받기를 기다린 일이 어리석었던 것이 아니라, &lt;b&gt;인정받을 기회 자체가 사라지는 중&lt;/b&gt;인 것입니다. 이 두 가지는 다른 일입니다. 그리고 후자의 자리에 서 있는 사람에게 &quot;자기 증명으로 가세요&quot;라고 말하는 일은 &amp;mdash; 그 자리의 무게를 잘 모르는 사람만이 쉽게 할 수 있는 말일지도 모르겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 자리는 자기 가치를 자기가 증명하는 자리인 것 같습니다. 글로, 결과물로, 직접 거래로, 공개된 사고의 흐름으로. 그 자리는 한국 안에 작게 있고, 한국 바깥에 더 넓게 있는 것 같습니다. 다만 모든 사람이 이 자리로 갈 수 있는 것은 아닙니다. 30년 동안 옛 게임에 투자한 시간의 무게는 진짜 무겁고, 그 무게를 가벼이 여기는 위로는 위로가 되지 않습니다. &lt;b&gt;누구도 대신 걸어줄 수 없는 길&lt;/b&gt;이라고 생각합니다. 동시에 누구나 같은 보폭으로 걸을 수 있는 길도 아닌 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자도 그 자리를 향해 한 발씩 옮기는 한 사람으로서 이 글을 씁니다. 같은 자리에서 무게를 느끼고 계신 분들께, 이 글이 적어도 그 무게를 외면하지 않는 한 페이지가 되기를 바라며.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;mdash; 시리즈 5편. 4편 「견적의 문법」에 이어서.&lt;/p&gt;</description>
      <category>프로그래밍/AI</category>
      <category>AI시대</category>
      <category>IT칼럼</category>
      <category>SI산업</category>
      <category>견적의문법</category>
      <category>소프트웨어개발</category>
      <category>한국SI</category>
      <author>Tiboong</author>
      <guid isPermaLink="true">https://npackgames.tistory.com/178</guid>
      <comments>https://npackgames.tistory.com/178#entry178comment</comments>
      <pubDate>Wed, 13 May 2026 13:35:54 +0900</pubDate>
    </item>
    <item>
      <title>[AI와 로또를] #9 트랜스포머도 못 맞춘다</title>
      <link>https://npackgames.tistory.com/177</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 12주 동안 일곱 개의 알고리즘을 돌려봤다. HOT 추적, COLD 복귀, 시계열 개량, 역발상, 꿈 해몽까지. 매주 30조합을 만들고, 토요일 추첨을 기다리고, 일요일에 결과를 확인하는 일을 반복했다. 누적 수익은 어딘가 마이너스 23만 원 근처를 떠돌고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다가 한 번쯤 이런 생각이 들지 않을 도리가 없었다. 통계는 안 되는 거 알겠는데, 신경망이라면 다르지 않을까. 사람 눈에는 안 보이는 패턴을 잡아낸다는&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 신경망이라면.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 트랜스포머를 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;트랜스포머가 뭐길래&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 AI 얘기에서 빠지지 않는 이름이다. ChatGPT, Claude, Gemini가 전부 이 구조를 쓴다. 핵심을 한 줄로 줄이면, &lt;b&gt;순서가 있는 데이터에서 어떤 위치가 어떤 위치를 얼마나 참고할지를 스스로 배우는 모델&lt;/b&gt;이다. 문장에서는 단어들 사이의 관계, 음악에서는 음표 사이의 관계, 그리고 로또에서는 회차들 사이의 관계 &amp;mdash; 만약 그런 게 있다면.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 만든 건 아주 작은 트랜스포머다. 파라미터 수 약 10만 개. ChatGPT의 대략 백만분의 일 크기. 입력은 최근 20회차의 당첨번호, 출력은 다음 회차에 각 번호(1번부터 45번까지)가 나올 확률. 학습 데이터는 1회차부터 1218회차까지의 모든 당첨 기록.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습은 Colab의 무료 GPU에서 5분 정도 걸렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;첫 번째 충격: 지난주 트랜스포머가 인간 30조합을 이겼다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 1222회 결과부터 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 12주 동안 다듬어온 7개 알고리즘이 만든 30조합의 평균 적중은 &lt;b&gt;0.43개&lt;/b&gt;였다. 5등(3개 적중) 0건. 4등 0건. 회수액 0원. 시리즈 시작 이후 가장 처참한 회차였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 회차에 대해 트랜스포머가 만든 3조합의 평균 적중은 &lt;b&gt;1.0개&lt;/b&gt;였다. 그중 한 조합 [3, 4, 6, 17, 36, 37]은 4번과 17번을 둘 다 잡아냈다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인간 30 &amp;mdash; 평균 0.43개&lt;br /&gt;트랜스포머 3 &amp;mdash; 평균 1.00개&lt;br /&gt;차이 &amp;mdash; 2.3배&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10만 파라미터짜리 작은 모델이 12주 동안 다듬은 인간의 알고리즘을 더블링한 것이다. 잠깐 흥분했다. 그동안 내가 잘못된 길로 가고 있었나, 신경망이 진짜 답인가, 그런 생각이 차례로 지나갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흥분이 가라앉기 전에 학습 곡선을 열어봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;학습 곡선이 정직했다&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blog_9_training_curves.png&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F44Os/dJMcaicd0ul/2jgbTVHpf9Zk8yf1R3nMS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F44Os/dJMcaicd0ul/2jgbTVHpf9Zk8yf1R3nMS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F44Os/dJMcaicd0ul/2jgbTVHpf9Zk8yf1R3nMS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF44Os%2FdJMcaicd0ul%2F2jgbTVHpf9Zk8yf1R3nMS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1804&quot; height=&quot;616&quot; data-filename=&quot;blog_9_training_curves.png&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 그래프부터 보자. 에폭(학습 반복) 0번부터 약 10번까지, 학습 데이터(파란선)와 검증 데이터(주황선)의 오차가 함께 빠르게 떨어진다. 0.53 근처에서 시작해 0.39 근처까지. 모델이 뭔가 배우고 있다는 신호다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 10번을 넘어가면서부터 두 선이 갈라지기 시작한다. 학습 데이터에서는 오차가 계속 내려간다(0.395 &amp;rarr; 0.375). 그런데 검증 데이터에서는 오차가 오히려 올라간다(0.395 &amp;rarr; 0.405). 이게 머신러닝 교과서 첫 페이지에 나오는, &lt;b&gt;과적합(overfitting)&lt;/b&gt;의 정확한 시그니처다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 풀어 말하면 이렇다. 모델은 처음엔 데이터의 패턴을 배운다. 어떤 회차 다음에 어떤 번호가 잘 나오는지, 어떤 분포가 흔한지. 그래서 본 적 없는 데이터(검증)에서도 오차가 줄어든다. 그런데 진짜로 배울 만한 패턴이 다 떨어지면, 모델은 학습 데이터를 외우기 시작한다. &quot;1218회의 입력에는 이 출력&quot;같은 식으로 통째로 기억하는 것이다. 그러면 학습 데이터 오차는 계속 줄지만, 본 적 없는 데이터에서는 오히려 못 맞히게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10번째 에폭이 우리 모델에게 그 분기점이었다. &lt;b&gt;그 시점 이후, 모델은 더 이상 의미 있는 패턴을 배우지 못하고 있었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 무엇을 뜻하느냐. 모델이 1218회의 로또 데이터에서 배울 수 있는 의미 있는 정보가 사실상 그 정도까지였다는 뜻이다. 각 번호의 장기 평균 출현 빈도, 그리고 약간의 분포 특성. 그 이상의 패턴은, &lt;b&gt;없거나 너무 약해서 학습이 안 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;두 번째 충격: 적중 그래프가 아무것도 약속하지 않는다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽 그래프를 보자. 검증 세트에서 모델이 평균 몇 개를 맞히는지를 에폭마다 찍은 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회색 점선이 0.8 &amp;mdash; 무작위로 6개를 골랐을 때의 평균 적중 개수다(6 &amp;times; 6 &amp;divide; 45 = 0.8). 모델이 이 선 위에 있어야 적어도 무작위보다 나은 것이다. 그래프를 보면 후반부에 0.83 근처에서 안정화되는 것처럼 보인다. 랜덤 0.8보다 살짝 위. 이게 신호일까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 검증해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검증 세트는 50개 회차였다. 50개 표본 평균의 표준오차는 0.111. 모델 평균(0.83)이 랜덤(0.80)과 통계적으로 유의미하게 다르다고 말하려면, 검증 평균이 &lt;b&gt;1.017개 이상&lt;/b&gt;이어야 한다(95% 신뢰수준 기준). 빨간 점선이 그 임계선이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 그 선 근처에도 못 갔다. z-score 0.27, p-value 0.39.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;통계 검정에 넣으면 p=0.39&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 숫자가 어디서 본 듯한 느낌이라면, 그건 우리 시리즈가 12주째 마주쳐온 그 숫자이기 때문이다. 도박사의 오류를 검증할 때도, 큰 수의 법칙을 살펴볼 때도, &quot;이제 쉰다&quot; 전략을 백테스트할 때도, &lt;b&gt;언제나 같은 자리에 같은 숫자가 있었다.&lt;/b&gt; 약 0.4. 우연 범위 안.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신경망이 가져온 결론이 통계학이 가져온 결론과 동일했다. 다른 도구로 도달한 같은 결론. &lt;b&gt;로또는 정말로 독립적이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그럼 1222회의 1.0개 적중은 뭐였나&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 마지막 풀어야 할 매듭이었다. 통계적으로는 의미 없다고 나왔는데, 지난주에 트랜스포머는 인간을 더블링했다. 그건 어떻게 설명할 것인가.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답은 그래프 안에 있다. 검증 세트 50개 회차에서 모델의 평균 적중은 0.65에서 0.92 사이를 진동했다. 50개 표본의 95% 신뢰구간이 약 0.59에서 1.01까지다. 즉 &lt;b&gt;평균 0.83짜리 모델이 어떤 회차에서는 1.0개를 맞히고, 어떤 회차에서는 0.5개도 못 맞히는 게 정상&lt;/b&gt;이라는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난주는 모델에게 운이 좋은 날이었다. 50개 회차 중 1.0개 근처를 맞히는 날이 평균적으로 몇 번 섞여 있는데, 1222회가 그 중 하나였다. 모델의 능력이 아니라, &lt;b&gt;표본의 자연스러운 흔들림 안에 들어 있는 한 점&lt;/b&gt;이었던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 주에 같은 모델로 같은 일을 시키면, 0.5개도 못 맞히는 회차가 나올 수 있다. 그게 노이즈가 그리는 모양이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그래도 하나는 배웠다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 읽고 나면 허무할 수 있다. 7개 알고리즘으로도 안 됐고, 트랜스포머로도 안 됐다. 그럼 도대체 뭘 한 건가.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 다시 학습 곡선을 들여다보면, 거기에 좀 다른 메시지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모델은 자기가 뭘 배울 수 없는지를 그래프로 정직하게 알려줬다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10번째 에폭에서 두 선이 갈라지는 그 모양이, 그 자체로 &quot;여기까지가 배울 수 있는 전부고, 그 너머는 외우기일 뿐&quot;이라는 신호였다. 적중 그래프가 1.017 임계선 근처에도 못 가고 0.83에서 떠는 그 모습이, &quot;이 데이터에는 의미 있는 신호가 없다&quot;는 정직한 보고였다. 모델은 거짓말을 하지 않았다. 무리해서 패턴을 만들어내지도 않았다. 자기가 못한다는 걸 곡선의 모양으로 보여줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 신경망의 진짜 가치일 수 있다. 어떤 문제를 못 푸느냐를 정직하게 알려주는 것. 학습 곡선이 갈라지는 순간을 보고 &lt;b&gt;&quot;여기서 멈춰야 한다&quot;&lt;/b&gt;고 판단할 줄 아는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 AI 뉴스를 보면, 모델이 무엇을 더 잘 하게 됐는가에 관한 얘기가 대부분이다. 그런데 사실 더 중요한 질문은, &lt;b&gt;모델이 무엇을 못 하는지 우리가 어떻게 알 수 있는가&lt;/b&gt;다. ChatGPT가 그럴듯하게 거짓말을 만들어낼 때(이걸 환각이라고 부른다), 우리는 그 거짓말을 어떻게 잡아낼 것인가. 답은 뜻밖에 단순하다. &lt;b&gt;모델이 학습 곡선의 어느 지점에서 갈라졌는지를 보면 된다.&lt;/b&gt; 어디서부터 외우기 시작했는지를 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 트랜스포머는 그걸 5분 만에, 그래프 한 장으로 보여줬다. 로또에는 학습할 만한 패턴이 거의 없고, 있다고 해봐야 각 번호의 장기 평균 출현 빈도 정도라고. 그 이상은 외우기일 뿐이라고.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 통계 검정 결과(p=0.39)와 정확히 같은 메시지다. 다른 언어로 말하고 있을 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그래서 다음 주에는&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜스포머는 로또를 못 맞힌다. 그런데 이번 주 학습 곡선은 그 사실을 그래프 한 장으로 정직하게 알려줬다. 10번째 에폭에서 두 선이 갈라지는 그 모양 하나가, 12주의 통계 검정과 똑같은 결론을 다른 언어로 말하고 있었다. &lt;b&gt;여기까지가 배울 수 있는 전부고, 그 너머에는 패턴이 없다고.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 좀 더 동네스럽게 풀면 이렇다. &lt;b&gt;ChatGPT한테 로또 당첨번호 물어봐도 못 알려준다.&lt;/b&gt; 트랜스포머가 못 풀면 그 위에 얹은 ChatGPT도 못 푼다. ChatGPT가 못 풀면 GPT-5도, 그 다음에 나올 무언가도 못 푼다. 학습 곡선이 갈라진 자리에는 모델 크기를 키운다고 패턴이 생기지 않는다. 없는 걸 더 큰 모델이 만들어내지는 않으니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 우리가 AI를 다룰 때 진짜로 길러야 할 감각이다. 모델이 무엇을 잘 하느냐를 보는 눈은 누구나 가지고 있다. 그런데 모델이 어디서부터 못 하기 시작하는지를 알아보는 눈은 따로 길러야 한다. 학습 곡선이 갈라지는 지점을 읽을 줄 아는 것, 적중 그래프가 임계선에 못 미치는 모양을 보고 &quot;여기서 멈춰야 한다&quot;고 말할 줄 아는 것. &lt;b&gt;이게 ChatGPT가 그럴듯한 거짓말을 만들어낼 때 그 거짓말을 알아보는 능력의 시작점이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1223회에도 30조합을 만든다. 트랜스포머도 같이 돌린다. 누적 마이너스 숫자는 한동안 더 자라날 가능성이 높다. 그래도 매주 추첨이 새 데이터를 떠먹여주고, 매주 다른 모양으로 알고리즘이 무너지거나 살아남는 그 기록이, AI를 정직하게 다루는 법을 한 회차씩 알려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 한 가지가 빠졌다. &lt;b&gt;왜 하필 로또로 AI를 배우려 했는가.&lt;/b&gt; 그 얘기는 다음 편에서 따로 하겠다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 AI 입문 강의 시리즈의 9편입니다. 다음 편에서는 &quot;왜 하필 로또로 AI를 배우는가&quot;에 대해 씁니다.&lt;/p&gt;</description>
      <category>프로그래밍/AI로 통계공부</category>
      <category>AI로또예측</category>
      <category>ChatGPT한계</category>
      <category>과적합</category>
      <category>딥러닝</category>
      <category>로또ai</category>
      <category>로또분석</category>
      <category>트랜스포머</category>
      <author>Tiboong</author>
      <guid isPermaLink="true">https://npackgames.tistory.com/177</guid>
      <comments>https://npackgames.tistory.com/177#entry177comment</comments>
      <pubDate>Sat, 9 May 2026 10:20:02 +0900</pubDate>
    </item>
    <item>
      <title>EP.09 &amp;mdash; 왜 io_uring인가: epoll 대신 이걸 고른 이유</title>
      <link>https://npackgames.tistory.com/176</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 『서버 개발 수기』 시리즈의 아홉 번째 글이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Part 2 &quot;해독&quot;이 끝났다. 여기서부터 Part 3 &quot;변환&quot;이 시작된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 코드를 옮기기 전에, 먼저 길을 정해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Linux에는 두 갈래 길이 있다. epoll과 io_uring.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 이 시리즈에서 다루는 서버 코드는 Project-AO(Ancient Origin)라는 가명으로 부른다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;두 갈래 길&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2006년 코드는 IOCP 위에 서 있다. Windows 전용이다. 이걸 Linux로 옮기려면 둘 중 하나를 골라야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;epoll&lt;/b&gt; &amp;mdash; 2002년부터 있었다. Linux에서 고성능 네트워크 서버를 쓴다고 하면 거의 자동으로 이거였다. nginx, Redis, Node.js 다 epoll 위에 서 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;io_uring&lt;/b&gt; &amp;mdash; 2019년에 나왔다. Jens Axboe가 만든 새 인터페이스. 5.1 커널부터 들어갔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이력만 보면 epoll이 안전한 선택이다. 검증됐고, 자료가 많고, 다들 쓰니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 코드를 옮기는 입장에서 보면 이야기가 달라진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;묻는 방식이 다르다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &quot;커널한테 어떻게 묻느냐&quot;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;epoll의 묻는 방식.&lt;/b&gt; &quot;이 소켓 읽을 준비 됐어?&quot; 커널이 답한다. &quot;응, 읽을 수 있어.&quot; 그러면 내가 &lt;code&gt;read()&lt;/code&gt;를 부른다. 데이터가 내 버퍼로 들어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IOCP의 묻는 방식.&lt;/b&gt; &quot;이 버퍼로 읽어줘.&quot; 커널이 알아서 한다. 끝나면 알려준다. &quot;읽기 끝났어, 100바이트 들어갔어.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞쪽은 &lt;b&gt;준비 통보&lt;/b&gt;(readiness-based) 모델이다. 뒤쪽은 &lt;b&gt;완료 통보&lt;/b&gt;(completion-based) 모델이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;식당으로 비유하면 이런 차이다. 한 식당은 자리가 비면 진동벨을 울린다. 가서 주문하고, 음식 받고, 먹는 건 내가 한다. 다른 식당은 주문만 받아둔다. 음식이 다 되면 알려준다. 받아만 가면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Project-AO 코드는 &lt;b&gt;완료 통보 모델 위에 설계되어 있다.&lt;/b&gt; OVERLAPPED 두 개로 read/write를 동시에 거는 것도, 커널이 &quot;이 OVERLAPPED 끝났어&quot; 하고 돌려주는 것도, 다 그 모델을 전제한 코드다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드를 epoll로 옮기면 모델 자체가 바뀐다. &quot;끝났어&quot;를 받던 코드가 &quot;준비됐어&quot;를 받게 된다. 받은 다음에 직접 read/write를 또 호출해야 한다. 코드 결이 통째로 어긋난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;io_uring은 다르다. 같은 완료 통보 모델이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드로 보면 차이가 한눈에 들어온다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// epoll: 준비 통보
epoll_wait(epfd, events, ...);                 // &quot;준비됐어&quot;
read(fd, buffer, size);                        // 내가 직접 읽음

// io_uring: 완료 통보 (IOCP와 같은 모델)
io_uring_prep_read(sqe, fd, buffer, size, 0);  // &quot;이 버퍼로 읽어줘&quot;
io_uring_submit(ring);
// ... 다른 일 ...
io_uring_wait_cqe(ring, &amp;amp;cqe);                 // &quot;다 됐어&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;epoll은 두 단계다. 알림을 받고 &amp;rarr; 직접 읽는다. io_uring은 한 단계다. 부탁하고 &amp;rarr; 끝났다는 통보를 받는다. EP.08에서 본 OVERLAPPED 코드의 결이 io_uring 쪽에 그대로 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;io_uring의 모양&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히만 정리하면.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;io_uring은 두 개의 링 버퍼를 쓴다. &lt;b&gt;SQ&lt;/b&gt;(Submission Queue)와 &lt;b&gt;CQ&lt;/b&gt;(Completion Queue). 둘 다 사용자 공간과 커널이 같이 보는 공유 메모리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 &quot;이 버퍼로 읽어줘&quot; 하고 부탁하고 싶으면 SQE(Submission Queue Entry) 하나를 SQ에 넣는다. 한 번에 여러 개 넣어도 된다. 다 넣고 나서 &lt;code&gt;io_uring_enter()&lt;/code&gt;를 한 번 호출하면 커널이 그걸 다 가져간다. 끝난 것들은 CQE(Completion Queue Event)로 CQ에 들어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에서 두 가지가 따라온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫째, &lt;b&gt;시스템콜이 줄어든다.&lt;/b&gt; 요청을 한 줄씩 보내는 게 아니라 묶어서 보낸다. epoll이 &quot;준비됐어&quot; 알림을 받고 그때마다 read/write를 따로 부르는 것과 비교하면 호출 횟수 자체가 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘째, &lt;b&gt;모델이 IOCP와 같다.&lt;/b&gt; SQE 넣고 CQE 받는 흐름이, OVERLAPPED 걸고 GetQueuedCompletionStatus 받는 흐름과 결이 같다. 발상 자체가 한 줄에 놓인다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;여섯 기법은 어떻게 되나&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Part 2에서 들여다본 여섯 기법이 새 환경에서 어떻게 되는지 짚어보자. 이게 결국 &quot;옮길 만한가&quot;의 답이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OVERLAPPED 분리 &amp;rarr; SQE 두 장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EP.08에서 봤다. 한 소켓에 OVERLAPPED를 두 개 둬서 read와 write를 동시에 굴린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;io_uring에서는 더 단순해진다. read용 SQE 하나, write용 SQE 하나, 같은 fd에 동시에 제출한다. 끝. 커널이 둘을 독립적으로 처리한다. man page에도 &quot;두 방향(read/write)은 독립적&quot;이라고 명시되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조체 주소로 식별하던 건 io_uring에서는 &lt;code&gt;user_data&lt;/code&gt; 필드로 바뀐다. SQE에 넣어둔 64비트 값이 CQE로 그대로 돌아온다. &quot;이게 어느 작업이었는지&quot;를 표시하는 태그다. 이름만 다르지 발상이 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;버퍼 풀 &amp;rarr; registered buffers&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EP.04에서 봤다. 16슬롯짜리 버퍼 풀을 만들어서 매번 할당/해제 안 하고 돌려쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;io_uring에서는 이걸 &lt;b&gt;OS 차원에서 받아준다.&lt;/b&gt; &lt;code&gt;IORING_REGISTER_BUFFERS&lt;/code&gt;라는 기능이 있다. 버퍼들을 미리 커널에 등록해두면 매 I/O마다 페이지 매핑하는 비용이 사라진다. 등록된 버퍼는 인덱스로 참조한다. 우리가 슬롯 번호로 관리했던 것과 닮았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2006년에 사용자 공간에서 직접 만들었던 발상이, 2019년 이후 OS 차원의 정식 기능이 되어 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Write 병합 &amp;rarr; batching&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EP.06에서 봤다. send를 매번 부르지 않고 16개 모아서 한 번에 보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;io_uring은 &lt;b&gt;batching이 디자인의 핵심이다.&lt;/b&gt; SQ에 SQE를 여러 개 쌓아두고 &lt;code&gt;io_uring_enter()&lt;/code&gt; 한 번으로 제출한다. 우리가 사용자 공간에서 직접 했던 일이, 인터페이스 자체에 박혀 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 모양은 다르다. 우리는 &quot;바이트를 모아서 한 번에 send&quot;였고, io_uring은 &quot;send 요청을 모아서 한 번에 제출&quot;이다. 그런데 목적은 같다. &lt;b&gt;시스템콜 횟수를 줄인다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참조 카운팅, 락-프리 포인터, 스핀락 &amp;rarr; 그대로&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EP.05, EP.07, EP.03에서 본 세 기법은 OS 무관이다. 사용자 공간에서 도는 동기화 메커니즘이니까. io_uring으로 옮긴다고 해서 바꿀 이유가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 io_uring을 멀티스레드에서 쓰려면 SQ/CQ 링의 head/tail을 사용자 공간에서 직접 동기화해야 한다고 한다. 락-프리 패턴이 거기서도 필요하다. 우리 코드에 있는 동기화 자산이 그대로 쓰임새가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ep09_mapping.png&quot; data-origin-width=&quot;2579&quot; data-origin-height=&quot;1679&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYoY68/dJMcahEhgK8/P2fZQtLqvIov7tIrsJgETK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYoY68/dJMcahEhgK8/P2fZQtLqvIov7tIrsJgETK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYoY68/dJMcahEhgK8/P2fZQtLqvIov7tIrsJgETK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYoY68%2FdJMcahEhgK8%2FP2fZQtLqvIov7tIrsJgETK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2579&quot; height=&quot;1679&quot; data-filename=&quot;ep09_mapping.png&quot; data-origin-width=&quot;2579&quot; data-origin-height=&quot;1679&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;여기서 잠깐&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 흐름만 보면 io_uring이 만능 같다. 그건 사실이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;io_uring 커뮤니티에 잘 알려진 비교가 있다. &lt;b&gt;단순한 ping-pong 워크로드에서는 io_uring이 epoll을 이긴다. 그런데 streaming 워크로드에서는 epoll이 더 빠른 경우도 있다.&lt;/b&gt; 단순 echo 서버 벤치마크에서 epoll이 io_uring보다 빠르다는 보고도 GitHub에 올라와 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;io_uring의 진가는 &lt;b&gt;batching을 적극적으로 활용할 때&lt;/b&gt; 나온다. 그냥 epoll 코드를 io_uring API로 1:1 치환하면 별 차이가 없거나 오히려 느릴 수 있다는 게 여러 벤치마크의 결론이다. 한 DBMS 연구에 따르면, libaio를 io_uring으로 바로 바꿨을 때 1.06배 향상이지만, 시스템을 io_uring 특성(batching, registered buffers)에 맞춰 재설계하면 2배까지 간다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 io_uring은 여전히 활발히 개발 중인 신기능이다. 5.1에서 처음 들어왔지만, 멀티샷 receive(6.0)나 zero-copy 송수신 같은 기능이 계속 추가되고 있다. 과거에는 보안 이슈도 몇 번 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니까 &quot;무조건 io_uring&quot;은 아니다. 워크로드를 보고 골라야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이 시리즈의 코드는 &lt;b&gt;MMORPG 서버&lt;/b&gt;다. 한 소켓에 양방향으로 끊임없이 데이터가 흐르고, 한 번의 게임 틱마다 수백 개의 패킷이 동시에 나가고 들어오는 구조. 이건 io_uring의 batching이 정확히 빛을 보는 워크로드에 가깝다. 그리고 무엇보다, &lt;b&gt;모델이 같다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그래서 io_uring이다&lt;/h2&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;모델이 같다. &lt;b&gt;완료 통보&lt;/b&gt;. 코드 결이 어긋나지 않는다.&lt;/li&gt;
&lt;li&gt;여섯 기법 중 셋이 OS 차원에서 받아준다. 직접 안 만들어도 된다.&lt;/li&gt;
&lt;li&gt;나머지 셋은 OS 무관이라 그대로 가져갈 수 있다.&lt;/li&gt;
&lt;li&gt;워크로드도 io_uring 쪽이 더 어울린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;epoll로 가면 코드를 바닥부터 다시 짜야 한다. &quot;준비 통보&quot; 모델로 머리부터 발끝까지 갈아엎어야 한다. io_uring으로 가면 발상은 그대로 두고 인터페이스만 바꾸면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 io_uring이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그런데 여기서 한 가지 의문&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2006년에 짠 코드가 &lt;b&gt;2019년에 나온 인터페이스&lt;/b&gt;와 모델이 같다는 게, 사실 좀 이상한 일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2006년의 IOCP는 Windows 전용이었다. Linux 진영에서는 &quot;epoll이면 충분하다&quot;는 분위기가 오래 갔다. io_uring이 나온 건 13년 뒤인 2019년이다. &quot;epoll로는 부족하다&quot;는 이야기가 그 무렵 슬슬 나오기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니까 2006년에 누군가가 IOCP 위에 짠 이 코드는, &lt;b&gt;그 시점에는 Windows 전용 기법으로 보였지만, 13년 뒤에는 모델 자체가 옳았다는 게 증명된 셈이다.&lt;/b&gt; Linux도 결국 같은 곳으로 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 포팅의 자신감이 되어준다. 인터페이스만 갈아끼우면 된다는, 모델은 안 바꿔도 된다는 자신감.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 한 가지 더, 마음에 걸리는 게 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;io_uring의 SQ/CQ 링 버퍼 구조. 사용자 공간과 커널이 공유 메모리로 통신하면서, 요청을 모아서 묶음으로 처리하는 구조. 이게 io_uring만의 발상이 아니다. 어디선가 비슷한 그림을 본 적이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어디였더라.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 회에서는&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시리즈의 마지막 글이다. Part 3 &quot;변환&quot;의 시작이자, 1막의 에필로그.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2006년 MMORPG 서버 코드에서 발견한 발상들이, 어떻게 2025년의 LLM 인프라에서 다시 나타나는지. 버퍼 풀과 KV cache, Write 병합과 continuous batching, 그리고 비동기 I/O와 LLM serving 아키텍처.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;20년 전 코드가 본 미래는 io_uring 하나가 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr; EP.10: 에필로그 &amp;mdash; 2006년 MMORPG 서버와 2025년 LLM 인프라&lt;/b&gt;&lt;/p&gt;</description>
      <category>그냥 글을 써 봅니다/서버 개발 수기</category>
      <category>Claude와함께</category>
      <category>epoll</category>
      <category>iocp</category>
      <category>io_uring</category>
      <category>Linux커널</category>
      <category>MMORPG서버</category>
      <category>게임서버</category>
      <category>네트워크프로그래밍</category>
      <category>비동기IO</category>
      <category>서버개발수기</category>
      <author>Tiboong</author>
      <guid isPermaLink="true">https://npackgames.tistory.com/176</guid>
      <comments>https://npackgames.tistory.com/176#entry176comment</comments>
      <pubDate>Tue, 5 May 2026 09:21:39 +0900</pubDate>
    </item>
  </channel>
</rss>