반응형

요즘 MCP(Model Context Protocol)를 활용해 실무에 어떤 기능들을 적용할 수 있을지 살펴보고 있습니다. 그중에서도 특히 Cursor 에디터와 Figma를 MCP로 연동하여 디자인-to-코드 퍼블리싱 워크플로우를 자동화하는 방법을 테스트해보았는데요. 이번 글에서는 그 과정을 정리해서 공유드리고자 합니다.

 

개인적으로는 GAN(생성적 적대 신경망) 구조에서 영감을 받아, 마치 Generator(코드 생성)와 Discriminator(코드 리뷰)가 서로 경쟁하며 결과물을 개선하는 형태로 구현해 보았습니다.

 

 

** GAN이 뭐냐면?

더보기

쉽게 말해서: “서로 경쟁하며 발전하는 두 AI”

GAN(Generative Adversarial Network)은 두 개의 인공지능이 서로 경쟁하면서 더 나은 결과를 만들어내는 구조입니다.
이 두 AI는  생성자(Generator)”와 “감별자(Discriminator)”**로 이루어져 있습니다.

🧩 두 역할을 비유로 설명하면

  • Generator (생성자):
    마치 그림을 그리는 학생처럼, 처음엔 서툴지만 점점 더 진짜처럼 보이는 이미지를 만들려고 노력합니다.
    (코드 생성 단계에서는 “AI가 새로운 코드를 작성하는 역할”)
  • Discriminator (감별자):
    미술 선생님처럼, 학생이 만든 그림이 진짜인지 가짜인지 판단하고 피드백을 줍니다.
    (코드 리뷰 단계에서는 “AI가 코드의 품질, 구조, 가독성 등을 평가하는 역할”)

🔁 이 둘이 반복적으로 경쟁하며 발전

  1. Generator가 코드를 만듭니다.
  2. Discriminator가 그 코드를 검사하고 문제점을 지적합니다.
  3. Generator는 지적받은 부분을 반영해 더 나은 코드를 만듭니다.
  4. 이 과정을 여러 번 반복하면서 점점 더 완성도 높은 코드가 만들어집니다.

 

또한 직접 간단한 MCP 서버를 만들어 코드 리뷰 에이전트도 구성했어요

(mcp 서버 참고 링크 - https://github.com/lodado/MCP-Code-Review-Agent )

 

그럼, 하나씩 살펴볼게요!

 

🧩 1. MCP란?

 

MCP(Model Context Protocol)는 AI 코드 에디터(예: Cursor, Claude Desktop 등)가 로컬 리소스나 외부 API에 접근할 수 있도록 해주는 표준 프로토콜입니다. 쉽게 말해 “AI가 Figma API, GitLab, Notion, DB 등 외부 도구를 직접 불러와 사용할 수 있게 하는 인터페이스"라고 보면 됩니다.

 

조금 더 풀어서 설명하자면, MCP 환경에서는 MCP 서버가 어댑터(Adapter) 역할을 하고, Cursor와 같은 에디터는 클라이언트(Client) 역할을 합니다. 예를 들어 MCP 서버가 Figma API와 통신하여 디자인 정보를 가져오면, Cursor는 그 정보를 받아와 코드 생성 등에 활용할 수 있게 되죠. MCP는 프로토콜이기 때문에 Cursor뿐만 아니라 Codex, Claude Code 등 여러 AI 코딩 플랫폼에 동일하게 연결할 수 있습니다. (이 글에서는 주로 Cursor와 codex를 기반으로 설명드릴게요.)

 

⚙️ 2. 사전 준비

본격적인 자동화 실험을 하기 전에 몇 가지 준비 사항이 있었습니다. 아래 항목들을 미리 갖춰야 했어요:

 

  • Cursor 에디터: MCP 클라이언트를 지원하는 최신 버전이 필요합니다 (2024.10 이후 버전). 저는 Cursor를 사용했고, MCP 설정을 할 수 있는지 꼭 확인해야 합니다. 꼭 cursor가 필요한건 아닙니다만, 전 cursor에서 진행했습니다.
  • Codex(혹은 claude code 등):  실제 리뷰를 진행할 에이전트입니다. MCP 내부에서 코드 리뷰 AI 서버로 이용하며, 로그인이 되어 있어야합니다.openai API를 사용해도 되긴하는데, 해당 방법시 사용 토큰당 과금 요소가 필요합니다. 

  • Node.js: v18 이상이 설치되어 있어야 합니다 (MCP 서버 실행용으로 사용).

  • Figma Personal Access Token: Figma API에 접근하기 위한 개인 액세스 토큰이 필요합니다. Figma 계정의 설정(Security 탭)에서 생성할 수 있어요. 참고: 발급은 Figma 개발자 사이트에서 할 수 있으며, Dev Mode 활성화 등 권한이 필요합니다.

  • Figma File Key: MCP로 연동하려는 Figma 파일의 고유 키 값입니다. 해당 파일의 URL에서 확인할 수 있습니다 (파일 URL 중 file/{파일Key} 형태로 포함되어 있어요).

위 준비를 마쳤다면, 이제 Cursor에 MCP 서버를 연결해야 합니다. Figma MCP 서버로는 오픈소스인 Figma-Context-MCP 프로젝트를 활용했습니다. Cursor 설정(Settings)에서 MCP 섹션을 열어, 공식 가이드에 따라 Figma MCP를 추가로 등록합니다. (자세한 설정 방법은 GLips/Figma-Context-MCP 깃허브Framelink의 Figma MCP Quickstart 문서를 참고했습니다.)

Cursor 설정 파일에 MCP 서버를 등록할 때 아래와 같은 JSON 구성을 넣어주면 됩니다:

 

{
  "mcpServers": {
    "Framelink MCP for Figma": {
      "command": "npx",
      "args": [
        "-y", 
        "figma-developer-mcp", 
        "--figma-api-key=YOUR-FIGMA-API-KEY", 
        "--stdio"
      ]
    }
  }
}

 

 

위와 같이 설정하고 MCP 서버(Figma MCP)를 실행하면, Cursor와 Figma 간 연동 준비가 완료됩니다. 이제 Cursor에서 Figma 디자인 정보를 불러와서 코드 생성을 시작할 수 있어요! 😃

 

 

이제부터는 실제로 Figma 디자인을 가져와 코드로 변환하고, AI 코드 리뷰 에이전트를 통해 자동으로 개선하는 과정을 단계별로 설명하겠습니다. 전체 흐름을 요약하면 “Figma 디자인 -> 코드 생성 -> AI 코드 리뷰 -> 수정 반영”의 반복 루프라고 볼 수 있는데요, 이는 마치 앞서 언급한 GAN의 제너레이터-디스크리미네이터가 서로 경쟁하며 결과물을 개선하는 것과 비슷합니다.

 

실제 workflow, 해당 스크린샷은 figma 자체 제공MCP를 활용한 화면

 

 

그럼 1단계부터 차근차근 살펴보겠습니다.

 

1.1 Figma MCP를 통한 디자인 데이터 추출

 

우선 Figma에서 구현하려는 UI 컴포넌트를 하나 선택했습니다. 예를 들어, 아래와 같은 Figma 디자인 파일의 특정 컴포넌트를 가져온다고 해볼게요:

 

jira의 Calender component

@Figma 디자인 링크
위 디자인 컴포넌트를 shared 폴더 내에 구현해주세요. React + Tailwind를 사용하여 UI를 만들고, 코드의 응집도(cohesion)를 높이는 한편 결합도(coupling)는 최소화해주세요. 비즈니스 로직과 UI는 hook 또는 props를 활용해서 분리하도록 합니다.

 

위와 같은 프롬프트를 Cursor에 입력하면, MCP를 통해 해당 Figma 노드(컴포넌트)의 정보가 AI에게 전달됩니다. 그러면 Cursor의 코드 생성 AI가 지시에 따라 React + Tailwind CSS 조합으로 해당 컴포넌트의 코드를 생성합니다. 저는 프롬프트에 디자인 구현 시 “응집도를 높이고 결합도를 줄여라”, “로직과 UI 분리” 등의 베스트 프랙티스도 함께 써주어, 생성된 코드 품질을 높이려고 했어요. 😀

 

1.3 Compound Pattern 적용 (컴파운드 패턴)

 

코드를 생성할 때 고려한 중요한 아키텍처 원칙 중 하나는 Compound Component Pattern의 적용이었습니다. 복잡한 UI를 구현하다 보면 Props drilling(상위 컴포넌트의 props를 여러 단계 하위로 전달)이 많이 발생할 수 있는데요, 이를 줄이기 위해 컴파운드 패턴으로 컴포넌트를 설계했습니다. 즉, 하나의 컴포넌트를 여러 하위 구성요소로 쪼개어 구성하는 방식입니다.

예를 들어 Card 컴포넌트를 Compound Pattern으로 구성하면 다음과 같은 형태가 됩니다:

 

// Compound Pattern 적용 예시
<Card>
  <Card.Header>
    <Card.Title>제목</Card.Title>
  </Card.Header>
  <Card.Body>
    <Card.Content>내용</Card.Content>
  </Card.Body>
  <Card.Footer>
    <Card.Actions>액션 영역</Card.Actions>
  </Card.Footer>
</Card>

 

위처럼 컴포넌트의 계층을 명확히 분리하면, 필요한 데이터나 함수를 하위 컴포넌트에 전달할 때 중간 단계의 불필요한 props 전달을 줄일 수 있습니다. 또한 각 부분을 독립적인 컴포넌트로 재사용할 수도 있어 응집도를 높이면서도 결합도는 낮게 유지할 수 있죠.

 

1.4 필요시 Provider 패턴 사용

컴파운드 패턴으로 대부분의 UI 상태 전달 문제를 해결했지만, 만약 그것만으로 부족한 복잡한 상태 관리가 필요할 경우에는 Context API를 활용한 Provider 패턴을 적용했습니다. 예를 들어 전역적인 상태 공유나 깊은 트리 구조에서의 데이터 전달이 필요한 경우 Context를 사용하면 유용합니다:

 

// Context Provider 패턴 예시
const AppContext = createContext();

const AppProvider = ({ children }) => {
  // 공유할 상태와 로직 정의
  const value = { /* 상태 값들 */ };

  return (
    <AppContext.Provider value={value}>
      {children}
    </AppContext.Provider>
  );
};

 

 

위처럼 Context를 쓰면 컴포넌트 트리 어디서든 AppContext를 통해 값에 접근할 수 있으므로, props drilling 없이도 필요한 데이터를 주고받을 수 있어요. 다만 Context 사용은 전역으로 상태를 공유하는 것이므로, 정말 필요한 경우에만 제한적으로 사용하고 기본적으로는 컴파운드 패턴 내에서 해결하는 것을 우선으로 했습니다.

이렇게 해서 1단계에서는 Figma 디자인 -> 초안 코드 생성까지 완료되었습니다. 이제 생성된 코드를 AI 에이전트를 통해 리뷰하고 개선하는 단계로 넘어가겠습니다.

 

2단계: 코드 리뷰 및 개선 (반복 루프)

 

2단계에서는 앞서 생성된 코드를 다양한 관점에서 AI로 리뷰하고, 피드백을 반영하여 코드를 개선하는 과정을 거쳤습니다. 저는 이를 세 가지 종류의 리뷰 에이전트를 통해 여러 번 반복적으로 수행했는데요, 첫 번째는 일반 코드 품질 리뷰, 두 번째는 웹 접근성(WCAG) 관점 리뷰, 세 번째는 아키텍처 및 코드 구조 리뷰였습니다.

 

이 부분에서 저는 MCP를 이용해 직접 만든 코드 리뷰 에이전트를 활용했습니다. 이 MCP 서버는 OpenAI의 Codex(프로그래밍 특화 모델)나 기타 LLM을 활용해 코드의 개선점을 알려주는 역할을 합니다. 쉽게 말해, 제 코드 리뷰 에이전트(MCP 서버)는 AI에게 코드를 보여주고 “문제점을 찾아줘”라고 물어보면 여러 가지 분석 결과를 주는 비서 같은 존재입니다. 이를 통해 사람 대신 AI가 1차로 리뷰를 해주니 상당히 편하더군요!

 

각 리뷰를 진행한 이휴, 리뷰 결과를 바로 100% 신뢰하기보다는 제가 한 번 더 정리하고 핵심만 추출하는 과정을 거쳤습니다. AI가 장황하게 피드백을 주기도 하고, 가끔 맥락에 맞지 않는 제안을 할 때도 있어서, 필터링과 우선순위화를 사람이 도와주는 셈이죠. 그럼 각 리뷰 단계별로 어떤 작업을 했는지 살펴보겠습니다.

 

 

MCP 코드 및 작동 방식은 링크(https://github.com/lodado/MCP-Code-Review-Agent) 서 살펴 보실 수 있습니다.

로컬에 띄워놓고, codex를 사용하여 리뷰 및 검증후 MCP 프로토콜이 넘기는 형태로 구현했습니다. 

 

2.1 Codex 기반 일반 코드 리뷰

 

먼저 Codex 기반의 일반 코드 품질 리뷰를 수행했습니다. 이 리뷰는 생성된 코드의 전반적인 품질, 가독성, 버그 여부 등을 점검해주는 역할입니다. 제 코드 리뷰 MCP 에이전트에서 이 기능을 호출하는 명령은 다음과 같았습니다:

 

 `Analyze the following TypeScript code and provide a comprehensive code review focusing on:

1. **Context**: What does this code do?
2. **Security Issues**: Any security vulnerabilities or concerns
3. **Performance Issues**: Performance bottlenecks or inefficiencies  
4. **Architecture Issues**: Design patterns, coupling, separation of concerns
5. **Logic Issues**: Potential bugs, edge cases, logical errors

Note: Do not include barrel export related content in your analysis.`

 

피드백 정제 과정: Codex로부터 분석 결과(JSON 형태 혹은 텍스트 리포트)가 오면, 다음과 같은 과정을 거쳐서 피드백을 정리했습니다.

  1. AI 리뷰 결과 해석: Codex가 내놓은 리뷰 내용을 쭉 읽어봅니다. 보통 코드 스타일 지적, 잠재 버그, 최적화 제안 등이 섞여 나오는데, 일단 빠르게 훑어보죠.
  2. 핵심 개선사항 추출: 리뷰 결과에서 정말로 중요한 지적사항이나 코드 품질에 큰 영향을 미치는 포인트를 뽑아냅니다. (예: "함수 A의 복잡도가 높다", "불필요한 변수 선언이 있다" 등)
  3. 우선순위 결정: 추출된 개선사항들에 우선순위를 매깁니다. 바로 고치지 않으면 위험한 버그 -> 리팩토링하면 좋은 부분 -> 스타일 사항 순으로 중요한 것부터 순위를 정했어요.
  4. 구체적인 수정 방향 계획: 각 중요한 지적에 대해 어떻게 수정할지 방향을 간략히 정리합니다. 예를 들어 "함수 A 복잡도 개선"이라면, 함수 쪼개기를 고려한다든지, "변수 최소화"라면 불필요한 변수를 삭제하거나 로직 단순화 계획을 세우는 식이죠.

이렇게 핵심만 추린 후에, 우선순위 높은 항목부터 코드를 개선했습니다. Codex의 제안을 맹신하기보다는 참고하는 수준으로 활용하고, 제가 수용할 부분은 수용하고 아닌 부분은 걸러냈습니다.

 

2.2 웹 접근성 체크 (Accessibility Agent 리뷰)

 

다음으로, 웹 접근성(WCAG) 관점에서의 리뷰를 진행했습니다. 아무리 UI를 잘 구현해도, 시멘틱 마크업이나 접근성이 떨어지면 실제 서비스에서 품질이 낮아질 수 있으니까요. 이를 위해 MCP 리뷰 에이전트의 accessibility 모드를 활용했습니다. 명령어는 아래와 같습니다:

 

`You are a Senior Frontend Publisher with 10+ years of experience in web accessibility and semantic web development. Analyze the following React/TypeScript code focusing specifically on:

1. **Web Accessibility (WCAG 2.1/2.2)**: 
   - ARIA attributes usage and correctness
   - Keyboard navigation support
   - Screen reader compatibility
   - Color contrast and visual accessibility
   - Focus management and tab order
   - Semantic HTML structure

2. **Semantic Web Standards**:
   - Proper HTML5 semantic elements usage
   - Microdata or JSON-LD structured data
   - Meta tags and SEO optimization
   - Progressive enhancement principles

3. **React Accessibility Best Practices**:
   - Proper use of React Aria hooks
   - Accessible form controls and validation
   - Error message accessibility
   - Component accessibility patterns

4. **Performance & UX for Accessibility**:
   - Loading states and skeleton screens
   - Error boundaries and fallbacks
   - Responsive design considerations
   - Animation and motion preferences`

 

피드백 정제 과정: 접근성 리뷰 결과에 대해서도 다음과 같이 정리했습니다.

  1. WCAG 준수 여부 확인: AI가 지적한 내용 중 웹 콘텐츠 접근성 가이드라인(WCAG)에 비춰 심각하게 위반되는 사항이 있는지 체크했습니다. (예: 컨트라스트 비율 부족, 폼 레이블 누락 등)
  2. 시맨틱 HTML 사용 검토: <div>로 떼운 부분이 없는지, 적절한 시맨틱 태그(<header>, <main>, <button> 등)를 썼는지 지적된 부분을 살폈습니다. 혹시 빼먹은 시맨틱 태그가 있다면 수정 계획에 포함했습니다.
  3. 키보드 내비게이션/포커스: 키보드만으로 UI 조작이 가능한지, focus outline이나 tabindex 처리 등 키보드 접근성 관련 지적사항을 확인했습니다.
  4. 스크린 리더 호환성: 스크린 리더가 읽기 어렵거나 애매한 label/alt 텍스트 문제는 없는지 AI 피드백을 통해 점검하고 개선점을 정했습니다.

이 단계에서는 접근성 전문가가 리뷰해주는 느낌으로 꽤 유용한 지적을 많이 얻었습니다.

예를 들어 “이미지에 대체 텍스트가 없습니다”와 같은 기본적인 접근성 문제부터, calendar 구성 요소에 role="application"을 반영하거나 aria-roledescription 등 구체적인 ARIA 속성 제안을 통해 개선할 수 있었죠.

 

이러한 피드백을 하나씩 반영하면서 전체 컴포넌트의 접근성 완성도를 높일 수 있었습니다.

 

2.3 아키텍처 & 코드 구조 리뷰 (Toxic Architect Agent)

 

세 번째로는 코드 아키텍처설계 품질에 대한 리뷰를 진행했습니다. 이름하여 "Toxic Architecture Agent"라고 불리는 모드였는데요 😅, 말 그대로 유해한(안 좋은) 아키텍처 패턴이 있는지 검토하는 에이전트였습니다. 이 리뷰는 코드가 SOLID 원칙을 잘 지키고 있는지, 디자인 패턴을 적절히 활용하거나 남용하고 있지는 않은지 등을 점검해줍니다. MCP 에이전트 명령은 다음과 같았습니다:

 

`You are a brutally honest Senior Frontend Architect with 15+ years of experience. You have zero tolerance for poor design, messy code, or weak abstractions. You are direct, sarcastic, and extremely critical—but never use profanity or personal insults. Your tone is harsh but constructive, focused on technical precision and architectural correctness.

Analyze the following TypeScript code with a focus on:

1. **SOLID Principles Violations**:
   - SRP: UI logic, side effects, and data fetching mixed in the same component or module
   - OCP: feature extensions requiring if/else or switch statements
   - LSP: broken abstractions, subclass contracts violated
   - ISP: oversized interfaces, custom hooks doing too much
   - DIP: direct dependency on infra (fetch, storage, SDK) without abstractions

2. **Clean Code Violations**:
   - Poor separation between layers
   - Business logic leaking into components or pages
   - DTOs or API responses exposed to the UI layer
   - Folder structure not aligned with change axes or boundaries
   - Missing or inverted dependency direction

3. **Code Quality Issues**:
   - High coupling and low cohesion
   - "God" components, repeated patterns, and duplicated logic
   - Poor naming conventions and inconsistent coding styles
   - Overuse of \`any\`, \`unknown\`, or overly broad unions
   - Missing error handling, weak input validation
   - Performance bottlenecks (unnecessary re-renders, missing memoization)
   - Unclear data flow or improper state management
   - Excessive prop drilling (3+ levels deep - consider Context API, global state, or compound patterns)

4. **Design Pattern Misuse**:
   - Wrong or missing patterns (e.g., reinventing context or observer logic)
   - Over-engineered abstractions with no payoff
   - Misuse of hooks (unstable deps, side-effect chaos)
   - Excessive prop drilling (consider global state, providers, or compound patterns)
   - Global state abuse or inappropriate state management choices

5. **Frontend Essentials (Performance, Accessibility, Security, DX)**:
   - Performance: unnecessary renders, missing virtualization, bundle bloat, image misuse
   - Accessibility: missing roles/labels, focus traps, keyboard nav, contrast issues
   - Security: XSS, unsafe innerHTML, missing boundary validation
   - DX: missing unit/integration tests, weak linting or typing, broken CI coverage

6. **React / Next.js Specifics**:
   - Improper useEffect deps or cleanup
   - Confused client/server boundaries
   - Wrong caching, ISR, or SWR strategy
   - Incorrect use of Suspense/streaming
   - State mutations, non-serializable data leaks

Note: Do not include barrel export related content in your analysis.`

 

피드백 정제 과정: 아키텍처 리뷰 결과에 대해서도 비슷한 방식으로 핵심을 정리했는데, 특히 신경 쓴 점들은 다음과 같습니다.

  1. SOLID 원칙 준수 검토: 단일 책임 원칙 위반이나, 의존성 역전 원칙 미흡 등 SOLID 원칙에 어긋나는 코드 구조 지적이 있었는지 확인했습니다. 예컨대 "한 컴포넌트가 너무 많은 일을 한다"라는 피드백이 있다면 단일 책임 원칙 위반이니 리팩토링을 계획했어요.
  2. 디자인 패턴 적용/남용 평가: 코드에서 사용한 패턴들이 적절했는지 봤습니다. Compound 패턴이나 Provider 패턴을 썼는데 AI가 보기에 어색한 부분은 없는지, 혹은 "굳이 복잡한 패턴을 쓸 필요 없이 훨씬 단순하게 가능하다"는 지적은 없는지 확인했습니다.
  3. 전반적 코드 구조 개선 제안: 폴더 구조나 컴포넌트 구조에 대한 제안도 나왔는데, 예를 들어 "UI 로직과 비즈니스 로직이 완전히 분리되지 않았다" 등의 피드백이 있었습니다. 이러한 지적에 대해서는 구조를 어떻게 더 개선할지 방안을 생각해보고 적용했습니다.

이렇게 세 가지 리뷰(일반 코드 품질, 접근성, 아키텍처)를 거치면서, 코드를 생성 -> 리뷰 -> 개선 -> 다시 리뷰 -> 개선... 하는 루프를 여러 번 반복하게 되었습니다. 거의 코드가 스스로 리팩토링하며 발전하는 모습이라, 보면서도 신기했습니다. 😆 GAN의 Generator-Discriminator가 서로 경쟁하며 출력물을 개선하듯이, 코드 생성기와 리뷰어 AI가 번갈아 가며 코드를 다듬어주니 결과적으로 초기 버전 대비 훨씬 깔끔하고 견고한 코드가 나오더군요.

 

3단계: 최종 검증

여러 차례에 걸친 개선 루프를 돌린 후, 마지막으로는 최종 검증 단계를 진행했습니다. 최종 검증에서는 지금까지 누적된 변경사항을 종합적으로 점검하고, 혹시 놓친 문제가 없는지 확인했습니다.

 

핵심 원칙 되짚어보기

이번 실험/구현 과정을 통해 얻은 교훈 혹은 지켜야겠다고 느낀 핵심 원칙을 정리하면 다음과 같습니다:

  1. Compound Pattern 우선 적용: 가능하다면 UI 컴포넌트를 설계할 때 Prop drilling을 줄일 수 있는 Compound 컴포넌트 패턴을 활용하자. 각 UI 요소를 독립적인 컴포넌트로 구성하면 재사용성과 유지보수성이 크게 향상된다. (Context API는 보조적으로!)
  2. AI 피드백의 선별명확한 적용: AI 리뷰 에이전트가 주는 피드백은 한 번 걸러서 핵심만 취하고, 팀원이나 다른 사람이 봐도 이해하기 쉽게 정제해서 공유하자. 두서없이 적용하기보다 우선순위를 정해 단계적으로 수정하고, 그 내용을 명확히 기록해두면 좋다.
  3. 누적 검증과 반복 개선: 한 번의 생성-리뷰로 끝내지 말고, 여러 번의 루프를 누적하여 돌림으로써 코드 품질을 점진적으로 높이자. 각 단계의 피드백을 다음 단계에 반영하면서, 최종적으로 전체 코드를 다시 검증하면 놓치는 부분 없이 탄탄한 결과물을 얻을 수 있다.

사용한 MCP 리뷰 명령어 요약

 

혹시 MCP 기반 코드 리뷰 에이전트를 활용해보고 싶은 분들을 위해, 제가 사용했던 주요 명령어들을 한 곳에 모아 요약합니다:

codex login이 되어 있어야합니다.

아마  MCP 연결도 되긴 할텐데, cursor에서는 샌드박스 & 보안 관련 이슈로 MCP 인터페이스엔 연결 못했고
jsonrpc + cli 명령으로 연결 및 테스트했습니다. 

# Clone and build the tool locally (package not yet published to npm)
git clone https://github.com/lodado/MCP-Code-Review-Agent
cd MCP-Code-Review-Agent
npm install
npm run build
npm link

# 1. Codex 일반 코드 리뷰 (최신 수정 부분만 분석)
echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"codex_review","arguments":{"reviewType":"modified","analysisType":"codex"}}}' | mcp-code-review-agent

# 2. 웹 접근성 리뷰 (코드 전체 풀스캔)
echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"codex_review","arguments":{"reviewType":"full","analysisType":"accessibility"}}}' | mcp-code-review-agent

# 3. 아키텍처 리뷰 (코드 전체 풀스캔)
echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"codex_review","arguments":{"reviewType":"full","analysisType":"toxic-architect"}}}' | mcp-code-review-agent

# 4. 하이브리드 종합 리뷰 (코드 전체 풀스캔)
echo '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"codex_review","arguments":{"reviewType":"full","analysisType":"hybrid"}}}' | mcp-code-review-agent

# 5. 정적 분석 리뷰 (최신 수정 부분만 분석)
echo '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"codex_review","arguments":{"reviewType":"modified","analysisType":"static"}}}' | mcp-code-review-agent

 

 

기대 효과 및 마무리

이번에 MCP와 Figma, 그리고 AI 코드 리뷰 에이전트를 조합한 워크플로우를 실험해보면서 느낀 기대 효과는 다음과 같습니다:

 

  • 코드 품질 향상: 여러 각도의 AI 리뷰(코덱스, 접근성, 아키텍처)를 거치면서 코드의 품질이 종합적으로 개선되었습니다. 사람이 놓칠 수 있는 부분까지 AI가 짚어주니 든든했어요.
  • 웹 접근성 강화: 평소 간과하기 쉬운 접근성 부분도 전문가 수준으로 챙길 수 있었어요. 덕분에 결과물 UI가 더 포용력있는 사용자 경험을 제공할 것으로 기대됩니다.
  • 아키텍처 개선: SOLID 원칙과 Clean code 관점에서 코드를 한번 더 점검하니, 초기 설계에서 미처 생각 못 한 구조적 개선 포인트를 잡아낼 수 있었습니다. 장기적으로 유지보수성이 향상될 거라 믿습니다.
  • 개발 효율성 증대: 사람 대신 AI 에이전트들이 리뷰와 수정 가이드를 제시해주니, 개발자가 반복적인 코드 정돈 작업에 들이는 시간이 줄었습니다. 물론 최종 판단은 개발자가 해야 하지만, 이러한 자동화된 워크플로우가 개발 생산성을 높여준 것은 분명합니다.

다만, 아쉬운 점은 다음과 같습니다.

 

