<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>조급하지말고 천천히</title>
    <link>https://dol9.tistory.com/</link>
    <description>노력한다고 성공하는건 아니지만
성공한 사람은 다 노력했다.-더파이팅</description>
    <language>ko</language>
    <pubDate>Thu, 27 Aug 2026 11:03:32 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>오산돌구</managingEditor>
    <item>
      <title>15일에 19번 업데이트? Claude Code 1월 업데이트 정리</title>
      <link>https://dol9.tistory.com/340</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;업무에서 Claude Code를 설계와 검증에 더 시간을 쏟고 구현은 거의 안 하는 느낌으로 사용하고 있다.&lt;/p&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 Code에 좋다고 하는 추가 skill이나 에이전트 소개가 많이 나오는데 크게 관심이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 정말 좋다고 하면 엔트로픽에서 Claude Code에 조만간 반영하겠지.. 생각이 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 TODO -&amp;gt; TASK를 바꾼것도 비슷한 맥락이다.&lt;/p&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 Code에 대한 내 생각을 적어봤는데 이번글에서는 그래서 올해 Claude Code에 어떤 수정이 있었는지 정리해 봤다.&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_z05axlz05axlz05a.png&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;2048&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmANtd/dJMcafrRq60/jZdgktvyGk1GdjbubXPAUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmANtd/dJMcafrRq60/jZdgktvyGk1GdjbubXPAUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmANtd/dJMcafrRq60/jZdgktvyGk1GdjbubXPAUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmANtd%2FdJMcafrRq60%2FjZdgktvyGk1GdjbubXPAUK%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;600&quot; height=&quot;600&quot; data-filename=&quot;Gemini_Generated_Image_z05axlz05axlz05a.png&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;2048&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년 12월에 회사에서 발표한 slash command(skill로 합쳐짐)와 단축키 슬라이드다.&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;Screenshot 2026-01-25 at 1.35.17 PM.png&quot; data-origin-width=&quot;2118&quot; data-origin-height=&quot;1172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/duoua7/dJMcafelK1A/SrKTxKsUpgg2Uv7qNnrcrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/duoua7/dJMcafelK1A/SrKTxKsUpgg2Uv7qNnrcrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/duoua7/dJMcafelK1A/SrKTxKsUpgg2Uv7qNnrcrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fduoua7%2FdJMcafelK1A%2FSrKTxKsUpgg2Uv7qNnrcrK%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;2118&quot; height=&quot;1172&quot; data-filename=&quot;Screenshot 2026-01-25 at 1.35.17 PM.png&quot; data-origin-width=&quot;2118&quot; data-origin-height=&quot;1172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-01-25 at 1.35.33 PM.png&quot; data-origin-width=&quot;1964&quot; data-origin-height=&quot;1172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCif7r/dJMb99ZuE7F/8I7pwLVD78i6k9tyL0L48k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCif7r/dJMb99ZuE7F/8I7pwLVD78i6k9tyL0L48k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCif7r/dJMb99ZuE7F/8I7pwLVD78i6k9tyL0L48k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCif7r%2FdJMb99ZuE7F%2F8I7pwLVD78i6k9tyL0L48k%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;1964&quot; height=&quot;1172&quot; data-filename=&quot;Screenshot 2026-01-25 at 1.35.33 PM.png&quot; data-origin-width=&quot;1964&quot; data-origin-height=&quot;1172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RELEASE NOTE&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리해 달라고 먼저 요청하고 수정했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k1y3H/dJMcafFpe9s/3FexIr55MAL865PI2uO8W1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k1y3H/dJMcafFpe9s/3FexIr55MAL865PI2uO8W1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k1y3H/dJMcafFpe9s/3FexIr55MAL865PI2uO8W1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk1y3H%2FdJMcafFpe9s%2F3FexIr55MAL865PI2uO8W1%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;1192&quot; height=&quot;422&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 85.930233%; height: 360px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;버전&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;날짜&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;핵심변경사항&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.379845%;&quot;&gt;v2.1.0&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%;&quot;&gt;1월 9일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%;&quot;&gt;메이저 업데이트!! Skill 핫리로드, language 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;v2.1.2&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;1월 9일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;winget 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;v2.1.3&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;1월 9일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;slash command와 skill 통합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;v2.1.4&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;1월 10일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;백그라운드 작업 비활성화 옵션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;v2.1.5&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;1월 12일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;임시 디렉토리 커스터마이징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;v2.1.6&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;1월 13일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;/config 검색, /stats 필터링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;v2.1.7&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;1월 14일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;커스텀 키바인딩, MCP 자동모드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;v2.1.9&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;1월 16일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;plansDirectory, 외부 에디터지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;v2.1.10&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;1월 17일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;Setup 훅 이벤트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;v2.1.11&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;1월 17일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;MCP 연결 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;v2.1.12&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;1월 17일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;렌더링 버그 수정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;v2.1.14&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;1월 20일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;Bash 히스토리 자동완성, 플러그인 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;v2.1.15&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;1월 21일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;React Compiler 성능 개선&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;v2.1.16&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;1월 22일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;Task Management 시스템&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;v2.1.17&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;1월 22일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;AVX 호환성 수정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.379845%; height: 18px;&quot;&gt;v2.1.18&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%; height: 18px;&quot;&gt;1월 23일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%; height: 18px;&quot;&gt;고급 키바인딩 시스템&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.379845%;&quot;&gt;v2.1.19&lt;/td&gt;
&lt;td style=&quot;width: 14.745045%;&quot;&gt;1월 23일&lt;/td&gt;
&lt;td style=&quot;width: 56.805342%;&quot;&gt;VSCode 세션 포크, Custom command 인자 문법&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;핵심기능 Top 7&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Skill 시스템 기능 강화 (v2.1.0, v2.1.19)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자동 스킬&lt;/b&gt;: ~/.claude/skills 폴더 파일 수정 시 세션 재시작 없이 즉시 반영&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;`context: fork`&lt;/b&gt;: 스킬을 fork된 subagent 컨텍스트에서 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;slash command 통합&lt;/b&gt;: `/스킬명` 으로 스킬 직접 호출 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Hook 지원&lt;/b&gt;: 스킬 frontmatter에서 PreToolUse, PostToolUse, Stop Hook 정의&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;승인 없이 사용 가능&lt;/b&gt;: 추가 권한이나 훅이 없는 스킬은 승인 절차 없이 바로 실행 가능 (v2.1.19)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실전활용 예시 1&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769318999315&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;```yaml
# ~/.claude/skills/test-runner.md
---
name: test-runner
description: 테스트를 실행하고 결과를 분석합니다
context: fork
allowed-tools:
  - Bash(npm test *)
  - Bash(jest *)
  - Read
hooks:
  PreToolUse:
    - command: echo &quot;  테스트 시작: $(date)&quot;
  PostToolUse:
    - command: |
        if [ $TOOL_EXIT_CODE -ne 0 ]; then
          echo &quot;❌ 테스트 실패 - 로그 확인 필요&quot;
        fi
---


# 테스트 러너

이 스킬은 프로젝트의 테스트를 실행하고 결과를 분석합니다.

## 수행 작업
1. package.json에서 테스트 스크립트 확인
2. 테스트 실행
3. 실패한 테스트 분석 및 해결책 제안
``


# 사용 방법
```bash
# Claude Code에서
/test-runner
```&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실전활용 예시 2&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포크 한 subagent에서 skill 실행&lt;/p&gt;
&lt;pre id=&quot;code_1769319107033&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;```yaml
# ~/.claude/skills/code-review.md
---
name: code-review
description: Git 변경사항을 분석하고 코드 리뷰를 수행합니다
context: fork
allowed-tools:
  - Bash(git diff *)
  - Bash(git log *)
  - Read
  - Grep
---

# 코드 리뷰어

현재 브랜치의 변경사항을 분석하여 코드 리뷰를 수행합니다.

