92. 스레드 (Thread) - 경량 프로세스 (LWP)

핵심 인사이트 (3줄 요약)

  1. 본질: 스레드 (Thread)는 프로세스 내에서 실행되는 가장 작은 CPU 할당 단위로, 코드/데이터/힙 영역을 동료 스레드와 공유하면서도 자신만의 스택(Stack)과 레지스터(PC 포함) 문맥을 독립적으로 갖는 경량화된 실행 흐름이다.
  2. 가치: 스레드 간 문맥 교환은 페이지 테이블(메모리 맵)을 교체할 필요가 없어 프로세스 간 교환에 비해 압도적으로 빠르며, 메모리 공유 덕분에 데이터 통신(IPC) 비용도 획기적으로 낮다.
  3. 융합: 유저 모드 스레드의 유연성과 커널 모드 스레드의 동시성을 결합하기 위해 LWP (Lightweight Process)라는 중간 다리 객체가 등장했으며, 이는 현대 다중 코어 시스템에서 비동기 I/O와 스레드 풀(Thread Pool) 아키텍처의 근간이 된다.

Ⅰ. 개요 및 필요성 (Context & Necessity)

  • 개념: 과거에는 "실행의 흐름"과 "자원 할당의 단위"가 프로세스(Process)라는 하나의 덩어리로 결합되어 있었다. 스레드 (Thread)는 이 덩어리에서 '실행의 흐름'만을 분리해 낸 개념이다. 한 프로세스 안에 여러 개의 스레드를 둠으로써, 동일한 메모리 자원을 둔 채 다중 작업을 동시에 처리할 수 있게 한 것이다. 이러한 가벼운 특성 때문에 스레드를 경량 프로세스 (LWP, Lightweight Process)라고도 부른다(리눅스 등 특정 OS 환경).
  • 필요성: 웹 서버가 수천 명의 클라이언트 접속을 처리해야 한다고 가정하자. 클라이언트마다 프로세스(fork())를 복제 생성하면, 엄청난 메모리 복사 비용과 무거운 문맥 교환(Context Switch) 오버헤드 때문에 시스템은 금방 한계에 다다른다. 프로세스를 쪼개어 무거운 자원은 하나만 두고, 가벼운 제어 흐름(스레드)만 여러 개 생성하여 멀티 코어의 이점을 극대화하기 위해 스레드 모델이 필연적으로 등장했다.
  • 💡 비유: 프로세스가 주방, 냉장고, 가스레인지를 모두 갖춘 '독립된 식당 건물'이라면, 스레드는 그 한 주방 안에서 각자 프라이팬(스택)을 들고 요리하는 여러 명의 '요리사'와 같습니다. 식당을 새로 지을 필요 없이 요리사만 늘리면 더 많은 주문을 빠르게 처리할 수 있습니다.
  • 등장 배경: 초기 OS에서는 유저 공간에서 자체적으로 흐름을 쪼개는 유저 수준 스레드(User-level Thread)가 쓰였으나, 하나의 스레드가 I/O 블로킹 시 전체 프로세스가 멈추는 치명적 단점이 있었다. 이를 해결하기 위해 OS 커널이 직접 스레드를 인식하고 스케줄링하는 커널 수준 스레드(Kernel-level Thread)가 등장했고, 둘의 장점을 합치기 위해 LWP라는 추상화 계층이 고안되었다.

하나의 묵직한 프로세스 안에 여러 개의 스레드가 존재할 때, 무엇을 공유하고 무엇을 독립적으로 가지는지를 명확히 보여주는 구조도이다.

  ┌───────────────────────────────────────────────────────────────────────────┐
  │              단일 스레드 프로세스 vs 멀티 스레드 프로세스 구조            │
  ├───────────────────────────────────────────────────────────────────────────┤
  │                                                                           │
  │  [ Single-threaded Process ]  [ Multi-threaded Process ]                  │
  │  ┌─────────────────────────┐  ┌───────────────────────────┐               │
  │  │  Code │  Data │  Files  │  │   Code │  Data │  Files   │               │
  │  ├─────────────────────────┤  ├───────────────────────────┤               │
  │  │                         │  │          (공유됨)           │             │
  │  │      Heap (동적 메모리)    │  │        Heap (동적 메모리)     │        │
  │  ├─────────────────────────┤  ├──────┬─────────┬──────────┤               │
  │  │                         │  │Stack │  Stack  │  Stack   │               │
  │  │       Stack             │  │ (T1) │   (T2)  │   (T3)   │               │
  │  │                         │  ├──────┼─────────┼──────────┤               │
  │  │ Registers (PC, SP)      │  │ Regs │  Regs   │  Regs    │               │
  │  └─────────────────────────┘  └──────┴─────────┴──────────┘               │
  │                                                                           │
  │ 핵심 차이: 전역 변수(Data)와 Heap은 공유, 지역 변수(Stack)와 PC는 독립    │
  └───────────────────────────────────────────────────────────────────────────┘

