개발 지식 책

데이터 중심 애플리케이션 설계, 분산 시스템 아키텍처 구축 핵심 가이드

강코의 코딩 일기 2026. 5. 28. 12:25
반응형

복잡한 분산 시스템 환경에서 견고하고 확장 가능한 애플리케이션을 설계하는 데 필요한 원칙과 기술을 다룬 '데이터 중심 애플리케이션 설계' 도서의 핵심 내용을 리뷰합니다.

데이터가 폭증하고 사용자 기대치는 계속해서 높아지는 개발 환경에서, 여러분의 애플리케이션은 과연 견고하고 안정적으로 작동하고 있습니까? 예측 불가능한 트래픽 증가나 시스템 장애 발생 시에도 서비스 연속성을 보장할 수 있으신가요? 많은 개발자가 단일 서버 환경에서는 익숙하지만, 분산 시스템이라는 복잡한 환경에 직면하면 데이터 일관성, 확장성, 내결함성 등 수많은 문제에 부딪히곤 합니다.

이러한 문제의식은 비단 특정 개발팀만의 고민이 아닙니다. 현대의 거의 모든 서비스는 데이터 중심적이며, 자연스럽게 분산 환경에서 운영됩니다. 하지만 이를 위한 아키텍처 설계 원칙을 명확히 이해하지 못하면, 눈덩이처럼 불어나는 기술 부채와 예측 불가능한 장애에 시달리게 됩니다. 이러한 상황에서 길잡이가 되어줄 단 한 권의 책을 꼽으라면, 주저 없이 "데이터 중심 애플리케이션 설계: 분산 시스템 시대의 견고한 아키텍처 구축 원칙"을 추천합니다.

이 책은 단순한 기술 스택 나열이나 특정 프레임워크 사용법을 설명하는 것을 넘어, 데이터 시스템이 작동하는 근본적인 원리설계 사상을 깊이 있게 다룹니다. 마치 복잡한 도시의 지도를 펼쳐 놓고 각 도로와 건물의 역할을 이해하듯, 데이터 시스템의 각 구성 요소와 그 상호작용을 명쾌하게 설명해 줍니다. 이 글에서는 이 책이 제시하는 핵심적인 통찰과 함께, 개발 현장에서 겪는 고질적인 문제들을 어떻게 해결할 수 있는지 실용적인 관점에서 살펴보겠습니다.


📑 목차

데이터 중심 애플리케이션 설계: 분산 시스템 시대의 견고한 아키텍처 구축 원칙 도서 리뷰 - pipe system, tube, construction site, light, cable, industry, system, connection, flow through, embarrassed, plug-in system, round, circles, channel, industry, industry, industry, industry, industry, circles, circles

Image by Kranich17 on Pixabay

왜 지금 데이터 중심 애플리케이션 설계가 필수적인가?

애플리케이션 개발은 이제 단순히 기능을 구현하는 것을 넘어섭니다. 사용자 수의 폭발적인 증가, 데이터 양의 기하급수적 성장, 그리고 24시간 365일 무중단 서비스를 요구하는 기대치는 데이터 시스템이 감당해야 할 부담을 극대화했습니다. 이러한 환경에서 우리는 다음과 같은 문제들에 직면합니다.

  • 데이터 정합성 문제: 여러 서버에 분산된 데이터가 서로 다른 값을 가질 때, 사용자에게 혼란을 주고 비즈니스 로직에 치명적인 오류를 발생시킵니다.
  • 확장성 한계: 단일 서버의 자원은 유한합니다. 트래픽이 증가할 때 시스템이 유연하게 확장되지 않으면 서비스 중단으로 이어질 수 있습니다.
  • 복잡성 증가: MSA(Microservice Architecture)와 같은 분산 아키텍처는 서비스 간의 통신, 데이터 동기화 등 새로운 복잡성을 야기합니다.
  • 장애 내구성 부족: 한 부분의 고장이 전체 시스템 마비로 이어지는 단일 장애점(Single Point of Failure) 문제는 치명적입니다.