## 검토 항목
- 코드 스타일 일관성
- 잠재적 버그
- 성능 이슈
- 보안 취약점
- 테스트 커버리지
```&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.&amp;nbsp;Task&amp;nbsp;Management&amp;nbsp;시스템&amp;nbsp;(v2.1.16,&amp;nbsp;v2.1.19)&lt;/h3&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;# Claude Code에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1e1f29;&quot;&gt;p&lt;/span&gt;&lt;span&gt;rint a list that usable tools.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;756&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3xUge/dJMcacIGQOC/foBuRrnhBq7i1xyKKea0hK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3xUge/dJMcacIGQOC/foBuRrnhBq7i1xyKKea0hK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3xUge/dJMcacIGQOC/foBuRrnhBq7i1xyKKea0hK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3xUge%2FdJMcacIGQOC%2FfoBuRrnhBq7i1xyKKea0hK%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;750&quot; height=&quot;485&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;756&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;새로운 Task Management가 맞지 않는다면 환경변수 추가로 비활성화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;```bash&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;export&amp;nbsp;CLAUDE_CODE_ENABLE_TASKS=false&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;&quot;JWT&amp;nbsp;인증으로&amp;nbsp;마이그레이션 할&amp;nbsp;건데,&amp;nbsp;단계별로&amp;nbsp;작업&amp;nbsp;목록을&amp;nbsp;만들고&amp;nbsp;의존성을&amp;nbsp;설정해서&amp;nbsp;순서대로&amp;nbsp;진행해 줘&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;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.&amp;nbsp;고급&amp;nbsp;키바인딩&amp;nbsp;시스템&amp;nbsp;(v2.1.18)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://code.claude.com/docs/en/keybindings&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://code.claude.com/docs/en/keybindings&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769319828673&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;```json
// ~/.claude/settings.json
{
  &quot;keyBindings&quot;: {
    // 기본 키바인딩
    &quot;submit&quot;: &quot;ctrl+enter&quot;,
    &quot;escape&quot;: &quot;ctrl+c&quot;,

    // 컨텍스트별 설정
    &quot;vim.normal&quot;: {
      &quot;gg&quot;: &quot;scrollToTop&quot;,
      &quot;G&quot;: &quot;scrollToBottom&quot;
    },
    &quot;vim.insert&quot;: {
      &quot;ctrl+[&quot;: &quot;exitInsertMode&quot;
    }
  }
}
```&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.&amp;nbsp;커스텀&amp;nbsp;명령어&amp;nbsp;인자&amp;nbsp;문법&amp;nbsp;개선&amp;nbsp;(v2.1.19)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;short hand syntax: `$0`, `$1`, `$2`&lt;/p&gt;
&lt;pre id=&quot;code_1769319971582&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;```yaml
# ~/.claude/commands/deploy.md
---
name: deploy
description: 특정 환경에 배포합니다
---

# 배포 명령어

환경: $0
브랜치: $1

## 실행
$0 환경으로 $1 브랜치를 배포합니다.
```


**사용 방법:**
```bash
/deploy production main
# &amp;rarr; $0 = &quot;production&quot;, $1 = &quot;main&quot;
```&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5.&amp;nbsp;MCP&amp;nbsp;자동&amp;nbsp;모드&amp;nbsp;(v2.1.7,&amp;nbsp;v2.1.9)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP의 도구 설명이 전체 컨텍스트 사용량이 임계값을 초과하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP 연결을 자동으로 지연시켜 컨텍스트를 최적화한다.&lt;/p&gt;
&lt;pre id=&quot;code_1769320081169&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;```json
// ~/.claude/settings.json
{
  &quot;mcpServers&quot;: {
    &quot;my-server&quot;: {
      &quot;command&quot;: &quot;node&quot;,
      &quot;args&quot;: [&quot;./server.js&quot;],
      &quot;mode&quot;: &quot;auto:30&quot;  // 30% 컨텍스트 초과 시 지연
    }
  }
}
```

| 모드 | 설명 |
|------|------|
| `&quot;mode&quot;: &quot;auto&quot;` | 기본값 (10% 초과 시 지연) |
| `&quot;mode&quot;: &quot;auto:30&quot;` | 30% 초과 시 지연 |
| `&quot;mode&quot;: &quot;auto:0&quot;` | 항상 지연 (필요할 때만 연결) |
| `&quot;mode&quot;: &quot;enabled&quot;` | 항상 연결 |&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실전활용 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769320119234&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;mcpServers&quot;: {
    // 자주 사용하는 서버: 항상 연결
    &quot;database&quot;: {
      &quot;command&quot;: &quot;mcp-postgres&quot;,
      &quot;mode&quot;: &quot;enabled&quot;
    },
    // 가끔 사용하는 서버: 컨텍스트 절약
    &quot;analytics&quot;: {
      &quot;command&quot;: &quot;mcp-analytics&quot;,
      &quot;mode&quot;: &quot;auto:50&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.&amp;nbsp;Bash&amp;nbsp;히스토리&amp;nbsp;자동완성&amp;nbsp;(v2.1.14)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. `!` 모드로 진입&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이전에 실행했던 명령어 일부 입력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Tab 키를 눌러 히스토리에서 자동완성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;```bash&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 예시: 이전에 &quot;npm run build:production&quot; 실행했다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;!npm run bu[Tab]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# -&amp;gt; npm run build:production 자동완성&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개발자 경험 향상&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;설정 옵션 완벽 가이드&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1769317930632&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  // 응답 언어 설정 (v2.1.0)
  &quot;language&quot;: &quot;korean&quot;,

  // 계획 파일 저장 위치 (v2.1.9)
  &quot;plansDirectory&quot;: &quot;~/Documents/claude-plans&quot;,

  // 턴 시간 표시 숨기기 (v2.1.7)
  &quot;showTurnDuration&quot;: false,

  // @-멘션 파일 선택기에서 .gitignore 적용 (v2.1.0)
  &quot;respectGitignore&quot;: true,

  // 커스텀 키바인딩 (v2.1.7, v2.1.18 확장)
  &quot;keyBindings&quot;: {
    &quot;submit&quot;: &quot;ctrl+enter&quot;,
    &quot;escape&quot;: &quot;ctrl+c&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;환경변수&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1769320312310&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;```bash
# ~/.zshrc 또는 ~/.bashrc에 추가

# 임시 디렉토리 커스터마이징 (v2.1.5)
export CLAUDE_CODE_TMPDIR=&quot;/tmp/claude-workspace&quot;

# 백그라운드 작업 비활성화 (v2.1.4)
export CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=true

# 스트리밍/데모용 이메일/조직 숨기기 (v2.1.0)
export IS_DEMO=true

#   Task Management 비활성화 (v2.1.19)
export CLAUDE_CODE_ENABLE_TASKS=false
```&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Hook 시스템 확장&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;새로운 Hook 이벤트: Setup (v2.1.10)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`--init`, `--init-only`, `--maintenance` 플래그로 실행될 때 트리거된다.&lt;/p&gt;
&lt;pre id=&quot;code_1769318103520&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// settings.json
{
  &quot;hooks&quot;: {
    &quot;Setup&quot;: [
      {
        &quot;command&quot;: &quot;echo '초기화 시작: $(date)' &amp;gt;&amp;gt; ~/.claude/init.log&quot;
      },
      {
        &quot;command&quot;: &quot;./scripts/check-env.sh&quot;,
        &quot;timeout&quot;: 5000
      }
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;일회성 Hook&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`once: true`&lt;/p&gt;
&lt;pre id=&quot;code_1769318146497&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;hooks&quot;: {
    &quot;PreToolUse&quot;: [
      {
        &quot;command&quot;: &quot;echo '첫 도구 사용!'&quot;,
        &quot;once&quot;: true  // 세션당 한 번만 실행
      }
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;PreToolUse의 additionalContext 반환&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1769321738473&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash
# ~/.claude/hooks/context-provider.sh

# JSON 형태로 additionalContext 반환
echo '{&quot;additionalContext&quot;: &quot;현재 프로젝트는 TypeScript 5.0을 사용합니다.&quot;}'&lt;/code&gt;&lt;/pre&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;b&gt;TIP&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 효율적인 Skill 구조&lt;/h3&gt;
&lt;pre id=&quot;code_1769318248365&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;~/.claude/skills/
├── daily/
│   ├── standup.md       # 일일 스탠드업 정리
│   └── review.md        # 코드 리뷰
├── project/
│   ├── deploy.md        # 배포 자동화
│   └── db-migrate.md    # DB 마이그레이션
└── personal/
    ├── blog-helper.md   # 블로그 작성 도우미
    └── learning.md      # 학습 노트 정리&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. MCP 서버 최적화 설정&lt;/h3&gt;
&lt;pre id=&quot;code_1769318297430&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;mcpServers&quot;: {
    // 핵심 서버: 항상 연결
    &quot;filesystem&quot;: {
      &quot;command&quot;: &quot;mcp-filesystem&quot;,
      &quot;mode&quot;: &quot;enabled&quot;
    },
    // 보조 서버: 필요할 때만
    &quot;github&quot;: {
      &quot;command&quot;: &quot;mcp-github&quot;,
      &quot;mode&quot;: &quot;auto:20&quot;
    },
    // 무거운 서버: 명시적 요청 시만
    &quot;kubernetes&quot;: {
      &quot;command&quot;: &quot;mcp-k8s&quot;,
      &quot;mode&quot;: &quot;auto:0&quot;
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 커스텀 키바인딩 설정&lt;/h3&gt;
&lt;pre id=&quot;code_1769318330032&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;keyBindings&quot;: {
    &quot;submit&quot;: &quot;ctrl+enter&quot;,      // 전송
    &quot;escape&quot;: &quot;ctrl+c&quot;,          // 취소
    &quot;interrupt&quot;: &quot;ctrl+d&quot;        // 중단
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt; &lt;/span&gt;&lt;b&gt;주요 환경변수 목록&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 90.465116%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.868217%;&quot;&gt;변수명&lt;/td&gt;
&lt;td style=&quot;width: 31.12403%;&quot;&gt;설명&lt;/td&gt;
&lt;td style=&quot;width: 31.472868%;&quot;&gt;사용 가능 위치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.868217%;&quot;&gt;CLAUDE_PROJECT_DIR&lt;/td&gt;
&lt;td style=&quot;width: 31.12403%;&quot;&gt;프로젝트 루트 절대 경로&lt;/td&gt;
&lt;td style=&quot;width: 31.472868%;&quot;&gt;Hooks, MCP Server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.868217%;&quot;&gt;CLAUDE_ENV_FILE&lt;/td&gt;
&lt;td style=&quot;width: 31.12403%;&quot;&gt;환경변수 영속화용 파일 경로&lt;/td&gt;
&lt;td style=&quot;width: 31.472868%;&quot;&gt;SessionStart, Setup Hook만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.868217%;&quot;&gt;CLAUDE_PLUGIN_ROOT&lt;/td&gt;
&lt;td style=&quot;width: 31.12403%;&quot;&gt;플러그인 디렉토리 경로&lt;/td&gt;
&lt;td style=&quot;width: 31.472868%;&quot;&gt;Plugin hooks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.868217%;&quot;&gt;CLAUDE_CODE_REMOTE&lt;/td&gt;
&lt;td style=&quot;width: 31.12403%;&quot;&gt;원격 실행 여부 (&quot;true&quot; or 빈값)&lt;/td&gt;
&lt;td style=&quot;width: 31.472868%;&quot;&gt;모든 곳&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;활용 예시&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hook에서 사용&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&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; &quot;hooks&quot;: {&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &quot;PreToolUse&quot;: [&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;{&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&quot;matcher&quot;: &quot;Bash&quot;,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&quot;command&quot;: &quot;\&quot;$CLAUDE_PROJECT_DIR\&quot;/.claude/hooks/validate.sh&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/span&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &lt;/span&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;plandDirectory 설정에 활용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉽게도 settings.json에서는 환경변수 치환이 &lt;i&gt;&lt;b&gt;지원되지 않는다.&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;// ❌ 이렇게는 안 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&quot;plansDirectory&quot;: &quot;$CLAUDE_PROJECT_DIR/claudeworkspace&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;}&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;// ✅ 상대 경로로 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&quot;plansDirectory&quot;: &quot;./claudeworkspace&quot;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;SessionStart Hook에서 환경변수 설정&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1769321670839&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash
# .claude/hooks/session-start.sh

# 다른 명령어에서 사용할 변수 영속화
echo &quot;export MY_WORKSPACE=$CLAUDE_PROJECT_DIR&quot; &amp;gt;&amp;gt; &quot;$CLAUDE_ENV_FILE&quot;
echo &quot;export MY_PLANS_DIR=$CLAUDE_PROJECT_DIR/claudeworkspace&quot; &amp;gt;&amp;gt; &quot;$CLAUDE_ENV_FILE&quot;

---
Hook 입력으로 받는 정보 (stdin JSON)

{
  &quot;session_id&quot;: &quot;abc123&quot;,
  &quot;cwd&quot;: &quot;/current/working/directory&quot;,
  &quot;transcript_path&quot;: &quot;/path/to/conversation.json&quot;,
  &quot;permission_mode&quot;: &quot;default&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;★ Insight ─────────────────────────────────────&lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- &lt;b&gt;CLAUDE_PROJECT_DIR vs cwd&lt;/b&gt;: CLAUDE_PROJECT_DIR는 Claude Code를 시작한 프로젝트 루트이고, cwd는 현재 작업 디렉토리입니다. cd 명령으로 이동해도 CLAUDE_PROJECT_DIR는 변하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- &lt;b&gt;settings.json 제한&lt;/b&gt;: 설정 파일에서는 환경변수 치환이 안 되므로,./claudeworkspace처럼 상대 경로를 사용하면 프로젝트 루트 기준으로 자동 해석됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;- &lt;b&gt;CLAUDE_ENV_FILE&lt;/b&gt;: SessionStart 훅에서만 사용 가능하며, 여기에 export VAR=value를 추가하면 이후 Bash 명령어에서 해당 변수를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;───────────────────────────────────────────────── &lt;span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  참고 자료&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://github.com/anthropics/claude-code/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Claude Code GitHub Releases&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 CHANGELOG&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://code.claude.com/docs/en/keybindings&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;키바인딩 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://claudelog.com/claude-code-changelog/)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ClaudeLog Changelog&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://releasebot.io/updates/anthropic/claude-code&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Releasebot Updates&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발하면서/타인글보면서</category>
      <author>오산돌구</author>
      <guid isPermaLink="true">https://dol9.tistory.com/340</guid>
      <comments>https://dol9.tistory.com/340#entry340comment</comments>
      <pubDate>Sun, 25 Jan 2026 15:42:42 +0900</pubDate>
    </item>
    <item>
      <title>오픈소스 만들어 Github에 올릴 때 하면 좋은 체크리스트 .. (2)</title>
      <link>https://dol9.tistory.com/339</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Github 블로그 글을 보며 정리한 글에 이어서 이번에는 README와 릴리즈 노트 관련 글을 읽고 정리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dol9.tistory.com/338&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dol9.tistory.com/338&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1749556772598&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;오픈소스 만들어 Github에 올릴 때 하면 좋은 체크리스트 .. (1)&quot; data-og-description=&quot;아직은 진행중이라 Private으로 진행하고 있지만 간단한 오픈소스를 만들어 공개해보려고 한다. 외부 OpenAPI를 호출하는 아주 단순하고 목적이 명확한 클라이언트다.기능이야 문서 보고 Copilot과 &quot; data-og-host=&quot;dol9.tistory.com&quot; data-og-source-url=&quot;https://dol9.tistory.com/338&quot; data-og-url=&quot;https://dol9.tistory.com/338&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/i3SUS/hyY5iguS5G/Cc9YLjfIHg10ERxv1Et2w1/img.png?width=800&amp;amp;height=652&amp;amp;face=0_0_800_652,https://scrap.kakaocdn.net/dn/9vIzY/hyY4dsX4hk/T5esjBIuy6NFauyaKwp4m0/img.png?width=800&amp;amp;height=652&amp;amp;face=0_0_800_652&quot;&gt;&lt;a href=&quot;https://dol9.tistory.com/338&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dol9.tistory.com/338&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/i3SUS/hyY5iguS5G/Cc9YLjfIHg10ERxv1Et2w1/img.png?width=800&amp;amp;height=652&amp;amp;face=0_0_800_652,https://scrap.kakaocdn.net/dn/9vIzY/hyY4dsX4hk/T5esjBIuy6NFauyaKwp4m0/img.png?width=800&amp;amp;height=652&amp;amp;face=0_0_800_652');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;오픈소스 만들어 Github에 올릴 때 하면 좋은 체크리스트 .. (1)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;아직은 진행중이라 Private으로 진행하고 있지만 간단한 오픈소스를 만들어 공개해보려고 한다. 외부 OpenAPI를 호출하는 아주 단순하고 목적이 명확한 클라이언트다.기능이야 문서 보고 Copilot과&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dol9.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 아는 걸 실행하는 게 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실행 할 때는 아무 생각 없이 할 수 있도록 준비하는 게 좋은데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하면서 꽤나 효과적이라고 생각하는게 금방 완료 가능한 할 일들을 체크리스트로 만들어 하나씩 완성해 나가는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 체크리스트를 만들기 위해 정리가 목적이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.daytona.io/dotfiles/how-to-write-4000-stars-github-readme-for-your-project&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.daytona.io/dotfiles/how-to-write-4000-stars-github-readme-for-your-project&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1749556965971&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;How to Write A 4000 Stars GitHub README for Your Project&quot; data-og-description=&quot;Guide on How We Created a GitHub Project README that Propelled Our Open Source Project to 4k Stars&quot; data-og-host=&quot;www.daytona.io&quot; data-og-source-url=&quot;https://www.daytona.io/dotfiles/how-to-write-4000-stars-github-readme-for-your-project&quot; data-og-url=&quot;https://www.daytona.io/dotfiles/how-to-write-4000-stars-github-readme-for-your-project&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/buxBlx/hyY5hWdRr9/yNIR4Elwu9OxJMQkDBQfjk/img.jpg?width=2520&amp;amp;height=1260&amp;amp;face=0_0_2520_1260,https://scrap.kakaocdn.net/dn/QkPIS/hyY334Wb55/M94ka9ZKqfC8rhWsFeHmy0/img.jpg?width=2520&amp;amp;height=1260&amp;amp;face=0_0_2520_1260,https://scrap.kakaocdn.net/dn/bQBqjL/hyY8aafLbJ/nZh9mLlHrlOKqJKhfzVw41/img.jpg?width=1564&amp;amp;height=782&amp;amp;face=0_0_1564_782&quot;&gt;&lt;a href=&quot;https://www.daytona.io/dotfiles/how-to-write-4000-stars-github-readme-for-your-project&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.daytona.io/dotfiles/how-to-write-4000-stars-github-readme-for-your-project&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/buxBlx/hyY5hWdRr9/yNIR4Elwu9OxJMQkDBQfjk/img.jpg?width=2520&amp;amp;height=1260&amp;amp;face=0_0_2520_1260,https://scrap.kakaocdn.net/dn/QkPIS/hyY334Wb55/M94ka9ZKqfC8rhWsFeHmy0/img.jpg?width=2520&amp;amp;height=1260&amp;amp;face=0_0_2520_1260,https://scrap.kakaocdn.net/dn/bQBqjL/hyY8aafLbJ/nZh9mLlHrlOKqJKhfzVw41/img.jpg?width=1564&amp;amp;height=782&amp;amp;face=0_0_1564_782');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How to Write A 4000 Stars GitHub README for Your Project&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Guide on How We Created a GitHub Project README that Propelled Our Open Source Project to 4k Stars&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.daytona.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에서 README는 가장 중요하다.&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;b&gt;HEADING&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 로고 만들기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간과하기 쉬운데 첫줄에 로고가 있으면 깊은 인상을 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 신뢰를 줄 수 있는 배지&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 테스트 커버리지나 활성화 등 사용자에게 프로젝트의 건강함을 어필할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 프로젝트를 잘 표현하는 캐치프레이즈&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 잘 표현하는 한 줄로 시작하고 다음 줄에 추가 설명을 진행하면 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들의 관심을 끄는데 매우 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 이미지나 애니메이션 같은 시각화&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글만 있는것보다 시각화 자료가 함께 있다면 더욱 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 기능 소개&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 사람들의 관심을 끌었으니 프로젝트에서 매력적인 기능을 나열한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 프로젝트와 차별되는 고유한 가치를 강조해서 사용자가 관심을 넘어 호기심을 갖도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6. QuickStart&lt;/b&gt;&lt;/h4&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;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;BODY&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Why&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 읽은 사용자의 관점으로 프로젝트에 참여해야 하는 이유를 명확하게 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그들의 필요와 문제를 해결해줌으로써 관계 구축에 도움이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 배경설명&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 프로젝트가 없지만 배경설명 적는 걸 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당신이 프로젝트를 시작하게된 동기와 여정들을 공유하면, 독자가 더욱 공감 가고 기억에 남게 해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. Getting Started&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QuickStart의 확장버전이다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 간결하게 유지&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불필요하게 README 파일이 길어지면 사용자는 프로젝트가 복잡하다고 느낄 수 있으므로 간결하게 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;프로젝트의 건강함(??)&lt;/b&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Contributing&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 작성된 기여 가이드는 컨드리뷰터들이 모범사례를 따라 유용한 문제를 작성할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표준화된 P/R을 제출할 수 있어 관리자가 프로젝트 관리를 쉽게 해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 라이선스&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 라이선스 명확히 명시하여 지적 재산권과 권리 모두를 보호한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 행동강령&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트가 커뮤니티와 어떻게 소통하고 사용할 것인지를 정의하고 컨드리뷰터들의 기대치를 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 소통하는 채널 만들기&lt;/b&gt;&lt;/h4&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;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&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;파일이 커지면 세부항목을 별도의 문서 폴더의 README 파일을 만들어 참조하는 것도 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크가 깨지거나 빈 README가 있는지 확인하고 깔끔하게 유지시킨다.&lt;/p&gt;</description>
      <category>개발하면서/타인글보면서</category>
      <author>오산돌구</author>
      <guid isPermaLink="true">https://dol9.tistory.com/339</guid>
      <comments>https://dol9.tistory.com/339#entry339comment</comments>
      <pubDate>Tue, 10 Jun 2025 21:40:21 +0900</pubDate>
    </item>
    <item>
      <title>오픈소스 만들어 Github에 올릴 때 하면 좋은 체크리스트 .. (1)</title>
      <link>https://dol9.tistory.com/338</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;아직은 진행중이라 Private으로 진행하고 있지만 간단한 오픈소스를 만들어 공개해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 OpenAPI를 호출하는 아주 단순하고 목적이 명확한 클라이언트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능이야 문서 보고 Copilot과 함께 조금씩 개선하면 되는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공개하는데 비슷한 생각을 가진 사람과 이런저런 얘기하고 만들어가면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재밌을 것 같아서 오픈소스 관련 내용을 찾아봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러개 찾았는데 그중 내가 필요한 내용들을 정리했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.blog/enterprise-software/collaboration/a-checklist-and-guide-to-get-your-repository-collaboration-ready/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.blog/enterprise-software/collaboration/a-checklist-and-guide-to-get-your-repository-collaboration-ready/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1749205108546&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;A checklist and guide to get your repository collaboration-ready&quot; data-og-description=&quot;In the world of software development, collaboration can make the difference between a brittle last-minute release and a reliable, maintainable, pain-free project. Whether you&amp;rsquo;ve been coding for a day or a decade, your colleagues are there to help strengt&quot; data-og-host=&quot;github.blog&quot; data-og-source-url=&quot;https://github.blog/enterprise-software/collaboration/a-checklist-and-guide-to-get-your-repository-collaboration-ready/&quot; data-og-url=&quot;https://github.blog/enterprise-software/collaboration/a-checklist-and-guide-to-get-your-repository-collaboration-ready/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/0VS0O/hyY32cZ3aY/iXqIuPpoP210FxhBmLUUm1/img.png?width=1200&amp;amp;height=640&amp;amp;face=0_0_1200_640,https://scrap.kakaocdn.net/dn/K4gL4/hyY37STUgQ/rPTxUGuiiPShKLzzd1v8gK/img.png?width=1200&amp;amp;height=640&amp;amp;face=0_0_1200_640,https://scrap.kakaocdn.net/dn/dF0VlB/hyY4cs7exB/N4jRZRqEZ7KdTwqIDAnCa0/img.jpg?width=1024&amp;amp;height=707&amp;amp;face=0_0_1024_707&quot;&gt;&lt;a href=&quot;https://github.blog/enterprise-software/collaboration/a-checklist-and-guide-to-get-your-repository-collaboration-ready/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.blog/enterprise-software/collaboration/a-checklist-and-guide-to-get-your-repository-collaboration-ready/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/0VS0O/hyY32cZ3aY/iXqIuPpoP210FxhBmLUUm1/img.png?width=1200&amp;amp;height=640&amp;amp;face=0_0_1200_640,https://scrap.kakaocdn.net/dn/K4gL4/hyY37STUgQ/rPTxUGuiiPShKLzzd1v8gK/img.png?width=1200&amp;amp;height=640&amp;amp;face=0_0_1200_640,https://scrap.kakaocdn.net/dn/dF0VlB/hyY4cs7exB/N4jRZRqEZ7KdTwqIDAnCa0/img.jpg?width=1024&amp;amp;height=707&amp;amp;face=0_0_1024_707');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;A checklist and guide to get your repository collaboration-ready&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;In the world of software development, collaboration can make the difference between a brittle last-minute release and a reliable, maintainable, pain-free project. Whether you&amp;rsquo;ve been coding for a day or a decade, your colleagues are there to help strengt&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.blog&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈소스 만든 메인테이너의 가장 중요한 책임은 다른 사람들이 본인의 프로젝트를&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적절하게 사용하고, 이해하고, 기여할 수 있도록 하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 미션을 지원하기 위해 Github이 있다. Github 기능들을 소개한다.&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;b&gt;1. Repository settings&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Visibility&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Repository -&amp;gt; Settings 탭에서 수정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;Public&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누구나 접근할 수 있고 코드를 로컬에 다운로드할 수 있고 이슈를 만들고 P/R을 보낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불특정 다수로부터 기능이 잘 동작하는지 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;피드백을 받을 수 있고 다양한 개선 방법도 들을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;보안 정보가 없는 개인프로젝트에 적합하고 회사 프로젝트에는 적합하지 않다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;Internal&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Enterprise에서 사용 가능한 Visibility 옵션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조직 내부 누구나 접근 가능하지만 외부에서는 접근할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;민감한 정보가 없는 회사 프로젝트에 어울린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;Private&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 제한적인 옵션이다. 초대한 사람만 접근 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;Collaborators&lt;/u&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;읽기 전용, P/R과 커밋 또는 Admin 같은 역할을 부여할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Private과 차이점은 초대 이후 역할을 부여할 수 있다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Protect the main branch&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.blog/news-insights/product-news/github-repository-rules-are-now-generally-available/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Repository rule을&lt;/a&gt; 이용하여 main branch에 바로 commit 하는 것을 막을 수 있다. (다양한 기능 존재)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소 한 명 이상의 승인을 받아야 한다면 `&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;CODEOWNERS` 파일을 이용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;추가로 P/R 마다 unit test 같은 자동화된 실행이 필요하다면 &lt;a href=&quot;https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;status checks를&lt;/a&gt; 이용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;2. Repository contents&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;소프트웨어 프로젝트는 코드 작성만 포함된 게 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;이 프로젝트가 왜 만들어졌는지, 잘 사용하는 방법, 기여하는 방법 등&lt;br /&gt;협력자와의 가이드 역할도 해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;README.md&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;해당 repository를 방문한 사람이 가장 처음 보는 파일이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;프로젝트의 기능과 어떻게 설정하고 사용하는지 적혀있어야 한다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;LICENSE.md&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;해당 repository에 있는 코드와 콘텐츠에 대해 다른 사람들이 할 수 있는 것과 할 수 없는 것이 정의되어 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;프로젝트에 가장 적합한 라이선스에 대한 지침은 &lt;a href=&quot;http://www.choosealicense.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;choosealicense.com에서&lt;/a&gt; 확인하고 repository에 라이선스를 추가하자.&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;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;CONTRIBUTING.md&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;해당 파일에 프로젝트에 새로운 코드, 문서 등 기여하는 방법과 이유를 명확하게 적어서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;잠재적 기여자가 직면할 혼란과 마찰을 줄인다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짧은 경우 README에 적어도 상관없지만 두 문단 이상인 경우 파일 분리하는 걸 추천한다.&lt;/p&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;P/R 제출 과정, 그리고 코딩 표준과 스타일 가이드라인에 대한 정보가 포함되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/github/docs/blob/v1.0.1/CONTRIBUTING.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GitHub docs 프로젝트&lt;/a&gt;를 참고 (지금은 지워졌다. 필요 없을지도??)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;CODEOWNERS&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;전체 또는 파일 확장자나 폴더 별로 1명 이상의 코드 책임자를 지정할&amp;nbsp; 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;P/R이 생성되면 수정된 코드에 대해 자동으로 리뷰어 할당이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&quot;&amp;middot;. md&quot; 확장자가 있으면 안 된다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;CODE_OF_CONDUCT.md&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;프로젝트 참여자가 따라야 할 사회적 규범과 규칙, 책임을 설정한다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;이러한 파일은 사람들이 저장소에 훨씬 더 쉽게 접근할 수 있고, 이해가 쉬워지며,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;기여하는 게 더 자연스럽게 흐르는 걸 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;3. Automation and checks&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;Never send a human to do a machine's job&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;Linting&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;Linters는 다양한 에러를 감지하고 일관된 코드 스타일을 유지하기 위해 코드를 분석하는 도구다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/marketplace/actions/super-linter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;Super-Linter&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;Building and testing&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;프로젝트에서 사용하는 언어와 프레임워크에 따라 다르겠지만 대부분의 repository에서 자동으로 실행 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;&lt;a href=&quot;https://github.com/marketplace&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Github Workflow 마켓플레이스&lt;/a&gt;에서 build, Test App &amp;amp; Actions 검색해서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;본인 repository에 맞는 도구를 찾자.&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;size18&quot;&gt;&lt;b&gt;Checks&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 말한 자동화된 도구(Linter, 빌드, 테스트)를 추가하면 실패하는 경우 배포하는 걸 막을 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;status checks를&lt;/a&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;이런 자동화 도구와 status checks를 통해 repository 기여의 품질과 일관성의 신뢰를 높이고&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Security&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Roles&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협력자에게 역할과 권한 주는 걸 결정할 때는 신중해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Writer는 신뢰하는 개인을 위한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Admin은 제품 방향을 관리하고 검토하는 핵심 인물을 위한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Secrets management&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API keys나 비밀번호, 인증서 같이 비공개로 유지해야 할 예민한 데이터를 Secrets라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드라 로그에 직접 입력하면 안 되고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;third pary keystore나 GitHub의 secret-management 도구를 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Security scanners&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 애플리케이션 코드 80~90%는 third pary 소스(프레임워크, 패키지)가 차지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dependabot은 공개 repository에서 사용할 수 있고, 모든 조직에서 자동으로 활성할 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불안정한 third pary소스가 발견되면 알림을 주고 수정 방법을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dependabot이 활성 중인지 확인하려면 repository의 보안탭을 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Advanced options&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Issue templates&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버그 수정이나 개선사항에 대한 문의할 때 issue 기능을 사용하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 구조화되지 않아 필요한 모든 정보를 알기 위해선 여러 번의 핑퐁이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;a href=&quot;https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;issue templates&lt;/a&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. Next steps&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Responsiveness&lt;/b&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;이슈와 P/R에 신속하게 대응하고 피드백을 주며, 새 기여자에게 가이드 주는 것까지 포함한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Project management&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무를 관리하고 진행상황을 시각화하기 위해 GitHub Project를 설정하면 좋다.&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;size18&quot;&gt;&lt;b&gt;Visibility&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에 소개한 방법들을 적용해서 잘 관리는 됐지만 아무도 알지 못하면 쓸모가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그 포스트나 데모, 프로젝트 랜딩 페이지 등을 통해 프로젝트를 홍보하자.&lt;/p&gt;
&lt;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;size18&quot;&gt;&lt;b&gt;Community engagement&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커뮤니티와 소통하는 것은 건강하고 협력하는 환경을 만드는데 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.blog/wp-content/uploads/2023/08/Is-Your-Repo-Collaboration-Ready_-A-Comprehensive-Guide-and-Checklist-Google-Docs.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;체크리스트&lt;/a&gt;를 출력하여 GitHub repository를 생성하거나 수정하&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/github/form-templates#readme&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Issue Template&lt;/a&gt; 활용해서 개선하자!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발하면서/타인글보면서</category>
      <author>오산돌구</author>
      <guid isPermaLink="true">https://dol9.tistory.com/338</guid>
      <comments>https://dol9.tistory.com/338#entry338comment</comments>
      <pubDate>Fri, 23 May 2025 17:00:40 +0900</pubDate>
    </item>
    <item>
      <title>Valkey 8에 반영된 개선사항 - multi thread</title>
      <link>https://dol9.tistory.com/337</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 글에서 new hash table을 보면서 많은 수정이 있다고 느꼈는데 이번에 볼 multi thread도 많은 수정이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dol9.tistory.com/336&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dol9.tistory.com/336&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1746535885909&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Valkey 8에 반영된 개선사항 - new hash table&quot; data-og-description=&quot;작년 3월, Redis는 기존 BSD 라이선스에서 Redis Source Available License (RSALv2)과Server Side Public License (SSPLv1) 듀얼 라이센스를 적용했다.https://redis.io/blog/redis-adopts-dual-source-available-licensing/ 개인 사용자나 Re&quot; data-og-host=&quot;dol9.tistory.com&quot; data-og-source-url=&quot;https://dol9.tistory.com/336&quot; data-og-url=&quot;https://dol9.tistory.com/336&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dbTH6z/hyYMesu9uH/hLLZje3WuGiq2euLcTB6K0/img.png?width=800&amp;amp;height=536&amp;amp;face=0_0_800_536,https://scrap.kakaocdn.net/dn/dL1cbS/hyYRybqfQm/lpSKkuPvNUlEc7sIkLiPr1/img.png?width=800&amp;amp;height=536&amp;amp;face=0_0_800_536,https://scrap.kakaocdn.net/dn/D7AaN/hyYMXYfP25/26z7PkOcLaNDfWm3IkqPkk/img.png?width=2092&amp;amp;height=1672&amp;amp;face=0_0_2092_1672&quot;&gt;&lt;a href=&quot;https://dol9.tistory.com/336&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dol9.tistory.com/336&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dbTH6z/hyYMesu9uH/hLLZje3WuGiq2euLcTB6K0/img.png?width=800&amp;amp;height=536&amp;amp;face=0_0_800_536,https://scrap.kakaocdn.net/dn/dL1cbS/hyYRybqfQm/lpSKkuPvNUlEc7sIkLiPr1/img.png?width=800&amp;amp;height=536&amp;amp;face=0_0_800_536,https://scrap.kakaocdn.net/dn/D7AaN/hyYMXYfP25/26z7PkOcLaNDfWm3IkqPkk/img.png?width=2092&amp;amp;height=1672&amp;amp;face=0_0_2092_1672');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Valkey 8에 반영된 개선사항 - new hash table&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;작년 3월, Redis는 기존 BSD 라이선스에서 Redis Source Available License (RSALv2)과Server Side Public License (SSPLv1) 듀얼 라이센스를 적용했다.https://redis.io/blog/redis-adopts-dual-source-available-licensing/ 개인 사용자나 Re&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dol9.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis는 single thread다 -&amp;gt; single thread니까 분산락하면 Redis지!! 라는 생각의 흐름이였는데 multi thread??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능 개선의 비교군인 Valkey 7.2에서는 어떻게 처리했는지 알아보고 Valkey 8에선 multi thread를 어떻게 적용했는지 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7.2 이해하는데 시간이 꽤 오래걸렸지만 나름 파악이 된 상태에서 8을 보니 금방 이해가 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ Valkey 7.2에 설명 중 틀리거나 보완이 필요한거 알려주시면 감사하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_valkey8_multithread.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XKY97/btsN3hd0K9V/KuarPt6rVV52nnmafPEjs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XKY97/btsN3hd0K9V/KuarPt6rVV52nnmafPEjs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XKY97/btsN3hd0K9V/KuarPt6rVV52nnmafPEjs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXKY97%2FbtsN3hd0K9V%2FKuarPt6rVV52nnmafPEjs1%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;750&quot; height=&quot;750&quot; data-filename=&quot;edited_valkey8_multithread.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;700&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Valkey 7.2 single thread&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rdb, aof 같은 별도의 스레드는 차치하더라도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알아보니 클라이언트의 모든 요청을 정말 single thread로 처리하는 게 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;node.js 처럼 IO Multiplexing 기술을 이용해서 여러 IO 연산을 single thread로 동시에 처리하는 거였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 지금까지 잘못 알고 있었네... (코 쓱..)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PckHZ/btsN1MNm7D2/8jKh1KLu0kwqsrSA5qKA70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PckHZ/btsN1MNm7D2/8jKh1KLu0kwqsrSA5qKA70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PckHZ/btsN1MNm7D2/8jKh1KLu0kwqsrSA5qKA70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPckHZ%2FbtsN1MNm7D2%2F8jKh1KLu0kwqsrSA5qKA70%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;800&quot; height=&quot;264&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;264&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;Valkey 7.2 코드를 보기 전에 Multiplexing 개념을&amp;nbsp;알면&amp;nbsp;좋을 것&amp;nbsp;같다..&amp;nbsp;찍먹 해보자&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;IO Multiplexing&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 두 글에서 Multiplexing 개념과 실제 코드까지 설명이 너무 잘 됐는데 내가 부족해서 온전히 이해를 못 하겠다. ㅜ,ㅜ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.naver.com/n_cloudplatform/222189669084&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.naver.com/n_cloudplatform/222189669084&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.naver.com/n_cloudplatform/222255261317&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.naver.com/n_cloudplatform/222255261317&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1746531199800&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[네이버클라우드 기술&amp;amp;경험] IO Multiplexing (IO 멀티플렉싱) 기본 개념부터 심화까지 -2부-&quot; data-og-description=&quot;I/O Multiplexing (IO 멀티플렉싱) *톺아보기 포스팅! *톺아보다 : 샅샅이 더듬어 뒤지면서 찾아보다....&quot; data-og-host=&quot;blog.naver.com&quot; data-og-source-url=&quot;https://blog.naver.com/n_cloudplatform/222255261317&quot; data-og-url=&quot;https://blog.naver.com/n_cloudplatform/222255261317&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/MEbR1/hyYMSigMta/1bj3KiuUwdfMwsY4zNzVX1/img.png?width=743&amp;amp;height=372&amp;amp;face=0_0_743_372&quot;&gt;&lt;a href=&quot;https://blog.naver.com/n_cloudplatform/222255261317&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.naver.com/n_cloudplatform/222255261317&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/MEbR1/hyYMSigMta/1bj3KiuUwdfMwsY4zNzVX1/img.png?width=743&amp;amp;height=372&amp;amp;face=0_0_743_372');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[네이버클라우드 기술&amp;amp;경험] IO Multiplexing (IO 멀티플렉싱) 기본 개념부터 심화까지 -2부-&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I/O Multiplexing (IO 멀티플렉싱) *톺아보기 포스팅! *톺아보다 : 샅샅이 더듬어 뒤지면서 찾아보다....&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.naver.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;i&gt;대강 느낌적으로 정리해 보면&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Everything is a File&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;OS 입장에서는 일반 파일뿐 아니라 socket, 터미널, 파이프, 디바이스 모두 File Descriptor(이하 fd) 중 하나로 취급한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fd는 표준입력(0), 표준 출력(1), 표준 에러(2) 총 3가지 종류가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Multiplexing은 여러 fd를 하나의 프로세스가 관리하는 기술이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 fd를 어떤 상태로 대기하냐에 따라서&lt;br /&gt;&lt;u&gt;&lt;b&gt;리눅스&lt;/b&gt;&lt;/u&gt;는 select, poll, epoll. &lt;u&gt;&lt;b&gt;맥&lt;/b&gt;&lt;/u&gt;은 kqueue, &lt;u&gt;&lt;b&gt;윈도우&lt;/b&gt;&lt;/u&gt;는 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;Multiplexing은 &lt;u&gt;&lt;b&gt;Asynchronous Blocking I/O&lt;/b&gt;&lt;/u&gt;라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Application에서 Kernel로 System call 하고 제어권은 Application에서 유지한 채 다른 작업을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Kernel에서 작업이 완료되어 데이터가 준비되면 Kernel에서 Application으로 콜백 신호를 보내고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Application은 Kernel에 있던 데이터를 user 영역의 buffer로 복사하는 흐름이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dZ0vbr/btsNKjrDEdr/b01auSXKuMZBKxEJqSWko0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dZ0vbr/btsNKjrDEdr/b01auSXKuMZBKxEJqSWko0/img.gif&quot; data-alt=&quot;https://developer.ibm.com/articles/l-async/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dZ0vbr/btsNKjrDEdr/b01auSXKuMZBKxEJqSWko0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dZ0vbr/btsNKjrDEdr/b01auSXKuMZBKxEJqSWko0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;541&quot; height=&quot;340&quot; data-origin-width=&quot;541&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://developer.ibm.com/articles/l-async/&lt;/figcaption&gt;
&lt;/figure&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;b&gt;그래서 Valkey 7.2에서는 어떻게 동작한다고?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Valkey 7.2에서 클라이언트 요청을 어떻게 처리했는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 서버가 시작되면 Server와 Listener가 초기화된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Server 초기화할 때는 &lt;u&gt;File Event&lt;/u&gt;와&amp;nbsp;&lt;u&gt;Timer Event&lt;/u&gt;를&amp;nbsp;만든다.&lt;br /&gt;Listner 초기화할 때는 server listner에 accept 했을 때 실행할 &lt;u&gt;AcceptHandler&lt;/u&gt;를 등록한다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;int main(int argc, char **argv) {
  ...
  initServer();
  ...
  initListeners();
  ...
  aeMain(server.el);
}

void initServer(void) {
  ...
  // event loop fd 생성
  server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
  ...
  // 앞에서 만들어진 event loop fd에 timer event 추가
  if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
    serverPanic(&quot;Can't create event loop timers.&quot;);
    exit(1);
  }
  ...
}

void initListeners(void) {
    /* Setup listeners from server config for TCP/TLS/Unix */
    int conn_index;
    connListener *listener;
    if (server.port != 0) {
        conn_index = connectionIndexByType(CONN_TYPE_SOCKET);
        if (conn_index &amp;lt; 0)
            serverPanic(&quot;Failed finding connection listener of %s&quot;, CONN_TYPE_SOCKET);
        listener = &amp;amp;server.listeners[conn_index];
        ...
        listener-&amp;gt;port = server.port;
    }
    ...
    /* create all the configured listener, and add handler to start to accept */
    int listen_fds = 0;
    for (int j = 0; j &amp;lt; CONN_TYPE_MAX; j++) {
        ...
        if (createSocketAcceptHandler(listener, connAcceptHandler(listener-&amp;gt;ct)) != C_OK)
            serverPanic(&quot;Unrecoverable error creating %s listener accept handler.&quot;, listener-&amp;gt;ct-&amp;gt;get_type(NULL));

       listen_fds += listener-&amp;gt;count;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 initServer 함수에서 생성되는 이벤트만 알아보자. (File Event, Fired Event, Time Event)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;읽기와 쓰기 그리고 BARRIER 중 하나로 마스킹 된 server.maxclients+CONFIG_FDSET_INCR 개의 &lt;b&gt;File Event&lt;/b&gt; 풀을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;epoll 기준, epoll_wait로 처리할 이벤트를 받아 실행할 File Event 정보를 마스킹과 fd를 보관하는 &lt;b&gt;Fired Event&lt;/b&gt; 풀을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 &lt;b&gt;Time Event&lt;/b&gt;는 양방향 리스트로 만들어져 있고 id와 시간을 갖고 있고 trigger 될 때 실행할 timeProc가 있다.&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;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k3UcC/btsN2qbL3V9/v6ZbRHy1ItwwRFYp0YkuVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k3UcC/btsN2qbL3V9/v6ZbRHy1ItwwRFYp0YkuVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k3UcC/btsN2qbL3V9/v6ZbRHy1ItwwRFYp0YkuVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk3UcC%2FbtsN2qbL3V9%2Fv6ZbRHy1ItwwRFYp0YkuVK%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;1000&quot; height=&quot;505&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;505&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. initListners에서 AcceptHandler는 어떻게 처리될까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 글에서 소켓 리스너부터 EventLoop가 어떻게 처리되는지 잘 설명되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@ohjinseo/Redis%EA%B0%80-%EC%8B%B1%EA%B8%80-%EC%8A%A4%EB%A0%88%EB%93%9C-%EB%AA%A8%EB%8D%B8%EC%9E%84%EC%97%90%EB%8F%84-%EB%86%92%EC%9D%80-%EC%84%B1%EB%8A%A5%EC%9D%84-%EB%B3%B4%EC%9E%A5%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-IO-Multiplexing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Redis가 싱글 스레드 모델임에도 높은 성능을 보장하는 이유&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1746531667446&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Redis가 싱글 스레드 모델임에도 높은 성능을 보장하는 이유 (I/O Multiplexing)&quot; data-og-description=&quot;틀린 내용이 있을 수 있어 피드백 해주시면 감사하겠습니다  Redis는 Remote Dictionary Server이며, Dictionary, Set 등과 같은 메모리 내 자료구조를 제공하는 TCP 서버이다. Redis는 캐싱, 세션 저장소, 실&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@ohjinseo/Redis%EA%B0%80-%EC%8B%B1%EA%B8%80-%EC%8A%A4%EB%A0%88%EB%93%9C-%EB%AA%A8%EB%8D%B8%EC%9E%84%EC%97%90%EB%8F%84-%EB%86%92%EC%9D%80-%EC%84%B1%EB%8A%A5%EC%9D%84-%EB%B3%B4%EC%9E%A5%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-IO-Multiplexing&quot; data-og-url=&quot;https://velog.io/@ohjinseo/Redis가-싱글-스레드-모델임에도-높은-성능을-보장하는-이유-IO-Multiplexing&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/t3j0f/hyYM5vbDGX/BiNK3p5PMVIxXVOM4rCCP1/img.png?width=1200&amp;amp;height=766&amp;amp;face=0_0_1200_766,https://scrap.kakaocdn.net/dn/bfdnOf/hyYRwLqqmP/1wHic3YwQsUwxAokQEMAQK/img.png?width=1200&amp;amp;height=766&amp;amp;face=0_0_1200_766,https://scrap.kakaocdn.net/dn/bcweDK/hyYM3KWHc2/Gtp75Wp0giGeax8I9RLxLK/img.png?width=1400&amp;amp;height=610&amp;amp;face=0_0_1400_610&quot;&gt;&lt;a href=&quot;https://velog.io/@ohjinseo/Redis%EA%B0%80-%EC%8B%B1%EA%B8%80-%EC%8A%A4%EB%A0%88%EB%93%9C-%EB%AA%A8%EB%8D%B8%EC%9E%84%EC%97%90%EB%8F%84-%EB%86%92%EC%9D%80-%EC%84%B1%EB%8A%A5%EC%9D%84-%EB%B3%B4%EC%9E%A5%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-IO-Multiplexing&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@ohjinseo/Redis%EA%B0%80-%EC%8B%B1%EA%B8%80-%EC%8A%A4%EB%A0%88%EB%93%9C-%EB%AA%A8%EB%8D%B8%EC%9E%84%EC%97%90%EB%8F%84-%EB%86%92%EC%9D%80-%EC%84%B1%EB%8A%A5%EC%9D%84-%EB%B3%B4%EC%9E%A5%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0-IO-Multiplexing&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/t3j0f/hyYM5vbDGX/BiNK3p5PMVIxXVOM4rCCP1/img.png?width=1200&amp;amp;height=766&amp;amp;face=0_0_1200_766,https://scrap.kakaocdn.net/dn/bfdnOf/hyYRwLqqmP/1wHic3YwQsUwxAokQEMAQK/img.png?width=1200&amp;amp;height=766&amp;amp;face=0_0_1200_766,https://scrap.kakaocdn.net/dn/bcweDK/hyYM3KWHc2/Gtp75Wp0giGeax8I9RLxLK/img.png?width=1400&amp;amp;height=610&amp;amp;face=0_0_1400_610');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Redis가 싱글 스레드 모델임에도 높은 성능을 보장하는 이유 (I/O Multiplexing)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;틀린 내용이 있을 수 있어 피드백 해주시면 감사하겠습니다  Redis는 Remote Dictionary Server이며, Dictionary, Set 등과 같은 메모리 내 자료구조를 제공하는 TCP 서버이다. Redis는 캐싱, 세션 저장소, 실&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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번에서 봤듯이 initListners 함수 안에서 클라이언트가 접속하면 실행할 acceptHandler 함수를 붙인다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;if (createSocketAcceptHandler(listener, connAcceptHandler(listener-&amp;gt;ct)) != C_OK)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createSocketAcceptHandler 함수는 listener fd를 read 형태의 File Event로 Multiplexing에 등록한다.&lt;/p&gt;
&lt;pre id=&quot;code_1747543905818&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int createSocketAcceptHandler(connListener *sfd, aeFileProc *accept_handler) {
  int j;

  for (j = 0; j &amp;lt; sfd-&amp;gt;count; j++) {
    if (aeCreateFileEvent(server.el, sfd-&amp;gt;fd[j], AE_READABLE, accept_handler,sfd) == AE_ERR) {
      /* Rollback */
      ...   
    }
  }
  return C_OK;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 접속하면 아래 흐름으로 accept_handler가 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 서버 main 함수에서 while loop를 돌면서 aeProcessEvents를 실행하는 aeMain을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. aeProcessEvents함수는 aeApiPoll를 실행해 발생한 이벤트 목록을 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 2-1. aeApiPoll이 하는 일은 epoll, kqueue 등 기술에 맞게(?) waiting 후 이벤트 목록을 받으면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; Fired Event에 마스킹 정보와 fd 정보를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 마스킹이 read인지 write인지에 따라 분기가 있어 알맞은 함수를 실행한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WcfWW/btsN27vKssE/Zqd5HLWY62nzjQ5TJaaWMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WcfWW/btsN27vKssE/Zqd5HLWY62nzjQ5TJaaWMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WcfWW/btsN27vKssE/Zqd5HLWY62nzjQ5TJaaWMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWcfWW%2FbtsN27vKssE%2FZqd5HLWY62nzjQ5TJaaWMK%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;2000&quot; height=&quot;628&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;accept_handler을 타고 가다 보면 networking.c -&amp;gt; acceptCommonHandler에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;createClient&lt;/b&gt; 함수를 호출해 클라이언트 구조체를 초기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 클라이언트의 요청받는 handler를 설정해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 응답 쓰는 건 writeToClient를 직접 호출한다.&lt;/p&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;1. connection을 맺으면 reader handler를 설정해 주는데 readQueryFromClient 함수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. connection type에 reader_handler 함수를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. connection fd를 aeCreateFileEvent에 등록하는데 이벤트가 발생하면 ae_handler를 실행하도록 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. ae_handler는 connSocketEventHandler 함수인데 결국 마스킹값에 따라 분기처리된다.&lt;br /&gt;5. readQueryFromClient를 실행하는데 이 함수는 클라이언트로부터 명령어를 읽어 실행하는 역할을 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;1009&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMAUlR/btsN2eCBp9R/zMovqkyyTdbGepTNl6IdtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMAUlR/btsN2eCBp9R/zMovqkyyTdbGepTNl6IdtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMAUlR/btsN2eCBp9R/zMovqkyyTdbGepTNl6IdtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMAUlR%2FbtsN2eCBp9R%2FzMovqkyyTdbGepTNl6IdtK%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;1500&quot; height=&quot;1009&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1500&quot; data-origin-height=&quot;1009&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;size18&quot;&gt;&lt;b&gt;3. 클라이언트 요청은 이것이 끝이 아니었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 구조와 ttl 정도만 살펴봤지 이 정도로 클라이언트 처리 과정을 본 건 처음이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번 정도 했으면 다 봤겠지 했는데 웬걸... 별도의 pthread가 돌고 있었다. :wow:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 살펴본 aeProcessEvents에서 aeApiPoll을 실행하기 전에 beforeSleep 함수를 실행하는데&lt;br /&gt;이때 지연된 read, write 요청을 IO 스레드를 이용해서 처리하는 로직이 있다.&amp;nbsp; networking.c -&amp;gt; IOThreadMain&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fan-out -&amp;gt; fan-in을 이용해 스레드 세이프하게 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Main Thread가 0보다 큰 값으로 setIOPendingCount를 호출하면서 IO Thread &lt;b&gt;fan-out&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Main Thread가 getIOPendingCount를 주기적으로 호출하면서 0이 될 때까지 기다리다가 0이 되면 Main Thread &lt;b&gt;fan-in&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;542&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cv6Aai/btsN2aUqpfe/69yJ3m7PwWs8baRR1yxg01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cv6Aai/btsN2aUqpfe/69yJ3m7PwWs8baRR1yxg01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cv6Aai/btsN2aUqpfe/69yJ3m7PwWs8baRR1yxg01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcv6Aai%2FbtsN2aUqpfe%2F69yJ3m7PwWs8baRR1yxg01%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;2000&quot; height=&quot;542&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;542&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;size18&quot;&gt;&lt;b&gt;3. File Event &amp;amp; Filred Event&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 클라이언트 흐름을 설명하면서 File Event 처리도 함께 설명했지만 정리하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굉장히 단순화했지만 뭐.. 느낌 있으니까..ㅋㅋㅋ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEEwRs/btsN2cxXyCQ/dfMpuYGPNaX0L5ddIBdJ80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEEwRs/btsN2cxXyCQ/dfMpuYGPNaX0L5ddIBdJ80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEEwRs/btsN2cxXyCQ/dfMpuYGPNaX0L5ddIBdJ80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEEwRs%2FbtsN2cxXyCQ%2FdfMpuYGPNaX0L5ddIBdJ80%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;800&quot; height=&quot;574&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;574&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;size18&quot;&gt;&lt;b&gt;4. Time Event&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;initServer 함수에서 Time Event 초기화 부분을 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1747555217249&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
  serverPanic(&quot;Can't create event loop timers.&quot;);
  exit(1);
}

// eventLoop에 Time Event head세팅! 실행하는 함수는 serverCron
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
        aeTimeProc *proc, void *clientData,
        aeEventFinalizerProc *finalizerProc)
{
    long long id = eventLoop-&amp;gt;timeEventNextId++;
    aeTimeEvent *te;

    te = zmalloc(sizeof(*te));
    if (te == NULL) return AE_ERR;
    te-&amp;gt;id = id;
    te-&amp;gt;when = getMonotonicUs() + milliseconds * 1000;
    te-&amp;gt;timeProc = proc;
    te-&amp;gt;finalizerProc = finalizerProc;
    te-&amp;gt;clientData = clientData;
    te-&amp;gt;prev = NULL;
    te-&amp;gt;next = eventLoop-&amp;gt;timeEventHead;
    te-&amp;gt;refcount = 0;
    if (te-&amp;gt;next)
        te-&amp;gt;next-&amp;gt;prev = te;
    eventLoop-&amp;gt;timeEventHead = te;
    return id;
}&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;aeProcessEvents 마지막 즈음에 Time Event 실행하는 함수가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Time Event에 설정한 시간보다 현재시간이 과거면 timeProc(serverCron)를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AE_NOMORE은 -1인데 serverCron은 `1000/server.hz;` 를 리턴하기 때문에 -1이 나올 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 serverCron 실행 후 삭제하지 않고 when 값을 now + retval*1000로 수정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;539&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H4XA6/btsN2QOMpdu/VkR5IW8K8l0UvpIN6M5dl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H4XA6/btsN2QOMpdu/VkR5IW8K8l0UvpIN6M5dl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H4XA6/btsN2QOMpdu/VkR5IW8K8l0UvpIN6M5dl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH4XA6%2FbtsN2QOMpdu%2FVkR5IW8K8l0UvpIN6M5dl1%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;1000&quot; height=&quot;539&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;539&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4rSrQ/btsN1shhUIS/3XArBSWIpxMSEyKwvCzbM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4rSrQ/btsN1shhUIS/3XArBSWIpxMSEyKwvCzbM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4rSrQ/btsN1shhUIS/3XArBSWIpxMSEyKwvCzbM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4rSrQ%2FbtsN1shhUIS%2F3XArBSWIpxMSEyKwvCzbM0%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;751&quot; height=&quot;358&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;381&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;7.2에서 File Event와 Time Event가 어떻게 실행되는지 알아봤고&lt;br /&gt;File Event를 이용해서 Valkey가 listner와 client event가 어떻게 실행되는지 알아봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pending 요청을 별도의 thread가 처리하긴 하지만 대부분의 일을 Main Thread가 한다.&lt;/p&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://gutte.tistory.com/153?pidx=4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Redis EventLoop에 디버깅으로 자세하게 소개한 글&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Valkey 8에 적용된 multi thread&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Valkey 블로그에 소개된 multi thread 그림이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7.2에서 pending read/write 요청을 fan-out, fan-in 방식으로 IO Thread로 처리하는 건 알았는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림은 뭔가 peding 구분 없이 IO Thread에서 많은것들을 처리하는 걸로 보인다.&lt;/p&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;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bI4wCE/btsN1T6sQeU/OG6HikhQged0Kw7pYGyHUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bI4wCE/btsN1T6sQeU/OG6HikhQged0Kw7pYGyHUK/img.png&quot; data-alt=&quot;https://valkey.io/blog/unlock-one-million-rps/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bI4wCE/btsN1T6sQeU/OG6HikhQged0Kw7pYGyHUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbI4wCE%2FbtsN1T6sQeU%2FOG6HikhQged0Kw7pYGyHUK%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;800&quot; height=&quot;610&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;610&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://valkey.io/blog/unlock-one-million-rps/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Valkey 블로그 글 정리&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;IO Thread가 하는 작업에는&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;1. 클라이언트의 명령을 읽어 구문 분석하거나&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;2. 클라이언트에게 응답을 다시 작성하거나&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;3. TCP 연결에서 I/O 이벤트를 폴링 하거나&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;4. 메모리 할당을 해제하는 작업이 포함된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;IO Thread는 I/O 처리하느라 바쁘지만 Main Thread는 실제 명령을 실행하는 데 더 많은 시간을 할애한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;코드 살펴보기&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;1. IO Job을 ring buffer 형태로 만들어 data와 함께 job_handler 실행하도록 만들었다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;io_jobs 256개를 만들고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;io_jobs 하나당 2048개의 ringbuffer를 만들었다. 와우!!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;총 256*2048= 524,288 개&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747557745582&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define IO_THREADS_MAX_NUM 256

static pthread_t io_threads[IO_THREADS_MAX_NUM] = {0};
static pthread_mutex_t io_threads_mutex[IO_THREADS_MAX_NUM];

/* IO jobs queue functions - Used to send jobs from the main-thread to the IO thread. */
typedef void (*job_handler)(void *);
typedef struct iojob {
    job_handler handler;
    void *data;
} iojob;

typedef struct IOJobQueue {
    iojob *ring_buffer;
    size_t size;
    _Atomic size_t head __attribute__((aligned(CACHE_LINE_SIZE))); /* Next write index for producer (main-thread) */
    _Atomic size_t tail __attribute__((aligned(CACHE_LINE_SIZE))); /* Next read index for consumer  (IO-thread) */
} IOJobQueue;
IOJobQueue io_jobs[IO_THREADS_MAX_NUM] = {0};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;IOJobQueue에 Job을 추가하는 `IOJobQueue_push` 함수를 어디서 호출하는지 알면 파악이 될 것 같다.&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;size18&quot;&gt;&lt;b&gt;2. `&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;IOJobQueue_push` 함수를 실행하는 곳을 찾아서..&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;io_threads.c에서 &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;IOJobQueue_push 호출하는 곳을 찾아봤다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;1. client의 요청 읽을 때&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;2. client에 응답 쓸 때&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;3. client 해제할 때&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;4. 이미&amp;nbsp;존재하는&amp;nbsp;key의&amp;nbsp;value를&amp;nbsp;덮어쓰는&amp;nbsp;경우&amp;nbsp;기존&amp;nbsp;value&amp;nbsp;해제할&amp;nbsp;때&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;5.&amp;nbsp;beforeSleep 할&amp;nbsp;때&amp;nbsp;이벤트&amp;nbsp;목록을&amp;nbsp;가져오는&amp;nbsp;aePollApi&amp;nbsp;호출&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;6. TLS 한정 Accept도 IO thread로 처리&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;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;1. client의 요청 읽을 때&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747559286505&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void readQueryFromClient(connection *conn) {
  ...
  if (postponeClientRead(c)) return;
}


// Valkey 7.2
int postponeClientRead(client *c) {
    if (server.io_threads_active &amp;amp;&amp;amp;
        server.io_threads_do_reads &amp;amp;&amp;amp;
        !ProcessingEventsWhileBlocked &amp;amp;&amp;amp;
        !(c-&amp;gt;flags &amp;amp; (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_BLOCKED)) &amp;amp;&amp;amp;
        io_threads_op == IO_THREADS_OP_IDLE)
    {
        listAddNodeHead(server.clients_pending_read,c);
        c-&amp;gt;pending_read_list_node = listFirst(server.clients_pending_read);
        return 1;
    } else {
        return 0;
    }
}


// Valkey 8.1
int postponeClientRead(client *c) {
  if (ProcessingEventsWhileBlocked) return 0;

  // IOJobQueue에 push	
  return (trySendReadToIOThreads(c) == C_OK);
}&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;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;2. client에 응답 쓸 때&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747559661331&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Valkey 7.2
void sendReplyToClient(connection *conn) {
    client *c = connGetPrivateData(conn);
    writeToClient(c,1);
}

// Valkey 8.1
void sendReplyToClient(connection *conn) {
    client *c = connGetPrivateData(conn);
    if (trySendWriteToIOThreads(c) == C_OK) return;
    writeToClient(c);
}&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;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;3. client 해제할 때&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747559804245&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Valkey 7.2
void freeClientArgv(client *c) {
    int j;
    for (j = 0; j &amp;lt; c-&amp;gt;argc; j++)
        decrRefCount(c-&amp;gt;argv[j]);
    c-&amp;gt;argc = 0;
    c-&amp;gt;cmd = NULL;
    c-&amp;gt;argv_len_sum = 0;
    c-&amp;gt;argv_len = 0;
    zfree(c-&amp;gt;argv);
    c-&amp;gt;argv = NULL;
}


// Valkey 8.1
void freeClientArgv(client *c) {
    /* If original_argv exists, 'c-&amp;gt;argv' was allocated by the main thread,
     * so it's more efficient to free it directly here rather than offloading to IO threads */
    if (c-&amp;gt;original_argv || tryOffloadFreeArgvToIOThreads(c, c-&amp;gt;argc, c-&amp;gt;argv) == C_ERR) {
        for (int j = 0; j &amp;lt; c-&amp;gt;argc; j++) decrRefCount(c-&amp;gt;argv[j]);
        zfree(c-&amp;gt;argv);
    }
    c-&amp;gt;argc = 0;
    c-&amp;gt;cmd = NULL;
    c-&amp;gt;io_parsed_cmd = NULL;
    c-&amp;gt;argv_len_sum = 0;
    c-&amp;gt;argv_len = 0;
    c-&amp;gt;argv = NULL;
}&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;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;4. 이미 존재하는 key의 value를 덮어쓰는 경우 기존 value 해제할 때&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747560255346&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Valkey 7.2
static void dbSetValue(...) {
  if (server.lazyfree_lazy_server_del) {
    freeObjAsync(key,old,db-&amp;gt;id);
  } else {
  /* This is just decrRefCount(old); */
    db-&amp;gt;dict-&amp;gt;type-&amp;gt;valDestructor(db-&amp;gt;dict, old);
  }
}

// Valkey 8.1
static void dbSetValue(...) {
  /* For efficiency, let the I/O thread that allocated an object also deallocate it. */
  if (tryOffloadFreeObjToIOThreads(old) == C_OK) {
    /* OK */
  } else if (server.lazyfree_lazy_server_del) {
    freeObjAsync(key, old, db-&amp;gt;id);
  } else {
    decrRefCount(old);
  }
  *valref = new;
}&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;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;5. beforeSleep 할 때 이벤트 목록을 가져오는 aePollApi 호출&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747560487646&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Valkey 8에 추가

void beforeSleep(struct aeEventLoop *eventLoop) {
  /* When I/O threads are enabled and there are pending I/O jobs, the poll is offloaded to one of the I/O threads. */
  trySendPollJobToIOThreads();
  ...
}


void trySendPollJobToIOThreads(void) {
  ...
  IOJobQueue_push(jq, IOThreadPoll, server.el);
}

void IOThreadPoll(void *data) {
  aeEventLoop *el = (aeEventLoop *)data;
  struct timeval tvp = {0, 0};
  int num_events = aePoll(el, &amp;amp;tvp);

  server.io_ae_fired_events = num_events;
  atomic_store_explicit(&amp;amp;server.io_poll_state, AE_IO_STATE_DONE, memory_order_release);
}

int aePoll(aeEventLoop *eventLoop, struct timeval *tvp) {
  AE_LOCK(eventLoop);

  int ret = aeApiPoll(eventLoop, tvp);

  AE_UNLOCK(eventLoop);
  return ret;
}&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;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;6. TLS 한정 Accept도 IO thread로 처리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747560644785&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Valkey 8.1

static int connTLSAccept(connection *_conn, ConnectionCallbackFunc accept_handler) {
  ...
  /* Try to offload accept to IO threads */
  if (trySendAcceptToIOThreads(_conn) == C_OK) return C_OK;
}

int trySendAcceptToIOThreads(connection *conn) {
  ...
  IOJobQueue_push(job_queue, ioThreadAccept, c);
}

static void ioThreadAccept(void *data) {
  client *c = (client *)data;
  connAccept(c-&amp;gt;conn, NULL);
  c-&amp;gt;io_read_state = CLIENT_COMPLETED_IO;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;multi thread라고 했지만 실제 명령어 실행은 Main Thread에서 진행하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IO 작업이나 epoll_wait 같은 대기작업들을 IO Thread가 처리하면서 Transaction 기능은 온전히 유지되는 것 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료 찾다 보니 Redis 8도 보게 되었는데 뭔가 비슷한 느낌이 물씬 난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어라? Redis 8도 비슷하게 적용했나 보네..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://riferrei.com/the-engineering-wisdom-behind-rediss-single-threaded-design/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://riferrei.com/the-engineering-wisdom-behind-rediss-single-threaded-design/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1746534390544&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;The Engineering Wisdom Behind Redis&amp;rsquo;s Single-Threaded Design &amp;ndash; Ricardo Ferreira&quot; data-og-description=&quot;In the relentless pursuit of performance, our industry often gravitates toward seemingly obvious solutions: more cores, more threads, more concurrency. Yet Redis&amp;mdash;one of the most performant databases in the world&amp;mdash;has maintained its commitment to a prima&quot; data-og-host=&quot;riferrei.com&quot; data-og-source-url=&quot;https://riferrei.com/the-engineering-wisdom-behind-rediss-single-threaded-design/&quot; data-og-url=&quot;https://riferrei.com/the-engineering-wisdom-behind-rediss-single-threaded-design/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://riferrei.com/the-engineering-wisdom-behind-rediss-single-threaded-design/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://riferrei.com/the-engineering-wisdom-behind-rediss-single-threaded-design/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;The Engineering Wisdom Behind Redis&amp;rsquo;s Single-Threaded Design &amp;ndash; Ricardo Ferreira&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;In the relentless pursuit of performance, our industry often gravitates toward seemingly obvious solutions: more cores, more threads, more concurrency. Yet Redis&amp;mdash;one of the most performant databases in the world&amp;mdash;has maintained its commitment to a prima&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;riferrei.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;681&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lj50n/btsN28uER3B/IC6dToi5kLbhCuN0q5PSeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lj50n/btsN28uER3B/IC6dToi5kLbhCuN0q5PSeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lj50n/btsN28uER3B/IC6dToi5kLbhCuN0q5PSeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flj50n%2FbtsN28uER3B%2FIC6dToi5kLbhCuN0q5PSeK%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;750&quot; height=&quot;639&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;681&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;/p&gt;</description>
      <category>개발하면서/코드보면서</category>
      <category>redis</category>
      <category>valkey</category>
      <author>오산돌구</author>
      <guid isPermaLink="true">https://dol9.tistory.com/337</guid>
      <comments>https://dol9.tistory.com/337#entry337comment</comments>
      <pubDate>Sun, 18 May 2025 17:29:27 +0900</pubDate>
    </item>
    <item>
      <title>Valkey 8에 반영된 개선사항 - new hash table</title>
      <link>https://dol9.tistory.com/336</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;작년 3월, Redis는&amp;nbsp; 기존 BSD 라이선스에서 Redis Source Available License (&lt;b&gt;RSALv2&lt;/b&gt;)과&lt;br /&gt;Server Side Public License (&lt;b&gt;SSPLv1&lt;/b&gt;) 듀얼 라이센스를 적용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://redis.io/blog/redis-adopts-dual-source-available-licensing/&quot;&gt;https://redis.io/blog/redis-adopts-dual-source-available-licensing/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1414&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mKqgl/btsNJwSirrt/tDgRdhG7tqkmMKjjqxnak1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mKqgl/btsNJwSirrt/tDgRdhG7tqkmMKjjqxnak1/img.png&quot; data-alt=&quot;https://github.com/redis/redis/pull/13157&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mKqgl/btsNJwSirrt/tDgRdhG7tqkmMKjjqxnak1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmKqgl%2FbtsNJwSirrt%2FtDgRdhG7tqkmMKjjqxnak1%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;1414&quot; height=&quot;356&quot; data-origin-width=&quot;1414&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://github.com/redis/redis/pull/13157&lt;/figcaption&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;개인 사용자나 Redis를 이용한 라이브러리들은 영향 없지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS, Azure, GCP 같은 클라우드 업체에서 라이선스 버전 이후의 Redis는 서비스 제공이 어렵게 된 거다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러자 라이센스 반발로 만들어진 Redis 포크 프로젝트 Valkey를 많은 대기업들이 지원하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그저 개인의 치기 어린 포크 프로젝트가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://news.hada.io/topic?id=14436&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://news.hada.io/topic?id=14436&lt;/a&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년이 지난 지금,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;antirez가 복귀하고 VectorSet 데이터 타입이 추가되고, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Redis에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;AGPL 라이선스도 추가했지만 딱히 끌리지 않는다.&lt;br /&gt;※ antirez 옹 미안해요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올 초부터 Valkey에서 달라진 점들을 알고 싶었는데, 이번 기회에 알아보고자 한다.&lt;/p&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://valkey.io/blog/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://valkey.io/blog/&lt;/a&gt; 내용 중 Valkey 8 글들을 Google NotebookLM에 던져놓고 알아보기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료를 요약해 주고 정리도 해줘서 시작하는 허들은 낮출 수 있었지만 결국 내 걸로 만들려면 많은 시간과 노력은 필요했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2014&quot; data-origin-height=&quot;1350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hr70K/btsNKhz0q64/l8UJcpKq9K8XwKx1k1PAl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hr70K/btsNKhz0q64/l8UJcpKq9K8XwKx1k1PAl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hr70K/btsNKhz0q64/l8UJcpKq9K8XwKx1k1PAl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHr70K%2FbtsNKhz0q64%2Fl8UJcpKq9K8XwKx1k1PAl1%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;2014&quot; height=&quot;1350&quot; data-origin-width=&quot;2014&quot; data-origin-height=&quot;1350&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Valkey 8에 적용된 개선 내용&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;new hash table&lt;/li&gt;
&lt;li&gt;dictionary per slot&lt;/li&gt;
&lt;li&gt;key embedding&lt;/li&gt;
&lt;li&gt;multi-threaded architecture&lt;/li&gt;
&lt;li&gt;specific&amp;nbsp;command/data&amp;nbsp;type&amp;nbsp;optimizations&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번글에서는 Valkey 8.1에 반영된 새로운 hash table을 알아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://valkey.io/blog/new-hash-table/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://valkey.io/blog/new-hash-table/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1746336919088&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Valkey &amp;middot; A new hash table&quot; data-og-description=&quot;Viktor is an open source developer from Ericsson and one of the maintainers of Valkey.&quot; data-og-host=&quot;valkey.io&quot; data-og-source-url=&quot;https://valkey.io/blog/new-hash-table/&quot; data-og-url=&quot;https://valkey.io/blog/new-hash-table/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b22Hot/hyYM4P3Dtj/LuMY6Da3Ck8i0SE2iih2g1/img.png?width=1320&amp;amp;height=788&amp;amp;face=0_0_1320_788,https://scrap.kakaocdn.net/dn/FCFcd/hyYMTAYgL9/S8JpapaVMUkrXGlPn4Nqvk/img.png?width=925&amp;amp;height=573&amp;amp;face=0_0_925_573,https://scrap.kakaocdn.net/dn/LlEOa/hyYMT8PbWE/Bs0BcZWHdLvfRdoXHhtIoK/img.png?width=914&amp;amp;height=550&amp;amp;face=0_0_914_550&quot;&gt;&lt;a href=&quot;https://valkey.io/blog/new-hash-table/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://valkey.io/blog/new-hash-table/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b22Hot/hyYM4P3Dtj/LuMY6Da3Ck8i0SE2iih2g1/img.png?width=1320&amp;amp;height=788&amp;amp;face=0_0_1320_788,https://scrap.kakaocdn.net/dn/FCFcd/hyYMTAYgL9/S8JpapaVMUkrXGlPn4Nqvk/img.png?width=925&amp;amp;height=573&amp;amp;face=0_0_925_573,https://scrap.kakaocdn.net/dn/LlEOa/hyYMT8PbWE/Bs0BcZWHdLvfRdoXHhtIoK/img.png?width=914&amp;amp;height=550&amp;amp;face=0_0_914_550');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Valkey &amp;middot; A new hash table&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Viktor is an open source developer from Ericsson and one of the maintainers of Valkey.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;valkey.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하면 새로운 hash table 구조는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;key-value 하나당 약 20 byte를 아끼고, TTL이 적용된 key-value는 약 30 byte를 아낀다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와우!!!!!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dVUPOT/btsNKjdv1RQ/kHP2FFV8Gz0xrIhyaJUiY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dVUPOT/btsNKjdv1RQ/kHP2FFV8Gz0xrIhyaJUiY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dVUPOT/btsNKjdv1RQ/kHP2FFV8Gz0xrIhyaJUiY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdVUPOT%2FbtsNKjdv1RQ%2FkHP2FFV8Gz0xrIhyaJUiY1%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;914&quot; height=&quot;550&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;550&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;새로운 hash table을 알아보기 전에 기존 구조를 알아보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Valkey 7.2 Hash Table&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오래전에 파악한 내용이 있어 다시 봤는데 음... 다시 정리해 보자.&lt;/p&gt;
&lt;figure id=&quot;og_1746339939937&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Dict&quot; data-og-description=&quot;Dict는 다양한 자료구조를 담는 기본적인 redis의 자료구조입니다. key, value형식으로 되어있고 value에 사용자가 입력한 자료구조(?)들이 들어가게 되죠출처 : http://en.wikipedia.org/wiki/File:Hash_table_5_0_1_1&quot; data-og-host=&quot;dol9.tistory.com&quot; data-og-source-url=&quot;https://dol9.tistory.com/190&quot; data-og-url=&quot;https://dol9.tistory.com/190&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c8FWOf/hyYMQdbT0F/bAkgLFtugjHM0ytLQV5WKk/img.jpg?width=800&amp;amp;height=1017&amp;amp;face=0_0_800_1017,https://scrap.kakaocdn.net/dn/3ExrL/hyYMZOJ7Lp/cSdeUZxF2bdpSpiBm9hOkK/img.jpg?width=800&amp;amp;height=1017&amp;amp;face=0_0_800_1017,https://scrap.kakaocdn.net/dn/TshTm/hyYM2xWKYr/bKxLLn1ABCSuMbxXYftIX1/img.jpg?width=960&amp;amp;height=2886&amp;amp;face=0_0_960_2886&quot;&gt;&lt;a href=&quot;https://dol9.tistory.com/190&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dol9.tistory.com/190&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c8FWOf/hyYMQdbT0F/bAkgLFtugjHM0ytLQV5WKk/img.jpg?width=800&amp;amp;height=1017&amp;amp;face=0_0_800_1017,https://scrap.kakaocdn.net/dn/3ExrL/hyYMZOJ7Lp/cSdeUZxF2bdpSpiBm9hOkK/img.jpg?width=800&amp;amp;height=1017&amp;amp;face=0_0_800_1017,https://scrap.kakaocdn.net/dn/TshTm/hyYM2xWKYr/bKxLLn1ABCSuMbxXYftIX1/img.jpg?width=960&amp;amp;height=2886&amp;amp;face=0_0_960_2886');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Dict&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Dict는 다양한 자료구조를 담는 기본적인 redis의 자료구조입니다. key, value형식으로 되어있고 value에 사용자가 입력한 자료구조(?)들이 들어가게 되죠출처 : http://en.wikipedia.org/wiki/File:Hash_table_5_0_1_1&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dol9.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Valkey 7.2에서 dict 구조를 살펴보면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2092&quot; data-origin-height=&quot;1672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cySIUj/btsNL2VYBof/q8Xz33ySsnY4FQsA55icak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cySIUj/btsNL2VYBof/q8Xz33ySsnY4FQsA55icak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cySIUj/btsNL2VYBof/q8Xz33ySsnY4FQsA55icak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcySIUj%2FbtsNL2VYBof%2Fq8Xz33ySsnY4FQsA55icak%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;2092&quot; height=&quot;1672&quot; data-origin-width=&quot;2092&quot; data-origin-height=&quot;1672&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;특정 조건(해시충돌, 키 사용량 등)을 기준으로 bucket 수를 늘리기 위한 rehash를 목적으로 dictEntry **ht_table[2]가 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;rehash가 발생하지 않을 때는 ht_table[0]에서 조회/추가/삭제를 하지만,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;rehash 중일 때는 추가는 ht_table[1]만, 조회, 삭제는 ht_table[0]과 ht_table[1] 두 곳에 진행한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 구조에 &quot;FOO&quot; -&amp;gt; &quot;BAR&quot; key-value 쌍으로 저장된 상태에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회를 하려고&amp;nbsp;&lt;b&gt;$ get FOO &lt;/b&gt;를 실행하면 메모리 접근은 4번 일어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. ht_table[0][hash_function(&quot;FOO&quot;)%hash_size]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. ht_table[0][hash_function(&quot;FOO&quot;)%hash_size] -&amp;gt; key&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. ht_table[0][hash_function(&quot;FOO&quot;)%hash_size] -&amp;gt; val&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. ht_table[0][hash_function(&quot;FOO&quot;)%hash_size] -&amp;gt; val -&amp;gt; ptr&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 주소 정보를 담고 있는 포인터는 공짜가 아니다. 64bit OS 기준 8 byte다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;507&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQsmF2/btsNJ3vjSRq/nFrB7vvt8nJwBfcd57MSKk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQsmF2/btsNJ3vjSRq/nFrB7vvt8nJwBfcd57MSKk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQsmF2/btsNJ3vjSRq/nFrB7vvt8nJwBfcd57MSKk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQsmF2%2FbtsNJ3vjSRq%2FnFrB7vvt8nJwBfcd57MSKk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;423&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;507&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Valkey 8.1 Hash Table&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아예 새로운 프로젝트다.&amp;nbsp; 새롭게 보이는 kvstore, hashtable 파일에 블로그에 쓰인 디자인들이 구현되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 주석에 쓰여있는 친절한 설명과 Copilot의 /explain 기능, 그리고 상상력을 더해 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 Hash Table의 흐름은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3412&quot; data-origin-height=&quot;1214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DUtwO/btsNJ0egnUS/C3ABYQ6xPc97enUOWw06wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DUtwO/btsNJ0egnUS/C3ABYQ6xPc97enUOWw06wk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DUtwO/btsNJ0egnUS/C3ABYQ6xPc97enUOWw06wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDUtwO%2FbtsNJ0egnUS%2FC3ABYQ6xPc97enUOWw06wk%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;3412&quot; height=&quot;1214&quot; data-origin-width=&quot;3412&quot; data-origin-height=&quot;1214&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. kvstore와 hashtable 조합으로 기존 dict를 교체했다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 뜰 때 관리하는 DB 구조체가 있는데(7.2 redisDb, 8.1 serverDb)&lt;br /&gt;가장 중요한 dict와 expires key-value를 dict에서 kvstore + hashtable로 바뀌었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ dict 변수명을 keys로 바꾼 것 나이스!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kvstore 없이 바로 hashtable만 써도 될 것 같은데 뭔가 이유가 있겠지.. 하며 다음을 기약한다.&lt;/p&gt;
&lt;pre id=&quot;code_1746349867739&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Valkey 7.2
/* Redis database representation. There are multiple databases identified
 * by integers from 0 (the default database) up to the max configured
 * database. The database number is the 'id' field in the structure. */
typedef struct redisDb {
    dict *dict;                 /* The keyspace for this DB */
    dict *expires;              /* Timeout of keys with a timeout set */
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/
    dict *blocking_keys_unblock_on_nokey;   /* Keys with clients waiting for
                                             * data, and should be unblocked if key is deleted (XREADEDGROUP).
                                             * This is a subset of blocking_keys*/
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    int id;                     /* Database ID */
    long long avg_ttl;          /* Average TTL, just for stats */
    unsigned long expires_cursor; /* Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
    clusterSlotToKeyMapping *slots_to_keys; /* Array of slots to keys. Only used in cluster mode (db 0). */
} redisDb;

struct dict {
    dictType *type;

    dictEntry **ht_table[2];
    unsigned long ht_used[2];

    long rehashidx; /* rehashing not in progress if rehashidx == -1 */

    /* Keep small vars at end for optimal (minimal) struct padding */
    int16_t pauserehash; /* If &amp;gt;0 rehashing is paused (&amp;lt;0 indicates coding error) */
    signed char ht_size_exp[2]; /* exponent of size. (size = 1&amp;lt;&amp;lt;exp) */

    void *metadata[];           /* An arbitrary number of bytes (starting at a
                                 * pointer-aligned address) of size as defined
                                 * by dictType's dictEntryBytes. */
};



// Valkey 8.1
/* Database representation. There are multiple databases identified
 * by integers from 0 (the default database) up to the max configured
 * database. The database number is the 'id' field in the structure. */
typedef struct serverDb {
    kvstore *keys;                        /* The keyspace for this DB */
    kvstore *expires;                     /* Timeout of keys with a timeout set */
    dict *blocking_keys;                  /* Keys with clients waiting for data (BLPOP)*/
    dict *blocking_keys_unblock_on_nokey; /* Keys with clients waiting for
                                           * data, and should be unblocked if key is deleted (XREADEDGROUP).
                                           * This is a subset of blocking_keys*/
    dict *ready_keys;                     /* Blocked keys that received a PUSH */
    dict *watched_keys;                   /* WATCHED keys for MULTI/EXEC CAS */
    int id;                               /* Database ID */
    long long avg_ttl;                    /* Average TTL, just for stats */
    unsigned long expires_cursor;         /* Cursor of the active expire cycle. */
} serverDb;

struct _kvstore {
    int flags;
    hashtableType *dtype;
    hashtable **hashtables;
    int num_hashtables;
    int num_hashtables_bits;
    list *rehashing;                          /* List of hash tables in this kvstore that are currently rehashing. */
    int resize_cursor;                        /* Cron job uses this cursor to gradually resize hash tables (only used if num_hashtables &amp;gt; 1). */
    int allocated_hashtables;                 /* The number of allocated hashtables. */
    int non_empty_hashtables;                 /* The number of non-empty hashtables. */
    unsigned long long key_count;             /* Total number of keys in this kvstore. */
    unsigned long long bucket_count;          /* Total number of buckets in this kvstore across hash tables. */
    unsigned long long *hashtable_size_index; /* Binary indexed tree (BIT) that describes cumulative key frequencies up until
                                               * given hashtable-index. */
    size_t overhead_hashtable_lut;            /* Overhead of all hashtables in bytes. */
    size_t overhead_hashtable_rehashing;      /* Overhead of hash tables rehashing in bytes. */
};

struct hashtable {
    hashtableType *type;
    ssize_t rehash_idx;        /* -1 = rehashing not in progress. */
    bucket *tables[2];         /* 0 = main table, 1 = rehashing target.  */
    size_t used[2];            /* Number of entries in each table. */
    int8_t bucket_exp[2];      /* Exponent for num buckets (num = 1 &amp;lt;&amp;lt; exp). */
    int16_t pause_rehash;      /* Non-zero = rehashing is paused */
    int16_t pause_auto_shrink; /* Non-zero = automatic resizing disallowed. */
    size_t child_buckets[2];   /* Number of allocated child buckets. */
    void *metadata[];
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 캐시라인에 맞춘 64 byte에 bucket을 만들었다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시라인 설명을 위해 어렴풋하게 알고 있는 프로그램 실행 흐름을 얘기하면&lt;br /&gt;작성한 파일을 메인 메모리에 올리고 명령어와 데이터를 CPU로 가져와서 실행한다고 알고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 CPU가 데이터를 빨리 가져오기 위해 CPU 캐시가 존재하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;캐시&amp;nbsp;라인&lt;/b&gt;&lt;/u&gt;은 CPU 캐시 메모리의 기본 데이터 전송 단위, 즉 캐시의 최소 단위 크기를 말하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 하드웨어는 64 바이트라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 적극 활용하려고 Valkey 8.1에서는 bucket 크기를 64 바이트로 맞췄다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bucket 크기를 64 byte로 bucket을 맞췄을 때 장점은 hash 충돌이 발생한 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU에서 추가 메모리 접근 없이 이미 CPU 캐시에 로드한 데이터에 다른 Entry 정보도 있기 때문에 빠른 탐색이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 값이 다른 key인데 hash 값이 같다면 동일한 bucket에 담겨 있을 테고 그럼 두 번째 Entry에 저장이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 bucket은 8byte의 &lt;u&gt;&lt;b&gt;메타데이터&lt;/b&gt;&lt;/u&gt;와 8 byte &lt;u&gt;&lt;b&gt;Entry&lt;/b&gt;&lt;/u&gt; 7개로 구성되어 있다. (8 + 8*7 = 64)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메타 데이터&lt;/b&gt;는 1. chain 여부, 2. key 존재 여부, 3. Entry 7개의 key를 1 byte로 표현한 secondary hash로 구성되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나씩 알아보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;1378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/balJ1Q/btsNJKpfSgl/7YKJEyfFGg84B0LKTnJEG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/balJ1Q/btsNJKpfSgl/7YKJEyfFGg84B0LKTnJEG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/balJ1Q/btsNJKpfSgl/7YKJEyfFGg84B0LKTnJEG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbalJ1Q%2FbtsNJKpfSgl%2F7YKJEyfFGg84B0LKTnJEG1%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;1334&quot; height=&quot;1378&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;1378&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. bucket이 가득 찼을 땐 자식 bucket을 만들어 처리한다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 하나의 bucket에는 7개의 Entry가 존재한다고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 7개 Entry가 있는 bucket에 Entry를 추가하면 어떤 일이 일어날까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 새로운 Bucket을 만들고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 기존 Bucket의 일곱 번째 Entry를 새로운 Bucket 첫번째로&amp;nbsp;옮긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 기존 Bucket 메타데이터 수정&lt;br /&gt;&amp;nbsp; &amp;nbsp; bucket_from-&amp;gt;chained = 1 그리고 bucket_from-&amp;gt;presence&amp;nbsp;&amp;amp;=&amp;nbsp;~(1&amp;nbsp;&amp;lt;&amp;lt;&amp;nbsp;pos_from)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 추가할 Entry는 새로운 bucket 두 번째 Entry에 추가해 준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWKNPi/btsNLcxY7qz/PEaHshhikspRe6KhMTjGE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWKNPi/btsNLcxY7qz/PEaHshhikspRe6KhMTjGE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWKNPi/btsNLcxY7qz/PEaHshhikspRe6KhMTjGE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWKNPi%2FbtsNLcxY7qz%2FPEaHshhikspRe6KhMTjGE0%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;1236&quot; height=&quot;844&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;844&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 해당 bucket에 Entry 존재 여부는 bit로 먼저 판단한다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 bucket의 데이터가 비었는지, 가득 찼는지, Entry에 데이터가 있는지 없는지를 판단할 때&lt;br /&gt;7개 Entry를 직접 조회하지 않고 presence를 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1이면 존재, 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;앞에서 살펴본 Child bucket이 생성 됐는데 키가 삭제되면서 Child bucket이 비었거나&lt;br /&gt;Entry 중간에 비어있다면 정리해 줄 때도 presence가 잘 쓰인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 보는 함수가 있어 살펴보니 gcc 빌트인 함수라고 한다. 오.. 득템&lt;br /&gt;&lt;b&gt;__builtin_popcount&lt;/b&gt;: parameter 값을 이진수로 나타냈을 때 1이 몇 개인지 카운트해 주는 함수다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;__builtin_ctz(count trailing zeros)&lt;/b&gt;: parameter 값을 이진수로 나타냈을 때 LSB부터 왼쪽으로 이동하면서&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;0이 몇개인지 카운트해주는 함수다. 반대는 &lt;b&gt;__builtin_clz&lt;/b&gt;(MSB부터 오른쪽으로 이동)&lt;/p&gt;
&lt;pre id=&quot;code_1746359337212&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void compactBucketChain(hashtable *ht, size_t bucket_index, int table_index) {
    bucket *b = &amp;amp;ht-&amp;gt;tables[table_index][bucket_index];
    while (b-&amp;gt;chained) {
        bucket *next = getChildBucket(b);
        // child bucket도 chained 타입이고 비어있다면 child bucket 삭제(포인트 바꾸고 메모리 해제)
        if (next-&amp;gt;chained &amp;amp;&amp;amp; next-&amp;gt;presence == 0) {
            /* Empty bucket in the middle of the chain. Remove it from the chain. */
            bucket *next_next = getChildBucket(next);
            b-&amp;gt;entries[ENTRIES_PER_BUCKET - 1] = next_next;
            zfree(next);
            if (ht-&amp;gt;type-&amp;gt;trackMemUsage) ht-&amp;gt;type-&amp;gt;trackMemUsage(ht, -sizeof(bucket));
            ht-&amp;gt;child_buckets[table_index]--;
            continue;
        }

        // child bucket이 비어있고 chained 타입이 아니라면 삭제(prune)
        if (!next-&amp;gt;chained &amp;amp;&amp;amp; (next-&amp;gt;presence == 0 || __builtin_popcount(next-&amp;gt;presence) == 1)) {
            /* Next is the last bucket and it's empty or has only one entry.
             * Delete it and turn b into an &quot;unchained&quot; bucket. */
            pruneLastBucket(ht, b, next, table_index);
            return;
        }

        // 중간에 비어있는 Entry가 있다면 가장 마지막에 있는 child bucket의 Entry로 채움
        if (__builtin_popcount(b-&amp;gt;presence) &amp;lt; ENTRIES_PER_BUCKET - 1) {
            /* Fill the holes in the bucket. */
            for (int pos = 0; pos &amp;lt; ENTRIES_PER_BUCKET - 1; pos++) {
                if (!isPositionFilled(b, pos)) {
                    fillBucketHole(ht, b, pos, table_index);
                    if (!b-&amp;gt;chained) return;
                }
            }
        }

        /* Bucket is full. Move forward to next bucket. */
        b = next;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 입력한 key가 몇 번째 Entry인지 secondary hash로 판단 후 Entry key값을 비교한다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 저장 할 때 key 값에 hash 함수를 적용해 64 비트로 만든 후&lt;br /&gt;그중 상위 8비트를 highBit 함수로 구해서 hashes에 저장한다.&lt;/p&gt;
&lt;pre id=&quot;code_1746360448280&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# &amp;lt;limits.h&amp;gt; 64 bit 8
#define CHAR_BIT  __CHAR_BIT__

/* For the hash bits stored in the bucket, we use the highest bits of the hash
 * value, since these are not used for selecting the bucket. */
static inline uint8_t highBits(uint64_t hash) {
    return hash &amp;gt;&amp;gt; (CHAR_BIT * 7);
}

/* Helper to insert an entry. Doesn't check if an entry with a matching key
 * already exists. This must be ensured by the caller. */
static void insert(hashtable *ht, uint64_t hash, void *entry) {
    ...
    bucket *b = findBucketForInsert(ht, hash, &amp;amp;pos_in_bucket, &amp;amp;table_index);
    b-&amp;gt;entries[pos_in_bucket] = entry;
    b-&amp;gt;presence |= (1 &amp;lt;&amp;lt; pos_in_bucket);
    b-&amp;gt;hashes[pos_in_bucket] = highBits(hash);
    ht-&amp;gt;used[table_index]++;
}&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;그럼 나중에 해당 key를 조회하거나 삭제할 때 bucket에 있는 Entry key를 하나씩 꺼내고 비교하는 게 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hash값에 상위 8비트와 hashes에 있는 값을 비교하고(isPositionFilled(b, pos) &amp;amp;&amp;amp; b-&amp;gt;hashes[pos] == h2)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매칭되면 실제 key를 비교하는 식이다. (compareKeys)&lt;/p&gt;
&lt;pre id=&quot;code_1746361277938&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static bucket *findBucket(hashtable *ht, uint64_t hash, const void *key, int *pos_in_bucket, int *table_index) {
    if (hashtableSize(ht) == 0) return 0;
    uint8_t h2 = highBits(hash);
    int table;
    ...
    for (table = 0; table &amp;lt;= 1; table++) {
        ...
        do {
            /* Find candidate entries with presence flag set and matching h2 hash. */
            for (int pos = 0; pos &amp;lt; numBucketPositions(b); pos++) {
                if (isPositionFilled(b, pos) &amp;amp;&amp;amp; b-&amp;gt;hashes[pos] == h2) {
                    /* It's a candidate. */
                    void *entry = b-&amp;gt;entries[pos];
                    const void *elem_key = entryGetKey(ht, entry);
                    if (compareKeys(ht, key, elem_key) == 0) {
                        /* It's a match. */
                        assert(pos_in_bucket != NULL);
                        *pos_in_bucket = pos;
                        if (table_index) *table_index = table;
                        return b;
                    }
                }
            }
            b = getChildBucket(b);
        } while (b != NULL);
    }
    return NULL;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진짜 비트 하나하나 알차게 쓰네...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6. 메모리를 아끼기 위해 embed type이 있는데 항상 적용되는 건 아니다.&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8.1에서 메모리 접근 횟수 감소와 메모리 사용이 줄어든 이유는 serverObject에 포인터가 아닌 값을 직접 저장하고 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 Valkey 8.1은 항상 embed type으로 저장이 될까?&amp;nbsp; 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장할 때 value 길이가 44 이하일 때 Embed String 타입으로 저장하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;44 초과면 7.2에서 본것처럼 포인터로 저장한다.&lt;/p&gt;
&lt;pre id=&quot;code_1746361754131&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* Create a string object with EMBSTR encoding if it is smaller than
 * OBJ_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
 * used.
 *
 * The current limit of 44 is chosen so that the biggest string object
 * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
    if (len &amp;lt;= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr, len);
    else
        return createRawStringObject(ptr, len);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;serverObject에 key, value, expire 데이터가 embed 타입으로 어떻게 저장되는지 알아보자.&lt;/b&gt;&lt;/u&gt;&lt;br /&gt;※ object.c의 `static robj *createEmbeddedStringObjectWithKeyAndExpire`&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;serverObject 구조체는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type + encoding + lru + hasexpire + hasembkey + refcount = 64 bit&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;64 bit 다음에 void* ptr이 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHT575/btsNJ1YCQUs/Nn1y83iTIHhbMQFyLTSC61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHT575/btsNJ1YCQUs/Nn1y83iTIHhbMQFyLTSC61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHT575/btsNJ1YCQUs/Nn1y83iTIHhbMQFyLTSC61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHT575%2FbtsNJ1YCQUs%2FNn1y83iTIHhbMQFyLTSC61%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;682&quot; height=&quot;538&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;538&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;구조체에 bit 필드 선언하는 방법과 구조체에서 마지막 void 포인터는 구조체 크기에 영향을 안 준다는 구글 AI 검색 내용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크흐~~ 옛날에 고생하면서 찾았던 기억이 나면서 스샷 한번 ㅋㅋㅋ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cF55zm/btsNKz8moY0/YbCr97hdfubTbp3YkSWHh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cF55zm/btsNKz8moY0/YbCr97hdfubTbp3YkSWHh0/img.png&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;1614&quot; data-is-animation=&quot;false&quot; style=&quot;width: 53.1953%; margin-right: 10px;&quot; data-widthpercent=&quot;53.82&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cF55zm/btsNKz8moY0/YbCr97hdfubTbp3YkSWHh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcF55zm%2FbtsNKz8moY0%2FYbCr97hdfubTbp3YkSWHh0%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;1300&quot; height=&quot;1614&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byWqbY/btsNKdEtsqi/hakZnrEiBnKlLEdCLE2iY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byWqbY/btsNKdEtsqi/hakZnrEiBnKlLEdCLE2iY0/img.png&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;1884&quot; data-is-animation=&quot;false&quot; style=&quot;width: 45.6419%;&quot; data-widthpercent=&quot;46.18&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byWqbY/btsNKdEtsqi/hakZnrEiBnKlLEdCLE2iY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyWqbY%2FbtsNKdEtsqi%2FhakZnrEiBnKlLEdCLE2iY0%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;1302&quot; height=&quot;1884&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746363699108&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static robj *createEmbeddedStringObjectWithKeyAndExpire(const char *val_ptr,
                                                        size_t val_len,
                                                        const sds key,
                                                        long long expire) {
    // 64 bit + value size + expire size(optional) + key size(optional)
    size_t min_size = sizeof(robj) + val_sds_size;
    if (expire != -1) {
        min_size += sizeof(long long);
    }
    if (has_embkey) {
        /* Size of embedded key, incl. 1 byte for prefixed sds hdr size. */
        min_size += 1 + key_sds_size;
    }

    /* 64 bit 알차게 값 할당 */
    size_t bufsize = 0;
    robj *o = zmalloc_usable(min_size, &amp;amp;bufsize);
    o-&amp;gt;type = OBJ_STRING;
    o-&amp;gt;encoding = OBJ_ENCODING_EMBSTR;
    o-&amp;gt;refcount = 1;
    o-&amp;gt;lru = 0;
    o-&amp;gt;hasexpire = (expire != -1);
    o-&amp;gt;hasembkey = has_embkey;


    /* embedded 데이터 설정을 위해 void포인터 크기 만큼(8 byte) 이동 */
    char *data = (void *)(o + 1);

    /* Set the expire field. */
    if (o-&amp;gt;hasexpire) {
        *(long long *)data = expire;
        data += sizeof(long long);
    }

    /* Copy embedded key. */
    if (o-&amp;gt;hasembkey) {
        *data++ = sdsHdrSize(key_sds_type);
        sdswrite(data, key_sds_size, key_sds_type, key, key_sds_len);
        data += key_sds_size;
    }

    /* Copy embedded value (EMBSTR) always as SDS TYPE 8. Account for unused
     * memory in the SDS alloc field. */
    size_t remaining_size = bufsize - (data - (char *)(void *)o);
    o-&amp;gt;ptr = sdswrite(data, remaining_size, SDS_TYPE_8, val_ptr, val_len);

    return o;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 느낌이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3t2dk/btsNKqwTa5A/7RCrK8moKgOYHbKKc9QGpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3t2dk/btsNKqwTa5A/7RCrK8moKgOYHbKKc9QGpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3t2dk/btsNKqwTa5A/7RCrK8moKgOYHbKKc9QGpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3t2dk%2FbtsNKqwTa5A%2F7RCrK8moKgOYHbKKc9QGpk%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;738&quot; height=&quot;482&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;482&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 해시 테이블 코드 보면서 앞으로 Redis와 합쳐지기는 어렵다는 생각이 더 굳어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 아끼고 성능 개선하려고 만들어진 코드를 보며 재미도 느끼고 오... 이렇게 까지 한다고? 신기해하며 코드를 봤는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편으론 Redis보다 복잡해진 느낌도 받아서 좀 더 알아봐야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발하면서/코드보면서</category>
      <category>redis</category>
      <category>valkey</category>
      <author>오산돌구</author>
      <guid isPermaLink="true">https://dol9.tistory.com/336</guid>
      <comments>https://dol9.tistory.com/336#entry336comment</comments>
      <pubDate>Sun, 4 May 2025 22:23:27 +0900</pubDate>
    </item>
    <item>
      <title>Spring Framework 6.2 문서 읽고 이해하기 ... IOC Container (4)</title>
      <link>https://dol9.tistory.com/335</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Lazy 빈 인스턴스화&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 ApplicationContext는 초기화될 때 모든 빌은 eager 하게 생성하고 구성하도록 구현되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 eager 한 생성은 괜찮은 방법인데 왜냐하면 실행 후 몇 시간 또는 며칠 뒤가 아니라 즉시 발견할 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;eager 한 생성이 필요하지 않은 경우 lazy 하게 만들 수 있는데 ApplicationContext가 초기화될 때가 아니라&lt;br /&gt;요청할 때 빈을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lazy 빈을 설정하는 방법은 @Lazy annotation 또는 XML에 &amp;lt;bean /&amp;gt;에 lazy-init 속성으로 제어할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740885264757&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;lazy&quot; class=&quot;com.something.ExpensiveToCreateBean&quot; lazy-init=&quot;true&quot;/&amp;gt;

&amp;lt;bean name=&quot;notLazy&quot; class=&quot;com.something.AnotherBean&quot;/&amp;gt;&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;하지만 eager 한 빈이 lazy 한 빈의 의존관계가 있다면 ApplicationContext는 빈 생성을 위해 lazy 한 빈을 시작할 때 생성한다.&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;b&gt;메서드 주입&lt;/b&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;싱글톤인 빈은 다른 싱글톤 빈 또는 non-싱글톤 빈과 협업을 할 수 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글톤 빈이 non-싱글톤 빈과&amp;nbsp;협업하려고 할 때 서로 다른 빈의 생명주기로 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글톤 빈 A가 non-싱글톤 Bygw 사용한다고 가정했을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너는 A를 최초 한 번만 생성하므로 속성 설정은 딱 한 번만 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 A에게 매번 새로운 인스턴스인 B를 제공할 수도 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결책은 IoC를 포기하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ApplicationContextAware 인터페이스를 구현하고 getBean(&quot;B&quot;)를 호출하는 방식으로&lt;br /&gt;A가 필요할 때마다 매번 B 인스턴스를 컨테이너에 요청하는 방식이다.&lt;/p&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://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html#beans-factory-aware&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Customizing the Nature of a Bean&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://spring.io/blog/2004/08/06/method-injection&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Method Injection&lt;/a&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;b&gt;메서드 주입 검색&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 주입 검색은 컨테이너가 관리하는 빈에서 메서드를 오버라이드하고&amp;nbsp;&lt;br /&gt;다른 이름의 빈을 반환하는 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Framework는 CGLIB 라이브러리가 생성한 bytecode를 이용해서&lt;br /&gt;메서도 오버라이드한 하위 클래스를 동적으로 생성하는 방식으로 메서드 주입을 구현했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;동적 하위 클래스 생성방식을 사용하기 위해서는 class와 오버라이드한 메서드 모두 final이면 안된다.&lt;br /&gt;해당 클래스를 유닛테스트 하려면 abstract를 stub 해줘야 한다. (구현해줘야 한다.)&lt;br /&gt;마지막으로 메서드 주입 검색은 factory 메서드와 같이 동작하지 않으면&lt;br /&gt;특히 configuration 클래스의 @Bean 메서드와 함께 동작하지 않는다.&lt;br /&gt;왜냐하면 컨테이너가 인스턴스 생성을(앞에서 말한 B) 담당하지 않아, 하위 클래스를 만들 수 없기 때문이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740966785178&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

	public Object process(Object commandState) {
		// grab a new instance of the appropriate Command interface
		Command command = createCommand();
		// set the state on the (hopefully brand new) Command instance
		command.setState(commandState);
		return command.execute();
	}

	// okay... but where is the implementation of this method?
	protected abstract Command createCommand();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740966799081&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- a stateful bean deployed as a prototype (non-singleton) --&amp;gt;
&amp;lt;bean id=&quot;myCommand&quot; class=&quot;fiona.apple.AsyncCommand&quot; scope=&quot;prototype&quot;&amp;gt;
	&amp;lt;!-- inject dependencies here as required --&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;!-- commandManager uses myCommand prototype bean --&amp;gt;
&amp;lt;bean id=&quot;commandManager&quot; class=&quot;fiona.apple.CommandManager&quot;&amp;gt;
	&amp;lt;lookup-method name=&quot;createCommand&quot; bean=&quot;myCommand&quot;/&amp;gt;
&amp;lt;/bean&amp;gt;&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;또는 abstract 메서드에 @Lookup annotation을 이용하는 방법도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 Scope&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 정의를 만들 때 XML이나 Annotation 등의 방법으로 실제 인스턴스를 만드는 레시피를 적는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 레시피라는 개념은 중요한데 Java class처럼 하나의 레시피로 많은 인스턴스를 만든다는 걸 의미하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 의존성이나 설정값을 제어하는 것 외에도 빈 정의의 Scope(범위)도 제어할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Scope은 굉장히 강력하고 유연한데 Java class에서 Scope을 선택하는 게 아니라 설정으로 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈은 여러 Scope 중 하나를 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Framework는 총 6개의 Scope을 지원하는데 그중 4개는 web-aware ApplicationContext을 사용할 때만 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6개 외에도 사용자가 직접 자신만의 Scope을 만들 수도 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style9&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.976744%;&quot;&gt;Scope&lt;/td&gt;
&lt;td style=&quot;width: 78.023256%;&quot;&gt;Description&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.976744%;&quot;&gt;singleton&lt;/td&gt;
&lt;td style=&quot;width: 78.023256%;&quot;&gt;(Default) Spring IoC 컨테이너마다 한개의 빈 정의는 한개의 인스턴스로 Scope을 지정한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.976744%;&quot;&gt;prototype&lt;/td&gt;
&lt;td style=&quot;width: 78.023256%;&quot;&gt;한개의 빈 정의를 임의 개수의 인스턴스로 Scope을 지정한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.976744%;&quot;&gt;request&lt;/td&gt;
&lt;td style=&quot;width: 78.023256%;&quot;&gt;한개의 빈 정의를 한개의 HTTP 요청 라이프 사이클에 적용한다.&lt;br /&gt;각 HTTP 요청은 하나의 빈 정의에서 생성된 인스턴스를 가지고 있다.&lt;br /&gt;(web-are ApplicationContext)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.976744%;&quot;&gt;session&lt;/td&gt;
&lt;td style=&quot;width: 78.023256%;&quot;&gt;한개의 빈 정의를 HTTP 세션 라이프사이클에 적용한다.&lt;br /&gt;(web-are ApplicationContext)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.976744%;&quot;&gt;application&lt;/td&gt;
&lt;td style=&quot;width: 78.023256%;&quot;&gt;한개의 빈 정의를 ServletContext 라이프사이클에 적용한다.&lt;br /&gt;(web-are ApplicationContext)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.976744%;&quot;&gt;websocket&lt;/td&gt;
&lt;td style=&quot;width: 78.023256%;&quot;&gt;한개의 빈 정의를 WebSocket 라이프사이클에 적용한다.&lt;br /&gt;(web-are ApplicationContext)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Singleton Scope&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;singleton 빈의 인스턴스는 단 하나만 관리되며, 빈 정의에 있는 ID와 동일한 모든 요청은 특정 빈의 인스턴스를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 인스턴스는 singleton 빈 캐시에 저장되고, 해당 빈 이름의 요청이 있을 때마다 캐시에 있는 객체를 반환한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0hstm/btsMzQpYUrj/keGI73fbYjxd1ddyXKKnFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0hstm/btsMzQpYUrj/keGI73fbYjxd1ddyXKKnFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0hstm/btsMzQpYUrj/keGI73fbYjxd1ddyXKKnFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0hstm%2FbtsMzQpYUrj%2FkeGI73fbYjxd1ddyXKKnFk%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;800&quot; height=&quot;398&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring의 singleton 빈 개념은 GoF책에 나온 singleton 패턴과는 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GoF의 singleton은 Java Class를 하드코딩해서 Scope을 지정하고 &lt;br /&gt;ClassLoader당 하나의 인스턴스만 생성되고 관리되도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Spring의 singleton은 컨테이너와 빈 단위로 잘 설명이 된다.&lt;br /&gt;하나의 Spring 컨테이너에서 특정 클래스의 하나의 빈을 정의했다면 Spring 컨테이너는 빈 정의에 의해 하나의 인스턴스만 생성한다.&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;b&gt;Prototype Scope&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;non-singleton인 prototype은 해당빈에 요청이 올 때마다 빈 정의에 따라 새로운 인스턴스를 만들어 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원칙적으로 stateful 빈은 prototype으로 만들고 stateless 빈은 singleton으로 만든다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UHul5/btsMyVkZzgj/9v236XKP5PtoRVd95u4Btk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UHul5/btsMyVkZzgj/9v236XKP5PtoRVd95u4Btk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UHul5/btsMyVkZzgj/9v236XKP5PtoRVd95u4Btk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUHul5%2FbtsMyVkZzgj%2F9v236XKP5PtoRVd95u4Btk%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;800&quot; height=&quot;397&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 Scope과는 다르게 prototype은 Spring이 온전하게 lifecycle을 관리하지 않는다.&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;초기화 lifecycle 콜백 메서드는 모든 Scope의 객체들이 호출되지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해제 lifecyle 콜백 메서드의 경우 prototype Scope의 객체는 호출되지 않아서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 코드에서 직접 객체를 정리하고 리소스를 해제해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 해제가 필요한 bean들을 참조하는 custom 빈을 만드는 방법도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html#beans-factory-lifecycle&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Customizing the Nature of a Bean&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발하면서/타인글보면서</category>
      <author>오산돌구</author>
      <guid isPermaLink="true">https://dol9.tistory.com/335</guid>
      <comments>https://dol9.tistory.com/335#entry335comment</comments>
      <pubDate>Wed, 5 Mar 2025 08:05:42 +0900</pubDate>
    </item>
    <item>
      <title>Spring Framework 6.2 문서 읽고 이해하기 ... IOC Container (3)</title>
      <link>https://dol9.tistory.com/334</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;엔터프라이즈 급 어플리케이션은 대부분 객체 하나만 갖고 있지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어 간단한 어플레이션도 사용자에게 일관된 기능을 제공하기 위해 서로 협력하는 객체들로 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서는 독립적으로 동작하는 빈을 얘기했는데,&lt;br /&gt;지금부터는 어플리케이션의 목표를 온전히 달성하기 위해 객체끼리 협력하는 방법을 설명한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;spring-injection.webp&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ptIuH/btsMzt2oQW7/s8b7wZUvMmZDDVvKcyqd0K/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ptIuH/btsMzt2oQW7/s8b7wZUvMmZDDVvKcyqd0K/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ptIuH/btsMzt2oQW7/s8b7wZUvMmZDDVvKcyqd0K/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FptIuH%2FbtsMzt2oQW7%2Fs8b7wZUvMmZDDVvKcyqd0K%2Fimg.webp&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;600&quot; height=&quot;600&quot; data-filename=&quot;spring-injection.webp&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;의존성 주입&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 주입(Dependency injection, DI)은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 주입, factory 메서드로 인자 또는 setter 메서드로 property 설정 방식으로 &lt;i&gt;&lt;b&gt;의존성을 정의하는 프로세스&lt;/b&gt;&lt;/i&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;이 과정들은 기본적으로 생성자나 서비스 로케이터 패턴을 이용해서 의존성의 위치나 인스턴스화를 스스로 제어한다. (IoC)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DI 원칙 덕분에 코드는 좀더 깔끔해지고, 디커플링은 의존관계를 더 효과적으로 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈으로 생성될 객체는 종속성이 무엇인지, 위치나 타입 등을 몰라도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과, 테스트가 쉬운 클래스가 되는데, 특히 interface나 abstract class로 의존관계를 정의했다면&lt;br /&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;DI는 크게 두가지 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 기반 의존정 주입(DI)과 Setter 메서드를 이용한 의존성 주입이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;생성자 기반 의존성 주입&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 기반 의존성 주입은 컨테이너가 의존관계인 argument와 함께 생성자를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static factory 메소드 호출하는 방식도 생성자 방식과 거의 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 컨테이너의 인터페이스나 기본 클래스, annotation을 사용하지 않는 POJO의 생성자 기반 의존성 주입 예시다.&lt;/p&gt;
&lt;pre id=&quot;code_1740303200469&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SimpleMovieLister {

	// the SimpleMovieLister has a dependency on a MovieFinder
	private final MovieFinder movieFinder;

	// a constructor so that the Spring container can inject a MovieFinder
	public SimpleMovieLister(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	// business logic that actually uses the injected MovieFinder is omitted...
}&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;생성자의 argument 매칭은 타입을 이용하여 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 타입의 빈이 여러개가 있어 모호한 argument가 정의되어 있지 않다면 정의된 순서대로 빈을 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1740303431741&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package x.y;

public class ThingOne {

	public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
		// ...
	}
}&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;ThingTwo, ThingThree가 상속하지 않아 모호하지 않는다고 가정해 보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 설정만으로 훌륭하게 동작한다!! &amp;lt;constructor-arg /&amp;gt; element에 index나 타입 정의를 신경 쓰지 않아도 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1740303691785&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;beans&amp;gt;
	&amp;lt;bean id=&quot;beanOne&quot; class=&quot;x.y.ThingOne&quot;&amp;gt;
		&amp;lt;constructor-arg ref=&quot;beanTwo&quot;/&amp;gt;
		&amp;lt;constructor-arg ref=&quot;beanThree&quot;/&amp;gt;
	&amp;lt;/bean&amp;gt;

	&amp;lt;bean id=&quot;beanTwo&quot; class=&quot;x.y.ThingTwo&quot;/&amp;gt;

	&amp;lt;bean id=&quot;beanThree&quot; class=&quot;x.y.ThingThree&quot;/&amp;gt;
&amp;lt;/beans&amp;gt;&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;또 다른 예로 아래와 같이 Bean이 아닌 int, String 같이 자료형을 받을 경우 인덱스 번호나(0부터 시작) type으로 값을 주입할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740792591193&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package examples;

public class ExampleBean {

	// Number of years to calculate the Ultimate Answer
	private final int years;

	// The Answer to Life, the Universe, and Everything
	private final String ultimateAnswer;

	public ExampleBean(int years, String ultimateAnswer) {
		this.years = years;
		this.ultimateAnswer = ultimateAnswer;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1740792889062&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;exampleBean&quot; class=&quot;examples.ExampleBean&quot;&amp;gt;
	&amp;lt;constructor-arg type=&quot;int&quot; value=&quot;7500000&quot;/&amp;gt;
	&amp;lt;constructor-arg type=&quot;java.lang.String&quot; value=&quot;42&quot;/&amp;gt;
&amp;lt;/bean&amp;gt;

또는

&amp;lt;bean id=&quot;exampleBean&quot; class=&quot;examples.ExampleBean&quot;&amp;gt;
	&amp;lt;constructor-arg index=&quot;0&quot; value=&quot;7500000&quot;/&amp;gt;
	&amp;lt;constructor-arg index=&quot;1&quot; value=&quot;42&quot;/&amp;gt;
&amp;lt;/bean&amp;gt;&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;Parameter 이름으로도 값 세팅할 수 있는데 아래처럼 생성자에 @ConstructorProperties를 사용하거나&lt;br /&gt;annotation을 쓰지 않으려면 컴파일할 때 -parameter flag를 추가하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XML의 property 값은 String으로 정의하고 conversion service를 이용해서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;실제 타입으로 변환 후 값을 할당한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/validation/convert.html#core-convert-ConversionService-API&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Spring Type Conversion&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1740796590358&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package examples;

public class ExampleBean {

	// Fields omitted

	@ConstructorProperties({&quot;years&quot;, &quot;ultimateAnswer&quot;})
	public ExampleBean(int years, String ultimateAnswer) {
		this.years = years;
		this.ultimateAnswer = ultimateAnswer;
	}
}


&amp;lt;bean id=&quot;exampleBean&quot; class=&quot;examples.ExampleBean&quot;&amp;gt;
	&amp;lt;constructor-arg name=&quot;years&quot; value=&quot;7500000&quot;/&amp;gt;
	&amp;lt;constructor-arg name=&quot;ultimateAnswer&quot; value=&quot;42&quot;/&amp;gt;
&amp;lt;/bean&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Setter 메서드 의존성 주입&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Setter 기반 DI는 컨테이너가 기본 생성자 또는 parameter가 없는 factory 메서드를 실행해서 빈을 초기화 후 setter 메소드를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 별도의 인터페이스, 기본 클래스, 어노테이션과 의존성이 없는 POJO를 Setter 메소드 의존성 주입 예시다.&lt;/p&gt;
&lt;pre id=&quot;code_1740875320579&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SimpleMovieLister {

	// the SimpleMovieLister has a dependency on the MovieFinder
	private MovieFinder movieFinder;

	// a setter method so that the Spring container can inject a MovieFinder
	public void setMovieFinder(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	// business logic that actually uses the injected MovieFinder is omitted...
}&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;PropertyEditor 인스턴스와 함께 BeanDefinition 형식으로 의존성을 정의하는 방법도 있지만 잘 사용하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XML bean 설정 또는 @Component, @Controller 같은 annotation 설정,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 @Configuration 클래스의 @Bean 메소드를 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에 3가지 방법은 내부적으로 BeanDefinition 인스턴스로 변환되고, 전체 IoC 컨테이너 인스턴스 로드하는 데 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;생성자 기반 vs Setter 메서드 기반&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;Spring 팀은 생성자 기반 방식을 권장한다.&lt;br /&gt;1. 컴포넌트를 불변 객체로 구현할 수 있고,&lt;br /&gt;2. 생성자 기반 컴포넌트는 항상 완전하게 초기화된 인스턴스를 리턴한다. (new 한 다음에 별도 설정이 필요 없다)&lt;br /&gt;3. 추가로, 생성자 argument 개수가 많다면 책임이 많다고 볼 수도 있어 관심사를 적절하게 분리할 타이밍임을 알아챌 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;종속성 해결 프로세스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. ApplicaitonContext가 생성되고 모든 bean 정의가 있는 메타데이터 설정을 초기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 메타데이터 설정은 XML, Java, annotation으로 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 각각의 빈의 의존성은 property, 생성자 파라미터, static-factory 메서드 파라미터에 표현되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. property와 생성자 파라미터는 실제 값이거나 컨테이너에 있는 다른 빈 참조다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. property나 생성자 파라미터에 지정한 형식에서 실제 유형으로 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 기본적으로 Spring은 문자열로 지정된 값을 int, long, String, boolean 등 모든 기본 실제 유형으로 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Container는 컨테이너가 생성될 때 모든 빈 설정이 유효한지 검사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 bean의 property는 실제 생성되기 전까지 할당되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너가 생성될 때 빈은 singleton-scoped(default)으로 설정되고 미리 인스턴스화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 경우는, 요청이 있을 때 빈을 생성하는 방법도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html#beans-factory-lifecycle-initializingbean&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Customizing the Nature of a Bean&lt;/a&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;b&gt;depdends-on 사용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 Bean이 다른 Bean에 의존성이 있는 경우 XML의 &amp;lt;ref&amp;gt;나 autowiring을 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 데이터베이스 드라이버 초기화처럼 의존관계가 덜한 관계도 있다. ※ 실제 사용하지는 않지만 초기화는 필요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때는 @DependesOn 어노테이션을 이용해서 강제로 의존관계의 Bean을 초기화할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740878410325&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;beanOne&quot; class=&quot;ExampleBean&quot; depends-on=&quot;manager&quot;/&amp;gt;
&amp;lt;bean id=&quot;manager&quot; class=&quot;ManagerBean&quot; /&amp;gt;



&amp;lt;!-- multiple depends-on  --&amp;gt;
&amp;lt;bean id=&quot;beanOne&quot; class=&quot;ExampleBean&quot; depends-on=&quot;manager,accountDao&quot;&amp;gt;
	&amp;lt;property name=&quot;manager&quot; ref=&quot;manager&quot; /&amp;gt;
&amp;lt;/bean&amp;gt;

&amp;lt;bean id=&quot;manager&quot; class=&quot;ManagerBean&quot; /&amp;gt;
&amp;lt;bean id=&quot;accountDao&quot; class=&quot;x.y.jdbc.JdbcAccountDao&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발하면서/타인글보면서</category>
      <author>오산돌구</author>
      <guid isPermaLink="true">https://dol9.tistory.com/334</guid>
      <comments>https://dol9.tistory.com/334#entry334comment</comments>
      <pubDate>Sun, 2 Mar 2025 11:00:47 +0900</pubDate>
    </item>
    <item>
      <title>Spring Framework 6.2 문서 읽고 이해하기 ... IOC Container (2)</title>
      <link>https://dol9.tistory.com/333</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-properties-detailed.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-properties-detailed.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IoC Container 찍먹 해본 지난 글에 이어서 이번에는 Bean을 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dol9.tistory.com/332&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dol9.tistory.com/332&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1739109410533&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring Framework 6.2 문서 읽고 이해하기 ... IOC Container (1)&quot; data-og-description=&quot;현재 정산 백엔드 직군으로 일하고 있고 사용하는 기술은 Spring Boot와 Kotlin이다.&amp;nbsp;(사람마다 깊이에 대한 기준은 다르지만) Spring을 깊은 이해 없이 활용 가능한 정도로 공부한 후 사용하다가 문제&quot; data-og-host=&quot;dol9.tistory.com&quot; data-og-source-url=&quot;https://dol9.tistory.com/332&quot; data-og-url=&quot;https://dol9.tistory.com/332&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/e8o2L/hyYb86N9oZ/96k9PIAfZR5wI3dnEywtr0/img.png?width=800&amp;amp;height=334&amp;amp;face=0_0_800_334,https://scrap.kakaocdn.net/dn/baMJPb/hyYclkKXKf/xdG7vwFkTuTwdtcMn3g0j1/img.png?width=800&amp;amp;height=334&amp;amp;face=0_0_800_334,https://scrap.kakaocdn.net/dn/bpMCqH/hyYb6nBau7/kGQXZz39vWStlx9wQ2p8T0/img.png?width=2054&amp;amp;height=1664&amp;amp;face=0_0_2054_1664&quot;&gt;&lt;a href=&quot;https://dol9.tistory.com/332&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dol9.tistory.com/332&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/e8o2L/hyYb86N9oZ/96k9PIAfZR5wI3dnEywtr0/img.png?width=800&amp;amp;height=334&amp;amp;face=0_0_800_334,https://scrap.kakaocdn.net/dn/baMJPb/hyYclkKXKf/xdG7vwFkTuTwdtcMn3g0j1/img.png?width=800&amp;amp;height=334&amp;amp;face=0_0_800_334,https://scrap.kakaocdn.net/dn/bpMCqH/hyYb6nBau7/kGQXZz39vWStlx9wQ2p8T0/img.png?width=2054&amp;amp;height=1664&amp;amp;face=0_0_2054_1664');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Framework 6.2 문서 읽고 이해하기 ... IOC Container (1)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;현재 정산 백엔드 직군으로 일하고 있고 사용하는 기술은 Spring Boot와 Kotlin이다.&amp;nbsp;(사람마다 깊이에 대한 기준은 다르지만) Spring을 깊은 이해 없이 활용 가능한 정도로 공부한 후 사용하다가 문제&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dol9.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oPHcL/btsMuhG7GDl/5UZrdOmHiurYDjec0nXJbk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oPHcL/btsMuhG7GDl/5UZrdOmHiurYDjec0nXJbk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oPHcL/btsMuhG7GDl/5UZrdOmHiurYDjec0nXJbk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoPHcL%2FbtsMuhG7GDl%2F5UZrdOmHiurYDjec0nXJbk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;224&quot; height=&quot;224&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IoC Container는 한개 이상의 빈을 관리하는데, 이 빈들은 XML &amp;lt;bean /&amp;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;빈 정의는 아래 정보를 BeanDefinition 객체에 담아놓는다.&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;&lt;b&gt;빈의 행위&lt;/b&gt;: 컨테이너에서 빈이 어떻게 동작하는지에 대한 상태 정보 (Scope, 라이프사이클 콜백 등등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빈이 실행되기 위해 참조하는 다른 빈&lt;/b&gt;: 참조하는 다른 빈을 collaborators 또는 depdencies라고 부른다&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;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 52.790697%; height: 180px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style5&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.11811%; height: 18px;&quot;&gt;&lt;b&gt;Property&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.215162%; height: 18px;&quot;&gt;&lt;b&gt;Explained in ...&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.11811%; height: 18px;&quot;&gt;Class&lt;/td&gt;
&lt;td style=&quot;width: 20.215162%; height: 18px;&quot;&gt;빈 인스턴스화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.11811%; height: 18px;&quot;&gt;Name&lt;/td&gt;
&lt;td style=&quot;width: 20.215162%; height: 18px;&quot;&gt;빈 이름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.11811%; height: 18px;&quot;&gt;Scope&lt;/td&gt;
&lt;td style=&quot;width: 20.215162%; height: 18px;&quot;&gt;빈 Scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.11811%; height: 18px;&quot;&gt;Constructor arguments&lt;/td&gt;
&lt;td style=&quot;width: 20.215162%; height: 18px;&quot;&gt;의존성 주입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.11811%; height: 18px;&quot;&gt;Properties&lt;/td&gt;
&lt;td style=&quot;width: 20.215162%; height: 18px;&quot;&gt;의존성 주입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.11811%; height: 18px;&quot;&gt;Autowiring mode&lt;/td&gt;
&lt;td style=&quot;width: 20.215162%; height: 18px;&quot;&gt;Autowiring 협력자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.11811%; height: 18px;&quot;&gt;Lazy initialization mode&lt;/td&gt;
&lt;td style=&quot;width: 20.215162%; height: 18px;&quot;&gt;게으른 빈 인스턴스화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.11811%; height: 18px;&quot;&gt;Initialization method&lt;/td&gt;
&lt;td style=&quot;width: 20.215162%; height: 18px;&quot;&gt;초기화 콜백&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.11811%; height: 18px;&quot;&gt;Destruction method&lt;/td&gt;
&lt;td style=&quot;width: 20.215162%; height: 18px;&quot;&gt;소멸자 콜백&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ApplicationContext에는 빈 생성에 필요한 빈 정의 말고도, registerSingleton(..), registerBeanDefinition(..)을 이용해서&lt;br /&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;빈 이름&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 빈은 한 개 이상의 식별자를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대개 한개의 식별자만 사용하고 식별자가 더 필요한 경우 별칭을 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XML로 메타데이터를 설정한 경우 id 또는 name 속성을 이용해 식별자를 지정할 수 있다.&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;별칭을 만들고 싶다면 name속성에 콤마(,), 세미콜론(;), 공백( )을 구분자로 여러 개를 입력하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 id 속성은 xsd:string 타입으로 정의됐지만 고유성은 IoC 컨테이너에서만 강제되고, XML parser에서는 강제되지 않는다.&lt;br /&gt;(XML 쓰는 중에는 중복인지 아닌지 알 수 없다는 뜻)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name이나 id가 필수는 아닌데, 둘다 명시하지 않는 경우&amp;nbsp;자동으로 고유한 이름을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 빈 이름으로 참조하거나 XML에서 ref 속성 또는 서비스 로케이터 패턴을 사용한다면 반드시 이름을 지정해야 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;빈 이름 규칙&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;자바에서 멤버변수의 이름 규칙과 동일하게 소문자로 시작하고 camelCase 형식을 쓴다.&lt;br /&gt;이름이 없는 경우 java.beans.Introspector.decapitalize 를 이용해서 자동으로 생성한다.&lt;br /&gt;https://docs.oracle.com/javase/8/docs/api/java/beans/Introspector.html#decapitalize-java.lang.String-&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;빈 정의 밖에서 빈 별칭 정의하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 id 하나, name 여러개 조합으로 여러 이름을 정의할 수 있다고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 alias 속성을 이용해서 별칭만 정의할 수 있다.&lt;br /&gt;별칭을 이용하면 어플리케이션의 각 컴포넌트가 컴포넌트에 특화된 이름으로 참조하는 상황에 유용하게 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 동일한 빈에 대해서 서로 다른 시스템이 사용한다고 할 때 별칭을 사용하면 네임스페이스 느낌으로 관리가 된다.&lt;br /&gt;※ myApp-dataSource, subsystemA-dataSource, subsystemB-dataSource 모두 같은 빈이다.&lt;/p&gt;
&lt;pre id=&quot;code_1739193756918&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;alias name=&quot;fromName&quot; alias=&quot;toName&quot;/&amp;gt;

&amp;lt;alias name=&quot;myApp-dataSource&quot; alias=&quot;subsystemA-dataSource&quot;/&amp;gt;
&amp;lt;alias name=&quot;myApp-dataSource&quot; alias=&quot;subsystemB-dataSource&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;Java-configuration&lt;/b&gt;&lt;/i&gt;&lt;br /&gt;XML이 아니라 Java configuration을 사용하고 있다면 @Bean annotation으로 별칭을 설정할 수 있다.&lt;br /&gt;자세한 내용은 &lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/java/bean-annotation.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Using the @Bean Annotation&lt;/a&gt; 을 보자&lt;/blockquote&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;b&gt;빈 인스턴스화&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈 정의(Bean definition)는 하나 이상의 객체를 생성하는 레시피다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너는 생성 요청을 받으면 빈 정의 레시피를 살펴보고, 빈 정의로 캡슐화된 메타데이터 설정을 사용하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 생성 또는 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XML 메타데이터 설정을 사용한다면 &amp;lt;bean /&amp;gt;의 class 속성을 사용하여 인스턴스화시킬 클래스를 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class 속성은 일반적으로 필수 값인데 예외 상황이 있다. ex: Factory Method 또는 Bean 상속&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로 자바 코드의 new 연산자 호출하듯이 container에서 생성자를 직접 호출해서 빈을 생성한다.&lt;/li&gt;
&lt;li&gt;컨테니어가 static 팩토리 메소드를 호출해서 빈 생성하는 방법은 덜 일반적이다.&lt;br /&gt;static 팩토리 메소드 호출 결과가 동일한 클래스일 수도 있고 다른 클래스일 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;생성자를 이용한 인스턴스화&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring IoC 컨테이너는 어떠한 클래스라도 관리할 수 있다. 다수의 Spring 유저는 기본 생성자를 이용해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bean을 만들고 setter, getter로 프로퍼티 채우는 방식을 선호한다. (예전에 써서 그런가? 이젠 생성자 주입을 선호)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XML 메타데이터 설정을 쓴다면 아래와 같이 빈을 정의할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1740284201103&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;exampleBean&quot; class=&quot;examples.ExampleBean&quot;/&amp;gt;

&amp;lt;bean name=&quot;anotherExample&quot; class=&quot;examples.ExampleBeanTwo&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 주입을 좀 더 알고 싶다면 &lt;a href=&quot;https://dol9.tistory.com/334&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Dependency Injection&lt;/a&gt;을 참고하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Static Factory 메소드를 이용한 인스턴스화&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static factory 메소드로 빈을 정의하기 위해서는 clsss 속성에 정의한 클래스에 static factory method 이름이&lt;br /&gt;factory-method로 추가해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1740284315638&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;bean id=&quot;clientService&quot; class=&quot;examples.ClientService&quot; 
		factory-method=&quot;createInstance&quot;/&amp;gt;&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;앞에서 정의한 Bean Definition을 동작하기 위한 클래스 예시는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1740284344271&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ClientService {
	private static ClientService clientService = new ClientService();
	private ClientService() {}

	public static ClientService createInstance() {
		return clientService;
	}
}&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;한 클래스가 여러 static factory 메서드를 가지고 여러 타입을 리턴할 수도 있다. (개인적으로 있다 정도만 알아두기로 했다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보다 자세한 static factory 메소드를 알고 싶다면 &lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-properties-detailed.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Dependencies and Configuration in Detail&lt;/a&gt;을 참고하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발하면서/타인글보면서</category>
      <category>Spring Framework</category>
      <author>오산돌구</author>
      <guid isPermaLink="true">https://dol9.tistory.com/333</guid>
      <comments>https://dol9.tistory.com/333#entry333comment</comments>
      <pubDate>Sun, 23 Feb 2025 13:28:20 +0900</pubDate>
    </item>
    <item>
      <title>Spring Framework 6.2 문서 읽고 이해하기 ... IOC Container (1)</title>
      <link>https://dol9.tistory.com/332</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;현재 정산 백엔드 직군으로 일하고 있고 사용하는 기술은 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Spring Boot와 Kotlin이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;(사람마다 깊이에 대한 기준은 다르지만) Spring을 깊은 이해 없이 &lt;b&gt;활용&lt;/b&gt; 가능한 정도로 공부한 후&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; 사용하다가 &lt;/span&gt;&lt;span&gt;문제 생기면 그때 필요한 부분만 알아가는 식이였다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;IOC Container라는 게 있어서 POJO + Meta정보를 이용해서 Bean을 생성하고 lifecycle도 관리해 주고,&lt;br /&gt;Reflection을 이용해서 DI 해준다 정도... 아는 것도 모르는 것도 아닌 두리뭉실한 느낌&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이를 반대로 말하면 문제없는 영역은 잘 모른다는 게 문제다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot가 알아서 해주는 게 많아 개발자는 비즈니스 로직에만 집중한다는 장점이 있지만&lt;br /&gt;프로젝트가 커지다 보면 비즈니스 로직만 알아서는 부족함을 느낀다. 개발 속도가 느려진다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금 상황이 그렇다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드를 봐야겠다고 결심하게 된 결정적인 계기는&lt;br /&gt;지금 프로젝트를 리팩토링 하려고 테스트 코드 작성하는데 감으로만 알고 있던 걸로는 진행이 안 됐다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;더 이상 구글 검색결과에 나온 방법들로는 해결되지 않았다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;내가 생각한 개발자의 가장 큰 장점은 공개되어 있는 코드들이&amp;nbsp;많아&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;시간과 노력을 투자한다면 내부 동작 원리를 알 수 있다는 점이다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;그래서 당장 업무에 도움 될지를 상상하며 내부 동작을 알아보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;첫 시작은&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/reference/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Spring framework 6.2!!&lt;/span&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;나의 지적호기심 채우려고 너무 깊게 알아가는 모습을 항상 경계하자.&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Core Technologies / The IoC Container&amp;nbsp;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IOC&lt;/b&gt;는 Inversion of Control의 약자로 제어를 내부가 아닌 외부에서 한다는 개념이고&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DI&lt;/b&gt;는 생성자, property, factory method, setter method 등을 이용하여 의존 관계에 있는 객체를 주입받는 걸 말한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;IoC 컨테이너는 두 개 패키지가 기본이고 `org.springframeowrk.beans`, `&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;org.springframeowrk.context`&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;beans 패키지는 BeanFactory interface를, context 패키지는 ApplicationContext interface를 root로 보면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;BeanFactory는 모든 유형의 객체를 관리할 수 있는 메커니즘을 제공하고,&lt;br /&gt;ApplicationContext는 BeanFactory의 하위 interface로 아래와 같은 추가 기능을 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring AOP 기능과 쉬운 연동&lt;/li&gt;
&lt;li&gt;i18n 관리&lt;/li&gt;
&lt;li&gt;이벤트 메시지 관리&lt;/li&gt;
&lt;li&gt;웹에서 사용하기 위한 WebApplicationContext 같은 계층별 Context&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-02-02 at 11.16.13 AM.png&quot; data-origin-width=&quot;2408&quot; data-origin-height=&quot;1006&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dN7NXT/btsMbAnC8Ic/NtxuvSC2n0PcNWh5mC86h0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dN7NXT/btsMbAnC8Ic/NtxuvSC2n0PcNWh5mC86h0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dN7NXT/btsMbAnC8Ic/NtxuvSC2n0PcNWh5mC86h0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdN7NXT%2FbtsMbAnC8Ic%2FNtxuvSC2n0PcNWh5mC86h0%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;2408&quot; height=&quot;1006&quot; data-filename=&quot;Screenshot 2025-02-02 at 11.16.13 AM.png&quot; data-origin-width=&quot;2408&quot; data-origin-height=&quot;1006&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;BeanFactory는 프레임워크 설정과 기본 기능을 제공하고, ApplicationContext는 추가로 엔터프라이즈 기능을 제공한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이후 Spring IoC Container 설명 ApplicationContext으로 진행한다.&lt;br /&gt;※ BeanFactory에 대한 자세한 내용은 &lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/beanfactory.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;The BeanFactory API&lt;/a&gt; 읽을 때 알아보겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Container Overview&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring IoC Container = ApplicationContext interface 느낌.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bean의 인스턴스화, 설정, 조합등을 책임지는데 메타정보를 읽어 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 ApplicationContext interface 구현체가 있는데 그중에&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;AnnotationConfigApplicationContext, ClassPathXmlApplicationContext&lt;/i&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;비즈니스 로직이 있는 객체(POJO)와 메타정보를 가져와서 ApplicationContext에서 즉시 사용가능한 Bean 만들어준다.&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-origin-width=&quot;498&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cz3xRS/btsMbWjBMk8/RsGjDtxPkuOmBlZaTAAMW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cz3xRS/btsMbWjBMk8/RsGjDtxPkuOmBlZaTAAMW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cz3xRS/btsMbWjBMk8/RsGjDtxPkuOmBlZaTAAMW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcz3xRS%2FbtsMbWjBMk8%2FRsGjDtxPkuOmBlZaTAAMW0%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;498&quot; height=&quot;296&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;296&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;h3 data-ke-size=&quot;size23&quot;&gt;Configuration Metadata&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메타정보는 다양한 방식으로 정의할 수 있는데 대부분의 개발자들은 주로 Java-based configuration 방식을 사용한다.&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;Annotation-based configuration&lt;/b&gt;: 애플리케이션 컴포넌트 클래스에 annotation을 사용해서 bean을 정의하는 방식&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Java-based configuration&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;: 애플리케이션과 별개의 클래스에 bean을 정의하는 방식&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XML 방식도 있는데 XML에 &amp;lt;beans/&amp;gt;를 적고 하위에 &amp;lt;bean/&amp;gt;을 적어서 bean을 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ &amp;lt;import /&amp;gt; element를 이용해서 다른 xml파일에 정의한 bean 내용을 가져올 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1739078854564&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd&quot;&amp;gt;

    &amp;lt;!-- services --&amp;gt;
    &amp;lt;bean id=&quot;petStore&quot; class=&quot;com.tistory.dol9.context.support.PetStore&quot;/&amp;gt;
&amp;lt;/beans&amp;gt;&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;ClassPathXmlApplicationContext 생성자 argument로 Java classpath 기준 xml을 넘겨주면 IoC container가 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XML 경로는 상대경로 대신 절대경로 사용을 권장한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 GenericApplicationContext와 reader delegate를 이용해서 container를 생성할 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1739078951039&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// create and configure beans using ClassPathXmlApplicationContext
val context: ApplicationContext = ClassPathXmlApplicationContext(SINGLE_CONTEXT)

val service: PetStore = context.getBean(&quot;petStore&quot;, PetStore::class.java)

// create and configure beans using GenericApplicationContext
val context = GenericApplicationContext()
val beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(PetStore::class.java).beanDefinition
context.registerBeanDefinition(&quot;petStore&quot;, beanDefinition)
context.refresh()

val service: PetStore = context.getBean(&quot;petStore&quot;, PetStore::class.java)
val userList = service.userName


// PetStore
class PetStore {
	val userName: List&amp;lt;String&amp;gt;
		get() = mutableListOf(&quot;dol9&quot;, &quot;dol10&quot;)
}&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;위에 작성한 XML과 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/kgcrom/learn-something/commit/e95a391445213127ff9eb6e7017b7c9a833d98bc&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/kgcrom/learn-something/commit/e95a391445213127ff9eb6e7017b7c9a833d98bc&lt;/a&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;Spring Framework 6.2 git clone 한 뒤 코드를 봤지만 이해 안 되는 게 참 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서 Resource까지 보면서 대강 분위기 익히고 코드를 봐야겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2054&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b20yPh/btsMbGut3ph/UcK5iEg3g0M2g1tMDqjVaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b20yPh/btsMbGut3ph/UcK5iEg3g0M2g1tMDqjVaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b20yPh/btsMbGut3ph/UcK5iEg3g0M2g1tMDqjVaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb20yPh%2FbtsMbGut3ph%2FUcK5iEg3g0M2g1tMDqjVaK%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;2054&quot; height=&quot;1664&quot; data-origin-width=&quot;2054&quot; data-origin-height=&quot;1664&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;</description>
      <category>개발하면서/타인글보면서</category>
      <category>Spring Framework</category>
      <author>오산돌구</author>
      <guid isPermaLink="true">https://dol9.tistory.com/332</guid>
      <comments>https://dol9.tistory.com/332#entry332comment</comments>
      <pubDate>Sun, 9 Feb 2025 16:00:12 +0900</pubDate>
    </item>
    <item>
      <title>CSS를 공부하다가 AI활용에 대해 생각해봤다.</title>
      <link>https://dol9.tistory.com/331</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 작업할게 생겨서 Next.js + React 그리고 Ant 디자인 시스템으로 인하우스 화면을 개발하고 있다.&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;ant.design에서 원하는 동작의(?) 컴포넌트를 검색한 후 화면 컴포넌트에 복붙 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 원하는 위치에 올 때까지(?) 정렬이랑 마진 주면서 대강 맞춘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력을 받고 API 호출하고 결과 보여주는 팀 내에서 사용하는 인하우스 시스템이어서 크게 문제가 되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 기능 동작과 누락한 컴포넌트 없이 잘 구현했지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐라 표현할 수 없는...뭔가 애매~한 느낌과 화면 크기에 따라 텍스트가 넘어가고 깨지고 난리도 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;layout 잡는 게 중요하다고 다시 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 인하우스 화면 개발할 때는 기능 동작을 최우선으로 삼아&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면 개발은 주먹구구 식으로  했는데, 이번에 시간을 들여 공부하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;영상과 글로 Flex, Grid를 공부하다가 알게 된 근본 사이트!!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 Box model 보고 있는데 이 개념을 모르고 CSS 만졌던 내 모습이 부끄러웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체에 한가지의 역할과 책임을 주고 격리시킨다는 개념과 Box model이 비슷하다는 느낌도 들었다.&lt;/p&gt;
&lt;figure id=&quot;og_1731509625402&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;CSS 학습 &amp;nbsp;|&amp;nbsp; web.dev&quot; data-og-description=&quot;웹 스타일링 전문 지식을 한 단계 끌어올릴 수 있는 상시 CSS 과정 및 참고 자료.&quot; data-og-host=&quot;web.dev&quot; data-og-source-url=&quot;https://web.dev/learn/css&quot; data-og-url=&quot;https://web.dev/learn/css?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/TWxaH/hyXwqf995L/4dhDJ2RrRnAKOBuT6nUOsK/img.jpg?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/bEr0qd/hyXwsygZ97/Rl1d72jjT6BmC5gXna1M7K/img.jpg?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675&quot;&gt;&lt;a href=&quot;https://web.dev/learn/css&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://web.dev/learn/css&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/TWxaH/hyXwqf995L/4dhDJ2RrRnAKOBuT6nUOsK/img.jpg?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675,https://scrap.kakaocdn.net/dn/bEr0qd/hyXwsygZ97/Rl1d72jjT6BmC5gXna1M7K/img.jpg?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;CSS 학습 &amp;nbsp;|&amp;nbsp; web.dev&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;웹 스타일링 전문 지식을 한 단계 끌어올릴 수 있는 상시 CSS 과정 및 참고 자료.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;web.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flex, Grid 그리고 Box model 까지 학습했지만 개발은 해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;여기서 놀란 점!!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 나열한 지식을 모르고 Github Copilot에게 질문했을 때 답변과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지식을 알고 구체적으로 질문했을 때의 답변의 퀄리티가 상당히 차이가 있었다. (후자가 좋다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 코드 자동완성도 뭔가 착 달라붙는 느낌??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정보는 흘러넘쳐나고 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;이제 막 개발을 시작한 사람이 AI와 함께 한다고 해서 20년 이상 개발해 온 개발자만큼 작성하는 모습은 상상하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;b&gt;결국 기본이 더 중요해진 것 같다.&lt;/b&gt;&lt;/i&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;/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;그래서 내가 가진 역량이 5라면 AI를 더 잘 쓰려고 노력하기보다는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10의 역량을 가지도록 (AI와 함께)노력하는 게 오히려 더 나은 결과물을 만든다고 생각한다.&amp;nbsp; (성공/실패와는 별개로 꽤 괜찮은 결과물)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ASI 세상이 오면 또 모르겠다..&lt;/p&gt;
&lt;figure id=&quot;og_1731592503567&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[IT TREND] AGI를 향한 기대와 우려&quot; data-og-description=&quot;시작하며 4월에도 기술 시장 소식은 AI를 중심으로 끊임없이 이어지고 있습니다. 금주의 수 많은 소식들을 살펴보며 특히 눈에 띄었던 것은, 바로 AGI (Artificial General Intelligence)에 대하여 다루는 &quot; data-og-host=&quot;tech.kakaoenterprise.com&quot; data-og-source-url=&quot;https://tech.kakaoenterprise.com/189&quot; data-og-url=&quot;https://tech.kakaoenterprise.com/189&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ZMzTA/hyXzOGArvn/N7NkHLn6R1zVRxHholkIMk/img.png?width=800&amp;amp;height=360&amp;amp;face=0_0_800_360,https://scrap.kakaocdn.net/dn/dd6Z9S/hyXwubWteA/Wo4YnTwC2Id7NFuXhtdOi1/img.png?width=800&amp;amp;height=360&amp;amp;face=0_0_800_360,https://scrap.kakaocdn.net/dn/c0WzHs/hyXzQRWrFi/wbmcLPKJ2ErR1EDVGZ6DHK/img.png?width=2000&amp;amp;height=1125&amp;amp;face=0_0_2000_1125&quot;&gt;&lt;a href=&quot;https://tech.kakaoenterprise.com/189&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tech.kakaoenterprise.com/189&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ZMzTA/hyXzOGArvn/N7NkHLn6R1zVRxHholkIMk/img.png?width=800&amp;amp;height=360&amp;amp;face=0_0_800_360,https://scrap.kakaocdn.net/dn/dd6Z9S/hyXwubWteA/Wo4YnTwC2Id7NFuXhtdOi1/img.png?width=800&amp;amp;height=360&amp;amp;face=0_0_800_360,https://scrap.kakaocdn.net/dn/c0WzHs/hyXzQRWrFi/wbmcLPKJ2ErR1EDVGZ6DHK/img.png?width=2000&amp;amp;height=1125&amp;amp;face=0_0_2000_1125');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[IT TREND] AGI를 향한 기대와 우려&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;시작하며 4월에도 기술 시장 소식은 AI를 중심으로 끊임없이 이어지고 있습니다. 금주의 수 많은 소식들을 살펴보며 특히 눈에 띄었던 것은, 바로 AGI (Artificial General Intelligence)에 대하여 다루는&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tech.kakaoenterprise.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;b&gt;저희는 프로그래머가 오랫동안 '운전석'에 있는 미래를 그리고 있어요&lt;/b&gt;&lt;br /&gt;&lt;/span&gt;cursor 대표 인터뷰 중&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://maily.so/josh/posts/2nznwx6gzp5&quot;&gt;https://maily.so/josh/posts/2nznwx6gzp5&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1731594197255&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;&amp;quot;AI가 개발자를 대체? &amp;lsquo;이 능력&amp;rsquo; 키우세요.&amp;quot; 커서 창업자 인터뷰&quot; data-og-description=&quot;Open AI를 제치고 세계 가장 핫한 툴 1위가 된 커서&quot; data-og-host=&quot;maily.so&quot; data-og-source-url=&quot;https://maily.so/josh/posts/2nznwx6gzp5&quot; data-og-url=&quot;https://maily.so/josh/posts/2nznwx6gzp5&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hHp88/hyXwqm53oM/92sXKkXufH1RSNaleiatq1/img.png?width=1410&amp;amp;height=898&amp;amp;face=927_304_1054_442,https://scrap.kakaocdn.net/dn/pazCK/hyXzONmxoi/iI1dSCXsnuKz45q0MlYkK1/img.png?width=1410&amp;amp;height=898&amp;amp;face=927_304_1054_442,https://scrap.kakaocdn.net/dn/bjSkWK/hyXzN8KyTc/QHdl3W5Tsi2qO5w9UPV3t0/img.png?width=879&amp;amp;height=698&amp;amp;face=0_0_879_698&quot;&gt;&lt;a href=&quot;https://maily.so/josh/posts/2nznwx6gzp5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://maily.so/josh/posts/2nznwx6gzp5&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hHp88/hyXwqm53oM/92sXKkXufH1RSNaleiatq1/img.png?width=1410&amp;amp;height=898&amp;amp;face=927_304_1054_442,https://scrap.kakaocdn.net/dn/pazCK/hyXzONmxoi/iI1dSCXsnuKz45q0MlYkK1/img.png?width=1410&amp;amp;height=898&amp;amp;face=927_304_1054_442,https://scrap.kakaocdn.net/dn/bjSkWK/hyXzN8KyTc/QHdl3W5Tsi2qO5w9UPV3t0/img.png?width=879&amp;amp;height=698&amp;amp;face=0_0_879_698');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;&quot;AI가 개발자를 대체? &amp;lsquo;이 능력&amp;rsquo; 키우세요.&quot; 커서 창업자 인터뷰&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Open AI를 제치고 세계 가장 핫한 툴 1위가 된 커서&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;maily.so&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS 러닝코스 마무리하면 프론트 개발 하면서 AI와 긴밀하게 협업하는 모습을 상상하니 벌써 설렌다.&lt;/p&gt;</description>
      <category>살아가면서/생각하면서</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>오산돌구</author>
      <guid isPermaLink="true">https://dol9.tistory.com/331</guid>
      <comments>https://dol9.tistory.com/331#entry331comment</comments>
      <pubDate>Thu, 14 Nov 2024 23:37:23 +0900</pubDate>
    </item>
  </channel>
</rss>