<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>codie0226 님의 블로그</title>
    <link>https://codie0226.tistory.com/</link>
    <description>codie0226 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Sun, 26 Jul 2026 15:41:56 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>codie0226</managingEditor>
    <image>
      <title>codie0226 님의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/7796988/attach/b2b0509f8c49484997dc028884671b67</url>
      <link>https://codie0226.tistory.com</link>
    </image>
    <item>
      <title>[하루 1 CS] 데이터베이스 4 - 데이터베이스 설계 과정</title>
      <link>https://codie0226.tistory.com/18</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;&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;데이터베이스 설계는 크게 다음과 같은 5단계로 진행된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;요구사항 분석&lt;/li&gt;
&lt;li&gt;개념적 설계&lt;/li&gt;
&lt;li&gt;논리적 설계&lt;/li&gt;
&lt;li&gt;물리적 설계&lt;/li&gt;
&lt;li&gt;구현&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 하나씩 알아보겠다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 요구사항 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 단계이자 가장 중요한 단계다. 이 단계에서는 데이터베이스를 사용할 사람이 어떤 정보를 저장하고 조회해야 하는지를 파악한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말해 &quot;이 시스템에서 어떤 데이터가 필요하고, 누가 어떤 데이터에 접근하고 어떤 기능을 수행해야 하는가?&quot;를 정리하는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항 분석에서 수행하는 것들은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자 인터뷰 또는 기획 문서 분석&lt;/li&gt;
&lt;li&gt;어떤 데이터 항목을 저장할 것인지 파악&lt;/li&gt;
&lt;li&gt;데이터 간의 관계 파악&lt;/li&gt;
&lt;li&gt;각 데이터의 특성 파악 (고유해야 하는지, 빈 값이 허용되는지 등)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 학교 수강 신청 시스템을 만든다고 하면, &quot;학생 정보, 강의 정보, 수강 신청 내역이 필요하다&quot;는 게 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단계에서 제대로 정리하지 않으면, 나중에 빠진 정보가 생겨서 테이블을 추가하거나 기존 테이블을 대대적으로 수정해야 하는 상황이 온다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 개념적 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항을 바탕으로 실제 데이터베이스와는 독립적인 추상적인 데이터 구조를 만드는 단계다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단계의 결과물은 &lt;b&gt;ER 다이어그램(Entity-Relationship Diagram)&lt;/b&gt;이다. 쉽게 말해 데이터가 어떤 엔티티(Entity)들로 구성되고, 그것들이 서로 어떤 관계(Relationship)를 가지는지를 시각적으로 표현한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ER 다이어그램을 그릴 때 핵심이 되는 요소는 다음 세 가지다.&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;엔티티(Entity)&lt;/b&gt;: 데이터를 저장할 대상. 학생, 강의, 교수 같은 것들이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;속성(Attribute)&lt;/b&gt;: 엔티티가 가지는 특성. 학생이라면 학번, 이름, 학과 같은 것들이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;관계(Relationship)&lt;/b&gt;: 엔티티 간의 연관성. 학생은 강의를 수강한다, 교수는 강의를 담당한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단계에서는 특정 DBMS(MySQL, PostgreSQL 등)에 종속되지 않고 순수하게 개념적으로만 설계한다. 따라서 DBMS를 나중에 바꾸더라도 이 설계는 그대로 재사용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 논리적 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적 설계에서 만든 ER 다이어그램을 실제 관계형 데이터베이스 모델로 변환하는 단계다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말해 ER 다이어그램의 엔티티들이 실제 테이블 스키마로 변환되는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 중요한 변환 규칙들이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티 &amp;rarr; 테이블&lt;/li&gt;
&lt;li&gt;속성 &amp;rarr; 컬럼&lt;/li&gt;
&lt;li&gt;관계 &amp;rarr; 외래키(FK) 또는 연결 테이블&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학생-강의 관계를 예로 들면, 학생 테이블과 강의 테이블이 각각 만들어지고, 학생과 강의는 다대다(N:M) 관계이므로 수강신청 테이블이 별도로 만들어진다.&lt;/p&gt;
&lt;table style=&quot;height: 180px;&quot; width=&quot;856&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;학생 테이블&lt;/th&gt;
&lt;th&gt;강의 테이블&lt;/th&gt;
&lt;th&gt;수강신청 테이블&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;학번 (PK)&lt;/td&gt;
&lt;td&gt;강의코드 (PK)&lt;/td&gt;
&lt;td&gt;학번 (FK)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;이름&lt;/td&gt;
&lt;td&gt;강의명&lt;/td&gt;
&lt;td&gt;강의코드 (FK)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;학과&lt;/td&gt;
&lt;td&gt;담당교수&lt;/td&gt;
&lt;td&gt;수강연도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;학기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단계에서 정규화(Normalization)도 진행된다. 중복 데이터를 없애고, 데이터 이상(Anomaly)이 발생하지 않도록 테이블을 적절히 분리하는 과정이다. (정규화에 대한 자세한 내용은 다음에 따로 다뤄볼 예정이다.)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 물리적 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논리적 설계가 &quot;무엇을 저장할 것인가&quot;라면, 물리적 설계는 &quot;어떻게 저장할 것인가&quot;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 DBMS에서 동작하도록 구체적인 저장 방식과 성능을 고려하는 단계이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물리적 설계에서 결정하는 것들은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 타입&lt;/b&gt;: 이름을 VARCHAR(50)으로 할지, 학번을 INT로 할지 CHAR(6)으로 할지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인덱스(Index)&lt;/b&gt;: 자주 조회되는 컬럼에 인덱스를 걸어 검색 속도를 높임&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파티셔닝&lt;/b&gt;: 데이터가 너무 많으면 테이블을 물리적으로 분리하여 성능을 최적화&lt;/li&gt;
&lt;li&gt;&lt;b&gt;저장 공간 계산&lt;/b&gt;: 테이블의 예상 데이터 크기와 디스크 사용량 산정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단계부터는 MySQL, PostgreSQL, Oracle 같은 특정 DBMS에 맞게 설계가 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 학번 컬럼 하나를 설계하더라도&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;-- MySQL 기준
student_id INT NOT NULL AUTO_INCREMENT,

-- Oracle 기준
student_id NUMBER(6) NOT NULL,&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 DBMS마다 문법과 타입이 다르다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 단계다. 물리적 설계를 바탕으로 실제 SQL로 테이블을 만들면 된다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE TABLE student (
    student_id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    department VARCHAR(50),
    phone VARCHAR(20),
    PRIMARY KEY (student_id)
);

CREATE TABLE course (
    course_id CHAR(8) NOT NULL,
    course_name VARCHAR(100) NOT NULL,
    professor VARCHAR(50),
    PRIMARY KEY (course_id)
);