이 책은 이러한 문제들이 왜 발생하는지, 그리고 어떤 원칙과 기술적 접근 방식을 통해 해결할 수 있는지를 체계적으로 설명합니다. 특정 기술 스택에 얽매이지 않고, 데이터 시스템이 갖춰야 할 신뢰성(Reliability), 확장성(Scalability), 유지보수성(Maintainability)이라는 세 가지 핵심 원칙을 중심으로 논의를 전개합니다. 이는 마치 건축가가 건물의 용도에 따라 가장 적합한 구조와 재료를 선택하듯, 개발자에게 올바른 아키텍처를 설계할 수 있는 사고의 틀을 제공합니다.

신뢰성, 확장성, 유지보수성: 견고한 시스템의 3요소

이 세 가지 원칙은 모든 데이터 시스템 설계의 기반이 됩니다. 책에서는 각 원칙의 중요성을 강조하며, 실제 사례를 통해 어떻게 이 원칙들을 시스템에 녹여낼 수 있는지 보여줍니다.

  • 신뢰성: 하드웨어 고장, 소프트웨어 버그, 네트워크 문제 등 다양한 상황에서도 시스템이 올바르게 작동하고 예상대로 동작하는 능력입니다. 예컨대, 데이터베이스 이중화, 자동 복구 시스템 구축 등이 여기에 해당합니다.
  • 확장성: 시스템의 성능 저하 없이 트래픽이나 데이터 양 증가에 대응할 수 있는 능력입니다. 수직 확장(Scale Up)과 수평 확장(Scale Out)의 장단점을 명확히 이해하고, 서비스의 특성에 맞는 확장 전략을 수립하는 것이 중요합니다.
  • 유지보수성: 시간이 지나도 시스템을 쉽게 이해하고, 수정하며, 개선할 수 있는 능력입니다. 좋은 코드와 문서화는 물론, 모듈화된 아키텍처와 명확한 책임 분리가 중요합니다.

이 세 가지를 균형 있게 고려하는 것이 지속 가능한 애플리케이션을 만드는 핵심입니다. 책은 이 원칙들을 추상적인 개념으로만 다루지 않고, 구체적인 기술적 선택과 연결하여 설명하기 때문에 즉시 현업에 적용 가능한 통찰을 제공합니다.


다양한 데이터 모델저장소 탐색

애플리케이션의 데이터 모델은 단순히 데이터를 저장하는 방법을 넘어, 개발자가 세상을 바라보고 문제를 해결하는 방식에 큰 영향을 미칩니다. 이 책은 관계형 데이터베이스(RDBMS)부터 NoSQL 데이터베이스에 이르기까지, 다양한 데이터 모델과 그에 따른 저장소의 특징을 심도 있게 분석합니다. 마치 망치 하나로 모든 문제를 해결하려 하는 대신, 각 문제에 가장 적합한 도구를 선택하는 지혜를 가르쳐 줍니다.

관계형 데이터 모델 vs. NoSQL 데이터 모델

오랫동안 개발의 표준이었던 관계형 데이터베이스는 정형화된 데이터와 복잡한 트랜잭션 처리에 강점을 가집니다. 하지만 유연성이 떨어지고 수평 확장이 어렵다는 단점이 있습니다. 반면, NoSQL 데이터베이스는 다양한 형태로 발전하여 각각의 특화된 사용 사례를 가집니다.

구분 관계형 데이터 모델 (RDBMS) NoSQL 데이터 모델
데이터 구조 정형화된 테이블, 스키마 존재 스키마리스, 유연한 구조 (문서, 키-값, 그래프 등)
확장성 수직 확장(Scale Up)에 유리, 수평 확장(Scale Out) 어려움 수평 확장(Scale Out)에 유리
트랜잭션 ACID 속성 보장 (강력한 일관성) BASE 속성 지향 (최종 일관성)
주요 사용 사례 금융 시스템, ERP, 복잡한 비즈니스 로직 대규모 데이터 처리, 실시간 웹 서비스, 소셜 그래프

책은 각 데이터 모델이 어떤 문제에 더 적합한지, 그리고 각 모델이 지닌 근본적인 한계는 무엇인지를 심도 있게 분석합니다. 예를 들어, 소셜 네트워크 서비스에서 친구 관계와 같은 복잡한 연결을 표현할 때는 관계형 데이터베이스보다 그래프 데이터베이스가 훨씬 효율적일 수 있습니다. 또한, 웹 페이지 방문 기록이나 IoT 센서 데이터처럼 대량의 비정형 데이터를 저장할 때는 문서 데이터베이스키-값 저장소가 유리합니다.

