Compare commits
13 Commits
045e3da923
...
feat/modif
| Author | SHA1 | Date | |
|---|---|---|---|
| 126b48ad3c | |||
| 2a3fa7e895 | |||
| 731dbb4170 | |||
| e50295e929 | |||
| 84d66b57ea | |||
| 038a4fabc6 | |||
| 4836d09320 | |||
| 1ce49fb793 | |||
| dee09ad16a | |||
| 596b6d4d84 | |||
| 135de03c21 | |||
| cd284d94ae | |||
| bf5537c384 |
0
imagery-make-dataset/dev.backup
Executable file → Normal file
0
imagery-make-dataset/dev.backup
Executable file → Normal file
32
kamco-make-dataset-generation/.gitignore
vendored
Normal file
32
kamco-make-dataset-generation/.gitignore
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Gradle
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
!build/libs/
|
||||||
|
!build/libs/*.jar
|
||||||
|
out/
|
||||||
|
|
||||||
|
# IntelliJ IDEA
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Compiled class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Package files (gradle wrapper jar is intentionally tracked)
|
||||||
|
*.war
|
||||||
|
*.nar
|
||||||
|
*.ear
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
|
||||||
|
# Kotlin
|
||||||
|
*.kotlin_module
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
|||||||
#Sun Feb 08 18:46:40 KST 2026
|
|
||||||
gradle.version=8.14.3
|
|
||||||
Binary file not shown.
Binary file not shown.
8
kamco-make-dataset-generation/.idea/.gitignore
generated
vendored
8
kamco-make-dataset-generation/.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
19
kamco-make-dataset-generation/.idea/compiler.xml
generated
19
kamco-make-dataset-generation/.idea/compiler.xml
generated
@@ -1,19 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="CompilerConfiguration">
|
|
||||||
<annotationProcessing>
|
|
||||||
<profile default="true" name="Default" enabled="true" />
|
|
||||||
<profile name="Gradle Imported" enabled="true">
|
|
||||||
<outputRelativeToContentRoot value="true" />
|
|
||||||
<processorPath useClasspath="false">
|
|
||||||
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.42/8365263844ebb62398e0dc33057ba10ba472d3b8/lombok-1.18.42.jar" />
|
|
||||||
</processorPath>
|
|
||||||
<module name="kamco-geojson-scheduler.main" />
|
|
||||||
</profile>
|
|
||||||
</annotationProcessing>
|
|
||||||
<bytecodeTargetLevel target="21" />
|
|
||||||
</component>
|
|
||||||
<component name="JavacSettings">
|
|
||||||
<option name="ADDITIONAL_OPTIONS_STRING" value="-parameters" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
19
kamco-make-dataset-generation/.idea/gradle.xml
generated
19
kamco-make-dataset-generation/.idea/gradle.xml
generated
@@ -1,19 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
|
||||||
<component name="GradleSettings">
|
|
||||||
<option name="linkedExternalProjectsSettings">
|
|
||||||
<GradleProjectSettings>
|
|
||||||
<option name="delegatedBuild" value="false" />
|
|
||||||
<option name="testRunner" value="PLATFORM" />
|
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
|
||||||
<option name="gradleJvm" value="corretto-21" />
|
|
||||||
<option name="modules">
|
|
||||||
<set>
|
|
||||||
<option value="$PROJECT_DIR$" />
|
|
||||||
</set>
|
|
||||||
</option>
|
|
||||||
</GradleProjectSettings>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
8
kamco-make-dataset-generation/.idea/misc.xml
generated
8
kamco-make-dataset-generation/.idea/misc.xml
generated
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
|
||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
|
||||||
<file type="web" url="file://$PROJECT_DIR$" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="corretto-21" project-jdk-type="JavaSDK" />
|
|
||||||
</project>
|
|
||||||
117
kamco-make-dataset-generation/Jenkinsfile
vendored
Normal file
117
kamco-make-dataset-generation/Jenkinsfile
vendored
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
pipeline {
|
||||||
|
agent any
|
||||||
|
|
||||||
|
parameters {
|
||||||
|
string(name: 'SPRING_PROFILES_ACTIVE', defaultValue: 'prod', description: 'Spring Profile (dev/prod)')
|
||||||
|
string(name: 'BATCH_DATE', defaultValue: '', description: 'Batch Date (YYYY-MM-DD, empty = today)')
|
||||||
|
string(name: 'ADDITIONAL_PARAMS', defaultValue: '', description: 'Additional Parameters (e.g., limit=100)')
|
||||||
|
choice(name: 'ACTION', choices: ['RUN', 'VERIFY_ONLY'], description: 'Action to perform')
|
||||||
|
}
|
||||||
|
|
||||||
|
tools {
|
||||||
|
jdk 'jdk21'
|
||||||
|
}
|
||||||
|
|
||||||
|
environment {
|
||||||
|
BRANCH = 'main'
|
||||||
|
GIT_REPO = 'https://kamco.git.gs.dabeeo.com/MVPTeam/kamco-cd-cron.git'
|
||||||
|
JAR_NAME = 'generator-dataset-for-training.jar'
|
||||||
|
TODAY = sh(script: "date +%Y-%m-%d", returnStdout: true).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Pre-built JAR is included in the repository
|
||||||
|
// To update the JAR:
|
||||||
|
// 1. On a machine with internet, run: ./gradlew clean bootJar
|
||||||
|
// 2. Commit the updated build/libs/generator-dataset-for-training.jar
|
||||||
|
// 3. Push to repository
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Checkout') {
|
||||||
|
steps {
|
||||||
|
checkout([
|
||||||
|
$class : 'GitSCM',
|
||||||
|
branches : [[name: "${env.BRANCH}"]],
|
||||||
|
userRemoteConfigs: [[
|
||||||
|
url : "${env.GIT_REPO}",
|
||||||
|
credentialsId: 'jenkins-dev-token'
|
||||||
|
]]
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stage('Get Commit Hash') {
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
env.COMMIT_HASH = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
|
||||||
|
echo "Current commit hash: ${env.COMMIT_HASH}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Verify JAR') {
|
||||||
|
steps {
|
||||||
|
dir("kamco-make-dataset-generation") {
|
||||||
|
script {
|
||||||
|
def jarPath = "build/libs/${env.JAR_NAME}"
|
||||||
|
if (!fileExists(jarPath)) {
|
||||||
|
error("JAR file not found: ${jarPath}")
|
||||||
|
}
|
||||||
|
echo "JAR file verified: ${jarPath}"
|
||||||
|
|
||||||
|
// Display JAR info
|
||||||
|
sh "ls -lh ${jarPath}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Run JAR') {
|
||||||
|
when {
|
||||||
|
expression { params.ACTION == 'RUN' }
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
dir("kamco-make-dataset-generation") {
|
||||||
|
script {
|
||||||
|
def jarPath = "build/libs/${env.JAR_NAME}"
|
||||||
|
|
||||||
|
// Determine batch date: use parameter if provided, otherwise use today
|
||||||
|
def batchDate = params.BATCH_DATE ?: env.TODAY
|
||||||
|
|
||||||
|
echo "========================================="
|
||||||
|
echo "Running JAR: ${jarPath}"
|
||||||
|
echo "Profile: ${params.SPRING_PROFILES_ACTIVE}"
|
||||||
|
echo "Batch Date: ${batchDate}"
|
||||||
|
echo "Additional Params: ${params.ADDITIONAL_PARAMS}"
|
||||||
|
echo "========================================="
|
||||||
|
|
||||||
|
// Build Java command
|
||||||
|
def javaCmd = "java -jar ${jarPath}"
|
||||||
|
|
||||||
|
// Add Spring profile
|
||||||
|
if (params.SPRING_PROFILES_ACTIVE) {
|
||||||
|
javaCmd += " --spring.profiles.active=${params.SPRING_PROFILES_ACTIVE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add batch date parameter
|
||||||
|
javaCmd += " date=${batchDate}"
|
||||||
|
|
||||||
|
// Add additional parameters
|
||||||
|
if (params.ADDITIONAL_PARAMS) {
|
||||||
|
javaCmd += " ${params.ADDITIONAL_PARAMS}"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Executing: ${javaCmd}"
|
||||||
|
|
||||||
|
// Execute JAR
|
||||||
|
sh """
|
||||||
|
${javaCmd}
|
||||||
|
"""
|
||||||
|
|
||||||
|
echo "JAR execution completed successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
com.kamco.cd.geojsonscheduler.GeoJsonSchedulerApplication
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: jdbc:postgresql://192.168.2.127:15432/kamco_cds
|
|
||||||
username: kamco_cds
|
|
||||||
password: kamco_cds_Q!W@E#R$
|
|
||||||
hikari:
|
|
||||||
minimum-idle: 2
|
|
||||||
maximum-pool-size: 5
|
|
||||||
|
|
||||||
training-data:
|
|
||||||
geojson-dir: /kamco-nfs/dataset
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: jdbc:postgresql://localhost:5432/kamco_cds
|
|
||||||
username: kamco_cds
|
|
||||||
password: kamco_cds
|
|
||||||
|
|
||||||
training-data:
|
|
||||||
geojson-dir: /tmp/geojson
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: jdbc:postgresql://127.0.0.1:15432/kamco_cds
|
|
||||||
username: kamco_cds
|
|
||||||
password: kamco_cds_Q!W@E#R$
|
|
||||||
hikari:
|
|
||||||
minimum-idle: 2
|
|
||||||
maximum-pool-size: 5
|
|
||||||
|
|
||||||
training-data:
|
|
||||||
geojson-dir: /kamco-nfs/dataset
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
spring:
|
|
||||||
application:
|
|
||||||
name: kamco-geojson-scheduler
|
|
||||||
profiles:
|
|
||||||
active: local
|
|
||||||
datasource:
|
|
||||||
driver-class-name: org.postgresql.Driver
|
|
||||||
hikari:
|
|
||||||
minimum-idle: 2
|
|
||||||
maximum-pool-size: 2
|
|
||||||
connection-timeout: 20000
|
|
||||||
idle-timeout: 300000
|
|
||||||
max-lifetime: 1800000
|
|
||||||
batch:
|
|
||||||
job:
|
|
||||||
enabled: true
|
|
||||||
initialize-schema: never
|
|
||||||
|
|
||||||
training-data:
|
|
||||||
docker:
|
|
||||||
image: kamco-cd-dataset:latest
|
|
||||||
user: "1000:1000"
|
|
||||||
dataset-volume: /kamco-nfs/dataset:/dataset
|
|
||||||
images-volume: /kamco-nfs/images:/kamco-nfs:ro
|
|
||||||
input-root: /dataset
|
|
||||||
output-root: /dataset
|
|
||||||
patch-size: 512
|
|
||||||
overlap-pct: 50
|
|
||||||
train-val-test-ratio:
|
|
||||||
- "0.7"
|
|
||||||
- "0.2"
|
|
||||||
- "0.1"
|
|
||||||
keep-empty-ratio: 0.1
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Manifest-Version: 1.0
|
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
|||||||
Manifest-Version: 1.0
|
|
||||||
|
|
||||||
257
kamco-make-dataset-generation/claudedocs/DATABASE_SETUP.md
Normal file
257
kamco-make-dataset-generation/claudedocs/DATABASE_SETUP.md
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
# 데이터베이스 설정 가이드
|
||||||
|
|
||||||
|
## 개요
|
||||||
|
이 애플리케이션은 PostgreSQL 데이터베이스를 사용하며, 다음 테이블이 필요합니다:
|
||||||
|
1. Spring Batch 메타데이터 테이블 (자동 생성)
|
||||||
|
2. batch_history 테이블 (수동 생성 필요)
|
||||||
|
|
||||||
|
## 필수 테이블
|
||||||
|
|
||||||
|
### 1. Spring Batch 메타데이터 테이블
|
||||||
|
Spring Batch가 자동으로 생성합니다:
|
||||||
|
- BATCH_JOB_INSTANCE
|
||||||
|
- BATCH_JOB_EXECUTION
|
||||||
|
- BATCH_JOB_EXECUTION_PARAMS
|
||||||
|
- BATCH_STEP_EXECUTION
|
||||||
|
- BATCH_STEP_EXECUTION_CONTEXT
|
||||||
|
- BATCH_JOB_EXECUTION_CONTEXT
|
||||||
|
|
||||||
|
**설정**:
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
batch:
|
||||||
|
jdbc:
|
||||||
|
initialize-schema: always
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. batch_history 테이블 (커스텀)
|
||||||
|
배치 작업 실행 이력을 저장하는 커스텀 테이블입니다.
|
||||||
|
|
||||||
|
**용도**:
|
||||||
|
- 배치 작업 시작/종료 시간 기록
|
||||||
|
- 배치 실행 상태 추적 (STARTED/COMPLETED/FAILED)
|
||||||
|
- 비즈니스 ID별 배치 이력 관리
|
||||||
|
|
||||||
|
## 새 환경 데이터베이스 초기 설정
|
||||||
|
|
||||||
|
### Option 1: SQL 스크립트 실행 (권장)
|
||||||
|
|
||||||
|
**1. batch_history 테이블 생성**:
|
||||||
|
```bash
|
||||||
|
# PostgreSQL에 연결
|
||||||
|
psql -h [host] -U [username] -d [database]
|
||||||
|
|
||||||
|
# schema.sql 실행
|
||||||
|
\i src/main/resources/sql/schema.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
**또는 직접 SQL 실행**:
|
||||||
|
```sql
|
||||||
|
-- batch_history 테이블 생성
|
||||||
|
CREATE TABLE IF NOT EXISTS public.batch_history (
|
||||||
|
uuid UUID PRIMARY KEY,
|
||||||
|
job VARCHAR(255) NOT NULL,
|
||||||
|
id VARCHAR(255) NOT NULL,
|
||||||
|
created_dttm TIMESTAMP NOT NULL,
|
||||||
|
updated_dttm TIMESTAMP NOT NULL,
|
||||||
|
status VARCHAR(50) NOT NULL,
|
||||||
|
completed_dttm TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 인덱스 생성
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_batch_history_job ON public.batch_history(job);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_batch_history_status ON public.batch_history(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_batch_history_created ON public.batch_history(created_dttm DESC);
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. 권한 설정** (필요한 경우):
|
||||||
|
```sql
|
||||||
|
-- 애플리케이션 사용자에게 권한 부여
|
||||||
|
GRANT ALL PRIVILEGES ON TABLE public.batch_history TO [app_user];
|
||||||
|
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO [app_user];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: 자동 초기화 활성화 (개발 환경만)
|
||||||
|
|
||||||
|
**application-local.yml 또는 application-dev.yml**:
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
sql:
|
||||||
|
init:
|
||||||
|
mode: always
|
||||||
|
schema-locations: classpath:sql/schema.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
**주의**:
|
||||||
|
- 운영 환경에서는 `mode: never` 사용 권장
|
||||||
|
- 테이블이 이미 존재하면 권한 에러 발생 가능
|
||||||
|
|
||||||
|
## 테이블 구조
|
||||||
|
|
||||||
|
### batch_history
|
||||||
|
|
||||||
|
| 컬럼명 | 데이터 타입 | NULL | 설명 |
|
||||||
|
|--------|------------|------|------|
|
||||||
|
| uuid | UUID | NOT NULL | 배치 실행 고유 ID (Primary Key) |
|
||||||
|
| job | VARCHAR(255) | NOT NULL | 배치 작업 이름 |
|
||||||
|
| id | VARCHAR(255) | NOT NULL | 비즈니스 ID |
|
||||||
|
| created_dttm | TIMESTAMP | NOT NULL | 생성 일시 |
|
||||||
|
| updated_dttm | TIMESTAMP | NOT NULL | 수정 일시 |
|
||||||
|
| status | VARCHAR(50) | NOT NULL | 상태 (STARTED/COMPLETED/FAILED) |
|
||||||
|
| completed_dttm | TIMESTAMP | NULL | 완료 일시 |
|
||||||
|
|
||||||
|
**인덱스**:
|
||||||
|
- `idx_batch_history_job`: job 컬럼 인덱스
|
||||||
|
- `idx_batch_history_status`: status 컬럼 인덱스
|
||||||
|
- `idx_batch_history_created`: created_dttm 컬럼 인덱스 (DESC)
|
||||||
|
|
||||||
|
## 환경별 설정
|
||||||
|
|
||||||
|
### Local 환경
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://localhost:5432/kamco_local
|
||||||
|
username: dev_user
|
||||||
|
password: dev_password
|
||||||
|
sql:
|
||||||
|
init:
|
||||||
|
mode: always # 자동 초기화 활성화
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production 환경
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://prod-db:5432/kamco_prod
|
||||||
|
username: app_user
|
||||||
|
password: ${DB_PASSWORD}
|
||||||
|
sql:
|
||||||
|
init:
|
||||||
|
mode: never # 자동 초기화 비활성화
|
||||||
|
```
|
||||||
|
|
||||||
|
## 트러블슈팅
|
||||||
|
|
||||||
|
### 에러: "must be owner of table batch_history"
|
||||||
|
|
||||||
|
**원인**: 테이블이 이미 존재하지만, 현재 DB 사용자가 owner가 아님
|
||||||
|
|
||||||
|
**해결**:
|
||||||
|
1. SQL 자동 초기화 비활성화:
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
sql:
|
||||||
|
init:
|
||||||
|
mode: never
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 또는 테이블 소유권 변경:
|
||||||
|
```sql
|
||||||
|
ALTER TABLE public.batch_history OWNER TO [app_user];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 에러: "relation batch_history does not exist"
|
||||||
|
|
||||||
|
**원인**: batch_history 테이블이 생성되지 않음
|
||||||
|
|
||||||
|
**해결**:
|
||||||
|
1. SQL 스크립트 수동 실행:
|
||||||
|
```bash
|
||||||
|
psql -h [host] -U [user] -d [database] -f src/main/resources/sql/schema.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 또는 자동 초기화 활성화 (개발 환경만):
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
sql:
|
||||||
|
init:
|
||||||
|
mode: always
|
||||||
|
schema-locations: classpath:sql/schema.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 에러: "permission denied for schema public"
|
||||||
|
|
||||||
|
**원인**: DB 사용자에게 public 스키마 권한 없음
|
||||||
|
|
||||||
|
**해결**:
|
||||||
|
```sql
|
||||||
|
GRANT ALL ON SCHEMA public TO [app_user];
|
||||||
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO [app_user];
|
||||||
|
```
|
||||||
|
|
||||||
|
## 데이터베이스 마이그레이션
|
||||||
|
|
||||||
|
### 기존 환경에서 테이블 확인
|
||||||
|
```sql
|
||||||
|
-- batch_history 테이블 존재 확인
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'batch_history'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 테이블 구조 확인
|
||||||
|
\d public.batch_history
|
||||||
|
|
||||||
|
-- 인덱스 확인
|
||||||
|
\di public.idx_batch_history_*
|
||||||
|
```
|
||||||
|
|
||||||
|
### 테이블 재생성 (데이터 삭제 주의!)
|
||||||
|
```sql
|
||||||
|
-- 기존 테이블 삭제 (주의: 모든 데이터 손실!)
|
||||||
|
DROP TABLE IF EXISTS public.batch_history CASCADE;
|
||||||
|
|
||||||
|
-- schema.sql 재실행
|
||||||
|
\i src/main/resources/sql/schema.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## 백업 및 복구
|
||||||
|
|
||||||
|
### 테이블 백업
|
||||||
|
```bash
|
||||||
|
pg_dump -h [host] -U [user] -d [database] -t batch_history > batch_history_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 테이블 복구
|
||||||
|
```bash
|
||||||
|
psql -h [host] -U [user] -d [database] < batch_history_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## 모니터링 쿼리
|
||||||
|
|
||||||
|
### 최근 배치 실행 이력 조회
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
uuid,
|
||||||
|
job,
|
||||||
|
id,
|
||||||
|
created_dttm,
|
||||||
|
completed_dttm,
|
||||||
|
status,
|
||||||
|
EXTRACT(EPOCH FROM (completed_dttm - created_dttm)) as duration_seconds
|
||||||
|
FROM public.batch_history
|
||||||
|
ORDER BY created_dttm DESC
|
||||||
|
LIMIT 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 실패한 배치 조회
|
||||||
|
```sql
|
||||||
|
SELECT *
|
||||||
|
FROM public.batch_history
|
||||||
|
WHERE status = 'FAILED'
|
||||||
|
ORDER BY created_dttm DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 배치 작업별 성공률
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
job,
|
||||||
|
COUNT(*) as total_runs,
|
||||||
|
SUM(CASE WHEN status = 'COMPLETED' THEN 1 ELSE 0 END) as successful_runs,
|
||||||
|
ROUND(100.0 * SUM(CASE WHEN status = 'COMPLETED' THEN 1 ELSE 0 END) / COUNT(*), 2) as success_rate
|
||||||
|
FROM public.batch_history
|
||||||
|
GROUP BY job
|
||||||
|
ORDER BY total_runs DESC;
|
||||||
|
```
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
# 폐쇄망 환경 사전 빌드 JAR 배포 가이드
|
||||||
|
|
||||||
|
## 개요
|
||||||
|
인터넷이 차단된 Jenkins 서버에서 빌드 없이 사전에 빌드된 JAR 파일을 사용하여 배포합니다.
|
||||||
|
|
||||||
|
## 아키텍처
|
||||||
|
|
||||||
|
### 기존 방식 (실패)
|
||||||
|
```
|
||||||
|
Jenkins 서버 → Git Clone → Gradle Build (의존성 다운로드 필요) → JAR 생성 → 배포
|
||||||
|
❌ 인터넷 필요
|
||||||
|
```
|
||||||
|
|
||||||
|
### 새로운 방식 (성공)
|
||||||
|
```
|
||||||
|
로컬 PC → Gradle Build → JAR 생성 → Git Commit
|
||||||
|
Jenkins 서버 → Git Clone → JAR 사용 (빌드 불필요) → 배포
|
||||||
|
✅ 인터넷 불필요
|
||||||
|
```
|
||||||
|
|
||||||
|
## 장점
|
||||||
|
|
||||||
|
1. **인터넷 불필요**: Jenkins 서버가 완전히 폐쇄망이어도 동작
|
||||||
|
2. **빠른 배포**: 빌드 시간 불필요 (~3분 → ~10초)
|
||||||
|
3. **간단한 설정**: 복잡한 Gradle cache나 local repository 불필요
|
||||||
|
4. **일관성**: 로컬에서 테스트한 동일한 JAR 파일 배포
|
||||||
|
|
||||||
|
## 단점
|
||||||
|
|
||||||
|
1. **Git 용량 증가**: JAR 파일(~19MB)이 버전 관리됨
|
||||||
|
2. **로컬 빌드 필요**: 코드 변경 시 로컬에서 빌드 후 커밋 필요
|
||||||
|
3. **바이너리 커밋**: Git에 바이너리 파일 포함
|
||||||
|
|
||||||
|
## 사용 방법
|
||||||
|
|
||||||
|
### 1. 로컬 환경 (인터넷 연결 가능한 PC)
|
||||||
|
|
||||||
|
#### 코드 변경 시
|
||||||
|
```bash
|
||||||
|
cd kamco-make-dataset-generation
|
||||||
|
|
||||||
|
# 1. 코드 수정
|
||||||
|
# ... 코드 변경 ...
|
||||||
|
|
||||||
|
# 2. JAR 빌드
|
||||||
|
./gradlew clean bootJar
|
||||||
|
|
||||||
|
# 3. JAR 파일 확인
|
||||||
|
ls -lh build/libs/generator-dataset-for-training.jar
|
||||||
|
|
||||||
|
# 4. Git 커밋
|
||||||
|
git add .
|
||||||
|
git add -f build/libs/generator-dataset-for-training.jar
|
||||||
|
git commit -m "Update: 기능 추가 및 JAR 업데이트"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Jenkins 서버 (폐쇄망 환경)
|
||||||
|
|
||||||
|
#### Jenkins 파라미터
|
||||||
|
|
||||||
|
파이프라인 실행 시 다음 파라미터를 설정할 수 있습니다:
|
||||||
|
|
||||||
|
| 파라미터 | 타입 | 기본값 | 설명 |
|
||||||
|
|---------|------|--------|------|
|
||||||
|
| SPRING_PROFILES_ACTIVE | String | prod | Spring Profile (dev/prod) |
|
||||||
|
| BATCH_DATE | String | (공백) | Batch 날짜 (YYYY-MM-DD, 공백 = 오늘) |
|
||||||
|
| ADDITIONAL_PARAMS | String | (공백) | 추가 파라미터 (예: limit=100) |
|
||||||
|
| ACTION | Choice | RUN | 실행 모드 (RUN / VERIFY_ONLY) |
|
||||||
|
|
||||||
|
#### 실행 예시
|
||||||
|
|
||||||
|
**1. 기본 실행 (오늘 날짜)**:
|
||||||
|
```
|
||||||
|
ACTION: RUN
|
||||||
|
SPRING_PROFILES_ACTIVE: prod
|
||||||
|
BATCH_DATE: (공백) ← 자동으로 오늘 날짜 사용
|
||||||
|
ADDITIONAL_PARAMS: (공백)
|
||||||
|
|
||||||
|
→ java -jar generator-dataset-for-training.jar --spring.profiles.active=prod date=2026-02-09
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. 특정 날짜로 실행**:
|
||||||
|
```
|
||||||
|
ACTION: RUN
|
||||||
|
SPRING_PROFILES_ACTIVE: prod
|
||||||
|
BATCH_DATE: 2024-01-15
|
||||||
|
ADDITIONAL_PARAMS: (공백)
|
||||||
|
|
||||||
|
→ java -jar generator-dataset-for-training.jar --spring.profiles.active=prod date=2024-01-15
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. 오늘 날짜 + 추가 파라미터**:
|
||||||
|
```
|
||||||
|
ACTION: RUN
|
||||||
|
SPRING_PROFILES_ACTIVE: prod
|
||||||
|
BATCH_DATE: (공백)
|
||||||
|
ADDITIONAL_PARAMS: limit=100 debug=true
|
||||||
|
|
||||||
|
→ java -jar generator-dataset-for-training.jar --spring.profiles.active=prod date=2026-02-09 limit=100 debug=true
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. 개발 환경 테스트**:
|
||||||
|
```
|
||||||
|
ACTION: RUN
|
||||||
|
SPRING_PROFILES_ACTIVE: dev
|
||||||
|
BATCH_DATE: 2024-02-01
|
||||||
|
ADDITIONAL_PARAMS: dryRun=true
|
||||||
|
|
||||||
|
→ java -jar generator-dataset-for-training.jar --spring.profiles.active=dev date=2024-02-01 dryRun=true
|
||||||
|
```
|
||||||
|
|
||||||
|
**5. JAR 검증만 (실행 안 함)**:
|
||||||
|
```
|
||||||
|
ACTION: VERIFY_ONLY
|
||||||
|
→ JAR 파일 존재만 확인하고 종료
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 날짜 파라미터 동작 방식
|
||||||
|
|
||||||
|
**BATCH_DATE 파라미터**:
|
||||||
|
- **비어있음 (기본값)**: Jenkins 서버의 현재 날짜 자동 사용 (YYYY-MM-DD)
|
||||||
|
- **값 입력**: 입력한 날짜 사용 (예: 2024-01-15)
|
||||||
|
|
||||||
|
**예시**:
|
||||||
|
```bash
|
||||||
|
# BATCH_DATE가 비어있으면
|
||||||
|
TODAY=$(date +%Y-%m-%d) # 2026-02-09
|
||||||
|
java -jar app.jar date=2026-02-09
|
||||||
|
|
||||||
|
# BATCH_DATE에 2024-01-15 입력하면
|
||||||
|
java -jar app.jar date=2024-01-15
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Jenkins 파이프라인 단계
|
||||||
|
|
||||||
|
Jenkins는 자동으로:
|
||||||
|
1. **Environment 설정**: 오늘 날짜 계산 (TODAY 변수)
|
||||||
|
2. **Checkout**: Git에서 코드 체크아웃
|
||||||
|
3. **Verify JAR**: JAR 파일 존재 확인
|
||||||
|
4. **Run JAR** (ACTION=RUN인 경우만):
|
||||||
|
- BATCH_DATE가 비어있으면 TODAY 사용
|
||||||
|
- JAR 실행 with 파라미터
|
||||||
|
|
||||||
|
## 파일 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
kamco-make-dataset-generation/
|
||||||
|
├── build/
|
||||||
|
│ └── libs/
|
||||||
|
│ └── generator-dataset-for-training.jar ← Git에 포함됨
|
||||||
|
├── src/
|
||||||
|
├── build.gradle
|
||||||
|
├── Jenkinsfile
|
||||||
|
└── .gitignore ← build/libs/*.jar 예외 처리
|
||||||
|
```
|
||||||
|
|
||||||
|
## .gitignore 설정
|
||||||
|
|
||||||
|
```gitignore
|
||||||
|
# Gradle
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
!build/libs/ # libs 디렉토리는 추적
|
||||||
|
!build/libs/*.jar # jar 파일은 추적
|
||||||
|
out/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Jenkinsfile 구조
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
parameters {
|
||||||
|
string(name: 'SPRING_PROFILES_ACTIVE', defaultValue: 'prod', ...)
|
||||||
|
string(name: 'BATCH_DATE', defaultValue: '', ...)
|
||||||
|
string(name: 'ADDITIONAL_PARAMS', defaultValue: '', ...)
|
||||||
|
choice(name: 'ACTION', choices: ['RUN', 'VERIFY_ONLY'], ...)
|
||||||
|
}
|
||||||
|
|
||||||
|
environment {
|
||||||
|
TODAY = sh(script: "date +%Y-%m-%d", returnStdout: true).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Verify JAR') {
|
||||||
|
// JAR 파일 존재 확인
|
||||||
|
// 파일 정보 출력
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Run JAR') {
|
||||||
|
when {
|
||||||
|
expression { params.ACTION == 'RUN' }
|
||||||
|
}
|
||||||
|
// 날짜 결정: BATCH_DATE가 비어있으면 TODAY 사용
|
||||||
|
def batchDate = params.BATCH_DATE ?: env.TODAY
|
||||||
|
// JAR 실행
|
||||||
|
// java -jar generator-dataset-for-training.jar --spring.profiles.active=prod date=${batchDate} ${ADDITIONAL_PARAMS}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 배포 설정
|
||||||
|
|
||||||
|
Deploy stage에 실제 배포 명령을 추가하세요:
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
stage('Deploy') {
|
||||||
|
steps {
|
||||||
|
dir("kamco-make-dataset-generation") {
|
||||||
|
script {
|
||||||
|
def jarPath = "build/libs/${env.JAR_NAME}"
|
||||||
|
|
||||||
|
// 예시 1: SCP로 서버 전송
|
||||||
|
sh "scp ${jarPath} user@target-server:/app/lib/"
|
||||||
|
|
||||||
|
// 예시 2: SSH로 서비스 재시작
|
||||||
|
sh "ssh user@target-server 'systemctl restart kamco-app'"
|
||||||
|
|
||||||
|
// 예시 3: Docker 이미지 빌드
|
||||||
|
sh "docker build -t kamco-app:${env.COMMIT_HASH} ."
|
||||||
|
sh "docker push kamco-app:${env.COMMIT_HASH}"
|
||||||
|
|
||||||
|
// 예시 4: Kubernetes 배포
|
||||||
|
sh "kubectl set image deployment/kamco-app kamco-app=kamco-app:${env.COMMIT_HASH}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 트러블슈팅
|
||||||
|
|
||||||
|
### JAR 파일이 Git에 추가되지 않음
|
||||||
|
|
||||||
|
**문제**: `git status`에서 JAR 파일이 보이지 않음
|
||||||
|
|
||||||
|
**해결**:
|
||||||
|
```bash
|
||||||
|
# Force add로 추가
|
||||||
|
git add -f build/libs/generator-dataset-for-training.jar
|
||||||
|
|
||||||
|
# .gitignore 확인
|
||||||
|
cat .gitignore | grep build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Jenkins에서 JAR 파일을 찾을 수 없음
|
||||||
|
|
||||||
|
**문제**: "JAR file not found" 에러
|
||||||
|
|
||||||
|
**해결**:
|
||||||
|
```bash
|
||||||
|
# 로컬에서 JAR 커밋 확인
|
||||||
|
git log --all -- build/libs/generator-dataset-for-training.jar
|
||||||
|
|
||||||
|
# JAR가 커밋되었는지 확인
|
||||||
|
git ls-tree -r HEAD --name-only | grep jar
|
||||||
|
|
||||||
|
# 커밋되지 않았다면 다시 커밋
|
||||||
|
git add -f build/libs/generator-dataset-for-training.jar
|
||||||
|
git commit -m "Add pre-built JAR file"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
### JAR 파일 크기가 너무 큼
|
||||||
|
|
||||||
|
**문제**: Git repository 크기 증가 우려
|
||||||
|
|
||||||
|
**해결**:
|
||||||
|
1. 이 방법이 적절하지 않다면 Gradle cache 방법 사용
|
||||||
|
2. Git LFS(Large File Storage) 사용 고려
|
||||||
|
3. Artifactory/Nexus 같은 artifact repository 구축
|
||||||
|
|
||||||
|
## 의존성 업데이트
|
||||||
|
|
||||||
|
새로운 의존성 추가 시:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. build.gradle 수정
|
||||||
|
# 2. 로컬에서 빌드
|
||||||
|
./gradlew clean bootJar
|
||||||
|
|
||||||
|
# 3. 새 JAR 커밋
|
||||||
|
git add build.gradle
|
||||||
|
git add -f build/libs/generator-dataset-for-training.jar
|
||||||
|
git commit -m "Add new dependency: spring-boot-starter-security"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
## 버전 관리 전략
|
||||||
|
|
||||||
|
### Git 커밋 메시지 예시
|
||||||
|
```bash
|
||||||
|
# 기능 추가
|
||||||
|
git commit -m "feat: 사용자 인증 기능 추가 및 JAR 업데이트"
|
||||||
|
|
||||||
|
# 버그 수정
|
||||||
|
git commit -m "fix: 데이터 처리 버그 수정 및 JAR 업데이트"
|
||||||
|
|
||||||
|
# JAR만 업데이트
|
||||||
|
git commit -m "build: JAR 재빌드 (의존성 업데이트)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 태그 사용
|
||||||
|
```bash
|
||||||
|
# 릴리스 태그
|
||||||
|
git tag -a v1.0.0 -m "Release version 1.0.0"
|
||||||
|
git push origin v1.0.0
|
||||||
|
|
||||||
|
# Jenkins에서 특정 버전 배포
|
||||||
|
git checkout v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## 성능 비교
|
||||||
|
|
||||||
|
| 항목 | 기존 방식 | 새로운 방식 |
|
||||||
|
|------|----------|------------|
|
||||||
|
| 빌드 시간 | ~3분 | 없음 (~10초) |
|
||||||
|
| 인터넷 필요 | 필요 | 불필요 |
|
||||||
|
| 디스크 사용 | ~500MB (Gradle cache) | ~19MB (JAR) |
|
||||||
|
| 설정 복잡도 | 높음 | 낮음 |
|
||||||
|
| 일관성 | 빌드 환경에 따라 다름 | 동일한 JAR |
|
||||||
|
|
||||||
|
## 주의사항
|
||||||
|
|
||||||
|
1. **JAR 파일 크기**: 매 커밋마다 19MB 추가 (Git history 증가)
|
||||||
|
2. **빌드 책임**: 개발자가 로컬에서 빌드 책임
|
||||||
|
3. **테스트 중요성**: 로컬에서 충분한 테스트 후 커밋
|
||||||
|
4. **Git LFS 고려**: JAR 파일이 자주 변경되면 Git LFS 사용 검토
|
||||||
|
|
||||||
|
## 대안 방법
|
||||||
|
|
||||||
|
이 방법이 적합하지 않은 경우:
|
||||||
|
|
||||||
|
### 1. Gradle Cache 방법
|
||||||
|
- ~/.gradle 디렉토리를 Jenkins 서버로 복사
|
||||||
|
- 한 번만 설정, 이후 offline 빌드
|
||||||
|
|
||||||
|
### 2. Artifact Repository
|
||||||
|
- Nexus, Artifactory 구축
|
||||||
|
- 폐쇄망 내부에 Maven repository 운영
|
||||||
|
|
||||||
|
### 3. Docker Image
|
||||||
|
- Docker image에 JAR 포함
|
||||||
|
- Docker registry 사용
|
||||||
|
|
||||||
|
## 참고 자료
|
||||||
|
|
||||||
|
- Spring Boot Documentation: https://docs.spring.io/spring-boot/
|
||||||
|
- Gradle Documentation: https://docs.gradle.org/
|
||||||
|
- Git LFS: https://git-lfs.github.com/
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,11 +0,0 @@
|
|||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: jdbc:postgresql://192.168.2.127:15432/kamco_cds
|
|
||||||
username: kamco_cds
|
|
||||||
password: kamco_cds_Q!W@E#R$
|
|
||||||
hikari:
|
|
||||||
minimum-idle: 2
|
|
||||||
maximum-pool-size: 5
|
|
||||||
|
|
||||||
training-data:
|
|
||||||
geojson-dir: /kamco-nfs/model_output/labeling/
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: jdbc:postgresql://localhost:5432/kamco_cds
|
|
||||||
username: kamco_cds
|
|
||||||
password: kamco_cds
|
|
||||||
|
|
||||||
training-data:
|
|
||||||
geojson-dir: /tmp/geojson
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: jdbc:postgresql://10.100.0.10:25432/temp
|
|
||||||
username: temp
|
|
||||||
password: temp123!
|
|
||||||
hikari:
|
|
||||||
minimum-idle: 2
|
|
||||||
maximum-pool-size: 5
|
|
||||||
|
|
||||||
training-data:
|
|
||||||
geojson-dir: /kamco-nfs/model_output/labeling/
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
spring:
|
|
||||||
application:
|
|
||||||
name: kamco-geojson-scheduler
|
|
||||||
profiles:
|
|
||||||
active: local
|
|
||||||
datasource:
|
|
||||||
driver-class-name: org.postgresql.Driver
|
|
||||||
hikari:
|
|
||||||
minimum-idle: 2
|
|
||||||
maximum-pool-size: 2
|
|
||||||
connection-timeout: 20000
|
|
||||||
idle-timeout: 300000
|
|
||||||
max-lifetime: 1800000
|
|
||||||
batch:
|
|
||||||
job:
|
|
||||||
enabled: true
|
|
||||||
initialize-schema: always
|
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
package com.kamco.cd.geojsonscheduler.batch;
|
package com.kamco.cd.geojsonscheduler.batch;
|
||||||
|
|
||||||
|
import com.kamco.cd.geojsonscheduler.listener.BatchHistoryListener;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.batch.core.Job;
|
import org.springframework.batch.core.Job;
|
||||||
import org.springframework.batch.core.Step;
|
import org.springframework.batch.core.Step;
|
||||||
|
import org.springframework.batch.core.StepExecutionListener;
|
||||||
import org.springframework.batch.core.job.builder.JobBuilder;
|
import org.springframework.batch.core.job.builder.JobBuilder;
|
||||||
import org.springframework.batch.core.repository.JobRepository;
|
import org.springframework.batch.core.repository.JobRepository;
|
||||||
import org.springframework.batch.core.step.builder.StepBuilder;
|
import org.springframework.batch.core.step.builder.StepBuilder;
|
||||||
@@ -19,8 +21,11 @@ public class ExportGeoJsonJobConfig {
|
|||||||
private final ExportGeoJsonTasklet exportGeoJsonTasklet;
|
private final ExportGeoJsonTasklet exportGeoJsonTasklet;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public Job exportGeoJsonJob() {
|
public Job exportGeoJsonJob(BatchHistoryListener historyListener) { // 1. 리스너 주입 받기
|
||||||
return new JobBuilder("exportGeoJsonJob", jobRepository).start(exportGeoJsonStep()).build();
|
return new JobBuilder("exportGeoJsonJob", jobRepository)
|
||||||
|
.listener(historyListener) // 2. 리스너 등록
|
||||||
|
.start(exportGeoJsonStep())
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -38,63 +38,115 @@ public class ExportGeoJsonTasklet implements Tasklet {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
|
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
|
||||||
|
log.info("========================================");
|
||||||
|
log.info("배치 작업 시작");
|
||||||
|
log.info("========================================");
|
||||||
|
|
||||||
|
// 1. StepContext를 통해 바로 가져오기 (가장 추천)
|
||||||
|
String jobName = chunkContext.getStepContext().getJobName();
|
||||||
|
log.info("Job Name: {}", jobName);
|
||||||
|
|
||||||
// 진행중인 회차 중, complete_cnt 가 존재하는 회차 목록 가져오기
|
// 진행중인 회차 중, complete_cnt 가 존재하는 회차 목록 가져오기
|
||||||
|
log.info("진행중인 회차 목록 조회 중...");
|
||||||
List<AnalCntInfo> analList = repository.findAnalCntInfoList();
|
List<AnalCntInfo> analList = repository.findAnalCntInfoList();
|
||||||
|
log.info("진행중인 회차 수: {}", analList.size());
|
||||||
|
|
||||||
|
int processedAnalCount = 0;
|
||||||
for (AnalCntInfo info : analList) {
|
for (AnalCntInfo info : analList) {
|
||||||
|
log.info("----------------------------------------");
|
||||||
|
log.info("회차 처리 중: AnalUid={}, ResultUid={}", info.getAnalUid(), info.getResultUid());
|
||||||
|
log.info("전체 건수: {}, 파일 건수: {}", info.getAllCnt(), info.getFileCnt());
|
||||||
|
|
||||||
if (Objects.equals(info.getAllCnt(), info.getFileCnt())) {
|
if (Objects.equals(info.getAllCnt(), info.getFileCnt())) {
|
||||||
|
log.info("모든 파일이 이미 처리됨. 건너뜀.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//추론 ID
|
||||||
String resultUid = info.getResultUid();
|
String resultUid = info.getResultUid();
|
||||||
|
log.info("ResultUid: {}", resultUid);
|
||||||
|
|
||||||
|
//insert 하기 jobname, resultUid , 시작시간
|
||||||
// 어제까지 검수 완료된 총 데이터의 도엽별 목록 가져오기
|
// 어제까지 검수 완료된 총 데이터의 도엽별 목록 가져오기
|
||||||
|
log.info("검수 완료된 도엽 목록 조회 중... (AnalUid={})", info.getAnalUid());
|
||||||
List<AnalMapSheetList> analMapList = repository.findCompletedAnalMapSheetList(info.getAnalUid());
|
List<AnalMapSheetList> analMapList = repository.findCompletedAnalMapSheetList(info.getAnalUid());
|
||||||
|
log.info("검수 완료된 도엽 수: {}", analMapList.size());
|
||||||
|
|
||||||
//TODO 도엽이 4개이상 존재할때 만 RUN 하기
|
//TODO 도엽이 4개이상 존재할때 만 RUN 하기
|
||||||
if (analMapList.isEmpty()) {
|
if (analMapList.isEmpty()) {
|
||||||
|
log.warn("검수 완료된 도엽이 없음. 건너뜀.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//insert 하기 jobname, resultUid , 시작시간
|
||||||
boolean anyProcessed = false;
|
boolean anyProcessed = false;
|
||||||
|
int processedMapSheetCount = 0;
|
||||||
|
int totalGeoJsonFiles = 0;
|
||||||
|
|
||||||
for (AnalMapSheetList mapSheet : analMapList) {
|
for (AnalMapSheetList mapSheet : analMapList) {
|
||||||
|
log.info(" 도엽 처리 중: MapSheetNum={}", mapSheet.getMapSheetNum());
|
||||||
|
|
||||||
//도엽별 geom 데이터 가지고 와서 geojson 만들기
|
//도엽별 geom 데이터 가지고 와서 geojson 만들기
|
||||||
List<CompleteLabelData> completeList =
|
List<CompleteLabelData> completeList =
|
||||||
repository.findCompletedYesterdayLabelingList(
|
repository.findCompletedYesterdayLabelingList(
|
||||||
info.getAnalUid(), mapSheet.getMapSheetNum());
|
info.getAnalUid(), mapSheet.getMapSheetNum());
|
||||||
|
log.info(" 완료된 라벨링 데이터 수: {}", completeList.size());
|
||||||
|
|
||||||
if (!completeList.isEmpty()) {
|
if (!completeList.isEmpty()) {
|
||||||
List<Long> geoUids = completeList.stream().map(CompleteLabelData::getGeoUid).toList();
|
List<Long> geoUids = completeList.stream().map(CompleteLabelData::getGeoUid).toList();
|
||||||
|
log.info(" GeoUID 목록 생성 완료: {} 건", geoUids.size());
|
||||||
|
|
||||||
List<GeoJsonFeature> features = completeList.stream().map(GeoJsonFeature::from).toList();
|
List<GeoJsonFeature> features = completeList.stream().map(GeoJsonFeature::from).toList();
|
||||||
|
log.info(" GeoJSON Feature 변환 완료: {} 개", features.size());
|
||||||
|
|
||||||
FeatureCollection collection = new FeatureCollection(features);
|
FeatureCollection collection = new FeatureCollection(features);
|
||||||
String filename = mapSheet.buildFilename(resultUid);
|
String filename = mapSheet.buildFilename(resultUid);
|
||||||
|
log.info(" GeoJSON 파일명: {}", filename);
|
||||||
|
|
||||||
// 형식 /kamco-nfs/dataset/request/uuid/filename
|
// 형식 /kamco-nfs/dataset/request/uuid/filename
|
||||||
Path outputPath = Paths.get(trainingDataDir + File.separator + "request" + File.separator + resultUid, filename);
|
Path outputPath = Paths.get(trainingDataDir + File.separator + "request" + File.separator + resultUid, filename);
|
||||||
|
log.info(" 출력 경로: {}", outputPath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(outputPath.getParent());
|
Files.createDirectories(outputPath.getParent());
|
||||||
|
log.info(" 디렉토리 생성 완료: {}", outputPath.getParent());
|
||||||
|
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
|
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
objectMapper.writeValue(outputPath.toFile(), collection);
|
objectMapper.writeValue(outputPath.toFile(), collection);
|
||||||
|
log.info(" GeoJSON 파일 저장 완료: {}", outputPath);
|
||||||
|
|
||||||
repository.updateLearnDataGeomFileCreateYn(geoUids);
|
repository.updateLearnDataGeomFileCreateYn(geoUids);
|
||||||
|
log.info(" DB 업데이트 완료: {} 건", geoUids.size());
|
||||||
|
|
||||||
anyProcessed = true;
|
anyProcessed = true;
|
||||||
|
processedMapSheetCount++;
|
||||||
|
totalGeoJsonFiles++;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error(e.getMessage());
|
log.error(" GeoJSON 파일 생성 실패: {}", e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info("회차 처리 완료: ResultUid={}", resultUid);
|
||||||
|
log.info(" 처리된 도엽 수: {}", processedMapSheetCount);
|
||||||
|
log.info(" 생성된 GeoJSON 파일 수: {}", totalGeoJsonFiles);
|
||||||
|
|
||||||
if (anyProcessed) {
|
if (anyProcessed) {
|
||||||
|
log.info("Docker 컨테이너 실행 중... (ResultUid={})", resultUid);
|
||||||
dockerRunnerService.run(resultUid);
|
dockerRunnerService.run(resultUid);
|
||||||
|
log.info("Docker 컨테이너 실행 완료 (ResultUid={})", resultUid);
|
||||||
|
processedAnalCount++;
|
||||||
|
} else {
|
||||||
|
log.warn("처리된 도엽이 없어 Docker 실행 건너뜀 (ResultUid={})", resultUid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info("========================================");
|
||||||
|
log.info("배치 작업 완료");
|
||||||
|
log.info("처리된 회차 수: {}", processedAnalCount);
|
||||||
|
log.info("========================================");
|
||||||
|
|
||||||
return RepeatStatus.FINISHED;
|
return RepeatStatus.FINISHED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.kamco.cd.geojsonscheduler.listener;
|
||||||
|
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.springframework.batch.core.JobExecution;
|
||||||
|
import org.springframework.batch.core.JobExecutionListener;
|
||||||
|
import org.springframework.batch.core.BatchStatus;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Log4j2
|
||||||
|
@Component
|
||||||
|
public class BatchHistoryListener implements JobExecutionListener {
|
||||||
|
|
||||||
|
private final BatchHistoryService batchHistoryService;
|
||||||
|
|
||||||
|
public BatchHistoryListener(BatchHistoryService batchHistoryService) {
|
||||||
|
this.batchHistoryService = batchHistoryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeJob(JobExecution jobExecution) {
|
||||||
|
log.info("=========================================================");
|
||||||
|
log.info("배치 Job 시작 - BatchHistoryListener");
|
||||||
|
log.info("=========================================================");
|
||||||
|
|
||||||
|
// 1. UUID 생성 (또는 파라미터에서 가져오기)
|
||||||
|
UUID uuid = UUID.randomUUID();
|
||||||
|
log.info("배치 UUID 생성: {}", uuid);
|
||||||
|
|
||||||
|
// 2. JobExecutionContext에 UUID 저장 (afterJob에서 쓰기 위해)
|
||||||
|
jobExecution.getExecutionContext().put("batch_uuid", uuid);
|
||||||
|
|
||||||
|
// 3. Job 이름과 비즈니스 ID(파라미터 등) 가져오기
|
||||||
|
String jobName = jobExecution.getJobInstance().getJobName();
|
||||||
|
String businessId = jobExecution.getJobParameters().getString("id", "UNKNOWN"); // 파라미터 'id'가 있다고 가정
|
||||||
|
log.info("Job Name: {}", jobName);
|
||||||
|
log.info("Business ID: {}", businessId);
|
||||||
|
log.info("Job Instance ID: {}", jobExecution.getJobInstance().getInstanceId());
|
||||||
|
log.info("Job Execution ID: {}", jobExecution.getId());
|
||||||
|
|
||||||
|
// 4. 시작 기록
|
||||||
|
log.info("batch_history 테이블에 시작 기록 저장 중...");
|
||||||
|
batchHistoryService.startBatch(uuid, jobName, businessId);
|
||||||
|
log.info("배치 시작 기록 저장 완료");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterJob(JobExecution jobExecution) {
|
||||||
|
log.info("=========================================================");
|
||||||
|
log.info("배치 Job 종료 - BatchHistoryListener");
|
||||||
|
log.info("=========================================================");
|
||||||
|
|
||||||
|
// 1. 저장해둔 UUID 꺼내기
|
||||||
|
UUID uuid = (UUID) jobExecution.getExecutionContext().get("batch_uuid");
|
||||||
|
log.info("배치 UUID: {}", uuid);
|
||||||
|
|
||||||
|
// 2. 성공 여부 판단 (COMPLETED면 성공, 그 외 실패)
|
||||||
|
boolean isSuccess = jobExecution.getStatus() == BatchStatus.COMPLETED;
|
||||||
|
log.info("배치 상태: {}", jobExecution.getStatus());
|
||||||
|
log.info("배치 성공 여부: {}", isSuccess ? "성공" : "실패");
|
||||||
|
|
||||||
|
if (jobExecution.getStatus() == BatchStatus.FAILED) {
|
||||||
|
log.error("배치 실행 실패!");
|
||||||
|
jobExecution.getAllFailureExceptions().forEach(t ->
|
||||||
|
log.error("실패 원인: {}", t.getMessage(), t)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 실행 시간 계산
|
||||||
|
if (jobExecution.getStartTime() != null && jobExecution.getEndTime() != null) {
|
||||||
|
Duration duration = Duration.between(jobExecution.getStartTime(), jobExecution.getEndTime());
|
||||||
|
long seconds = duration.getSeconds();
|
||||||
|
long millis = duration.toMillis();
|
||||||
|
log.info("배치 실행 시간: {} ms ({} 초)", millis, seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 종료 기록
|
||||||
|
if (uuid != null) {
|
||||||
|
log.info("batch_history 테이블에 종료 기록 저장 중...");
|
||||||
|
batchHistoryService.finishBatch(uuid, isSuccess);
|
||||||
|
log.info("배치 종료 기록 저장 완료");
|
||||||
|
} else {
|
||||||
|
log.warn("배치 UUID가 없어 종료 기록을 저장할 수 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("=========================================================");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package com.kamco.cd.geojsonscheduler.listener;
|
||||||
|
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Log4j2
|
||||||
|
@Service
|
||||||
|
public class BatchHistoryService {
|
||||||
|
|
||||||
|
private final JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
|
public BatchHistoryService(JdbcTemplate jdbcTemplate) {
|
||||||
|
this.jdbcTemplate = jdbcTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배치 시작 시 이력 저장 (INSERT)
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public void startBatch(UUID uuid, String jobName, String businessId) {
|
||||||
|
log.info("[BatchHistoryService] 배치 시작 기록 저장");
|
||||||
|
log.info(" UUID: {}", uuid);
|
||||||
|
log.info(" Job Name: {}", jobName);
|
||||||
|
log.info(" Business ID: {}", businessId);
|
||||||
|
|
||||||
|
String sql = """
|
||||||
|
INSERT INTO public.batch_history
|
||||||
|
(uuid, job, id, created_dttm, updated_dttm, status)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
""";
|
||||||
|
|
||||||
|
Timestamp now = Timestamp.valueOf(LocalDateTime.now());
|
||||||
|
log.info(" 시작 시간: {}", now);
|
||||||
|
|
||||||
|
// 초기 상태는 'STARTED'로 저장
|
||||||
|
int rowsAffected = jdbcTemplate.update(sql,
|
||||||
|
uuid,
|
||||||
|
jobName,
|
||||||
|
businessId,
|
||||||
|
now, // created_dttm
|
||||||
|
now, // updated_dttm
|
||||||
|
"STARTED"
|
||||||
|
);
|
||||||
|
|
||||||
|
log.info("[BatchHistoryService] 배치 시작 기록 저장 완료 ({} rows affected)", rowsAffected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배치 종료 시 이력 업데이트 (UPDATE)
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public void finishBatch(UUID uuid, boolean isSuccess) {
|
||||||
|
log.info("[BatchHistoryService] 배치 종료 기록 업데이트");
|
||||||
|
log.info(" UUID: {}", uuid);
|
||||||
|
log.info(" 성공 여부: {}", isSuccess);
|
||||||
|
|
||||||
|
String sql = """
|
||||||
|
UPDATE public.batch_history
|
||||||
|
SET status = ?,
|
||||||
|
updated_dttm = ?,
|
||||||
|
completed_dttm = ?
|
||||||
|
WHERE uuid = ?
|
||||||
|
""";
|
||||||
|
|
||||||
|
Timestamp now = Timestamp.valueOf(LocalDateTime.now());
|
||||||
|
String status = isSuccess ? "COMPLETED" : "FAILED";
|
||||||
|
log.info(" 완료 시간: {}", now);
|
||||||
|
log.info(" 최종 상태: {}", status);
|
||||||
|
|
||||||
|
int rowsAffected = jdbcTemplate.update(sql,
|
||||||
|
status,
|
||||||
|
now, // updated_dttm (마지막 변경 시간)
|
||||||
|
now, // completed_dttm (완료 시간)
|
||||||
|
uuid
|
||||||
|
);
|
||||||
|
|
||||||
|
if (rowsAffected > 0) {
|
||||||
|
log.info("[BatchHistoryService] 배치 종료 기록 업데이트 완료 ({} rows affected)", rowsAffected);
|
||||||
|
} else {
|
||||||
|
log.warn("[BatchHistoryService] 업데이트된 row가 없습니다. UUID가 존재하지 않을 수 있습니다: {}", uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -118,7 +118,7 @@ public class TrainingDataReviewJobRepository {
|
|||||||
public void updateLearnDataGeomFileCreateYn(List<Long> geoUids) {
|
public void updateLearnDataGeomFileCreateYn(List<Long> geoUids) {
|
||||||
String placeholders = geoUids.stream().map(id -> "?").collect(Collectors.joining(","));
|
String placeholders = geoUids.stream().map(id -> "?").collect(Collectors.joining(","));
|
||||||
String sql =
|
String sql =
|
||||||
"UPDATE tb_map_sheet_learn_data_geom SET file_create_yn = true, modified_date = NOW()"
|
"UPDATE tb_map_sheet_learn_data_geom SET file_create_yn = true, updated_dttm = NOW()"
|
||||||
+ " WHERE geo_uid IN ("
|
+ " WHERE geo_uid IN ("
|
||||||
+ placeholders
|
+ placeholders
|
||||||
+ ")";
|
+ ")";
|
||||||
|
|||||||
@@ -11,10 +11,18 @@ spring:
|
|||||||
connection-timeout: 20000
|
connection-timeout: 20000
|
||||||
idle-timeout: 300000
|
idle-timeout: 300000
|
||||||
max-lifetime: 1800000
|
max-lifetime: 1800000
|
||||||
|
sql:
|
||||||
|
init:
|
||||||
|
mode: never
|
||||||
|
# schema-locations: classpath:sql/schema.sql
|
||||||
|
# Note: batch_history 테이블이 이미 존재하므로 SQL 초기화 비활성화
|
||||||
|
# 새 환경에서 테이블 생성이 필요한 경우, 아래 SQL을 수동으로 실행:
|
||||||
|
# src/main/resources/sql/schema.sql
|
||||||
batch:
|
batch:
|
||||||
job:
|
job:
|
||||||
enabled: true
|
enabled: true
|
||||||
initialize-schema: never
|
jdbc:
|
||||||
|
initialize-schema: always
|
||||||
|
|
||||||
training-data:
|
training-data:
|
||||||
docker:
|
docker:
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
-- batch_history 테이블 생성
|
||||||
|
CREATE TABLE IF NOT EXISTS public.batch_history (
|
||||||
|
uuid UUID PRIMARY KEY,
|
||||||
|
job VARCHAR(255) NOT NULL,
|
||||||
|
id VARCHAR(255) NOT NULL,
|
||||||
|
created_dttm TIMESTAMP NOT NULL,
|
||||||
|
updated_dttm TIMESTAMP NOT NULL,
|
||||||
|
status VARCHAR(50) NOT NULL,
|
||||||
|
completed_dttm TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 인덱스 생성 (조회 성능 향상)
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_batch_history_job ON public.batch_history(job);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_batch_history_status ON public.batch_history(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_batch_history_created ON public.batch_history(created_dttm DESC);
|
||||||
|
|
||||||
|
-- 코멘트
|
||||||
|
COMMENT ON TABLE public.batch_history IS '배치 작업 실행 이력';
|
||||||
|
COMMENT ON COLUMN public.batch_history.uuid IS '배치 실행 고유 ID';
|
||||||
|
COMMENT ON COLUMN public.batch_history.job IS '배치 작업 이름';
|
||||||
|
COMMENT ON COLUMN public.batch_history.id IS '비즈니스 ID';
|
||||||
|
COMMENT ON COLUMN public.batch_history.created_dttm IS '생성 일시';
|
||||||
|
COMMENT ON COLUMN public.batch_history.updated_dttm IS '수정 일시';
|
||||||
|
COMMENT ON COLUMN public.batch_history.status IS '상태 (STARTED/COMPLETED/FAILED)';
|
||||||
|
COMMENT ON COLUMN public.batch_history.completed_dttm IS '완료 일시';
|
||||||
Reference in New Issue
Block a user