[다이어그램 해설] 다이어그램 우측의 멀티 스레드 구조가 성능 혁신의 핵심이다. 텍스트(Code), 전역 변수(Data), 동적 할당 메모리(Heap), 열린 파일 디스크립터(Files)는 프로세스라는 컨테이너 안에서 모든 스레드가 완전히 공유한다. 따라서 스레드 간에 데이터를 주고받을 때 복잡한 IPC(파이프, 소켓 등)가 필요 없이 그냥 포인터 주소만 읽으면 된다. 반면, 각 스레드가 자신만의 독립적인 실행 흐름을 보장받으려면 각자가 호출한 함수의 순서(지역 변수들)와 현재 실행 위치를 기억해야 한다. 그래서 스택(Stack)과 레지스터(PC, SP 등) 문맥은 스레드 개수만큼 분리되어 독립적으로 할당된다. 이 절묘한 '공유와 독립의 분리'가 스레드를 빠르고 강력하게 만든다.

  • 📢 섹션 요약 비유: 회사(프로세스)의 공용 회의실, 복사기, 공용 폴더(Heap, Data)는 다 같이 쓰지만, 각 직원은 자기만의 수첩(Register)과 개인 서랍장(Stack)을 가지고 독립적으로 업무를 보는 것과 같습니다.

Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)

구성 요소 (스레드 관리의 삼원 구조)

요소명실행 공간역할 및 특징생성/문맥교환 비용비유
유저 스레드 (User Thread)User Space라이브러리(ex. Pthreads)가 관리, 커널은 모름매우 낮음 (OS 개입 없음)직원들끼리의 자체 당번표
커널 스레드 (Kernel Thread)Kernel SpaceOS 커널이 스케줄링의 단위로 직접 인식하고 관리중간 (시스템 콜 발생)정식 근로계약 체결 직원
LWP (Lightweight Process)Kernel Space유저 스레드와 커널 스레드를 이어주는 중간 다리중간 (커널 구조체 할당)외주 파견직과 본사 관리자 매핑
TCB (Thread Control Block)Kernel Space스레드별 레지스터, PC, 스택 포인터 정보 보존낮음 (PCB보다 작음)직원 개인의 업무 일지

유저-커널 매핑 모델과 LWP (Lightweight Process)

스레드를 구현하는 방식은 유저 스레드를 커널 스레드에 어떻게 연결하느냐에 따라 크게 3가지(Many-to-One, One-to-One, Many-to-Many)로 나뉜다. 여기서 LWP의 역할이 중요해진다.

  ┌────────────────────────────────────────────────────────────────────────────┐
  │       다대다 (Many-to-Many) 매핑 모델과 LWP (경량 프로세스) 구조           │
  ├────────────────────────────────────────────────────────────────────────────┤
  │                                                                            │
  │ [ User Space ]                                                             │
  │     (유저 스레드)    U1         U2          U3          U4                 │
  │                   │          │           │           │                     │
  │  ─ ─ ─ ─ ─ ─ ─ ─ ─│─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─                 │
  │ [ Kernel Space ]  ▼          ▼           ▼           ▼                     │
  │                   ┌──────────┐           ┌───────────┐                     │
  │     (경량 프로세스)  │   LWP 1  │           │   LWP 2   │                  │
  │                   └────┬─────┘           └─────┬─────┘                     │
  │                        │                       │                           │
  │                        ▼                       ▼                           │
  │     (커널 스레드)      [ K1 ]                  [ K2 ]                      │
  │                        │                       │                           │
  │                     [ CPU Core 0 ]          [ CPU Core 1 ]                 │
  │                                                                            │
  │ 핵심: LWP는 커널이 스케줄링하는 가상 CPU 역할을 하며 블로킹을 격리함       │
  └────────────────────────────────────────────────────────────────────────────┘