이러한 이해는 개발자가 단순히 유행을 좇아 특정 데이터베이스를 선택하는 것이 아니라, 자신의 애플리케이션 특성과 요구사항에 맞춰 최적의 데이터 저장 전략을 수립하는 데 결정적인 도움을 줍니다. 단순히 "어떤 DB를 써야 할까?"라는 질문 대신, "우리 서비스의 데이터는 어떤 형태이며, 어떤 접근 패턴을 가질까?"라는 본질적인 질문을 던지도록 유도합니다.


분산 시스템의 도전과 해결책: 일관성, 합의, 트랜잭션

분산 시스템은 현대 애플리케이션의 필수 요소가 되었지만, 동시에 수많은 복잡성을 내포합니다. 여러 대의 서버가 네트워크를 통해 통신하는 환경에서는 단일 서버 환경에서는 존재하지 않던 고유한 문제들이 발생합니다. 이 책은 특히 분산 시스템에서 발생하는 핵심적인 도전 과제일관성, 합의, 트랜잭션 문제를 명쾌하게 해부하고, 이를 해결하기 위한 다양한 접근 방식을 제시합니다.

분산 시스템의 근본적인 문제: CAP 정리와 일관성 모델

CAP 정리(Consistency, Availability, Partition tolerance)는 분산 시스템 설계의 출발점이 되는 중요한 이론입니다. 이 정리는 분산 시스템이 일관성(Consistency), 가용성(Availability), 분할 내성(Partition tolerance) 중 동시에 두 가지만을 만족할 수 있음을 말합니다. 즉, 네트워크 분할(Partition)이 발생하는 상황에서, 시스템은 강력한 일관성을 포기하고 가용성을 선택하거나, 가용성을 포기하고 강력한 일관성을 선택해야 합니다.

대부분의 현대 분산 시스템은 네트워크 분할에 대비해야 하므로, 사실상 일관성가용성 사이의 균형점을 찾아야 합니다. 이 책은 이러한 딜레마 속에서 다양한 일관성 모델(강력한 일관성, 최종 일관성 등)과 그에 따른 장단점을 설명합니다. 예를 들어, 금융 거래와 같이 데이터의 정확성이 최우선인 경우에는 강력한 일관성이 필요하지만, 소셜 미디어 피드처럼 약간의 지연이 허용되는 경우에는 최종 일관성 모델을 채택하여 가용성과 성능을 높일 수 있습니다.


// 예시: 분산 환경에서 데이터 복제 후 최종 일관성을 달성하는 개념
class DistributedDataStore {
    Map<String, String> data = new ConcurrentHashMap<>();

    // 데이터 쓰기 요청
    public void write(String key, String value) {
        data.put(key, value);
        // 다른 노드들에게 비동기적으로 복제 요청
        replicateAsync(key, value); 
    }

    // 데이터 읽기 요청
    public String read(String key) {
        // 가장 최근에 업데이트된 값 또는 로컬 복제본 반환
        return data.get(key); 
    }

    private void replicateAsync(String key, String value) {
        // 다른 노드들에게 복제 메시지를 보내는 로직 (네트워크 지연 발생 가능)
        System.out.println("데이터 복제 요청: " + key + " -> " + value);
    }
}

위 코드 예시는 최종 일관성을 지향하는 시스템에서 데이터 쓰기가 발생했을 때, 즉시 로컬에 반영하고 다른 노드에는 비동기적으로 복제 요청을 보내는 개념을 보여줍니다. 이 경우, 읽기 요청 시 아직 복제가 완료되지 않은 노드에서는 이전 값을 반환할 수 있지만, 결국에는 모든 노드가 동일한 값으로 수렴하게 됩니다.

분산 트랜잭션과 합의 프로토콜

단일 데이터베이스에서는 쉽게 처리되던 트랜잭션(여러 작업을 하나로 묶어 원자적으로 처리하는 것)이 분산 환경에서는 매우 복잡해집니다. 이 책은 2단계 커밋(2PC)과 같은 분산 트랜잭션 프로토콜의 작동 원리와 그 한계점을 깊이 있게 다룹니다. 또한, Paxos, Raft와 같은 분산 합의 프로토콜이 어떻게 여러 노드 간에 데이터의 일관된 상태를 유지하고 리더를 선출하는지 설명합니다.

