Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
title: "Maven 빌드와 Java Application 실행은 무엇이 다를까?"
description: "Eclipse에서 Spring Boot를 실행할 때 Maven의 빌드 단계와 Java Application의 클래스패스를 구분하고, 패키징·디버깅·JVM 옵션의 적용 위치를 정리합니다."
pubDate: "2026-10-24T00:00:00+09:00"
categories: "Programming"
tags: ["Java", "Maven", "Eclipse", "Spring Boot"]
slug: "programming/maven-build-java-application"
relatedSlugs: ["programming/devtools-hcr-jrebel"]
---

## 핵심 요약

- Maven은 지정한 단계와 플러그인 설정에 따라 컴파일·테스트·패키징 등을 수행합니다.
- Eclipse의 Java Application은 Main Class와 런타임 클래스패스를 기준으로 JVM을 실행합니다. 배포 JAR를 미리 만들 필요는 없습니다.
- IDE 실행 성공과 배포 산출물의 실행 성공은 별도로 확인해야 합니다.

## Maven으로 빌드하고 Java Application으로 실행해도 될까?

Eclipse에서 Spring Boot 프로젝트를 개발할 때 사용할 수 있는 일반적인 조합입니다. 두 작업의 역할이 다르기 때문입니다.

| 구분 | Maven Build | Java Application |
| --- | --- | --- |
| 중심 설정 | `pom.xml`, 실행할 phase·goal | 프로젝트, Main Class, 클래스패스, JRE |
| 주 역할 | 빌드 수명주기와 플러그인 작업 실행 | 지정한 `main()`으로 애플리케이션 시작 |
| JAR 생성 | 패키징 설정과 실행 단계에 따라 수행 | 실행 구성 자체가 패키징하는 것은 아님 |
| 입력 | 소스·리소스·의존성·플러그인 설정 | 클래스 파일·리소스·런타임 의존성 |
| 디버깅 | 실행 대상과 별도 디버그 구성에 따라 달라짐 | Debug As로 중단점 사용 가능 |

“Maven Build”라는 이름만으로 어떤 작업을 했는지는 알 수 없습니다. `compile`만 실행했는지, `verify`나 `install`까지 실행했는지 확인해야 합니다.

## Maven은 지정한 단계까지 진행한다

Maven의 기본 빌드 수명주기는 여러 단계로 나뉘며, 뒤쪽 단계를 지정하면 그 앞 단계도 순서대로 수행합니다.[1]

| 명령 | 목적 |
| --- | --- |
| `mvn compile` | 프로젝트의 주 소스 컴파일 |
| `mvn test` | 테스트 단계까지 수행 |
| `mvn package` | 프로젝트의 packaging 형식으로 산출물 생성 |
| `mvn verify` | 패키징 이후 검증 단계까지 수행 |
| `mvn install` | 검증한 산출물을 로컬 Maven 저장소에 설치 |

실제로 어떤 검사나 생성 작업이 수행되는지는 연결된 플러그인과 프로필에 달려 있습니다. 예를 들어 통합 테스트가 자동으로 실행되려면 해당 단계의 플러그인 설정이 필요합니다.

`clean`은 별도의 수명주기입니다. `mvn clean verify`는 기존 빌드 출력을 정리한 뒤 검증 단계까지 실행하는 조합입니다. 매번 로컬 저장소에 설치할 이유가 없다면 `clean install`을 모든 코드 수정마다 실행할 필요는 없습니다.

## Java Application은 생성된 JAR를 실행하는가?

보통은 아닙니다. Eclipse의 Java Application 실행 구성은 프로젝트의 빌드 경로를 바탕으로 클래스패스를 구성하고, 지정한 Main Class를 실행합니다.[2]

Spring Boot 프로젝트라면 `SpringApplication.run(...)`을 호출하는 `main()`이 진입점이 됩니다. 프로젝트의 컴파일 결과와 리소스, 의존성 JAR들이 클래스패스에 있으면 배포용 실행 JAR를 만들지 않고도 시작할 수 있습니다.

Eclipse의 자동 빌드는 변경 사항을 점진적으로 컴파일할 수 있습니다.[5] 다만 컴파일 오류, 실행 전 빌드 설정, 리소스 복사와 생성 코드 설정이 올바른지 확인해야 합니다. Maven 플러그인에서만 수행하는 코드 생성이나 리소스 필터링은 IDE 실행에서 같은 방식으로 수행되지 않을 수 있습니다.