[다이어그램 해설] 초기 다대일(Many-to-One) 모델에서는 유저 스레드 100개가 있어도 커널 스레드는 1개뿐이어서, 한 유저 스레드가 디스크 읽기(블로킹 I/O)를 시도하면 나머지 99개도 모두 멈춰버렸다(커널이 볼 땐 1개의 프로세스가 블로킹된 것이므로). 반면 리눅스 등에서 채택하는 일대일(One-to-One) 또는 다대다(Many-to-Many) 모델에서는 LWP라는 커널 자료구조(리눅스에서는 clone()으로 생성된 가벼운 task_struct)를 유저 스레드와 매핑한다. 그림에서 U1이 블로킹 시스템 콜을 호출해 LWP1이 Wait Queue로 들어가더라도, U3는 LWP2를 통해 CPU Core 1에서 멀쩡하게 계속 실행된다. LWP는 곧 커널이 인식하는 최소 스케줄링 단위이자 자원 할당 단위로 작용한다.

문맥 교환의 효율성 원리

프로세스 A에서 프로세스 B로 넘어갈 때는 메모리 공간표(Page Table)를 통째로 교체해야 하므로 CPU 내부의 TLB (Translation Lookaside Buffer)가 전부 비워지는 엄청난 캐시 미스 오버헤드가 발생한다. 그러나 프로세스 A 내부의 스레드 1에서 스레드 2로 교환할 때는, 둘이 같은 메모리 공간을 공유하므로 페이지 테이블 교체가 생략된다. 오직 TCB의 레지스터 값만 복원하면 끝이므로 비용이 기하급수적으로 낮다.

  • 📢 섹션 요약 비유: 부서 간에 소통하려면 결재 서류를 올리고 절차를 밟아야(프로세스 IPC) 하지만, 같은 부서 내의 파티션 너머 동료와는 그냥 고개만 돌려 말하면(스레드 메모리 공유) 되는 구조입니다.

Ⅲ. 융합 비교 및 다각도 분석

심층 기술 비교: 멀티 프로세스 vs 멀티 스레드 워크로드 아키텍처

비교 항목멀티 프로세스 (Multi-Process)멀티 스레드 (Multi-Thread)실무 판단 포인트
메모리 구조철저히 독립적, 프로세스당 메모리 증가코드/데이터/힙 공유메모리 용량 및 데이터 공유 빈도
안정성 (장애 격리)하나가 죽어도(Segfault) 남은 프로세스 생존한 스레드 버그로 전체 프로세스 패닉서비스 신뢰성 vs 성능
통신 비용 (IPC)높음 (파이프, 소켓, 공유 메모리 매핑 필요)매우 낮음 (전역 변수 포인터 즉시 접근)데이터 교환 빈도
생성/소멸 오버헤드무거움 (fork() 호출 및 자원 복사 비용)가벼움 (clone(), 스레드 풀 재사용)동적 스케일링 민첩성
대표적인 채택 사례Nginx 워커, Chrome 브라우저 탭 격리Apache(일부), MySQL, 자바(JVM) 앱도메인의 보안 및 장애 요건

스레드 생성과 프로세스 생성의 비용 차이를 유발하는 커널 레벨의 호출 구조를 비교 도식으로 살펴본다.

  ┌───────────────────────────────────────────────────────────────────────────────────┐
  │        프로세스 생성(fork) vs 스레드 생성(clone) 내부 동작 오버헤드 차이          │
  ├───────────────────────────────────────────────────────────────────────────────────┤
  │                                                                                   │
  │    [ fork() - 프로세스 복제 ]        [ clone() - LWP 스레드 생성 ]                │
  │                                                                                   │
  │  1. 새 PCB 구조체 전체 할당         1. 새 TCB 구조체(LWP) 할당                    │
  │  2. 페이지 테이블 완전 복사          2. 페이지 테이블 포인터만 공유 (Copy ❌)     │
  │     (Copy-On-Write 지만 비용 발생) 3. 파일 디스크립터 테이블 공유                 │
  │  3. 새로운 주소 공간 초기화          4. 별도의 User Stack 공간만 매핑             │
  │  4. 파일 디스크립터 복사                                                          │
  │                                                                                   │
  │  생성 시간: ─────────▶ (느림)      생성 시간: ──▶ (매우 빠름)                     │
  │                                                                                   │
  │  결론: 스레드는 메모리 뼈대를 새로 만들지 않고 "얹혀 가기" 때문에 생성 비용 극소화│
  └───────────────────────────────────────────────────────────────────────────────────┘