CREATE TABLE enrollment (
    student_id INT NOT NULL,
    course_id CHAR(8) NOT NULL,
    year INT,
    semester TINYINT,
    PRIMARY KEY (student_id, course_id, year, semester),
    FOREIGN KEY (student_id) REFERENCES student(student_id),
    FOREIGN KEY (course_id) REFERENCES course(course_id)
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드로 보면 간단해 보이지만, 앞에서 거쳐온 요구사항 분석 &amp;rarr; 개념적 설계 &amp;rarr; 논리적 설계 &amp;rarr; 물리적 설계 단계가 제대로 이루어져야 이 SQL도 의미가 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 설계 과정을 한 줄로 정리하면 이렇다.&lt;/p&gt;
&lt;table style=&quot;height: 129px;&quot; width=&quot;860&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 24px;&quot;&gt;
&lt;th style=&quot;height: 24px;&quot;&gt;단계&lt;/th&gt;
&lt;th style=&quot;height: 24px;&quot;&gt;핵심 질문&lt;/th&gt;
&lt;th style=&quot;height: 24px;&quot;&gt;결과물&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;요구사항 분석&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;무엇이 필요한가?&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;요구사항 명세서&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;개념적 설계&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;어떤 개념들이 있고 어떻게 연결되는가?&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;ER 다이어그램&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;논리적 설계&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;테이블로 어떻게 표현할 것인가?&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;테이블 스키마&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;물리적 설계&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;어떻게 저장하고 최적화할 것인가?&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;물리적 스키마 및 인덱스 설계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;구현&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;SQL로 어떻게 만드는가?&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;실제 DDL 쿼리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 DB 설계를 할 때는 이 과정이 번거롭게 느껴질 수 있다. 사실 작은 프로젝트는 ER 다이어그램까지 그릴 필요는 없겠지만, 어느 정도 규모가 있는 시스템이라면 초반에 설계에 시간을 투자하는 게 나중에 훨씬 편하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 논리적 설계 단계에서 언급했던 정규화에 대해 알아보겠다.&lt;/p&gt;</description>
      <category>코딩/하루 1 CS</category>
      <category>ERD</category>
      <category>SQL</category>
      <category>데이터베이스</category>
      <category>데이터베이스 설계</category>
      <author>codie0226</author>
      <guid isPermaLink="true">https://codie0226.tistory.com/18</guid>
      <comments>https://codie0226.tistory.com/18#entry18comment</comments>
      <pubDate>Tue, 16 Jun 2026 22:42:23 +0900</pubDate>
    </item>
    <item>
      <title>[하루 1 CS] 데이터베이스 3 - 테이블의 연관 관계</title>
      <link>https://codie0226.tistory.com/17</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 3번째 포스트이다. 이번에는 RDB에서의 테이블 간의 연관 관계를 짧게 정리해보겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터베이스의 연관 관계란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에서 테이블 간의 데이터는 서로에게 연관된 정보를 품고 종속될 수 있다. 이러한 &lt;b&gt;테이블 간의 관계를 릴레이션(Relation)&lt;/b&gt;이라고 하고, 연관 관계, 관계, 연관 등 다양하게 표현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스의 연관 관계는 크게 세가지로 분류된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;1:1&lt;/li&gt;
&lt;li&gt;1:N&lt;/li&gt;
&lt;li&gt;N:1&lt;/li&gt;
&lt;li&gt;N:M&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나씩 살펴보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 1:1 관계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1:1 관계는 가장 간단한 관계이다. 하나의 레코드가 다른 테이블의 하나의 레코드와 연관 관계를 갖는다는 것을 의미한다.&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; width=&quot;144&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td width=&quot;72&quot; height=&quot;22&quot;&gt;제목&lt;/td&gt;
&lt;td width=&quot;72&quot;&gt;저자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;채식주의자&lt;/td&gt;
&lt;td&gt;한강&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;해리포터&lt;/td&gt;
&lt;td&gt;J.K 롤링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;그리고 아무도 없었다&lt;/td&gt;
&lt;td&gt;애거서 크리스티&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[저자] 테이블&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; width=&quot;144&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td width=&quot;72&quot; height=&quot;22&quot;&gt;저자&lt;/td&gt;
&lt;td width=&quot;72&quot;&gt;국적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;한강&lt;/td&gt;
&lt;td&gt;대한민국&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;J.K 롤링&lt;/td&gt;
&lt;td&gt;영국&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;애거서 크리스티&lt;/td&gt;
&lt;td&gt;영국&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 도서 데이터베이스 내부의 두 테이블이라고 생각하자. [책] 테이블과 [저자] 테이블에는 공통적으로 (저자) 속성을 연관 관계의 외래키로 사용하고 있다. 이 때 각 테이블에서 (저자)라는 속성을 어떻게 보유하는지 살펴보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[책] 테이블 - 책 한 권에 고유한 저자 1명&lt;/li&gt;
&lt;li&gt;[저자] 테이블 - 저자 한 명에 고유한 국적 1개&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 한 테이블 별로 하나의 (저자)에 대해서 1개의 관계를 갖게 되면 1:1 관계이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈치 빠른 사람들은 여기서 하나의 함정이 있다는 것을 알아차릴 것이다. 왜냐하면 1명의 저자가 1권의 책만 출판한다는 가정이 없기 때문이다. 여기에 이어서 개념을 확장해보겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 1:N, N:1 관계&lt;/h3&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;1:N과 N:1은 묶어서 설명하도록 하겠다. 결국 어떤 테이블을 기준으로 보느냐에 따른 '관점의 차이'이기 때문에, 하나의 개념만 확실히 알면 된다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;위의 도서 데이터베이스 예제를 이어서 살펴보자.&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6&quot;&gt;[책] 테이블&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;7&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;제목&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;저자&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;7,1,0,0&quot;&gt;채식주의자&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;7,1,1,0&quot;&gt;한강&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;7,2,0,0&quot;&gt;해리포터&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;7,2,1,0&quot;&gt;J.K 롤링&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;7,3,0,0&quot;&gt;그리고 아무도 없었다&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;7,3,1,0&quot;&gt;애거서 크리스티&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;7,4,0,0&quot;&gt;오리엔트 특급 살인&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;7,4,1,0&quot;&gt;애거서 크리스티&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8&quot;&gt;[저자] 테이블&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;9&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;저자&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;국적&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,1,0,0&quot;&gt;한강&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,1,1,0&quot;&gt;대한민국&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,2,0,0&quot;&gt;J.K 롤링&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,2,1,0&quot;&gt;영국&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,3,0,0&quot;&gt;애거서 크리스티&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;9,3,1,0&quot;&gt;영국&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 [책] 테이블에 애거서 크리스티의 책(오리엔트 특급 살인)이 한 권 더 추가되었다. 이제 단순한 1:1 매칭이 아니라, 외래키(저자)를 기준으로 테이블 간에 &lt;b data-index-in-node=&quot;93&quot; data-path-to-node=&quot;10&quot;&gt;1:N(또는 N:1) 관계&lt;/b&gt;가 성립한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;두 테이블의 관계를 관점별로 풀어서 해석해 보면 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;[저자] 테이블 관점 (1 : N)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,1,0,0&quot;&gt;한 명의 저자&lt;/b&gt;가 여러 권의 다른 책(N)들을 가질 수 있다.&lt;/li&gt;
&lt;li&gt;예시: '애거서 크리스티'라는 한 명의 데이터가 [책] 테이블의 레코드 2개와 매칭됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;[책] 테이블 관점 (N : 1)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[책] 테이블에 있는 여러 권의 책(N)들은 각각 한 명의 고유한 저자(1)에게 종속된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;결국 1:N과 N:1은 외래키를 기준으로 &lt;b data-index-in-node=&quot;23&quot; data-path-to-node=&quot;13&quot;&gt;어떤 테이블을 중심으로 생각하느냐&lt;/b&gt;에 따라 이름만 바뀔 뿐 본질은 같다. 실제 관계형 데이터베이스(RDB)를 설계할 때 가장 많이 쓰이는 형태이므로 반드시 숙지해두어야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. N:M&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 N:M 관계이다. N:M 관계는 조금 특별하다. 일반적인 테이블 2개로는 성립할 수 없고, 중간 다리 역할을 하는 &lt;b&gt;연관 테이블 (Relation Table/Entity)&lt;/b&gt;이 필요하기 때문이다. 이번에도 도서 테이블을 예시로 들어보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[책] 테이블&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; width=&quot;144&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td width=&quot;72&quot; height=&quot;22&quot;&gt;제목&lt;/td&gt;
&lt;td width=&quot;72&quot;&gt;저자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;채식주의자&lt;/td&gt;
&lt;td&gt;한강&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;해리포터&lt;/td&gt;
&lt;td&gt;J.K 롤링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;그리고 아무도 없었다&lt;/td&gt;
&lt;td&gt;애거서 크리스티&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;오리엔트 특급 살인&lt;/td&gt;
&lt;td&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;[유저] 테이블&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; width=&quot;144&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td width=&quot;72&quot; height=&quot;22&quot;&gt;이름&lt;/td&gt;
&lt;td width=&quot;72&quot;&gt;성별&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;코디&lt;/td&gt;
&lt;td&gt;남&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;엘리&lt;/td&gt;
&lt;td&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;[대여] 연관 테이블&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; width=&quot;302&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td width=&quot;72&quot; height=&quot;22&quot;&gt;이름&lt;/td&gt;
&lt;td width=&quot;72&quot;&gt;제목&lt;/td&gt;
&lt;td width=&quot;79&quot;&gt;대여일&lt;/td&gt;
&lt;td width=&quot;79&quot;&gt;반납일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;코디&lt;/td&gt;
&lt;td&gt;해리포터&lt;/td&gt;
&lt;td&gt;06월 15일&lt;/td&gt;
&lt;td&gt;06월 17일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;코디&lt;/td&gt;
&lt;td&gt;그리고 아무도 없었다&lt;/td&gt;
&lt;td&gt;06월 15일&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;엘리&lt;/td&gt;
&lt;td&gt;채식주의자&lt;/td&gt;
&lt;td&gt;05월 19일&lt;/td&gt;
&lt;td&gt;05월 24일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;엘리&lt;/td&gt;
&lt;td&gt;그리고 아무도 없었다&lt;/td&gt;
&lt;td&gt;05월 25일&lt;/td&gt;
&lt;td&gt;06월 01일&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[유저] 테이블 - 한 유저는 여러 책을 대여할 수 있다.&lt;/li&gt;
&lt;li&gt;[책] 테이블 - 한 책은 여러 유저에게 대여할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음... 여기까지 봤을 때는 연관 테이블의 필요성을 느끼지 않을 수도 있다. 하지만 연관 테이블 없이는 관계형 데이터베이스의 중요한 요소 중 하나인 &lt;b&gt;제 1정규형(1NF)&lt;/b&gt;을 위배하게 된다. 정규화에 대해서는 나중에 살펴보도록 하겠지만, 여기에서는 제 1정규형의 개념만 살펴보도록 하겠다. 만약 [책] 테이블이 이렇게 되어있다고 가정해보자.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; width=&quot;223&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td width=&quot;72&quot; height=&quot;22&quot;&gt;제목&lt;/td&gt;
&lt;td width=&quot;72&quot;&gt;저자&lt;/td&gt;
&lt;td width=&quot;79&quot;&gt;빌린 사람&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;채식주의자&lt;/td&gt;
&lt;td&gt;한강&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;해리포터&lt;/td&gt;
&lt;td&gt;J.K 롤링&lt;/td&gt;
&lt;td&gt;코디&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;그리고 아무도 없었다&lt;/td&gt;
&lt;td&gt;애거서 크리스티&lt;/td&gt;
&lt;td&gt;코디, 엘리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td height=&quot;22&quot;&gt;오리엔트 특급 살인&lt;/td&gt;
&lt;td&gt;애거서 크리스티&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 테이블은 (빌린 사람)이라는 요소에 여러 개의 값 (Value)들이 들어있다. 그러나 이러한 형태의 레코드는 바람직하지 않다. 책이 대여될 때마다 빌린 사람이 바뀌어야 하고, 비즈니스 로직상 대여할 때마다, 반납할 때마다 값이 존재할 수도 없을 수도 있다. 이처럼 하나의 속성의 여러 값이 들어있으면, 제1정규형을 어기게 된다. 데이터 무결성이 깨지는 로직이므로 중간다리 역할을 할 연관 테이블이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연관 테이블은 각각의 테이블의 외래키 역할을 하는 속성을 들고, 부가적인 속성을 추가하여 정보를 더할 수 있다. 위의 예시에서는 (제목), (이름)을 [책]과 [유저] 테이블에서 가져오고, 대여일과 반납일을 추가로 기록할 수 있다. 데이터 무결성을 지키면서도 효과적으로 유저의 활동을 관리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관계형 데이터베이스의 연관 관계는 데이터를 효율적으로 처리하고 무결성을 지키는 데에 필수적이다. 따라서 데이터베이스를 설계하는 단계에서부터 서비스의 비즈니스 로직을 반영할 수 있도록 논리적으로 잘 맞물릴 수 있도록 주의해야 한다. 의외로 프로젝트를 진행하다보면 잘못 설계된 데이터베이스로 인해 데이터베이스 수정 작업을 해야 할 일이 생긴다. 그런 상황이 발생하면 수정해야 하는 코드도 많아지고, 인프라 전반적인 로직 베이스가 흔들릴 수도 있으므로 데이터베이스를 다룰 때 가장 많이 신경써야 하는 부분이 바로 설계 작업이다. 내일은 데이터베이스의 설계 단계를 알아보도록 하겠다.&lt;/p&gt;</description>
      <category>코딩/하루 1 CS</category>
      <category>1:1</category>
      <category>1:N</category>
      <category>N:M</category>
      <category>관계형 데이터베이스</category>
      <category>데이터베이스</category>
      <author>codie0226</author>
      <guid isPermaLink="true">https://codie0226.tistory.com/17</guid>
      <comments>https://codie0226.tistory.com/17#entry17comment</comments>
      <pubDate>Mon, 15 Jun 2026 23:55:32 +0900</pubDate>
    </item>
    <item>
      <title>[하루 1 CS] 데이터베이스 2 - 테이블의 키</title>
      <link>https://codie0226.tistory.com/16</link>
      <description>&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 관계형 데이터베이스(Relational Database)의 다양한 종류의 키(Key)와 그 역할, 의미에 대해 알아보겠다.&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;Key란?&lt;/h2&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;관계형 데이터베이스에서 키는 매우 중요한 역할을 수행한다. 테이블 내부의 레코드들을 고유하게 식별하고, 다른 테이블과의 연관 관계를 맺는 기준이 되기 때문이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;키는 기본적으로 데이터베이스 테이블의 요소인 속성(Attribute)으로 정의된다. 하나의 속성이 하나의 키가 될 수도 있고, 여러 속성의 쌍(Attribute Pair)이 하나의 키를 정의할 수도 있다. 키는 개념의 포함 관계와 목적에 따라 다음과 같이 분류된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;슈퍼키 (Super Key)&lt;/li&gt;
&lt;li&gt;후보키 (Candidate Key)&lt;/li&gt;
&lt;li&gt;기본키 (Primary Key)&lt;/li&gt;
&lt;li&gt;외래키 (Foreign Key)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;이제 개념의 가장 큰 틀인 슈퍼키부터 차례대로 알아보겠다.&lt;/p&gt;
&lt;hr data-path-to-node=&quot;10&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size26&quot;&gt;1. 슈퍼키 (Super Key)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;슈퍼키는 테이블에서 &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;12&quot;&gt;레코드를 고유하게 식별할 수 있는 속성들의 집합&lt;/b&gt;이다. 즉, 데이터베이스의 '유일성(Uniqueness)'을 만족하는 가장 포괄적인 키이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 학생 테이블에 (학번, 이름, 학과, 전화번호)가 있다면 학번은 당연히 중복되지 않으므로 레코드를 구별할 수 있다. 따라서 (학번)은 슈퍼키이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;뿐만 아니라 (학번, 이름), (학번, 학과), 심지어 (학번, 이름, 학과, 전화번호)처럼 학번을 포함한 어떤 속성의 조합이든 레코드를 고유하게 식별할 수만 있다면 모두 슈퍼키가 될 수 있다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;하지만 슈퍼키는 레코드를 구별하는 데 꼭 필요하지 않은 속성(예: 이름, 학과 등)까지 불필요하게 포함하고 있다는 단점이 있다.&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size26&quot;&gt;2. 후보키 (Candidate Key)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;후보키는 슈퍼키들 중에서 &lt;b data-index-in-node=&quot;14&quot; data-path-to-node=&quot;16&quot;&gt;최소성(Minimality)을 만족하는 키&lt;/b&gt;를 의미한다. 즉, 레코드를 고유하게 식별하는 데 꼭 필요한 최소한의 속성들로만 구성된 필수적인 키다. 따라서 후보키는 '기본키가 될 수 있는 후보들'을 뜻하며, 논리적 설계 단계에서 찾아내게 된다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;아래 유저 테이블을 다시 살펴보자.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;18&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;학번&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;이름&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;학과&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;전화번호&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,1,0,0&quot;&gt;123456&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,1,1,0&quot;&gt;코디&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,1,2,0&quot;&gt;컴퓨터공학과&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,1,3,0&quot;&gt;010-1234-5678&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,2,0,0&quot;&gt;223344&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,2,1,0&quot;&gt;레이&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,2,2,0&quot;&gt;시각디자인과&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,2,3,0&quot;&gt;010-9876-5432&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,3,0,0&quot;&gt;567890&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,3,1,0&quot;&gt;피아&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,3,2,0&quot;&gt;인공지능학과&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;18,3,3,0&quot;&gt;010-1111-2222&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;개념적으로 분석해 보면 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이름은 동명이인이 존재할 수 있으므로 탈락이다.&lt;/li&gt;
&lt;li&gt;(이름, 학과) 조합 역시 우연히 같은 이름의 학생이 같은 학과에 들어올 수 있으므로 완전히 고유하다고 장담할 수 없다.&lt;/li&gt;
&lt;li&gt;이 테이블에서 절대 중복되지 않는 최소한의 고유 값은 (학번)과 (전화번호)뿐이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;따라서 이 테이블의 후보키는 (학번)과 (전화번호) 2개가 된다.&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size26&quot;&gt;3. 기본키 (Primary Key)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;기본키는 앞서 찾아낸 여러 후보키 중에서 설계자가 최종적으로 선택한 '대표 키'이다. 데이터베이스 레코드를 구분 짓는 가장 기본적인 역할을 수행하며, 다음과 같은 특징을 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;24&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,0,0&quot;&gt;Null(빈 값)을 가질 수 없다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,1,0&quot;&gt;중복된 값을 가질 수 없다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,2,0&quot;&gt;값이 자주 변경되지 않는 불변성을 지녀야 한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 위 예시에서 후보키였던 (학번)과 (전화번호) 중, 왜 &lt;b data-index-in-node=&quot;36&quot; data-path-to-node=&quot;25&quot;&gt;전화번호는 기본키로 잘 쓰이지 않을까?&lt;/b&gt; 가장 큰 이유는 &lt;b data-index-in-node=&quot;67&quot; data-path-to-node=&quot;25&quot;&gt;변동 가능성&lt;/b&gt; 때문이다. 학번은 한 번 발급되면 변하지 않지만, 전화번호는 사용자가 언제든 바꿀 수 있는 값이다. 또한 사람에 따라 전화번호를 등록하지 않는(Null) 경우가 생길 수도 있다. 게다가 전화번호는 민감한 개인정보이므로 시스템 식별자로 자주 노출되는 것보다 학번 같은 일련번호를 기본키로 사용하는 것이 보안과 성능 측면 모두에서 유리하다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;추가적으로 기본키는 별도의 설정을 하지 않아도 DBMS 내에서 자동으로 인덱스(Index)로 지정된다. 덕분에 기본키를 기준으로 검색과 삽입 연산이 매우 빠르게 이루어지게 된다.&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;27&quot; data-ke-size=&quot;size26&quot;&gt;4. 외래키 (Foreign Key)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;외래키는 &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;28&quot;&gt;테이블과 테이블을 이어주는 연결고리&lt;/b&gt; 역할을 하는 키이다. 관계형 데이터베이스에서는 여러 테이블이 서로 연관되어 있는 경우가 많다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;29&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;학번 (PK)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;이름&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;학과&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;전화번호&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;29,1,0,0&quot;&gt;123456&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;29,1,1,0&quot;&gt;코디&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;29,1,2,0&quot;&gt;컴퓨터공학과&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;29,1,3,0&quot;&gt;010-1234-5678&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;29,2,0,0&quot;&gt;223344&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;29,2,1,0&quot;&gt;레이&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;29,2,2,0&quot;&gt;시각디자인과&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;29,2,3,0&quot;&gt;010-9876-5432&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-path-to-node=&quot;30&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;학번 (FK)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;담당교수&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;30,1,0,0&quot;&gt;123456&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;30,1,1,0&quot;&gt;철수&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;30,2,0,0&quot;&gt;223344&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span data-path-to-node=&quot;30,2,1,0&quot;&gt;영희&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-path-to-node=&quot;31&quot; data-ke-size=&quot;size16&quot;&gt;위 구조에서 '학생 정보 테이블'과 '담당교수 테이블'을 이어주는 매개체가 바로 두 테이블에 공통으로 존재하는 &lt;b data-index-in-node=&quot;62&quot; data-path-to-node=&quot;31&quot;&gt;(학번)&lt;/b&gt; 속성이다. 이때 담당교수 테이블의 (학번)은 학생 테이블의 기본키를 참조하고 있으므로 외래키(FK)가 된다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;외래키는 데이터의 중복을 방지하고 데이터 무결성 (Data Integrity)을 유지하기 위해 필수적이다. 만약 '코디' 학생이 졸업하여 학생 테이블에서 학번(123456)이 삭제되었는데, 담당교수 테이블에는 학번이 그대로 남아있다면 데이터의 결손(유령 데이터)이 발생하여 시스템에 치명적인 오류를 야기할 수 있다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;33&quot; data-ke-size=&quot;size23&quot;&gt;외래키 제약 조건&lt;/h3&gt;
&lt;p data-path-to-node=&quot;34&quot; data-ke-size=&quot;size16&quot;&gt;이러한 데이터 결손을 방지하기 위해 외래키에는 함부로 원본 데이터를 변경하거나 삭제할 수 없도록 제약 조건(Constraint)을 걸 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;35&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35,0,0&quot;&gt;Strict (제한/거부):&lt;/b&gt; 다른 테이블에서 참조하고 있는 원본 레코드는 아예 변경하거나 삭제할 수 없도록 막는다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;35,1,0&quot;&gt;CASCADE (연쇄 반영):&lt;/b&gt; 원본 테이블의 레코드를 변경하거나 삭제하면, 이를 참조하고 있는 다른 테이블의 레코드도 똑같이 자동으로 변경되거나 삭제된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;이러한 제약 조건은 서비스의 특징에 맞게 DELETE나 UPDATE 연산 시 어떻게 작동할지 미리 꼼꼼하게 설계해야 한다.&lt;/p&gt;</description>
      <category>코딩/하루 1 CS</category>
      <category>기본키</category>
      <category>데이터베이스</category>
      <category>외래키</category>
      <category>키</category>
      <author>codie0226</author>
      <guid isPermaLink="true">https://codie0226.tistory.com/16</guid>
      <comments>https://codie0226.tistory.com/16#entry16comment</comments>
      <pubDate>Sat, 13 Jun 2026 23:34:08 +0900</pubDate>
    </item>
    <item>
      <title>[하루 1 CS] 데이터베이스 1 - 기본 개념과 DBMS</title>
      <link>https://codie0226.tistory.com/15</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 데이터베이스에 대한 개념을 정리하겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터베이스란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사전적 의미로는 어렵게 생각하지 않아도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정보 데이터를 디지털화하여 만든 집합소&lt;/b&gt;라고 생각하면 된다. 개인적인 정보 저장 외에도 기업, 정부와 같은 조직 사이에서도 공유하고 관리할 수 있도록 설계한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 자체로는 보관소의 역할을 하고, 여기에 DBMS라는 소프트웨어를 얹어서 검색과 데이터 추가, 삭제와 같은 연산을 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DBMS란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataBase Management System (DBMS)는 데이터베이스에 CRUD 작업을 가능하게 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Create: 데이터베이스 생성 및 레코드 추가&lt;/li&gt;
&lt;li&gt;Read: 데이터베이스 정보 회수 및 조회&lt;/li&gt;
&lt;li&gt;Update: 데이터베이스 정보 수정 및 업데이트&lt;/li&gt;
&lt;li&gt;Delete: 데이터베이스 정보 삭제&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CRUD는 데이터베이스를 다루는 작업을 할 때 (백엔드 서버 또는 모니터링 시스템) 필수적인 요소이다. 따라서 각 DBMS 별로 특징과 활용도를 알아두면 적재적소에 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DBMS에도 여러 종류가 있고, 크게 두 갈래로 나눌 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;RDBMS&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDBMS는 Relational DBMS이다. 정보를 테이블로 분류하고, 각 테이블마다 정보의 요소(Attribute)를 나타내는 컬럼(Column)과 하나의 데이터를 나타내는 단위인 레코드(Record. 또는 튜플(Tuple)이라고 한다.)로 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블에는 각 레코드를 고유하게 표현하는&amp;nbsp;&lt;b&gt;대표키(Primary Key)&lt;/b&gt;가 있으므로 중복된 데이터를 구별할 수 있고, 다른 테이블과 연관 관계를 가진 요소를 표현하는 &lt;b&gt;외래키(Foreign Key)&lt;/b&gt;로 다른 테이블과의 관계도 명시할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 나타나는 RDBMS의 가장 큰 특징은 바로 &lt;b&gt;데이터 무결성 (Data Integrity)&lt;/b&gt;이다. 데이터를 고유한 키로 구분하고, 다른 테이블과의 연관 관계에서 &lt;b&gt;제약 조건(Constraint)&lt;/b&gt;을 걸어서 의도하지 않은 데이터 삭제 및 변조를 예방할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RDBMS에는 MySQL, PostgreSQL, MariaDB, AWS Aurora DB, Oracle DB 등 다양한 종류가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 RDBMS 프로그램에서는 데이터의 CRUD 연산과 데이터베이스 테이블 생성 및 수정, 데이터베이스 유저 관리 등을 수행할 수 있는 &lt;b&gt;SQL(Structured Query Language)&lt;/b&gt;이라는 프로그래밍 언어를 사용한다. 여기에서 SQL까지 다루게 되면 글이 너무 길어지니, 지금은 SQL이라는 언어만 소개하고 넘어가겠다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;NoSQL&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NoSQL 데이터베이스는 말 그대로 SQL을 사용하지 않는 데이터베이스이다. 이러한 NoSQL 데이터베이스는 종류별로 데이터를 보관하는 방식이 다르고, 접근하는 방식이 다르기 때문에 사용하는 데이터베이스마다 개별로 사용법을 알아두어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NoSQL에는 대표적으로 MongoDB, Redis, AWS Dynamo DB가 있고 이번에는 이 중에 제일 잘 알려진 MongoDB를 기준으로 간단하게 소개해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB에서는 테이블 대신 Document와 Document의 집합인 Collection으로 데이터를 보관한다. Document는 MongoDB에서 부여하는 고유한 ID값으로 구분할 수 있고, Document 내부에는 JSON 데이터 형식으로 자유롭게 정보를 저장할 수 있다. Collection은 이러한 Document의 집합, 즉 테이블의 역할을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NoSQL 데이터베이스의 특징으로는 &lt;b&gt;빠른 연산 성능&lt;/b&gt;이다. 데이터 간의 복잡한 관계 연산이나 무결성 제약 조건을 포기한 대가로 RDBMS와 비교하여 굉장히 빠른 조회 및 삽입 연산이 가능하다. 하지만 데이터 무결성이 보장되지 않고, 데이터가 쉽게 수정될 수 있기 때문에 안정적이고 오류에 민감한 서비스에는 사용하기 어렵다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어떤 DBMS를 사용해야 할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 이렇게 두가지 DBMS에 대해 알아보았다. 두 특징을 한 번 마무리 정리해보자면&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 80px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20.8914%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 41.2403%; height: 17px;&quot;&gt;RDBMS&lt;/td&gt;
&lt;td style=&quot;width: 37.8682%; height: 17px;&quot;&gt;NoSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20.8914%; height: 21px;&quot;&gt;데이터 구조&lt;/td&gt;
&lt;td style=&quot;width: 41.2403%; height: 21px;&quot;&gt;엄격한 스키마, 테이블 구조&lt;/td&gt;
&lt;td style=&quot;width: 37.8682%; height: 21px;&quot;&gt;유연한 구조 (JSON/Document 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20.8914%; height: 21px;&quot;&gt;장점&lt;/td&gt;
&lt;td style=&quot;width: 41.2403%; height: 21px;&quot;&gt;데이터 무결성, 제약 조건&lt;/td&gt;
&lt;td style=&quot;width: 37.8682%; height: 21px;&quot;&gt;높은 확장성, 빠른 읽기/쓰기 성능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 20.8914%; height: 21px;&quot;&gt;추천 서비스&lt;/td&gt;
&lt;td style=&quot;width: 41.2403%; height: 21px;&quot;&gt;금융, 결제, 회원 관리 등 오류가 없어야 하는 서비스&lt;/td&gt;
&lt;td style=&quot;width: 37.8682%; height: 21px;&quot;&gt;SNS, 실시간 채팅/로그, 빅데이터 등 대용량 연산이 필요한 서비스&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 DBMS를 사용해야하는지에 대한 정답은 없다. 서비스에 완벽하게 들어맞는 데이터베이스를 구축할 수는 없기 때문이다. 따라서 DBMS의 특징을 잘 알아두고, 내가 관리하는 데이터의 특성에 따라 데이터베이스를 선택하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 하나의 DBMS를 사용해야하는 법도 없다. 회원관리는 RDBMS로, 실시간 채팅 및 로그 관리는 NoSQL 데이터베이스로 사용하는 유연한 아키텍처도 고민해볼 법 하다.&lt;/p&gt;</description>
      <category>코딩/하루 1 CS</category>
      <category>DBMS</category>
      <category>MongoDB</category>
      <category>mysql</category>
      <category>데이터베이스</category>
      <author>codie0226</author>
      <guid isPermaLink="true">https://codie0226.tistory.com/15</guid>
      <comments>https://codie0226.tistory.com/15#entry15comment</comments>
      <pubDate>Fri, 12 Jun 2026 16:36:10 +0900</pubDate>
    </item>
    <item>
      <title>[하루 1 CS] 시작</title>
      <link>https://codie0226.tistory.com/14</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이제 컴퓨터공학과 4학년으로서 전공 수업도 들었고, 많은 CS 개념을 배우게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 사람은 망각의 동물인지라... 어딘가에 기록하지 않거나 복습하지 않으면 자연스럽게 까먹을 수 밖에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 더 늦기 전에 오늘부터 짧더라도 하루에 CS 전공 지식을 하나씩 기록해보겠다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1047&quot; data-origin-height=&quot;705&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/suAHL/dJMcajoVHZu/bAxqBsCql29ceiaNqpFRF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/suAHL/dJMcajoVHZu/bAxqBsCql29ceiaNqpFRF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/suAHL/dJMcajoVHZu/bAxqBsCql29ceiaNqpFRF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsuAHL%2FdJMcajoVHZu%2FbAxqBsCql29ceiaNqpFRF0%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;1047&quot; height=&quot;705&quot; data-origin-width=&quot;1047&quot; data-origin-height=&quot;705&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 바로 시작&lt;/p&gt;</description>
      <category>코딩/하루 1 CS</category>
      <category>CS</category>
      <category>컴공</category>
      <category>컴퓨터공학과</category>
      <author>codie0226</author>
      <guid isPermaLink="true">https://codie0226.tistory.com/14</guid>
      <comments>https://codie0226.tistory.com/14#entry14comment</comments>
      <pubDate>Thu, 11 Jun 2026 21:59:38 +0900</pubDate>
    </item>
    <item>
      <title>[탄소중립 아카데미 6기] API 최적화하기</title>
      <link>https://codie0226.tistory.com/13</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EcoCache 프로젝트에 &lt;code&gt;/chat&lt;/code&gt; API를 붙이면서 탄소 배출량 모니터링 기능도 함께 통합했다. 매 요청마다 현재 탄소 집약도(Carbon Intensity, CI)를 가져와서 응답에 포함시키는 구조였는데, 어느 순간부터 API 응답이 굉장히 느려지기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 LLM 응답 생성이 느린 건가 싶었다. 근데 실제로 &lt;code&gt;timings&lt;/code&gt; 필드를 찍어보니 이상한 곳에서 5초가 넘게 잡히고 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qv2vj/dJMcadh1nLv/WAl0nLAtqmgx74HTKMkK21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qv2vj/dJMcadh1nLv/WAl0nLAtqmgx74HTKMkK21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qv2vj/dJMcadh1nLv/WAl0nLAtqmgx74HTKMkK21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqv2vj%2FdJMcadh1nLv%2FWAl0nLAtqmgx74HTKMkK21%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;1280&quot; height=&quot;184&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;병목 1: Carbon API 호출&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 문제는 &lt;code&gt;_get_current_ci()&lt;/code&gt; 함수였다. 매 &lt;code&gt;/chat&lt;/code&gt; 요청마다 Electricity Maps 외부 API를 직접 호출하고 있었다. 네트워크 레이턴시가 합쳐지면서 운이 나쁘면 몇 초씩 걸렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 바꿨다. &lt;code&gt;carbon/collector.py&lt;/code&gt;가 15분마다 CI 값을 PostgreSQL에 저장하도록 이미 되어 있었으니, &lt;code&gt;/chat&lt;/code&gt; API는 그 DB에서 최신 값을 읽기만 하면 됐다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;# 변경 전: 매 요청마다 외부 API 직접 호출
def _get_current_ci() -&amp;gt; float | None:
    try:
        from carbon_optimizer import get_optimizer
        return get_optimizer().get_current_ci()
    except Exception:
        return None

# 변경 후: DB에서 먼저 읽고, 없으면 fallback
# (get_latest_ci_from_db는 동기 호출 &amp;mdash; 조회 시간이 짧아 실용적으로 문제 없음)
async def _get_current_ci() -&amp;gt; float | None:
    ci = get_latest_ci_from_db()
    if ci is not None:
        return ci
    try:
        return get_optimizer().get_current_ci()
    except Exception:
        return None&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 원칙을 평가 스크립트(&lt;code&gt;run_eval.py&lt;/code&gt;)에도 적용했다. CIASC 임계값을 계산하려고 평가 루프 안에서 매 쿼리마다 &lt;code&gt;apply_mode(&quot;ciasc&quot;)&lt;/code&gt;를 다시 호출하고 있었는데, 이건 루프 시작 전에 한 번만 계산하면 되는 값이었다. optimizer에 5분 TTL 캐시가 있어서 25번 모두 실제 HTTP 요청이 나가는 건 아니지만, 불필요한 임계값 재계산(및 잠재적 API 호출)이 25번 발생하는 구조였다. 해결책은 단순했다 &amp;mdash; 루프 밖에서 한 번만 계산하고 끝.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;병목 2: EmissionsTracker 초기화 오버헤드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 호출을 없앤 다음에도 여전히 첫 요청이 5초 넘게 걸렸다. &lt;code&gt;timings&lt;/code&gt;를 다시 뜯어보니 이번엔 carbon 모니터링 자체가 느렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 &lt;code&gt;EmissionsTracker&lt;/code&gt; 인스턴스를 &lt;b&gt;매 요청마다 새로 만들고 있었다&lt;/b&gt;는 거다. CodeCarbon 라이브러리의 &lt;code&gt;EmissionsTracker&lt;/code&gt;는 초기화할 때 GPU/CPU 하드웨어 탐지를 수행한다. 이게 생각보다 비싸서 매번 ~5초씩 잡아먹고 있었다.&lt;/p&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;# 변경 전: track() 호출할 때마다 새 인스턴스 생성
# @contextmanager
def track(self, stage_name, ...):
    tracker = self._build_tracker()  # 매번 하드웨어 탐지
    tracker.start()
    ...

# 변경 후: __init__에서 한 번만 만들고 재사용
def __init__(self, ...):
    ...
    self._tracker = self._build_tracker()  # 딱 한 번

# @contextmanager
def track(self, stage_name, ...):
    tracker = self._tracker  # 캐시된 인스턴스 사용
    tracker.start()
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;allow_multiple_runs=True&lt;/code&gt; 옵션이 이미 설정되어 있어서 같은 인스턴스를 반복 사용하는 건 이미 고려된 구조였다. 그냥 초기화를 밖으로 빼주기만 하면 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과: 캐시 히트 케이스 기준 &lt;b&gt;5100ms &amp;rarr; 610ms, 8.4배 개선&lt;/b&gt;.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;397&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TsiBQ/dJMcacQTBP6/Do529x8twwgo414tKTvMMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TsiBQ/dJMcacQTBP6/Do529x8twwgo414tKTvMMk/img.png&quot; data-alt=&quot;Before&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TsiBQ/dJMcacQTBP6/Do529x8twwgo414tKTvMMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTsiBQ%2FdJMcacQTBP6%2FDo529x8twwgo414tKTvMMk%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;397&quot; height=&quot;285&quot; data-origin-width=&quot;397&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Before&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;421&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXuvOS/dJMcaf09qaW/I1L7nNCTSJipEMElbLm0r1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXuvOS/dJMcaf09qaW/I1L7nNCTSJipEMElbLm0r1/img.png&quot; data-alt=&quot;After&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXuvOS/dJMcaf09qaW/I1L7nNCTSJipEMElbLm0r1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXuvOS%2FdJMcaf09qaW%2FI1L7nNCTSJipEMElbLm0r1%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;408&quot; height=&quot;290&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;421&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;After&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;남은 과제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 어느정도의 채팅 성능이 보장된 상태이다. 남은 것은 채팅할 때 외부 LLM API를 호출하거나 더 빠르고 좋은 환경에서 서버를 실행하는 것이다. 그리고 캐싱 전략과 동적으로 threshold 값을 바꿀 수 있게 하는 핵심 로직을 구현하면 된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;API 바꾸기&lt;/li&gt;
&lt;li&gt;캐시되지 않은 답변은 캐시로 저장하기&lt;/li&gt;
&lt;li&gt;threshold가 Carbon API 값에 따라 동적으로 변화하여 사용자 경험과 탄소 중립 목표 실현하기&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정도로 남은 과제를 요약할 수 있겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리하면서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돌아보면 두 문제 다 같은 패턴이다. 매 요청마다 안 해도 되는 것들을 매 요청마다 새로 하고 있었다. 외부 API 호출, 하드웨어 탐지 &amp;mdash; 둘 다 비싼 작업이고, 둘 다 한 번만 해서 캐시할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 병목을 처음 파악했을 때는 Carbon API 쪽만 의심했다. 그게 해결되고 나서도 여전히 느리길래 &lt;code&gt;timings&lt;/code&gt;를 좀 더 꼼꼼히 들여다봤더니 두 번째 문제가 나왔다. 측정 코드가 없었으면 진단이 훨씬 오래 걸렸을 거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;run_eval.py&lt;/code&gt;의 재호출 문제는 성능보다 정확성 문제이기도 했다. CI 값이 평가 도중에 바뀌면 같은 배치 안에서도 임계값이 달라지는 상황이 생길 수 있어서 아무래도 한 번만 계산하는 게 맞다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 인프라 쪽에서 비슷한 패턴이 또 보이면 같은 방식으로 접근할 것 같다. 측정 먼저, 가정 나중에.&lt;/p&gt;</description>
      <category>탄소중립</category>
      <category>Rag</category>
      <category>캐시</category>
      <category>탄소중립</category>
      <author>codie0226</author>
      <guid isPermaLink="true">https://codie0226.tistory.com/13</guid>
      <comments>https://codie0226.tistory.com/13#entry13comment</comments>
      <pubDate>Mon, 8 Jun 2026 19:35:52 +0900</pubDate>
    </item>
    <item>
      <title>[홈서버 구축기] 도메인 연결하기</title>
      <link>https://codie0226.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;홈서버를 기왕 만들었으면 나만의 사이트를 만들고 호스팅을 해보고 싶은 욕구가 슬슬 생기기 시작한다. 웹 개발자로서 사이드 프로젝트 배포하고 관리하면서, 이왕 해보는거 수익도 창출해보면 얼마나 좋을까? 여기까지 열정이 있는건 좋은데, 막상 건드리기에는 많은 시간과 노력이 필요하다. 이번 포스팅에서는 내가 겪었던 과정을 소개해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 도메인 연결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홈서버를 통해 사이트를 호스팅하기 위해서는 도메인 연결이 필수적이다. 도메인이 필요한 이유는 크게 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;내 홈서버의 IP를 숨기기 위함&lt;/li&gt;
&lt;li&gt;홈서버와 브라우저 간 https 통신을 구축하기 위함&lt;/li&gt;
&lt;li&gt;사람들에게 더 직관적인 도메인명으로 사이트를 알릴 수 있음&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자의 입장에서는 보안적인 이슈로 인해 도메인이 필요하고, 일반적인 유저 입장에서는 사이트를 더 찾기 쉽게 하기 위해서 도메인이 필요하다. 그렇다면 도메인의 역할은 정확하게 무엇일까?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도메인이란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크의 세상에서는 내 컴퓨터의 주소를 나타내는 일련의 숫자가 필요하다. 말 그대로 '주소'의 역할을 하며, 넓은 네트워크 세상에서 나 자신을 정의하는 단위라고 할 수 있다. 이걸 IP주소라고 하고, 서브넷 내부에서 부여된 &lt;b&gt;private(개인)&lt;/b&gt; IP와 외부 인터넷과 연결된 &lt;b&gt;public(공인)&lt;/b&gt; IP로 나눌 수 있다. 우리가 브라우저에서 사용하는 모든 인터넷 사이트들도 어딘가의 서버에서 호스팅되고 있을 것이고, 그것을 나타내는 공인 IP도 당연히 존재한다. 근데 널리 쓰이고 있는 IPv4 는 32비트짜리 1과 0의 집합일 뿐이고, 십진수로 나타내도 그냥 수수께끼의 숫자로 보일 수밖에 없다. 그래서 이것을 우리가 흔히 아는 도메인명 (google.com, naver.com 등등)으로 바꾸어주는 역할을 하는 서버가 있다. 바로 DNS(Domain Name Service)인데, 도메인명을 IP주소로 바꾸어서 우리가 브라우저(클라이언트), 또는 서버에서 보내는 요청을 올바른 네트워크 상의 위치로 바꾸어준다. 그럼 DNS를 사용하려면 어떻게 해야 할까?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도메인 제공자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히도 이러한 도메인을 제공해주는 업체들이 있다. (Domain provider) 제공업체는 인터넷에 검색하면 정말 많이 나오고, 당장 필요하다면 지금 할인 많이하는 제공업체에 찾아가면 좋다. 나는 가비아에서 도메인을 구매하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 3.51.12.png&quot; data-origin-width=&quot;2530&quot; data-origin-height=&quot;1692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMw1TQ/dJMcabRXdW8/vSRVL0k1h3rA8qpghDYaN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMw1TQ/dJMcabRXdW8/vSRVL0k1h3rA8qpghDYaN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMw1TQ/dJMcabRXdW8/vSRVL0k1h3rA8qpghDYaN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMw1TQ%2FdJMcabRXdW8%2FvSRVL0k1h3rA8qpghDYaN0%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;2530&quot; height=&quot;1692&quot; data-filename=&quot;스크린샷 2026-06-06 오후 3.51.12.png&quot; data-origin-width=&quot;2530&quot; data-origin-height=&quot;1692&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보면 주요 도메인이라고 나와있고 gTLD라고 나와있다. TLD라는 것은 &lt;b&gt;최상위 도메인&lt;/b&gt;이라는 것을 의미한다. 도메인은 단계별로 나뉘어 있는데, 크게 세 단계로 나눌 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;최상위 도메인(TLD) - .com, .kr, .us, .org 와 같은 구조. 도메인명 제일 오른쪽에 붙게 되고, 국가나 목적의 의미를 가진다. 일반 (gTLD) 도메인인 .com, .net 등이 있고, 국가 (ccTLD) 도메인인 .kr, .us 등이 존재한다.&lt;/li&gt;
&lt;li&gt;차상위 도메인(SLD) - TLD 왼쪽에 붙는 두번째 도메인 단계이다. codie.kr, .co.kr, .io.kr, .go.kr 과 같은 구조. 회사의 브랜드나 어느 기관의 종류인지 구별 가능하다(co, io, go). 만약 codie.kr과 같은 구조라면 codie가 SLD가 된다.&lt;/li&gt;
&lt;li&gt;서브 도메인(Sub Domain) - 한 도메인명에서 기능별로 구별한 것. blog.codie0226.kr, shop.codie0226.kr과 같은 모습이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가비아와 같은 도메인 제공업체에서는 주로 최상위 도메인과 차상위 도메인을 구할 수 있다. 다양한 종류의 도메인이 있으므로, 내가 구해야하는 도메인명을 직접 검색하고 알아봐야 한다. 도메인 검색에서 codie를 검색하면 다음과 같이 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2470&quot; data-origin-height=&quot;1562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/trhyJ/dJMcaiXPlA1/QpKFDMBliEKETJiT1awkH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/trhyJ/dJMcaiXPlA1/QpKFDMBliEKETJiT1awkH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/trhyJ/dJMcaiXPlA1/QpKFDMBliEKETJiT1awkH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtrhyJ%2FdJMcaiXPlA1%2FQpKFDMBliEKETJiT1awkH1%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;2470&quot; height=&quot;1562&quot; data-origin-width=&quot;2470&quot; data-origin-height=&quot;1562&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 있던 내용대로 codie라는 도메인명에 단계별로 도메인이 구분된 것을 확인할 수 있다. codie.com이란 도메인명은 이미 다른 회사나 기관에서 사용하고 있으므로 이미 등록되어 있어, 사용할 수 없다. codie.kr은 내가 이미 등록한 도메인이므로, 마찬가지로 사용할 수 없다. 비어있는 도메인 중에서 자유롭게 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한가지 주의해야 할 점은 무작정 가격이 싸다고 바로 구입해선 안된다. 1년치 등록비용인지, 1개월치 등록비용인지 &lt;b&gt;반드시 기간을 확인&lt;/b&gt;하고 구입해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 구입하였으면, 네임서버를 등록해야 한다. 여기까지 벌써 머리가 아픈데, 아직도 할 일이 많다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네임 서버 등록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 네임서버를 등록해야 한다. 지금 구입한 도메인명은 이 사이트의 이름에 대한 사용권같은 것이다. 따라서 이걸 진짜 IP주소로 변환해줄 수 있는 네임서버를 등록해야 한다. 가비아의 경우에는 기본 가비아 네임서버를 사용해도 되고, 타사 네임서버도 연동할 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;가비아 기본 네임서버를 사용하는 경우: 가비아에서 제공하는 호스팅 서비스등을 이용하는 경우.&lt;/li&gt;
&lt;li&gt;타사 네임서버를 사용하는 경우: DDNS(IP 자동 갱신), SSL 인증서 발급 등의 서비스를 제공해주는 타 업체를 사용하는 경우. (Cloudflare등)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 홈서버에 등록해야 하는 경우이므로, 기본 네임서버가 아닌 타사 네임서버를 활용해야 한다. 그렇다면 어떤 DNS 업체가 좋을까...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답은 역시 Cloudflare다. SSL 인증서 발급도 무료로 지원되고, 서브 도메인으로 서비스를 분리하는 것도 쉽고, 기본적인 캐싱과 트래픽 감지 및 디도스 방어가 가능해서 제일 무난한 선택지이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 클라우드 플레어 연결하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 플레어에 가입하고 Domain 메뉴에서 Add a domain 메뉴를 선택하면 다음과 같은 화면이 보인다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AemVO/dJMcabdk9La/g7KM1L3UvLQuZEcbd2DFZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AemVO/dJMcabdk9La/g7KM1L3UvLQuZEcbd2DFZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AemVO/dJMcabdk9La/g7KM1L3UvLQuZEcbd2DFZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAemVO%2FdJMcabdk9La%2Fg7KM1L3UvLQuZEcbd2DFZ0%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;1442&quot; height=&quot;830&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 우리의 도메인명을 넣으면 된다. 아래에 있는 설정을 열면 연결된 DNS 레코드를 자동으로 읽어오고 AI 크롤러 설정을 할 수 있다. 근데 이건 어짜피 나중에도 바꿀 수 있으므로 일단 넘어가도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 도메인을 추가하였으면 클라우드 플레어의 네임서버를 먼저 찾아주어야 한다. 클라우드 플레어는 네임서버의 역할을 할 수 있지만, 도메인명을 제공한 주체가 아니므로 도메인 제공업체에게 네임서버를 넘겨주면 된다. 도메인 추가후 config 화면에서 네임서버를 찾을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 5.13.48.png&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf1FO0/dJMcabYNEDY/A6TpDageiE5VBdkyKGxiv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf1FO0/dJMcabYNEDY/A6TpDageiE5VBdkyKGxiv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf1FO0/dJMcabYNEDY/A6TpDageiE5VBdkyKGxiv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf1FO0%2FdJMcabYNEDY%2FA6TpDageiE5VBdkyKGxiv1%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;462&quot; height=&quot;276&quot; data-filename=&quot;스크린샷 2026-06-06 오후 5.13.48.png&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Settings 버튼을 클릭하면 ns 네임서버가 2개 나온다. 이 네임서버 2개를 가비아 도메인 설정 화면에 있는 네임서버 칸에 넣으면 된다.&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;클라우드 플레어에 DNS 레코드 추가하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 DNS에 나의 도메인과 연결할 IP주소를 추가하면 된다.&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;2766&quot; data-origin-height=&quot;1078&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pcknM/dJMcahLrWMQ/L5PcntgHX5yioLk6kyqooK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pcknM/dJMcahLrWMQ/L5PcntgHX5yioLk6kyqooK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pcknM/dJMcahLrWMQ/L5PcntgHX5yioLk6kyqooK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpcknM%2FdJMcahLrWMQ%2FL5PcntgHX5yioLk6kyqooK%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;2766&quot; height=&quot;1078&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2766&quot; data-origin-height=&quot;1078&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 도메인을 추가하면 된다. 나는 원본 도메인과 내가 사용하는 서브도메인까지 올려놓은 상태이다. 여기에 Proxy status라는 설정 값이 보인다. Proxy 설정을 하면 기본적으로 SSL 인증서를 제공하고, 나의 원본 IP 주소까지 숨겨준다. 따라서 ssh 연결 목적이 아닌 일반적인 호스팅 목적으로는 proxy 설정을 반드시 해야 한다.&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 나의 도메인을 연결하는 과정을 보았다. 도메인은 나의 서버 IP를 가리는 것과 동시에 나의 서비스의 정체성이 될 수 있는 중요한 요소이다. 그만큼 초기단계에서부터 잘 설정해주면 최소한의 유지보수로 도메인 관리를 할 수 있다. 하지만 여기까지 했어도 실제 호스팅을 위해서는 아직 과정이 더 필요하다. 다음 포스팅에서는 내가 홈서버에서 다양한 웹 호스팅을 할 수 있게 만든 설정을 소개하도록 하겠다.&lt;/p&gt;</description>
      <category>백엔드/홈서버</category>
      <category>가비아</category>
      <category>도메인</category>
      <category>클라우드플레어</category>
      <category>홈서버</category>
      <author>codie0226</author>
      <guid isPermaLink="true">https://codie0226.tistory.com/12</guid>
      <comments>https://codie0226.tistory.com/12#entry12comment</comments>
      <pubDate>Sat, 6 Jun 2026 17:28:41 +0900</pubDate>
    </item>
    <item>
      <title>[에이전트] Claude로 블로그도 자동화하기</title>
      <link>https://codie0226.tistory.com/11</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 들어 블로그를 자주 쓰는 편인데, 항상 같은 문제에 부딪혔다. 주제는 있고 내용도 알고 있는데, 막상 글을 쓰기 시작하면 귀찮다. 그리고 Claude한테 대신 써달라고 하면 말투가 너무 정중하거나, 설명이 너무 형식적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 &quot;Claude가 내 말투를 파악하고 알아서 쓰면 안 되나?&quot;라는 생각으로 이어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 블로그도 한 번 Claude가 할 수 있게 하네싱 해보기로 했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Claude Code의 Skills란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code에는 skills라는 기능이 있다. 쉽게 말하면, 특정 작업을 어떻게 수행할지 Claude에게 미리 가르쳐두는 문서다. gstack 환경 기준으로 `~/.claude/skills/` 디렉토리 아래에 마크다운 파일로 저장하면, `/skill-name` 처럼 슬래시 커맨드로 호출할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 skills는 단순한 프롬프트 저장소가 아니다. 조건 분기, 서브 에이전트 생성, 파일 읽기/쓰기까지 가능하다. 그래서 꽤 복잡한 워크플로우도 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 skills 자체는 이미 많이 활용하고 있었는데, 블로그 작성 전용 스킬은 없었다. 그래서 만들었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;만드는 과정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;말투를 어떻게 학습시키냐&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 고민이 이거였다. Claude가 내 말투를 알려면, 뭔가 레퍼런스가 있어야 한다. 그래서 `~/.claude/skills/blog-writer/reference/` 폴더를 만들고, 내가 직접 쓴 블로그 글들을 거기 넣어두기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킬이 실행되면 이 폴더의 글들을 읽어서 8가지 특성을 분석한다. 어조, 문장 길이, 단어 선택, 문단 구조, 시작 방식, 마무리 방식, 감정 표현, 특징적인 표현들. 이걸 `style-analysis.md`라는 파일에 저장해둔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1117&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sKQQ2/dJMcaalcJ2n/DKtfUlBjUXimup5izHDrc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sKQQ2/dJMcaalcJ2n/DKtfUlBjUXimup5izHDrc1/img.png&quot; data-alt=&quot;style-analysis.md&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sKQQ2/dJMcaalcJ2n/DKtfUlBjUXimup5izHDrc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsKQQ2%2FdJMcaalcJ2n%2FDKtfUlBjUXimup5izHDrc1%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;1117&quot; height=&quot;846&quot; data-origin-width=&quot;1117&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;style-analysis.md&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;캐싱이 중요한 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 레퍼런스 파일을 전부 다시 읽으면 토큰 낭비다. 그래서 한 번 분석한 결과를 캐시 파일에 저장해두고, 다음에 스킬을 호출할 땐 그 파일만 읽으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 레퍼런스 파일이 바뀔 수도 있으니까, 캐시 파일에 &quot;어떤 파일들을 분석했는지&quot; 목록도 같이 저장해둔다. 현재 reference 폴더의 파일 목록이 달라졌으면 재분석, 그렇지 않으면 캐시 사용. 간단하지만 효과적이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;팩트체크 서브 에이전트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분이 가장 나중에 추가됐다. 글을 다 쓰고 나면 사실 확인이 필요한 경우가 있다. 기술적인 내용에서 개념을 잘못 설명하거나, 날짜나 수치가 틀리거나.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 초안 완성 후 새로운 서브 에이전트를 하나 더 띄워서 팩트체크를 맡긴다. 이 에이전트는 세 가지만 본다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사실 관계가 맞는지 (원인/결과, 상관관계)&lt;/li&gt;
&lt;li&gt;수치, 날짜, 이름이 정확한지&lt;/li&gt;
&lt;li&gt;개념 설명이 도메인적으로 맞는지&lt;/li&gt;
&lt;/ol&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;460&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cB1pum/dJMcacDoqKi/MplVnmBSnl9KBYKvGBvtP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cB1pum/dJMcacDoqKi/MplVnmBSnl9KBYKvGBvtP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cB1pum/dJMcacDoqKi/MplVnmBSnl9KBYKvGBvtP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcB1pum%2FdJMcacDoqKi%2FMplVnmBSnl9KBYKvGBvtP0%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;460&quot; height=&quot;466&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 동작 흐름&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킬을 `/blog-writer 주제` 형태로 호출하면 다음 순서로 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**Step 1 &amp;mdash; 스타일 분석 로드**&lt;br /&gt;`style-analysis.md`가 있으면 바로 읽고, 없으면 reference 폴더를 분석해서 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**Step 2 &amp;mdash; 블로그 브리프 수집**&lt;br /&gt;주제, 독자, 목적, 길이를 확인한다. 이미 주제를 넘겼으면 나머지만 물어본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**Step 3 &amp;mdash; 초안 작성**&lt;br /&gt;분석된 말투 지문대로 글을 쓴다. 이미지가 들어가면 좋을 위치에는 `  이미지 제안` 블록을 인라인으로 삽입한다. 이 단계에서는 파일을 저장하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**Step 4 &amp;mdash; 팩트체크**&lt;br /&gt;서브 에이전트가 초안을 검토하고 오류, 불확실한 내용을 보고한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**Step 5 &amp;mdash; 유저 승인 후 저장**&lt;br /&gt;팩트체크 결과를 보여주고, 어떤 수정을 반영할지 확인받은 뒤 `.md` 파일로 저장한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Upjhx/dJMcaaZPi7t/wNPADPUcXddO6sLjTYtYd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Upjhx/dJMcaaZPi7t/wNPADPUcXddO6sLjTYtYd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Upjhx/dJMcaaZPi7t/wNPADPUcXddO6sLjTYtYd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUpjhx%2FdJMcaaZPi7t%2FwNPADPUcXddO6sLjTYtYd1%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;450&quot; height=&quot;528&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;써보니까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굉장히 만족스럽다. 말투가 일치하는 건 물론이고, 이미지 위치 제안이 생각보다 유용하다. 직접 글 쓸 때는 어디에 이미지를 넣을지 고민을 많이 하는데, 알아서 표시해줘서 편하다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 레퍼런스 파일을 조금 더 다양하게 넣어서 말투를 더 정확하게 잡아가야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/codie0226/blog-writer&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/codie0226/blog-writer&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780594106002&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - codie0226/blog-writer&quot; data-og-description=&quot;Contribute to codie0226/blog-writer development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/codie0226/blog-writer&quot; data-og-url=&quot;https://github.com/codie0226/blog-writer&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/IX7TG/dJMb8UHWLk0/rLIbEkN3m55Hl8BkyMiv9K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/dJplHs/dJMb8YXTcRS/Xy6XNUk5CNeyOdlo7GfUa0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/codie0226/blog-writer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/codie0226/blog-writer&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/IX7TG/dJMb8UHWLk0/rLIbEkN3m55Hl8BkyMiv9K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/dJplHs/dJMb8YXTcRS/Xy6XNUk5CNeyOdlo7GfUa0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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 - codie0226/blog-writer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to codie0226/blog-writer development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.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;</description>
      <category>코딩</category>
      <category>claude code</category>
      <category>Claude Skills</category>
      <category>블로그 작성</category>
      <author>codie0226</author>
      <guid isPermaLink="true">https://codie0226.tistory.com/11</guid>
      <comments>https://codie0226.tistory.com/11#entry11comment</comments>
      <pubDate>Fri, 5 Jun 2026 02:29:05 +0900</pubDate>
    </item>
    <item>
      <title>[홈서버 구축기] 홈서버 모니터링 시스템 만들어보기</title>
      <link>https://codie0226.tistory.com/10</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;여러 프로젝트들을 직접 경험해보면서 홈서버를 자연스럽게 많이 사용하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다 보면 홈서버에 자주 접속하면서 하드웨어 상황과 도커 컨테이너의 헬스 체크를 하기 어렵다. 밖에서는 PC 없이 세부적인 모니터링이 힘들고, PC로 점검을 해도 대시보드 없이는 한 눈에 하드웨어 상황을 실시간으로 알 수 없다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;오픈 소스로 해결&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그를 위한 해결책으로 여러 서버 모니터링 프로그램들이 있다. Grafana, Zabbix 등의 여러 오픈소스 서버 모니터링 시스템들이 있지만, 역시 모니터링 계의 강자인 Grafana 또는 Zabbix를 사용할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 전에 둘 간의 차이와 특징을 알아둘 필요가 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 47px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 17.0542%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 41.9379%; height: 17px;&quot;&gt;Zabbix&lt;/td&gt;
&lt;td style=&quot;width: 41.0078%; height: 17px;&quot;&gt;Grafana&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 10px;&quot;&gt;
&lt;td style=&quot;width: 17.0542%; height: 10px;&quot;&gt;주요 역할&lt;/td&gt;
&lt;td style=&quot;width: 41.9379%; height: 10px;&quot;&gt;Full Stack 모니터링(데이터 수집, 저장, 알림, 분석)&lt;/td&gt;
&lt;td style=&quot;width: 41.0078%; height: 10px;&quot;&gt;데이터 시각화, 대시보드 플랫폼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 10px;&quot;&gt;
&lt;td style=&quot;width: 17.0542%; height: 10px;&quot;&gt;데이터 수집&lt;/td&gt;
&lt;td style=&quot;width: 41.9379%; height: 10px;&quot;&gt;자체 에이전트를 통한 수집&lt;/td&gt;
&lt;td style=&quot;width: 41.0078%; height: 10px;&quot;&gt;외부 데이터 소스 연동 필수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 10px;&quot;&gt;
&lt;td style=&quot;width: 17.0542%; height: 10px;&quot;&gt;UI&lt;/td&gt;
&lt;td style=&quot;width: 41.9379%; height: 10px;&quot;&gt;투박하고 전통적인 UI&lt;/td&gt;
&lt;td style=&quot;width: 41.0078%; height: 10px;&quot;&gt;현대적이고 직관적인 대시보드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.0542%;&quot;&gt;강점&lt;/td&gt;
&lt;td style=&quot;width: 41.9379%;&quot;&gt;레거시 인프라, 네트워크 장비, 대규모 서버 감시&lt;/td&gt;
&lt;td style=&quot;width: 41.0078%;&quot;&gt;클라우드 네이티브, 시계열 대시보드, MSA&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 나는 Grafana를 선택했다. 아무래도 구현하기 더 간편하기도 하고, Prometheus와 Loki를 연동하여 MSA 기반 아키텍처에서 확실한 강점을 보여주기 때문이다. 또한 더 많은 데이터소스들을 통해서 확장하기도 편하다.&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;Grafana 구축 준비하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana는 별도의 소스코드 없이 도커 컨테이너로 띄울 수 있다. 기본적으로 제공해주는 Web UI로도 가볍게 모니터링 대시보드 구축을 할 수 있다. 하지만 Grafana 혼자서는 디바이스의 리소스를 모아올 수 없다. 따라서 외부 데이터 소스를 연동하여 내부적인 데이터를 읽어와야 한다.&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;Prometheus, Loki, cAdvisor, Node Exporter&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 데이터소스와, 하드웨어 정보를 긁어오는 프로그램들이 필요하다. 크게 다음과 같은 세가지 프로그램을 많이 사용한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Cadvisor&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너의 리소스 데이터를 수집하는 역할이다. 컨테이너 자체 대몬이 하드웨어 자원을 얼마나 사용하고 있는지 데이터를 추출한다. 쉽게 말해 각 컨테이너의 CPU, 메모리, 네트워크 트래픽, 디스크 I/O 등 하드웨어 사용량을 스캔하는 역할이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cAdvisor는 숫자 데이터 자체를 추출할 수 있지만, 이 데이터를 저장하고 보여주는 기능은 없다. 따라서 cAdvisor 위에 다른 툴을 얹어서 사용해야 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Node Exporter&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너가 아닌 전체 시스템의 메트릭을 제공한다. 전체 시스템의 CPU 사용량, 코어별 사용량, 메모리 사용량, 디스크 사용량, 네트워크 대역폭 등에 대한 정보를 제공한다. cAdvisor와 마찬가지로 데이터를 저장하고 보여주는 기능은 없고, 다른 툴을 얹어서 사용해야 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. Prometheus&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus가 바로 이 역할을 수행한다. Prometheus는 시계열 메트릭 데이터 수집 및 저장을 담당한다. cAdvisor에서 수집한 숫자 데이터를 긁어와서 시계열 데이터베이스에 저장하고, 특정 조건에 도달하면 알람을 울릴 수 있는 기능까지 수행해준다. 종합적인 시스템 모니터링 및 헬스 체크 용도라고 할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4 Loki&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 Loki가 필요하다. Loki는 쉽게 말해서 로그 수집 저장소이다. 컨테이너나 특정 어플리케이션 내부에서 출력되는 모든 텍스트 로그를 모아서 관리한다. 보통 서버 프로그램은 백그라운드에서 실행되고, 컨테이너 내부에서 실행되므로 로그를 확인하기 위해서는 꽤 번거로운 과정이 있어야 하지만, Loki는 여러 어플리케이션의 로그를 자동으로 수집한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 Grafana에 데이터소스로 연동해야 하는 것은 Prometheus와 Loki이다. cAdvisor는 데이터소스가 아닌 리소스 메트릭을 수치로 수집하는 역할만을 수행한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Docker로 모든 과정 준비하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Docker로 모든 과정을 한번에 끝낼 수 있다. docker-compose.yml 파일을 통해서 Grafana, cAdvisor, Prometheus, Loki 컨테이너를 전부 생성하고 연동하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;Grafana Compose&quot; href=&quot;https://github.com/codie0226/GrafanaCompose&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/codie0226/GrafanaCompose&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780302562375&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - codie0226/GrafanaCompose: 쉬운 Grafana 대시보드 셋업입니다.&quot; data-og-description=&quot;쉬운 Grafana 대시보드 셋업입니다. Contribute to codie0226/GrafanaCompose development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/codie0226/GrafanaCompose&quot; data-og-url=&quot;https://github.com/codie0226/GrafanaCompose&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/llDMC/dJMb9kT90Iw/jblA9tdMQXkjbbJkgDacLk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/Hqx8x/dJMb87gdC8S/TXjzHKSDIhAd7HP1UoczH0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/codie0226/GrafanaCompose&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/codie0226/GrafanaCompose&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/llDMC/dJMb9kT90Iw/jblA9tdMQXkjbbJkgDacLk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/Hqx8x/dJMb87gdC8S/TXjzHKSDIhAd7HP1UoczH0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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 - codie0226/GrafanaCompose: 쉬운 Grafana 대시보드 셋업입니다.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;쉬운 Grafana 대시보드 셋업입니다. Contribute to codie0226/GrafanaCompose development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker compose up -d 명령어로 컨테이너를 띄우고 접속하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인 서버 ip와 Grafana 포트로 접속하면 Web UI를 볼 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대시보드 커스텀하기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1911&quot; data-origin-height=&quot;843&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5nujy/dJMcaiQ2SjG/Nn0MKlmi15BSxW4DancN4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5nujy/dJMcaiQ2SjG/Nn0MKlmi15BSxW4DancN4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5nujy/dJMcaiQ2SjG/Nn0MKlmi15BSxW4DancN4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5nujy%2FdJMcaiQ2SjG%2FNn0MKlmi15BSxW4DancN4K%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;1911&quot; height=&quot;843&quot; data-origin-width=&quot;1911&quot; data-origin-height=&quot;843&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 접속하면 이렇게 화면이 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 로그인 유저네임과 비밀번호는 둘 다 admin이다. 따라서 첫 로그인 이후 반드시 비밀번호를 변경해두어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;881&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wLn5e/dJMcadhWsCs/ACASFWf9WukejFzmVjbmI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wLn5e/dJMcadhWsCs/ACASFWf9WukejFzmVjbmI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wLn5e/dJMcadhWsCs/ACASFWf9WukejFzmVjbmI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwLn5e%2FdJMcadhWsCs%2FACASFWf9WukejFzmVjbmI1%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;1914&quot; height=&quot;881&quot; data-origin-width=&quot;1914&quot; data-origin-height=&quot;881&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 데이터소스를 먼저 추가해주어야 한다. 파란 Add new data source 버튼을 누른다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1575&quot; data-origin-height=&quot;887&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXM0xJ/dJMcadITNOD/84ZSIIMQjKKczwhu8wMm11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXM0xJ/dJMcadITNOD/84ZSIIMQjKKczwhu8wMm11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXM0xJ/dJMcadITNOD/84ZSIIMQjKKczwhu8wMm11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXM0xJ%2FdJMcadITNOD%2F84ZSIIMQjKKczwhu8wMm11%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;1575&quot; height=&quot;887&quot; data-origin-width=&quot;1575&quot; data-origin-height=&quot;887&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana에서 지원하는 여러 데이터 소스의 목록이 나온다. 현재 Prometheus와 Loki를 사용할 것이므로 둘 모두 추가해주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;675&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Vbu1h/dJMcajoOwMO/QuK68yfrNQSKPWwfkq4ZFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Vbu1h/dJMcajoOwMO/QuK68yfrNQSKPWwfkq4ZFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Vbu1h/dJMcajoOwMO/QuK68yfrNQSKPWwfkq4ZFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVbu1h%2FdJMcajoOwMO%2FQuK68yfrNQSKPWwfkq4ZFK%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;716&quot; height=&quot;675&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;675&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의해야 할 점은 뇌빼고 localhost로 주소를 적으면 안된다. 현재 docker compose의 network로 컨테이너들이 연결되어 있으므로 docker compose의 service명으로 주소를 적어주면 된다.&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;1910&quot; data-origin-height=&quot;847&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be1FFG/dJMcajoOwGM/pn9gnuA3wpXkJTCbAgALk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be1FFG/dJMcajoOwGM/pn9gnuA3wpXkJTCbAgALk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be1FFG/dJMcajoOwGM/pn9gnuA3wpXkJTCbAgALk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe1FFG%2FdJMcajoOwGM%2Fpn9gnuA3wpXkJTCbAgALk0%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;1910&quot; height=&quot;847&quot; data-origin-width=&quot;1910&quot; data-origin-height=&quot;847&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터소스를 추가했으면 이제 본격적으로 대시보드를 만들면 된다. 메뉴바에 보면 Dashboard 버튼이 있다. 클릭하면 대시보드 목록이 출력된다. 오른쪽 위에 있는 New 버튼을 누르면, New dashboard, New folder, Import가 있다. New dashboard는 아예 비어있는 대시보드부터 만들 수 있고, Import 하면 Grafana 라이브러리나 다른 사람이 만들어둔 대시보드를 그대로 옮겨올 수 있다. 내가 사용한 라이브러리 대시보드는 Cadvisor Exporter와 Node Exporter full이다. 다른 라이브러리 대시보드는 아래에서 더 찾아볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://grafana.com/grafana/dashboards/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://grafana.com/grafana/dashboards/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780303370209&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;Grafana dashboards | Grafana Labs&quot; data-og-description=&quot;No results found. Please clear one or more filters.&quot; data-og-host=&quot;grafana.com&quot; data-og-source-url=&quot;https://grafana.com/grafana/dashboards/&quot; data-og-url=&quot;https://grafana.com/grafana/dashboards/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zcbut/dJMb87gdDjS/vTtfjA19XMIxagaihnaPwK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/E5NWg/dJMb88Gb7f8/8eKQqEPK7AZmYnwxK36jqk/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://grafana.com/grafana/dashboards/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://grafana.com/grafana/dashboards/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zcbut/dJMb87gdDjS/vTtfjA19XMIxagaihnaPwK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/E5NWg/dJMb88Gb7f8/8eKQqEPK7AZmYnwxK36jqk/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&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;Grafana dashboards | Grafana Labs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;No results found. Please clear one or more filters.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;grafana.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;1531&quot; data-origin-height=&quot;799&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CZ9XV/dJMcagyVCWB/FfZytUDCX0RWn0F61RMJs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CZ9XV/dJMcagyVCWB/FfZytUDCX0RWn0F61RMJs0/img.png&quot; data-alt=&quot;Node Exporter Full&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CZ9XV/dJMcagyVCWB/FfZytUDCX0RWn0F61RMJs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCZ9XV%2FdJMcagyVCWB%2FFfZytUDCX0RWn0F61RMJs0%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;1531&quot; height=&quot;799&quot; data-origin-width=&quot;1531&quot; data-origin-height=&quot;799&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Node Exporter Full&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;897&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxOzPx/dJMcahYUnG4/fyrhEprGUbaxOsXzr3vUo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxOzPx/dJMcahYUnG4/fyrhEprGUbaxOsXzr3vUo1/img.png&quot; data-alt=&quot;Cadvisor Exporter&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxOzPx/dJMcahYUnG4/fyrhEprGUbaxOsXzr3vUo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxOzPx%2FdJMcahYUnG4%2FfyrhEprGUbaxOsXzr3vUo1%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;1512&quot; height=&quot;897&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;897&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Cadvisor Exporter&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 두 개의 대시보드를 뚝딱 만들었다. Loki 대시보드도 이런 곳에 자유롭게 추가해줄 수 있다.&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;596&quot; data-origin-height=&quot;798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V5A7K/dJMcacQPcwE/PeZbdexrlkE3BhDFEkXvok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V5A7K/dJMcacQPcwE/PeZbdexrlkE3BhDFEkXvok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V5A7K/dJMcacQPcwE/PeZbdexrlkE3BhDFEkXvok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV5A7K%2FdJMcacQPcwE%2FPeZbdexrlkE3BhDFEkXvok%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;596&quot; height=&quot;798&quot; data-origin-width=&quot;596&quot; data-origin-height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너에서 원하는 것을 선택하고, Operation에서 line filter, label 등을 추가해 나에게 필요한 정보만 수집할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Grafana는 오픈 소스로 널리 알려진 대시보드이기도 하고, 다양하게 커스텀하여 사용할 수 있어서 홈서버 관리부터 기업 인프라 관리까지 여러 곳에서 모니터링을 쉽게 할 수 있다. 미리 대시보드 구축하는 것을 연습해보면 좋을 것 같다. 다음에는 Grafana의 알림 기능을 소개해보겠다.&lt;/p&gt;</description>
      <category>백엔드/홈서버</category>
      <category>grafana</category>
      <category>LOKI</category>
      <category>Prometheus</category>
      <category>서버 모니터링</category>
      <category>홈서버</category>
      <author>codie0226</author>
      <guid isPermaLink="true">https://codie0226.tistory.com/10</guid>
      <comments>https://codie0226.tistory.com/10#entry10comment</comments>
      <pubDate>Mon, 1 Jun 2026 17:48:39 +0900</pubDate>
    </item>
    <item>
      <title>[탄소중립 아카데미 6기] API 구현 및 연동</title>
      <link>https://codie0226.tistory.com/9</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;지난 이야기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 임베딩과 llm 응답 생성까지는 구현이 되었다. 실제 웹 화면과 연동하여 사용할 수 있는지가 중요하기 때문에.. 이번에는 웹페이지에 띄워보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;웹페이지 수정하기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;431&quot; data-origin-height=&quot;629&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biVYMv/dJMcabxxeef/RAtOt356csIToUmuTH2xU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biVYMv/dJMcabxxeef/RAtOt356csIToUmuTH2xU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biVYMv/dJMcabxxeef/RAtOt356csIToUmuTH2xU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiVYMv%2FdJMcabxxeef%2FRAtOt356csIToUmuTH2xU0%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;431&quot; height=&quot;629&quot; data-origin-width=&quot;431&quot; data-origin-height=&quot;629&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 챗봇의 초기 버전이다. 팀원분께서 나름 주제와 잘 어울리는 테마로 이쁘게 만들어주셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 페이지를 받고 나서, 이걸 서버와 잘 연동을 해야 한다. 그 전에 코드를 뜯어서 API 스키마를 동일하게 설정하여주고, CORS 설정까지 해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779708731921&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
	&quot;success&quot;: true,
	&quot;error&quot;: null,
	&quot;result&quot;: {
		&quot;response&quot;: &quot;...&quot;,
		&quot;similarity&quot;: 0.6111,
		&quot;cache_hit&quot;: true,
		&quot;latency&quot;: 2000,
		&quot;co2_grams&quot;: 1.36,
		&quot;ci_g_per_kwh&quot;: 400,
		&quot;sources&quot;: [doc_id...]
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번에 정했던 통합 스키마로 채팅을 출력되게 하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쨌든 API를 연동하고 나면 채팅을 실제로 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;627&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjWtZ9/dJMcaaFsAvo/vF0jPR03wpXKgdj4ycGTek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjWtZ9/dJMcaaFsAvo/vF0jPR03wpXKgdj4ycGTek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjWtZ9/dJMcaaFsAvo/vF0jPR03wpXKgdj4ycGTek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjWtZ9%2FdJMcaaFsAvo%2FvF0jPR03wpXKgdj4ycGTek%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;435&quot; height=&quot;627&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;627&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;다만 아래에 보면 응답하는데 14.82초가 걸린다. 로컬 모델을 가져다 쓰고 있긴 하지만, 그렇다고 해도 너무 오래 걸린다.&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;1362&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btboX9/dJMcahq2jw7/bmI9lOhAf88J5VsmVift91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btboX9/dJMcahq2jw7/bmI9lOhAf88J5VsmVift91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btboX9/dJMcahq2jw7/bmI9lOhAf88J5VsmVift91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtboX9%2FdJMcahq2jw7%2FbmI9lOhAf88J5VsmVift91%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;1362&quot; height=&quot;375&quot; data-origin-width=&quot;1362&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LM 스튜디오에서 찍어본 로그이다. 보면 추론하는데 6.22초가 걸렸고, total time으로 7319ms가 걸린 것을 확인할 수 있다. 이걸 제외하고도 거의 5초이상 다른 곳에서 시간을 잡아먹고 있다. 어디인지 보고 해결해야 한다. 쿼리 과정이 꽤나 복잡하기 때문에, 각 과정 별로 로그를 출력하고 소비된 시간을 측정해보기로 하였다.&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;1614&quot; data-origin-height=&quot;233&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZgHXn/dJMcajh1Zxv/cCbVPKHbWrG3T5x6yJfSlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZgHXn/dJMcajh1Zxv/cCbVPKHbWrG3T5x6yJfSlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZgHXn/dJMcajh1Zxv/cCbVPKHbWrG3T5x6yJfSlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZgHXn%2FdJMcajh1Zxv%2FcCbVPKHbWrG3T5x6yJfSlk%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;1614&quot; height=&quot;233&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1614&quot; data-origin-height=&quot;233&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를 확인해보니, 시간이 크게 걸리는 곳은 두 부분이었다. 첫 번째는 탄소 집약도 API 호출이었고, 하나는 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;&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;403&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRSUoF/dJMcaak5Snv/QIT6KKBl26cvHEPls2ftVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRSUoF/dJMcaak5Snv/QIT6KKBl26cvHEPls2ftVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRSUoF/dJMcaak5Snv/QIT6KKBl26cvHEPls2ftVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRSUoF%2FdJMcaak5Snv%2FQIT6KKBl26cvHEPls2ftVk%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;403&quot; height=&quot;286&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;qa pair에 등록되어 있는 것과 정확하게 같은 프롬프트로 테스트를 해보았다. 당연히 캐시 히트가 이루어졌고, 답변도 qa pair에 등록되어 있는 것과 같은 답변이 출력되었다. 바람직한 답변이지만, 아직도 5초 이상이 걸리고 있다. 왜인지 살펴보니...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 히트가 되어서, document fallback이 이루어지지 않았어도, LM 스튜디오에 계속 요청을 날리고 있었다. qa pair에 등록된 질문은 이미 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;h2 data-ke-size=&quot;size26&quot;&gt;캐시 활용해보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시된 답변을 사용하는거면, LLM을 거칠 필요가 없다. LLM을 거치지 않는다면, 답변 생성하는데 걸리는 5초에서 7초의 시간을 아낄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 qa_pair 검색 -&amp;gt; cache hit -&amp;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;397&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf4hWr/dJMcafUhCJW/NPRWIpD6X5N2LBCB2FSPP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf4hWr/dJMcafUhCJW/NPRWIpD6X5N2LBCB2FSPP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf4hWr/dJMcafUhCJW/NPRWIpD6X5N2LBCB2FSPP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf4hWr%2FdJMcafUhCJW%2FNPRWIpD6X5N2LBCB2FSPP0%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;397&quot; height=&quot;285&quot; data-origin-width=&quot;397&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;15초에서 5초 가량으로 시간이 줄어들었다! 역시 LLM 답변을 생성하는 시간이 압도적으로 긴 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;남은 과제들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 연동을 하고, 조금의 최적화를 해보았다. 그래도 긍정적인 점은, 캐시 응답이 제대로 온다는 것과, GPU 사용량이 잘 출력된다는 것이다.&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;이 외에도 RAG 답변 고도화, 프롬프트 엔지니어링 등이 남아있다. 또한 실질적인 데이터와 탄소 최적화 수치를 알기 위해서 더 체계적인 테스트를 수행해야 한다.&lt;/p&gt;</description>
      <category>탄소중립</category>
      <author>codie0226</author>
      <guid isPermaLink="true">https://codie0226.tistory.com/9</guid>
      <comments>https://codie0226.tistory.com/9#entry9comment</comments>
      <pubDate>Mon, 25 May 2026 21:02:56 +0900</pubDate>
    </item>
  </channel>
</rss>