따라서 “저장했으니 실행 중 서버에도 모두 반영되었다”거나 “Maven 빌드 결과와 IDE 컴파일 결과가 항상 같다”라고 가정해서는 안 됩니다.

## JVM 옵션은 실행 대상에 넣기

Java Application의 Arguments 탭에서는 VM arguments와 Program arguments를 구분합니다.[2] 예를 들어 VM arguments에 다음을 넣을 수 있습니다.

```text
-Xmx512m -Dspring.profiles.active=local
```

Spring Boot의 애플리케이션 인자로 프로필을 전달한다면 Program arguments에 다음처럼 넣습니다.

```text
--spring.profiles.active=local
```

반면 `spring-boot:run`은 Maven 플러그인이 별도 프로세스로 애플리케이션을 실행합니다. Maven 프로세스용 옵션과 애플리케이션 JVM용 옵션은 구분해야 합니다.[3]

```bash
mvn spring-boot:run "-Dspring-boot.run.jvmArguments=-Xmx512m"
```

실행 방식이 바뀌었을 때 메모리 설정이나 프로필이 달라진다면, 같은 옵션을 실제 대상 프로세스에 전달했는지 먼저 확인할 수 있습니다.

## 실행 JAR도 따로 확인하기

Spring Boot Maven Plugin의 `repackage`는 의존성을 포함해 `java -jar`로 실행할 수 있는 아카이브를 만듭니다. 단순한 JAR 패키징과는 구분됩니다.[4]

프로젝트에서 `repackage`가 `package` 단계에 연결되어 있다고 가정하면 다음처럼 배포 산출물을 만들고 확인합니다. 파일명은 실제 생성된 JAR로 바꿔야 합니다.

```bash
mvn clean package
java -Xmx512m -jar target/application.jar --spring.profiles.active=local
```

IDE에서 정상 실행되더라도 패키징에서 빠진 리소스, 다른 프로필, 배포 환경의 JDK나 외부 설정 때문에 JAR 실행은 실패할 수 있습니다. 배포할 파일을 실제 배포와 가까운 방식으로 실행하는 검증이 필요합니다.

## 평소 개발에서는 어떻게 나눌까?

평소 코드 수정과 중단점 확인에는 Java Application 또는 Debug As를 사용하고, 빌드 설정을 바꿨거나 배포 전에 Maven 검증과 JAR 실행을 확인하는 흐름을 사용할 수 있습니다.

Debug As에서의 Hot Code Replace도 모든 변경을 무조건 반영하지는 않습니다. 메서드 추가 등 구조 변경의 제약과 DevTools의 재시작 방식은 [DevTools·HCR·JRebel 비교](/blog/programming/devtools-hcr-jrebel)에서 따로 다룹니다.

## 참고 자료