[다이어그램 해설] 리눅스 커널에서는 프로세스와 스레드를 생성하는 시스템 콜이 사실상 clone()이라는 내부 함수로 통합되어 있다. 차이는 플래그(Flag) 옵션에 있다. fork() 시에는 플래그가 엄격하게 세팅되어 독립된 메모리 공간과 새로운 파일 테이블을 뼈대부터 다시 짜야 한다(COW 기법이 돕긴 하지만 여전히 무겁다). 반면 Pthreads 라이브러리가 스레드를 만들 때 호출하는 clone(CLONE_VM | CLONE_FILES) 플래그는 "기존 가상 메모리 공간과 파일 테이블을 그냥 포인터로 덮어쓰고(공유) 스택만 따로 줘"라는 뜻이다. 이 지시 덕분에 커널은 최소한의 메타데이터(TCB)만 생성하여 즉각적인 흐름 제어를 만들어낸다.

과목 융합 관점

  • 소프트웨어 공학 (SE): 멀티 스레드 모델은 메모리를 공유하므로 자원 접근 시 경합 조건(Race Condition)이 필연적으로 발생한다. 이를 막기 위한 뮤텍스(Mutex), 세마포어(Semaphore) 설계가 복잡성을 가중시키며 교착 상태(Deadlock)의 위험을 높인다.

  • 프로그래밍 언어 (PL): Java의 스레드는 과거 OS 커널 스레드와 1:1 매핑되어 무거웠으나, 최근 Project Loom을 통해 유저 스레드 성격의 가상 스레드(Virtual Thread)를 도입하여 비동기 프로그래밍의 복잡성 없이 고성능 I/O 처리를 지원하도록 진화했다.

  • 📢 섹션 요약 비유: 멀티 프로세스는 각자 독립된 사무실을 얻어 완벽한 프라이버시(안정성)를 가지지만 월세(오버헤드)가 비싸고, 멀티 스레드는 원룸에 여러 명이 셰어하우스를 해서 월세는 싸지만 한 명이 불을 내면 다 같이 죽는(Segfault) 위험이 있습니다.


Ⅳ. 실무 적용 및 기술사적 판단

실무 시나리오 및 판단

  1. 시나리오 — 스레드 풀 (Thread Pool) 소진으로 인한 응답 거부: 톰캣(Tomcat) 기반 웹 서버에 순간적으로 많은 트래픽이 몰려 모든 스레드가 외부 API 응답을 기다리며 대기 상태(Blocked)에 빠지고, 신규 접속이 Time Out 되는 상황.
    • 판단: 스레드를 매번 새로 만드는 비용을 줄이려 스레드 풀을 썼지만, I/O 대기로 인해 풀 안의 스레드가 고갈(Exhaustion)된 전형적인 문제다. 실무에서는 외부 호출 시 비동기 논블로킹(Non-blocking I/O) 모델(예: WebFlux, Netty)로 전환하거나, 서킷 브레이커(Circuit Breaker)를 도입해 무한 대기를 잘라내고 풀을 신속히 회수해야 한다.
  2. 시나리오 — 무분별한 스레드 생성으로 인한 문맥 교환 폭증: 초보 개발자가 "병렬 처리가 무조건 빠르다"고 착각하여 8코어 서버에서 1,000개의 스레드를 띄워 연산 작업을 수행. 오히려 단일 스레드보다 처리 속도가 10배 느려짐.
    • 판단: CPU 바운드(계산 위주) 워크로드에서는 스레드 개수를 논리 코어 개수(ex. 8개)와 일치시키는 것이 최적이다. 1,000개의 스레드가 CPU 8개를 두고 쟁탈전을 벌이면, 실제 연산은 못 하고 스레드 간 문맥 교환(Context Switch)에만 시스템 CPU 자원의 90% 이상을 낭비하는 스래싱(Thrashing)이 발생한다.
  3. 시나리오 — 스레드 간 동기화 병목 (Lock Contention): 여러 스레드가 동시에 공용 데이터(전역 변수)를 읽고 쓸 때 데이터 오염을 막기 위해 굵은 단위의 락(Coarse-grained Lock)을 걸어, 결국 모든 스레드가 직렬(순차적)로 동작하게 되는 병목.
    • 판단: 멀티 스레드의 장점이 완전히 소멸된 안티패턴이다. 실무에서는 락의 단위를 쪼개는 세밀한 락(Fine-grained Lock)으로 변경하거나, 아예 락을 쓰지 않는 락 프리(Lock-free) 자료구조, 혹은 ConcurrentHashMap 등을 적용해 경합을 분산시켜야 한다.