이러한 프로토콜들은 복잡하고 구현하기 어렵지만, 분산 시스템의 내결함성(Fault Tolerance)일관성을 보장하는 핵심 메커니즘입니다. 책은 단순히 이론을 나열하는 것을 넘어, 실제 시스템(예: 주키퍼, etcd)에서 이러한 합의 프로토콜이 어떻게 활용되는지 구체적인 예시를 들어 설명하여 독자의 이해를 돕습니다. 이를 통해 개발자는 "왜 이런 복잡한 메커니즘이 필요한가?"라는 질문에 대한 명확한 답을 얻을 수 있습니다.


데이터 중심 애플리케이션 설계: 분산 시스템 시대의 견고한 아키텍처 구축 원칙 도서 리뷰 - solar system, sun, mercury, venus, earth, mars, jupiter, saturn, nature, neptune, uranus, planets, planetary system, celestial bodies, science, space, outer space, galaxy, astronomy

Image by 51581 on Pixabay

데이터 처리의 미래: 배치, 스트림, 그리고 람다 아키텍처

데이터는 생성되는 순간부터 다양한 방식으로 처리되고 분석됩니다. 이 책은 배치 처리(Batch Processing)스트림 처리(Stream Processing)라는 두 가지 주요 데이터 처리 패러다임을 비교하고, 이들을 조합하여 람다 아키텍처(Lambda Architecture)카파 아키텍처(Kappa Architecture)를 구축하는 원칙을 제시합니다.

배치 처리와 스트림 처리의 특징

배치 처리는 일정 시간 동안 축적된 대량의 데이터를 한 번에 처리하는 방식입니다. 주로 Hadoop MapReduce와 같은 기술 스택이 사용되며, 복잡한 분석이나 리포트 생성에 적합합니다. 장점은 대용량 데이터 처리 효율성이 높고, 비용 효율적이라는 점입니다. 단점은 실시간성이 떨어진다는 것입니다.

반면, 스트림 처리는 데이터가 생성되는 즉시 처리하는 방식입니다. Apache Kafka, Apache Flink, Apache Storm 등이 대표적인 기술이며, 실시간 추천 시스템, 이상 탐지, 사기 방지 시스템 등에 활용됩니다. 장점은 낮은 지연 시간으로 실시간 분석이 가능하다는 점이며, 단점은 복잡한 이벤트 처리와 데이터 정합성 유지에 어려움이 있을 수 있다는 것입니다.

구분 배치 처리 (Batch Processing) 스트림 처리 (Stream Processing)
데이터 처리 방식 일정 기간/양의 데이터를 모아 한 번에 처리 데이터가 생성되는 즉시 실시간으로 처리
주요 지표 처리량 (Throughput) 지연 시간 (Latency)
대표 기술 Hadoop MapReduce, Apache Spark Batch Apache Kafka, Apache Flink, Apache Storm
적합한 용도 대규모 데이터 분석, 일괄 리포트 생성 실시간 모니터링, 추천 시스템, 이상 탐지

람다 아키텍처와 카파 아키텍처: 실시간 데이터 처리의 진화

이 책은 람다 아키텍처가 배치 레이어와 스피드 레이어를 결합하여 배치 처리의 정확성과 스트림 처리의 실시간성을 동시에 확보하는 방법을 설명합니다. 배치 레이어는 과거의 모든 데이터를 정확하게 처리하여 마스터 데이터셋을 생성하고, 스피드 레이어는 실시간으로 들어오는 데이터를 처리하여 최신 정보를 제공합니다. 이러한 이중화된 구조는 복잡성을 증가시키지만, 데이터의 정확성과 최신성이라는 두 마리 토끼를 잡을 수 있게 합니다.

반면, 카파 아키텍처는 모든 데이터를 스트림으로 간주하고 단일 스트림 처리 엔진을 사용하여 복잡성을 줄이는 접근 방식입니다. 이는 람다 아키텍처의 유지보수 문제를 해결하는 대안으로 제시됩니다. 책은 각 아키텍처의 설계 원칙, 장단점, 그리고 적용 사례를 상세하게 다루어, 개발자가 자신의 서비스 요구사항에 가장 적합한 데이터 처리 아키텍처를 선택할 수 있도록 돕습니다.

