<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Road To Developer</title>
    <link>https://roadtodeveloper.tistory.com/</link>
    <description>Your life does not get better by chance; it gets better by change.</description>
    <language>ko</language>
    <pubDate>Wed, 17 Jun 2026 19:45:06 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>RTD</managingEditor>
    <image>
      <title>Road To Developer</title>
      <url>https://tistory1.daumcdn.net/tistory/8443133/attach/ceb55dad48a84326bdceba85f09d70c0</url>
      <link>https://roadtodeveloper.tistory.com</link>
    </image>
    <item>
      <title>[특화 프로젝트] 추천 장소 조회 API 개발</title>
      <link>https://roadtodeveloper.tistory.com/21</link>
      <description>&lt;h1&gt;빅데이터 분산 처리 프로젝트 - 추천 장소 조회 API 개발&lt;/h1&gt;
&lt;h2&gt;목표&lt;/h2&gt;
&lt;p&gt;분석 결과를 활용하여 &amp;quot;나와 비슷한 여행자들의 pick&amp;quot;을 반환하는 REST API를 구현하는 것이 목표다.&lt;/p&gt;
&lt;h3&gt;전체 흐름&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;클라이언트 요청
    → GET /api/recommend?nationality=KR&amp;amp;ageGroup=20s&amp;amp;gender=M&amp;amp;...
    → Spring Boot가 PostgreSQL의 popular_places 테이블 조회
    → 조건에 맞는 추천 장소 목록을 JSON으로 반환&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;1. Entity 클래스 생성&lt;/h2&gt;
&lt;p&gt;Entity는 PostgreSQL 테이블을 Java 클래스로 매핑한 것이다.&lt;br&gt;Spark가 생성한 &lt;code&gt;popular_places&lt;/code&gt; 테이블의 각 컬럼이 Java 필드가 된다.&lt;/p&gt;
&lt;h3&gt;패키지 구조&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;com.travel.bigdata.entity&lt;/code&gt; 패키지를 새로 생성했다.&lt;/p&gt;
&lt;h3&gt;PopularPlace.java&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.travel.bigdata.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Table(name = &amp;quot;popular_places&amp;quot;)
@Getter
@Setter
public class PopularPlace {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String nationality;

    @Column(name = &amp;quot;age_group&amp;quot;)
    private String ageGroup;

    private String gender;

    @Column(name = &amp;quot;travel_purpose&amp;quot;)
    private String travelPurpose;

    private String lifestyle;

    @Column(name = &amp;quot;place_id&amp;quot;)
    private String placeId;

    @Column(name = &amp;quot;visit_count&amp;quot;)
    private Long visitCount;

    @Column(name = &amp;quot;total_score&amp;quot;)
    private Long totalScore;