안전한 멀티 스레딩 아키텍처 설계를 위한 실무적 의사결정 흐름을 요약한다.

  ┌────────────────────────────────────────────────────────────────────────────────┐
  │              다중 작업 처리를 위한 아키텍처 의사결정 트리                      │
  ├────────────────────────────────────────────────────────────────────────────────┤
  │                                                                                │
  │   [새로운 요청/작업 처리 아키텍처 설계]                                        │
  │                │                                                               │
  │                ▼                                                               │
  │       작업이 치명적 오류를 발생시킬 위험이 큰가?                               │
  │          ├─ 예 ─────▶ [멀티 프로세스 모델 채택 (Chrome 탭 방식)]               │
  │          │                     │                                               │
  │          │                     └─▶ 장애 격리 확보, 메모리 희생                 │
  │          │                                                                     │
  │          └─ 아니오                                                             │
  │                │                                                               │
  │                ▼                                                               │
  │       주로 디스크/네트워크를 기다리는 I/O 바운드 작업인가?                     │
  │          ├─ 예 ─────▶ [이벤트 루프 / 비동기 + 스레드 풀 결합]                  │
  │          │                     │                                               │
  │          │                     └─▶ Node.js, Netty 방식                         │
  │          │                                                                     │
  │          └─ 아니오 (CPU 연산 위주 바운드)                                      │
  │                │                                                               │
  │                ▼                                                               │
  │   [코어 수에 맞춘 소수 스레드 생성 (Thread=Core 수) + Lock-free 병렬화]        │
  │                                                                                │
  │  결론: 맹목적인 멀티 스레드는 독이며 워크로드 특성에 맞춘 모델 선택이 필수     │
  └────────────────────────────────────────────────────────────────────────────────┘

[다이어그램 해설] 이 의사결정 트리는 아키텍트가 성능과 안정성 사이의 트레이드오프를 어떻게 저울질해야 하는지 보여준다. 외부 사용자의 알 수 없는 코드를 실행하는 브라우저 탭이나 웹 서버 CGI 등은 아무리 스레드가 가벼워도 하나의 보안 취약점이나 오류가 전체 시스템을 무너뜨리지 못하게 무거운 멀티 프로세스 모델로 격리하는 것이 원칙이다. 하지만 트래픽의 수신이나 단순 I/O 릴레이는 이벤트 기반 비동기 스레드 모델을 쓰는 것이 압도적으로 효율적이다. 연산 최적화의 경우 락을 잡느라 허비하는 시간을 없애기 위해 코어 수와 스레드 수를 1:1로 맞추고 코드를 함수형이나 불변 객체 중심으로 리팩토링하는 결단이 요구된다.

도입 체크리스트 및 안티패턴

  • 기술적 체크리스트: top -H -p <PID> 명령어로 프로세스 내부의 스레드(LWP)별 CPU 및 메모리 점유율을 실시간으로 추적하고 있는가? 스레드 간 공유 자원에 데드락이 없는지 Thread Dump 분석 훈련이 되어 있는가?

  • 안티패턴: 부모 프로세스에서 fork()를 호출하기 직전에 멀티 스레드를 과도하게 띄우는 패턴. POSIX 환경에서 멀티 스레드 프로세스가 fork()를 호출하면, 락을 쥔 상태의 스레드 문맥은 꼬인 채로 복제되어 자식 프로세스에서 락이 영원히 풀리지 않는 치명적 데드락(Deadlock) 늪에 빠진다.

  • 📢 섹션 요약 비유: 명의(스레드)를 100명 모셔와도, 메스(CPU)가 한 개뿐이거나 환자 배를 가를 권한(Lock)을 순서대로 기다려야 한다면 수술 속도는 전혀 빨라지지 않는 것과 같습니다.