이 섹션은 단순히 "어떤 기술을 써야 한다"고 말하는 대신, "이러한 요구사항에는 이런 처리 방식이 적합하고, 그 이유는 이렇다"는 식으로 근본적인 설계 사상을 이해하도록 돕습니다. 이는 변화무쌍한 데이터 처리 기술의 흐름 속에서도 흔들리지 않는 핵심 원칙을 갖추는 데 필수적입니다.


데이터 중심 애플리케이션 설계: 분산 시스템 시대의 견고한 아키텍처 구축 원칙 도서 리뷰 - train, buildings, urban, modern, skytrain, mrt, technology, traffic, architecture, city, cityscape, skyline, travel, railway, railroad, railway system, transportation, train, train, train, train, train, technology, technology, technology, technology, technology, railway

Image by harrywahidi on Pixabay

실전 적용을 위한 아키텍처 구축 원칙

이 책은 이론적인 개념 설명에만 머무르지 않고, 실제 데이터 중심 애플리케이션 아키텍처를 구축하는 데 필요한 실용적인 원칙과 가이드라인을 제시합니다. 마치 복잡한 설계도를 보고 건물을 짓는 방법을 배우듯, 추상적인 개념들을 구체적인 시스템 설계로 연결하는 방법을 가르쳐 줍니다.

메시지 전달 시스템과 이벤트 기반 아키텍처

분산 시스템에서 서비스 간의 통신은 매우 중요합니다. 이 책은 메시지 큐(Message Queue)발행/구독(Publish/Subscribe) 시스템과 같은 메시지 전달 시스템이 어떻게 서비스 간의 결합도를 낮추고 시스템의 확장성과 내결함성을 높이는 데 기여하는지 설명합니다. 예를 들어, Kafka와 같은 메시지 브로커를 활용하여 이벤트 기반 아키텍처를 구축하면, 각 서비스는 독립적으로 작동하면서도 느슨하게 연결되어 전체 시스템의 유연성을 극대화할 수 있습니다.


// 예시: 이벤트 기반 아키텍처에서 메시지 발행 및 구독 개념 (의사 코드)
class EventPublisher {
    private MessageBroker broker; // Kafka, RabbitMQ 등

    public EventPublisher(MessageBroker broker) {
        this.broker = broker;
    }

    public void publishUserCreatedEvent(String userId, String email) {
        String event = "{ \"type\": \"UserCreated\", \"userId\": \"" + userId + "\", \"email\": \"" + email + "\" }";
        broker.publish("user_events", event);
        System.out.println("UserCreated 이벤트 발행: " + userId);
    }
}

class EmailServiceSubscriber {
    private MessageBroker broker;

    public EmailServiceSubscriber(MessageBroker broker) {
        this.broker = broker;
        broker.subscribe("user_events", this::handleUserEvent);
    }

    private void handleUserEvent(String event) {
        // 이벤트 파싱 및 이메일 전송 로직
        if (event.contains("UserCreated")) {
            System.out.println("EmailService: UserCreated 이벤트 수신, 이메일 전송 준비...");
            // 실제 이메일 전송 로직 호출
        }
    }
}

// 실제 사용 예시
// MessageBroker kafkaBroker = new KafkaMessageBroker(); 
// EventPublisher publisher = new EventPublisher(kafkaBroker);
// EmailServiceSubscriber emailService = new EmailServiceSubscriber(kafkaBroker);
// publisher.publishUserCreatedEvent("user123", "user123@example.com");

위 예시처럼, 사용자 생성 이벤트를 발행하면 이메일 서비스는 이 이벤트를 구독하여 사용자에게 환영 이메일을 보낼 수 있습니다. 이 과정에서 두 서비스는 직접적으로 서로를 호출하지 않고 메시지 브로커를 통해 소통하므로, 각 서비스의 변경이 다른 서비스에 미치는 영향을 최소화할 수 있습니다. 이는 마이크로서비스 아키텍처의 핵심 원칙 중 하나이기도 합니다.

데이터 통합 및 파생 데이터 관리

여러 시스템에 분산된 데이터를 통합하고 관리하는 것은 또 다른 난관입니다. 이 책은 변경 데이터 캡처(Change Data Capture, CDC), 이벤트 소싱(Event Sourcing)과 같은 기술들을 소개하며, 어떻게 데이터를 일관되게 통합하고 파생 데이터를 효율적으로 관리할 수 있는지 보여줍니다. 예를 들어, 이벤트 소싱은 모든 상태 변경을 이벤트 스트림으로 기록하여, 시스템의 과거 상태를 언제든지 재구성할 수 있게 합니다. 이는 감사(Audit) 기능이나 특정 시점으로의 롤백에 매우 유용합니다.