    private Integer rank;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;주요 어노테이션 설명&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;어노테이션&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Entity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;이 클래스가 DB 테이블과 연결됨을 선언&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Table(name = &amp;quot;popular_places&amp;quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;매핑할 테이블 이름 지정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;기본 키(Primary Key) 필드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@GeneratedValue&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;기본 키 자동 생성 전략&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Column(name = &amp;quot;age_group&amp;quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Java의 카멜케이스(&lt;code&gt;ageGroup&lt;/code&gt;)와 DB의 스네이크케이스(&lt;code&gt;age_group&lt;/code&gt;)를 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;2. Repository 생성&lt;/h2&gt;
&lt;p&gt;Repository는 DB에서 데이터를 조회하는 역할을 한다.&lt;br&gt;Spring Data JPA를 사용하면 메서드 이름만으로 SQL 쿼리가 자동 생성된다.&lt;/p&gt;
&lt;h3&gt;왜 Interface인가?&lt;/h3&gt;
&lt;p&gt;Repository는 Class가 아닌 &lt;strong&gt;Interface&lt;/strong&gt;로 생성한다.&lt;br&gt;Spring Data JPA가 메서드 이름을 분석하여 실제 구현 코드를 자동 생성해주기 때문이다.&lt;br&gt;개발자는 &amp;quot;어떤 조건으로 찾을지&amp;quot; 메서드 이름만 정의하면 된다.&lt;/p&gt;
&lt;h3&gt;PopularPlaceRepository.java&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;com.travel.bigdata.repository&lt;/code&gt; 패키지를 새로 생성했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.travel.bigdata.repository;

import com.travel.bigdata.entity.PopularPlace;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface PopularPlaceRepository extends JpaRepository&amp;lt;PopularPlace, Long&amp;gt; {

    List&amp;lt;PopularPlace&amp;gt; findByNationalityAndAgeGroupAndGenderAndTravelPurposeAndLifestyleOrderByRankAsc(
            String nationality, String ageGroup, String gender, String travelPurpose, String lifestyle
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;메서드 이름 → SQL 자동 변환&lt;/h3&gt;
&lt;p&gt;메서드 이름이 길지만, Spring Data JPA가 이를 파싱하여 아래 SQL을 자동 생성한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;findBy                → SELECT * FROM popular_places WHERE
Nationality           → nationality = ?
And AgeGroup          → AND age_group = ?
And Gender            → AND gender = ?
And TravelPurpose     → AND travel_purpose = ?
And Lifestyle         → AND lifestyle = ?
OrderByRankAsc        → ORDER BY rank ASC&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;SQL을 직접 작성할 필요 없이, 메서드 이름 규칙만 지키면 된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. 조회 API Controller 생성&lt;/h2&gt;
&lt;p&gt;사용자의 조건(국적, 연령대, 성별, 여행목적, 라이프스타일)을 받아&lt;br&gt;해당 군집의 인기 장소를 반환하는 API다.&lt;/p&gt;
&lt;h3&gt;RecommendController.java&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;controller&lt;/code&gt; 패키지에 생성했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.travel.bigdata.controller;

import com.travel.bigdata.entity.PopularPlace;
import com.travel.bigdata.repository.PopularPlaceRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping(&amp;quot;/api/recommend&amp;quot;)
public class RecommendController {

    private final PopularPlaceRepository repository;

    public RecommendController(PopularPlaceRepository repository) {
        this.repository = repository;
    }

    @GetMapping
    public ResponseEntity&amp;lt;List&amp;lt;PopularPlace&amp;gt;&amp;gt; getRecommendations(
            @RequestParam String nationality,
            @RequestParam String ageGroup,
            @RequestParam String gender,
            @RequestParam String travelPurpose,
            @RequestParam String lifestyle
    ) {
        List&amp;lt;PopularPlace&amp;gt; results = repository
                .findByNationalityAndAgeGroupAndGenderAndTravelPurposeAndLifestyleOrderByRankAsc(
                        nationality, ageGroup, gender, travelPurpose, lifestyle
                );

        return ResponseEntity.ok(results);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;동작 흐름&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;GET /api/recommend?nationality=KR&amp;amp;ageGroup=20s&amp;amp;gender=M&amp;amp;travelPurpose=SIGHTSEEING&amp;amp;lifestyle=ADVENTURE
    → RecommendController가 요청 파라미터를 받음
    → PopularPlaceRepository의 메서드 호출
    → Spring Data JPA가 SQL 자동 생성 및 실행
    → 결과를 JSON 리스트로 반환&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;4. 테스트&lt;/h2&gt;
&lt;h3&gt;API 호출&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl &amp;quot;http://localhost:8090/api/recommend?nationality=KR&amp;amp;ageGroup=20s&amp;amp;gender=M&amp;amp;travelPurpose=SIGHTSEEING&amp;amp;lifestyle=ADVENTURE&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;응답 결과&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;[
  {
    &amp;quot;id&amp;quot;: 1,
    &amp;quot;nationality&amp;quot;: &amp;quot;KR&amp;quot;,
    &amp;quot;ageGroup&amp;quot;: &amp;quot;20s&amp;quot;,
    &amp;quot;gender&amp;quot;: &amp;quot;M&amp;quot;,
    &amp;quot;travelPurpose&amp;quot;: &amp;quot;SIGHTSEEING&amp;quot;,
    &amp;quot;lifestyle&amp;quot;: &amp;quot;ADVENTURE&amp;quot;,
    &amp;quot;placeId&amp;quot;: &amp;quot;place_001&amp;quot;,
    &amp;quot;visitCount&amp;quot;: 3,
    &amp;quot;totalScore&amp;quot;: 5,
    &amp;quot;rank&amp;quot;: 1
  },
  {
    &amp;quot;id&amp;quot;: 2,
    &amp;quot;nationality&amp;quot;: &amp;quot;KR&amp;quot;,
    &amp;quot;ageGroup&amp;quot;: &amp;quot;20s&amp;quot;,
    &amp;quot;gender&amp;quot;: &amp;quot;M&amp;quot;,
    &amp;quot;travelPurpose&amp;quot;: &amp;quot;SIGHTSEEING&amp;quot;,
    &amp;quot;lifestyle&amp;quot;: &amp;quot;ADVENTURE&amp;quot;,
    &amp;quot;placeId&amp;quot;: &amp;quot;place_003&amp;quot;,
    &amp;quot;visitCount&amp;quot;: 2,
    &amp;quot;totalScore&amp;quot;: 4,
    &amp;quot;rank&amp;quot;: 2
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;20대 한국인 남성 관광 모험형 여행자에게 &lt;code&gt;place_001&lt;/code&gt;(1위, 점수 5)과 &lt;code&gt;place_003&lt;/code&gt;(2위, 점수 4)이 추천되었다.&lt;/p&gt;
&lt;h3&gt;DB 확인 (DBeaver)&lt;/h3&gt;
&lt;p&gt;DBeaver에서 &lt;code&gt;popular_places&lt;/code&gt; 테이블의 Data 탭을 클릭하면&lt;br&gt;API가 조회하는 데이터를 시각적으로 확인할 수 있다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;최종 프로젝트 구조&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;bigdata/
├── build.gradle
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/travel/bigdata/
│   │   │       ├── config/
│   │   │       │   └── HdfsConfig.java              ← HDFS 연결 설정
│   │   │       ├── controller/
│   │   │       │   ├── LogController.java            ← POST /api/logs (로그 저장)
│   │   │       │   └── RecommendController.java      ← GET /api/recommend (추천 조회)
│   │   │       ├── dto/
│   │   │       │   └── UserLogDto.java               ← 로그 데이터 구조
│   │   │       ├── entity/
│   │   │       │   └── PopularPlace.java             ← popular_places 테이블 매핑
│   │   │       ├── repository/
│   │   │       │   └── PopularPlaceRepository.java   ← DB 조회 인터페이스
│   │   │       ├── service/
│   │   │       │   └── HdfsService.java              ← HDFS 저장 로직
│   │   │       └── BigdataApplication.java
│   │   └── resources/
│   │       └── application.yml
│   └── test/
└── gradle/

travel-project/
├── docker-compose.yml
└── spark-jobs/
    ├── analyze_logs.py
    └── postgresql-42.7.4.jar&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;API 목록&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;메서드&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/logs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;사용자 행동 로그를 HDFS에 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/recommend&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;나와 비슷한 여행자들의 추천 장소 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;GET /api/recommend 파라미터&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;파라미터&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;nationality&lt;/td&gt;
&lt;td&gt;KR&lt;/td&gt;
&lt;td&gt;국적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ageGroup&lt;/td&gt;
&lt;td&gt;20s&lt;/td&gt;
&lt;td&gt;연령대 (10s, 20s, 30s, 40s, 50s+)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gender&lt;/td&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;성별 (M, F)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;travelPurpose&lt;/td&gt;
&lt;td&gt;SIGHTSEEING&lt;/td&gt;
&lt;td&gt;여행 목적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lifestyle&lt;/td&gt;
&lt;td&gt;ADVENTURE&lt;/td&gt;
&lt;td&gt;라이프스타일 (여행성향)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;전체 프로젝트 완료 현황&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;내용&lt;/th&gt;
&lt;th&gt;상태&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1단계&lt;/td&gt;
&lt;td&gt;Docker 인프라 세팅 (PostgreSQL, Hadoop, Spark)&lt;/td&gt;
&lt;td&gt;✅ 완료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2단계&lt;/td&gt;
&lt;td&gt;Spring Boot 프로젝트 생성 + PostgreSQL 연동&lt;/td&gt;
&lt;td&gt;✅ 완료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3단계&lt;/td&gt;
&lt;td&gt;HDFS에 사용자 로그 적재&lt;/td&gt;
&lt;td&gt;✅ 완료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4단계&lt;/td&gt;
&lt;td&gt;Spark 배치 분석 + 결과 저장&lt;/td&gt;
&lt;td&gt;✅ 완료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5단계&lt;/td&gt;
&lt;td&gt;추천 장소 조회 API 개발&lt;/td&gt;
&lt;td&gt;✅ 완료&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;전체 파이프라인 요약&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;사용자가 장소를 보거나(VIEW) 방문(GO)
    → POST /api/logs로 로그 전송
    → Spring Boot가 HDFS에 CSV로 저장
    → 새벽 4시 Spark 배치 실행 (spark-submit)
    → HDFS 로그를 읽어서 군집별 인기 장소 분석
    → 분석 결과를 PostgreSQL popular_places 테이블에 저장
    → 사용자가 앱에 접속하면 GET /api/recommend 호출
    → 국적/연령대/성별/여행목적/라이프스타일 기반으로 추천 장소 반환&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;기술 스택 요약&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;기술&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Java 21 / Spring Boot 3.5.11&lt;/td&gt;
&lt;td&gt;백엔드 API 서버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL 15&lt;/td&gt;
&lt;td&gt;분석 결과 저장 (RDB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hadoop HDFS 3.2.1&lt;/td&gt;
&lt;td&gt;대용량 사용자 로그 분산 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apache Spark 3.1.1&lt;/td&gt;
&lt;td&gt;빅데이터 배치 분석 (군집별 인기 장소)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker / Docker Compose&lt;/td&gt;
&lt;td&gt;인프라 컨테이너 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DBeaver&lt;/td&gt;
&lt;td&gt;DB 시각적 관리 도구&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;</description>
      <category>  빅데이터 분산</category>
      <author>RTD</author>
      <guid isPermaLink="true">https://roadtodeveloper.tistory.com/21</guid>
      <comments>https://roadtodeveloper.tistory.com/21#entry21comment</comments>
      <pubDate>Mon, 16 Mar 2026 12:19:53 +0900</pubDate>
    </item>
    <item>
      <title>[특화 프로젝트] Spark 배치 분석 및 결과 저장</title>
      <link>https://roadtodeveloper.tistory.com/20</link>
      <description>&lt;h1&gt;빅데이터 분산 처리 프로젝트 - Spark 배치 분석 및 결과 저장&lt;/h1&gt;
&lt;h2&gt;목표&lt;/h2&gt;
&lt;p&gt;HDFS에 쌓인 로그 데이터를 Spark로 분석하여&lt;br&gt;&amp;quot;비슷한 여행자들이 선호하는 장소&amp;quot; 결과를 도출하고 PostgreSQL에 저장하는 것이 목표다.&lt;/p&gt;
&lt;h3&gt;전체 흐름&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;HDFS (사용자 로그 CSV)
    → Spark가 읽어서 분석
    → 국적/연령대/성별/여행목적/라이프스타일별 인기 장소 도출
    → PostgreSQL의 popular_places 테이블에 저장&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;1. Spark 작업 실행 방식: spark-submit&lt;/h2&gt;
&lt;p&gt;Spark 분석 작업은 Spring Boot 내부에서 실행하는 것이 아니라,&lt;br&gt;별도의 스크립트를 Spark 클러스터에 제출(submit)하는 방식이다.&lt;br&gt;프로젝트 기획서에도 &amp;quot;새벽 4시 기준 스케줄링 : 트리거 역할&amp;quot;이라고 명시되어 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;새벽 4시 (스케줄러 트리거)
    → spark-submit 명령어 실행
    → Spark가 HDFS에서 로그 읽기
    → 군집별 인기 장소 분석
    → 결과를 PostgreSQL에 저장&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;분석 스크립트는 PySpark(Python)로 작성했다.&lt;br&gt;Java로도 가능하지만 Python이 더 간결하고 데이터 분석에 적합하다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. 사전 준비&lt;/h2&gt;
&lt;h3&gt;테스트 데이터 적재&lt;/h3&gt;
&lt;p&gt;분석을 위해 다양한 사용자 로그를 HDFS에 적재했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 한국인 20대 남성 관광객 로그
curl -X POST http://localhost:8090/api/logs -H &amp;quot;Content-Type: application/json&amp;quot; \
  -d &amp;quot;{\&amp;quot;userId\&amp;quot;:\&amp;quot;user001\&amp;quot;,\&amp;quot;nationality\&amp;quot;:\&amp;quot;KR\&amp;quot;,\&amp;quot;age\&amp;quot;:25,\&amp;quot;gender\&amp;quot;:\&amp;quot;M\&amp;quot;,\&amp;quot;travelPurpose\&amp;quot;:\&amp;quot;SIGHTSEEING\&amp;quot;,\&amp;quot;lifestyle\&amp;quot;:\&amp;quot;ADVENTURE\&amp;quot;,\&amp;quot;action\&amp;quot;:\&amp;quot;VIEW\&amp;quot;,\&amp;quot;placeId\&amp;quot;:\&amp;quot;place_001\&amp;quot;,\&amp;quot;timestamp\&amp;quot;:\&amp;quot;2026-03-16T10:30:00\&amp;quot;}&amp;quot;

# 일본인 30대 여성 맛집 탐방객 로그
curl -X POST http://localhost:8090/api/logs -H &amp;quot;Content-Type: application/json&amp;quot; \
  -d &amp;quot;{\&amp;quot;userId\&amp;quot;:\&amp;quot;user004\&amp;quot;,\&amp;quot;nationality\&amp;quot;:\&amp;quot;JP\&amp;quot;,\&amp;quot;age\&amp;quot;:32,\&amp;quot;gender\&amp;quot;:\&amp;quot;F\&amp;quot;,\&amp;quot;travelPurpose\&amp;quot;:\&amp;quot;FOOD\&amp;quot;,\&amp;quot;lifestyle\&amp;quot;:\&amp;quot;RELAXATION\&amp;quot;,\&amp;quot;action\&amp;quot;:\&amp;quot;GO\&amp;quot;,\&amp;quot;placeId\&amp;quot;:\&amp;quot;place_005\&amp;quot;,\&amp;quot;timestamp\&amp;quot;:\&amp;quot;2026-03-16T12:00:00\&amp;quot;}&amp;quot;

# ... 총 6건의 로그를 적재&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;PostgreSQL JDBC 드라이버 준비&lt;/h3&gt;
&lt;p&gt;Spark가 분석 결과를 PostgreSQL에 저장하려면 JDBC 드라이버가 필요하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# spark-jobs 폴더에 드라이버 다운로드
curl -L -o spark-jobs/postgresql-42.7.4.jar https://jdbc.postgresql.org/download/postgresql-42.7.4.jar&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;docker-compose.yml 수정&lt;/h3&gt;
&lt;p&gt;Spark Master가 로컬의 스크립트 파일에 접근할 수 있도록 volumes를 추가했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;  spark-master:
    image: bde2020/spark-master:3.1.1-hadoop3.2
    container_name: travel-spark-master
    ports:
      - &amp;quot;8080:8080&amp;quot;
      - &amp;quot;7077:7077&amp;quot;
    environment:
      CORE_CONF_fs_defaultFS: hdfs://namenode:9000
    volumes:
      - ./spark-jobs:/spark-jobs&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;./spark-jobs:/spark-jobs&lt;/code&gt;는 호스트의 spark-jobs 폴더를 컨테이너의 /spark-jobs 경로에 마운트한다.&lt;/p&gt;
&lt;h3&gt;폴더 구조&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;travel-project/
├── docker-compose.yml
└── spark-jobs/
    ├── analyze_logs.py            ← PySpark 분석 스크립트
    └── postgresql-42.7.4.jar      ← PostgreSQL JDBC 드라이버&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;3. PySpark 분석 스크립트&lt;/h2&gt;
&lt;h3&gt;analyze_logs.py&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from pyspark.sql import SparkSession
from pyspark.sql.functions import col, count, dense_rank, when, sum
from pyspark.sql.window import Window

# 1. Spark 세션 생성
spark = SparkSession.builder \
    .appName(&amp;quot;TravelLogAnalysis&amp;quot;) \
    .config(&amp;quot;spark.jars&amp;quot;, &amp;quot;/spark-jobs/postgresql-42.7.4.jar&amp;quot;) \
    .getOrCreate()

# 2. HDFS에서 로그 읽기
df = spark.read.csv(&amp;quot;hdfs://namenode:9000/user-logs/*/*&amp;quot;, header=False)

# 3. 컬럼 이름 지정
df = df.toDF(
    &amp;quot;user_id&amp;quot;, &amp;quot;nationality&amp;quot;, &amp;quot;age&amp;quot;, &amp;quot;gender&amp;quot;,
    &amp;quot;travel_purpose&amp;quot;, &amp;quot;lifestyle&amp;quot;, &amp;quot;action&amp;quot;, &amp;quot;place_id&amp;quot;, &amp;quot;timestamp&amp;quot;
)

# 4. 나이를 숫자로 변환하고 연령대 생성
df = df.withColumn(&amp;quot;age&amp;quot;, col(&amp;quot;age&amp;quot;).cast(&amp;quot;int&amp;quot;))
df = df.withColumn(&amp;quot;age_group&amp;quot;,
    when(col(&amp;quot;age&amp;quot;) &amp;lt; 20, &amp;quot;10s&amp;quot;)
    .when(col(&amp;quot;age&amp;quot;) &amp;lt; 30, &amp;quot;20s&amp;quot;)
    .when(col(&amp;quot;age&amp;quot;) &amp;lt; 40, &amp;quot;30s&amp;quot;)
    .when(col(&amp;quot;age&amp;quot;) &amp;lt; 50, &amp;quot;40s&amp;quot;)
    .otherwise(&amp;quot;50s+&amp;quot;)
)

# 5. 행동별 가중치 부여
df = df.withColumn(&amp;quot;score&amp;quot;,
    when(col(&amp;quot;action&amp;quot;) == &amp;quot;GO&amp;quot;, 3)
    .when(col(&amp;quot;action&amp;quot;) == &amp;quot;RE_RECOMMEND&amp;quot;, 2)
    .otherwise(1)  # VIEW
)

# 6. 군집별 장소 점수 합산
grouped = df.groupBy(
    &amp;quot;nationality&amp;quot;, &amp;quot;age_group&amp;quot;, &amp;quot;gender&amp;quot;,
    &amp;quot;travel_purpose&amp;quot;, &amp;quot;lifestyle&amp;quot;, &amp;quot;place_id&amp;quot;
).agg(
    count(&amp;quot;*&amp;quot;).alias(&amp;quot;visit_count&amp;quot;),
    sum(&amp;quot;score&amp;quot;).alias(&amp;quot;total_score&amp;quot;)
)

# 7. 군집 내에서 점수 높은 순으로 순위 매기기
window = Window.partitionBy(
    &amp;quot;nationality&amp;quot;, &amp;quot;age_group&amp;quot;, &amp;quot;gender&amp;quot;,
    &amp;quot;travel_purpose&amp;quot;, &amp;quot;lifestyle&amp;quot;
).orderBy(col(&amp;quot;total_score&amp;quot;).desc())

ranked = grouped.withColumn(&amp;quot;rank&amp;quot;, dense_rank().over(window))

# 8. 상위 5개 장소만 선택
top5 = ranked.filter(col(&amp;quot;rank&amp;quot;) &amp;lt;= 5)

# 9. 결과를 PostgreSQL에 저장
top5.write \
    .format(&amp;quot;jdbc&amp;quot;) \
    .option(&amp;quot;url&amp;quot;, &amp;quot;jdbc:postgresql://travel-postgres:5432/travel_db&amp;quot;) \
    .option(&amp;quot;dbtable&amp;quot;, &amp;quot;popular_places&amp;quot;) \
    .option(&amp;quot;user&amp;quot;, &amp;quot;admin&amp;quot;) \
    .option(&amp;quot;password&amp;quot;, &amp;quot;admin1234&amp;quot;) \
    .option(&amp;quot;driver&amp;quot;, &amp;quot;org.postgresql.Driver&amp;quot;) \
    .mode(&amp;quot;overwrite&amp;quot;) \
    .save()

print(&amp;quot;=== 분석 완료! popular_places 테이블에 저장됨 ===&amp;quot;)

spark.stop()&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;스크립트 단계별 설명&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;내용&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1~3&lt;/td&gt;
&lt;td&gt;HDFS에서 읽기&lt;/td&gt;
&lt;td&gt;모든 날짜(&lt;code&gt;/*/*&lt;/code&gt;)의 CSV 파일을 읽어서 컬럼명 부여&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;연령대 생성&lt;/td&gt;
&lt;td&gt;나이를 10대/20대/30대 등으로 그룹화. 기획서의 &amp;quot;userId별로 하면 경우의 수가 많아지니까 남성, 20대, 국적 이렇게 분류&amp;quot; 요구사항 반영&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;가중치 부여&lt;/td&gt;
&lt;td&gt;VIEW=1점, RE_RECOMMEND=2점, GO=3점. 기획서의 &amp;quot;각 로그별 스코어를 부여함 : 가중치를 위해&amp;quot; 요구사항 반영&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;군집별 합산&lt;/td&gt;
&lt;td&gt;국적+연령대+성별+여행목적+라이프스타일 조합별로 장소 점수를 합산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;순위 매기기&lt;/td&gt;
&lt;td&gt;각 군집 내에서 total_score 기준 내림차순 순위 부여&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;상위 5개&lt;/td&gt;
&lt;td&gt;군집별 인기 장소 Top 5만 추출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;PostgreSQL 저장&lt;/td&gt;
&lt;td&gt;결과를 popular_places 테이블에 overwrite 모드로 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;PostgreSQL 접속 주소&lt;/h3&gt;
&lt;p&gt;스크립트에서 PostgreSQL 접속 URL이 &lt;code&gt;localhost&lt;/code&gt;가 아니라 &lt;code&gt;travel-postgres&lt;/code&gt;인 이유는,&lt;br&gt;Spark가 Docker 컨테이너 안에서 실행되기 때문이다.&lt;br&gt;Docker 네트워크에서는 컨테이너 이름으로 서로 접근할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;호스트(내 PC)에서 접속할 때     → localhost:5432
Docker 컨테이너에서 접속할 때   → travel-postgres:5432&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;4. 실행&lt;/h2&gt;
&lt;h3&gt;spark-submit 명령어&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;MSYS_NO_PATHCONV=1 docker exec -it travel-spark-master \
  /spark/bin/spark-submit \
  --jars /spark-jobs/postgresql-42.7.4.jar \
  /spark-jobs/analyze_logs.py&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;⚠️ &lt;strong&gt;Windows Git Bash 사용 시 주의&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Git Bash는 &lt;code&gt;/spark&lt;/code&gt;를 Windows 경로(&lt;code&gt;C:/Program Files/Git/spark&lt;/code&gt;)로 자동 변환한다.&lt;br&gt;&lt;code&gt;MSYS_NO_PATHCONV=1&lt;/code&gt;을 앞에 붙여서 경로 변환을 방지해야 한다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;성공 로그&lt;/h3&gt;
&lt;p&gt;실행 후 대량의 Spark 로그가 출력되다가 마지막에 다음 메시지가 나오면 성공이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;=== 분석 완료! popular_places 테이블에 저장됨 ===&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;5. 결과 확인&lt;/h2&gt;
&lt;h3&gt;CLI로 확인&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker exec -it travel-postgres psql -U admin -d travel_db -c &amp;quot;SELECT * FROM popular_places;&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;결과&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt; nationality | age_group | gender | travel_purpose |  lifestyle  |  place_id  | visit_count | total_score | rank
-------------+-----------+--------+----------------+-------------+------------+-------------+-------------+------
 KR          | 20s       | M      | SIGHTSEEING    | ADVENTURE   | place_001  |           3 |           5 |    1
 KR          | 20s       | M      | SIGHTSEEING    | ADVENTURE   | place_003  |           2 |           4 |    2
 JP          | 20s       | F      | FOOD           | RELAXATION  | place_005  |           1 |           1 |    1
 JP          | 30s       | F      | FOOD           | RELAXATION  | place_005  |           1 |           3 |    1&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;결과 해석&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;한국인 / 20대 / 남성 / 관광 / 모험형&lt;/strong&gt;: place_001이 1위(점수 5), place_003이 2위(점수 4)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;일본인 / 여성 / 맛집 / 휴식형&lt;/strong&gt;: 20대와 30대 모두 place_005를 선호&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 데이터가 &amp;quot;나와 비슷한 여행자들의 pick&amp;quot; 추천의 기반이 된다.&lt;br&gt;예를 들어 20대 한국인 남성 관광객이 앱에 접속하면, 이 테이블에서 해당 군집을 조회하여&lt;br&gt;place_001, place_003을 추천할 수 있다.&lt;/p&gt;
&lt;h3&gt;GUI로 확인 (DBeaver)&lt;/h3&gt;
&lt;p&gt;커맨드 라인 대신 DBeaver를 사용하면 테이블 데이터를 시각적으로 확인할 수 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://dbeaver.io/download&quot;&gt;dbeaver.io&lt;/a&gt;에서 Community Edition 설치&lt;/li&gt;
&lt;li&gt;새 연결 → PostgreSQL 선택&lt;/li&gt;
&lt;li&gt;접속 정보 입력: Host &lt;code&gt;localhost&lt;/code&gt;, Port &lt;code&gt;5432&lt;/code&gt;, Database &lt;code&gt;travel_db&lt;/code&gt;, User &lt;code&gt;admin&lt;/code&gt;, Password &lt;code&gt;admin1234&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;travel_db &amp;gt; Schemas &amp;gt; public &amp;gt; Tables &amp;gt; popular_places&lt;/code&gt; 더블클릭&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;트러블슈팅&lt;/h2&gt;
&lt;h3&gt;Git Bash 경로 변환 문제&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;exec: &amp;quot;C:/Program Files/Git/spark/bin/spark-submit&amp;quot;: no such file or directory&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Git Bash가 &lt;code&gt;/spark&lt;/code&gt;를 Windows 경로로 변환해서 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결&lt;/strong&gt;: 명령어 앞에 &lt;code&gt;MSYS_NO_PATHCONV=1&lt;/code&gt;을 추가한다.&lt;/p&gt;
&lt;hr&gt;</description>
      <category>  빅데이터 분산</category>
      <category>Spark</category>
      <author>RTD</author>
      <guid isPermaLink="true">https://roadtodeveloper.tistory.com/20</guid>
      <comments>https://roadtodeveloper.tistory.com/20#entry20comment</comments>
      <pubDate>Mon, 16 Mar 2026 11:52:37 +0900</pubDate>
    </item>
    <item>
      <title>[특화 프로젝트] HDFS에 사용자 로그 적재</title>
      <link>https://roadtodeveloper.tistory.com/19</link>
      <description>&lt;h1&gt;빅데이터 분산 처리 프로젝트 - HDFS에 사용자 로그 적재&lt;/h1&gt;
&lt;h2&gt;목표&lt;/h2&gt;
&lt;p&gt;Spring Boot에서 HDFS에 사용자 행동 로그를 저장하는 파이프라인을 구현하는 것이 목표다.&lt;/p&gt;
&lt;h3&gt;전체 흐름&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;클라이언트 → POST /api/logs → Spring Boot → HDFS에 CSV 파일로 저장&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;1. Hadoop 라이브러리 추가&lt;/h2&gt;
&lt;p&gt;Spring Boot에서 HDFS에 접근하려면 Hadoop Client 라이브러리가 필요하다.&lt;br&gt;&lt;code&gt;build.gradle&lt;/code&gt;의 &lt;code&gt;dependencies&lt;/code&gt;에 추가했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-groovy&quot;&gt;dependencies {
    // 기존 의존성 생략...

    // HDFS 연동을 위한 Hadoop 라이브러리
    implementation &amp;#39;org.apache.hadoop:hadoop-client:3.2.1&amp;#39;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;버전은 Docker로 띄운 Hadoop(3.2.1)과 동일하게 맞췄다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. 프로젝트 패키지 구조&lt;/h2&gt;
&lt;p&gt;코드를 역할별로 분리하기 위해 4개의 패키지를 생성했다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;com.travel.bigdata
├── config/         ← 설정 클래스 (HDFS 연결 등)
├── controller/     ← API 엔드포인트
├── dto/            ← 데이터 구조 정의
├── service/        ← 비즈니스 로직
└── BigdataApplication.java&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;3. 로그 데이터 구조 정의 (DTO)&lt;/h2&gt;
&lt;p&gt;사용자 행동 로그에 담길 데이터를 정의하는 클래스다.&lt;br&gt;프로젝트 기획서에 명시된 필드(국적, 나이, 성별, 여행목적, 라이프스타일 등)를 그대로 반영했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.travel.bigdata.dto;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class UserLogDto {
    private String userId;          // 사용자 ID
    private String nationality;     // 국적
    private int age;                // 나이
    private String gender;          // 성별
    private String travelPurpose;   // 여행 목적
    private String lifestyle;       // 라이프스타일 (여행성향)
    private String action;          // 행동 (VIEW, GO, RE_RECOMMEND)
    private String placeId;         // 장소 ID
    private String timestamp;       // 로그 생성 시간
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Lombok 어노테이션&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;어노테이션&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Getter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;모든 필드의 getter 메서드 자동 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Setter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;모든 필드의 setter 메서드 자동 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@ToString&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;toString()&lt;/code&gt; 메서드 자동 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Lombok을 사용하면 보일러플레이트 코드를 직접 작성하지 않아도 된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. HDFS 연결 설정 (Config)&lt;/h2&gt;
&lt;p&gt;Spring Boot에서 HDFS에 접속하기 위한 설정 클래스다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.travel.bigdata.config;

import org.apache.hadoop.fs.FileSystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HdfsConfig {

    private final String hdfsUri = &amp;quot;hdfs://localhost:9000&amp;quot;;

    @Bean
    public FileSystem fileSystem() throws Exception {
        org.apache.hadoop.conf.Configuration configuration = new org.apache.hadoop.conf.Configuration();
        configuration.set(&amp;quot;fs.defaultFS&amp;quot;, hdfsUri);
        configuration.set(&amp;quot;dfs.client.use.datanode.hostname&amp;quot;, &amp;quot;true&amp;quot;);

        // HDFS에 root 사용자로 접속
        System.setProperty(&amp;quot;HADOOP_USER_NAME&amp;quot;, &amp;quot;root&amp;quot;);

        return FileSystem.get(configuration);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;주요 설정 설명&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;설정&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Configuration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;이 클래스가 Spring 설정 파일임을 선언&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@Bean&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;이 메서드의 반환 객체를 Spring이 관리하도록 등록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fs.defaultFS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HDFS의 주소. Docker의 NameNode 포트(9000)와 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dfs.client.use.datanode.hostname&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DataNode 접근 시 컨테이너 IP 대신 hostname을 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HADOOP_USER_NAME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HDFS에 root 권한으로 접속 (권한 문제 방지)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;5. HDFS 저장 로직 (Service)&lt;/h2&gt;
&lt;p&gt;로그 데이터를 받아서 HDFS에 CSV 파일로 저장하는 핵심 로직이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.travel.bigdata.service;

import com.travel.bigdata.dto.UserLogDto;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.springframework.stereotype.Service;

import java.time.LocalDate;

@Service
public class HdfsService {

    private final FileSystem fileSystem;

    public HdfsService(FileSystem fileSystem) {
        this.fileSystem = fileSystem;
    }

    public void saveLog(UserLogDto log) throws Exception {
        // 날짜별로 폴더를 나눠서 저장
        String date = LocalDate.now().toString();
        String fileName = &amp;quot;log_&amp;quot; + System.currentTimeMillis() + &amp;quot;.csv&amp;quot;;
        Path path = new Path(&amp;quot;/user-logs/&amp;quot; + date + &amp;quot;/&amp;quot; + fileName);

        // CSV 형태로 한 줄 만들기
        String csvLine = String.join(&amp;quot;,&amp;quot;,
                log.getUserId(),
                log.getNationality(),
                String.valueOf(log.getAge()),
                log.getGender(),
                log.getTravelPurpose(),
                log.getLifestyle(),
                log.getAction(),
                log.getPlaceId(),
                log.getTimestamp()
        );

        // HDFS에 파일 쓰기
        try (FSDataOutputStream outputStream = fileSystem.create(path, true)) {
            outputStream.writeBytes(csvLine + &amp;quot;\n&amp;quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;설계 포인트&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;날짜별 폴더 분리&lt;/strong&gt;: &lt;code&gt;/user-logs/2026-03-16/&lt;/code&gt; 형태로 저장하여 Spark가 특정 날짜의 로그만 선택적으로 읽을 수 있도록 했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSV 형태 저장&lt;/strong&gt;: Spark가 CSV를 쉽게 파싱할 수 있어 분석 단계에서 편리하다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;타임스탬프 기반 파일명&lt;/strong&gt;: &lt;code&gt;log_1773627176253.csv&lt;/code&gt;처럼 밀리초 단위 타임스탬프로 파일명을 생성하여 충돌을 방지했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;6. API 엔드포인트 (Controller)&lt;/h2&gt;
&lt;p&gt;외부에서 로그 데이터를 POST 요청으로 보내면 HDFS에 저장하는 API다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package com.travel.bigdata.controller;

import com.travel.bigdata.dto.UserLogDto;
import com.travel.bigdata.service.HdfsService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(&amp;quot;/api/logs&amp;quot;)
public class LogController {

    private final HdfsService hdfsService;

    public LogController(HdfsService hdfsService) {
        this.hdfsService = hdfsService;
    }

    @PostMapping
    public ResponseEntity&amp;lt;String&amp;gt; saveLog(@RequestBody UserLogDto logDto) {
        try {
            hdfsService.saveLog(logDto);
            return ResponseEntity.ok(&amp;quot;로그 저장 성공&amp;quot;);
        } catch (Exception e) {
            return ResponseEntity.internalServerError().body(&amp;quot;로그 저장 실패: &amp;quot; + e.getMessage());
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;요청-응답 흐름&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;POST /api/logs (JSON 데이터)
    → LogController가 요청을 받음
    → HdfsService.saveLog() 호출
    → HDFS에 CSV 파일 저장
    → &amp;quot;로그 저장 성공&amp;quot; 응답 반환&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;7. 테스트&lt;/h2&gt;
&lt;h3&gt;API 호출&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -X POST http://localhost:8090/api/logs \
  -H &amp;quot;Content-Type: application/json&amp;quot; \
  -d &amp;quot;{\&amp;quot;userId\&amp;quot;:\&amp;quot;user001\&amp;quot;,\&amp;quot;nationality\&amp;quot;:\&amp;quot;KR\&amp;quot;,\&amp;quot;age\&amp;quot;:25,\&amp;quot;gender\&amp;quot;:\&amp;quot;M\&amp;quot;,\&amp;quot;travelPurpose\&amp;quot;:\&amp;quot;SIGHTSEEING\&amp;quot;,\&amp;quot;lifestyle\&amp;quot;:\&amp;quot;ADVENTURE\&amp;quot;,\&amp;quot;action\&amp;quot;:\&amp;quot;VIEW\&amp;quot;,\&amp;quot;placeId\&amp;quot;:\&amp;quot;place_001\&amp;quot;,\&amp;quot;timestamp\&amp;quot;:\&amp;quot;2026-03-16T10:30:00\&amp;quot;}&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;응답: &lt;code&gt;로그 저장 성공&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;HDFS에서 파일 확인&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 파일 목록 확인
docker exec -it travel-namenode hdfs dfs -ls hdfs://namenode:9000/user-logs/2026-03-16/

# 파일 내용 확인
docker exec -it travel-namenode hdfs dfs -cat hdfs://namenode:9000/user-logs/2026-03-16/*.csv&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;출력 결과:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;user001,KR,25,M,SIGHTSEEING,ADVENTURE,VIEW,place_001,2026-03-16T10:30:00&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;전송한 로그 데이터가 CSV 형태로 HDFS에 정상 저장된 것을 확인했다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;트러블슈팅&lt;/h2&gt;
&lt;h3&gt;1. HADOOP_HOME 경고&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;HADOOP_HOME and hadoop.home.dir are unset.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Windows에서 Hadoop 라이브러리를 사용하면 발생하는 경고다.&lt;br&gt;네트워크로 HDFS에 접속하는 방식이므로 로컬에 Hadoop이 설치되어 있을 필요가 없다.&lt;br&gt;&lt;strong&gt;무시해도 정상 동작한다.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;2. Permission denied 에러&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Permission denied: user=SSAFY, access=WRITE, inode=&amp;quot;/&amp;quot;:root:supergroup:drwxr-xr-x&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;HDFS는 root 사용자 권한으로 동작하는데, Spring Boot가 Windows 사용자(SSAFY)로 접속하면 쓰기 권한이 없다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;해결&lt;/strong&gt;: HdfsConfig에서 &lt;code&gt;System.setProperty(&amp;quot;HADOOP_USER_NAME&amp;quot;, &amp;quot;root&amp;quot;)&lt;/code&gt; 설정으로 HDFS에 root 사용자로 접속하도록 했다.&lt;/p&gt;
&lt;h3&gt;3. DataNode 연결 실패 (UnresolvedAddressException)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;java.nio.channels.UnresolvedAddressException&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Spring Boot(호스트)가 Docker 컨테이너 내부의 DataNode에 접근하지 못하는 문제다.&lt;br&gt;세 가지를 수정하여 해결했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;docker-compose.yml 수정&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DataNode에 &lt;code&gt;hostname: datanode&lt;/code&gt; 설정 추가&lt;/li&gt;
&lt;li&gt;DataNode에 &lt;code&gt;ports: 9864, 9866&lt;/code&gt; 포트 매핑 추가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HDFS_CONF_dfs_datanode_hostname: datanode&lt;/code&gt; 환경변수 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;HdfsConfig.java 수정&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dfs.client.use.datanode.hostname&lt;/code&gt; 설정을 &lt;code&gt;true&lt;/code&gt;로 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Windows hosts 파일 수정&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C:\Windows\System32\drivers\etc\hosts&lt;/code&gt;에 &lt;code&gt;127.0.0.1 datanode&lt;/code&gt; 추가&lt;/li&gt;
&lt;li&gt;관리자 권한 PowerShell에서 아래 명령어로 추가 가능:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;Add-Content -Path &amp;quot;C:\Windows\System32\drivers\etc\hosts&amp;quot; -Value &amp;quot;127.0.0.1 datanode&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;최종 docker-compose.yml&lt;/h2&gt;
&lt;p&gt;3단계까지의 변경사항이 반영된 전체 파일이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  postgres:
    image: postgres:15
    container_name: travel-postgres
    ports:
      - &amp;quot;5432:5432&amp;quot;
    environment:
      POSTGRES_DB: travel_db
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: admin1234
    volumes:
      - postgres_data:/var/lib/postgresql/data

  namenode:
    image: bde2020/hadoop-namenode:2.0.0-hadoop3.2.1-java8
    container_name: travel-namenode
    ports:
      - &amp;quot;9870:9870&amp;quot;
      - &amp;quot;9000:9000&amp;quot;
    environment:
      CLUSTER_NAME: travel-cluster
      CORE_CONF_fs_defaultFS: hdfs://namenode:9000
    volumes:
      - namenode_data:/hadoop/dfs/name

  datanode:
    image: bde2020/hadoop-datanode:2.0.0-hadoop3.2.1-java8
    container_name: travel-datanode
    hostname: datanode
    depends_on:
      - namenode
    ports:
      - &amp;quot;9864:9864&amp;quot;
      - &amp;quot;9866:9866&amp;quot;
    environment:
      SERVICE_PRECONDITION: &amp;quot;namenode:9870&amp;quot;
      CORE_CONF_fs_defaultFS: hdfs://namenode:9000
      HDFS_CONF_dfs_datanode_hostname: datanode
    volumes:
      - datanode_data:/hadoop/dfs/data

  spark-master:
    image: bde2020/spark-master:3.1.1-hadoop3.2
    container_name: travel-spark-master
    ports:
      - &amp;quot;8080:8080&amp;quot;
      - &amp;quot;7077:7077&amp;quot;
    environment:
      CORE_CONF_fs_defaultFS: hdfs://namenode:9000

  spark-worker:
    image: bde2020/spark-worker:3.1.1-hadoop3.2
    container_name: travel-spark-worker
    depends_on:
      - spark-master
    environment:
      SPARK_MASTER: spark://spark-master:7077
      CORE_CONF_fs_defaultFS: hdfs://namenode:9000

volumes:
  postgres_data:
  namenode_data:
  datanode_data:&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;최종 프로젝트 구조&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;bigdata/
├── build.gradle
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/travel/bigdata/
│   │   │       ├── config/
│   │   │       │   └── HdfsConfig.java          ← HDFS 연결 설정
│   │   │       ├── controller/
│   │   │       │   └── LogController.java        ← POST /api/logs API
│   │   │       ├── dto/
│   │   │       │   └── UserLogDto.java           ← 로그 데이터 구조
│   │   │       ├── service/
│   │   │       │   └── HdfsService.java          ← HDFS 저장 로직
│   │   │       └── BigdataApplication.java
│   │   └── resources/
│   │       └── application.yml
│   └── test/
└── gradle/&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;</description>
      <category>  빅데이터 분산</category>
      <category>HDFS</category>
      <category>Spring boot</category>
      <author>RTD</author>
      <guid isPermaLink="true">https://roadtodeveloper.tistory.com/19</guid>
      <comments>https://roadtodeveloper.tistory.com/19#entry19comment</comments>
      <pubDate>Mon, 16 Mar 2026 11:21:29 +0900</pubDate>
    </item>
    <item>
      <title>[특화 프로젝트] Spring Boot 프로젝트 생성 및 PostgreSQL 연동</title>
      <link>https://roadtodeveloper.tistory.com/18</link>
      <description>&lt;h1&gt;빅데이터 분산 처리 프로젝트 - Spring Boot 프로젝트 생성 및 PostgreSQL 연동&lt;/h1&gt;
&lt;h2&gt;목표&lt;/h2&gt;
&lt;p&gt;1단계에서 Docker로 인프라(PostgreSQL, Hadoop, Spark)를 구성했다.&lt;br&gt;2단계에서는 Spring Boot 프로젝트를 생성하고, Docker로 띄운 PostgreSQL과 연결하는 것이 목표다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;1. Spring Initializr로 프로젝트 생성&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://start.spring.io&quot;&gt;https://start.spring.io&lt;/a&gt; 에서 프로젝트를 생성했다.&lt;/p&gt;
&lt;h3&gt;프로젝트 설정&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;설정값&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Project&lt;/td&gt;
&lt;td&gt;Gradle - Groovy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Boot&lt;/td&gt;
&lt;td&gt;3.5.11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Group&lt;/td&gt;
&lt;td&gt;com.travel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Artifact&lt;/td&gt;
&lt;td&gt;bigdata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Packaging&lt;/td&gt;
&lt;td&gt;Jar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;YAML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;Dependencies&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;의존성&lt;/th&gt;
&lt;th&gt;용도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Spring Web&lt;/td&gt;
&lt;td&gt;REST API 개발&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Data JPA&lt;/td&gt;
&lt;td&gt;PostgreSQL과 ORM 연동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL Driver&lt;/td&gt;
&lt;td&gt;PostgreSQL JDBC 드라이버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lombok&lt;/td&gt;
&lt;td&gt;보일러플레이트 코드 자동 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;GENERATE 버튼을 눌러 zip 파일을 다운로드하고, 압축을 풀어 IntelliJ에서 열었다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. Gradle이란?&lt;/h2&gt;
&lt;p&gt;Spring Boot 프로젝트는 수많은 외부 라이브러리에 의존한다.&lt;br&gt;Gradle은 이 라이브러리들을 자동으로 다운로드하고, 코드를 컴파일하고, 실행 가능한 파일(.jar)로 빌드해주는 도구다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;build.gradle&lt;/code&gt; 파일에 필요한 라이브러리를 선언하면, Gradle이 알아서 다운받아준다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-groovy&quot;&gt;dependencies {
    implementation &amp;#39;org.springframework.boot:spring-boot-starter-web&amp;#39;
    implementation &amp;#39;org.springframework.boot:spring-boot-starter-data-jpa&amp;#39;
    runtimeOnly &amp;#39;org.postgresql:postgresql&amp;#39;
    compileOnly &amp;#39;org.projectlombok:lombok&amp;#39;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;새로운 라이브러리가 필요하면 여기에 한 줄 추가하고 Gradle 새로고침만 하면 된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. IntelliJ에서 프로젝트 열기&lt;/h2&gt;
&lt;h3&gt;프로젝트 로드&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;IntelliJ 실행 → Open → 압축 푼 bigdata 폴더 선택&lt;/li&gt;
&lt;li&gt;우측 하단에 &amp;quot;Load Gradle Project&amp;quot; 팝업이 뜨면 클릭&lt;/li&gt;
&lt;li&gt;Gradle이 의존성 다운로드 및 프로젝트 빌드를 자동 수행&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;JDK 설정&lt;/h3&gt;
&lt;p&gt;프로젝트를 열면 &amp;quot;Project JDK is not defined&amp;quot; 경고가 뜰 수 있다.&lt;br&gt;상단의 &lt;strong&gt;Setup SDK&lt;/strong&gt; 링크를 클릭하고 JDK 21을 선택하면 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;⚠️ &lt;strong&gt;Java 버전 주의&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Spring Initializr에서 Java 17로 설정했지만, IntelliJ가 자동으로 JDK 21을 다운받은 경우&lt;br&gt;&lt;code&gt;build.gradle&lt;/code&gt;의 &lt;code&gt;languageVersion&lt;/code&gt;을 21로 맞춰줘야 빌드 에러가 발생하지 않는다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-groovy&quot;&gt;java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;빌드 확인&lt;/h3&gt;
&lt;p&gt;Gradle 패널의 새로고침 버튼( )을 클릭하면 빌드가 시작된다.&lt;br&gt;하단 Build 탭에 &lt;code&gt;BUILD SUCCESSFUL&lt;/code&gt;이 표시되면 성공이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. PostgreSQL 연동 설정&lt;/h2&gt;
&lt;h3&gt;application.yml&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;src/main/resources/application.yml&lt;/code&gt; 파일에 DB 접속 정보와 JPA 설정을 작성했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/travel_db
    username: admin
    password: admin1234
    driver-class-name: org.postgresql.Driver

  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect

server:
  port: 8090&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;주요 설정 설명&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;datasource.url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Docker로 띄운 PostgreSQL의 JDBC 접속 URL. 1단계에서 설정한 DB명(&lt;code&gt;travel_db&lt;/code&gt;)과 포트(&lt;code&gt;5432&lt;/code&gt;) 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;datasource.username / password&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;docker-compose.yml의 &lt;code&gt;POSTGRES_USER&lt;/code&gt;, &lt;code&gt;POSTGRES_PASSWORD&lt;/code&gt;와 동일하게 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jpa.hibernate.ddl-auto: update&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;엔티티 클래스를 기반으로 테이블을 자동 생성/수정. 개발 단계에서 편리함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jpa.show-sql: true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;실행되는 SQL 쿼리를 콘솔에 출력하여 디버깅에 활용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;server.port: 8090&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;기본 포트 8080은 Spark Master가 사용 중이므로 8090으로 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;5. 연결 테스트&lt;/h2&gt;
&lt;h3&gt;실행 전 확인사항&lt;/h3&gt;
&lt;p&gt;Docker 컨테이너가 켜져 있어야 한다. 터미널에서 확인 가능하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# travel-project 폴더에서
docker-compose up -d
docker ps&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;5개 컨테이너(postgres, namenode, datanode, spark-master, spark-worker)가 모두 Up 상태여야 한다.&lt;/p&gt;
&lt;h3&gt;Spring Boot 실행&lt;/h3&gt;
&lt;p&gt;IntelliJ에서 &lt;code&gt;BigdataApplication.java&lt;/code&gt; 파일을 열고, &lt;code&gt;public class BigdataApplication&lt;/code&gt; 옆의 초록색 ▶ 버튼을 클릭하여 Run을 선택한다.&lt;/p&gt;
&lt;h3&gt;성공 로그&lt;/h3&gt;
&lt;p&gt;콘솔에서 다음 로그들이 확인되면 연동 성공이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HikariPool-1 - Start completed.                    ← PostgreSQL 연결 성공
Tomcat started on port 8090 (http)                  ← 서버 8090 포트에서 실행
Started BigdataApplication in X.XX seconds          ← Spring Boot 정상 기동&lt;/code&gt;&lt;/pre&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;  &lt;strong&gt;WARN 로그에 대하여&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;실행 시 두 가지 WARN 메시지가 나올 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PostgreSQLDialect does not need to be specified explicitly&lt;/code&gt; — dialect를 명시하지 않아도 자동 감지된다는 안내. 무시해도 무방하다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spring.jpa.open-in-view is enabled by default&lt;/code&gt; — open-in-view 설정 관련 안내. 개발 단계에서는 무시해도 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;두 가지 모두 에러가 아닌 참고용 경고이므로 정상 동작에 영향을 주지 않는다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;프로젝트 구조&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;bigdata/
├── build.gradle                          ← Gradle 빌드 설정 (의존성 관리)
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/travel/bigdata/
│   │   │       └── BigdataApplication.java   ← Spring Boot 메인 클래스
│   │   └── resources/
│   │       └── application.yml               ← DB 연결 및 서버 설정
│   └── test/
└── gradle/&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;</description>
      <category>  빅데이터 분산</category>
      <category>gradle</category>
      <category>Spring boot</category>
      <author>RTD</author>
      <guid isPermaLink="true">https://roadtodeveloper.tistory.com/18</guid>
      <comments>https://roadtodeveloper.tistory.com/18#entry18comment</comments>
      <pubDate>Mon, 16 Mar 2026 09:51:36 +0900</pubDate>
    </item>
    <item>
      <title>[특화 프로젝트]Docker 인프라 세팅하는 법</title>
      <link>https://roadtodeveloper.tistory.com/17</link>
      <description>&lt;h1&gt;빅데이터 분산 처리 프로젝트 - On My Guide Docker 인프라 세팅&lt;/h1&gt;
&lt;h2&gt;프로젝트 개요&lt;/h2&gt;
&lt;p&gt;외국인 관광객을 위한 여행지 추천 시스템의 백엔드를 개발하는 프로젝트다.&lt;br&gt;사용자 행동 로그를 수집하고, 빅데이터 분산 처리를 통해 &amp;quot;나와 비슷한 여행자들의 선호 여행지&amp;quot;를 분석하여 추천하는 것이 목표다.&lt;/p&gt;
&lt;h3&gt;기술 스택&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Java 17 / Spring Boot 3.0.5 / Spring Security 6&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt; — 분석 결과 저장용 RDB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hadoop (HDFS)&lt;/strong&gt; — 대용량 사용자 로그 적재&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spark&lt;/strong&gt; — 배치 분산 처리 (군집 분석)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kafka&lt;/strong&gt; — 로그 이벤트 스트리밍&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;전체 데이터 파이프라인&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;사용자 행동 (GO, 상세보기, 재추천)
    ↓
Kafka (로그 이벤트 스트림)
    ↓
Spring Consumer (Kafka 메시지 수신)
    ↓
HDFS (사용자 로그 적재)
    ↓
Spark 배치 처리 (새벽 4시, 군집 분석)
    ↓
PostgreSQL (분석 결과 저장)
    ↓
조회 API → 클라이언트에 추천 장소 반환&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;왜 Docker를 사용하는가?&lt;/h2&gt;
&lt;p&gt;이 프로젝트에는 PostgreSQL, Hadoop, Spark, Kafka 등 여러 인프라가 필요하다.&lt;br&gt;이걸 하나하나 로컬에 직접 설치하면 버전 충돌, 환경 차이, 삭제 시 찌꺼기 등의 문제가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt;는 각 프로그램을 격리된 컨테이너에서 실행하는 도구다.&lt;br&gt;&lt;strong&gt;Docker Compose&lt;/strong&gt;는 여러 컨테이너를 &lt;code&gt;docker-compose.yml&lt;/code&gt; 파일 하나로 관리할 수 있게 해준다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker-compose up -d    # 전부 실행
docker-compose down     # 전부 종료&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;  &lt;code&gt;docker-compose.yml&lt;/code&gt;을 수정한 뒤 &lt;code&gt;docker-compose up -d&lt;/code&gt;를 다시 실행하면,&lt;br&gt;변경된 컨테이너만 재시작되고 기존 컨테이너는 유지된다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;1. PostgreSQL 컨테이너 세팅&lt;/h2&gt;
&lt;p&gt;가장 먼저 분석 결과를 저장할 PostgreSQL을 띄웠다.&lt;/p&gt;
&lt;h3&gt;docker-compose.yml (PostgreSQL만)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  postgres:
    image: postgres:15
    container_name: travel-postgres
    ports:
      - &amp;quot;5432:5432&amp;quot;
    environment:
      POSTGRES_DB: travel_db
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: admin1234
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;주요 설정 설명&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image: postgres:15&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL 15 버전 이미지 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ports: &amp;quot;5432:5432&amp;quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;호스트의 5432 포트를 컨테이너의 5432 포트에 매핑. Spring Boot에서 &lt;code&gt;localhost:5432&lt;/code&gt;로 접속 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;environment&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DB 이름, 사용자, 비밀번호를 환경변수로 설정. 컨테이너 최초 실행 시 자동 생성됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;volumes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;컨테이너를 종료해도 데이터가 유지되도록 볼륨 마운트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;동작 확인&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 실행
docker-compose up -d

# PostgreSQL 접속
docker exec -it travel-postgres psql -U admin -d travel_db

# 데이터베이스 목록 확인
\l

# 종료
\q&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;travel_db&lt;/code&gt;가 목록에 표시되면 성공이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. Hadoop (HDFS) 컨테이너 세팅&lt;/h2&gt;
&lt;p&gt;사용자 로그를 대량으로 적재할 HDFS를 추가했다.&lt;/p&gt;
&lt;h3&gt;HDFS란?&lt;/h3&gt;
&lt;p&gt;HDFS(Hadoop Distributed File System)는 대용량 파일을 분산 저장하는 파일 시스템이다.&lt;br&gt;두 가지 역할로 구성된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NameNode&lt;/strong&gt; — 파일이 어디에 저장되어 있는지 관리하는 메타데이터 서버 (전화번호부 역할)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DataNode&lt;/strong&gt; — 실제 데이터가 저장되는 스토리지 노드 (창고 역할)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;docker-compose.yml에 추가&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;  namenode:
    image: bde2020/hadoop-namenode:2.0.0-hadoop3.2.1-java8
    container_name: travel-namenode
    ports:
      - &amp;quot;9870:9870&amp;quot;
      - &amp;quot;9000:9000&amp;quot;
    environment:
      CLUSTER_NAME: travel-cluster
      CORE_CONF_fs_defaultFS: hdfs://namenode:9000
    volumes:
      - namenode_data:/hadoop/dfs/name

  datanode:
    image: bde2020/hadoop-datanode:2.0.0-hadoop3.2.1-java8
    container_name: travel-datanode
    depends_on:
      - namenode
    environment:
      SERVICE_PRECONDITION: &amp;quot;namenode:9870&amp;quot;
      CORE_CONF_fs_defaultFS: hdfs://namenode:9000
    volumes:
      - datanode_data:/hadoop/dfs/data&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;주요 설정 설명&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;9870&lt;/code&gt; 포트&lt;/td&gt;
&lt;td&gt;Hadoop 웹 관리 화면 (&lt;code&gt;http://localhost:9870&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;9000&lt;/code&gt; 포트&lt;/td&gt;
&lt;td&gt;HDFS 파일 읽기/쓰기용 포트 (Spring Boot, Spark에서 사용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CORE_CONF_fs_defaultFS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HDFS의 기본 파일시스템 주소 설정. 이 설정이 없으면 Windows 환경에서 경로 인식 에러 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;depends_on&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DataNode는 NameNode가 먼저 실행된 후에 시작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SERVICE_PRECONDITION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;NameNode가 완전히 준비될 때까지 DataNode가 대기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;동작 확인&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 실행
docker-compose up -d

# 웹 브라우저에서 확인
# http://localhost:9870 접속 → Hadoop Overview 화면이 뜨면 성공

# HDFS에 테스트 파일 저장
docker exec -it travel-namenode bash -c &amp;quot;echo &amp;#39;hello hdfs&amp;#39; &amp;gt; /tmp/test.txt &amp;amp;&amp;amp; hdfs dfs -mkdir -p /test &amp;amp;&amp;amp; hdfs dfs -put /tmp/test.txt /test/&amp;quot;

# 파일 확인
docker exec -it travel-namenode hdfs dfs -ls hdfs://namenode:9000/test/&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;⚠️ &lt;strong&gt;Windows Git Bash 사용 시 주의&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Git Bash는 &lt;code&gt;/&lt;/code&gt;를 Windows 경로(&lt;code&gt;C:\&lt;/code&gt;)로 자동 변환하는 이슈가 있다.&lt;br&gt;&lt;code&gt;hdfs dfs -ls /&lt;/code&gt; 명령어 실행 시 &lt;code&gt;No FileSystem for scheme &amp;quot;C&amp;quot;&lt;/code&gt; 에러가 발생하면,&lt;br&gt;전체 HDFS 주소를 명시하면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 에러 발생
docker exec -it travel-namenode hdfs dfs -ls /

# 해결 방법 1: 전체 주소 명시
docker exec -it travel-namenode hdfs dfs -ls hdfs://namenode:9000/

# 해결 방법 2: 경로 변환 비활성화
MSYS_NO_PATHCONV=1 docker exec -it travel-namenode hdfs dfs -ls /&lt;/code&gt;&lt;/pre&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;hr&gt;
&lt;h2&gt;3. Spark 컨테이너 세팅&lt;/h2&gt;
&lt;p&gt;HDFS에 적재된 로그 데이터를 분석할 Spark를 추가했다.&lt;/p&gt;
&lt;h3&gt;Spark란?&lt;/h3&gt;
&lt;p&gt;Spark는 대용량 데이터를 여러 노드에서 병렬로 분석하는 분산 처리 엔진이다.&lt;br&gt;이 프로젝트에서는 매일 새벽 4시에 HDFS의 사용자 로그를 읽어 군집 분석(성별, 나이, 국적 기반 그룹핑)을 수행한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Master&lt;/strong&gt; — 작업을 분배하는 관리 노드&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Worker&lt;/strong&gt; — 실제 연산을 수행하는 실행 노드&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;docker-compose.yml에 추가&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;  spark-master:
    image: bde2020/spark-master:3.1.1-hadoop3.2
    container_name: travel-spark-master
    ports:
      - &amp;quot;8080:8080&amp;quot;
      - &amp;quot;7077:7077&amp;quot;
    environment:
      CORE_CONF_fs_defaultFS: hdfs://namenode:9000

  spark-worker:
    image: bde2020/spark-worker:3.1.1-hadoop3.2
    container_name: travel-spark-worker
    depends_on:
      - spark-master
    environment:
      SPARK_MASTER: spark://spark-master:7077
      CORE_CONF_fs_defaultFS: hdfs://namenode:9000&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;주요 설정 설명&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;8080&lt;/code&gt; 포트&lt;/td&gt;
&lt;td&gt;Spark 웹 관리 화면 (&lt;code&gt;http://localhost:8080&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;7077&lt;/code&gt; 포트&lt;/td&gt;
&lt;td&gt;Master-Worker 간 내부 통신 포트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SPARK_MASTER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Worker에게 Master의 주소를 알려주는 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CORE_CONF_fs_defaultFS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Spark가 HDFS에 접근할 수 있도록 주소 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;동작 확인&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 실행
docker-compose up -d

# 컨테이너 상태 확인 (5개 모두 Up 상태여야 함)
docker ps

# 웹 브라우저에서 확인
# http://localhost:8080 접속 → Spark Master 화면에서 Workers(1) 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Status가 &lt;code&gt;ALIVE&lt;/code&gt;이고 Workers가 1로 표시되면 성공이다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;최종 docker-compose.yml&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  postgres:
    image: postgres:15
    container_name: travel-postgres
    ports:
      - &amp;quot;5432:5432&amp;quot;
    environment:
      POSTGRES_DB: travel_db
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: admin1234
    volumes:
      - postgres_data:/var/lib/postgresql/data

  namenode:
    image: bde2020/hadoop-namenode:2.0.0-hadoop3.2.1-java8
    container_name: travel-namenode
    ports:
      - &amp;quot;9870:9870&amp;quot;
      - &amp;quot;9000:9000&amp;quot;
    environment:
      CLUSTER_NAME: travel-cluster
      CORE_CONF_fs_defaultFS: hdfs://namenode:9000
    volumes:
      - namenode_data:/hadoop/dfs/name

  datanode:
    image: bde2020/hadoop-datanode:2.0.0-hadoop3.2.1-java8
    container_name: travel-datanode
    depends_on:
      - namenode
    environment:
      SERVICE_PRECONDITION: &amp;quot;namenode:9870&amp;quot;
      CORE_CONF_fs_defaultFS: hdfs://namenode:9000
    volumes:
      - datanode_data:/hadoop/dfs/data

  spark-master:
    image: bde2020/spark-master:3.1.1-hadoop3.2
    container_name: travel-spark-master
    ports:
      - &amp;quot;8080:8080&amp;quot;
      - &amp;quot;7077:7077&amp;quot;
    environment:
      CORE_CONF_fs_defaultFS: hdfs://namenode:9000

  spark-worker:
    image: bde2020/spark-worker:3.1.1-hadoop3.2
    container_name: travel-spark-worker
    depends_on:
      - spark-master
    environment:
      SPARK_MASTER: spark://spark-master:7077
      CORE_CONF_fs_defaultFS: hdfs://namenode:9000

volumes:
  postgres_data:
  namenode_data:
  datanode_data:&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;관리 대시보드 접속 정보&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;서비스&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;th&gt;용도&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Hadoop HDFS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://localhost:9870&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HDFS 상태 및 파일 시스템 모니터링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spark Master&lt;/td&gt;
&lt;td&gt;&lt;code&gt;http://localhost:8080&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Spark 작업 상태 및 Worker 모니터링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;localhost:5432&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DB 클라이언트 접속 (DBeaver 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;재밌다 빅데이터 분산하길 잘했다.&lt;/h2&gt;</description>
      <category>  빅데이터 분산</category>
      <category>docker</category>
      <category>빅데이터 분산</category>
      <author>RTD</author>
      <guid isPermaLink="true">https://roadtodeveloper.tistory.com/17</guid>
      <comments>https://roadtodeveloper.tistory.com/17#entry17comment</comments>
      <pubDate>Mon, 16 Mar 2026 00:27:21 +0900</pubDate>
    </item>
    <item>
      <title>[B형 기출] 커피점&amp;amp;제과점</title>
      <link>https://roadtodeveloper.tistory.com/16</link>
      <description>&lt;h1&gt;커피점·제과점 최적 주택 찾기 — 다중 소스 다익스트라 풀이&lt;/h1&gt;
&lt;h2&gt;1. 문제 요약&lt;/h2&gt;
&lt;p&gt;N개의 건물(0 ~ N-1)이 양방향 도로로 연결된 그래프가 주어진다. 도로에는 거리 정보가 있고, 실시간으로 새로운 도로가 추가될 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;calculate&lt;/code&gt; 호출 시 M개의 커피점 건물, P개의 제과점 건물, 제한 거리 R이 주어지면, 커피점도 제과점도 아닌 &lt;strong&gt;주택&lt;/strong&gt; 중에서 다음 조건을 만족하는 건물을 찾아야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;가장 가까운 커피점까지의 최단 거리 ≤ R&lt;/li&gt;
&lt;li&gt;가장 가까운 제과점까지의 최단 거리 ≤ R&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;조건을 만족하는 주택이 여러 개라면, &lt;strong&gt;(커피점 최단 거리 + 제과점 최단 거리)의 최솟값&lt;/strong&gt;을 반환한다. 조건을 만족하는 주택이 없으면 -1을 반환한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;2. Main 코드 분석 — 입력 구조와 채점 로직 이해&lt;/h2&gt;
&lt;p&gt;제출 시스템에서는 &lt;code&gt;main.py&lt;/code&gt;가 입력을 파싱하고, &lt;code&gt;solution.py&lt;/code&gt;의 함수들을 호출한다. main 코드를 분석하면 문제의 숨겨진 제약조건과 호출 패턴을 파악할 수 있다.&lt;/p&gt;
&lt;h3&gt;2.1 전체 입력 구조&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;T score            ← 테스트 케이스 수, 배점
num_commands       ← 이번 TC의 명령 수
cmd                ← 100(init), 200(add), 300(calculate)
...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;첫 줄에 테스트 케이스 수 &lt;code&gt;T&lt;/code&gt;와 배점 &lt;code&gt;score&lt;/code&gt;가 주어진다. 각 TC마다 명령의 수 &lt;code&gt;num_commands&lt;/code&gt;가 주어지고, 이후 명령 코드에 따라 분기한다.&lt;/p&gt;
&lt;h3&gt;2.2 명령 코드별 분석&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;cmd = 100 (init)&lt;/strong&gt; — 그래프 초기화&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;n, k = map(int, input().split())  # 건물 수, 도로 수
for i in range(k):
    s, e, d = map(int, input().split())  # 시작, 끝, 거리
init(n, k, sB, eB, mD)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TC 시작 시 한 번 호출된다. N개의 건물과 K개의 초기 도로가 주어진다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;cmd = 200 (add)&lt;/strong&gt; — 도로 추가&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;s, e, d = map(int, input().split())
add(s, e, d)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;기존 그래프에 새로운 양방향 도로를 하나 추가한다. 한 TC당 &lt;strong&gt;최대 2,000번&lt;/strong&gt; 호출된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;cmd = 300 (calculate)&lt;/strong&gt; — 최적 주택 탐색&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;m, p, r = map(int, input().split())  # 커피점 수, 제과점 수, 제한거리
mCoffee = [int(input()) for _ in range(m)]
mBakery = [int(input()) for _ in range(p)]
expected = int(input())              # 정답
ret = calculate(m, mCoffee, p, mBakery, r)
if ret != expected:
    result = 0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;핵심은 마지막 줄의 &lt;code&gt;expected&lt;/code&gt;다. main 코드가 정답을 읽어서 &lt;code&gt;calculate&lt;/code&gt;의 반환값과 비교한다. 하나라도 틀리면 해당 TC 전체가 0점이 된다. 한 TC당 &lt;strong&gt;최대 100번&lt;/strong&gt; 호출된다.&lt;/p&gt;
&lt;h3&gt;2.3 Main 분석에서 얻는 제약조건 정리&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;파라미터&lt;/th&gt;
&lt;th&gt;범위&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;N (건물 수)&lt;/td&gt;
&lt;td&gt;6 ~ 10,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;K (초기 도로 수)&lt;/td&gt;
&lt;td&gt;6 ~ 30,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;add() 호출&lt;/td&gt;
&lt;td&gt;TC당 최대 2,000회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;calculate() 호출&lt;/td&gt;
&lt;td&gt;TC당 최대 100회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M, P (커피점/제과점 수)&lt;/td&gt;
&lt;td&gt;1 ~ 1,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mDistance (도로 거리)&lt;/td&gt;
&lt;td&gt;1 ~ 1,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R (제한 거리)&lt;/td&gt;
&lt;td&gt;50 ~ 250,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;도로가 최대 K + 2,000 = &lt;strong&gt;32,000개&lt;/strong&gt;까지 늘어날 수 있다. 이 정보는 시간복잡도 분석에 핵심이 된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;3. 시간복잡도를 고려한 풀이 전략 도출&lt;/h2&gt;
&lt;h3&gt;3.1 나이브한 접근 — 왜 안 되는가?&lt;/h3&gt;
&lt;p&gt;가장 직관적인 방법은 각 커피점에서 다익스트라를 돌리고, 각 제과점에서도 다익스트라를 돌리는 것이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;커피점 M개 × 다익스트라 = M × O(E log N)&lt;/li&gt;
&lt;li&gt;제과점 P개 × 다익스트라 = P × O(E log N)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;M, P가 최대 1,000이고, E가 최대 32,000, N이 최대 10,000이므로, 한 번의 &lt;code&gt;calculate&lt;/code&gt; 호출에 약 &lt;strong&gt;1,000 × 32,000 × 14 ≈ 4.5억&lt;/strong&gt; 연산이 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;calculate&lt;/code&gt;가 최대 100번 호출되므로 총 &lt;strong&gt;450억 연산&lt;/strong&gt;. 파이썬은 물론이고 C++에서도 시간 내에 통과하기 어렵다.&lt;/p&gt;
&lt;h3&gt;3.2 핵심 아이디어 — 다중 소스 다익스트라 (Multi-Source Dijkstra)&lt;/h3&gt;
&lt;p&gt;우리가 필요한 것은 각 주택에서 &lt;strong&gt;&amp;quot;가장 가까운 커피점까지의 거리&amp;quot;&lt;/strong&gt; 와 &lt;strong&gt;&amp;quot;가장 가까운 제과점까지의 거리&amp;quot;&lt;/strong&gt; 이다. 이것은 개별 소스에서의 거리가 아니라, &lt;strong&gt;소스 집합에서의 최단 거리&lt;/strong&gt;다.&lt;/p&gt;
&lt;p&gt;다중 소스 다익스트라는 이 문제를 정확히 해결한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원리&lt;/strong&gt;: 가상의 슈퍼 소스 노드 S를 만들고, S에서 모든 커피점으로 거리 0인 간선을 연결한다고 상상해보자. S에서 다익스트라를 돌리면, 각 노드까지의 최단 거리는 곧 &amp;quot;가장 가까운 커피점까지의 거리&amp;quot;가 된다.&lt;/p&gt;
&lt;p&gt;실제 구현에서는 가상 노드를 만들 필요 없이, 모든 소스 노드를 &lt;strong&gt;거리 0으로 힙에 넣고 시작&lt;/strong&gt;하면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;일반 다익스트라:  힙 = [(0, source)]        ← 소스 1개
다중 소스:       힙 = [(0, c1), (0, c2), ...] ← 소스 M개&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이렇게 하면 다익스트라가 자연스럽게 각 노드에서 가장 가까운 소스까지의 거리를 구해준다.&lt;/p&gt;
&lt;h3&gt;3.3 최종 전략과 시간복잡도&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;calculate 1회 호출 시:
  1. 다중 소스 다익스트라 (커피점 전체 → 모든 노드)  : O(E log N)
  2. 다중 소스 다익스트라 (제과점 전체 → 모든 노드)  : O(E log N)
  3. 모든 주택 순회하며 최솟값 탐색                  : O(N)
  합계: O(E log N)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;한 번의 &lt;code&gt;calculate&lt;/code&gt;에 &lt;strong&gt;O(E log N) = 32,000 × 14 ≈ 45만 연산&lt;/strong&gt;. 100번 호출해도 약 &lt;strong&gt;4,500만 연산&lt;/strong&gt;으로, 충분히 빠르다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;접근법&lt;/th&gt;
&lt;th&gt;calculate 1회&lt;/th&gt;
&lt;th&gt;100회 호출 시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;나이브 (개별 다익스트라)&lt;/td&gt;
&lt;td&gt;O((M+P) × E log N) ≈ 9억&lt;/td&gt;
&lt;td&gt;900억&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;다중 소스 다익스트라&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;O(E log N) ≈ 45만&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4,500만&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;약 &lt;strong&gt;2,000배 차이&lt;/strong&gt;가 난다.&lt;/p&gt;
&lt;h3&gt;3.4 추가 최적화 — R을 이용한 조기 종료&lt;/h3&gt;
&lt;p&gt;다익스트라 진행 중 힙에서 꺼낸 거리가 R을 초과하면, 최소 힙의 특성상 이후에 꺼낼 값들도 모두 R 이상이다. 따라서 &lt;code&gt;break&lt;/code&gt;로 탐색을 즉시 종료할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;if d &amp;gt; R:
    break  # 이후 힙의 모든 원소는 d 이상 → R 초과 확정&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;R이 작을 때 탐색 범위가 대폭 줄어들어 실측 성능이 크게 개선된다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;4. Solution 코드 상세 설명&lt;/h2&gt;
&lt;h3&gt;4.1 전역 변수와 그래프 구조&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from heapq import heappush, heappop

N = 0       # 건물 수
adj = []    # 인접 리스트: adj[u] = [(v, w), ...]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그래프를 &lt;strong&gt;인접 리스트&lt;/strong&gt;로 표현한다. &lt;code&gt;adj[u]&lt;/code&gt;에는 &lt;code&gt;(인접 노드, 거리)&lt;/code&gt; 튜플의 리스트가 저장된다. 인접 행렬 대비 메모리 효율적이며, 간선 순회 시 불필요한 탐색이 없다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;heapq&lt;/code&gt;에서 &lt;code&gt;heappush&lt;/code&gt;, &lt;code&gt;heappop&lt;/code&gt;을 직접 import하는 것은 파이썬 최적화 테크닉이다. &lt;code&gt;heapq.heappush()&lt;/code&gt;처럼 모듈 경유 호출보다 함수 직접 호출이 약간 더 빠르다.&lt;/p&gt;
&lt;h3&gt;4.2 init — 그래프 초기화&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def init(n, k, sBuilding, eBuilding, mDistance):
    global N, adj
    N = n
    adj = [[] for _ in range(N)]
    for i in range(k):
        u = sBuilding[i]
        v = eBuilding[i]
        w = mDistance[i]
        adj[u].append((v, w))
        adj[v].append((u, w))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;양방향 도로이므로 &lt;code&gt;adj[u]&lt;/code&gt;와 &lt;code&gt;adj[v]&lt;/code&gt; 양쪽에 간선을 추가한다. 매 TC마다 호출되어 그래프를 완전히 새로 구성한다.&lt;/p&gt;
&lt;h3&gt;4.3 add — 도로 추가&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def add(sBuilding, eBuilding, mDistance):
    adj[sBuilding].append((eBuilding, mDistance))
    adj[eBuilding].append((sBuilding, mDistance))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O(1)로 간선을 추가한다. 문제 조건상 이미 존재하는 간선이 다시 주어지지 않으므로 중복 체크는 불필요하다.&lt;/p&gt;
&lt;h3&gt;4.4 _dijkstra — 다중 소스 다익스트라의 핵심&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def _dijkstra(sources, R):
    INF = float(&amp;#39;inf&amp;#39;)
    dist = [INF] * N          # ① 거리 배열 초기화
    hq = []
    for c in sources:          # ② 모든 소스를 거리 0으로 힙에 삽입
        dist[c] = 0
        heappush(hq, (0, c))
    _adj = adj                 # ③ 로컬 변수로 참조 (파이썬 속도 최적화)
    while hq:
        d, u = heappop(hq)    # ④ 최소 거리 노드 추출
        if d &amp;gt; dist[u]:        # ⑤ 이미 더 짧은 경로로 방문됨 → 스킵
            continue
        if d &amp;gt; R:              # ⑥ R 초과 → 조기 종료
            break
        for v, w in _adj[u]:   # ⑦ 인접 노드 완화 (relaxation)
            nd = d + w
            if nd &amp;lt; dist[v]:
                dist[v] = nd
                heappush(hq, (nd, v))
    return dist&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 단계를 자세히 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;① 거리 배열 초기화&lt;/strong&gt;: 모든 노드의 거리를 무한대로 설정한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② 다중 소스 삽입&lt;/strong&gt;: 일반 다익스트라와의 유일한 차이점이다. 소스가 여러 개일 때, 모든 소스를 거리 0으로 힙에 넣는다. 이렇게 하면 다익스트라가 &amp;quot;가장 가까운 소스로부터의 거리&amp;quot;를 자동으로 계산한다.&lt;/p&gt;
&lt;p&gt;직관적 이해를 위해 커피점이 {A, B}인 경우를 생각해보자. 노드 X까지의 거리가 dist(A→X) = 30, dist(B→X) = 20이라면, B가 먼저 X에 도달하여 dist[X] = 20이 된다. 이후 A에서 X로 가는 경로(30)가 힙에서 꺼내져도, 이미 dist[X] = 20이므로 ⑤에서 스킵된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③ 로컬 변수 참조&lt;/strong&gt;: &lt;code&gt;_adj = adj&lt;/code&gt;는 전역 변수 &lt;code&gt;adj&lt;/code&gt;를 로컬 변수 &lt;code&gt;_adj&lt;/code&gt;에 바인딩한다. 파이썬에서 전역 변수 접근은 로컬 변수보다 느리다. 반복문 내부에서 수만 번 접근하므로 체감 차이가 생긴다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑤ 중복 방문 스킵&lt;/strong&gt;: 파이썬의 &lt;code&gt;heapq&lt;/code&gt;는 decrease-key 연산을 지원하지 않는다. 대신 새 (거리, 노드) 쌍을 힙에 추가하고, 꺼낼 때 현재 dist보다 크면 무시하는 &lt;strong&gt;lazy deletion&lt;/strong&gt; 전략을 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑥ 조기 종료&lt;/strong&gt;: 최소 힙에서 꺼낸 거리가 R을 초과하면, 힙에 남아 있는 모든 원소의 거리도 R 이상이다. 어차피 R 초과인 노드는 결과에 영향을 주지 않으므로 즉시 &lt;code&gt;break&lt;/code&gt;한다.&lt;/p&gt;
&lt;h3&gt;4.5 calculate — 결과 조합&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def calculate(M, mCoffee, P, mBakery, R):
    dist_coffee = _dijkstra(mCoffee, R)   # ① 커피점들로부터의 최단 거리
    dist_bakery = _dijkstra(mBakery, R)   # ② 제과점들로부터의 최단 거리

    exclude = [False] * N                  # ③ 커피점/제과점 제외 마스크
    for c in mCoffee:
        exclude[c] = True
    for b in mBakery:
        exclude[b] = True

    ans = float(&amp;#39;inf&amp;#39;)
    for v in range(N):                     # ④ 모든 주택 순회
        if exclude[v]:                     #    커피점/제과점이면 스킵
            continue
        dc = dist_coffee[v]
        if dc &amp;gt; R:                         #    커피점 거리 조건 불만족 → 스킵
            continue
        db = dist_bakery[v]
        if db &amp;gt; R:                         #    제과점 거리 조건 불만족 → 스킵
            continue
        s = dc + db
        if s &amp;lt; ans:
            ans = s

    return ans if ans != float(&amp;#39;inf&amp;#39;) else -1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;① ~ ②&lt;/strong&gt;: 다익스트라를 정확히 2번 실행한다. &lt;code&gt;dist_coffee[v]&lt;/code&gt;는 노드 v에서 가장 가까운 커피점까지의 최단 거리, &lt;code&gt;dist_bakery[v]&lt;/code&gt;는 가장 가까운 제과점까지의 최단 거리다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③ 제외 마스크&lt;/strong&gt;: &lt;code&gt;set&lt;/code&gt; 대신 &lt;code&gt;boolean 배열&lt;/code&gt;을 사용했다. 파이썬에서 &lt;code&gt;set&lt;/code&gt;의 &lt;code&gt;in&lt;/code&gt; 연산은 평균 O(1)이지만 해시 오버헤드가 있다. boolean 배열은 인덱스로 직접 접근하므로 상수 시간이 더 작다. N이 최대 10,000이므로 메모리 부담도 없다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;④ 주택 순회&lt;/strong&gt;: 조건 검사 순서도 최적화의 일부다. &lt;code&gt;exclude&lt;/code&gt; 체크 → &lt;code&gt;dc &amp;gt; R&lt;/code&gt; 체크 → &lt;code&gt;db &amp;gt; R&lt;/code&gt; 체크 순으로 &lt;strong&gt;빠르게 탈락시킬 수 있는 조건을 먼저&lt;/strong&gt; 검사한다. 특히 &lt;code&gt;dc &amp;gt; R&lt;/code&gt;을 먼저 검사하여, 커피점에서 너무 먼 노드는 &lt;code&gt;db&lt;/code&gt;를 조회하지도 않는다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;5. 예제 트레이스로 검증&lt;/h2&gt;
&lt;p&gt;문제에서 제시된 순서 5를 따라가 본다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;calculate(1, {0}, 1, {5}, 50)&lt;/code&gt; — 도로 추가 후 (3-4: 30, 1-4: 30)&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;strong&gt;커피점 다익스트라 (소스: {0})&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;노드&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;거리&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;경로: 0→2→1 (30), 0→2 (30), 0→2→1→3 (40), 0→2→1→4 (60), 0→2→1→4→5 (50)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;제과점 다익스트라 (소스: {5})&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;노드&lt;/th&gt;
&lt;th&gt;0&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;거리&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;경로: 5→4→1 (50), 5→3 (60→50은 아님, 5→4→3이 50), 5→4 (20)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;주택 판별&lt;/strong&gt;: 0(커피점), 5(제과점) 제외 → 주택은 {1, 2, 3, 4}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;조건 검사 (R=50)&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;주택&lt;/th&gt;
&lt;th&gt;dc&lt;/th&gt;
&lt;th&gt;db&lt;/th&gt;
&lt;th&gt;dc≤50?&lt;/th&gt;
&lt;th&gt;db≤50?&lt;/th&gt;
&lt;th&gt;dc+db&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;90&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;최솟값은 80... 이 아니라 문제 예시의 답은 90이다.&lt;/p&gt;
&lt;p&gt;잠깐, 도로를 다시 확인해보자. 순서 4에서 1-4 도로(거리 30)가 추가되었으므로:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0→1: 초기 도로 0-1 (80), 또는 0→2→1 = 30+10 = 40. 즉 dist(0,1) = 40&lt;/li&gt;
&lt;li&gt;0→3: 0→2→1→3 = 30+10+10 = 50&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다시 계산하면:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;노드&lt;/th&gt;
&lt;th&gt;dist from 0&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;40 (0→2→1: 30+10)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;30 (0→2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;50 (0→2→1→3: 30+10+10)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;70 (0→2→1→4: 30+10+30)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;50 (0→2→1→4→5: 30+10+30+20=90? → 또는 0→2→1→3→5: 30+10+10+60=110)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;정리하면 dist(0, 5)의 최단은 0→2(30)→1(10)→4(30)→5(20) = 90? 아, 여기서 초기 도로를 다시 보자.&lt;/p&gt;
&lt;p&gt;초기 도로: (0,1,80), (2,0,30), (1,2,10), (1,3,10), (5,3,60), (4,5,20)&lt;br&gt;추가 도로: (3,4,30), (1,4,30)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dist(0,1) = min(80, 0→2→1 = 30+10 = 40) = &lt;strong&gt;40&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;dist(0,3) = 0→2→1→3 = 40+10 = &lt;strong&gt;50&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;dist(0,4) = min(0→2→1→4 = 40+30, 0→2→1→3→4 = 50+30) = &lt;strong&gt;70&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;dist(0,5) = min(0→...→4→5 = 70+20, 0→...→3→5 = 50+60) = &lt;strong&gt;90&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;노드&lt;/th&gt;
&lt;th&gt;dist from 5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;50 (5→4→3: 20+30 = 50, 또는 5→3: 60) → &lt;strong&gt;50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;50 (5→4→1: 20+30 = 50, 또는 5→3→1: 60+10 = 70) → &lt;strong&gt;50&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;60 (5→4→1→2: 50+10 = 60)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;70 (5→4→1→2→0: 60+30=90, 또는 5→4→1→0: 50+80=130...) → 아 0→2가 30이므로 0←2도 30: 60+30 = &lt;strong&gt;90&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;주택 판별 (0, 5 제외):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;주택&lt;/th&gt;
&lt;th&gt;dc (from 0)&lt;/th&gt;
&lt;th&gt;db (from 5)&lt;/th&gt;
&lt;th&gt;dc≤50?&lt;/th&gt;
&lt;th&gt;db≤50?&lt;/th&gt;
&lt;th&gt;dc+db&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;90&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;100&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;최솟값 = &lt;strong&gt;90&lt;/strong&gt;. 정답과 일치한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;6. 파이썬 최적화 포인트 정리&lt;/h2&gt;
&lt;p&gt;이 문제는 C/C++ 기준으로 설계되어 있어, 파이썬에서는 상수 최적화가 중요하다. 적용된 최적화들을 정리한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 모듈 함수 직접 import&lt;/strong&gt;: &lt;code&gt;from heapq import heappush, heappop&lt;/code&gt;으로 함수를 직접 가져온다. &lt;code&gt;heapq.heappush()&lt;/code&gt; 대비 속성 조회 오버헤드를 제거한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 전역 → 로컬 바인딩&lt;/strong&gt;: &lt;code&gt;_adj = adj&lt;/code&gt;로 전역 변수를 로컬에 바인딩한다. CPython에서 로컬 변수 접근은 &lt;code&gt;LOAD_FAST&lt;/code&gt;, 전역은 &lt;code&gt;LOAD_GLOBAL&lt;/code&gt; 바이트코드를 사용하며, 전자가 더 빠르다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 조기 종료&lt;/strong&gt;: R 제한을 활용한 &lt;code&gt;break&lt;/code&gt;는 탐색 공간을 대폭 줄인다. 특히 R이 작을 때 효과적이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 조건 검사 순서&lt;/strong&gt;: &lt;code&gt;exclude&lt;/code&gt; → &lt;code&gt;dc &amp;gt; R&lt;/code&gt; → &lt;code&gt;db &amp;gt; R&lt;/code&gt; 순으로 빠른 탈락을 유도한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. boolean 배열 vs set&lt;/strong&gt;: &lt;code&gt;exclude&lt;/code&gt;를 &lt;code&gt;set&lt;/code&gt;이 아닌 &lt;code&gt;list[bool]&lt;/code&gt;로 구현하여 해시 오버헤드를 제거했다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;7. 정리&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;내용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;핵심 알고리즘&lt;/td&gt;
&lt;td&gt;다중 소스 다익스트라 (Multi-Source Dijkstra)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시간복잡도&lt;/td&gt;
&lt;td&gt;calculate 1회당 O(E log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;공간복잡도&lt;/td&gt;
&lt;td&gt;O(N + E)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;핵심 최적화&lt;/td&gt;
&lt;td&gt;소스 집합을 한 번에 처리 / R 기반 조기 종료&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;다중 소스 다익스트라의 핵심은 &lt;strong&gt;&amp;quot;여러 소스에서 각각 돌리지 말고, 전부 거리 0으로 힙에 넣고 한 번만 돌리자&amp;quot;&lt;/strong&gt; 는 발상이다. 이 패턴은 &amp;quot;가장 가까운 X까지의 거리&amp;quot;를 구하는 모든 문제에 적용할 수 있으므로 꼭 익혀두자.&lt;/p&gt;</description>
      <category>  Algorithm</category>
      <author>RTD</author>
      <guid isPermaLink="true">https://roadtodeveloper.tistory.com/16</guid>
      <comments>https://roadtodeveloper.tistory.com/16#entry16comment</comments>
      <pubDate>Mon, 9 Mar 2026 16:09:00 +0900</pubDate>
    </item>
    <item>
      <title>[Linux] Shell Script 이해</title>
      <link>https://roadtodeveloper.tistory.com/15</link>
      <description>&lt;h1&gt;  리눅스 Shell Script &amp;amp; 패키지 관리 완벽 정리&lt;/h1&gt;
&lt;h2&gt;1. Shell &amp;amp; Script 기초&lt;/h2&gt;
&lt;h3&gt;  Shell의 종류&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bash (Bourne Again SHell)&lt;/strong&gt;: 우분투 기본 CLI 쉘, 리눅스 사용자에게 가장 인기 있음.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dash (Debian Almquist SHell)&lt;/strong&gt;: 임베디드 리눅스에서 주로 사용 (작은 용량).&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;확인 방법&lt;/strong&gt;:&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  cat /etc/passwd | grep ssafy   # 사용 중인 쉘 확인
  cat /etc/shells                # 설치된 모든 쉘 확인
  du -h /bin/bash /bin/dash      # 용량 비교&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;쉘 변경 (실습)&lt;/strong&gt;: &lt;code&gt;sudo chsh [계정명] -s /bin/dash&lt;/code&gt; 후 재부팅.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  Shell Script 개요&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;목적&lt;/strong&gt;: 반복적인 작업을 자동화하기 위함 (예: 백업, 초기 세팅 등).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확장자&lt;/strong&gt;: &lt;code&gt;.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;기본 구조&lt;/strong&gt;:&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  #!/bin/bash    # (Shebang) 스크립트를 실행할 쉘 지정
  # 내용 작성&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;실행 방법&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;source test.sh&lt;/code&gt;: 현재 쉘 환경에서 실행 (가장 많이 사용).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;./test.sh&lt;/code&gt;: 실행 권한 필요 (&lt;code&gt;chmod +x&lt;/code&gt;), 별도 프로세스로 실행.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  문법 핵심&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;변수&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;선언: &lt;code&gt;NAME=value&lt;/code&gt; (띄어쓰기 금지, 모든 값은 문자열 취급).&lt;/li&gt;
&lt;li&gt;사용: &lt;code&gt;$NAME&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;산술 연산: &lt;code&gt;$(( 1 + 2 ))&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;조건문 (if)&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;if [ 조건 ]; then ... fi&lt;/code&gt; (대괄호 안쪽 띄어쓰기 필수).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비교 연산자&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-lt&lt;/code&gt; (&amp;lt;), &lt;code&gt;-gt&lt;/code&gt; (&amp;gt;), &lt;code&gt;-eq&lt;/code&gt; (==), &lt;code&gt;-ne&lt;/code&gt; (!=), &lt;code&gt;-ge&lt;/code&gt; (&amp;gt;=), &lt;code&gt;-le&lt;/code&gt; (&amp;lt;=)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-f&lt;/code&gt;: 파일 존재 여부 (Regular file)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-x&lt;/code&gt;: 실행 권한 여부&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;배열&lt;/strong&gt;: &lt;code&gt;ARR=(1 2 3)&lt;/code&gt; / 출력: &lt;code&gt;${ARR[0]}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;환경변수&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;export NAME=value&lt;/code&gt;: 현재 세션에 변수 등록 (재부팅 시 사라짐).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;~/.bashrc&lt;/code&gt;에 등록: 영구 등록 (터미널 실행 시 자동 적용).&lt;/li&gt;
&lt;li&gt;확인: &lt;code&gt;printenv | grep NAME&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;2. Crontab (스케줄링 자동화)&lt;/h2&gt;
&lt;h3&gt;  Cron Daemon&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;역할&lt;/strong&gt;: 백그라운드에서 주기적인 작업을 관리 및 수행.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상태 확인&lt;/strong&gt;: &lt;code&gt;service cron status&lt;/code&gt; / &lt;strong&gt;재시작&lt;/strong&gt;: &lt;code&gt;service cron restart&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  Crontab 설정&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;편집&lt;/strong&gt;: &lt;code&gt;sudo vi /etc/crontab&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;시간 설정 문법 (5자리)&lt;/strong&gt;:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;분&lt;/code&gt; &lt;code&gt;시&lt;/code&gt; &lt;code&gt;일&lt;/code&gt; &lt;code&gt;월&lt;/code&gt; &lt;code&gt;요일&lt;/code&gt; &lt;code&gt;사용자&lt;/code&gt; &lt;code&gt;명령어&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;* * * * *&lt;/code&gt;: 매 분마다 수행.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*/3 * * * *&lt;/code&gt;: 3분마다 수행.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0 7 * * *&lt;/code&gt;: 매일 아침 7시에 수행.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;실습 예제 (매 분 스크립트 실행)&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  # 1. 스크립트 권한 부여
  sudo chmod a+x /home/ssafy/test.sh

  # 2. crontab 등록
  * * * * * ssafy /home/ssafy/test.sh&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3. 리눅스 파일 압축 및 해제&lt;/h2&gt;
&lt;h3&gt;  압축 방식 비교&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;압축률&lt;/strong&gt;: &lt;code&gt;zip&lt;/code&gt; &amp;lt; &lt;code&gt;gz&lt;/code&gt; &amp;lt; &lt;code&gt;bz2&lt;/code&gt; &amp;lt; &lt;code&gt;xz&lt;/code&gt; (xz가 압축률 가장 높음, 속도 느림).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  명령어 정리&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;형식&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;압축하기&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;풀기&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;특징&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;zip&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;zip -r [파일.zip] [대상]&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;unzip [파일.zip] -d [경로]&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;널리 쓰임, 원본 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;gzip&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;gzip [파일]&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;gunzip [파일.gz]&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;단일 파일 압축, 원본 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;xz&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;xz [파일]&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;xz -d [파일.xz]&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;높은 압축률, 원본 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;tar&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;tar -cvf [파일.tar] [대상]&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;tar -xvf [파일.tar] -C [경로]&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;파일 묶기 (압축 아님)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;tar.xz&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;tar -Jcvf [파일.tar.xz] [대상]&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;tar -Jxvf [파일.tar.xz] -C [경로]&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;묶기+압축 (가장 많이 씀)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;4. 소프트웨어 설치 및 배포 방법&lt;/h2&gt;
&lt;h3&gt;  1. Binary 파일 설치 (Node.js 예시)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;특징&lt;/strong&gt;: 컴파일된 실행 파일을 다운로드하여 배치.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;과정&lt;/strong&gt;:&lt;ol&gt;
&lt;li&gt;&lt;code&gt;wget&lt;/code&gt;으로 파일 다운로드 (&lt;code&gt;tar.xz&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;압축 해제: &lt;code&gt;sudo tar -Jxvf node-v20...tar.xz -C /usr/local/lib/nodejs/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;심볼릭 링크&lt;/strong&gt; 생성 (어디서든 실행 가능하게):&lt;br&gt;&lt;code&gt;sudo ln -s /usr/local/lib/nodejs/.../bin/node /usr/bin/node&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;확인: &lt;code&gt;node -v&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  2. dpkg (Debian Package) 설치&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;특징&lt;/strong&gt;: &lt;code&gt;.deb&lt;/code&gt; 확장자 파일 설치. &lt;strong&gt;의존성 문제를 해결해주지 않음&lt;/strong&gt; (단점).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;명령어&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;설치: &lt;code&gt;sudo dpkg -i [파일.deb]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;삭제: &lt;code&gt;sudo dpkg -P [패키지명]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;보완&lt;/strong&gt;: 의존성 오류 발생 시 &lt;code&gt;apt&lt;/code&gt;를 사용하여 해결하거나 설치.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  3. Source Code 설치 (빌드)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;특징&lt;/strong&gt;: 소스코드를 직접 다운받아 내 컴퓨터 환경에 맞춰 컴파일. (시간 오래 걸림, 커스터마이징 가능).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;과정 (Unix 표준)&lt;/strong&gt;:&lt;ol&gt;
&lt;li&gt;필수 도구 설치: &lt;code&gt;gcc&lt;/code&gt;, &lt;code&gt;g++&lt;/code&gt;, &lt;code&gt;make&lt;/code&gt;, &lt;code&gt;python3&lt;/code&gt; 등.&lt;/li&gt;
&lt;li&gt;압축 해제 및 디렉토리 이동.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;설정&lt;/strong&gt;: &lt;code&gt;./configure&lt;/code&gt; (환경 설정).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;빌드&lt;/strong&gt;: &lt;code&gt;make -j4&lt;/code&gt; (컴파일, 4코어 사용).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;설치&lt;/strong&gt;: &lt;code&gt;sudo make install&lt;/code&gt; (시스템에 복사).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;삭제&lt;/strong&gt;: &lt;code&gt;sudo make uninstall&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Linux</category>
      <category>Linux</category>
      <category>script</category>
      <category>Shell</category>
      <author>RTD</author>
      <guid isPermaLink="true">https://roadtodeveloper.tistory.com/15</guid>
      <comments>https://roadtodeveloper.tistory.com/15#entry15comment</comments>
      <pubDate>Mon, 12 Jan 2026 16:59:24 +0900</pubDate>
    </item>
    <item>
      <title>[Linux] Build System 이해</title>
      <link>https://roadtodeveloper.tistory.com/14</link>
      <description>&lt;h1&gt; ️ Build System &amp;amp; Linux Command 학습 정리&lt;/h1&gt;
&lt;h2&gt;1. Build System 개요&lt;/h2&gt;
&lt;h3&gt;  빌드(Build)란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;정의&lt;/strong&gt;: 소스코드(&lt;code&gt;.c&lt;/code&gt;, &lt;code&gt;.cpp&lt;/code&gt;)를 실행 가능한 소프트웨어(&lt;code&gt;.elf&lt;/code&gt;, &lt;code&gt;.exe&lt;/code&gt;)로 변환하는 과정 또는 결과물.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build System&lt;/strong&gt;: 빌드에 필요한 여러 작업을 도와주는 프로그램 (예: &lt;code&gt;make&lt;/code&gt;, &lt;code&gt;cmake&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  GCC 기준 빌드 과정 (2단계)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Compile &amp;amp; Assemble&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;소스코드 파일을 0과 1로 구성된 &lt;strong&gt;Object 파일&lt;/strong&gt;(&lt;code&gt;.o&lt;/code&gt;)로 변환.&lt;/li&gt;
&lt;li&gt;명령어: &lt;code&gt;gcc -c ./main.c&lt;/code&gt; (➔ &lt;code&gt;main.o&lt;/code&gt; 생성)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linking&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;생성된 Object 파일들과 Library들을 모아 하나로 합침.&lt;/li&gt;
&lt;li&gt;명령어: &lt;code&gt;gcc ./main.o ./yellow.o -o ./go&lt;/code&gt; (➔ 실행 파일 &lt;code&gt;go&lt;/code&gt; 생성)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;  빌드 자동화의 필요성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bash Shell Script (&lt;code&gt;.sh&lt;/code&gt;)&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;source 파일명.sh&lt;/code&gt;로 빌드 가능.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단점&lt;/strong&gt;: 파일 하나만 수정해도 전체를 다시 빌드해야 함 (시간 소요 큼).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make Build System&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;파일 간의 &lt;strong&gt;의존성(Dependency)&lt;/strong&gt;을 추적하여, &lt;strong&gt;변경된 파일만 컴파일&lt;/strong&gt;함 (속도 최적화).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;2. Make (소프트웨어 빌드 자동화 도구)&lt;/h2&gt;
&lt;h3&gt;  특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Makefile&lt;/code&gt;이라는 특별한 형식의 파일을 사용.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;설치&lt;/strong&gt;: &lt;code&gt;sudo apt install make -y&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;실행&lt;/strong&gt;: &lt;code&gt;make&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  Makefile 문법&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Target (타겟)&lt;/strong&gt;: 빌드하려는 최종 결과물 (1개 이상 필수).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependency (의존성)&lt;/strong&gt;: Target을 만들기 위해 필요한 파일 목록.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Command (명령어)&lt;/strong&gt;: 실행할 명령어 (&lt;strong&gt;반드시 Tab으로 들여쓰기&lt;/strong&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  변수 (Variable)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;사용&lt;/strong&gt;: &lt;code&gt;$(변수명)&lt;/code&gt; 또는 &lt;code&gt;${변수명}&lt;/code&gt;. 가독성을 위해 상단에 정의.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;할당 연산자&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;=&lt;/code&gt;: 코드 전체 기준 최종값 (지연 할당).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:=&lt;/code&gt;: 현재 위치 기준 값 (즉시 할당).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+=&lt;/code&gt;: 기존 값에 추가.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;특수 변수 (Automatic Variables)&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$@&lt;/code&gt;: Target 이름.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$^&lt;/code&gt;: Dependency 목록 전체.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$&amp;lt;&lt;/code&gt;: Dependency 목록 중 첫 번째 파일.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  주요 기능 및 옵션&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;컴파일 옵션&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-g&lt;/code&gt;: 디버깅 정보 포함.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-Wall&lt;/code&gt;: 모든 경고(Warning)를 에러처럼 표시.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-O2&lt;/code&gt;: 최적화 2단계.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wildcard 함수&lt;/strong&gt;: &lt;code&gt;*.c&lt;/code&gt; (현재 디렉토리의 모든 .c 파일).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확장자 치환&lt;/strong&gt;: &lt;code&gt;OBJS = $(SRCS:.c=.o)&lt;/code&gt; (.c를 .o로 변경).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  Makedepend 유틸리티&lt;/h3&gt;
&lt;p&gt;입력한 소스파일을 분석해 헤더파일 의존성을 자동으로 등록해주는 도구.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;설치&lt;/strong&gt;: &lt;code&gt;sudo apt install wutils-dev -y&lt;/code&gt; (또는 &lt;code&gt;xutils-dev&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;사용&lt;/strong&gt;: &lt;code&gt;makedepend main.c func1.c -Y&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;특징&lt;/strong&gt;: Makefile 하단에 의존성 추가, &lt;code&gt;Makefile.bak&lt;/code&gt; 백업 생성.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3. CMake (Cross Platform Build System)&lt;/h2&gt;
&lt;h3&gt;  특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;운영체제에 상관없이 빌드 가능한 &lt;strong&gt;크로스 플랫폼&lt;/strong&gt; 도구.&lt;/li&gt;
&lt;li&gt;Makefile을 자동으로 생성해주는 상위 빌드 시스템.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;설치&lt;/strong&gt;: &lt;code&gt;sudo apt install g++ cmake -y&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  사용 흐름&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;CMakeLists.txt&lt;/code&gt; 파일 작성.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cmake .&lt;/code&gt; 실행 → Makefile 자동 생성.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;make&lt;/code&gt; 실행 → 빌드 완료.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;4. 리눅스 파일 관리 명령어&lt;/h2&gt;
&lt;h3&gt;  파일 내용 및 검색&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cat [파일]&lt;/code&gt;: 파일 내용 출력.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;gt;&lt;/code&gt;: 내용 쓰기 (덮어쓰기).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt;: 내용 이어 쓰기 (Append).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grep [텍스트] [경로]&lt;/code&gt;: 문자열 검색.&lt;ul&gt;
&lt;li&gt;예: &lt;code&gt;ls -al | grep test*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  파일 찾기 및 정보&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;find [경로] -name [이름] -type [f/d]&lt;/code&gt;: 파일(f) 또는 디렉토리(d) 찾기.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;file [파일]&lt;/code&gt;: 파일 종류 확인 (ASCII text, executable 등).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;which [명령어]&lt;/code&gt;: 명령어 실행 파일의 위치 확인.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  용량 확인&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;du -sh [파일/디렉토리]&lt;/code&gt;: Disk Usage 확인.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-s&lt;/code&gt;: 총 사용량만 출력 (Summary).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-h&lt;/code&gt;: 사람이 보기 편한 단위로 출력 (Human-readable).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;5. Shell 명령어 &amp;amp; 기타&lt;/h2&gt;
&lt;h3&gt;  시스템 정보 및 로그&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;echo [텍스트]&lt;/code&gt;: 화면 출력.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;date&lt;/code&gt;: 현재 시간 확인.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uptime&lt;/code&gt;: 시스템 부팅 후 경과 시간 및 부하 확인.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dmesg&lt;/code&gt;: 커널 부팅 로그 및 하드웨어 메시지 출력.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;history&lt;/code&gt;: 사용한 명령어 기록 확인 (&lt;code&gt;!번호&lt;/code&gt;로 재실행).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  C언어 연동&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;system(&amp;quot;명령어&amp;quot;)&lt;/code&gt;: C 코드 내에서 쉘 명령어를 실행 (&lt;code&gt;#include &amp;lt;stdlib.h&amp;gt;&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  심볼릭 링크 (Symbolic Link)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;정의&lt;/strong&gt;: 윈도우의 &amp;#39;바로가기&amp;#39;와 같은 링크 파일. 원본을 가리킴.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;명령어&lt;/strong&gt;: &lt;code&gt;ln -s [원본] [링크명]&lt;/code&gt;&lt;ul&gt;
&lt;li&gt;예: &lt;code&gt;ln -s ./bts ./bbq&lt;/code&gt; (bbq가 bts를 가리킴).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확인&lt;/strong&gt;: &lt;code&gt;ls -al&lt;/code&gt;로 확인 시 &lt;code&gt;-&amp;gt;&lt;/code&gt; 화살표로 연결 표시됨.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Linux</category>
      <category>build</category>
      <category>Linux</category>
      <author>RTD</author>
      <guid isPermaLink="true">https://roadtodeveloper.tistory.com/14</guid>
      <comments>https://roadtodeveloper.tistory.com/14#entry14comment</comments>
      <pubDate>Mon, 12 Jan 2026 16:58:36 +0900</pubDate>
    </item>
    <item>
      <title>[Raspberry pi] sensor</title>
      <link>https://roadtodeveloper.tistory.com/13</link>
      <description>&lt;h1&gt;  [TIL] 라즈베리파이 신호 처리 및 센서(Sense HAT) 심화 학습&lt;/h1&gt;
&lt;h2&gt;1. 신호 처리 기초 (Analog vs Digital)&lt;/h2&gt;
&lt;h3&gt;  아날로그 신호의 처리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;문제점&lt;/strong&gt;: 라즈베리파이에는 아날로그 신호를 디지털로 변환하는 &lt;strong&gt;ADC(Analog-to-Digital Converter)&lt;/strong&gt; 하드웨어가 내장되어 있지 않음.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해결책 (PWM)&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PWM (Pulse Width Modulation)&lt;/strong&gt;: 펄스 폭 변조.&lt;/li&gt;
&lt;li&gt;디지털 신호를 빠르게 스위칭(On/Off)하여 마치 아날로그 신호인 것처럼 보이게 하는 기술.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  주파수와 주기&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;공식&lt;/strong&gt;: $f(Hz) = 1 / T(s)$ (주파수는 주기에 반비례)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU 속도 예시&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;일반 CPU (5GHz)&lt;/strong&gt;: 1초에 50억 회 진동 (1회 진동 시 0.2ns 소요)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;라즈베리파이 5 (ARM Cortex-A76 2.4GHz)&lt;/strong&gt;: 1초에 24억 회 진동 (1회 진동 시 약 417μs 소요)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;2. 센서(Sensor)와 MEMS&lt;/h2&gt;
&lt;h3&gt;  센서 개요&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sense HAT&lt;/strong&gt;: 라즈베리파이 공식 센서 키트 (Add-on Board).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;센서(Sensor)&lt;/strong&gt;: 정보를 수집하여 수치 값으로 만들어내는 장치.&lt;ul&gt;
&lt;li&gt;예: 오감(시각, 청각) 외 초음파, 압력, 자기, 온도, 가스, 가속도 등.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  MEMS (Micro Electro Mechanical Systems)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;정의&lt;/strong&gt;: 반도체 제조 공정을 응용하여 만든 초소형 정밀 기계 시스템.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;활용 분야&lt;/strong&gt;: 자동차, 정보 통신 등 다양한 분야.&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;에어백&lt;/strong&gt;: 가속도 센서 활용.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;차체 제어&lt;/strong&gt;: 자이로 센서 활용.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;타이어&lt;/strong&gt;: 공기압 센서 활용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3. MCU와 드라이버 (Driver)&lt;/h2&gt;
&lt;h3&gt;  드라이버의 필요성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MCU의 한계&lt;/strong&gt;: GPIO 핀 부족, 직접 제어 시 프로그래밍의 복잡함 등으로 모든 모듈을 직접 제어하기 어려움.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;드라이버(Driver)의 역할&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;H/W Interface&lt;/strong&gt;: 모듈을 제어할 수 있는 인터페이스 역할.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;동작 원리&lt;/strong&gt;: MCU는 드라이버에게 명령을 내리고, 드라이버가 실제 모듈(LED, 모터 등)을 제어함.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;종류&lt;/strong&gt;: FND 드라이버, 모터 드라이버, LCD 드라이버, LED 드라이버 등.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;4. 라즈베리파이 Sense HAT 실습&lt;/h2&gt;
&lt;h3&gt;  개요 및 구성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;구성&lt;/strong&gt;: 다양한 센서 집합(자이로, 가속도, 기압, 지자기, 온습도) + 8x8 LED Matrix + 조이스틱.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;주의사항&lt;/strong&gt;: 하드웨어 연결 시 반드시 &lt;strong&gt;라즈베리파이 전원을 끄고&lt;/strong&gt; 연결할 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  Sense HAT Emulator (가상 시뮬레이터)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;하드웨어가 없거나 불량 테스트 용도로 사용 (실제 코드와 호환됨).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;설치 명령어&lt;/strong&gt;:&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;  git clone https://github.com/astro-pi/python-sense-emu
  cd ./python-sense-emu
  sudo python3 setup.py install&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  API 테스트 코드 (Python)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from sense_hat import SenseHat
sense = SenseHat()
sense.show_message(&amp;quot;HELLO&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;  트러블 슈팅 (OSError 발생 시)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;원인&lt;/strong&gt;: 설정 파일에 Sense HAT 오버레이가 누락된 경우.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해결 방법&lt;/strong&gt;:&lt;ol&gt;
&lt;li&gt;&lt;code&gt;/boot/firmware/config.txt&lt;/code&gt; 파일 열기 (&lt;code&gt;sudo vi ...&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dtoverlay=rpi-sense&lt;/code&gt; 내용 추가 후 저장.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo reboot&lt;/code&gt; (재부팅).&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  회로도 확인&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;공식 사이트&lt;/strong&gt;: &lt;a href=&quot;https://datasheets.raspberrypi.com/&quot;&gt;Raspberry Pi Datasheets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확인 방법&lt;/strong&gt;: &amp;#39;sense-hat&amp;#39; 검색 → Schematic(회로도) 클릭 → 부품 실크 번호(ex: U3, U4)로 상세 부품 확인 및 PDF 조회.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;5. IMU (Inertial Measurement Unit, 관성 측정 장치)&lt;/h2&gt;
&lt;h3&gt;  개요&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;물체의 기울기, 위치, 방향 등 정보를 측정하는 장치 (드론, 자동차, 선박 등 활용).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;센서 축(Axis) 구성&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;3축&lt;/strong&gt;: 가속도 or 자이로 or 지자기 중 택 1 (X, Y, Z).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;6축&lt;/strong&gt;: 자이로스코프 + 가속도 센서.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;9축&lt;/strong&gt;: 자이로스코프 + 가속도 센서 + 지자기 센서.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sense HAT 스펙&lt;/strong&gt;: &lt;strong&gt;9축 센서&lt;/strong&gt; 탑재 (회로도 실크: U4).&lt;ul&gt;
&lt;li&gt;자이로스코프 (Pitch, Roll, Yaw)&lt;/li&gt;
&lt;li&gt;가속도 센서 (X, Y, Z)&lt;/li&gt;
&lt;li&gt;지자기 센서 (X, Y, Z)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  데이터 보정 (필터링)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;필요성&lt;/strong&gt;: 자이로스코프 등의 Raw data는 오차가 있을 수 있어 보정이 필수.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;주요 필터&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;상보 필터 (Complementary Filter)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;칼만 필터 (Kalman Filter)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;참고&lt;/strong&gt;: 정확한 각도를 구하기 위해 사용되며, 최근에는 관련 라이브러리가 잘 구축되어 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  가속도 센서 활용 원리&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;가속도 적분 → &lt;strong&gt;속도&lt;/strong&gt; 산출.&lt;/li&gt;
&lt;li&gt;속도 적분 → &lt;strong&gt;이동 경로&lt;/strong&gt; 산출.&lt;/li&gt;
&lt;li&gt;진동 분석 → 장비 &lt;strong&gt;고장 여부&lt;/strong&gt; 판단.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;기울기&lt;/strong&gt; 측정 → 모터 제어 등에 활용 (예: MPU6050).&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;6. 기타 구성 요소 상세&lt;/h2&gt;
&lt;h3&gt;  LED Matrix&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Sense HAT에 내장된 디스플레이.&lt;/li&gt;
&lt;li&gt;공식 문서: &lt;a href=&quot;https://pythonhosted.org/sense-hat/api/#led-matrix&quot;&gt;Sense HAT API - LED Matrix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  조이스틱 (Joystick)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;특징&lt;/strong&gt;: 라즈베리파이에는 &lt;strong&gt;ADC가 없음&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;구조&lt;/strong&gt;: 명칭은 조이스틱이지만 실제로는 아날로그 스틱이 아닌 &lt;strong&gt;5개의 디지털 버튼&lt;/strong&gt;(좌, 우, 위, 아래, 클릭)으로 구성됨.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;회로도 실크&lt;/strong&gt;: J2&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Linux</category>
      <category>Embedded</category>
      <category>raspberry pi</category>
      <author>RTD</author>
      <guid isPermaLink="true">https://roadtodeveloper.tistory.com/13</guid>
      <comments>https://roadtodeveloper.tistory.com/13#entry13comment</comments>
      <pubDate>Sat, 10 Jan 2026 11:13:02 +0900</pubDate>
    </item>
    <item>
      <title>라즈베리파이 &amp;amp; 임베디드 기초</title>
      <link>https://roadtodeveloper.tistory.com/12</link>
      <description>&lt;h1&gt;  라즈베리파이 &amp;amp; 임베디드 기초 학습 정리&lt;/h1&gt;
&lt;h2&gt;1. 라즈베리파이 (Raspberry Pi) 개요&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;정의&lt;/strong&gt;: &lt;strong&gt;SBC (Single Board Computer)&lt;/strong&gt;. 많은 리눅스 및 임베디드 SW 개발자가 교육용으로 다루는 훌륭한 &lt;strong&gt;MPU&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;역할&lt;/strong&gt;: GUI 원격 제어기로서 &lt;strong&gt;Edge Device&lt;/strong&gt;와 DB 사이에서 중계 및 제어 역할을 담당.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;라즈베리파이 5 스펙 (Model B)&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;칩셋&lt;/strong&gt;: Broadcom BCM2712&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU&lt;/strong&gt;: 2.4GHz ARM Cortex-A76 쿼드코어&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RAM&lt;/strong&gt;: 8GB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;통신&lt;/strong&gt;: WiFi, BLE (Bluetooth 5.0), 이더넷 등&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IDE&lt;/strong&gt;: Thonny Python IDE 내장 (Python 주로 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;2. 전기전자 기초&lt;/h2&gt;
&lt;h3&gt;  전압과 전류&lt;/h3&gt;
&lt;p&gt;임베디드 장치는 적절한 용량의 전기가 공급되어야 고장나지 않고 동작합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;전압 (Voltage, V)&lt;/strong&gt;: 전하가 갖는 전위의 차이. (압력)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;전류 (Current, A)&lt;/strong&gt;: 단위 시간당 흐르는 전하의 양. (흐름)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;전력 (Power, W)&lt;/strong&gt;: &lt;code&gt;W = V * A&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;멀티미터 측정&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;병렬&lt;/strong&gt; 연결: 전압 측정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;직렬&lt;/strong&gt; 연결: 전류 측정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;전원&lt;/strong&gt;: 임베디드는 주로 &lt;strong&gt;직류(DC)&lt;/strong&gt;를 사용하며, 항상 &lt;strong&gt;VCC(+)&lt;/strong&gt;와 &lt;strong&gt;GND(-)&lt;/strong&gt;를 가짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  저항 (Resistor)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;역할&lt;/strong&gt;: 전기의 흐름을 방해하여 원하는 전압/전류를 맞춤 (과전류 방지).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;주요 저항 색띠&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;220Ω&lt;/strong&gt;: 빨/빨/갈 (4띠), 빨/빨/검/갈 (5띠)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;10kΩ&lt;/strong&gt;: 갈/검/주&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;브레드보드&lt;/strong&gt;: 빨간색 라인(VCC), 파란/검정 라인(GND) 연결.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  정격 전압/전류 (Rating)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;정격&lt;/strong&gt;: 기기가 동작하는 데 필요한 표준 값.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;규칙&lt;/strong&gt;: &lt;strong&gt;공급 전압은 정격과 일치&lt;/strong&gt;해야 하며, &lt;strong&gt;공급 전류는 정격보다 커야&lt;/strong&gt; 함.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;라즈베리파이 5 전원&lt;/strong&gt;: 5V / 5A (정품 어댑터 권장).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GPIO 전기 규격&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;공급 전압: &lt;strong&gt;최대 3.3V&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;핀당 최대 전류: &lt;strong&gt;16mA&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;모든 핀 총 전류: &lt;strong&gt;1A&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3. 임베디드 개발 방식&lt;/h2&gt;
&lt;h3&gt;  개발 형태 분류&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Bare-metal (Firmware)&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;OS 없이 하드웨어 위에서 직접 동작하는 코드 작성.&lt;/li&gt;
&lt;li&gt;주로 작은 MCU(마이크로컨트롤러) 개발 시 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RTOS (Real Time OS)&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;실시간성을 보장하는 가벼운 OS 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embedded Linux&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;리눅스 기반의 풍부한 개발 도구와 생태계 활용. (라즈베리파이 해당)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;  크로스 컴파일 (Cross Compile)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;정의&lt;/strong&gt;: 개발 환경(PC)과 실행 환경(타겟 보드)의 아키텍처가 다를 때 사용하는 컴파일 방식.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이유&lt;/strong&gt;: 개발 PC(Host)의 성능이 임베디드 보드(Target)보다 훨씬 우수하기 때문에, &lt;strong&gt;PC에서 컴파일하여 실행 파일만 보드로 전송&lt;/strong&gt;하여 실행함.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;4. GPIO 제어 및 실습&lt;/h2&gt;
&lt;h3&gt;  GPIO (General Purpose Input Output)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;정의&lt;/strong&gt;: 칩 제작사가 용도를 고정하지 않고, 사용자가 자유롭게 H/W를 연결해 제어하도록 만든 핀.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;제어 방법&lt;/strong&gt;:&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Device Driver&lt;/strong&gt;: 리눅스 커널 레벨에서 제어.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Library&lt;/strong&gt;: 랩핑(Wrapping)된 함수를 사용하여 쉽게 제어.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pinout 가이드&lt;/strong&gt;: &lt;a href=&quot;https://pinout.xyz/&quot;&gt;https://pinout.xyz/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;  파이썬 라이브러리&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;gpiozero&lt;/strong&gt; (추천): 초보자가 사용하기 쉽도록 직관적으로 설계됨.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RPi.GPIO&lt;/strong&gt;: 더 세부적인 로우 레벨 설정이 가능함.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;  LED 제어 실습 코드 (&lt;code&gt;led.py&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
from gpiozero import LED
from time import sleep

red = LED(14)  # GPIO 14번 핀에 연결

while True:
    red.on()   # High 신호 (켜기)
    sleep(1)
    red.off()  # Low 신호 (끄기)
    sleep(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;실행: python3 led.py&lt;/p&gt;
&lt;h2&gt;주의: LED는 긴 쪽이 +, 짧은 쪽이 -. 사용하지 않는 모듈은 분리(특히 GND).&lt;/h2&gt;
&lt;h2&gt;5. 스위치와 플로팅(Floating) 상태&lt;/h2&gt;
&lt;h3&gt;  플로팅 상태란?&lt;/h3&gt;
&lt;p&gt;스위치를 누르지 않았을 때, 핀의 전압이 High(1)인지 Low(0)인지 확정할 수 없는 불안정한 상태.&lt;/p&gt;
&lt;p&gt;모든 MCU 입력 핀에서 발생 가능.&lt;/p&gt;
&lt;h3&gt;  해결 방법 (Pull-up / Pull-down)&lt;/h3&gt;
&lt;p&gt;저항을 사용하여 평소 상태(Default)를 고정해 줌.&lt;/p&gt;
&lt;p&gt;풀업 (Pull-up):&lt;/p&gt;
&lt;p&gt;평소(스위치 Open)에 High 상태 유지.&lt;/p&gt;
&lt;p&gt;스위치를 누르면 Low가 됨.&lt;/p&gt;
&lt;p&gt;VCC 쪽에 저항 연결.&lt;/p&gt;
&lt;p&gt;선호: 노이즈 제거 및 회로 보호에 유리하여 더 많이 사용됨.&lt;/p&gt;
&lt;p&gt;풀다운 (Pull-down):&lt;/p&gt;
&lt;p&gt;평소에 Low 상태 유지.&lt;/p&gt;
&lt;p&gt;스위치를 누르면 High가 됨.&lt;/p&gt;
&lt;p&gt;GND 쪽에 저항 연결.&lt;/p&gt;
&lt;p&gt;Tip: 라즈베리파이 GPIO 핀에는 내부 풀업 회로가 내장되어 있어, 소프트웨어적으로 설정하거나 자동으로 적용되는 경우가 많습니다.&lt;/p&gt;
&lt;h3&gt; ️ 기타 팁&lt;/h3&gt;
&lt;p&gt;VNC 접속 불가 시: Raspberry Pi Configuration → Interfaces → VNC 활성화(Enable) 체크.&lt;/p&gt;
&lt;p&gt;회로 연결 순서: 항상 3.3V와 GND를 먼저 연결하여 기준 전위를 잡을 것.&lt;/p&gt;</description>
      <category>  Linux</category>
      <category>라즈베리파이</category>
      <category>임베디드</category>
      <author>RTD</author>
      <guid isPermaLink="true">https://roadtodeveloper.tistory.com/12</guid>
      <comments>https://roadtodeveloper.tistory.com/12#entry12comment</comments>
      <pubDate>Sat, 10 Jan 2026 11:11:44 +0900</pubDate>
    </item>
  </channel>
</rss>