1. 아직 figma MCP가 베타 단계이고, design variable이나 font등을 정확히 뽑아내진 못함(#fff등 순수 색상, hex 값으로 표출됨)
2. 다행이자 아쉬운 점은 AI가 아직은 불완전하다는 것이고,
    결국은 검수자의 숙련도에 따라 다른 결과가 나올 수 있음, 알잘딱깔센이 안됨!

 

결국 MVP 코드나 UI 코드를 빠르게 뽑아낼때엔 유용한 듯 싶습니다.

 

마치며, MCP를 활용하면 AI가 코딩 도구들과 직접 소통할 수 있게 되면서 개발 방식에 재미있는 변화가 생기는 것 같습니다. 이번에는 Figma 디자인을 코드로 자동화하는 시나리오였지만, 앞으로 GitHub PR 리뷰나 테스트 자동화 등 다양한 분야에 이런 AI 에이전트+MCP 조합을 응용해볼 수 있을 것 같아요. 혹시 비슷한 시도를 해보신 분이 있다면 경험을 공유해주시거나, 궁금한 점이 있다면 댓글로 남겨주세요. 읽어주셔서 감사합니다! 🙌

 

마지막으로 어떤 결과물이 나왔는지 공유드릴려고 생성된 코드 폴더를 제공하겠습니다. 

AI가 제안한 방향 및 리뷰 코드에서 거의 건드리지 않은 코드입니다.

돌아가지는 않겠지만.. 전체적인 코드를 한번 참고해주세요!

Calendar.zip
0.01MB

 

반응형
반응형

요즘 회사에서 일하던 프로젝트가 잘되서(?) 사업의 스케일이 커졌고, 모듈리식 레포지토리에서 작은 도메인 단위의

서브 프로젝트 여러개로 쪼개는 모노레포 리펙토링 작업을 진행하고 있다

 

공통 로직이랑 중복 코드를 추출하면서, 자연스럽게 추출 및 활용이 쉬운 코드와 어려운 코드가 나뉘는 걸 체감하고 있다.

 

처음엔 단순히 코드 복잡도나 의존성 때문이라고 생각했는데, 계속 리팩토링을 하다 보니까 그 차이의 본질이 결국 오염(contamination) 이라는 생각이 들었다. 한 모듈의 상태나 로직이 다른 곳에 얼마나 퍼져 있느냐, 그게 오염의 정도다.

오염이 심할수록 추출이 어렵고, 반대로 모듈 경계가 깔끔하게 지켜진 코드일수록 손쉽게 분리된다.

 

이를 “모듈화 관점에서의 오염(contamination)”은, 한 모듈의 책임/경계가 무너지고 부작용, 상태, 오류, 의존성, 규칙 등이 경계를 넘어 전파되는 현상 이라고 보면 될듯하다.

 

이 “오염”이라는 게 뭔지 구체적으로 말하자면, 한 모듈 내부에서만 머물러야 할 값이나 상태, 부작용이 경계를 넘어 퍼지는 현상을 말한다.

 

예를 들어 null이 대표적인 케이스다. 어떤 함수가 null을 반환하고, 그걸 받은 상위 함수가 별다른 검증 없이 그대로 다음 함수로 넘기면, 결국 그 null은 시스템 전반으로 퍼진다. 나중엔 어디서 깨졌는지도 모르는 상태에서 Cannot read property 'x' of null 같은 에러를 뿜는다.

 

 
function getUser(id: string) {
  const user = db.find(u => u.id === id);
  return user ?? null; // ❌ null 반환
}

function getUserName(id: string) {
  const user = getUser(id);
  return user.name; // ❌ 여기서 null 전파로 터짐
}

 

 

이게 바로 **데이터 오염(data contamination)**이다.
한 곳에서의 “불확실한 상태(null)”가 검증 없이 전달되며,
모듈 경계를 오염시키는 전형적인 예시다.

이런 식의 오염은 null뿐만 아니라 다양하게 존재한다.

 

  • 전역 변수를 직접 수정하는 상태 오염
  • 외부 변수에 의존하거나 부작용을 남기는 비순수 함수 오염
  • 특정 API 구조나 데이터 포맷에 직접 묶인 의존성 오염
  • try-catch 없이 흘러가는 에러 오염
  • CSS 클래스가 전역에 영향을 미치는 스타일 오염
  • 비즈니스 규칙이 있어야 할 곳이 아닌 곳에 섞여 있는 케이스

 

결국 오염이란 “내부의 영향이 외부로 새는 것”이다.
내부 로직의 부작용, 불확실한 값, 전역 변경이 다른 모듈로 번지면서, 코드 추출과 분리가 어려워지고, 유지보수성도 떨어지는 이유가 된다.

 

그래서 리팩토링을 하다 보면 “이 코드는 왜 이렇게 유지보수, 재사용 하기 힘들까?”의 답이 대부분 여기에 있다 —
경계 밖으로 새어나간 오염이 많을수록, 코드 리펙토링, 재사용이 힘들어진다.

 

결국 유지보수가 쉽고 어려운 건 코드량이나 난이도의 문제가 아니라, 얼마나 경계가 명확하고 불필요한 의존이 없는 구조인지에 달린 것 같다

반응형
반응형

이전에 면접 준비하며 준비했던 내용 공유합니다

 

운영체제 기본 개념

1. 하드웨어 (Hardware)

  • 중앙 처리 장치(CPU): 명령어 해석 및 실행.
  • 메모리(Memory): 프로그램과 데이터를 저장.
  • 입출력 장치(I/O devices): 사용자와 시스템 간 데이터 교환.

1. 부트스트랩 (Bootstrap)

  • 저장 위치: ROM(Read-Only Memory)에 저장.
  • 역할:
    1. 시스템 전원이 켜지면 실행됨.
    2. 운영체제의 커널(kernel) 을 찾아 주 메모리(RAM) 에 적재.
  • 결과: 커널이 실행되면서 시스템 초기화가 진행됨.

2. 커널과 시스템 프로세스

  • 커널(Kernel): 운영체제의 핵심 부분, 자원 관리와 제어 담당.
  • 시스템 프로세스 / 시스템 데몬(Daemon):
    • 커널 수행 중 계속 동작.
    • 백그라운드에서 시스템 기능(예: 스케줄링, 로그 관리, 네트워크 서비스 등)을 지원.

3. 주 메모리 (RAM)

  • 특징: 재기록 가능한 메모리.
  • 용도:
    • 실행 중인 프로그램과 데이터를 저장.
    • CPU가 직접 접근 가능.
    • 전원이 꺼지면 내용이 사라지는 휘발성 메모리.

4. 컴퓨터 구조

(1) 폰 노이만 구조 (Von Neumann Architecture)

  • 명령어와 데이터를 같은 버스(경로)를 통해 전달.
  • 특징: 구조 단순, 범용 컴퓨터에 널리 사용.
  • 단점: 명령어와 데이터가 한 버스를 공유해 병목 현상(폰 노이만 병목) 발생 가능.

(2) 하버드 아키텍처 (Harvard Architecture)

  • 명령어와 데이터를 서로 다른 버스에서 전달.
  • 특징: 명령어 읽기와 데이터 접근을 동시에 수행 가능 → 병목 최소화.
  • 주로 임베디드 시스템, DSP(Digital Signal Processor) 등에서 사용.

5. 입출력 구조 (I/O Structure)

1) 인터럽트 기반 I/O (Interrupt-Driven I/O)

  • 동작 방식:
    • CPU가 I/O 장치에 작업을 지시하고 다른 일을 수행.
    • I/O 장치가 작업을 끝내면 인터럽트(Interrupt) 신호를 CPU에 보냄.
    • CPU는 인터럽트 서비스 루틴(ISR)을 실행하여 결과를 처리.
  • 장점: CPU가 불필요하게 대기하지 않음.
  • 단점: 인터럽트가 자주 발생하면 오버헤드 증가.

2) 직접 메모리 접근 (DMA, Direct Memory Access)

  • 동작 방식:
    • CPU가 I/O 장치에 메모리 전송을 직접 하지 않고, DMA 컨트롤러에게 위임.
    • DMA 컨트롤러가 메모리와 I/O 장치 간 데이터를 직접 전송.
    • 전송이 완료되면 CPU에 인터럽트를 보내 알림.
  • 장점: CPU 개입 최소화 → 고속 데이터 전송 가능 (예: 디스크 ↔ 메모리).
  • 활용: 대량 데이터 전송이 필요한 경우 (예: 영상, 네트워크, 디스크 입출력).

2. 운영체제 (Operating System, OS)

  • 목적: 사용자가 시스템을 쉽게 사용할 수 있도록 설계.
  • 특징:
    • 자원 사용 자체에는 신경 쓰지 않음.
    • 자원 할당자(Resource Allocator): CPU, 메모리, I/O 자원들을 적절히 배분.
    • 제어 프로그램(Control Program): 프로그램 실행과 하드웨어 동작을 제어.
    메모리에 적재되어 있고, 실행되는 프로그램을 “프로세스”라 부름1. 인터럽트(Interrupt)
    • 발생 원인: CPU 외부의 하드웨어 장치가 CPU에게 서비스를 요청할 때 발생
    • 예: 키보드 입력, 디스크 I/O 완료, 네트워크 패킷 도착
    • 특징
      • 비동기적으로 발생 (CPU가 어떤 명령을 실행 중이든 외부 장치에서 신호를 보냄)
      • CPU는 현재 실행 중이던 명령을 끝내고 인터럽트 서비스 루틴(ISR)으로 점프
      • 주로 외부 이벤트 처리를 위해 사용
    • 예시:
      • 키보드에서 키가 눌림 → 인터럽트 발생 → 운영체제 커널이 입력 버퍼에 저장

    2. 트랩(Trap, 예외 Exception)
    • 발생 원인: CPU 내부에서 실행 중인 프로그램이 원인이 되는 상황
    • 예: 0으로 나누기, 잘못된 메모리 접근, 시스템 콜 호출
    • 특징
      • 동기적으로 발생 (특정 명령 실행 결과로 즉시 발생)
      • 크게 두 종류로 나뉨:
        1. 예외(Exception): 오류 상황 (Divide by zero, Page fault 등)
        2. 트랩(Trap, 소프트웨어 인터럽트): 의도적으로 발생시켜 운영체제 서비스 호출 (System call)
    • 예시:
      • int 0x80 (리눅스 시스템 콜) → 커널로 진입
      • 잘못된 포인터 접근 → Segmentation fault

    3. 차이점 요약구분 인터럽트(Interrupt) 트랩 / 예외(Trap, Exception)
    발생 원인 하드웨어 장치 (외부) CPU 내부 명령 실행 결과 (소프트웨어)
    발생 시점 비동기적 (언제든 발생 가능) 동기적 (명령 실행 직후 발생)
    주 용도 외부 이벤트 처리 오류 처리, 운영체제 서비스 호출
    예시 키보드 입력, 디스크 I/O 완료 0 나누기, 페이지 폴트, 시스템 콜

    👉 정리하면,
    • 인터럽트는 외부 장치가 CPU를 부르는 "하드웨어 호출"
    • 트랩/예외는 프로그램 실행 도중 CPU 내부에서 발생하는 "소프트웨어 호출/오류"
    이중 연산모드
    • CPU 내부에 모드 비트(Mode Bit) 라는 플래그가 있음.
    • 1 → 사용자 모드(User Mode)
    • 0 → 커널 모드(Kernel Mode, Supervisor Mode)

    2. 사용자 모드 (User Mode)
    • 일반 응용 프로그램이 동작하는 모드
    • 제한된 명령어만 실행 가능
      • 입출력(I/O) 명령, 메모리 보호를 무시하는 명령 등은 금지
    • 잘못된 동작이 발생하면 **트랩(예외)**을 발생시켜 운영체제가 처리

    3. 커널 모드 (Kernel Mode)
    • 운영체제(OS) 커널이 실행되는 모드
    • 모든 명령어 실행 가능 (특권 명령 포함)
      • 입출력 제어, 타이머 관리, 메모리 관리, 인터럽트 제어 등
    • 시스템 전체에 영향을 줄 수 있는 연산은 커널 모드에서만 수행

    4. 모드 전환
    • 부팅 시: 항상 커널 모드에서 시작 (운영체제가 제어권을 가짐)
    • 시스템 콜, 트랩, 인터럽트 발생 시: 하드웨어가 자동으로 커널 모드로 전환
    • 커널이 작업을 마친 후: 다시 사용자 모드로 전환하여 응용 프로그램 실행

    5. 이중 모드 운영의 목적
    • 보호(Protection): 사용자 프로그램이 임의로 하드웨어 자원(I/O, 메모리 등)을 제어하지 못하게 막음
    • 안정성(Stability): 오류가 발생해도 운영체제가 제어권을 가짐 → 시스템 전체가 멈추는 것 방지
    • 보안(Security): 악의적인 프로그램이 커널 수준 권한을 직접 사용하는 것을 차단
    운영체제의 메모리 관리
    1. 메모리 사용량 추적, 누구에 의해 사용되고 있는지
    2. 어떤 프로세스를 적재하고 제거할 것인지
    3. 필요에 따라 메모리 공간을 할당하고 회수
  • 1. 모드 비트 (Mode Bit)
  • 현재의 운영체제는 인터럽트 구동식이다

3. 응용 프로그램 (Application Programs)

  • 사용자 요구를 직접 해결하는 소프트웨어.
  • 예시: 워드 프로세서, 웹 브라우저 등.

4. 사용자 (Users)

  • 응용 프로그램을 활용해 컴퓨터를 사용하는 주체.

👉 요약하면, 하드웨어가 기본 자원, 운영체제가 이를 관리, 응용 프로그램이 사용자 목적을 달성, 그리고 사용자가 최종적으로 시스템을 활용하는 구조입니다.


운영체제 서비스

운영체제 서비스 (Operating System Services)

운영체제는 사용자와 하드웨어 사이에서 여러 **서비스(services)**를 제공하여 편의성, 효율성, 보안성을 보장합니다.


1. 사용자 인터페이스 제공 (User Interface)

  • CLI(Command Line Interface): 터미널 기반 명령어 입력 (예: Bash, PowerShell)
  • GUI(Graphical User Interface): 아이콘/윈도우 기반 그래픽 환경 (예: Windows, macOS)
  • 터치 인터페이스: 모바일 OS에서 활용

2. 프로그램 수행 (Program Execution)

  • 프로그램을 메모리에 적재하고 실행
  • 실행 중인 프로그램의 중단, 재개, 종료 기능 제공
  • 예: fork(), exec() 시스템 콜

3. 입출력 연산 (I/O Operations)

  • 사용자 프로그램은 직접 장치 제어 불가 → OS가 대신 수행
  • 키보드 입력, 디스크 읽기/쓰기, 네트워크 송수신 등
  • 장치 드라이버를 통해 하드웨어 추상화

4. 파일 시스템 조작 (File-System Manipulation)

  • 파일 생성, 삭제, 읽기/쓰기, 접근 권한 제어
  • 디렉토리 구조 관리
  • 예: open(), read(), write(), close() 시스템 콜

5. 통신 (Communication)

  • 프로세스 간 통신(IPC, Inter-Process Communication) 기능 제공
  • 공유 메모리(shared memory): 동일 메모리 영역에 접근
  • 메시지 전달(message passing): 커널을 통해 메시지 송수신
  • 예: 파이프(pipe), 소켓(socket), 큐(queue)

6. 오류 탐지 (Error Detection)

  • CPU, 메모리, I/O 장치 동작 중 발생하는 오류 감지 및 처리
  • 잘못된 명령 실행, 메모리 접근 위반(segmentation fault), 디스크 읽기 오류 등
  • 오류 발생 시 트랩을 발생시켜 운영체제가 개입

7. 자원 할당 (Resource Allocation)

  • CPU 시간, 메모리, I/O 장치, 파일 등 한정된 자원을 효율적으로 분배
  • 스케줄링(scheduling) 기법 활용 (FCFS, RR, Priority 등)

📌 정리

운영체제 서비스는 크게 **사용자 편의를 위한 서비스(UI, 실행, I/O, 파일, 통신)**와 **시스템 보호 및 효율성을 위한 서비스(오류 탐지, 자원 할당)**로 나눌 수 있습니다.

System Call (시스템 콜)

  • 정의: 사용자 프로그램이 운영체제의 커널이 제공하는 기능(입출력, 프로세스 제어, 메모리 관리 등)을 요청하는 인터페이스
  • 역할: 사용자 모드 → 커널 모드로 안전하게 전환하여 특권 명령 실행 가능하게 해줌
  • 예시
    • read(), write(), open(), close() (파일 조작)
    • fork(), exec(), wait() (프로세스 관리)
    • socket(), connect(), send(), recv() (통신)

2. API (응용 프로그래밍 인터페이스)

  • 정의: 응용 프로그램이 운영체제, 라이브러리, 프레임워크 기능을 쉽게 사용하도록 만든 함수/메서드 집합
  • 역할: 프로그래머가 직접 system call을 다루지 않고, 고수준 함수 호출로 편리하게 접근할 수 있게 함
  • 예시
    • C의 stdio.h 라이브러리 → 내부적으로 read()/write() 시스템 콜 호출
    • Java의 FileInputStream → OS의 파일 입출력 system call 사용

3. 관계

  • System Call: 운영체제 커널 기능에 직접 접근하는 인터페이스
  • API: 응용 프로그램이 쉽게 사용할 수 있도록 제공되는 함수 인터페이스 (보통 system call을 내부적으로 호출)

👉 즉,

  • API는 프로그래머 친화적인 인터페이스
  • System Call은 운영체제 친화적인 인터페이스

통신(IPC)

메세지 전달, 공유 메모리

1. 메시지 전달 (Message Passing)

  • 개념: 프로세스들이 커널(운영체제)을 매개로 하여 메시지를 주고받음
  • 방식
    • send(destination, message)
    • receive(source, message)
  • 특징
    • 프로세스 간에 메모리를 공유하지 않음
    • 분산 시스템에서도 사용 가능 (네트워크 기반 통신)
  • 장점: 단순하고, 프로세스 간 강한 독립성 보장
  • 단점: 커널 개입이 필요해 속도가 느릴 수 있음
  • 예시: 파이프(pipe), 소켓(socket), 메시지 큐(message queue)

2. 공유 메모리 (Shared Memory)

  • 개념: 운영체제가 특정 메모리 영역을 여러 프로세스가 공동으로 접근할 수 있도록 허용
  • 특징
    • 한 번 설정되면 커널 개입 없이 메모리 접근 가능
    • 가장 빠른 IPC 방식
  • 주의점: 동시에 접근할 때 데이터가 꼬이지 않도록 동기화 기법 필요 (세마포어, 뮤텍스)
  • 예시: POSIX shared memory (shm_open, mmap)

3. 비교 정리

구분 메시지 전달 (Message Passing) 공유 메모리 (Shared Memory)

데이터 교환 방식 커널을 통해 메시지 송수신 특정 메모리 영역을 공동 사용
속도 상대적으로 느림 (커널 개입 필요) 빠름 (직접 메모리 접근)
독립성 프로세스 간 강한 독립성 유지 프로세스 간 결합도 높음
동기화 필요성 커널이 동기화 보장 개발자가 직접 동기화 관리 필요
활용 예시 파이프, 소켓, 메시지 큐 shm, mmap, DB 공유 메모리 캐시

정리

  • 메시지 전달: 운영체제 커널을 통해 안전하게 통신, 하지만 속도는 느림.
  • 공유 메모리: 속도가 빠르지만 동기화 문제 해결을 개발자가 직접 해야 함.

프로세스

 

메모리에서 적재되어 실행중인 프로그램

작업을 할당받는 단위

  • 프로세스(Process): 실행 중인 프로그램. 자원(메모리 공간, 파일, 핸들 등)을 할당받는 단위
  • → 커널이 PCB(Process Control Block)로 관리: PID, 상태, 스케줄링 정보, 메모리 정보(페이지 테이블), 열려 있는 파일 등.
  • 스레드(Thread): 프로세스 안의 실행 흐름. CPU 스케줄링의 단위
  • → 각 스레드는 **자신만의 스택, 레지스터 집합, 프로그램 카운터(PC)**를 갖고, 코드·데이터·힙은 프로세스 내 다른 스레드와 공유.

프로세스의 전형적 메모리 구역(가상 메모리 레이아웃)

아래 순서는 보통의 개념도(낮은 주소 → 높은 주소)입니다.

  1. 텍스트(코드) 섹션 (text / code)
  • 기계어 명령이 저장되는 구역 (보통 읽기 전용, 공유 가능)
  • 여러 프로세스가 같은 실행 파일을 돌릴 때 코드 페이지를 공유해 메모리 절약 가능
  1. 데이터 섹션 (data)
  • 초기값이 있는 전역/정적 변수가 위치 (예: int g = 10;)
  • 프로세스마다 개별 복사본을 가짐(스레드 간 공유)
  1. BSS 섹션 (bss)
  • 초기값이 없는 전역/정적 변수 (예: int g2;)
  • 로더가 0으로 초기화하여 시작
  1. 힙(Heap)
  • 동적 할당 영역 (malloc/free, new/delete)
  • 런타임에 필요해질수록 “위로”(주소 증가 방향) 확장되는 것이 관례
  • 여러 스레드가 공유 → 동시성 안전을 위해 할당기 내부에 락/아레나 등 활용
  1. 스택(Stack)
  • 함수 호출 프레임이 쌓이는 영역: 복귀 주소, 매개변수, 지역변수, 저장된 레지스터
  • 일반적으로 “아래로”(주소 감소 방향) 성장
  • 스레드마다 독립 스택을 가짐 (스택 크기는 생성 시 정해지는 경우가 많음)
  1. (플랫폼에 따라) mmap/공유 라이브러리 영역
  • mmap, 공유 객체(.so/.dll) 등이 매핑되는 가변 영역

개념도 (단순화)

낮은 주소
[ 텍스트 ] → [ 데이터 ] → [ BSS ] → [ 힙 ↑ ] … [ mmap 등 ] … [ ↓ 스택 ]
높은 주소

프로그램 카운터(PC, Instruction Pointer)

  • 다음에 실행할 명령의 주소를 가리키는 레지스터.
  • 스레드마다 하나씩 존재(컨텍스트 스위치 시 PC/레지스터 저장·복원).
  • 함수 호출 시: 호출 명령이 복귀 주소를 스택에 저장, PC는 호출 대상 주소로 변경 → ret 시 스택의 복귀 주소로 PC 복원.

스택 자세히

  • 포함 내용:
  • 복귀 주소(return address), 매개변수(arguments), 지역변수, 저장된 레지스터(예: 프레임 포인터/링크 레지스터)
  • 단위: 스택 프레임(함수 호출 1회에 해당)
  • 특징/주의:
    • Stack Overflow: 너무 깊은 재귀, 큰 지역배열 등으로 보호 페이지 침범
    • 스택은 매우 빠름(연속 메모리 + 단순 푸시/팝) / 수명은 블록/스코프에 종속

힙 자세히

  • 동적 수명: 필요할 때 할당(allocate), 더 이상 필요 없으면 해제(free)
  • 특징/주의:
    • 메모리 누수(Leak): 해제를 잊으면 장기 실행 시 문제
    • Double Free / Use-After-Free: 이미 해제한 블록 재사용 → 치명적 버그
    • Fragmentation: 조각화로 인한 큰 블록 할당 실패 가능
    • 멀티스레드 환경: 동시성 이슈(락 경합, 캐시 라인 경합 등) → 현대 할당기는 아레나/슬랩 등으로 완화

데이터 섹션 & BSS 요약

  • 데이터(data): 초기값 있는 전역/정적(실행 파일 안에 초기값 저장)
  • BSS: 초기값 없는 전역/정적(로더가 0으로 초기화)
  • 공통: 프로세스 단위로 소유(스레드들이 공유)

텍스트 섹션 요약

  • 실행 코드 저장
  • 보통 읽기 전용 + 실행 가능, 프로세스 간 공유 가능한 페이지로 매핑 돼 메모리 절약

1. 프로세스 상태 (Process States)

운영체제는 프로세스를 관리하기 위해 여러 상태로 구분합니다. 대표적인 상태는 다음과 같습니다:

  1. New (생성)
    • 프로세스가 생성되고 있는 상태
    • PCB가 만들어지고, OS가 메모리/자원 할당 준비
    • 아직 실행은 시작되지 않음
  2. Ready (준비완료)
    • 실행할 준비가 끝났지만 CPU를 배정받지 못한 상태
    • 메모리와 필요한 자원은 확보 완료
    • 스케줄러가 CPU를 배정하면 곧 실행 상태로 전환됨
  3. Running (실행)
    • CPU를 점유하고 명령어를 수행 중인 상태
    • 단일 코어에서는 한 시점에 하나의 프로세스만 실행 가능
    • 실행 중 인터럽트나 시간 할당량 만료 시 다시 준비 상태로 전환
  4. Waiting (대기, Blocked)
    • I/O 요청, 이벤트 발생 대기 등으로 CPU가 아닌 외부 조건을 기다리는 상태
    • 예: 디스크 입출력 완료, 사용자 입력 등
    • 조건이 만족되면 준비 상태로 전환됨
  5. Terminated (종료)
    • 프로세스 실행이 끝난 상태
    • PCB와 자원이 정리(해제)됨

2. PCB (Process Control Block, 프로세스 제어 블록)

운영체제가 프로세스를 관리하기 위해 유지하는 핵심 데이터 구조.

각 프로세스마다 PCB가 1개씩 존재하며, 프로세스의 “신분증/이력카드” 같은 역할을 함.

PCB에 포함되는 정보

  1. 프로세스 상태
    • New, Ready, Running, Waiting, Terminated 중 현재 상태
  2. 프로그램 카운터 (Program Counter, PC)
    • 다음 실행할 명령어의 주소 저장
  3. CPU 레지스터 정보
    • 범용 레지스터, 스택 포인터, PSW(Program Status Word) 등
    • 컨텍스트 스위칭 시 저장/복원되어 실행 재개 가능
  4. 스케줄링 정보
    • 우선순위, 스케줄링 큐 포인터, CPU 사용 시간 등
  5. 메모리 관리 정보
    • 페이지 테이블, 세그먼트 테이블, 메모리 경계 레지스터 등
    • 해당 프로세스가 접근 가능한 메모리 영역 기록
  6. 계정 및 자원 정보
    • 파일 핸들, 입출력 장치 상태, 소유자 ID, 사용 권한 등

3. 정리 그림 (흐름도)

프로세스 상태 전이 예시

   [New]
     ↓
   [Ready] ← (I/O 완료) ← [Waiting]
     ↓ ↑
  (스케줄링)   (시간 종료/인터럽트)
     ↓ ↑
   [Running]
     ↓
  [Terminated]


👉 요약:

  • 프로세스 상태는 생성 → 준비 → 실행 → 대기/종료로 전이되며, CPU·I/O 스케줄링에 따라 바뀜.
  • PCB는 프로세스의 모든 관리 정보를 담는 자료구조로, 컨텍스트 스위칭스케줄링에 필수적임.

쓰레드

작업을 실행하는 단위

프로세스 안의 실행 흐름.

CPU 스케줄링의 단위

→ 각 스레드는 **자신만의 스택, 레지스터 집합, 프로그램 카운터(PC)**를 갖고,

코드·데이터·힙은 프로세스 내 다른 스레드와 공유

TCB(Thread Control Block)에 포함되는 정보

스레드별로 필요한 최소 정보만 따로 가짐:

  • 스레드 ID (TID)
  • 프로그램 카운터(PC): 다음 실행할 명령어
  • 레지스터 집합: 현재 CPU 레지스터 상태
  • 스택 포인터: 각 스레드는 자기만의 스택을 가짐
  • 스케줄링 정보: 우선순위, 상태(Running, Ready, Waiting 등)
  • (운영체제에 따라) 신호 처리 정보, TLS(Thread-Local Storage) 등

프로세스 스케줄링 (Process Scheduling)

운영체제는 동시에 여러 프로세스가 실행되는 환경에서, CPU를 누구에게 줄 것인지를 결정해야 합니다.

이를 담당하는 것이 **프로세스 스케줄러(Process Scheduler)**입니다.

  • 목표:
    • CPU 및 시스템 자원의 효율적 활용
    • 사용자에게 빠른 응답 제공 (대화형 시스템)
    • 공평성과 우선순위 보장
  • 잡 큐(Job Queue):
    • 시스템에 들어온 모든 프로세스가 대기하는 큐
    • 여기서 스케줄러가 선택해 실행 대기열(Ready Queue)로 보냄

2. 스케줄러의 종류

(1) 장기 스케줄러 (Long-Term Scheduler, Job Scheduler)

  • 역할: 어떤 프로세스를 메모리에 올려 실행할지 결정
  • 특징:
    • 실행할 **잡 큐(Job Pool)**에서 프로세스를 선택 → 메모리 적재
    • 실행 프로세스의 전체 수를 제어하여 CPU와 I/O의 균형 유지
  • 효과:
    • 입출력 중심 프로세스CPU 중심 프로세스를 적절히 혼합하여 자원 활용 극대화
  • 예시: 배치 시스템(batch system)에서 주로 사용됨
  • 현대의 시분할 시스템에서는 장기 스케줄러가 거의 사용되지 않음 (대부분 시스템이 자동으로 처리)

(2) 단기 스케줄러 (Short-Term Scheduler, CPU Scheduler)

  • 역할: **실행 준비(Ready Queue)**에 있는 프로세스 중, 누구에게 CPU를 줄지 결정
  • 실행 시점: 매우 짧은 시간 단위로 동작 (ms 단위)
  • 효과: 시스템 반응성과 CPU 활용률에 직접적 영향
  • 프로세스 특성 고려:
    • 입출력 중심 프로세스(I/O-bound process)
      • CPU는 짧게 쓰고, I/O 요청이 잦음
      • 빨리 실행해서 I/O 장치가 놀지 않도록 해주는 것이 중요
    • CPU 중심 프로세스(CPU-bound process)
      • 계산 위주, CPU를 오래 사용
      • I/O 요청이 적음
    • ⇒ 두 종류를 적절히 섞어야 전체 자원 활용이 좋아짐

(3) 중기 스케줄러 (Medium-Term Scheduler) – 선택적

  • 역할: 프로세스 실행을 일시 중단(suspend) → 메모리에서 내보냈다가 → 나중에 복귀
  • 이 과정을 **스와핑(Swapping)**이라 함
  • 효과:
    • 메모리 부족 시 부하를 줄이고, 다중 프로그래밍 정도를 제어
    • 우선순위 있는 프로세스가 자원을 빨리 확보할 수 있게 함

3. 스와핑 (Swapping)

  • 정의: 프로세스를 **보조기억장치(디스크)**로 내보냈다가, 필요할 때 다시 메모리로 불러오는 과정
  • 특징:
    • 프로세스는 원래 실행되던 시점부터 재개됨 (PCB에 상태 저장)
    • 메모리 공간 확보 및 CPU 이용률 최적화
  • 장점: 메모리 관리 유연성 증가, 다중 프로그래밍 향상
  • 단점: 디스크 I/O 오버헤드 발생 → 잦으면 성능 저하

4. 정리 표

스케줄러 종류 동작 위치 역할 실행 빈도 주요 목적

장기 스케줄러 잡 큐 → 메모리 어떤 프로세스를 실행시킬지 선택 느림 (분/초 단위) CPU/IO 밸런스 유지, 다중 프로그래밍 정도 제어
단기 스케줄러 준비 큐 → CPU 다음 실행할 프로세스 선택 매우 빠름 (ms 단위) 응답속도 개선, CPU 활용 극대화
중기 스케줄러 메모리 ↔ 디스크 프로세스 일시 중단/재개 (스와핑) 상황에 따라 메모리 회수, 우선순위 관리

👉 요약:

  • 장기 스케줄러: 잡 큐에서 메모리로 올릴 프로세스 선택 (CPU vs I/O 균형).
  • 단기 스케줄러: Ready Queue에서 CPU를 줄 프로세스 선택 (밀리초 단위, 즉각적).
  • 중기 스케줄러: 메모리 ↔ 디스크로 스와핑, 부하 조절.

프로세스 생성 (Process Creation)

운영체제에서 새로운 프로세스가 생성될 때, 일반적으로 다음 단계가 일어납니다.

  1. 부모 프로세스(Parent Process)가 자식 프로세스(Child Process)를 생성
    • Unix 계열: fork() 시스템 콜 사용
    • Windows 계열: CreateProcess() API 사용
  2. 부모와 자식의 실행 관계
    • 병행 실행(Concurrent Execution): 부모는 계속 실행, 자식은 독립적으로 실행
    • 부모가 대기(Wait): 부모는 wait()를 호출하여 자식이 끝날 때까지 기다림
  3. 프로세스 주소 공간
    • 자식은 **부모의 복사본(Copy)**을 가짐 (코드, 데이터, 스택 등)
    • 하지만 별도의 PID(Process ID) 부여 → 독립된 실행 단위
    • 자식이 원하면 exec() 계열 호출로 새로운 프로그램을 덮어씀

2. 프로세스 종료와 관련 개념

(1) 정상 종료

  • 자식 프로세스가 exit() 호출 시 종료
  • 운영체제는 **종료 상태(exit status)**를 부모에게 전달
  • 부모는 wait()를 호출해 종료 상태를 회수 → 자식 PCB가 해제됨

(2) 좀비 프로세스 (Zombie Process)

  • 정의: 자식이 종료되었지만, 부모가 wait()를 호출하지 않아 PCB가 해제되지 않은 상태
  • 특징:
    • 실행은 끝났지만 PCB는 그대로 남음 → 프로세스 테이블에 기록 유지
    • 상태는 “Z” (Zombie)로 표시
    • 다수의 좀비가 생기면 프로세스 테이블 고갈 문제 발생

(3) 고아 프로세스 (Orphan Process)

  • 정의: 부모가 먼저 종료되어 부모가 없는 자식 프로세스
  • 처리 방법:
    • Unix 계열에서는 **init 프로세스(PID 1)**가 고아 프로세스를 자동으로 인수 → 정상적으로 실행/종료 가능
    • 따라서 고아 프로세스는 시스템에 큰 문제는 일으키지 않음

3. 그림으로 정리

부모 fork()
   ├─ 부모 계속 실행
   └─ 자식 프로세스 생성
         ├─ 부모와 병행 실행
         ├─ 부모가 wait() 호출 → 정상 회수
         ├─ 부모가 회수 안 함 → [좀비 프로세스]
         └─ 부모 먼저 종료 → [고아 프로세스]