또한, 데이터 웨어하우스나 데이터 레이크를 구축하여 다양한 소스의 데이터를 통합하고 분석하는 전략도 다룹니다. 이는 비즈니스 인텔리전스(BI) 및 머신러닝 모델 학습에 필요한 데이터 기반을 마련하는 데 필수적입니다. 이 책은 이러한 복잡한 데이터 파이프라인을 설계할 때 고려해야 할 사항들을 명확히 제시하여, 개발자가 데이터의 흐름을 전체적으로 파악하고 최적의 통합 전략을 수립할 수 있도록 돕습니다.


이 책을 통해 얻을 수 있는 통찰과 결론

"데이터 중심 애플리케이션 설계"는 단순히 특정 기술 스택의 사용법을 알려주는 책이 아닙니다. 이 책은 개발자가 데이터 시스템을 설계하는 데 필요한 근본적인 사고방식과 원칙을 가르쳐 줍니다. 복잡한 분산 시스템 환경에서 발생하는 문제들을 깊이 있게 이해하고, 그것을 해결하기 위한 다양한 접근 방식들을 체계적으로 정리해 줍니다. 마치 복잡한 미로 속에서 헤매는 이에게 정확한 지도를 건네주는 것과 같습니다.

이 책을 통해 얻을 수 있는 가장 큰 통찰은 바로 "정답은 없다"는 것입니다. 대신, "각각의 문제에는 가장 적합한 해법이 있으며, 그 해법을 찾기 위한 원칙과 트레이드오프를 이해하는 것이 중요하다"는 점을 끊임없이 강조합니다. 관계형 데이터베이스와 NoSQL 데이터베이스 중 무엇이 더 좋은지, 강력한 일관성과 최종 일관성 중 무엇을 선택해야 할지 등의 질문에 대해, 이 책은 단순히 한쪽 손을 들어주는 대신, 각 선택의 배경과 장단점, 그리고 서비스 요구사항에 따른 최적의 선택 기준을 제시합니다.

이 책은 주니어 개발자에게는 데이터 시스템의 기본기를 탄탄하게 다질 수 있는 기회를, 시니어 개발자 및 아키텍트에게는 오랫동안 쌓아온 경험을 이론적 프레임워크와 연결하여 더욱 깊은 이해를 제공합니다. 시스템 장애에 대한 두려움, 데이터 일관성 문제로 인한 밤샘 디버깅, 확장성 고민으로 지쳐있던 개발자라면 이 책에서 명확한 해결의 실마리를 찾을 수 있을 것입니다.

핵심 요약: 이 책은 데이터 중심 애플리케이션 설계에 필수적인 신뢰성, 확장성, 유지보수성 원칙을 기반으로, 다양한 데이터 모델, 분산 시스템의 일관성 및 합의 문제, 그리고 데이터 처리 아키텍처(배치, 스트림, 람다)에 대한 심도 깊은 통찰을 제공합니다. 현업에서 마주치는 복잡한 문제들을 해결하기 위한 근본적인 지식과 실용적인 가이드라인을 제시하며, 개발자라면 반드시 읽어야 할 필독서입니다.

이 책을 읽고 여러분은 어떤 통찰을 얻으셨나요? 혹은 이 책에서 다루는 내용 중 어떤 부분이 가장 인상 깊었는지 댓글로 공유해 주세요!

📌 함께 읽으면 좋은 글

  • [개발 책 리뷰] 실용주의 프로그래머: 개발자의 지속 가능한 성장과 태도를 위한 필독서 리뷰
  • [기술 리뷰] Flutter vs React Native: 크로스 플랫폼 모바일 앱 개발 프레임워크 심층 비교
  • [커리어 취업] 개발자 포트폴리오 구축 전략: 프로젝트 선정부터 기술 스택 표현까지

이 글이 도움이 되셨다면 공감(♥)댓글로 응원해 주세요!
궁금한 점이나 다루었으면 하는 주제가 있다면 댓글로 남겨주세요.

반응형