Ⅴ. 기대효과 및 결론

정량/정성 기대효과

구분단일 프로세스/스레드 구조멀티 스레드(LWP 기반) + 풀 구조개선 효과
정량CPU 코어 1개만 활용멀티 코어(SMP) 100% 병렬 활용연산 및 처리량(TPS) 선형적 증가
정량요청당 1 프로세스 복제생성된 스레드 풀 재사용메모리 소비 90% 이상 절감
정성I/O 대기 시 화면 프리징백그라운드 워커 기반 비동기화GUI 응답성(Responsiveness) 극대화

미래 전망 및 표준

  • 경량 유저 스레드의 부활 (M:N 스케줄링): 과거 커널 스레드의 성능 한계를 극복하기 위해 Go 언어의 Goroutine, Java의 Virtual Thread, Rust의 코루틴(Coroutine) 등 유저 레벨에서 수백만 개의 스레드를 극도로 가볍게 스케줄링하고 런타임이 이를 소수의 커널 스레드(LWP)에 얹어주는 모델이 현대 동시성 프로그래밍의 대세로 완전히 굳어졌다.

  • NUMA 아키텍처 친화성: 서버의 코어가 100단위로 넘어가면서 스레드가 어느 코어의 어느 메모리 노드(NUMA)에 속해 있는지(Affinity)가 메모리 지연 시간에 결정적 영향을 미치게 되었다. 스레드를 이리저리 이주(Migration)시키지 않고 캐시 로컬리티를 유지하는 스마트한 커널 스케줄러가 표준이 되고 있다.

  • 📢 섹션 요약 비유: 자동차의 단일 대형 엔진(프로세스)을 쪼개어, 각 바퀴마다 작고 효율적인 전기 모터(스레드)를 달아 독립적으로 통제함으로써 더 빠르고 세밀한 사륜구동(멀티코어 병렬처리)의 시대로 접어들었습니다.


📌 관련 개념 맵 (Knowledge Graph)

개념 명칭관계 및 시너지 설명
문맥 교환 (Context Switch)스레드 간 교환 시에는 PCB 레벨의 메모리 매핑 교체 없이 TCB 내의 레지스터 세트만 교체하므로 비용이 비약적으로 절감된다.
스레드 풀 (Thread Pool)스레드 생성과 소멸에 드는 반복적 커널 오버헤드를 막기 위해, 미리 일정량의 스레드를 생성해 두고 큐에 쌓인 작업을 분배하는 디자인 패턴이다.
임계 구역 (Critical Section)스레드 간 데이터 영역(Heap, Data)을 공유함에 따라 동시 수정 시 데이터 정합성이 깨질 수 있는 코드 영역으로, 뮤텍스 등의 동기화 보호가 필수적이다.
SMP (Symmetric Multiprocessing)다중 CPU 코어 환경에서 각 코어가 동시에 다른 스레드를 독립적으로 디스패치하여 진정한 병렬성(Parallelism)을 달성하는 하드웨어 기반이다.
가상 스레드 (Virtual Thread)OS 커널 스레드의 무거움을 피하기 위해 언어 런타임(JVM 등) 수준에서 생성과 스케줄링을 통제하는 경량 실행 흐름 기술이다.

👶 어린이를 위한 3줄 비유 설명

  1. 스레드는 그림을 그릴 때 커다란 도화지(프로세스) 안에서 움직이는 여러 개의 크레용 손놀림이에요. 혼자 칠하는 것보다 여러 명이 동시에 칠하면 그림이 훨씬 빨리 완성되죠.
  2. 여러 스레드는 도화지나 물감(메모리)은 다 같이 나눠 쓰지만, 자기가 지금 어디를 칠하고 있는지(레지스터)는 각자 따로 기억하고 있어요.
  3. 하지만 서로 같은 곳을 칠하겠다고 싸우면 그림이 망가질 수 있기 때문에, 차례를 정해주는 규칙(동기화)이 아주 중요하답니다!