1. [Apache Maven — Introduction to the Build Lifecycle](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html)
2. [Eclipse — Creating a Java Application Launch Configuration](https://help.eclipse.org/latest/topic/org.eclipse.jdt.doc.user/tasks/tasks-java-local-configuration.htm)
3. [Spring Boot 3.4 Maven Plugin — Running your Application](https://docs.spring.io/spring-boot/3.4/maven-plugin/run.html)
4. [Spring Boot 3.4 Maven Plugin — Packaging Executable Archives](https://docs.spring.io/spring-boot/3.4/maven-plugin/packaging.html)
5. [Eclipse — Builds](https://help.eclipse.org/latest/topic/org.eclipse.platform.doc.user/concepts/concepts-22.htm)
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
title: "MySQL 인덱스와 파티션 프루닝: type이 ALL이어도 개선될까?"
description: "날짜 컬럼의 DATE() 제거 사례로 인덱스 범위 검색과 파티션 제외를 구분하고, EXPLAIN의 partitions·type·key·rows를 함께 읽는 방법을 정리합니다."
pubDate: "2026-10-24T00:00:00+09:00"
categories: "Programming"
tags: ["Database", "MySQL", "Index", "Partition Pruning", "EXPLAIN"]
slug: "programming/mysql-index-partition-pruning"
---

## 핵심 요약

- 인덱스는 키를 이용한 행 접근을, 파티션 프루닝은 불필요한 파티션 제외를 돕습니다. 두 최적화는 함께 적용될 수 있습니다.
- `type = ALL`이 유지돼도 검색할 파티션이 줄어들 수 있습니다.
- `rows`는 추정치입니다. 파티션 수와 추정 행 수 감소를 실제 실행 시간 개선과 구분해야 합니다.

## 날짜 조건을 바꿨는데 ALL이 그대로라면

날짜 컬럼에 적용한 `DATE()`를 제거한 뒤 `EXPLAIN`을 비교한다고 가정하겠습니다. 변경 전후 모두 `type = ALL`인데 `partitions`와 `rows`는 줄었다면 어떻게 설명해야 할까요?

“인덱스를 탔으니 빨라졌다”라고 쓰기 전에, **행에 접근하는 방식과 접근할 파티션의 범위를 따로 봐야 합니다.** 이 글은 MySQL 8.4의 실행 계획을 기준으로 설명합니다.

## 인덱스와 파티션은 줄이는 단위가 다르다

B-tree 인덱스는 정렬된 키 구조를 이용해 원하는 값이나 범위에 접근할 수 있게 합니다. 일반적인 인덱스 키와 상수 사이의 비교는 범위 검색의 후보가 됩니다.[1]

반면 파티셔닝은 하나의 논리적 테이블을 정해진 기준으로 나누는 기능입니다. Partition Pruning은 쿼리 조건과 파티션 정의를 바탕으로, 결과에 기여하지 않는 파티션을 검색 대상에서 제외하는 최적화입니다.[2]

| 구분 | 인덱스 접근 | 파티션 프루닝 |
| --- | --- | --- |
| 줄이는 범위 | 인덱스 키를 통한 행 접근 범위 | 조회할 파티션 |
| 필요한 조건 | 적절한 인덱스와 검색 조건, 비용 판단 | 파티션 정의와 해석 가능한 검색 조건 |
| 실행 계획에서 볼 곳 | `type`, `key`, `possible_keys` 등 | `partitions` |
| 함께 적용 | 남은 파티션에서 인덱스 사용 가능 | 인덱스 없이 일부 파티션 스캔도 가능 |

인덱스가 있어도 넓은 범위를 읽는 쿼리라면 옵티마이저가 전체 스캔을 더 유리하게 판단할 수 있습니다. 인덱스 존재와 실제 선택은 같은 말이 아닙니다.

## 날짜 컬럼의 함수 제거

다음은 MyBatis 파라미터를 사용한 조건의 예입니다. `MODIFY_TIME`은 유효한 `DATETIME` 값이며, `FROM_DATE`는 날짜 문자열이라는 전제입니다.

```sql
-- 변경 전
AND DATE(AH.MODIFY_TIME) >= DATE(DATE_FORMAT(#{FROM_DATE}, '%Y-%m-%d'))

-- 변경 후
AND AH.MODIFY_TIME >= DATE(DATE_FORMAT(#{FROM_DATE}, '%Y-%m-%d'))
```

오른쪽은 기준 날짜의 0시로 비교되고, 왼쪽 컬럼은 원래 값 그대로 비교할 수 있게 됩니다. 날짜 경계를 애플리케이션에서 만들어 타입에 맞게 바인딩하면 오른쪽 표현식도 단순화할 수 있습니다.

다만 데이터 타입, 잘못된 날짜 값의 처리, 입력 형식, `TIMESTAMP`의 세션 시간대 등에 따라 의미를 다시 확인해야 합니다. 날짜만 출력하는 SELECT 표현식과 검색 범위를 정하는 WHERE 조건도 구분하는 것이 좋습니다.

컬럼에 함수를 씌우면 일반 컬럼 인덱스의 범위 조건으로 직접 활용하기 어려워질 수 있습니다. 하지만 함수 기반 인덱스나 생성 컬럼 등 예외가 있으므로 “함수가 있으면 어떤 인덱스도 절대 못 쓴다”라고 일반화해서는 안 됩니다.

프루닝 역시 모든 함수에 똑같이 반응하지 않습니다. MySQL이 이해하는 파티션 표현식과 조건의 조합이 있으므로 실제 정의와 계획을 확인해야 합니다.[2]

## 작은 테이블로 확인할 수 있는 예제

다음은 **개념 확인용 DDL이며 실측 성능 결과는 아닙니다.** 새 테스트 DB에서 실행할 예제로, 기본 키에 파티션 컬럼도 포함했습니다.

```sql
CREATE TABLE action_history_demo (
id BIGINT NOT NULL,
modify_time DATETIME NOT NULL,
payload VARCHAR(100),
PRIMARY KEY (id, modify_time),
KEY idx_modify_time (modify_time)
) ENGINE = InnoDB
PARTITION BY RANGE COLUMNS (modify_time) (
PARTITION p_before VALUES LESS THAN ('2026-10-01'),
PARTITION p202610 VALUES LESS THAN ('2026-11-01'),
PARTITION p_future VALUES LESS THAN (MAXVALUE)
);

INSERT INTO action_history_demo VALUES
(1, '2026-09-30 23:59:59', 'September'),
(2, '2026-10-19 12:00:00', 'October'),
(3, '2026-11-01 00:00:00', 'November');

EXPLAIN FORMAT=TRADITIONAL
SELECT * FROM action_history_demo
WHERE modify_time >= '2026-10-01'
AND modify_time < '2026-11-01';
```

이 정의에서는 10월 범위가 `p202610`에 들어가므로, 해당 파티션으로 검색 범위가 좁혀지는지 확인할 수 있습니다.

MySQL 8.4는 설정에 따라 `EXPLAIN`의 기본 출력 형식이 달라질 수 있으므로, 여기서는 `FORMAT=TRADITIONAL`을 명시해 표 형태의 컬럼을 확인합니다.[4]

데이터가 적으면 `type`이 반드시 `range`가 되지는 않습니다. `type`, `key`, `partitions`를 함께 확인합니다. 또한 상한 없이 `modify_time >= '2026-10-01'`만 지정하면 11월 이후를 담는 `p_future`도 결과의 후보입니다.

## EXPLAIN에서 무엇을 확인할까?

| 항목 | 의미와 주의점 |
| --- | --- |
| `partitions` | 접근 대상으로 남은 파티션 |
| `type` | `ALL`, `range` 등 테이블 접근 방식 |
| `possible_keys` | 접근에 사용할 수 있는 후보. 실제 선택과는 다름 |
| `key` | 선택한 인덱스 |
| `rows` | 옵티마이저가 검사할 것으로 추정한 행 수 |

`ALL`은 테이블 스캔을 나타내지만, 파티션 테이블에서는 대상으로 남은 파티션 범위도 함께 읽어야 합니다. InnoDB의 `rows`는 실제로 읽은 행 수가 아닌 추정치입니다.[3]

변경 후에도 `type = ALL`이고 `partitions`가 줄었다면, 우선 “검색 대상 파티션을 줄였다”라고 설명할 수 있습니다. 인덱스 범위 검색으로 전환된 것까지 확인하지 않았다면 두 성과를 섞지 않는 것이 중요합니다.

## 리뷰와 보고에는 어떻게 쓸까?

예를 들어 다음처럼 확인한 범위를 명시할 수 있습니다.

> 날짜 컬럼의 함수 적용을 제거하여 실행 계획상 접근 대상 파티션과 예상 검사 행 수의 감소를 확인했다. 접근 방식은 ALL을 유지하며, 실행 시간 개선은 별도로 측정한다.

실행 시간을 평가하려면 같은 파라미터와 같은 데이터로 결과의 동등성을 확인하고, 캐시와 동시 부하 조건도 맞춰야 합니다. `EXPLAIN ANALYZE`는 쿼리를 실제로 실행하므로, 먼저 관리 가능한 검증 환경에서 다룹니다.[4]

파티션을 줄인 것은 실행 계획의 개선으로 보고할 수 있지만, 그것만으로 몇 배 빨라졌다고 말할 수는 없습니다. 관측한 계획과 측정한 시간을 분리하면 개선 내용을 정확하게 전달할 수 있습니다.

## 참고 자료

1. [MySQL 8.4 — Range Optimization](https://dev.mysql.com/doc/refman/8.4/en/range-optimization.html)
2. [MySQL 8.4 — Partition Pruning](https://dev.mysql.com/doc/refman/8.4/en/partitioning-pruning.html)
3. [MySQL 8.4 — EXPLAIN Output Format](https://dev.mysql.com/doc/refman/8.4/en/explain-output.html)
4. [MySQL 8.4 — EXPLAIN Statement](https://dev.mysql.com/doc/refman/8.4/en/explain.html)
Loading