✅ 요약:

  • 자식은 부모의 복사본으로 생성되지만, 독립된 PID를 가짐.
  • 부모와 자식은 병행 실행 가능, 또는 부모가 wait()로 자식 종료까지 대기 가능.
  • 좀비 프로세스: 자식 종료 후 부모가 회수하지 않음 → PCB만 남음.
  • 고아 프로세스: 부모가 먼저 종료 → init 프로세스가 대신 관리.

프로세스 간 통신(IPC, Inter-Process Communication)

  • 필요성:
    • 다중 프로세스 환경에서 데이터 교환, 동기화, 협력 수행을 위해 사용
    • OS는 안전성과 효율성을 위해 여러 가지 IPC 메커니즘을 제공

2. IPC 모델

(1) 공유 메모리 시스템 (Shared Memory)

  • 개념: 두 프로세스가 같은 메모리 영역을 공유하여 데이터를 주고받음
  • 특징:
    • 가장 빠른 통신 방법 (커널 개입 최소화, 단순 메모리 접근)
    • 하지만 동기화 문제(레이스 컨디션) 발생 가능 → 세마포어, 뮤텍스 필요
  • 예시: 생산자-소비자 문제, 대규모 데이터 교환

(2) 메시지 전달 시스템 (Message Passing)

  • 개념: 운영체제가 제공하는 커널 버퍼를 통해 프로세스들이 메시지를 송수신
  • 특징:
    • 커널을 거쳐야 해서 상대적으로 느림
    • 동기화 문제는 OS가 관리 → 구현 간단, 안전성 높음
  • 방식:
    • 직접 통신: 프로세스들이 서로의 ID를 알고 직접 메시지 교환
    • 간접 통신: 메일박스(mailbox), 포트(port) 같은 추상화된 채널 이용

3. 주요 IPC 기법

(1) 파이프 (Pipe)

  • 개념: 두 프로세스 간 단방향 통신 채널
  • 특징:
    • 한쪽은 쓰기(write), 다른 쪽은 읽기(read) 전용
    • 부모-자식 프로세스 간 자주 사용
    • 익명 파이프: 같은 계열 프로세스 간
    • 명명된 파이프(Named Pipe, FIFO): 무관한 프로세스 간도 가능

(2) 메시지 큐 (Message Queue)

  • 개념: 커널이 제공하는 큐(Queue) 자료구조를 사용
  • 특징:
    • 비동기적 메시지 전달 가능
    • 메시지 우선순위 지정 가능

(3) 소켓 (Socket)

  • 개념: 네트워크 기반 IPC 기법
  • 특징:
    • 동일 시스템 내 프로세스뿐 아니라, 다른 시스템 간 통신도 가능
    • TCP/UDP 기반으로 동작 → 클라이언트-서버 모델에 적합

(4) RPC (Remote Procedure Call)

  • 개념: 원격 프로세스의 함수를 마치 로컬 함수처럼 호출하는 기법
  • 특징:
    • 네트워크/분산 시스템에서 자주 사용
    • 호출 측은 네트워크 세부사항을 몰라도 함수 호출처럼 사용 가능
    • 내부적으로는 메시지 전달, 직렬화, 소켓 통신 등으로 구현

(5) 기타 IPC

  • 세마포어 (Semaphore): 동기화 및 상호 배제를 위한 카운터 기반 메커니즘
  • 공유 파일 (Shared File): 파일을 매개로 데이터 교환, 단 느리고 동기화 필요

4. 정리 표

방식 특징 장점 단점 활용 예

공유 메모리 메모리 영역 직접 공유 빠름 동기화 필요 생산자-소비자 버퍼
메시지 전달 커널이 메시지 송수신 관리 구현 단순, 안전 느림 분산 환경 프로세스 간 통신
파이프 단방향 스트림 간단, 부모-자식 간 통신 용이 단방향 제한 Unix 파이프(`
메시지 큐 커널의 큐 이용 비동기, 우선순위 지원 커널 자원 한정 IPC 채팅, 로깅
소켓 네트워크 기반 원격 통신 가능 구현 복잡 웹 서버-클라이언트
RPC 원격 함수 호출 추상화, 편리 내부 구현 복잡 분산 시스템, 마이크로서비스

✅ 요약:

  • IPC 모델: 공유 메모리 vs 메시지 전달
  • 주요 기법: 파이프, 메시지 큐, 소켓, RPC 등
  • 선택 기준: 데이터 크기, 속도 요구, 안정성, 프로세스 관계(같은 시스템 vs 분산 시스템)

 

스레드(Thread)의 정의

  • CPU 이용의 기본 단위
  • 프로세스 내에서 실행되는 실행 흐름 단위를 의미합니다.
  • 프로세스는 최소 1개의 스레드를 가지며, 멀티스레딩을 통해 하나의 프로세스에서 여러 스레드가 병렬로 실행될 수 있습니다.

스레드의 구성 요소

스레드마다 독립적으로 가지는 부분:

  • Thread ID : 스레드 고유 식별자
  • PC(Program Counter) : 명령어의 실행 위치
  • 레지스터 집합 : 연산에 필요한 임시 데이터 저장
  • 스택(Stack) : 함수 호출, 지역 변수 저장

스레드가 같은 프로세스 내에서 공유하는 부분:

  • 코드(Code) 영역
  • 데이터(Data) 영역
  • 운영체제 자원 (파일 핸들, 소켓 등)

스레드의 종류

  1. 사용자 스레드 (User Thread)
    • 사용자 수준 라이브러리에서 지원하는 스레드
    • 커널이 직접 인식하지 못하고, 하나의 프로세스 단위로 스케줄링됨
    • 장점: 생성/전환 속도가 빠르고, 오버헤드가 적음
    • 단점: 하나의 스레드가 커널 호출로 블록되면, 전체 프로세스가 블록될 수 있음
  2. 커널 스레드 (Kernel Thread)
    • 운영체제 커널이 직접 관리하는 스레드
    • 커널이 스케줄링하므로 다중 CPU 활용 가능
    • 단점: 생성/전환 시 시스템 콜이 필요해 비용이 크다

👉 정리하면, 스레드 = 프로세스 내 실행 흐름의 최소 단위이고,

**독립적인 실행 상태(PC, 레지스터, 스택)**는 따로 가지지만,

코드/데이터/자원은 같은 프로세스 내 스레드끼리 공유한다는 특징이 있습니다.

fork()와 스레드

  • POSIX 표준에 따르면 fork()는 호출한 스레드만 복사합니다.
  • 즉:
    • 부모 프로세스 → 여러 스레드 존재 가능
    • 자식 프로세스 → fork를 호출한 그 스레드만 존재 (다른 스레드는 복사되지 않음)
  • 이유: 모든 스레드를 그대로 복제하면 동기화 상태, 락(lock) 보유 상태 등이 복잡해져서 일관성을 깨뜨릴 수 있기 때문이에요.

👉 따라서, fork() 후에는 자식 프로세스는 단일 스레드 상태로 시작하고, 필요하면 exec()를 호출해 새 프로그램으로 덮어씌우는 경우가 많습니다.


🔹 exec()

  • exec 계열 함수(execl, execv, execve 등)는 현재 프로세스 전체를 새로운 프로그램으로 대체합니다.
  • 프로세스 메모리 공간, 코드, 데이터, 스택이 모두 교체되고, 기존 스레드는 모두 사라집니다.
  • 즉:
    • 호출한 프로세스의 PID는 유지됨
    • 실행 이미지는 완전히 새 프로그램으로 바뀜
    • 남는 스레드 없음 → 새 프로그램은 항상 단일 스레드로 시작

✅ 정리하면:

  • fork() → 호출한 스레드만 복사 (자식은 단일 스레드 상태).
  • exec() → 전체 프로세스를 대체 (모든 스레드 사라지고 새 프로그램 시작).

🔹 스레드 풀(Thread Pool)

✅ 개념

  • 프로그램 시작 시 일정 개수의 스레드를 미리 생성해두고, 이 스레드들을 작업(Task) 큐에 있는 일을 처리하는 데 재사용하는 방식.
  • 즉, 매번 새로운 스레드를 만들고 없애는 대신, **재활용(Recycling)**하는 구조.

✅ 동작 방식

  1. 스레드 풀 초기화 → 정해진 개수의 워커 스레드(worker thread) 생성.
  2. 클라이언트/사용자가 작업을 요청 → 작업이 **작업 큐(task queue)**에 들어감.
  3. 대기 중이던 워커 스레드가 큐에서 작업을 꺼내 실행.
  4. 작업 완료 후 → 스레드는 종료되지 않고 다시 큐를 감시하며 다음 작업 대기.

✅ 장점

  • 생성/제거 오버헤드 감소: 스레드를 매번 만들지 않고 재사용하므로 성능 향상.
  • 동시성 제어 용이: 풀 크기를 제한하면 동시에 실행되는 스레드 수를 조절 가능 → CPU 과부하 방지.
  • 응답 시간 단축: 요청이 들어올 때마다 바로 실행할 스레드가 준비되어 있음.

❌ 단점

  • 풀 크기 설정 어려움: 너무 작으면 병목, 너무 크면 문맥 전환 오버헤드 발생.
  • 장시간 블로킹 작업 문제: 워커 스레드가 오래 점유되면 다른 작업이 밀릴 수 있음.
  • 복잡성 증가: 큐 관리, 예외 처리, 동기화 관리가 필요.

🔹 동기(Synchronous) vs 비동기(Asynchronous)

  • 동기(Sync)
    • 요청한 작업이 끝날 때까지 호출한 쪽이 결과를 기다림.
    • 제어 흐름이 작업 완료 시점과 맞춰져 있음.
    • 예: read() 호출 → 데이터 다 읽을 때까지 반환하지 않음.
  • 비동기(Async)
    • 요청한 작업을 백그라운드에서 수행하고, 호출한 쪽은 즉시 반환받음.
    • 결과는 나중에 이벤트, 콜백, Future/Promise 등을 통해 알림.
    • 예: aio_read() → 바로 return, 읽기 완료되면 알림.

👉 즉, “동기는 결과 반환을 기다리고, 비동기는 기다리지 않는다”는 표현은 정확합니다


🔹 블로킹(Blocking) vs 논블로킹(Non-blocking)

  • 블로킹(Blocking)
    • 호출한 함수가 즉시 결과를 줄 수 없으면 → 호출한 스레드를 멈추고 대기.
    • 제어권을 커널/라이브러리 쪽에 넘겨주고, 작업이 끝날 때까지 안 돌려줌.
    • 예: read(fd, buf, size)에서 읽을 데이터가 없으면, 데이터가 올 때까지 멈춤.
  • 논블로킹(Non-blocking)
    • 호출한 함수가 즉시 결과를 줄 수 없으면 에러/특정 코드(EAGAIN 등)를 반환.
    • 즉, 제어권을 바로 돌려줌.
    • 예: read(fd, buf, size) 호출 시 읽을 게 없으면 1 즉시 반환.

👉 따라서 “블로킹은 제어권을 넘기고, 논블로킹은 제어권을 넘기지 않는다”는 표현은 살짝 부정확해요.

정확히는:

  • 블로킹 → 제어권을 넘겨주고 작업 완료까지 반환 안 함.
  • 논블로킹 → 제어권을 넘겨주긴 하지만, 결과 없으면 즉시 반환해서 호출자가 다시 사용할 수 있음.

🔹 한눈에 정리 (표)

구분 동기 비동기

블로킹 작업 끝날 때까지 기다림 (ex. read()) 콜백/알림을 쓰지만, 호출 자체는 블로킹됨 (드묾)
논블로킹 즉시 반환, 호출자가 반복 확인해야 함 (ex. read() + 반복) 즉시 반환 + 완료되면 알림/콜백 (ex. 이벤트 기반 I/O)

✅ 정리하면:

  • 동기/비동기 → “작업 완료 통보 방식”
  • 블로킹/논블로킹 → “호출 시 제어권 반환 여부”

 

CPU 스케줄링

 

CPU를 프로세스 들 간에 교환함

일반적으로 프로세스 스케줄링을 뜻함

CPU 스케줄러 (CPU Scheduler)

CPU가 유휴 상태가 될 때마다 **운영체제(OS)**는 **준비 완료 큐(Ready Queue)**에 있는 프로세스들 중 하나를 골라 실행합니다.

이 과정은 **단기 스케줄러(Short-term Scheduler)**가 담당합니다.

  • 역할: 메모리 내에서 실행 준비가 된 프로세스 중 하나를 선택 → CPU에 할당
  • 목표: CPU 이용률 극대화, 처리량 증가, 대기 시간·응답 시간 최소화, 공정성 보장

1. 스케줄링 종류

① 비선점 스케줄링 (Non-preemptive Scheduling)

  • 한 프로세스가 CPU를 할당받으면 자신이 종료되거나 대기 상태로 전환될 때까지 CPU를 계속 사용
  • 다른 프로세스는 기다려야 함
  • 장점: 컨텍스트 스위칭 오버헤드가 적음
  • 단점: 응답 시간이 길어질 수 있음
  • 예시 알고리즘
    • FCFS (First Come First Serve)
    • SJF (Shortest Job First)
    • HRN (Highest Response Ratio Next)

② 선점 스케줄링 (Preemptive Scheduling)

  • 실행 중인 프로세스가 있더라도 우선순위가 더 높은 프로세스가 도착하면 CPU를 빼앗아 올 수 있음
  • 장점: 응답 시간이 짧아져 대화형 시스템에 적합
  • 단점: 잦은 컨텍스트 스위칭으로 오버헤드 증가, 교착 상태(deadlock)나 기아(starvation) 발생 가능
  • 에이징(Aging) 기법으로 낮은 우선순위 프로세스의 기아 문제를 완화
  • 예시 알고리즘
    • SRTF (Shortest Remaining Time First)
    • Priority Scheduling
    • Round Robin (RR)
    • Multilevel Queue / Feedback Queue

2. 디스패처 (Dispatcher)

  • 정의: 단기 스케줄러가 선택한 프로세스에게 실제로 CPU 제어권을 넘겨주는 모듈
  • 주요 역할
    • 컨텍스트 스위칭 (context switching)
    • 사용자 모드 전환
    • 프로그램의 올바른 위치(PC, 레지스터)에서 실행 재개
  • 디스패처 지연 (Dispatcher Latency)
    • 하나의 프로세스에서 다른 프로세스로 CPU 제어가 넘어가는 데 걸리는 시간
    • → 너무 길면 시스템 성능 저하

📌 정리하면,

  • 단기 스케줄러: 어떤 프로세스가 CPU를 쓸지 선택
  • 디스패처: 실제로 CPU를 넘겨주는 실행자
  • 비선점/선점 여부에 따라 응답성과 공정성이 달라짐

 

임계구역 문제

임계구역 문제 (Critical Section Problem)

여러 프로세스가 공유 자원(메모리, 파일, I/O 장치 등)에 접근할 때 **경쟁 조건(Race Condition)**을 피하기 위해 필요한 규칙을 정의한 것. 이를 위해 운영체제는 임계구역에 대한 접근을 제어하는 프로토콜을 설계해야 함.

1. 요구 조건 (3가지 조건)

  1. 상호 배제 (Mutual Exclusion)
    • 한 번에 하나의 프로세스만 임계구역에 진입할 수 있어야 함.
    • 다른 프로세스가 이미 임계구역에 있으면, 나머지는 대기해야 함.
  2. 진행 (Progress)
    • 임계구역에 들어가려는 프로세스가 없을 경우, 들어갈 프로세스를 결정하는 데 불필요한 지연이 없어야 함.
    • 즉, CPU가 놀고 있는데도 임계구역 진입을 막으면 안 됨.
  3. 한정된 대기 (Bounded Waiting)
    • 특정 프로세스가 임계구역 진입을 무한정 기다리게 두어서는 안 됨.
    • 언젠가는 반드시 임계구역에 들어갈 수 있어야 한다는 공정성 보장 조건.

커널의 선점 여부

운영체제의 커널이 프로세스를 언제까지 실행시키는지와 관련 있음.

1. 비선점형 커널 (Non-preemptive Kernel)

  • 커널 모드에 들어간 프로세스는 스스로 CPU를 양보하거나 작업이 끝날 때까지 계속 실행.
  • 장점: 동기화가 상대적으로 단순 → 임계구역 충돌 발생 확률 낮음.
  • 단점: 한 프로세스가 오래 점유하면 다른 프로세스 대기 시간이 길어짐 → 시스템 반응성 저하.

2. 선점형 커널 (Preemptive Kernel)

  • 커널 모드에서 실행 중이더라도 운영체제가 강제로 CPU를 빼앗아 다른 프로세스 실행 가능.
  • 장점: 시스템 반응성 ↑ (특히 실시간 시스템에 적합).
  • 단점: 임계구역에서 선점되면 동기화 문제 발생 → 세마포어, 뮤텍스, 모니터 같은 동기화 도구 필요.

✅ 요약

  • 임계구역 문제 → 상호배제, 진행, 한정된 대기 조건 충족 필요.
  • 비선점형 커널은 단순하지만 비효율적, 선점형 커널은 효율적이지만 동기화 기법 필요.

피터슨 알고리즘

피터슨 알고리즘 (Peterson’s Algorithm)

  • 정의: 두 개의 프로세스가 하나의 공유 자원을 안전하게 사용할 수 있도록 보장하는 소프트웨어 기반 임계구역 해결 알고리즘.
  • 아이디어: "상호 배제"를 위해 플래그 변수 + turn 변수를 이용해 락(Locking) 개념을 구현.

1. 기본 구조

  • flag[2]: 프로세스가 임계구역에 들어가고 싶다는 의사를 표시 (true/false)
  • turn: 두 프로세스 중 누구 차례인지를 알려주는 변수
// 프로세스 i의 코드 (i = 0 or 1)
do {
    flag[i] = true;              // 임계구역 진입 의사 표시
    turn = j;                    // 상대방 차례로 설정
    while (flag[j] && turn == j); // 상대방이 원하고 차례이면 대기

    // ---- 임계구역 시작 ----
    critical_section();
    // ---- 임계구역 끝 ----

    flag[i] = false;             // 임계구역 나옴
    remainder_section();
} while (true);


2. 특징 (3대 조건 충족 여부)

  1. 상호 배제 (Mutual Exclusion)
    • 두 프로세스가 동시에 임계구역에 진입할 수 없음.
    • flag와 turn 조합으로 보장.
  2. 진행 (Progress)
    • 임계구역을 원하지 않는 프로세스는 다른 프로세스의 진입을 방해하지 않음.
  3. 한정된 대기 (Bounded Waiting)
    • 한 프로세스가 무한정 기다리지 않도록 보장 (turn 변수가 번갈아 기회를 줌).

3. 장단점

  • 장점
    • 순수 소프트웨어적 방법 (하드웨어 지원 필요 없음).
    • 임계구역 문제의 3대 조건을 모두 만족.
  • 단점
    • 두 프로세스 환경에서만 동작 (N개 프로세스는 불가능).
    • 현대의 멀티코어 환경에서는 메모리 재정렬/캐시 동기화 문제 때문에 실제로는 쓰이지 않음.
    • 대신 하드웨어 기반 Test-and-Set, Compare-and-Swap 같은 명령어나 세마포어, 뮤텍스, 모니터를 사용.

4. 락킹과의 관계

  • Peterson 알고리즘은 락(Lock) 개념을 소프트웨어적으로 구현한 초기 방식.
  • 단일 CPU 환경에서는 "인터럽트 금지" 방식으로도 해결 가능했지만, 멀티코어·멀티프로세서 환경에서는 하드웨어 락 지원 없이는 Peterson 알고리즘이 깨질 수 있음.

👉 정리: Peterson 알고리즘은 교과서적 중요성이 크고, 실제 시스템에서는 하드웨어 명령어나 동기화 도구가 더 많이 사용됨.

뮤텍스 락 (Mutex Lock)

  • Mutual Exclusion의 줄임말.
  • 임계구역(critical section)에 진입하기 전에 **락(lock)**을 획득해야 하고, 임계구역에서 나오면서 반드시 **락을 해제(unlock)**해야 함.
  • 한 시점에는 오직 하나의 프로세스/스레드만 락을 보유할 수 있음 → 상호 배제 보장.

1. 기본 원리

// Pseudo code
acquire(lock);     // 임계구역 들어가기 전에 락 획득
critical_section();
release(lock);     // 임계구역 빠져나올 때 락 반환

  • acquire(): 락을 얻을 수 있을 때까지 기다림.
  • release(): 락을 다른 프로세스가 사용할 수 있도록 반환.

2. 장점

  • 구현이 단순하고 직관적.
  • 선점형 커널 환경에서도 안전하게 임계구역 보호 가능.
  • 현대 운영체제에서 스레드 동기화 기본 도구로 널리 사용됨.

3. 단점

  1. 바쁜 대기 (Busy Waiting, Spin Lock)
    • 프로세스가 락을 얻을 때까지 계속 루프를 돌며 기다림 → CPU 낭비.
    • 예:
    • while (lock == 1); // 다른 스레드가 락 반환할 때까지 계속 반복
  2. 데드락(Deadlock) 가능
    • 락을 해제하지 못하거나, 여러 락을 교착 상태로 요청할 경우 발생.
  3. 우선순위 역전(Priority Inversion)
    • 낮은 우선순위 프로세스가 락을 보유하면, 높은 우선순위 프로세스도 기다려야 함.

4. 개선 방법

  • 세마포어(Semaphore): 대기 상태를 큐에 넣고 블록(block) → 바쁜 대기 해결.
  • 모니터(Monitor): 고수준 언어에서 동기화 지원.
  • 혼합 기법: 짧은 시간은 스핀 락, 오래 걸리면 블록 (하이브리드 락).

✅ 요약

  • 뮤텍스 락은 상호 배제를 보장하는 가장 단순한 방법.
  • 하지만 기본 구현은 바쁜 대기(Spin Lock) 문제를 가진다.
  • 실제 시스템에서는 세마포어나 모니터 등과 함께 개선된 형태로 사용된다.

세마포어 (Semaphore)

  • 1965년 Dijkstra가 제안한 동기화 기법.
  • 공유 자원에 대한 접근을 제어하기 위해 **정수 변수 S와 두 개의 원자적 연산(wait, signal)**을 사용.
  • 커널이 제공하는 원자적 연산이기 때문에 동시 실행 환경에서도 안전하게 동작.

1. 두 가지 연산

  1. wait (P 연산)
    • 자원을 얻기 전 검사
    • 사용 가능 자원이 없으면 대기
  2. wait(S) { while (S <= 0); // 바쁜 대기 (Spin) or 블록 S--; }
  3. signal (V 연산)
    • 자원 사용이 끝난 후 반환
  4. signal(S) { S++; }

2. 세마포어의 종류

  1. 이진 세마포어 (Binary Semaphore)
    • 값이 0 또는 1만 가짐.
    • 사실상 뮤텍스 락과 동일하게 동작.
    • 임계구역 보호에 사용.
  2. 계수 세마포어 (Counting Semaphore)
    • 값이 0 이상 정수.
    • 동시에 여러 개의 프로세스가 공유 자원에 접근할 수 있도록 허용.
    • 예: DB 연결 풀, 프린터 3대 → 초기값 S=3.

3. 특징

장점

  • 뮤텍스보다 일반적: 여러 자원 동시 관리 가능.
  • 바쁜 대기 문제 해결 가능: 대기 중인 프로세스를 큐에 넣어 블록시키고, signal 시 깨움.

⚠️ 단점

  • 프로그래밍 실수 위험 (wait/signal 불일치 → 데드락, 기아 문제).
  • 관리가 어렵기 때문에 고수준 언어에서는 **모니터(Monitor)**가 더 선호됨.

4. Mutex vs Semaphore 비교

구분 뮤텍스(Mutex) 세마포어(Semaphore)

자원 수 1개만 보호 N개 자원까지 보호 가능
0/1 (binary) 0 이상 정수
소유권 스레드가 소유 (owner만 unlock 가능) 소유 개념 없음
사용 용도 임계구역 보호 자원 개수 제어, 프로세스 동기화
구현 난이도 단순 상대적으로 복잡

✅ 요약

  • 세마포어 = 정수 변수 + wait/signal 연산
  • 이진 세마포어 = 뮤텍스
  • 계수 세마포어 = 여러 자원 관리 가능
  • 하지만 프로그래밍 복잡성 때문에 실무에서는 주로 뮤텍스 + 조건변수, 모니터를 사용

1. 모니터(Monitor)란?

  • 고수준 언어에서 제공하는 동기화 도구
  • 세마포어처럼 직접 wait/signal을 다루는 대신, 언어 차원에서 임계구역 진입/대기/신호를 관리해줌.
  • 즉, 프로그래머가 동기화 로직을 일일이 짜는 대신, 모니터가 자동으로 상호배제를 보장해 줌.

➡️ 세마포어의 단점(코드 복잡성, wait/signal 불일치 → 교착상태 가능)을 해결하기 위한 추상화 기법.


2. 모니터의 구성 요소

  1. 공유 변수 (Shared Variables)
    • 모니터 내부에서만 접근 가능한 자원(데이터 구조).
  2. 프로시저 (Procedures)
    • 공유 변수를 접근할 수 있는 루틴.
    • 임계구역은 이 루틴 안에서만 존재하며, 자동으로 상호배제 보장.
  3. 조건 변수 (Condition Variables)
    • 모니터 안에서 프로세스 동기화를 위해 사용.
    • wait() : 현재 프로세스를 조건 대기 큐에 넣고, 다른 프로세스에게 제어권 넘김.
    • signal() : 대기 중인 프로세스를 깨움.

3. 모니터 동작 방식

  • 한 번에 하나의 프로세스만 모니터 내부 실행 가능.
  • 다른 프로세스가 들어오면 자동으로 블록됨 (상호배제 보장).
  • 조건 변수로 대기/신호를 제어 → 세마포어 wait/signal과 유사하지만 자동 관리됨.

4. 예시 (생산자-소비자 문제)

monitor ProducerConsumer {
    int buffer[N];
    int count = 0;
    condition notFull, notEmpty;

    procedure insert(item) {
        if (count == N) wait(notFull);  // 버퍼가 꽉 찼으면 대기
        buffer[count++] = item;
        signal(notEmpty);               // 소비자에게 알림
    }

    procedure remove() {
        if (count == 0) wait(notEmpty); // 버퍼가 비었으면 대기
        item = buffer[--count];
        signal(notFull);                // 생산자에게 알림
        return item;
    }
}

  • 생산자는 insert() 호출 → 버퍼 꽉 차면 wait(notFull).
  • 소비자는 remove() 호출 → 버퍼 비면 wait(notEmpty).
  • 모니터는 상호배제 + 동기화를 자동 관리.

5. 장단점

장점

  • 상호배제 자동 보장 → 프로그래머가 실수로 놓칠 위험 감소.
  • 코드 가독성 높고, 동기화 오류 줄어듦.
  • 고급 언어(Java, C#, Python 등)에서 널리 지원 (synchronized, lock 구문 등).

⚠️ 단점

  • 구현이 복잡해 하드웨어/저수준 언어(C)에서는 직접 지원 어렵다.
  • 잘못된 조건 변수 사용 시 기아 가능성.

6. 현대 운영체제와 모니터

  • Java → synchronized 블록, wait() / notify()
  • C# → lock, Monitor.Wait() / Monitor.Pulse()
  • Python → threading.Condition

즉, 세마포어는 OS 수준의 원시적 도구라면,

모니터는 언어/런타임 수준의 고수준 동기화 도구라 할 수 있음.


✅ 요약

  • 모니터는 세마포어보다 추상화된 동기화 도구.
  • 프로세스/스레드가 동시에 모니터 안에 들어올 수 없도록 상호배제를 자동 보장.
  • 조건 변수(wait, signal)를 통해 세밀한 동기화 제어 가능.

 

교착상태 & 기아

1. 교착상태 (Deadlock)

  • 정의: 프로세스들이 서로가 가진 자원을 기다리며 무한 대기 상태에 빠진 것.
  • 예: P1은 프린터를 가지고 플로터를 기다리고, P2는 플로터를 가지고 프린터를 기다리는 경우.

발생 조건 (Coffman의 4가지 조건 – 모두 만족해야 발생)

  1. 상호 배제 (Mutual Exclusion)
  2. 자원은 한 번에 한 프로세스만 사용 가능.
  3. 점유 대기 (Hold and Wait)
  4. 최소 하나의 자원을 점유한 채로 다른 자원을 기다림.
  5. 비선점 (No Preemption)
  6. 할당된 자원은 강제로 빼앗을 수 없음.
  7. 순환 대기 (Circular Wait)
  8. 프로세스들이 원형으로 서로가 가진 자원을 기다림.

2. 기아 (Starvation)

  • 정의: 특정 프로세스가 우선순위 문제나 자원 할당 정책 때문에 무한히 자원을 얻지 못하고 기다리는 상태.
  • 예: 우선순위 스케줄링에서 낮은 우선순위 프로세스가 계속 밀려 실행되지 못하는 경우.

원인

  • 우선순위 기반 스케줄링
  • 자원 할당 시 특정 프로세스에 불리한 정책
  • 무한 대기 큐 구조

3. 우선순위 역전 (Priority Inversion)

  • 정의: 낮은 우선순위 프로세스가 자원을 가지고 있어서, 높은 우선순위 프로세스가 그 자원을 기다리며 실행되지 못하는 상황.
  • 중간 우선순위 프로세스가 계속 실행되면 높은 우선순위 프로세스는 더 오래 기다리게 됨 → 실질적 기아 발생.

해결 방법

  • Priority Inheritance (우선순위 상속):
  • 낮은 우선순위 프로세스가 자원을 가지고 있으면 임시로 높은 우선순위를 부여해 빠르게 자원 반환하도록 함.
  • Priority Ceiling (우선순위 천장):
  • 공유 자원에 대해 최대 우선순위를 미리 지정해, 해당 자원에 접근 시 우선순위를 높여줌.

4. 교착상태 vs 기아 비교

구분 교착상태 (Deadlock) 기아 (Starvation)

정의 프로세스들이 서로 자원을 기다리며 영원히 대기 특정 프로세스가 무한히 자원을 못 얻는 상태
원인 자원 할당의 원형 대기 우선순위 정책, 불공정 스케줄링
발생 조건 Coffman 4조건 필요 특정 조건 없음
해결 예방, 회피, 탐지 및 회복 Aging(우선순위 점진 상향), 공정 스케줄링

✅ 요약

  • 교착상태: 여러 프로세스가 서로 자원을 기다리며 꼼짝 못하는 상태 (시스템 전체 멈춤).
  • 기아: 특정 프로세스가 무한히 자원을 못 얻는 상태 (불공정성).
  • 우선순위 역전은 기아의 한 사례 → 우선순위 상속/천장 기법으로 해결 가능.

교착상태 해결 방법

교착상태를 다루는 방법은 크게 네 가지로 나눌 수 있습니다:


1. 예방 (Deadlock Prevention)

  • Coffman의 4가지 필요 조건 중 하나 이상을 아예 성립하지 않도록 설계하는 방식.

방법

  1. 상호 배제(Mutual Exclusion) 부정
    • 자원을 여러 프로세스가 동시에 사용할 수 있게 설계 (현실적으로 모든 자원에 불가능).
  2. 점유 대기(Hold & Wait) 부정
    • 프로세스가 실행 전에 필요한 모든 자원을 한 번에 할당.
    • 단점: 자원 활용률↓, 기아 발생 가능.
  3. 비선점(No Preemption) 부정
    • 자원을 빼앗을 수 있게 함 (ex: CPU 스케줄링 선점형, 메모리 페이지 스왑).
  4. 순환 대기(Circular Wait) 부정
    • 자원에 번호를 붙여, 오름차순으로만 할당.

➡️ 장점: 교착상태 자체를 원천적으로 막음.

➡️ 단점: 자원 낭비, 활용률 저하.


2. 회피 (Deadlock Avoidance)

  • 교착상태가 발생하지 않도록 자원 할당을 신중히 결정하는 방식.
  • 대표적 방법: 은행가 알고리즘 (Banker’s Algorithm)
    • 자원 요청 시, 현재 상태가 **안전 상태(Safe State)**인지 검사 후 허용.
    • 안전하지 않으면 요청을 거절.

➡️ 장점: 교착상태 자체를 피할 수 있음.

➡️ 단점: 프로세스의 최대 자원 요구량을 미리 알아야 함 → 비현실적.


3. 탐지 후 회복 (Deadlock Detection & Recovery)

  • 교착상태 발생을 허용하되, 탐지 알고리즘으로 발견하고 이후 회복.

탐지 방법

  • 자원 할당 그래프(Resource Allocation Graph) 활용
    • 순환(Cycle)이 있으면 교착상태 가능.

회복 방법

  1. 프로세스 종료
    • 교착상태에 연루된 프로세스들을 강제로 종료.
    • 한 번에 모두 종료 vs 하나씩 종료.
  2. 자원 선점(Preemption)
    • 일부 프로세스에서 자원을 빼앗아 다른 프로세스에 할당.
    • 단점: 프로세스 상태 rollback 필요 → 오버헤드 발생.

4. 무시 (Deadlock Ignorance)

  • 교착상태를 해결하는 비용이 너무 크기 때문에, 실제로는 무시하는 방법.
  • 대부분의 범용 OS (Windows, Linux, macOS)는 이 방법 사용.
  • 교착상태 발생 빈도가 낮고, 발생 시 시스템을 재부팅하면 됨.

➡️ 장점: 구현 단순, 오버헤드 없음.

➡️ 단점: 일부 프로세스가 멈출 수 있음.


✅ 요약

방법 특징 장점 단점

예방 (Prevention) 4조건 중 하나 제거 교착상태 절대 발생 X 자원 낭비, 비효율
회피 (Avoidance) 안전 상태 유지 교착상태 피할 수 있음 최대 자원 요구량 필요
탐지 & 회복 (Detection & Recovery) 교착상태 허용 후 탐지·복구 자원 활용률 ↑ 탐지·복구 비용 큼
무시 (Ignore) 그냥 무시 단순, 효율 ↑ 교착상태 시 시스템 멈춤

 

 

메모리 관리 전략

 

CPU는 PC(program counter)가 지시하는데로 메모리에서 다음 수행할 명령어를 가져옴

주 메모리 ↔ 프로세서 자체에 내장한 레지스터는 CPU의 유일한 범용 저장장치

주 메모리 접근시 속도 차이로 cpu클록 틱 사이클이 소요되고, 지연(stall) 현상이 발생함

논리, 물리 주소

1. CPU와 메모리 접근

  • CPU는 **프로그램 카운터(PC, Program Counter)**가 가리키는 메모리 주소에서 명령어를 가져와 실행.
  • CPU 내부에는 **레지스터(Register)**가 있어서 연산에 직접 사용되는 데이터를 저장.
  • *주 메모리(Main Memory, RAM)**는 CPU가 데이터를 읽고 쓰는 기본 저장 장치지만, **CPU 클록 대비 속도가 느려 지연(stall)**이 발생.
    • 이를 줄이기 위해 **캐시 메모리(Cache)**가 등장 (CPU ↔ 캐시 ↔ 메모리 구조).

2. 논리 주소와 물리 주소

  • 논리 주소(Logical Address, 가상주소)
    • CPU가 생성하는 주소 (프로그램 관점).
    • 각 프로세스는 독립적인 주소 공간을 가진다고 "착각"할 수 있음.
  • 물리 주소(Physical Address)
    • 실제 메모리 하드웨어(RAM)가 갖는 주소.
  • 주소 변환(Address Translation)
    • *MMU (Memory Management Unit)**가 논리 주소 → 물리 주소 변환을 담당.
    • 보통 **재배치 레지스터(Relocation Register)**를 사용해서 시작 위치를 보정.

➡️ 이를 통해 다중 프로세스 환경에서도 서로 간섭하지 않고 메모리 사용 가능.


3. 동적 적재 (Dynamic Loading)

  • 프로세스 전체를 메모리에 올리지 않고, 필요한 부분만 메모리에 적재.
  • 장점: 메모리 사용 효율 ↑, 다중 프로그래밍에 유리.
  • 예: 라이브러리를 호출할 때 실제 필요한 함수만 메모리에 로드.

4. 메모리 할당 기법 (연속 메모리 할당)

프로세스들을 메모리에 배치할 때, 빈 공간(free hole)을 어떻게 선택할지 결정하는 방식.

  1. 최초 적합(First Fit)
    • 처음 발견한 충분히 큰 공간에 배치.
    • 속도 빠름, 하지만 단편화(fragmentation) 발생 가능.
  2. 최적 적합(Best Fit)
    • 크기가 가장 작은, 딱 맞는 공간에 배치.
    • 메모리 낭비 최소화, 그러나 작은 조각(외부 단편화) 많이 생김.
  3. 최악 적합(Worst Fit)
    • 가장 큰 공간에 배치.
    • 큰 공간을 나눠 사용 → 큰 프로세스를 위한 공간 확보 가능.
    • 하지만 실제 효율은 떨어짐.

✅ 요약

  • CPU ↔ 메모리 속도 차이를 줄이기 위해 캐시가 필요.
  • 논리 주소는 CPU가 보는 주소, 물리 주소는 실제 메모리 주소.
  • MMU가 주소 변환 수행.
  • 동적 적재로 메모리 효율성을 높임.
  • 메모리 배치 기법: 최초 적합, 최적 적합, 최악 적합 → 각각 속도/효율/낭비 측면에서 장단점 다름.

*** 동적 할당 → 외부 단편화 발생

1. 단편화(Fragmentation)

메모리 할당 과정에서 생기는 사용하지 못하는 빈 공간 문제.

(1) 외부 단편화 (External Fragmentation)

  • 여러 번의 메모리 할당/해제로 인해 자잘한 빈 공간이 여기저기 흩어져 전체적으로는 충분한 메모리가 있어도 큰 프로세스를 넣을 수 없는 상황.
  • 예: 10KB 프로세스 필요 → 빈 공간이 2KB+3KB+5KB로 나뉘어 있으면 수용 불가.
  • 해결 방법:
    • 압축(Compaction): 메모리 내용을 옮겨서 빈 공간을 하나로 모음.

(2) 내부 단편화 (Internal Fragmentation)

  • 할당된 블록이 실제 요구보다 큰 경우 발생 → 블록 내에 낭비된 공간 존재.
  • 예: 12KB 요청 → 16KB 단위 블록 할당 → 4KB 낭비.

2. 세그멘테이션 (Segmentation)

  • *프로그래머가 논리적으로 프로그램을 나눈 단위(세그먼트)**를 메모리에 배치하는 기법.
  • 세그먼트 = 코드, 데이터, 스택 등 가변 크기 블록.
  • CPU가 생성하는 주소 = (세그먼트 번호, 오프셋)
  • *세그먼트 테이블(Segment Table)**을 통해 물리 주소 변환.

✅ 장점:

  • 프로그래머 관점 그대로 메모리 관리 가능 (논리적 단위 유지).
  • 외부 단편화 발생하지만, 내부 단편화는 적음.

3. 페이징 (Paging)

  • 메모리를 고정 크기 블록으로 나누는 기법.
  • 프레임(Frame): 물리 메모리를 나눈 블록.
  • 페이지(Page): 프로세스의 논리 주소 공간을 나눈 블록.
  • 크기 동일 (예: 4KB).

➡️ CPU가 생성하는 주소 = (페이지 번호, 페이지 오프셋)

➡️ 페이지 테이블(Page Table): 페이지 번호 → 프레임 번호 변환.

✅ 장점:

  • 외부 단편화 없음 (모두 같은 크기).
  • 내부 단편화만 발생 (마지막 페이지 일부 낭비).

4. TLB (Translation Lookaside Buffer)

  • 페이지 테이블 접근은 메모리 참조이므로 느림 → 매번 하면 2번 메모리 접근(페이지 테이블 + 실제 데이터) 필요.
  • 이를 줄이기 위해 TLB라는 고속 캐시 사용.
  • 최근 변환된 페이지 번호 ↔ 프레임 번호를 저장해 주소 변환 속도 향상.

✅ 요약

  • 단편화
    • 외부 단편화: 작은 조각 흩어짐 → 압축 or 페이징/세그멘테이션으로 해결.
    • 내부 단편화: 블록 단위 때문에 남는 공간 발생.
  • 세그멘테이션: 논리적 단위(코드, 데이터, 스택)를 가변 크기로 관리. → 프로그래머 친화적, 외부 단편화 존재.
  • 페이징: 고정 크기 블록(Frame/Page)으로 관리. → 외부 단편화 없음, 내부 단편화 존재.
    • 페이지 테이블 필요 → 성능 저하 → TLB로 보완.

 

. 가상 메모리(Virtual Memory)란?

  • 프로세스 전체가 물리 메모리에 적재되지 않아도 실행 가능하게 하는 기법.
  • 사용자 입장에서는 매우 큰 “연속적인 메모리 공간”을 쓰는 것처럼 보이지만, 실제로는 물리 메모리(RAM)와 보조기억장치(디스크, SSD 등)를 조합해서 구현.
  • *논리 주소(가상 주소)**와 물리 주소를 분리하여 관리.

2. 필요성

  1. 메모리 효율성 향상
    • 전체 프로그램을 메모리에 올릴 필요 없이 필요한 부분만 적재 → 더 많은 프로세스를 동시에 실행 가능 (멀티프로그래밍).
  2. 보호(Protection)
    • 프로세스마다 독립적인 주소 공간 제공 → 서로 침범 불가.
  3. 유연성
    • 실제 메모리보다 큰 프로그램도 실행 가능.

1. 요구 페이징 (Demand Paging)

  • 정의: 프로세스 전체를 메모리에 적재하지 않고, 실제로 필요할 때 해당 페이지를 메모리에 적재하는 기법.
  • 프로그램 실행 시 처음에는 필요한 최소한의 페이지만 로드 → 나머지는 실행 도중 필요할 때 디스크에서 불러옴.

(1) 참조의 지역성(Locality of Reference)

  • 시간 지역성(Temporal Locality): 최근 접근한 데이터는 곧 다시 접근될 가능성 ↑
  • 공간 지역성(Spatial Locality): 접근한 주소 근처의 데이터가 곧 참조될 가능성 ↑
  • 요구 페이징은 이 성질을 활용 → 성능이 실제로는 꽤 좋음.

(2) 페이지 부재(Page Fault)

  • CPU가 요청한 페이지가 메모리에 없을 때 발생.
  • 처리 과정:
    1. CPU → 페이지 없음 감지 (트랩 발생).
    2. OS → 디스크에서 해당 페이지 적재.
    3. 페이지 테이블 갱신 후 재실행.

(3) 유효 접근 시간 (Effective Access Time, EAT)

  • 실제 메모리 접근 시간은 **페이지 부재율(p)**에 비례.

EAT=(1−p)×메모리 접근 시간+p×페이지 폴트 처리 시간EAT = (1 - p) \times \text{메모리 접근 시간} + p \times \text{페이지 폴트 처리 시간}

EAT=(1−p)×메모리 접근 시간+p×페이지 폴트 처리 시간

  • 페이지 폴트 처리 시간은 디스크 I/O가 포함되므로 매우 크다.
  • 따라서 페이지 부재율은 극히 낮아야 성능 유지 가능.

2. 쓰기 시 복사 (Copy-on-Write, COW)

  • 정의: 프로세스가 fork()나 exec()로 복제될 때, 모든 페이지를 처음부터 복사하지 않고 → 부모와 자식이 같은 물리 페이지를 공유하다가 실제로 쓰기(write) 연산이 발생하는 순간 복사하는 기법.

(1) 동작 원리

  1. fork() 시 자식 프로세스는 부모의 페이지를 그대로 참조. (읽기 전용)
  2. 어느 한쪽이 해당 페이지를 쓰기(write) 시도 → 페이지 부재 발생.
  3. OS가 그 시점에만 페이지를 새로 복사해서 분리.

(2) 장점

  • 불필요한 페이지 복사를 방지 → 메모리 절약.
  • fork() 후 exec()가 곧바로 이어지는 경우(자식이 새로운 프로그램 실행) → 부모 메모리 복사는 거의 필요 없음.

✅ 요약

  • 요구 페이징(Demand Paging)
    • 필요한 페이지만 적재 → 메모리 효율 ↑
    • 참조의 지역성 때문에 실제 성능이 만족스러움
    • 하지만 페이지 부재율이 높아지면 성능 저하 (스래싱 위험)
  • 쓰기 시 복사(Copy-on-Write, COW)
    • 부모-자식이 페이지를 공유하다가 쓰기 시점에만 복사
    • fork() + exec() 최적화에 매우 효과적

1. 페이지 교체(Page Replacement)란?

  • 가상 메모리 시스템에서, 새로운 페이지를 메모리에 불러와야 하는데 빈 프레임이 없을 때 → 기존에 있던 페이지 중 하나를 교체하는 과정.
  • 어떤 페이지를 교체하느냐에 따라 **페이지 부재율(Page Fault Rate)**이 크게 달라짐.
  • 목표: 페이지 부재율을 최소화하는 알고리즘 선택.

2. 프레임 할당 (Frame Allocation)

  • 각 프로세스에 얼마나 많은 프레임을 줄 것인지 결정.
  • 너무 적으면 → 페이지 폴트 잦음.
  • 너무 많으면 → 다른 프로세스가 부족해짐.

3. 주요 페이지 교체 알고리즘

(1) 최적 교체 (Optimal Replacement, OPT)

  • 앞으로 가장 오랫동안 사용하지 않을 페이지를 교체.
  • 이론적으로 가장 좋은 성능 → 페이지 부재율 최소.
  • 하지만 미래 참조를 알 수 없으므로 실제 구현 불가, 비교 기준으로 사용.

(2) FIFO (First-In First-Out)

  • 메모리에 가장 오래 있던 페이지를 교체.
  • 구현 단순하지만, 성능이 나쁠 수 있음.
  • Belady의 모순(Belady’s Anomaly): 프레임 수를 늘렸는데도 페이지 폴트가 증가할 수 있음.

(3) LRU (Least Recently Used)

  • 가장 오랫동안 사용하지 않은 페이지를 교체.
  • 과거 사용 이력이 미래 사용 가능성과 연관 있다는 "참조의 지역성(Locality)"에 기반.
  • 일반적으로 가장 널리 쓰임.
  • 구현 방식:
    • 카운터 기반 (최근 접근 시간 기록)
    • 스택 기반 (최근 사용된 페이지를 스택 상단에 유지)

(4) LFU (Least Frequently Used)

  • 사용 빈도가 가장 낮은 페이지를 교체.
  • 문제: 최근 집중적으로 쓰였지만 앞으로 필요 없는 페이지도 남아있을 수 있음.

(5) Clock 알고리즘 (Second Chance)

  • FIFO 변형: 교체 대상 페이지에 참조 비트(Reference Bit) 확인.
  • 참조된 페이지는 한 번 기회를 주고 다음 후보로 넘김.
  • 성능은 LRU에 근접하면서 구현은 단순.

4. 선택 기준

  • 실제 운영체제에서는 LRU 또는 Clock 알고리즘이 주로 사용.
  • 이유:
    • OPT는 이상적이지만 불가능.
    • FIFO는 성능 불안정.
    • LRU는 locality 가정 하에 안정적으로 좋은 성능.
    • Clock은 LRU 근사치로 구현 효율성 높음.

✅ 요약

  • 페이지 교체는 빈 프레임이 없을 때 어떤 페이지를 제거할지 결정하는 문제.
  • 목표: 페이지 부재율 최소화.
  • 대표 알고리즘: OPT, FIFO, LRU, LFU, Clock.
  • 실제 OS는 **LRU(또는 근사 알고리즘)**을 가장 많이 사용.

1. 쓰레싱(Thrashing)이란?

  • 과도한 페이지 부재(Page Fault) 때문에 CPU가 실제 작업보다 페이징 처리에 더 많은 시간을 소모하는 현상.
  • 결과적으로 CPU 이용률 급격히 저하, 시스템 성능이 심각하게 떨어짐.
  • 즉, 프로세스가 필요한 페이지가 메모리에 거의 없어서 계속 디스크 ↔ 메모리 스왑이 일어나는 상태.

2. 원인

  1. 메모리 과다 할당 부족
    • 프로세스들이 동시에 실행되면서 각 프로세스에 충분한 프레임을 배정하지 못한 경우.
  2. 지역성(Locality) 위반
    • 요구 페이징은 참조의 지역성을 가정하는데, 프로그램이 메모리 전체를 자주 건드리면 페이지 폴트 ↑.
  3. 다중 프로그래밍 정도(Multiprogramming Degree) 과도
    • 너무 많은 프로세스를 동시에 메모리에 올려두면 각 프로세스가 필요한 최소 프레임을 확보하지 못함.

3. 증상

  • 페이지 부재율(Page Fault Rate) ↑
  • CPU 이용률(CPU Utilization) ↓
  • 디스크 I/O 폭증
  • 프로그램 실행 속도 급격히 저하

4. 해결 방법

(1) 최소 프레임 보장

  • 각 프로세스에 필요한 최소 프레임 수를 보장해야 함.
  • 예: 페이지 교체 알고리즘과 함께 최소 프레임 개수를 할당.

(2) 작업 집합 모델 (Working Set Model)

  • 프로세스가 일정 시간 동안 자주 참조하는 페이지 집합 = 작업 집합(Working Set).
  • 이 집합을 모두 메모리에 적재 → 페이지 폴트 감소.

(3) PFF (Page Fault Frequency) 방식

  • 페이지 폴트율을 측정해 임계값 초과 시 → 프레임을 늘려주고, 낮으면 줄임.
  • 동적으로 프레임 수를 조절해 thrashing 방지.

(4) 다중 프로그래밍 정도 조절

  • 시스템에 동시에 실행되는 프로세스 수를 줄임.
  • 즉, 일부 프로세스를 swap-out 해서 나머지 프로세스가 충분한 프레임 확보하도록 함.

✅ 요약

  • Thrashing = CPU가 일 못 하고 페이징만 하는 상태.
  • 원인: 프레임 부족, 지역성 위반, 다중 프로그래밍 과도.
  • 해결: 최소 프레임 보장, 작업 집합(Working Set), PFF(Page Fault Frequency), 다중 프로그래밍 정도 조절.
반응형

'CS > 운영체제' 카테고리의 다른 글

fork 함수와 프로세스  (0) 2022.03.01
반응형

이전에 면접 준비하며 정리했던 내용 공유합니다.

📌 CORS란?

  • Cross-Origin Resource Sharing(교차 출처 리소스 공유)
  • 브라우저 보안 정책인 **SOP(Same-Origin Policy, 동일 출처 정책)**을 완화해주는 메커니즘
  • 기본적으로 브라우저는 다른 출처(origin)의 리소스 요청을 제한합니다.

👉 즉, CORS는 “다른 출처의 리소스를 클라이언트가 안전하게 요청할 수 있도록 서버가 허용하는 정책”이에요.

📌 CORS 동작 방식

  1. 단순 요청 (Simple Request)
    • 특정 조건 만족 시(예: GET/POST + 특정 헤더만) 브라우저가 바로 요청을 보냄
    • 서버가 응답 헤더에 Access-Control-Allow-Origin을 포함해야 함
    • 예:
    • Access-Control-Allow-Origin: <https://myapp.com>

📌 주요 헤더 정리

Access-Control-Allow-Origin: 허용할 출처 (* 또는 특정 도메인)

Access-Control-Allow-Methods: 허용할 HTTP 메서드

Access-Control-Allow-Headers: 허용할 요청 헤더

Access-Control-Allow-Credentials: 인증정보(쿠키, 토큰) 허용 여부

Access-Control-Max-Age: 프리플라이트 응답 캐시 시간

  1. 프리플라이트 요청 (Preflight Request)
  • 조건을 벗어난 요청(예: PUT, DELETE, 커스텀 헤더 포함)일 경우
  • 브라우저가 먼저 OPTIONS 메서드로 서버에 “이 요청 해도 되나요?” 물어봄
  • 서버가 허용하면 실제 요청 전송

예:

OPTIONS /api/data HTTP/1.1
Origin: <https://myapp.com>
Access-Control-Request-Method: PUT

서버 응답:

Access-Control-Allow-Origin: <https://myapp.com>
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization

  1. Credential 요청 (쿠키/인증 포함)
    • fetch나 XHR에서 credentials: include 설정
    • 서버가 반드시 다음 헤더 필요:
    • Access-Control-Allow-Credentials: true Access-Control-Allow-Origin: <https://myapp.com> (와일드카드 * 사용 불가)

🟦 OSI 7계층 구조

1. 물리 계층 (Physical Layer)

  • 역할: 데이터의 비트(0/1) 신호를 실제 전송 매체(케이블, 전파 등)를 통해 송수신.
  • 장비 예시: 허브, 리피터, 케이블, 커넥터
  • 단위: 비트 (Bit)

2. 데이터 링크 계층 (Data Link Layer)

  • 역할: 물리 계층에서 전송된 비트를 프레임(Frame) 단위로 관리. 에러 검출/수정, 흐름 제어 수행.
  • MAC 주소 사용 → 동일 네트워크 내에서 장치 식별.
  • 장비 예시: 스위치, 브리지
  • 단위: 프레임 (Frame)

3. 네트워크 계층 (Network Layer)

  • 역할: 데이터의 목적지까지의 경로 선택(라우팅), 논리적 주소(IP) 부여.
  • IP 주소 사용 → 다른 네트워크 간 통신 가능.
  • 프로토콜 예시: IP, ICMP, ARP, RARP
  • 장비 예시: 라우터
  • 단위: 패킷 (Packet)

4. 전송 계층 (Transport Layer)

  • 역할: 종단 간(end-to-end) 통신 제공. 데이터의 신뢰성 보장.
  • 주요 프로토콜:
    • TCP(연결지향, 신뢰성 보장, 흐름/혼잡 제어)
    • UDP(비연결성, 빠르지만 신뢰성 낮음)
  • 단위: 세그먼트 (TCP) / 데이터그램 (UDP)

5. 세션 계층 (Session Layer)

  • 역할: 통신 세션(대화)의 생성, 유지, 종료 관리.
  • 예: 로그인 세션, 원격 프레젠테이션 연결
  • 기능: 동기화, 체크포인트, 복구

6. 표현 계층 (Presentation Layer)

  • 역할: 데이터의 표현 방식 통일. 암호화, 압축, 인코딩/디코딩.
  • 예: JPEG, GIF, MP3, TLS/SSL 암호화
  • 키워드: 번역기 역할

7. 응용 계층 (Application Layer)

  • 역할: 최종 사용자와 직접 맞닿아 있는 계층. 네트워크 서비스 제공.
  • 프로토콜 예시:
    • HTTP/HTTPS (웹)
    • FTP (파일 전송)
    • SMTP/IMAP/POP3 (이메일)
    • DNS (도메인 네임 변환)

🔹 정리 (계층별 단위 & 장비)

계층 단위 주요 장비/기술

7. 응용 데이터 웹 브라우저, 앱
6. 표현 데이터 암호화/압축
5. 세션 데이터 API 세션 관리
4. 전송 세그먼트/데이터그램 TCP, UDP
3. 네트워크 패킷 라우터
2. 데이터링크 프레임 스위치
1. 물리 비트 케이블, 허브

 

 

 

애플리케이션 구조

📌 애플리케이션 구조 (Application Architecture)

애플리케이션 구조는 개발자가 설계한 방식에 따라 다양한 종단 시스템(End System)에서 애플리케이션이 어떻게 조직되는지를 설명한다.

대표적으로 클라이언트-서버 구조P2P 구조가 있다.


🔹클라이언트-서버 구조 (Client–Server Architecture)

  • 서버(Host): 항상 켜져 있고, 클라이언트의 요청을 처리하는 중심 시스템
  • 클라이언트(Client): 다수의 호스트에서 실행되며, 서버에 요청을 보내는 역할
  • 특징
    • 중앙 집중형 구조
    • 보안, 데이터 관리, 성능 최적화에 유리
    • 서버 부하가 집중되면 확장 비용 증가

🔹 P2P 구조 (Peer-to-Peer Architecture)

  • 특징
    • 항상 켜져 있는 기반 서버에 최소한만 의존
    • 피어(Peer): 간헐적으로 연결되는 호스트 쌍이 직접 서로 통신
    • 자가 확장성(Self-Scalability): 네트워크에 참여하는 피어 수가 늘어날수록 자원도 함께 증가
    • 탈 중앙화된 구조로 확장성과 유연성이 뛰어남

🔹 프로세스 (Process)

  • 정의: 종단 시스템에서 실행되는 프로그램 단위
  • 관심 대상: 프로세스 간 통신
    • 같은 종단 시스템 내부 프로세스 간 통신
    • 다른 종단 시스템 간 프로세스 간 통신
  • 클라이언트 & 서버 개념
    • 클라이언트(Client): 통신 세션을 초기화하는 프로세스
    • 서버(Server): 통신 세션을 시작하기 위해 접속을 기다리는 프로세스

✅ 요약하면,

  • 클라이언트-서버 구조는 중앙 집중형으로 관리가 용이하지만 서버 부하가 커짐
  • P2P 구조는 분산형으로 확장성이 뛰어나지만 관리와 보안이 상대적으로 복잡
  • 프로세스 관점에서는 누가 먼저 접속을 시도하는지(클라이언트)와 기다리는지(서버)에 따라 역할이 나뉨

📌 프로세스와 컴퓨터 네트워크 사이의 인터페이스

🔹 소켓 (Socket)

  • 정의: 애플리케이션과 네트워크 사이의 API
  • 역할: 프로세스가 네트워크를 통해 메시지를 주고받을 수 있도록 하는 인터페이스

🔹 애플리케이션 계층 프로토콜

  • 호스트 식별: 인터넷에서는 IP 주소로 호스트를 식별
  • 주요 요구 사항
    1. 신뢰적 데이터 전송
      • 손실 없는 데이터 보장을 원할 때: TCP
      • 손실 허용 애플리케이션(예: 스트리밍): UDP
    2. 처리량(Throughput)
    3. 보안(Security): SSL/TLS 계층에서 제공
  • 인터넷에서 대표적으로 사용되는 프로토콜
  • TCP, UDP (트랜스포트 계층)
  • HTTP (애플리케이션 계층)

📌 HTTP (HyperText Transfer Protocol)

  • 특징
    • 온-디맨드(On-Demand) 방식
    • 웹에서 사용하는 대표적 애플리케이션 계층 프로토콜
    • 기본적으로 TCP(HTTP/2.0 이하) 위에서 동작
    • 비상태성(Stateless) → 쿠키(Cookie)로 상태 관리 보완

🔹 HTTP 버전 별 특징

✅ HTTP 1.0

  • 비 지속 연결 (Non-Persistent Connection)
  • 요청–응답마다 TCP 연결을 새로 설정
  • 지속 연결 (Persistent Connection) 지원 시작

✅ HTTP 1.1

  • 기본적으로 지속 연결(Persistent)
  • Keep-Alive 헤더를 통해 연결 유지

✅ HTTP 2.0

  • 멀티플렉싱(Multiplexing): 하나의 연결에서 다중 요청/응답 처리
  • 성능 개선: 지연 감소, 헤더 압축, 서버 푸시(Server Push) 지원

** 하나의 TCP 연결을 통해 여러 데이터 요청을 병렬로 전송


✅ 요약하면,

  • 소켓은 프로세스와 네트워크의 인터페이스
  • TCP/UDP가 신뢰성과 속도의 기준을 결정
  • HTTP는 웹의 핵심 애플리케이션 프로토콜로, 버전 업그레이드마다 성능 최적화연결 효율성이 강화됨
  • 쿠키로 Stateless 한 HTTP를 보완

📌 웹 캐싱 (Web Caching)

🔹 정의

  • 웹 캐시(Web Cache, Proxy Server):
  • 웹 서버 대신 클라이언트의 HTTP 요청을 처리하는 네트워크 개체
  • 동작 원리:
    1. 브라우저가 요청한 객체(HTML, CSS, JS, 이미지 등)를 캐시에 저장
    2. 같은 요청이 다시 오면 원 서버(origin server)에 가지 않고 캐시에서 응답

🔹 특징

  • 성능 향상: 지리적으로 가까운 캐시 서버에서 응답 → 지연 시간 감소
  • 대역폭 절약: 동일 콘텐츠 반복 요청 시 원 서버로의 전송 감소
  • 부하 분산: 원 서버의 트래픽을 줄여 부하 완화
  • 운영 주체: 일반적으로 ISP(Internet Service Provider) 가 구입 및 설치

🔹 조건부 GET (Conditional GET)

웹 캐시가 저장된 객체가 여전히 최신인지 확인하기 위해 사용되는 HTTP 요청 방식

  1. ETag(Entity Tag)
    • 서버가 객체에 대해 생성하는 고유 식별자
    • 요청 시 If-None-Match 헤더로 전달
    • 서버는 ETag 비교 후 같으면 304 Not Modified 응답 → 캐시된 데이터 사용
  2. Last-Modified
    • 객체의 최종 수정 시간 정보를 제공
    • 요청 시 If-Modified-Since 헤더로 전달
    • 변경 없으면 304 Not Modified 응답

🔹 요청 흐름 요약

클라이언트 ──HTTP 요청──> 캐시 서버
             │
             ├─ 캐시에 있으면 → 바로 응답
             │
             └─ 캐시에 없거나 만료되면 → 원 서버에 요청
                                   │
                                   └─ 최신 여부 확인 (조건부 GET: ETag, Last-Modified)

✅ 정리하면,

  • 웹 캐시는 프록시처럼 동작하여 성능과 효율을 개선
  • 조건부 GET은 캐시된 콘텐츠의 유효성을 확인하는 핵심 메커니즘
  • 304 Not Modified 응답으로 불필요한 데이터 전송을 줄임

Cache-Control 헤더

🔹 정의

  • HTTP/1.1부터 도입된 캐싱 정책 제어 헤더
  • 서버가 응답 시 Cache-Control 헤더를 포함하면, 브라우저나 프록시 캐시가 리소스를 얼마나, 어떤 방식으로 캐싱할지 결정

🔹 주요 지시자(Directives)

1) 캐싱 가능 여부

  • public
    • 모든 캐시(브라우저, 프록시, CDN)에서 저장 가능
  • private
    • 특정 사용자 브라우저에서만 캐싱 가능 (공유 캐시 불가, 예: 개인화 페이지)
  • no-store
    • 아예 저장 금지 (민감 정보: 로그인, 금융 데이터)

2) 유효 기간

  • max-age=<초>
    • 리소스를 최대 n초 동안 신선한(fresh) 상태로 간주
    • 예: max-age=3600 → 1시간 동안 캐시 사용
  • s-maxage=<초>
    • 공유 캐시(CDN, 프록시)에만 적용되는 max-age
    • 브라우저 캐시에는 적용되지 않음
  • must-revalidate
    • 캐시 만료 후 반드시 원 서버에 검증 요청

3) 재검증 정책

  • no-cache
    • 저장할 수는 있지만, 재사용 전 반드시 원 서버 검증 필요
  • proxy-revalidate
    • 공유 캐시가 만료된 콘텐츠를 재사용할 때 반드시 서버 검증

4) 캐싱 우선순위 (HTTP/2 이상에서 활용)

  • stale-while-revalidate=<초>
    • 만료된 캐시라도 재검증 요청하는 동안 임시로 사용 가능
  • stale-if-error=<초>
    • 원 서버 에러 발생 시 만료된 캐시를 지정된 시간 동안 사용 가능

예시

1) 일반적인 정적 파일 (이미지, JS, CSS)

Cache-Control: public, max-age=31536000, immutable
  • public → 공유 캐시 가능
  • max-age=31536000 → 1년 캐싱
  • immutable → 파일이 변경되지 않는다는 가정 (버전 변경 시 파일명에 hash 사용)

2) 사용자 맞춤형 페이지

Cache-Control: private, no-store
  • private → 브라우저 캐시만 허용
  • no-store → 실제로는 캐시 불가 → 로그인 페이지, 결제 페이지 등에 사용

3) API 응답 (짧은 캐싱 허용)

Cache-Control: no-cache, must-revalidate, max-age=0
  • 저장은 가능하지만 매번 서버 검증 필요
  • 최신성이 중요한 JSON API 응답 등에 사용

🔹 정리

지시자 의미

public 모든 캐시 저장 가능
private 개인 브라우저만 저장 가능
no-store 저장 자체 금지
no-cache 캐시 가능하나 재사용 전 검증 필요
max-age=<초> 캐시 유효 시간 설정
s-maxage=<초> 공유 캐시 전용 유효 시간
must-revalidate 만료 후 반드시 서버 검증
stale-while-revalidate 만료 중에도 임시 사용 가능
stale-if-error 서버 에러 시 만료된 캐시 사용 가능

📌 메일 프로토콜 (Email Protocols)

🔹 SMTP (Simple Mail Transfer Protocol)

  • 역할: 메일 전송(송신) 프로토콜
  • 위치: 클라이언트 → 메일 서버, 메일 서버 → 메일 서버 간 전송
  • 특징
    • 푸시(push) 방식: 발신자가 수신자 메일 서버로 직접 전달
    • 전송 전용 → 수신에는 사용하지 않음
  • 포트
    • 기본: 25
    • 보안: 465(SSL), 587(TLS)

🔹 POP3 (Post Office Protocol v3)

  • 역할: 메일 수신(받기) 프로토콜
  • 특징
    • 메일 서버에서 클라이언트로 다운로드 후 → 기본적으로 서버에서 삭제
    • 단일 기기에서 사용하기 적합 (ex: PC Outlook에서만 확인)
  • 포트
    • 기본: 110
    • 보안: 995(SSL/TLS)

🔹 IMAP (Internet Message Access Protocol)

  • 역할: 메일 수신 및 관리 프로토콜
  • 특징
    • 메일 서버와 동기화 (서버에 메일 보관)
    • 여러 기기에서 동일 메일함 동기화 가능 (PC, 모바일, 웹메일)
    • 폴더 관리, 읽음/안읽음 상태 동기화 지원
  • 포트
    • 기본: 143
    • 보안: 993(SSL/TLS)

📊 비교 표

프로토콜 주요 역할 동작 방식 장점 단점

SMTP 메일 송신 클라이언트 → 서버, 서버 ↔ 서버 전송 표준 수신 불가
POP3 메일 수신 서버 → 클라이언트 (다운로드 후 삭제) 단순, 서버 부하 적음 여러 기기 동기화 불가
IMAP 메일 수신 서버 ↔ 클라이언트 동기화 다중 기기 동기화, 관리 용이 서버 저장 공간 차지

✅ 정리하면,

  • SMTP = 메일 보내기 (송신)
  • POP3 = 메일 받기 (다운로드, 단일 기기)
  • IMAP = 메일 받기 + 동기화 (멀티 기기, 서버 유지)

DNS

호스트에 대한 하나의 식별자 - 호스트네임(hostname)

  1. DNS는 DNS 서버들의 계층 구조로 구현된 분산 데이터베이스
  2. 호스트가 분산 데이터베이스로 질의하도록 허락하는 애플리케이션 계층 프로토콜

HTTP, SMTP, FTP등 사용자가 제공한 호스트 네임을 IP 주소로 변환하기 위해 주로 이용

모든 질의는 UDP로 보내짐

분산 데이터베이스

계층 형태로 구성되며 전세계에 분산됨

/** AI한테 도식 요구 **/

root (TLD, top level domain) 아래에 계단 식으로 DNS 서버 구성

TYPE A = 호스트 네임에 대한 IP 주소 제공

TYPE CNAME = 별칭 호스트네임에 대한 정식 호스트네임 제공

 

📌 트랜스포트 계층 (Transport Layer)

🔹 개요

  • 위치: 애플리케이션 계층 ↔ 네트워크 계층 사이
  • 역할: 다른 호스트에서 실행 중인 애플리케이션 프로세스 간의 논리적 통신 제공
  • 구현 위치: 종단 시스템(End System)
  • 기능
    • 트랜스포트 다중화(Multiplexing)
    • → 여러 애플리케이션 데이터 스트림을 하나의 네트워크 계층 연결로 전달
    • 역다중화(Demultiplexing)
    • → 받은 세그먼트를 올바른 애플리케이션 프로세스로 전달

🔹 UDP (User Datagram Protocol)

  • 특징
    • 비연결형(Connectionless) → 연결 설정/유지 X
    • 비신뢰성(Unreliable) → 패킷 손실, 순서 뒤바뀜 발생 가능
    • 혼잡 제어 X → 애플리케이션 계층이 직접 제어
  • 오류 검출
    • UDP 체크섬(Checksum)으로 종단 간 오류 검출 제공
  • 장점
    • 오버헤드 적음 (헤더 8바이트)
    • 빠른 전송에 적합 (스트리밍, DNS, 게임 등)

🔹 TCP (Transmission Control Protocol)

  • 특징
    • 신뢰성 있는 연결형 프로토콜(Connection-oriented)
    • 패킷 손실 시 재전송 보장
    • 흐름 제어, 혼잡 제어 제공
  • 데이터 전송 방식
    • 파이프라이닝(Pipelining): 다수 패킷을 연속적으로 전송
    • 슬라이딩 윈도우(Sliding Window) 프로토콜 기반
      • 수신 ACK을 받아 누적 확인 응답 사용 (Go-Back-N 방식)
      • 또는 개별 확인 응답 사용 (Selective Repeat 방식)

📌 신뢰적 데이터 전송: GBN vs SR

🔹 Go-Back-N (GBN)

  • 송신자는 윈도우 크기 N만큼 패킷 전송 가능
  • 누적 확인 응답(Cumulative ACK) 사용
    • ACK n = n까지 모든 패킷 정상 수신
  • 손실 발생 시
    • 손실 지점 이후 모든 패킷을 다시 전송
송신자: [1][2][3][4][5] ...
수신자: [1][2][X][4][5] ...
          └── ACK 2 (3번 손실)
송신자: 3 이후 패킷 모두 재전송
  • 장점: 구현 간단
  • 단점: 불필요한 재전송 많음

🔹 Selective Repeat (SR)

  • 송신자는 윈도우 크기 N만큼 패킷 전송 가능
  • 개별 확인 응답(Individual ACK) 사용
    • 각 패킷 별도로 ACK 전송
  • 손실 발생 시
    • 해당 패킷만 선택적으로 재전송
송신자: [1][2][3][4][5] ...
수신자: [1][2][X][4][5] ...
          ├── ACK 1
          ├── ACK 2
          ├── ACK 4
          └── ACK 5
송신자: [3]만 재전송

  • 장점: 불필요한 재전송 ↓ → 효율 ↑
  • 단점: 구현 복잡, 수신자는 out-of-order 패킷을 버퍼링해야 함

🔹 전송 프로토콜 비교

구분 UDP TCP

연결 방식 비연결형 연결형 (3-way handshake)
신뢰성 보장하지 않음 보장 (재전송, 순서 정렬)
흐름 제어 없음 있음
혼잡 제어 없음 있음
속도 빠름 상대적으로 느림
대표 사용처 DNS, VoIP, 스트리밍, 게임 웹(HTTP/HTTPS), 이메일(SMTP, IMAP), 파일 전송(FTP)

✅ 정리

  • UDP: 빠르고 단순, 하지만 신뢰성 없음 → 애플리케이션이 직접 보완해야 함
  • TCP: 신뢰성·순서·흐름 제어·혼잡 제어까지 제공하는 완전한 전송 프로토콜

TCP 연결 - 연결 지향형 프로토콜

  • TCP는 연결 지향형 프로토콜
  • 데이터 전송 전, 송·수신 양측이 논리적 연결(Connection)을 수립해야 함
  • 이 과정을 3-Way Handshake 라고 부름

🔹 3-Way Handshake 과정

1️⃣ SYN (synchronize)

  • 클라이언트 → 서버
  • 클라이언트가 연결을 요청하며 SYN 플래그 설정
  • 초기 시퀀스 번호(ISN, Initial Sequence Number)를 함께 보냄

2️⃣ SYN + ACK

  • 서버 → 클라이언트
  • 클라이언트의 SYN을 수락하고, 자신의 ISN과 함께 응답
  • SYN=1, ACK=1 플래그 설정

3️⃣ ACK (acknowledgement)

  • 클라이언트 → 서버
  • 서버의 SYN을 수락했다는 의미로 ACK를 전송
  • 이 시점에서 TCP 연결 수립 완료

🔹 흐름도 (간단 도식)

[Client]                                  [Server]
   | ----------- SYN(seq=x) -------------> |
   | <----- SYN(seq=y), ACK(ack=x+1) ----- |
   | -------- ACK(ack=y+1) --------------> |
   |                                       |
   |     [TCP Connection Established]      |


🔹 특징

  • 신뢰성 있는 연결 수립 (양쪽 모두 송수신 준비 완료 보장)
  • 3번의 왕복 메시지로 동기화 → 최소한의 오버헤드로 안정성 확보
  • 이후 데이터 전송(데이터 세그먼트 교환) 시작

3️⃣ TCP 연결 종료 (4-Way Handshake)

연결을 종료하고 싶은 host가 active closer, 종료 당하는 주체가 passive closer

  1. Active Closer → FIN 송신 (FIN_WAIT_1)
  2. Passive Closer → ACK 응답 (CLOSE_WAIT / Active=FIN_WAIT_2)
  3. Passive Closer → FIN 송신 (LAST_ACK)
  4. Active Closer → ACK 응답 후 TIME_WAIT 진입 → 종료

4️⃣ TIME_WAIT 상태

  • Active Closer가 마지막 ACK를 보낸 후 일정 시간(보통 2MSL, 30~60초) 대기
  • 이유:
    1. 지연 패킷 제거 (이전 연결 패킷이 새 연결에 섞이지 않도록)
    2. 마지막 ACK 보장 (ACK 손실 시 상대방의 FIN 재전송 처리 가능)

정리

  • 3-Way Handshake: TCP 연결 수립
  • ISN: 시퀀스 번호의 시작점, 순서·보안 보장
  • 4-Way Handshake: TCP 연결 종료
  • TIME_WAIT: 지연 패킷/ACK 손실 대비 안전 대기

📌 SSL/TLS Handshake 과정

핵심 아이디어:

  • *초기에는 비대칭 암호화(RSA, ECDHE 등)**를 사용해 "세션 키(대칭키)"를 안전하게 합의
  • *그 이후부터는 대칭 암호화(AES 등)**로 실제 데이터 통신 수행 (빠르고 효율적)

🔹 상세 단계 (TLS 1.2 기준 예시)

1️⃣ Client Hello

  • 클라이언트 → 서버
  • 클라이언트가 지원하는 암호화 알고리즘 목록, TLS 버전, 난수(Random) 전달

2️⃣ Server Hello + 인증서 전송

  • 서버 → 클라이언트
  • 서버가 사용할 암호화 알고리즘 선택
  • 서버 인증서(X.509, 공개키 포함) 전달

3️⃣ 인증서 검증

  • 클라이언트는 받은 서버 인증서를 CA(인증기관)의 공개키로 검증
  • 올바르면 서버의 공개키를 사용 가능

4️⃣ 세션 키 생성 & 전송

  • (RSA 방식일 경우)
    • 클라이언트가 **대칭키(Pre-Master Secret)**를 생성
    • 서버의 공개키로 암호화해서 서버로 전송
  • (Diffie-Hellman/ECDHE 방식일 경우)
    • 양쪽이 난수 교환을 통해 동일한 세션 키를 계산

5️⃣ 서버가 세션 키 복호화

  • 서버는 자신의 개인키(Private Key)로 클라이언트가 보낸 대칭키를 해독

6️⃣ Finished 메시지 교환

  • 서로 “이제부터 대칭키 암호화를 사용한다”는 신호 교환
  • 이후부터는 AES, ChaCha20 같은 대칭키 암호화로 통신

흐름 제어 (Flow Control)

🔹 개념

  • 송신자가 너무 많은 데이터를 빠르게 보내지 않도록 제어하는 메커니즘
  • 목적: 수신자의 버퍼 오버플로우 방지

🔹 TCP에서 흐름 제어 방식

  • 수신 윈도우(Receive Window, rwnd) 사용
  • 수신 측은 자신이 현재 얼마나 데이터를 더 받을 수 있는지를 rwnd 값으로 송신자에게 알려줌
  • 송신자는 이 윈도우 크기를 참고하여 데이터 전송량을 조절

🔹 동작 예시

  1. 수신 버퍼가 여유로움 → rwnd 크게 설정 → 송신자 많은 데이터 전송 가능
  2. 수신 버퍼가 가득 참 → rwnd=0 통보 → 송신자는 전송 중단
  3. 수신자가 버퍼를 비우면 → rwnd 갱신 → 송신자가 다시 전송 재개

🔹 특징

  • End-to-End 제어: 송신자 ↔ 수신자 간 직접 협력
  • 흐름 제어 ≠ 혼잡 제어
    • 흐름 제어: 수신자 능력에 맞게 제어
    • 혼잡 제어: 네트워크 상황에 맞게 제어

정리

  • TCP는 **수신 윈도우(rwnd)**를 통해 흐름 제어를 제공
  • 수신자가 처리 가능한 만큼만 데이터를 전송하게 만들어 버퍼 오버플로우 방지

📌 TCP 혼잡 제어 (Congestion Control)

네트워크 내 혼잡(패킷 손실, 지연)을 피하기 위해 송신자가 전송 속도를 조절하는 메커니즘.

핵심 개념은 **혼잡 윈도우(cwnd, Congestion Window)**를 유지하면서 네트워크 상황에 맞게 변화시키는 것.


🔹 1. 슬로 스타트 (Slow Start)

  • 처음 연결 시 cwnd = 1 MSS (최소 단위)로 시작
  • ACK를 받을 때마다 cwnd 두 배로 증가 (지수적 증가)
  • 너무 빠르게 늘어나므로 특정 임계값(ssthresh)에 도달하면 혼잡 회피 단계로 전환
cwnd: 1 → 2 → 4 → 8 → 16 ... (ACK마다 배 증가)


🔹 2. 혼잡 회피 (Congestion Avoidance)

  • cwnd가 ssthresh에 도달한 이후부터는 선형 증가 (1 MSS씩)
  • 네트워크를 과도하게 밀어넣지 않고 안정적으로 성장
cwnd: 17 → 18 → 19 ... (매 RTT마다 +1)


🔹 3. 빠른 재전송 & 빠른 회복 (Fast Retransmit & Fast Recovery)

(1) 빠른 재전송 (Fast Retransmit)

  • 같은 패킷에 대한 중복 ACK 3개를 받으면 손실로 판단 (슬라이딩 윈도우)
  • 타이머 만료 기다리지 않고 즉시 재전송

(2) 빠른 회복 (Fast Recovery)

  • 혼잡 발생 시 ssthresh = cwnd / 2 로 낮춤
  • cwnd도 절반으로 줄이고 → 이후 선형 증가(혼잡 회피 모드로)

🔹 동작 흐름 요약

  1. 슬로 스타트: 지수적 증가 (빠른 속도 확보)
  2. 혼잡 회피: 임계치 이후 선형 증가 (안정적 성장)
  3. 패킷 손실 발생
    • 타임아웃 발생 → cwnd = 1로 리셋, 다시 슬로 스타트
    • 중복 ACK 3개 → 빠른 재전송 + 빠른 회복

📊 그림으로 이해

혼잡 윈도우 크기 (cwnd)
│
│        /''''''''''''''''''' 혼잡 회피 (선형 증가)
│      /
│    /      ← 슬로 스타트 (지수적 증가)
│  /
│/________________________________ RTT


✅ 정리

  • 슬로 스타트: 빠른 시작 (지수적 증가)
  • 혼잡 회피: 네트워크 보호 (선형 증가)
  • 빠른 재전송 & 빠른 회복: 손실 시 즉시 대응, 성능 저하 최소화

 

링크 계층

 

🔹 ARP (Address Resolution Protocol)

  • 기능: IP 주소(논리 주소)를 실제 하드웨어 주소(MAC 주소)로 변환.
  • 이유: IP만 알면 목적지까지 경로를 잡을 수 있지만, 실제로 같은 네트워크에서 패킷을 보내려면 MAC 주소가 필요하기 때문.
  • 흐름:
    1. 송신자가 “이 IP 주소 가진 애 누구냐?” 하고 브로드캐스트 요청(ARP Request)을 보냄.
    2. 해당 IP를 가진 장치가 자기 MAC 주소를 알려줌(ARP Reply).
    3. 송신자는 이 매핑(IP ↔ MAC)을 ARP 캐시에 저장해 두고, 이후 재사용.

👉 즉, IP → MAC 변환.


🔹 RARP (Reverse Address Resolution Protocol)

  • 기능: 반대로, MAC 주소(물리 주소)만 알고 있을 때 IP 주소를 알아내는 프로토콜.
  • 사용 사례: 예전에는 디스크가 없는 얇은 클라이언트(디스크리스 워크스테이션)가 부팅할 때 자기 MAC은 알지만, IP는 몰라서 서버한테 “내 MAC은 이건데, 내 IP 좀 알려줘”라고 요청할 때 사용.
  • 흐름:
    1. 클라이언트가 RARP 요청(자기 MAC 주소 포함)을 브로드캐스트.
    2. RARP 서버가 해당 MAC에 맞는 IP를 응답으로 알려줌.
    3. 클라이언트는 그 IP를 사용해 네트워크 시작.

👉 즉, MAC → IP 변환.

 

 

📌 REST(Representational State Transfer)란?

  • 2000년 Roy Fielding 박사 논문에서 제안된 웹 아키텍처 스타일
  • 자원을 “URI로 표현”하고, 해당 자원에 대한 행위는 “HTTP 메서드”로 구분해 처리하는 방식

📌 RESTful API란?

  • REST 원칙을 잘 지켜 구현한 API
  • 즉, HTTP의 특징을 최대한 활용해 **일관적이고, 확장 가능하며, 무상태(stateless)**로 설계된 API

📌 RESTful API의 핵심 특징

  1. 클라이언트-서버 분리 (Client-Server)
    • UI와 데이터 저장/처리가 명확히 분리
  2. 무상태성 (Stateless)
    • 서버는 요청 간 상태(Session)를 유지하지 않음
    • 모든 요청은 필요한 정보를 자체적으로 포함해야 함
  3. 캐시 가능성 (Cacheable)
    • HTTP 캐시(Cache-Control, ETag) 등을 활용 가능
  4. 계층화 (Layered System)
    • 로드밸런서, 프록시, 게이트웨이 등을 중간에 껴도 동작 보장
  5. 인터페이스 일관성 (Uniform Interface)
    • URI로 자원을 식별하고, HTTP 메서드로 행위 표현
    • 예:
      • GET /users/1 → 사용자 조회
      • POST /users → 새 사용자 생성
      • PUT /users/1 → 사용자 전체 수정
      • PATCH /users/1 → 사용자 일부 수정
      • DELETE /users/1 → 사용자 삭제

📌 RESTful하지 않은 API 예시

  • GET /getUser?id=1 → ❌ 동작이 URI에 들어감
  • POST /updateUser/1 → ❌ 메서드와 URI 의미가 중복
  • GET /users/delete/1 → ❌ 조회 메서드(GET)로 삭제 동작

👉 RESTful API에서는 자원(Resource) 은 URI로, 행위(Action) 는 HTTP 메서드로 표현해야 함.


📌 면접 답변 예시

“RESTful API란, REST 아키텍처 스타일을 잘 지켜 구현한 API를 말합니다.

자원을 URI로 표현하고, HTTP 메서드로 행위를 구분하며, 서버는 무상태성을 유지합니다.

예를 들어 GET /users/1은 사용자 1 조회, DELETE /users/1은 사용자 1 삭제를 의미합니다.

이렇게 설계하면 API가 일관되고, 확장성과 재사용성이 높아집니다.”

📌 HATEOAS란?

  • HATEOAS (Hypermedia As The Engine Of Application State)
  • REST 아키텍처의 제약 조건 중 하나
  • 클라이언트가 서버와 상호작용할 때, 응답에 포함된 하이퍼미디어(링크)를 통해 다음 행동을 발견하도록 하는 원칙

📌 실무에서는?

  • 사실 많은 API가 “RESTful”이라고 하지만 HATEOAS까지 구현하지는 않음
  • 대부분은 Swagger/OpenAPI 문서로 엔드포인트를 공유하는 방식
  • HATEOAS는 Hypermedia API (예: HAL, JSON:API, Siren) 같은 규격에서 사용됨
  • 복잡한 클라이언트-서버 통신이나 동적으로 상태 변화가 많은 시스템에서는 유용하지만,
  • 단순 CRUD API에서는 오버엔지니어링일 수 있음

 

 

풀링/SSE/web socket

📌 1. Polling

  • 방식: 클라이언트가 주기적으로 서버에 요청(Are there any updates?) → 서버가 응답
  • 특징
    • 구현이 가장 단순 (기본 HTTP 요청/응답)
    • 하지만 불필요한 요청이 많음 → 네트워크/서버 리소스 낭비
    • 지연(latency)는 짧게 잡으면 실시간성이 있지만, 트래픽 비용↑

📌 2. SSE (Server-Sent Events)

  • 방식: 서버가 단방향으로 클라이언트에 지속적으로 이벤트를 푸시
    • 클라이언트는 EventSource 객체로 연결
    • 서버는 text/event-stream MIME 타입으로 메시지 스트림 전송
  • 특징
    • 단방향(서버 → 클라이언트)만 지원
    • HTTP/1.1 기반, TCP 위에서 동작
    • 자동 재연결, 이벤트 스트림 지원 (id, retry, event)
    • 브라우저 네이티브 지원 좋음
    • 한계: 양방향 통신 불가, 바이너리 데이터 전송 불편

📌 3. WebSocket

  • 방식: HTTP 연결을 업그레이드(Handshake) → 이후는 TCP 소켓 기반 양방향 통신
  • 특징
    • 양방향 통신 (서버 ↔ 클라이언트 모두 자유롭게 메시지 전송)
    • 헤더 오버헤드가 적어 효율적
    • 바이너리/텍스트 전송 모두 가능
    • 실시간 채팅, 게임, 주식 시세 스트리밍에 적합
    • 단점: 구현 복잡도↑, 인프라/보안/프록시 설정 주의 필요

📌 비교 표

구분 Polling SSE (Server-Sent Events) WebSocket

통신 방식 요청/응답 반복 서버 → 클라이언트 단방향 양방향 (Full-duplex)
프로토콜 HTTP HTTP (text/event-stream) HTTP Handshake → TCP
데이터 포맷 제한 없음 UTF-8 텍스트 텍스트 + 바이너리
브라우저 지원 전체 지원 최신 브라우저 대부분 지원 최신 브라우저 대부분 지원
사용 예시 알림 확인, 간단 주기 업데이트 뉴스 피드, 주가/날씨 알림 채팅, 게임, 실시간 협업
장점 단순 구현 자동 재연결, 브라우저 기본 지원 실시간 양방향, 효율적
단점 낭비 많음 단방향 한계, 바이너리 불편 구현/인프라 복잡도↑

📌 면접 답변 예시

“Polling은 클라이언트가 주기적으로 서버에 요청하는 방식이라 단순하지만 비효율적입니다.

SSE는 서버가 지속적으로 클라이언트에 이벤트를 보내주는 단방향 스트리밍으로, 알림/피드 업데이트에 적합합니다.

WebSocket은 HTTP 연결을 업그레이드해서 양방향 통신을 지원하며, 채팅이나 게임 같이 빠른 상호작용이 필요한 서비스에 많이 쓰입니다.”

반응형

'CS > 네트워크' 카테고리의 다른 글

AnyCast란?  (0) 2022.04.11
URL & URI & URN  (0) 2022.02.27
HTTP 메소드 멱등성, 안전한 메소드  (0) 2022.01.31
[TCP] 3-way handshake와 4-way handshake  (0) 2022.01.23
Pooling, Long Pooling, Streaming  (0) 2022.01.23
반응형

최근 회사에서 커서를 구매해줘서 써봤는데, 인생이 달라졌습니다.. 지금까진 뗀석기로 개발한 기분인데요 

 

아무튼 cursor를 쓰면서, 내가 원하는 스크립트를 미리 입력하는 방법을 찾고 있었는데

"rule" 이라는 방법을 찾아서 간단히 공유하고, 제가 실제로 어떻게 쓰는지 몇가지 케이스를 공유해볼려고 합니다.

 

Cursor AI IDE의 룰(Rule), 이렇게 활용합니다

 

Cursor AI IDE의 룰(Rule)은 개발자가 원하는 코딩 원칙과 팀 규칙을 AI에게 전달하는 일종의 “가이드라인 문서”입니다.


보통 코드 스타일이나 문법 규칙은 ESLint, Prettier 같은 도구가 처리합니다. 하지만 Cursor 룰은 그보다 상위 개념으로, AI가 코드를 생성하는 순간부터 개발자 또는 팀의 철학과 컨벤션을 반영할 수 있게 해주는 것이 특징입니다.

즉, 룰을 정의해두면 AI는 단순히 문법적으로 맞는 코드를 제안하는 것이 아니라,

 

  • 팀의 아키텍처 구조를 지키고,
  • 특정 라이브러리 사용을 강제하거나 금지하며,
  • 에러 처리 방식이나 테스트 작성 원칙까지 고려한 코드

를 처음부터 작성해줍니다. 이로써 코드 리뷰에서 반복되는 지적을 줄이고, 개발자가 진짜 중요한 로직과 설계에 집중할 수 있도록 도와줍니다.

 

Cursor 룰은 어떻게 구성되어 있을까?

Cursor IDE에서 룰은 보통 프로젝트 루트에 .cursor/rules/ 폴더 형태로 관리됩니다. 이 안에 마크다운(.mdc) 파일을 작성해 규칙을 기술하게 되며, 각 파일은 하나의 주제를 담당할 수 있습니다.

예를 들어:

  • architecture.mdc → 폴더 간 의존성 규칙
  • performance.mdc → 성능 관련 제안
  • security.mdc → 보안 체크리스트
  • testing.mdc → 테스트 작성 가이드

이처럼 여러 개의 룰 파일을 만들어두면, Cursor가 코드 생성 시 해당 규칙들을 참고해 반영합니다.

 

룰 파일은 기본적으로 Markdown(.mdc) 문서지만, 맨 위에는 아래와 같은 frontmatter 블록을 가집니다.

---
description: 대시보드 UI 접근성 가이드
globs: ["app/(dashboard)/**", "**/*.tsx"]
alwaysApply: false
---
# 규칙
- 데이터 테이블에는 캡션/헤더 스코프를 명시합니다.
- 대용량 리스트는 가상화 시 aria-rowcount/aria-rowindex 검토.
 

이 영역이 바로 룰의 동작 방식과 적용 범위를 제어하는 옵션입니다.

 

.mdc 파일 상단의 --- 구간(프런트매터)으로 적용 범위와 방식을 제어합니다.

  • description: 룰의 요약/설명(Agent가 룰을 선택·요약할 때 사용)
  • globs: 이 패턴에 매칭되는 파일이 대화/작업에 등장할 때 자동 첨부
    • 예) ["app/(dashboard)/**", "**/*.tsx", "!**/*.test.ts"]
  • alwaysApply: true면 항상 컨텍스트에 포함
    이 3개가 룰 동작의 핵심입니다. (Cursor UI의 “Rule Type” 드롭다운도 내부적으로 이 속성 조합을 바꿔줍니다.)

 

어떻게 활용할 수 있을까?

 

  1. 팀 컨벤션 문서화
    기존에는 Notion이나 Wiki에만 적어두던 “팀 규칙”을 Cursor 룰로 옮겨 AI에게 직접 인식시킬 수 있습니다.
  2. 예: “새 페이지는 반드시 app/(dashboard)/ 구조를 따를 것”,
    “데이터 패칭에는 TanStack Query를 활용할 것”
  3. AI 코드 생성 품질 향상
    단순한 함수 작성 요청에도 AI가 미리 정의한 룰을 참고해 구조적이고 일관성 있는 코드를 제안합니다.
  4. 예: “API 호출 시 반드시 Result<T, E> 타입으로 감싸고, 실패 시 로깅 + 사용자 메시지 제공”

  5. 리뷰 비용 절감
    코드 리뷰 단계에서 발생하는 반복적인 스타일 및 규칙 지적이 줄어들어, 리뷰어는 아키텍처 적합성이나 로직 검증에 집중할 수 있습니다.

  6. 지속적인 팀 학습 도구
    신입 개발자나 새로운 팀원이 투입될 경우, 룰에 의해 생성되는 코드만 보더라도 팀이 어떤 방식을 선호하는지 자연스럽게 익힐 수 있습니다.

 

실제로 어떻게 활용하고 있는지?

 

1. PR 리뷰

---
description: "실무형 PR 템플릿"
alwaysApply: false
---

# 📑 실무형 PR 템플릿

## 목적

- PR 문서를 일관된 구조와 충분한 근거로 작성한다.
- 변경 이유(Why) → 설계(How) → 검증(Proof) 흐름을 따른다.
- 문서는 **마크다운** 형식으로 작성한다.

---

## 🟢 필수 섹션

### Description

- 변경 범위를 한 줄로 요약한다.
- 핵심 키워드를 포함한다.

### 배경

- 기존 동작의 한계나 문제점을 설명한다.
- 개선이 필요한 이유를 명확히 기술한다.

### 주요 변경사항

- 개선된 동작 방식을 요약한다.
- 데이터 구조, 처리 방식, 정책 등 변경된 규칙을 나열한다.

### 테스트

- 주요 시나리오 목록을 항목으로 나열한다.
- 실행 방법을 간단히 명시한다.
- 현재 테스트 결과를 요약한다.

### 성능 분석 (필수)

- **시간복잡도**와 **공간복잡도**를 명시한다.
- 계산 결과가 `O(n^2)` 이상일 경우, **주의가 필요하다는 경고 문구**를 반드시 포함한다.
  - 예: `이 PR은 최악의 경우 O(n^2) 복잡도를 가질 수 있으므로, 대규모 데이터 환경에서는 성능 저하에 유의해야 함.`

---

## 🟡 선택 섹션 (대규모/구조 변경 PR 시 작성)

### 타입/구조 변경

- 변경 전/후 구조를 항목으로 정리한다.
- 호환성 영향과 범위를 명확히 기술한다.

### 동작 규칙

- 포함/제외 조건을 단계별로 기술한다.
- 결과 반영 조건과 불변성 원칙을 기록한다.

### 구현 파일

- 변경된 파일 경로를 나열한다.
- 각 파일의 역할과 변경 이유를 요약한다.

### 검증 방법

- 테스트 통과 확인 절차를 적는다.
- 샘플 데이터나 실제 UI 확인 절차를 체크리스트로 작성한다.

### 변경 파일 요약

- 파일별 변경 의도를 불릿 형태로 정리한다.

---

## 작성 규칙 (Do/Don’t)

- **Do**

  - 필수 섹션은 항상 작성한다.
  - 선택 섹션은 변경 규모·영향에 따라 추가한다.
  - 구조 변경은 호환성 영향까지 반드시 기록한다.
  - 불변성 원칙은 필요 시 명시한다.
  - 시간·공간복잡도는 항상 기록하고, `O(n^2)` 이상일 경우 반드시 경고 문구를 추가한다.

- **Don’t**
  - 근거 없는 성능 수치를 기재하지 않는다.
  - 불필요하게 긴 코드 조각을 문서에 직접 삽입하지 않는다(파일 경로나 링크로 대체).

---

## 리뷰어 체크리스트

- [ ] 필수 섹션이 모두 포함되었는가?
- [ ] 변경 이유와 개선 방법이 명확하게 연결되는가?
- [ ] 구조 변경 시 영향 범위가 기술되었는가?
- [ ] 테스트 시나리오가 주요 변경사항을 커버하는가?
- [ ] 시간/공간복잡도가 기록되었는가?
- [ ] 복잡도가 `O(n^2)` 이상일 경우, 경고 문구가 포함되었는가?
- [ ] 성능 및 추후 과제가 현실적으로 기술되었는가?

 

AI가 가장 강력한 부분이죠. 분석엔 강력하지만 상대적으로 창조엔 약합니다. 가장 만족스러운 부분인데

cursor는 문서화에 큰 강점이 있습니다.

 

기획 문서 내용과 "지금까지 작업한 내용을 main 브랜치와 지금 브랜치의 차이를 기반으로 분석해서 PR 문서를 마크다운으로 만들어줘" 라고 위 룰과 함께 명령을 내리면,  작업 컨택스트가 없는 리뷰어도 손쉽게 맥락 파악이 가능해집니다.

 

그리고 리뷰어 혹은 개발자가 성능 병목사항을 일일히 짚어내기 어려운데, 시간복잡도 공간복잡도 측면에서 코드를 분석해주니 

코드 품질도 높일 수 있고 주니어/신입 개발자에게는 어떻게 개발해야하는지 피드백을 AI한테 받아서 개발이 가능해집니다. 

 

2. Test 코드 작성

---
description: Documentation and testing guidelines, available on demand
alwaysApply: false
---

## Documentation

- After defining any function, method, or component:
  - Write a JSDoc comment explaining its purpose, parameters, and return value.
  - Keep it concise but informative.

## Testing

- Test code with various inputs, including edge cases.
- use **vitest** for testing, not jest
- Include both positive and negative scenarios.
- Use a clear "as is → when → to be/should" structure:
  - `describe('<Unit>')`
    - `describe('as is: <current state>')`
      - `describe('when <condition>')`
        - `it('to be: <expected>, should <assertions>')`
- Avoid testing implementation details — focus on public behavior.

 

AI가 가장 강력한 부분 2죠.

잘개 쪼개져서 격리된 모듈과 순수함수로 코드를 구현하고, 작은 context 토큰 범위와 기획 문서를 제공한다면 비교적 정확한 테스트케이스를 뽑아낼 수 있습니다.

 

3. 컨벤션 강제 

 

---
description: Always apply modularization and abstraction principles for maintainable frontend architecture
alwaysApply: true
---

## Core Principles

### 1. Side Effect Isolation

- Treat code that depends on environment (time, network, browser API) as **side effects**.
- Isolate side effects into separate modules or inject them as parameters.
- In tests, mock side-effect modules to avoid flaky results.
- Keep core functions as pure as possible.

### 2. Component Layer Separation

- Split each component into the following logical layers:
  1. **UI (Render)** – Pure presentational JSX, no business logic.
  2. **State** – State management hooks, selectors, store.
  3. **Domain Logic** – Business rules and computations.
  4. **Network (API)** – Data fetching/mutations, isolated into API modules.
- Ensure that these layers communicate via explicit interfaces.

### 3. Strategy Pattern & Dependency Injection

- For business policies or algorithms that may change (e.g., payment calculation, discount rules):
  - Define them as strategy interfaces.
  - Implement strategies separately.
  - Inject the strategy into components/hooks instead of hardcoding.
- Components should depend only on the interface, not concrete implementations.

### 4. Domain Policy Ownership

- Avoid embedding business policy data directly in frontend code.
- Fetch policies from backend whenever possible.
- Frontend focuses on **how** to display/use the data, backend controls **what** policy applies.

 

기존 코드들이 규칙대로 잘 짜여졌다면 더욱 강력해집니다.

코드를 짤때, 팀이 원하는 스타일 및 컨밴션으로 코드를 구현하도록 권장합니다. 

AI가 마구잡이로 코드를 짜는걸 방지해주죠

 

 

마무리

 

정리하자면, Cursor 룰은 “AI에게 우리 팀의 개발 문화를 주입하는 장치"라고 볼 수 있습니다.
단순한 스타일 교정이 아니라, 아키텍처, 성능, 보안, 접근성, 테스트 등 다양한 영역의 원칙을 미리 정의해두고 이를 코드 생성 단계에 반영할 수 있다는 점이 큰 장점입니다.

덕분에 개발팀은 규칙을 반복적으로 확인하거나 리뷰에서 같은 지적을 주고받을 필요가 줄어들고, 진짜 중요한 설계와 문제 해결에 집중할 수 있습니다.

 

개인적으론 문서화가 제일 마음에 드네요. 코드 리뷰 전 코드 품질을 높이기 위한 하나의 셀프 피드백 단계를 추가한 느낌입니다.

그리고 클로드 코드도 대단하다는데 거기까진 못써봤네요

다만 오픈소스에서 클로드코드로 나온 결과물 코드를 몇개 봤는데 장난 아닌듯 싶습니다..

반응형
반응형

다국어 홈페이지 사이드 프로젝트에 접속한 전세계 유저 리스트

 

전 세계 사람들이 나에게 1원씩만(광고 수익) 주면 부자가 되지 않을까..? 싶어서 

16개국 다국어 지원을 통하여 사이드 프로젝트 홈페이지를 만들고 테스트해봤는데요.

어느정도 만족스러운 결과가 나와서 그 과정을 이 글에 공유해봅니다.

다국어 지원은 chatgpt AI로 번역했고, SEO 다국어 지원이 가능한지 가설 및 검증은 아래 링크의 "두루미스" 플랫폼을 참고해서 구현했습니다.

https://blog.naver.com/dreamyoungs_inc/223425945113

 

두루미스에 글을 작성하면 다른 언어들로 검색이 잘 될까요?

두루미스(durumis) 블로그 서비스를 개발하면서 저희도 가끔 고민했던 부분은 구글 검색에 다양한 언어로 ...

blog.naver.com

 

그리고 이 글에서는 "i18n을 어떻게 적용하는지"까지 내용에 넣으면 글이 너무 길어지기 때문에 다루지 않습니다.

next-intl등 여러 좋은 방식을 찾아보길 추천드립니다.

 

SEO란?


SEO
(Search Engine Optimization, 검색엔진 최적화)는 우리가 작성한 블로그 글이 구글 같은 검색 엔진 결과에서 더 잘 보이도록 만들어주는 과정입니다. 처음에는 다소 복잡해 보일 수 있지만, 하나씩 따라 해 보시면 분명 홈페이지 노출에 큰 도움이 될 거예요. 이 글에서는 SEO의 기초 개념부터 온페이지/테크니컬/오프페이지 SEO, 구조화 데이터 활용, 유용한 도구, 그리고 배포 전 체크리스트까지 차근차근 설명해 하겠습니다.

 

1. SEO 기본 용어 이해하기


먼저 SEO를 공부할 때 자주 접하는 핵심 용어들을 알아보겠습니다. 어려운 영어 용어도 많지만, 최대한 쉽게 풀어서 설명해 드릴게요.

 

  • 크롤링 (Crawling): 검색 엔진의 로봇 프로그램인 크롤러(crawler)가 웹사이트의 페이지들을 찾아다니며 콘텐츠를 수집하는 과정입니다. 예를 들어 구글의 크롤러인 Googlebot이 여러분의 블로그 글을 발견하고 내용을 긁어가는 작업이죠. 크롤링이 잘 되어야 내 블로그 글이 검색엔진에 알려지게 됩니다.

  • 인덱싱 (Indexing): 크롤링한 페이지들을 검색 엔진의 데이터베이스에 저장하고 정리하는 과정입니다. 검색 엔진은 수집된 정보를 분류하고 색인(인덱스)을 만들어 두는데, 도서관에서 책을 분류해 두는 것과 비슷합니다.
     페이지의 키워드나 내용에 따라 잘 정리되어야 사용자가 검색할 때 해당 페이지가 나타날 수 있어요.

  • 검색 결과 페이지 (SERP): Search Engine Results Page의 줄임말로, 말 그대로 사용자가 어떤 키워드로 검색했을 때 나오는 검색 엔진 결과 페이지를 뜻합니다. 특히 첫 번째 페이지 결과를 SERP라고 부르는 경우가 많습니다. 대부분의 사용자들은 검색 결과 첫 페이지까지만 보고 끝내기 때문에(75% 이상이 첫 페이지에서 검색을 마친다고 합니다). 내 블로그 글이 SERP 상위에 노출되는 것이 매우 중요합니다.

  • 메타 태그 (Meta Tag): 웹페이지의 <head> 영역에 들어가는 메타데이터(정보)로서, 검색 엔진과 브라우저에게 페이지 정보를 제공합니다. 대표적으로 제목(title)과 설명(description) 메타태그가 중요합니다. 메타 태그는 방문자 눈에는 직접 보이지 않지만, 검색 결과 목록에 표시되는 제목과 설명을 결정합니다.

  • 캐노니컬 태그 (Canonical Tag): 동일하거나 매우 비슷한 콘텐츠가 여러 URL로 중복돼 있을 때,
    검색 엔진에 "이 페이지의 원본은 이것이에요!" 하고 알려주는 태그입니다. <head> 안에 <link rel="canonical" href="정규 URL"> 형태로 넣습니다. 이 태그를 지정하면 검색 엔진은 중복 페이지들 중 canonical로 지정된 URL을 대표 페이지로 간주하여 랭킹 신호를 집중시킵니다. 예를 들어, example.com?page=1이나 example.com/index.html 등 여러 주소로 접속되는 같은 내용의 페이지가 있다면 하나를 canonical로 지정해야 불필요한 중복 색인을 막을 수 있습니다.

    ** 찾아본 결과, 다국어 홈페이지는 각각 번역한 페이지가 자신의 canoinical Tag 주인이 되는게 유리합니다.!

  • 사이트맵 (sitemap.xml): 웹사이트의 모든 페이지 목록을 모아둔 파일로, 책의 목차 같은 역할을 합니다. 주로 XML 형식으로 작성되며 사이트의 최상위 경로(예: https://내블로그/sitemap.xml)에 위치합니다. 사이트맵을 검색 엔진에 제출해 두면 크롤러가 사이트 구조를 한눈에 파악하여, 평소 찾기 어려운 페이지도 빠짐없이 크롤링하고 인덱싱할 수 있게 됩니다
     (※ 티스토리의 경우 기본적으로 RSS 피드가 사이트맵 역할을 하지만, 필요에 따라 직접 sitemap.xml을 제작해 Search Console에 제출할 수도 있습니다.)

  • robots.txt: 웹사이트 최상위 경로(루트 디렉터리)에 두는 텍스트 파일로, 검색 엔진 크롤러의 접근을 제어하는 지침을 적어둡니다. 예를 들어 "이러이러한 곳은 크롤링하지 마!" 혹은 "이 사이트맵 파일 위치는 여기야!" 하는 내용을 담습니다.
    robots.txt 파일이 없으면 크롤러는 웹사이트에서 접근할 수 있는 모든 페이지를 자유롭게 크롤링하며 특정 페이지를 검색 결과에 나오지 않게 막고 싶다면 robots.txt에 경로를 지정해 차단할 수 있습니다.
    또한 robots.txt 맨 아래에 Sitemap: 지시어를 넣어 사이트맵 위치도 알려줄 수 있어요.

  • 구조화 데이터 (Structured Data): 페이지의 내용을 검색 엔진이 더 체계적으로 이해할 수 있도록 추가하는 특별한 형식의 데이터입니다. 예를 들어 블로그 글이 어떤 주제의 기사인지, 작성자는 누구인지, 게시 날짜는 언제인지 등을 구조화된 형식으로 마크업하면, 검색 엔진이 그것을 읽고 이해하여 검색 결과에 풍부한 정보(리치 결과)를 보여줄 수 있습니다. 구조화 데이터는 주로 스키마.org 형태로 표현하며, HTML <script> 태그 안에 JSON-LD 형식으로 넣는 방법이 권장됩니다. 이를 적용하면 검색 결과에서 별점, 자주 묻는 질문(FAQ), 빵부스러기 경로 등 추가 정보가 표시될 수 있어 클릭률을
  • 높이는 데 도움이 됩니다. (실제로 리치 결과로 표시된 페이지는 일반 결과보다 클릭률이 82% 높았다는 사례도 있습니다)

각 용어가 뭘 뜻하는지 글 아래에서 차근차근 알려드릴게요.

 

2. 온페이지 SEO 최적화


온페이지 SEO
는 말 그대로 페이지 내부에서 할 수 있는 최적화 작업입니다. 블로그 글의 콘텐츠와 HTML 구조를 잘 정돈하여 검색 엔진 친화적으로 만드는 것을 의미합니다. 특히 티스토리 블로그 글을 작성할 때 아래 요소들을 신경 써 보세요.

 

메타 태그 최적화: 제목(title)과 설명(description)

 

온페이지 SEO의 첫 걸음은 메타태그 설정입니다. 검색 엔진은 페이지의 <title> 태그와 <meta name="description"> 태그를 읽어 해당 페이지의 제목과 요약을 이해합니다. 이 두 가지를 잘 써주는 것만으로도 SEO 효과를 크게 볼 수 있습니다:

 

  • 제목 태그: 블로그 글 제목은 <title>에 해당하며, 검색 결과에 파란색 큰 제목으로 표시됩니다. 글 내용과 밀접하며 핵심 키워드를 포함한 제목을 작성하세요. 예를 들어 이 글의 제목이 <title>티스토리 초보 개발자를 위한 블로그 SEO 가이드</title>라면, "티스토리", "개발자", "SEO 가이드" 같은 키워드가 담겨 있죠. 너무 길지 않게 (권장 50~60자 이내) 명확한 제목을 정하는 것이 좋습니다.
  • 메타 설명: <meta name="description" content="..."> 부분에 글 요약을 넣을 수 있습니다. 이 내용은 검색 결과의 회색 설명문구로 나타나며, 사용자들이 이 글을 클릭할지 결정하는 데 큰 영향을 줍니다. 간결하면서도 흥미를 끄는 요약을 1~2문장 정도 작성해 보세요. (티스토리에서는 글 작성 시 본문 요약 또는 설명 입력란이 있다면 활용하시면 됩니다.) 만약 메타 설명을 직접 작성하지 않으면, 검색 엔진이 본문 일부를 자동으로 발췌해 보여줍니다.

예시로 HTML 메타태그를 작성하면 다음과 같습니다

<head>
  <title>티스토리 초보 개발자를 위한 블로그 SEO 가이드</title>
  <meta name="description" content="티스토리에서 기술 블로그를 시작한 초심자 개발자를 위한 SEO 최적화 방법을 소개합니다.">
</head>

 

위와 같이 제목과 설명을 명확히 해 두면, 검색 엔진에 내 콘텐츠를 잘 소개하고 사용자도 클릭하기 전에 내용을 파악하기 쉬워집니다. 티스토리도 잘 찾아보시면 위 태그가 존재해요.

2-1) 시멘틱 태그 및 헤딩 태그 활용: H1~H3 구조 잡기

 

블로그 글을 작성할 때 시멘틱 태그를 체계적으로 사용하는 것도 중요합니다. 예시 한가지로 헤딩(heading) 태그를 들어볼게요.

헤딩은 글의 제목과 소제목들을 나타내는 HTML 태그인데요:

 

  • <h1>: 한 페이지에 단 한 번만 사용하는 최상위 제목입니다. 일반적으로 블로그 글의 제목이 h1에 해당합니다 (티스토리에서는 글 제목을 자동으로 h1으로 처리해줍니다).
  • <h2>, <h3>: 글의 주요 섹션 제목(h2)과 그 하위 소제목(h3)에 사용합니다. 글의 흐름을 논리적으로 나누어 주며, 중요한 키워드를 적절히 포함해 주는 게 좋습니다. 예를 들어 지금 보고 계신 글도 각 큰 챕터 제목이 h2, 그 안의 작은 주제가 h3 태그로 구성되어 있습니다.

검색 엔진은 헤딩 구조를 통해 이 페이지가 어떤 주요 주제와 하위 주제들로 이루어져 있는지 파악합니다. 따라서 헤딩을 계층적으로 잘 쓰면 검색엔진이 콘텐츠를 이해하기 쉽고, 독자들도 글을 한눈에 보기 편해집니다. 아래는 간단한 예시입니다:

 

<h1>블로그 글 제목 (예: 웹 접근성 가이드)</h1>
<p>여는 문단...</p>

<h2>1. 접근성이란 무엇인가?</h2>
<p>...설명...</p>

<h3>접근성의 중요 요소</h3>
<p>...설명...</p>

<h2>2. 접근성을 향상시키는 방법</h2>
<p>...설명...</p>

 

이처럼 h1 -> h2 -> h3 순으로 논리적인 아웃라인을 만들고, 단순히 글자 크기를 키우는 용도로 쓰지 않도록 주의하세요. 디자인을 위해 글자 크기를 키울 때는 CSS를 사용하고, 의미적인 구조는 헤딩 태그로 표현하는 것이 SEO에 좋습니다

 

2-2) 내부 링크 구성하기: 사이트 내 유기적 연결

내부 링크란 내 블로그의 다른 글이나 페이지를 본문 중에 연결하는 것입니다. 예를 들어 이전 글이나 관련 글이 있다면 본문에서 해당 키워드에 하이퍼링크를 걸어두는 식이죠. 내부 링크 최적화를 위한 팁은 다음과 같습니다:

 

  • 관련 글 연결: 현재 작성 중인 글과 주제가 비슷하거나 참고하면 좋을 이전 글이 있다면 링크를 거세요. 예를 들어 "지난 번에 HTTP와 HTTPS 차이에 대해 다뤘는데, 자세한 내용은 이 글에서 확인하세요."처럼요.
  • 키워드 앵커텍스트: 링크를 걸 때 클릭 here 같은 문구 대신 해당 페이지를 잘 나타내는 키워드로 링크를 거는 것이 좋습니다. 예를 들어 데이터베이스 성능 튜닝 글이라면 "데이터베이스 인덱싱 기법에 대해서는 이전 포스팅 인덱싱 최적화 방법을 참고하세요." 이렇게 굵은 키워드 부분에 링크를 거는 것이 SEO에 도움이 됩니다.
  • 사이트 구조 파악 도움: 내부 링크가 잘 연결되어 있으면, 방문자가 내 블로그 안에서 더 많은 페이지를 탐색하게 되어 체류 시간도 늘어나고 이탈률이 줄어듭니다. 또한 크롤러도 링크를 따라가며 사이트의 콘텐츠를 효율적으로 발견하므로 색인 구축에 유리해요.

내부 링크를 너무 과하게 걸 필요는 없지만, 자연스럽게 연결될 수 있는 부분이 있으면 적극 활용해 보세요. 티스토리에서는 글 작성 시 편집기에서 링크 아이콘을 눌러 쉽게 본문 링크를 추가할 수 있습니다.

참고: 온페이지 SEO에서는 이 외에도 이미지의 대체 텍스트(alt 속성)를 넣어주는 것, 모바일 친화적 디자인을 사용하는 것, 페이지 로딩 속도를 높이는 것 등이 모두 포함됩니다. 하지만 이러한 부분은 아래 테크니컬 SEO에서 추가로 다루겠습니다.

 

** 글을 잘 쓰면 자발적으로 사람들이 레퍼런스로 참조하겠죠? 글 품질이 중요한 부분입니다.

 

3. 테크니컬 SEO (Technical SEO) 고려사항


이제 테크니컬 SEO 분야로 넘어가 보겠습니다. 테크니컬 SEO는 말 그대로 사이트 기술적 요소들을 최적화하여 검색 엔진이 사이트를 더 잘 크롤링/인덱싱하고, 사용자에게도 더 나은 경험을 제공하도록 하는 작업들입니다. 초심자 개발자 분들도 알아두면 좋은 주요 항목을 살펴보겠습니다.

 

3-1) Canonical URL 설정하기

 

앞서 용어 설명에서 다룬 캐노니컬 태그를 다시 한 번 강조합니다. 만약 티스토리 블로그에서 동일한 콘텐츠가 여러 경로로 접근될 수 있다면 canonical 설정을 고려해야 합니다. 예를 들어, "http://"로도 열리고 "https://"로도 열리거나, ?category=... 같은 파라미터에 따라 같은 글이 다른 URL로 보이는 경우가 있을 수 있습니다. 이런 상황에서는 페이지 <head>에 다음과 같이 원본 URL을 지정하는 캐노니컬 태그를 넣습니다:

 

<link rel="canonical" href="https://yourblog.tistory.com/원본글주소" />

 

이렇게 하면 검색 엔진은 중복된 여러 URL 중 canonical로 지정된 주소만 대표로 취급하고 나머지는 중복으로 판단하여 랭킹에 불이익이 없도록 처리해줍니다.

티스토리 기본 도메인을 사용하는 경우에는 큰 문제는 없지만, 개인 도메인을 연결한 경우 yourblog.tistory.com와 www.yourdomain.com 두 가지 주소로 접속되는 이슈가 생길 수 있으니 이럴 때 canonical 태그로 하나로 정해주시면 좋습니다.

 

사이드 프로젝트에서 적용한 canonical Tag

 

한 대표 언어만 canonical tag로 설정해야할지 아니면 각 언어마다 canonical tag를 설정해야할지 헷갈렸었는데, 여러 페이지(https://webmasters.stackexchange.com/questions/140553/use-of-canonical-tag-on-website-with-different-language-versions-and-separate-mo) 를 참고해보면 각 언어마다 canonical tag를 설정해주는게 옳은 방식이라고 합니다.

 

사이드 프로젝트에서 기본주소/{언어} 로 각 언어로 번역한 동일한 페이지를 만들어놓고, 각 페이지 별로 각각 카노니컬 태그를 설정해줬습니다. 예를 들어 /ko(한국어) 페이지에는 /ko가 canonical Tag 주인으로 설정되어 있고 다른 언어를 alternate로 설정해두었고, /ja(일본어) 페이지에선 /ja가 canonical Tag로 지정되어 있고 다른 언어들이 alternate로 지정되어 있습니다.

 

3-2) 사이트맵 파일과 robots.txt 설정하기

 

sitemap.xmlrobots.txt 파일은 테크니컬 SEO의 기본이라 불립니다. 티스토리 블로그도 마찬가지로 이 개념을 이해하고 활용하면 검색 엔진 크롤러 친화적인 사이트가 됩니다.

  • 사이트맵(sitemap.xml): 내 블로그의 글 목록(및 페이지 목록)을 모두 나열한 XML 파일입니다. 직접 작성하기 어렵다면 검색하면 사이트맵 자동 생성 도구도 많으니 활용하시면 돼요. 완성된 sitemap.xml 파일은 Search Console에 제출해서 구글에 알려줄 수 있습니다. 사이트맵을 제출해 두면 구글이 내 블로그의 새 글도 빠르게 발견할 수 있고, 혹시 내부 링크가 부족해 못 찾는 페이지도 색인될 수 있습니다.
  • 사이트맵 예시 (XML 형식):
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://yourblog.tistory.com/</loc>
    <lastmod>2025-05-25</lastmod>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://yourblog.tistory.com/예시글1</loc>
    <lastmod>2025-05-20</lastmod>
    <priority>0.8</priority>
  </url>
  <!-- ...다른 URL들... -->
</urlset>

 

 

  • 위 예시는 사이트의 홈 페이지와 어떤 글 하나를 목록에 넣은 것입니다. <lastmod>는 마지막 수정 날짜, <priority>는 페이지 중요도를 표시하는데 필수는 아니라서 없어도 괜찮습니다. 티스토리의 경우 기본 제공되는 RSS 피드를 이용해 Search Console에서 사이트맵으로 등록할 수 있습니다. (티스토리 RSS가 글 목록을 제공하므로 사실상 사이트맵 역할을 일부 합니다.)

 

  • robots.txt: 사이트의 루트(최상위 경로)에 robots.txt라는 텍스트 파일을 두면 검색 로봇들이 먼저 이를 확인합니다. 이 파일 안에는 크롤링 허용/비허용 규칙사이트맵 위치 등을 기술할 수 있습니다 티스토리 공식 도메인은 기본적인 robots.txt가 자동으로 설정되어 있어 수정할 순 없지만, 개인 도메인을 사용하면서 직접 서버를 운영한다면 robots.txt를 직접 만들어야 합니다.

    robots.txt 예시:
User-agent: *
Disallow: /admin/
Allow: /

Sitemap: https://yourblog.tistory.com/sitemap.xml

 

위 내용은 모든 크롤러(User-agent: *) 에게 /admin/ 경로는 크롤링하지 말라고 지시하고, 그 외는 모두 허용(Allow)하는 설정입니다. 마지막 줄에는 사이트맵 URL을 명시했습니다. 대다수의 경우 특별히 숨기고 싶은 페이지가 없다면 Disallow 지시자 없이 모두 허용하는 것이 좋습니다. 참고로 robots.txt를 잘못 설정해서 전체 사이트를 Disallow해버리면 검색 유입에 치명적이니, 항상 조심해서 다뤄주세요.

 

3-3) Core Web Vitals 지표 개선하기 (LCP, INP, CLS)

구글은 사용자 경험을 중시하기 때문에, 웹 성능 지표들도 검색 순위에 반영하고 있습니다. 특히 Core Web Vitals(코어 웹 바이탈)라는 세 가지 주요 지표를 정의하여 사이트 품질을 평가하고 있는데요, 각각 다음을 의미합니다.

  • LCP (Largest Contentful Paint): 최대 콘텐츠 표시 시간으로, 페이지 로드 시작 후 가장 큰 콘텐츠 요소(이미지 또는 큰 텍스트 블록 등)가 화면에 나타나는 데 걸리는 시간입니다. 쉽게 말해 본격적인 내용이 로딩되는 속도라고 볼 수 있어요. 2.5초 이내에 LCP가 발생하면 좋은 것으로 평가됩니다.
  • INP (Interaction to Next Paint): 다음 페인트까지의 상호작용으로, 사용자가 페이지에서 어떤 동작(클릭 등)을 했을 때 다음 화면이 그 동작에 반응하여 렌더링되기까지의 시간입니다. 이는 인터랙션 반응성을 측정하는 지표입니다. 0.2초(200ms) 미만이면 우수한 것으로 간주됩니다. (기존에는 FID(첫 입력 지연) 지표를 사용했지만, INP가 보다 포괄적인 지표로 새롭게 도입되었습니다.)
  • CLS (Cumulative Layout Shift): 누적 레이아웃 이동 점수로, 페이지 로딩 중 예상치 못한 레이아웃 변경이 얼마나 발생하는지를 수치화한 것입니다. 예를 들어 이미지가 늦게 로드되어 텍스트가 밀려난다거나, 광고 삽입으로 화면 요소들이 튀는 경우 CLS 점수가 높아집니다. 0.1 이하이면 안정적인 페이지로 평가합니다

 

 

이 세 가지 지표는 사용자에게 쾌적한 페이지 경험을 제공하는지를 나타내며, 구글 검색에서도 중요한 참고 신호로 사용되고 있습니다. 그렇다면, 각 지표를 어떻게 개선할 수 있을까요? 몇 가지 팁을 소개합니다:

 

  • LCP 개선: 가장 큰 요소가 빨리 나타나도록 사이트를 최적화하세요. 예를 들어 첫 화면에 보이는 이미지 용량을 줄이고, 필요한 경우 지연 로드(lazy-load)는 아래쪽 이미지에만 적용합니다. 또, 서버 응답속도를 높이거나 CDN을 이용해 콘텐츠 전송을 빠르게 하는 것도 LCP 향상에 도움이 됩니다. 한마디로 사용자가 첫 콘텐츠를 빨리 볼 수 있게 해주는 작업들입니다.
  • INP 개선: 자바스크립트 등의 대응 속도를 높이기 위해 노력합니다. 너무 무거운 스크립트는 비동기로 로드하거나 필요한 부분만 실행하고, 사용자가 상호작용할 때 메인 쓰레드를 오랫동안 블로킹하지 않도록 코드를 최적화합니다. 복잡한 작업은 Web Worker 등으로 분산하거나, 애니메이션 및 이벤트 핸들러를 효율적으로 작성하는 등의 방법이 있습니다. 결과적으로 사용자 조작에 즉각 반응하는 페이지를 만들자는 것이죠.

  • CLS 개선: 로딩 중 레이아웃이 튀지 않도록 미리 대비합니다. 예를 들어 이미지에는 width/height 속성을 지정하여 자리가 미리 확보되게 하고, 광고나 동적 콘텐츠가 삽입되는 공간도 최소한의 높이를 미리 예약해 둡니다. 글꼴이 로드되며 갑자기 텍스트가 커지는 경우도 있으니, 웹폰트 사용 시 FOIT/FOUT 현상도 신경 쓰는 것이 좋습니다. 핵심은 사용자 스크롤 중에 내용이 밀려서 불편하지 않게 만드는 것입니다.

위 개선 사항들은 모두 개발 단계에서 조금씩 신경 써야 하는 부분이지만, 초심자 분들도 이미지 크기 최적화나 불필요한 스크립트 최소화, 그리고 티스토리에서 제공하는 반응형 스킨 등을 활용하는 것으로도 충분히 시작할 수 있습니다. 

 

3-4) HTTPS 보안 적용의 필요성

 

마지막 테크니컬 요소로 HTTPS에 대해 짚고 넘어가겠습니다. HTTPS는 HTTP에 보안 프로토콜(TLS)이 추가된 것으로, 사용자와 서버 사이의 통신을 암호화하여 도청이나 위변조를 방지합니다. 오늘날 웹 환경에서 HTTPS는 사실상 기본이며, SEO 측면에서도 매우 중요합니다.

 

구글은 이미 2014년에 HTTPS 사용 여부를 랭킹 신호로 활용하기 시작했다고 발표한 바 있습니다. 비록 영향력은 작다고 했지만 시간이 지날수록 중요도가 커지고 있고, 현재는 HTTPS가 기본이 된 사이트들이 검색에서 유리한 것은 확실합니다. 사용자의 신뢰도 측면에서도 주소창에 자물쇠 아이콘이 보이지 않는 사이트는 클릭을 꺼리기 때문에 방문자 유치에도 불리해요.

 

티스토리 자체는 https:// 프로토콜을 지원하므로, 개인 도메인을 연결한 경우에도 SSL 설정을 꼭 하셔야 합니다. (티스토리 관리자 설정 > 블로그에서 HTTPS 사용 여부를 확인할 수 있습니다. 또는 Cloudflare 같은 서비스를 이용해 개인 도메인에 무료 SSL을 적용할 수도 있습니다.) 사이트 전체가 HTTPS로 제공되지 않고 일부만 HTTP라면 혼합된 콘텐츠 경고가 뜨면서 사용자에게 불안감을 줄 수도 있으니, 블로그 자산(이미지 등)도 가급적 https 주소를 사용하세요.

 

요약하면, 속도/보안 등 기술적인 부분도 SEO의 일부입니다. 검색엔진은 양질의 콘텐츠와 함께 빠르고 안전한 사이트를 사용자에게 제공하고자 하므로, 우리 블로그도 이에 맞게 기술적인 토대를 다져두면 좋겠습니다.

 

 

3-5) 구조화 데이터(JSON-LD) 적용하기

앞서 구조화 데이터에 대해 간단히 설명드렸는데요, 이번에는 왜 구조화 데이터를 써야 하는지어떻게 적용하는지 조금 더 살펴보겠습니다.

 

구조화 데이터를 사용하는 이유

일반적인 웹페이지는 사람이 읽기 위한 HTML 콘텐츠로 작성되지만, 구조화 데이터검색엔진이 기계적으로 이해하기 쉽도록 추가로 제공하는 데이터입니다. 이 데이터를 추가하면 다음과 같은 이점이 있습니다:

  • 콘텐츠 명확한 이해: 예를 들어 블로그 글에 구조화 데이터를 넣으면, 이 글의 제목, 저자, 발행일, 본문 요약 등이 어떤 것인지 명시적으로 표시할 수 있습니다. 검색 엔진은 이를 참고하여 해당 정보를 보다 정확하게 파악합니다.
  • 리치 결과(Rich Results): 구조화된 데이터를 활용하면, 구글 검색 결과에 별점, 썸네일 이미지, 방문자 리뷰, 레시피 조리 시간, FAQ 항목 등 다양한 추가 정보를 노출시킬 수 있습니다. 이런 풍부한 결과는 사용자 눈길을 끌어 CTR(클릭률) 상승에 도움이 됩니다. (실제로 앞서 언급했듯이, 리치 결과가 일반 결과보다 클릭률이 82% 높다는 분석도 있습니다.)
  • 음성 검색 및 AI 활용: 구조화 데이터로 콘텐츠를 잘 정리해 두면, 구글 어시스턴트 같은 음성 검색이나 향후 AI 검색 기능에서도 내 콘텐츠를 적절한 답변으로 활용할 확률이 높아집니다. 이는 미래를 대비한 포인트이긴 하지만 알아두면 좋겠죠.

그렇다면, 이렇게 좋은 구조화 데이터를 어떻게 추가할까요? 구글에서는 JSON-LD 형식을 사용할 것을 권장하고 있습니다. JSON-LD는 자바스크립트 객체 표기 형태로 데이터를 표현하기 때문에, 따로 페이지의 화면에 보이지 않고 <script> 태그 안에 정보를 넣어두기만 하면 됩니다.

 

구조화 데이터 간단 예제 

<script type="application/ld+json"> 
{
  "@context": "https://schema.org",
  "@type": "WebApplication",
  "applicationCategory": "EntertainmentApplication",
  "name": "Simmey - 우리의 얼굴 매칭 AI를 사용하여 부모님과 얼마나 닮았는지 확인해보세요!",
  "description": "우리의 얼굴 매칭 AI를 사용하여 부모님과 얼마나 닮았는지 확인해보세요! 재미있고 흥미로운 가족 닮은꼴 비교를 시작할 수 있는 메인 페이지입니다.",
  "author": {
    "@type": "Person",
    "name": "lodado"
  },
  "keywords": "face matching, ai, fun, family",
  "url": "https://mamapapa.vercel.app/",
  "datePublished": "2025-05-25T08:11:55.646Z",
  "isAccessibleForFree": true,
  "publisher": {
    "@type": "Organization",
    "name": "lodado",
    "logo": {
      "@type": "ImageObject",
      "url": "https://mamapapa.vercel.app/Logo.svg"
    }
  },
  "operatingSystem": "ALL",
  "browserRequirements": "A modern browser with JavaScript enabled",
  "offers": {
    "@type": "Offer",
    "price": "0.00",
    "priceCurrency": "USD"
  }
}
</script>

 

위는 제가 실제로 사이드프로젝트에 적용한 JSON-LD  태그입니다. 

 

Tip: 구조화 데이터를 추가한 후에는 구글 리치 결과 테스트 도구를 사용해서 올바르게 구현되었는지 검증해 보세요. 오류가 있다면 검색엔진에 반영되지 않을 수 있으니, 반드시 확인하는 습관을 들이는 것이 좋습니다.

 

3-6) 메타태그 - Open Graph 태그 

 

twitter meta tag

 

 

이외에도 twitter, facebook등 여러 소셜 미디어에서 사용하는 메타 태그 - Open Graph 태그 (og:title, og:description, og:image 등) 를 추가하면 SEO 향상에 도움이 됩니다.

 

4) 오프페이지 SEO 개념과 실천 방법


지금까지는 내 블로그 내부에서 할 수 있는 SEO에 집중했다면, 이제 오프페이지 SEO에 대해 이야기해보겠습니다. 오프페이지 SEO란 내 사이트 외부의 요소들로 검색 순위에 영향을 주는 것들을 말합니다. 그중 가장 대표적인 것이 백링크(Backlink) 입니다.


오프페이지 SEO란?

간단히 말해, 다른 사이트로부터 내 사이트로 연결되는 링크외부에서의 평판을 관리하는 것입니다. 구글 같은 검색 엔진은 어떤 사이트에 좋은 콘텐츠가 많으면, 다른 사이트들도 그 사이트를 많이 링크한다고 판단합니다. 그래서 품질 좋은 백링크가 많을수록 신뢰도와 권위가 높다고 보고 랭킹을 올려주는 것이죠. 반대로 출처가 의심스러운 스팸 링크가 많으면 오히려 패널티를 받을 수도 있습니다.

 

거창한 링크 빌딩 전략을 펼치기는 어렵겠지만, 쉬운 방법 몇 가지를 실천해 볼 수 있습니다:

  • 콘텐츠 공유: 내가 쓴 유용한 글이 있다면 SNS에 직접 공유하세요. 트위터, 페이스북, 링크드인, 개발자 커뮤니티 등 관련된 곳에 포스팅해서 사람들의 관심을 끌면 자연스럽게 방문자도 늘고, 누군가 내 글을 인용해갈 가능성도 생깁니다. 처음부터 큰 영향은 아니어도 노출 기회를 넓힌다는 측면에서 중요합니다.

  • 커뮤니티 참여 및 링크: 개발자라면 Stack Overflow나 Hashnode, Reddit, 또는 국내 개발자 커뮤니티(예: OKky, Dev.to, Velog 등)에 활동하면서 관련 질문에 답변을 달거나 글을 쓰며 자연스럽게 내 블로그 글 링크를 남길 수도 있습니다. 단, 무턱대고 홍보 링크만 남기는 것은 스팸으로 보일 수 있으니 주의하세요. 정말 해당 질문에 도움이 되는 상세한 답변을 제공하면서 참고 링크로 내 글을 언급하는 식이 바람직합니다.
  • 게스트 포스팅 & 협업: 기회가 된다면 다른 블로그에 게스트 포스트를 기고하고 내 블로그를 소개하거나, 지인들의 블로그와 교차로 링크 교환을 할 수도 있습니다. 예를 들어 다른 분의 블로그에 내가 쓴 글을 올리면서 "원문: 내 블로그 링크"를 남기는 방식입니다. 이는 어느 정도 컨텐츠 신뢰를 쌓은 후에 가능하겠지만, 하나의 방법이 될 수 있어요.

  • 디렉토리 및 프로필 활용: 기술 블로그들의 모음 디렉토리에 내 블로그를 등록하거나(국내에 SEO 디렉토리는 흔치 않지만, 해외엔 alltop 같은 사이트들이 있음), 개발 관련 프로필(GitHub, LinkedIn)에 블로그 주소를 넣어두는 것도 링크 노출을 늘리는 소소한 팁입니다.

무엇보다 중요한 것은 고퀄리티 콘텐츠 생산입니다. 콘텐츠가 좋아야 사람들이 자발적으로 공유하고 링크해 주기 때문에, 이것이 가장 이상적인 오프페이지 SEO라 할 수 있습니다. 처음부터 백링크 숫자에 집착하기보다는 블로그에 유용한 글을 꾸준히 쌓고, 커뮤니티에서 신뢰를 얻는 것이 장기적으로 SEO에 큰 도움이 될 거예요.

 

고퀼리티 콘텐츠 생산을 어떻게 하냐고요?
가장 어려운 부분일꺼 같은데 운칠기삼이라고 봅니다.
제 사이드 프로젝트도 이유는 모르겠지만 러시아 검색엔진쪽에서 많이 유입되는거 같네요..

 

배포 전 SEO 체크리스트

마지막으로, 이제 홈페이지를 발행하기 전에 중요 요소들을 하나씩 체크해 봅시다. 아래 SEO 체크리스트를 통해 빠뜨린 부분은 없는지 확인해보세요:

 

  • 제목 태그와 메타 설명 작성: 글의 주제를 명확히 담은 제목(title)을 정했고, 매력적인 메타 설명(description)을 추가하셨나요? (검색 결과에 표시될 문구입니다.)

  • URL 주소 확인: 티스토리에서 자동 생성된 제 글 URL(슬러그)이 의미있게 잘 설정되어 있나요? (가능하다면 영문으로 간단히 키워드를 포함하는 게 좋지만, 티스토리는 자동으로 생성됩니다. 예: https://myblog.tistory.com/15보다는 .../seo-guide 처럼 식별 가능하면 좋습니다.)
  • 헤딩 구조 점검: 본문에 h1은 하나만 있고, h2/h3 등의 소제목이 논리적으로 계층을 잘 이루고 있나요? 혹시 글씨 크기를 키우려고 헤딩을 남용하지 않았는지 확인하세요.

  • 키워드 최적화: 글 내용 중에 주요 키워드가 자연스럽게 포함되어 있나요? 제목, 첫 문단, 헤딩 등에 핵심 단어가 들어가면 좋습니다. 다만 키워드 과다 남용은 금물입니다. 독자를 위한 글쓰기가 우선이에요.

  • 내부 링크 추가: 관련있는 다른 포스팅이 있다면 서로 링크를 연결해 두었나요? 새로운 글에서는 이전 관련 글을, 그리고 나중에라도 이전 글에 이 새로운 글의 링크를 추가하면 더욱 좋습니다.

  • 이미지 대체 텍스트(Alt): 글에 이미지나 그림을 넣었다면 <img alt="..."> 속성에 이미지 설명을 넣었는지 확인합니다. 이는 시각장애인용 보조기기에도 필요하고 SEO에도 유용합니다.

  • 모바일 환경 확인: 모바일에서 내 블로그가 읽기 편한지 확인해 보셨나요? 화면 크기에 따라 레이아웃이 깨지거나 글씨가 너무 작지 않은지 점검하세요. 모바일 친화적(모바일 프렌들리) 사이트는 검색 순위에도 유리합니다.

  • 페이지 속도 확인: PageSpeed Insights 등으로 페이지 점수가 너무 낮게 나오지는 않았나요? 이미지 최적화필요없는 스크립트 지우기 등을 통해 속도를 개선해 보세요. LCP/CLS 지표가 나쁘게 나오면 해당 요소를 수정하는 것도 잊지 마세요.

  • HTTPS 적용 여부: 블로그 주소가 http://가 아닌 https://로 접속되고 있는지 확인합니다. 티스토리 기본 도메인은 HTTPS이지만, 개인 도메인은 SSL 설정을 해야 합니다. 브라우저 주소창에 자물쇠가 뜨는지 꼭 확인하세요.

  • 구조화 데이터 추가: 가능하다면 JSON-LD 형태의 구조화 데이터 스크립트를 페이지에 넣었는지 확인합니다. (티스토리에서는 스킨 편집이나 개별 글 HTML 모드에서 추가 가능) 나중에라도 한 번 도전해보세요.

  • Search Console 색인 요청: 글을 발행했다면 Google Search Console에 가서 새 URL을 색인 요청 해보세요. 이렇게 하면 크롤러가 비교적 빨리 방문해서 색인해갈 확率이 높아집니다.

  • SNS 공유 준비: 마지막으로, 소셜 미디어에 공유할 준비가 되었나요? Open Graph 태그 (og:title, og:description, og:image 등)도 자동 설정되도록 스킨을 구성하면 좋습니다. 공유했을 때 미리보기 카드가 깔끔히 나오면 클릭률 상승에 도움이 되니까요.

내용이 다소 많았지만, 요약하자면 검색엔진이 내 블로그를 잘 찾아가도록 돕고, 방문자에게 좋은 경험을 주는 것이 SEO의 핵심입니다.

 

마지막으로 제가 테스트한 사이드 프로젝트 공유하고 글 마쳐봅니다..!

 

https://mamapapa.vercel.app/ko

 

Simmey - 우리의 얼굴 매칭 AI를 사용하여 부모님과 얼마나 닮았는지 확인해보세요!

우리의 얼굴 매칭 AI를 사용하여 부모님과 얼마나 닮았는지 확인해보세요! 재미있고 흥미로운 가족 닮은꼴 비교를 시작할 수 있는 메인 페이지입니다.

mamapapa.vercel.app

 

홈페이지 자체는 닮은 얼굴 매칭 - 테스트 결과를 보여주는 단순한 웹앱인데요. 사실 광고 및 마케팅을 안해서 SEO를 적용시키더라도 방문자가 별로 없을줄 알았는데, YAndex라고 러시아 검색 엔진에서 자동으로 크롤링해가고 많은 유저(?)들이 들어와서 놀랬네요

 

 

reference

https://wormwlrm.github.io/2023/05/07/SEO-for-Technical-Blog.html

 

기술 블로그를 위한 SEO - 재그지그의 개발 블로그

개인 기술 블로그에 적용한 SEO 방법들을 소개하고, 그 결과물을 공유합니다.

wormwlrm.github.io

https://searchadvisor.naver.com/

 

네이버 서치어드바이저

네이버 서치어드바이저와 함께 당신의 웹사이트를 성장시켜보세요

searchadvisor.naver.com

 

및 구글 서치어드바이저 등

 

 

 

 

반응형
반응형

worker 적용한 cavas(좌측)와 미적용된 canvas(우측), 상단의 count 올라가는 속도가 차이가 난다.

 

 

웹 성능 최적화를 위해 브라우저의 메인 스레드(Main Thread) 부담을 줄이는 것은 매우 중요합니다. 이번 글에서는 웹 초보자도 이해하기 쉽게 Web Worker, OffscreenCanvas, Comlink를 활용해 메인 스레드 작업을 분산하고 성능을 향상시키는 방법을 소개합니다.

 

Web Worker란? 왜 메인 스레드 작업을 분산해야 하나?

Web Worker는 브라우저에서 메인 스레드와 별도로 동작하는 백그라운드 스레드입니다. 쉽게 말해 웹 페이지의 UI와 독립적으로 실행되는 자바스크립트 환경입니다. Web Worker를 사용하면 메인 스레드가 해야 할 무거운 작업을 워커로 위임할 수 있어요

 

이로써 메인 스레드는 보다 중요한 UI 렌더링이나 사용자 입력 처리 등에 집중하고, 무거운 연산으로 인한 프리즈(멈춤) 현상을 줄일 수 있습니다.

 

웹 성능이 저하되는 흔한 이유는 메인 스레드가 과부하되어 UI가 응답하지 않는 상태가 되는 것입니다. 예를 들어 복잡한 계산이나 큰 이미지 처리 등을 메인 스레드에서 하면, 그동안 버튼 클릭이나 화면 렌더링이 멈출 수 있죠. Web Worker로 이러한 작업을 옮기면 브라우저가 다중 스레드처럼 작업하여 UI를 매끄럽게 유지할 수 있습니다. 요약하면, Web Worker는 웹 앱에 멀티스레딩 효과를 주어 성능을 높이는 도구입니다.

 

** 성능상 우월한 방법이 아닙니다! 병렬 처리를 위할때 유용합니다. 

 

OffscreenCanvas: 워커에서 캔버스를 그리기 위한 비밀병기

Canvas를 활용한 그래픽 연산은 웹에서 많이 사용되지만, 기존에는 이 <canvas> 요소가 메인 스레드의 DOM과 연결되어 있어 워커에서 직접 조작할 수 없었습니다. OffscreenCanvas(오프스크린 캔버스)는 이런 제약을 해결해 주는 기술입니다. OffscreenCanvas는 화면에 보이지 않는 캔버스를 의미하며, 캔버스를 DOM과 분리하여 화면 밖(off-screen)에서 렌더링할 수 있게 해줍니다. 덕분에 Web Worker 내부에서도 캔버스에 그림을 그릴 수 있죠.

 

 

오프스크린 캔버스를 쓰는 이유는 간단합니다. 메인 스레드의 부담을 줄이기 위해서입니다. 예를 들어 복잡한 애니메이션이나 물리 시뮬레이션을 Canvas로 구현한다면, 메인 스레드에서 매 프레임 그림을 그리느라 다른 작업을 못할 수 있습니다.

 

OffscreenCanvas를 사용하면 이러한 캔버스 그리기 연산을 워커로 넘겨 병렬 처리할 수 있습니다. 메인 스레드는 UI 업데이트이벤트 처리에 집중하고, 워커는 OffscreenCanvas에 그림을 그린 다음 그 결과만 메인 스레드로 보내 화면에 표시하는 식이죠. 이렇게 하면 캔버스 애니메이션도 부드럽게 돌리고, UI도 끊김 없이 반응하도록 만들 수 있습니다.

 

Comlink: Web Worker와의 통신을 쉽게 해주는 라이브러리

Web Worker를 직접 사용하다 보면, postMessage와 onmessage로 메시지를 주고받는 코드가 다소 번거롭습니다.

 

 

Comlink(컴링크)는 이를 간단하게 만들어주는 경량 라이브러리입니다. Comlink를 쓰면 마치 메인 스레드에서 워커 내부 함수나 변수를 직접 호출하는 것처럼 프로그래밍할 수 있어요.

 

웹 워커와 통신 (postMessage) 흐름도

 

내부적으로는 postMessage를 추상화하여 자동으로 메시지를 전달해주기 때문에, 개발자는 복잡한 메시지 핸들러 대신 평범한 함수 호출 형태로 워커와 소통할 수 있습니다

 

예를 들어 Comlink 없이라면:

  • 메인 스레드에서 worker.postMessage(data)로 데이터를 보내고,
  • 워커 내부에서 self.onmessage로 이벤트를 받아 처리한 뒤,
  • 다시 self.postMessage(result)로 결과를 메인에 보내고,
  • 메인에서는 worker.onmessage로 결과를 받는

일련의 과정을 코딩해야 합니다.

 

Comlink를 쓰면 Web Worker와의 통신을 프록시 객체로 감싸서(일종의 RPC), 개발자가 쉽게 비동기 함수 호출처럼 다루도록 도와주는 도구입니다.

 

예제로 보는 Web Worker + OffscreenCanvas 활용 (Matter.js 물리 시뮬레이션)

이제 간단한 예시로 위 개념들을 연결해보겠습니다. Matter.js라는 자바스크립트 2D 물리 엔진을 이용해 공 몇 개가 튕기는 물리 시뮬레이션을 만든다고 가정해봅시다. 이 시뮬레이션은 계산량이 많으니 Web Worker에서 실행하고, 메인 스레드에서는 현재 프레임 수나 객체 개수 등의 count 상태만 화면에 표시하도록 해볼게요.

 

Main Thread

const canvas = document.getElementById('simCanvas');
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker('physics-worker.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);

// UI에서 1초마다 count 증가 표시 (워커와는 별개로 동작)
let count = 0;
setInterval(() => {
  document.getElementById('counter').innerText = `Count: ${count++}`;
}, 1000);

 

  • 메인 스레드(main.js): 메인 HTML에 <canvas id="simCanvas"></canvas>와 <div id="counter"></div>가 있다고 합시다. 메인에서는 워커를 생성하고 캔버스를 OffscreenCanvas로 넘깁니다.

 

  • 위 코드에서 transferControlToOffscreen()은 DOM 캔버스를 OffscreenCanvas 객체로 변환하여 워커로 소유권 이전(transfer) 하는 역할을 합니다. 이렇게 하면 이후부터는 워커가 해당 캔버스에 직접 그림을 그릴 수 있어요. 또한 메인에서는 1초마다 단순 카운트 숫자를 올려서 #counter 영역에 표시합니다. 이 count 증가 로직은 메인 스레드에서 별도로 돌기 때문에, 물리 시뮬레이션이 돌아가더라도 UI 카운터가 멈추지 않고 계속 업데이트 됩니다.

 

Worker Thread

importScripts('matter.min.js');  // 워커에서 Matter.js 라이브러리 로드
let engine, render;

onmessage = (event) => {
  const offscreenCanvas = event.data.canvas;
  const ctx = offscreenCanvas.getContext('2d');
  // Matter.js 엔진 초기화 (중력, 세계 설정 등)
  engine = Matter.Engine.create();
  // Matter.js에서는 render를 직접 사용하지 않고 OffscreenCanvas의 ctx로 그림
  function update() {
    Matter.Engine.update(engine, 16);            // 물리 세계 한 스텝 진행 (approx 60fps)
    drawScene(ctx, engine.world);               // 사용자 정의: world의 객체들을 ctx로 그리기
    requestAnimationFrame(update);              // 다음 프레임 업데이트 예약
  }
  update(); // 시뮬레이션 시작
};

 

  • 워커 스레드(physics-worker.js): 워커 쪽에서는 메인으로부터 메시지를 받아 OffscreenCanvas 객체를 얻습니다. 그리고 Matter.js 엔진을 초기화하고, 주기적으로 물리 시뮬레이션 업데이트 + 캔버스 렌더링을 합니다.

 

 

  • 위 예시는 단순화를 위해 의사코드 형태로 나타냈지만, 핵심은 워커에서 주기적으로 Matter.js 엔진을 업데이트하고 OffscreenCanvas의 2D 컨텍스트에 결과를 그림입니다.
  • requestAnimationFrame도 워커 환경에서 사용할 수 있는데, 이 경우 OffscreenCanvas에 그리는 것이므로 메인 화면에 바로 반영됩니다. 메인 스레드는 이 과정에 관여하지 않으므로, 물리 계산과 캔버스 렌더링으로 인한 메인 스레드 블로킹이 발생하지 않습니다. 메인은 그저 앞서 설정한 setInterval로 UI 카운트만 올리고 있을 뿐이죠.

worker 적용한 cavas(좌측)와 미적용된 canvas(우측), 상단의 count 올라가는 속도가 차이가 난다.

 

  • 메인에서의 결과: 실제로 실행해보면, <canvas>에는 워커가 그린 Matter.js 물리 시뮬레이션 (예: 공들이 튕기는 애니메이션)이 매끄럽게 표시되고, 동시에 <div id="counter">에는 1, 2, 3... 하는 카운트 숫자가 끊김 없이 증가하는 것을 볼 수 있습니다. 만약 워커를 사용하지 않고 모든 작업을 메인에서 했다면, 물리 연산이나 그리기로 인해 카운트 업데이트가 지연되거나 멈췄을 겁니다. 이처럼 워커+오프스크린 캔버스 구조는 무거운 연산을 백그라운드에서 처리하면서도 메인 UI를 부드럽게 유지시킵니다.

맺으며

정리하면, Web Worker는 무거운 작업을 메인 스레드에서 떼어내어 비동기로 처리할 수 있게 해주고, OffscreenCanvas는 그런 워커에서 그래픽을 그릴 수 있도록 도와주며, Comlink는 메인-워커 간 통신을 개발자 친화적으로 만들어 줍니다. 이 세 가지를 조합하면, 초기 단계의 웹 개발자도 비교적 쉽게 메인 UI의 성능을 최적화하는 구조를 설계할 수 있습니다.

 

** 다시 적지만, 애니메이션이 좀 더 매끄러워지는거 같은 성능상의 이점은 없습니다

다만 "무거운 연산"을 메인 쓰레드에서 분리 가능합니다. 


reference 

 

https://web.dev/articles/offscreen-canvas

 

 

반응형
반응형

개념 설명: 3D → 2D 투영의 기본 원리

 
3D에 있는 한 점을 2D 화면에 찍는 것을 투영이라고 합니다. 투영을 쉽게 이해하려면, 손전등과 그림자 비유를 떠올릴 수 있습니다. 벽을 화면이라고 생각하고, 3D 물체에 빛을 비춰 벽에 드리운 그림자가 바로 그 물체의 투영입니다. 이때 손전등을 물체에 바짝 가까이 대면 그림자가 물체 크기와 다르게 크게 또는 작게 일그러져 보이는데, 이것이 원근 투영과 비슷합니다. 반대로 태양빛처럼 아주 멀리서 평행하게 오는 빛을 생각해보면, 물체의 그림자는 물체의 크기와 동일한 비율로 찍히게 됩니다. 이처럼 빛이 평행하게 온다면 물체까지의 거리에 상관없이 그림자 크기가 변하지 않는데, 이것이 정투영의 원리입니다.
 
컴퓨터 그래픽스에서 정투영 투영은 실제로 아주 간단한 수학적 변환입니다. 3차원 좌표 (X, Y, Z)가 주어지면, 그대로 X, Y 좌표만 가져오고 Z 좌표는 버립니다. 이렇게 X와 Y를 사용해 평면에 찍으면 3D 점을 2D 화면에 옮길 수 있습니다.
(다른 방법으로는 원근 투영이 있습니다)
 
예를 들어, 높이가 다른 두 사람이 있다고 해도 정투영으로 보면 둘 다 같은 키로 보이겠지만, 원근 투영으로 보면 가까이 선 사람은 크게, 멀리 선 사람은 작게 표현되는 차이가 생기는 것입니다.

 정투영이란 무엇인가?

 
정투영(Orthographic Projection, 직교 투영이라고도 합니다)은 3D 공간에 있는 물체를 2D 화면에 그릴 때, 거리와 상관없이 똑같은 비율로 투영하는 방법입니다. 다시 말해, 가까이 있는 물체나 멀리 있는 물체나 크기 변화 없이 동일한 비율로 그려집니다
 
반대로 현실 세계에서 우리가 보는 모습이나 3D 게임의 일반 카메라 시점은 원근 투영(Perspective Projection)이라고 하는데, 이는 가까운 것은 크게, 먼 것은 작게 보이도록 그리는 방식입니다. 원근 투영 덕분에 우리는 깊이감과 거리감을 느낄 수 있지만, 치수를 정확하게 재거나 평면도면을 그릴 때는 오히려 불편할 수 있습니다.
 

정투영과 원근 투영 차이

 
위 그림은 원근 투영정투영의 차이를 간단히 보여줍니다. 왼쪽은 원근 투영으로 본 큐브(정육면체)이고, 오른쪽은 정투영으로 본 모습입니다. 왼쪽 그림에서는 앞쪽 면의 빨간 테두리가 크게 보이고, 뒤쪽 면의 파란 테두리는 멀어지면서 작아져 내부의 작은 사각형처럼 보입니다.
 
회색으로 그린 모서리 선들도 멀어지면서 서로 가까워져, 마치 선들이 한 점으로 모이는 듯한 원근감이 나타납니다. 반면 오른쪽 정투영 그림에서는 앞면(빨간색)과 뒷면(파란색)의 크기가 동일하게 그려져 있습니다. 모든 모서리 선이 서로 평행하게 표시되며, 거리에 따른 크기 왜곡이 없어서 뒷면이 앞면에 정확히 겹쳐 보입니다. 이처럼 정투영은 거리와 무관하게 실제 크기를 그대로 보여주기 때문에, 도면 작업이나 멀리 있는 객체까지 정확한 치수로 표현해야 하는 경우에 유용합니다.
 
 
이제 실제 코드에서 이 단계들이 어떻게 구현되는지 projectOrtho 함수를 통해 알아보겠습니다.
우선, 원리를 간단히 설명해보자면 아래와 같습니다.
 

  1. 피벗 이동: 3D 점을 기준점(pivot)이 중심이 되도록 좌표를 이동합니다. (예: 카메라가 보는 중심으로 좌표계 원점을 맞춤)
  2. 회전 변환: X축, Y축, Z축 순서로 3D 점을 회전시킵니다. (예: 장면을 위아래로 보기 위해 X축 회전, 옆으로 보기 위해 Y축 회전 등)
  3. 투영: 회전된 3D 좌표에서 Z 값을 버리고 X, Y만 남겨 2D 평면에 투영합니다. (정투영이라서 원근 왜곡 없음)
  4. 배율 조정: 3D 단위를 픽셀 크기에 맞게 확대하거나 축소합니다. (예: 1미터를 100픽셀로 보이게 스케일링)
  5. 오프셋 이동: 계산된 2D 좌표에 화면상의 위치 offset을 더해 캔버스 좌표로 변환합니다. (예: 캔버스 중심이나 좌상단 기준으로 옮김)

 
실제 코드에서 이 단계들이 어떻게 구현되는지 projectOrtho 함수를 통해 알아보겠습니다.
 

코드 분석: projectOrtho 함수 단계별 설명

 
이제 provided된 projectOrtho 함수의 내부를 살펴보며, 앞에서 설명한 정투영 변환의 각 단계를 코드로 확인해봅시다. 이 함수는 3D 좌표를 받아 정투영을 적용한 2D 화면 좌표를 계산해주는데, React + TypeScript로 Canvas에 그릴 때 사용할 핵심 로직입니다. 복잡해 보이지만, 우리가 방금 이해한 다섯 단계를 차례로 수행하고 있을 뿐입니다. 코드와 함께 하나씩 살펴볼까요
 

// 3D 점을 정투영하여 2D 화면 좌표로 변환하는 함수
function projectOrtho(
  point: { x: number, y: number, z: number }, 
  pivot: { x: number, y: number, z: number }, 
  rotation: { x: number, y: number, z: number },  // 각도 값(라디안)
  scale: number, 
  offset: { x: number, y: number } 
) {
  // 1. 피벗 기준으로 좌표 이동 (pivot을 원점으로 옮기기)
  let x = point.x - pivot.x;
  let y = point.y - pivot.y;
  let z = point.z - pivot.z;

  // 2. X축 회전: 위아래 방향 회전 (pitch)
  const cosX = Math.cos(rotation.x);
  const sinX = Math.sin(rotation.x);
  let y1 = y * cosX - z * sinX;
  let z1 = y * sinX + z * cosX;
  y = y1;
  z = z1;

  // 3. Y축 회전: 좌우 방향 회전 (yaw)
  const cosY = Math.cos(rotation.y);
  const sinY = Math.sin(rotation.y);
  let x2 = x * cosY + z * sinY;
  let z2 = -x * sinY + z * cosY;
  x = x2;
  z = z2;

  // 4. Z축 회전: 평면 회전 (roll)
  const cosZ = Math.cos(rotation.z);
  const sinZ = Math.sin(rotation.z);
  let x3 = x * cosZ - y * sinZ;
  let y3 = x * sinZ + y * cosZ;
  x = x3;
  y = y3;

  // 이제 정투영이므로 z는 사용하지 않습니다 (Z값 버림)

  // 5. 스케일 조정: 3D 단위를 화면 확대배율에 맞춤
  x = x * scale;
  y = y * scale;

  // 6. 오프셋 적용: 캔버스 좌표계로 이동 (예: 캔버스 중심을 (0,0)→(offset.x, offset.y)로)
  const screenX = x + offset.x;
  const screenY = y + offset.y;

  // 계산된 2D 화면 좌표 반환
  return { x: screenX, y: screenY };
}

 
 
 

  • 피벗 이동 전후: 3D 공간에 좌표축과 점이 있다고 생각해 봅니다. 피벗 이동을 하기 전에는 점이 원점에서 point.x, point.y, point.z만큼 떨어진 곳에 있습니다. 피벗을 원점으로 맞추고 나면, 이제 그 점은 새로운 좌표계에서 (x - pivot.x, y - pivot.y, z - pivot.z) 위치로 보이게 됩니다. 쉽게 말하면, 기준점을 가운데로 옮겼더니 점의 좌표가 바뀌었다고 이해하면 됩니다. 하지만 실제 공간에서 점의 물리적 위치는 변하지 않았고, 우리가 좌표만 옮겨서 바라보고 있는 것입니다.

 

  • 축별 회전 효과: 3차원 회전 행렬을 이용합니다. 
Rotation Transformation

 
X축, Y축, Z축으로 차례로 회전하면, 3D 점은 공간에서 이리저리 자리를 바꿉니다. X축으로 회전하면 점이 위아래로 이동하는 것처럼 보이고, Y축으로 회전하면 좌우로, Z축으로 회전하면 화면 평면상에서 회전합니다. 최종적으로 세 번의 회전을 모두 마치면, 처음의 3D 점은 우리가 정한 각도에서 바라본 위치로 옮겨져 있게 됩니다. 여러 회전이 한꺼번에 적용되었기 때문에, 점의 새 좌표 (x3, y3, z3)는 원래 좌표와 많이 달라졌겠지만, 이 좌표는 우리가 장면을 해당 각도로 본 경우에 그 점이 어디 있는지를 나타낸다고 생각하면 됩니다.
 

  • 투영 및 스케일: 이제 3D 좌표의 z는 버리고 (x3, y3)만 남깁니다. 이 단계에서 이미 2D 투영이 이루어졌다고 볼 수 있습니다. 남은 (x3, y3)는 아직 수치적으로는 작은 값일 수 있는데, 스케일을 곱해서 화면에 보일 크기로 확대합니다. 만약 점들이 서로 가까이 모여 있었다면 스케일을 키워서 벌려주고, 너무 넓게 퍼져 있었다면 스케일을 줄여서 화면에 잘 들어오도록 할 수 있습니다.

 

  • 오프셋 적용: 마지막으로 offset을 더해 캔버스 좌표로 변환하면, 이제 진짜 화면 픽셀 좌표가 나옵니다. 이 좌표를 사용해서 Canvas에 점을 찍으면, 우리가 3D에서 지정했던 그 점이 2D 화면상의 정확한 위치에 표시됩니다.

 
 
요약하면, projectOrtho 함수는 (피벗 이동) → (회전) → (투영) → (스케일) → (오프셋) 순서로 3D 좌표를 변환하여 2D 화면 좌표를 반환합니다. 
 
 
이제 projectOrtho 함수를 이용해서 실제 HTML5 Canvas에 3D 점들을 찍어보는 간단한 React 컴포넌트를 만들어보겠습니다. React와 TypeScript를 사용하므로, 함수형 컴포넌트와 훅(hook)을 활용해볼게요. 예제에서는 정육면체 모서리 8개 점을 3D 좌표로 정의하고, 이를 정투영으로 화면에 표시해보겠습니다.
 

render({ ctx, engine, id }) {
      const cfg = shapePrefabs.box3D as Shape3DConfig;
      const pos = engine.position3DStore.get(id)!; // { x, y, z }
      const rotation = engine.rotationStore.get(id)!; // { x, y, z }

      const perspective = engine.perspective3DManager.getRotation3D();
      const cameraDistance = engine.perspective3DManager.getCameraDistance();

      const camera = engine.perspective3DManager.getCamera();

      const { width, height, depth } = cfg.size as Shape3DConfig["size"];

      // 8개 꼭짓점 정의 (center 기준)
      const verts = [
        { x: -width / 2, y: -height / 2, z: -depth / 2 },
        { x: width / 2, y: -height / 2, z: -depth / 2 },
        { x: width / 2, y: height / 2, z: -depth / 2 },
        { x: -width / 2, y: height / 2, z: -depth / 2 },
        { x: -width / 2, y: -height / 2, z: depth / 2 },
        { x: width / 2, y: -height / 2, z: depth / 2 },
        { x: width / 2, y: height / 2, z: depth / 2 },
        { x: -width / 2, y: height / 2, z: depth / 2 },
      ];


      // 월드 좌표로 이동 및 투영
      const projected = verts.map((v) => {
        const point = { x: v.x + pos.x - camera.x, y: v.y + pos.y - camera.y, z: v.z + pos.z - camera.z };

        return projectOrtho({
          canvas: ctx.canvas,
          point: point,
          pivot: { x: pos.x - camera.x, y: pos.y - camera.y, z: pos.z - camera.z },
          rotation: {
            ...rotation,
            x: rotation.x + perspective.x,
            y: rotation.y + perspective.y,
            z: rotation.z + perspective.z,
          },
          offset: { x: 0, y: 0 },
          cameraDistance: cameraDistance,
        });
      });

      // 연결할 에지(12개)
      const edges = [
        [0, 1],
        [1, 2],
        [2, 3],
        [3, 0],
        [4, 5],
        [5, 6],
        [6, 7],
        [7, 4],
        [0, 4],
        [1, 5],
        [2, 6],
        [3, 7],
      ];

      for (let k = 0; k < edges.length; k++) {
        const [i, j] = edges[k];
        const p = projected[i];
        const q = projected[j];

        // HSL로 무지갯빛 색상 생성
        const hue = (k / edges.length) * 360;
        ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;

        // 선 하나만 그리기
        ctx.beginPath();
        ctx.moveTo(p.x, p.y);
        ctx.lineTo(q.x, q.y);
        ctx.stroke();
      }

      ctx.stroke();
    },

 
위 코드에 추상화된 클래스 or 함수가 있어서 이해하기 어려울수도 있는데,

핵심적인 부분은 projectOrtho 함수로 3D를 2D로 변환하는 부분이 핵심이고
나머지는 사각형 정보를 담는 헬퍼 함수라고 보시면 됩니다.  (x,y,z 좌표 정보나 카메라 위치, 줌 인 & 아웃 정보 등)
 
 

canvas에 정투영한 3D 정사각형

 
 
 
 
 
reference
 
https://cynthis-programming-life.tistory.com/entry/3%EC%B0%A8%EC%9B%90-%ED%9A%8C%EC%A0%84-%ED%96%89%EB%A0%AC-%EA%B5%AC%ED%95%98%EA%B8%B0-by-%EC%98%A4%EC%9D%BC%EB%9F%AC%EA%B0%81-Input
 

반응형
반응형

AI가 발전되어
이제 canvas나 svg tag를 직접 조작할수 있는 레벨까지 온거 같네요.
 
이 글에서는 svg에 대해 간단히 설명하고 "Figma"에서 svg를 직접 추출해내어 react로 수작업으로 progressbar를 만들며 간단한 인터렉션을 넣어봅시다.
 

SVG 간단 설명

SVG는 Scalable Vector Graphics의 약자로, 크기를 늘리거나 줄여도 화질이 깨지지 않는 벡터 이미지 포맷이에요
 
쉽게 말해, 그림을 수학적인 좌표와 도형으로 표현하는 언어이지요. 그래서 그림을 태그(tag)와 속성으로 작성하게 되는데, HTML이 <div>나 <p> 같은 태그로 문서를 구조화하듯, SVG도 여러 태그를 사용해요. 겁먹을 필요 없어요. 하나씩 알아볼까요?
 

  • <svg>: 모든 SVG 코드의 뿌리, 도화지 역할을 하는 태그예요. 이 태그 안에 우리가 그리고 싶은 도형들을 넣으면 돼요.
  • <g>: 그룹(group)을 만드는 태그예요. 여러 도형을 <g>로 묶으면 한꺼번에 이동하거나 스타일을 같이 적용하기 편해요. 폴더처럼 묶어준다고 생각하면 쉬워요.
  • <rect>: 사각형(rectangle)을 그리는 태그예요. x, y 좌표와 width, height 속성을 주면 해당 위치에 네모를 그려줘요. 색을 채우고 싶다면 fill 속성으로 색상을 지정하면 돼요.
  • <path>: 가장 만능 도형 그리기 태그예요. 직선, 곡선 등 복잡한 모양은 <path>의 d 속성에 특별한 문자열(그림 그리는 명령어들)을 넣어서 표현해요. 마치 점 잇기 놀이로 그림을 그린다고 상상해보세요.
  • <linearGradient>: 예쁘게 색을 섞어주는 그라디언트(gradient) 효과를 정의하는 태그예요. 시작 색부터 끝 색까지 점차 변하는 색깔띠를 만들 수 있어요. 이 태그는 주로 <defs> 안에 넣어서 정의하고, 나중에 도형에서 fill="url(#그라디언트ID)"처럼 불러내 써요.
  • <stop>: 방금 말한 그라디언트에서 색이 바뀌는 지점을 지정하는 태그예요. offset 속성으로 위치(0%~100%)를 정하고 stop-color로 색을 정해요. 여러 개의 <stop>을 넣으면 색이 여러 번 변할 수도 있어요.
  • <clipPath>: 그림을 자르는 액자 같은 역할이에요. <clipPath> 안에 특정 모양을 그려놓고 다른 도형에 적용하면, 그 모양 안쪽 부분만 보이고 바깥 부분은 잘려 보이게 돼요. 꼭 종이를 오려서 겹쳐놓은 것처럼요.
  • <mask>: 마스크는 투명한 필름지처럼 부분 투명하거나 가려주는 효과를 줄 때 써요. 예를 들어, <mask> 안에 검정~흰색 그라디언트를 넣고 어떤 이미지에 씌우면, 천천히 사라지는 페이드 효과를 낼 수 있어요.
  • <filter>: 블러(흐리게)나 그림자 같은 특수 효과를 줄 때 사용해요. 포토샵의 필터를 떠올리시면 돼요. <filter> 안에 종류와 강도를 지정하고 도형에 적용하면, 그림자가 생기거나 반짝거리게 만들 수도 있어요.

 
어때요, 하나하나 보니까 생각보다 할 만하지요? 😀 이 밖에도 <circle>로 원 그리기, <text>로 글자 쓰기 등 많은 태그가 있지만, 위에 소개한 태그들만 알아도 SVG의 핵심은 대부분 배운 셈이에요. 이제 실제로 SVG를 어떻게 활용하는지 알아볼까요?
 

Figma에서 SVG 추출하기

다운받은 svg 파일들

 
Figma로 멋진 이미지를 만들었다고 가정해봐요. 그 이미지를 그대로 우리 React 앱에 가져오려면 SVG 파일로 추출하면 편해요. 방법은 정말 간단해요. Figma에서 원하는 객체를 선택하고 오른쪽 Export 패널에서 포맷을 SVG로 선택 후 Export 버튼을 눌러보세요.
 
또는 더 쉬운 방법으로, 객체를 오른쪽 클릭한 뒤 Copy as SVG를 선택하면 SVG 코드를 바로 복사할 수도 있어요 이렇게 얻은 SVG 코드나 파일을 이제 React에서 사용해보겠습니다.
 

React와 SVG로 세로 프로그레스 바 만들기

 
이제 Figma에서 가져온 SVG를 이용해, 세로로 된 프로그레스 바(슬라이더)를 만들어 볼 거예요.
이 프로그레스 바는 최소값(min)최대값(max) 두 가지 값을 가지며, 둘 다 사용자가 드래그해서 조절할 수 있는 인터랙티브한 컴포넌트예요. React와 TypeScript로 구현하면서, SVG로 도형을 그리고 pointer 이벤트로 드래그 기능까지 넣어볼게요!
 

1. 컴포넌트 상태 정의하기

 
먼저 React 함수 컴포넌트를 만들고, 프로그레스 바의 현재 최소값과 최대값을 상태(state)로 관리하겠습니다. React의 useState 훅을 사용해서 두 개의 숫자 상태 (minVal과 maxVal)를 만들어요. 초기값은 예를 들어 20과 80으로 설정할게요 (전체 범위를 0~100으로 생각). 그리고 드래그 중인 상태를 추적하기 위해 dragging이라는 상태도 하나 만들어요. dragging은 현재 드래그 중인 손잡이가 "min", "max" 또는 없음을 나타내도록 할 것입니다.

 
import React, { useState } from 'react';

function VerticalRangeBar() {
  const [minVal, setMinVal] = useState<number>(20);   // 최소값 (%)
  const [maxVal, setMaxVal] = useState<number>(80);   // 최대값 (%)
  const [activeThumb, setActiveThumb] = useState<'min' | 'max' | null>(null);
  ...
}

 
이렇게 상태를 정의해두면, 나중에 SVG에서 이 값들에 따라 손잡이 위치나 색깔 변화를 줄 수 있어요.

2. SVG로 모양 그리기

이제 컴포넌트의 JSX 반환값으로 SVG 요소를 작성해 볼게요.
우선 progress bar의 "몸통"을 그려볼게요.
 

      <defs>
        {/* Gradient & clip */}
        <linearGradient
          id="rangeGradient"
          gradientUnits="objectBoundingBox"
          x1="0"
          y1="0"
          x2="0"
          y2="1"
        >
          <stop offset="0%" stopColor="var(--ChartColor-ChartColor97, #FFFFE0)" />
          <stop offset="48.56%" stopColor="var(--ChartColor-ChartColor99, #89C0C4)" />
          <stop offset="100%" stopColor="var(--ChartColor-ChartColor100, #579EB9)" />
        </linearGradient>
        <clipPath id="rangeClip">
          <rect x={trackX} y={maxPos} width={trackThickness} height={minPos - maxPos} />
        </clipPath>

        {/* Button filter & clip */}
        <filter
          id="filter0_dd_1314_121150"
          x="0.186035"
          y="0.25"
          width="50"
          height="38"
          filterUnits="userSpaceOnUse"
          colorInterpolationFilters="sRGB"
        >
          <feFlood floodOpacity="0" result="BackgroundImageFix" />
          <feColorMatrix
            in="SourceAlpha"
            type="matrix"
            values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
            result="hardAlpha"
          />
          <feOffset />
          <feGaussianBlur stdDeviation="1.1" />
          <feColorMatrix
            type="matrix"
            values="0 0 0 0 0.0117647 0 0 0 0 0.0156863 0 0 0 0 0.0156863 0 0 0 0.56 0"
          />
          <feBlend
            mode="normal"
            in2="BackgroundImageFix"
            result="effect1_dropShadow_1314_121150"
          />
          <feColorMatrix
            in="SourceAlpha"
            type="matrix"
            values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
            result="hardAlpha"
          />
          <feOffset />
          <feGaussianBlur stdDeviation="0.6" />
          <feColorMatrix
            type="matrix"
            values="0 0 0 0 0.0117647 0 0 0 0 0.0156863 0 0 0 0 0.0156863 0 0 0 0.5 0"
          />
          <feBlend
            mode="normal"
            in2="effect1_dropShadow_1314_121150"
            result="effect2_dropShadow_1314_121150"
          />
          <feBlend
            mode="normal"
            in="SourceGraphic"
            in2="effect2_dropShadow_1314_121150"
            result="shape"
          />
        </filter>
        <clipPath id="clip0_1314_121150">
          <rect x="15.186" y="12.25" width="20" height="14" rx="5" fill="white" />
        </clipPath>
      </defs>

      {/* Track background */}
      <rect
        x={trackX}
        y={margin}
        width={trackThickness}
        height={height}
        fill="#ddd"
       
      />
      {/* Gradient clipped to range */}
      <rect
        x={trackX}
        y={margin}
        width={trackThickness}
        height={height}
        fill="url(#rangeGradient)"
        clipPath="url(#rangeClip)"
 
      />

 
 
위 코드에서 <linearGradient id="rangeGradient">를 정의하고, 트랙용 <rect>의 fill에 url(#"rangeGradient")을 지정했어요.
이렇게 하면 트랙 사각형에 gradient 효과를 지정할 수 있습니다.
def 안에 각종 효과(gradient, filter, clip path 등)을 미리 선언해놓고, 다른 컴포넌트에서 해당 효과를 id를 참조해서 사용이 가능합니다. 
 

track example

 
"track"을 그려놓고 지금 min, max 값만큼 clip해서 그라데이션 효과를 적용합니다.
 
이제 손잡이를 그려볼게요. 손잡이는 min과 max 값을 조절하는 버튼이라고 생각하면 됩니다. 손잡이는 아까 따로 다운로드해두었던 scroll.svg 를 활용합니다. 
 

...(위 코드 생략)

    <g
        transform={`translate(${btnOffsetX}, ${btnOffsetY(minPos)})`}
      >
        <SliderButton
          onMouseDown={e => { e.preventDefault(); setActiveThumb('min'); }}
        />
      </g>

      {/* Max thumb */}
      <g
        transform={`translate(${btnOffsetX}, ${btnOffsetY(maxPos)})`}
      >
        <SliderButton
          onMouseDown={e => { e.preventDefault(); setActiveThumb('max'); }}
        />
      </g>
    </svg>
    
interface SliderButtonProps {
  onMouseDown: (e: React.MouseEvent<SVGRectElement, MouseEvent>) => void;
}

const SliderButton: React.FC<SliderButtonProps> = ({ onMouseDown }) => (
  <g filter="url(#filter0_dd_1314_121150)">
    <path
      d="M12.186 17.25C12.186 14.4886 14.4246 12.25 17.186 12.25H33.186C35.9475 12.25 38.186 14.4886 38.186 17.25V21.25C38.186 24.0114 35.9475 26.25 33.186 26.25H17.186C14.4246 26.25 12.186 24.0114 12.186 21.25V17.25Z"
      fill="#F4F4F4"
    />
    <g clipPath="url(#clip0_1314_121150)">
      <rect
        x="15.186"
        y="12.25"
        width="20"
        height="14"
        rx="5"
        fill="#D9D9FF"
        fillOpacity="0.11"
      />
      <path
        d="M19.8527 23.25H30.5194C30.886 23.25 31.186 22.95 31.186 22.5833C31.186 22.2167 30.886 21.9167 30.5194 21.9167H19.8527C19.486 21.9167 19.186 22.2167 19.186 22.5833C19.186 22.95 19.486 23.25 19.8527 23.25ZM19.8527 19.9167H30.5194C30.886 19.9167 31.186 19.6167 31.186 19.25C31.186 18.8833 30.886 18.5833 30.5194 18.5833H19.8527C19.486 18.5833 19.186 18.8833 19.186 19.25C19.186 19.6167 19.486 19.9167 19.8527 19.9167ZM19.186 15.9167C19.186 16.2833 19.486 16.5833 19.8527 16.5833H30.5194C30.886 16.5833 31.186 16.2833 31.186 15.9167C31.186 15.55 30.886 15.25 30.5194 15.25H19.8527C19.486 15.25 19.186 15.55 19.186 15.9167Z"
        fill="#C6C6C6"
      />
    </g>
    {/* Transparent rect to capture events */}
    <rect
      width="51"
      height="39"
      fill="transparent"
      onMouseDown={onMouseDown}
    />
  </g>
);

 
버튼 좌표 배치 원리는 간단한데, transform 애니메이션을 활용해서 지금 "y 값 좌표" 에 버튼을 위치시킵니다. 
 

최종 프로그래스바 UI

 
 

3. 포인터 이벤트로 드래그 기능 추가하기

이제 가장 재미있는 부분입니다. 손잡이를 드래그해서 위아래로 움직이면 값이 변하도록 만들어볼 거예요.
 
웹에서 드래그 동작을 구현하려면 마우스나 터치 이벤트를 다뤄야 하는데, React에선 Pointer Events를 쓰면 아주 편리합니다. Pointer Event는 마우스, 터치, 펜 등 다양한 입력을 하나의 통일된 방식으로 처리할 수 있는 이벤트예요 예전에는 PC와 모바일을 모두 지원하려면 onMouseDown과 onTouchStart 등을 각각 코딩해야 했지만, 이제는 onPointerDown 하나로 모두 처리할 수 있답니다.
 
우리는 이미 JSX에서 <circle> 손잡이에 onPointerDown을 설정해 두었죠? 사용자가 손잡이를 누르는 순간 어떤 손잡이를 움직이는지 상태를 업데이트하고 (dragging을 "min" 또는 "max"로 set), 이후 움직이는 동안(pointermove)과 놓는 순간(pointerup)을 포착해서 값을 갱신하면 돼요.
 

  // Handles the pointer down event when a thumb is pressed
  // Sets the active thumb ('min' or 'max') and captures the pointer
  const handlePointerDown = (thumb: 'min' | 'max') => (e: React.PointerEvent) => {
     ...
  };
  
  // Handles the pointer move event when the thumb is dragged
  // Updates the value of the active thumb based on the pointer's position
  
  const handlePointerMove = (e: React.PointerEvent) => {
	...
  };
  
  // Handles the pointer up event when the thumb is released
  // Releases the pointer capture and clears the active thumb
  
  const handlePointerUp = (e: React.PointerEvent) => {
   	...
  };

 
 

최종 결과물

 
최종 코드는 아래에 있습니다.
 
https://codesandbox.io/p/devbox/kll755
 

결론: SVG 활용 꿀팁 ✨

이번 예제로 SVG의 힘을 조금 느끼셨나요? 정리하자면, SVG는 화면 크기에 관계없이 선명하고 코드로 그리는 그림이라 자유롭게 수정하거나 상호작용할 수 있다는 게 장점이에요. 마무리로, 실무에서 SVG를 유용하게 활용하는 몇 가지 팁을 알려드릴게요:
 

  • 반응형 디자인: SVG는 벡터 방식이라 화면 크기에 따라 자유롭게 확대/축소해도 깨지지 않아요. 따라서 아이콘이나 일러스트를 SVG로 사용하면 레티나 디스플레이에서도 언제나 또렷하게 보입니다. CSS로 너비나 높이를 %로 주거나 viewBox를 적절히 설정하면 부모 컨테이너 크기에 맞춰 유연하게 대응할 수도 있어요.
  • 아이콘 최적화: Figma나 일러스트레이터에서 SVG를 내보낼 때 불필요한 데이터가 붙거나 좌표가 복잡하게 저장될 수 있어요. 이런 경우 SVG 압축 도구를 사용해보세요. 예를 들어 SVGO라는 오픈 소스 툴을 쓰면 SVG 파일 크기를 많이 줄일 수 있어요복잡한 경로는 단순화하고, 쓰이지 않는 요소는 제거해서 성능을 높이는 거죠. 최적화된 SVG는 파일 크기가 작아져 웹페이지 로딩도 빨라진답니다.

  • 인터랙티브 컴포넌트: SVG 요소들은 DOM 요소이기도 해서, JavaScript나 CSS로 동적으로 제어하기가 쉬워요. 이번에 만든 프로그레스 바처럼 드래그 이벤트를 처리하는 것은 물론이고, <circle>에 마우스오버하면 색을 바꾸거나, 클릭하면 애니메이션을 주는 것도 가능합니다. 예를 들어, SVG에 CSS 클래스를 적용해서 .active일 때 특정 부분의 색깔을 변경하거나, 간단한 트랜지션 효과를 줄 수도 있어요. 또한 D3.js나 GSAP 같은 라이브러리와 결합하면 복잡한 데이터 시각화나 모션 그래픽도 구현할 수 있어요. 상상한 대로 자유롭게 SVG를 가지고 놀아보세요!

SVG를 배우기 시작하면 웹 개발에서 디자인과 상호작용을 다루는 새로운 무기가 생긴 셈이에요. 이번 튜토리얼이 따뜻한 입문 가이드가 되었길 바랍니다. 앞으로도 SVG를 활용해 멋지고 유용한 컴포넌트들을 많이 만들어보세요! 😃
 
P.S) 엄청 복잡한 인터렉션은 가내수공업이 아니고 lottieFiles같은 라이브러리의 힘을 빌리는게 좋을듯..?

반응형
반응형

 

canvas에서 충돌 애니메이션을 구현하는 방법을 간단하게 알아봅시다. 

아래 글을 이해하는데는 벡터 관련 지식이 약간 필요합니다.

출처: https://www.amazon.com/HTML5-Canvas-Native-Interactivity-Animation/dp/1449334989

 

 

핵심 개념은 단 세 가지뿐입니다.

  1. “좌표” 2. “속도(움직임)” 3. “거울에 비치듯 방향을 뒤집기”

1. 좌표 — 종이에 그린 그래프랑 똑같다

 

용어 뜻 예시

 

x 왼쪽 ↔ 오른쪽 거리 x = 0 → 맨 왼쪽, x = 500 → 맨 오른쪽
y 위 ↔ 아래 거리 y = 0 → 맨 위, y = 500 → 맨 아래

 

화면을 가로 500 × 세로 500 칸짜리 눈금종이라 생각하면 됩니다.

빨간 공 하나를 “좌표 (x, y)” 두 숫자로 위치시킵니다.

 

2. 속도 = “한 프레임(0.016초쯤)마다 몇 칸 움직이느냐”

let vx = 3;   // x축으로 한 번에 +3칸
let vy = 2;   // y축으로 한 번에 +2칸
  • 속도 벡터 (vx, vy) : “오른쪽으로 3칸, 아래로 2칸”이라는 화살표
  • 움직이기 : x += vx; y += vy;
  • → 숫자를 더하기만 하면 새 위치가 나옵니다.

👉 ‘벡터’란 단어를 어려워하지 마세요.

그냥 “Δx(+3), Δy(+2)”라는 두 개의 숫자를 한 쌍으로 들고 다닌다는 뜻뿐입니다.

 

3. 충돌 감지 — “부딪쳤다”를 어떻게 알까?

 

3‑1. 벽(사각형)과 부딪힘

  • 왼쪽 벽 : 공의 왼쪽 끝 x - R 이 0보다 작아졌다면 → “벽을 뚫었다”는 뜻
  • 오른쪽 벽 : x + R 이 500보다 크면 뚫음
  • 위·아래도 같은 논리로 구현
if (x - R <= 0 && vx < 0) vx = -vx;   // 왼쪽 벽
  • vx < 0 여야만 뒤집는 이유: 이미 오른쪽(+)으로 가고 있는 상황이라면 굳이 뒤집을 필요 없죠.

3-2. 동글동글 장애물(원)과 부딪힘

  • 두 원 중심 사이의 거리
  • 공 반지름 + 장애물 반지름 보다 작으면 겹쳤다(충돌)
const dx = x - obs.x;
const dy = y - obs.y;
const dist = Math.hypot(dx, dy);   // √(dx² + dy²)
if (dist < R + obs.r) { … }

 

** Math.hypot(a,b) 는 “피타고라스” 공식(√(a²+b²))을 간단히 써 주는 함수


4 튕겨 나가기 — 거울에 비친 화살표처럼 “반사”하기

 

4‑1. 벽은 간단 — 한 축만 부호 반전

  • 왼·오른쪽 벽 → x 방향만 뒤집기 → vx = ‑vx
  • 위·아래 벽 → y 방향만 뒤집기 → vy = ‑vy

결과 : “↘” 로 가던 화살표가 벽에 부딪히면 “↙”로 바뀝니다.

4‑2. 둥근 벽(원) — 법선(normal)이라는 “정면 방향”을 이용

  1. 법선 : 공→장애물로 그린 선을 1칸 길이로 만든 화살표
  2. → (nx, ny) = (dx/dist, dy/dist)
  • 정의: 법선 벡터는 “충돌 지점에서 표면에 수직으로 뻗은 단위 벡터”를 말해요.
  • 충돌 예제에서는 공→장애물 중심을 잇는 방향으로 벡터를 구한 뒤, 이걸 길이 1로 정규화(normalize) 한 것이 법선입니다.

법선 계산하는 방법

 

  1. 반사 공식
    • v·n (v dot n) = vx·nx + vy·ny
      스칼라곱: 그냥 두 숫자씩 곱해서 더한 값입니다

3. 코드

const dot = vx*nx + vy*ny;
vx = vx - 2 * dot * nx;
vy = vy - 2 * dot * ny;

왜 이렇게 하면 “거울 반사”가 될까?

화살표(속도)를 “정면 성분”과 “옆으로 스치는 성분”으로 쪼갠 뒤,

정면 성분만 → 반대 방향으로 두 배 돌려서 빼 버린다고 생각하면 됩니다.

한글 말장난보다, 거울에 비친 모습을 빼서 더한다는 직관이 더 쉬워요!

 

5 끼임 방지 — 살짝 밀어내기

충돌 직후, 공이 이미 장애물 안쪽에 일부 들어가 있을 수 있습니다.

겹친 거리(overlap)만큼 한 발짝 밀어내기

 

✋ 정리

궁금증 아주 쉬운 답

“벡터?” 숫자 두 개를 한 묶음(↗ 화살표)으로 본다
“dot(스칼라곱)?” a₁·b₁ + a₂·b₂ — 곱하고 더하기뿐
“왜 반사가 되죠?” 화살표를 거울에 비춘 뒤 그 방향으로 바꿔 꽂는 것
“무슨 고급 수학 쓰나요?” 피타고라스(√)와 곱셈·뺄셈 끝!

결국 우리가 한 일

① 위치(x, y)를 더하기로 옮겼다 → ② 부딪혔는지 간단한 비교·√로 확인 →

③ 맞으면 화살표(vx, vy)를 뒤집거나 반사 공식으로 바꿨다.

 

 

코드 예시

 

충돌 시뮬레이션 예시 코드(동영상은 20FPS)

 

 

아래 링크에서 확인 가능합니다.

 

https://codesandbox.io/p/devbox/kc8674

 

 

 

reference 

 

HTML5 Canvas: Native Interactivity and Animation for the Web 에서 일부 내용 발췌

반응형

+ Recent posts