feat: ������ ���� (t_std_snp_data �� std_snp_data, t_std_snp_svc �� std_snp_svc) #24
@ -4,7 +4,7 @@
|
||||
|
||||
## 기술 스택
|
||||
- Java 17, Spring Boot 3.2.1, Spring Batch 5.1.0
|
||||
- PostgreSQL (스키마: t_std_snp_data)
|
||||
- PostgreSQL (스키마: std_snp_data)
|
||||
- Quartz Scheduler (JDBC Store)
|
||||
- Spring Kafka (AIS Target → Kafka 파이프라인)
|
||||
- WebFlux WebClient (외부 API 호출)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
-- ============================================================
|
||||
-- ChnPrmShip 캐시 검증 진단 쿼리
|
||||
-- 대상: t_std_snp_data.ais_target (일별 파티션)
|
||||
-- 대상: std_snp_data.ais_target (일별 파티션)
|
||||
-- 목적: 최근 2일 내 대상 MMSI별 최종위치 캐싱 검증
|
||||
-- ============================================================
|
||||
|
||||
@ -22,7 +22,7 @@ SELECT
|
||||
(SELECT COUNT(*) FROM tmp_chn_mmsi) - COUNT(DISTINCT a.mmsi) AS mmsi_without_data_2d,
|
||||
ROUND(COUNT(DISTINCT a.mmsi) * 100.0
|
||||
/ NULLIF((SELECT COUNT(*) FROM tmp_chn_mmsi), 0), 1) AS hit_rate_pct
|
||||
FROM t_std_snp_data.ais_target a
|
||||
FROM std_snp_data.ais_target a
|
||||
JOIN tmp_chn_mmsi t ON a.mmsi = t.mmsi
|
||||
WHERE a.message_timestamp >= NOW() - INTERVAL '2 days';
|
||||
|
||||
@ -37,7 +37,7 @@ SELECT COUNT(*) AS cached_count,
|
||||
NOW() - MAX(message_timestamp) AS newest_age
|
||||
FROM (
|
||||
SELECT DISTINCT ON (a.mmsi) a.mmsi, a.message_timestamp
|
||||
FROM t_std_snp_data.ais_target a
|
||||
FROM std_snp_data.ais_target a
|
||||
JOIN tmp_chn_mmsi t ON a.mmsi = t.mmsi
|
||||
WHERE a.message_timestamp >= NOW() - INTERVAL '2 days'
|
||||
ORDER BY a.mmsi, a.message_timestamp DESC
|
||||
@ -58,7 +58,7 @@ SELECT DISTINCT ON (a.mmsi)
|
||||
a.cog,
|
||||
a.heading,
|
||||
NOW() - a.message_timestamp AS data_age
|
||||
FROM t_std_snp_data.ais_target a
|
||||
FROM std_snp_data.ais_target a
|
||||
JOIN tmp_chn_mmsi t ON a.mmsi = t.mmsi
|
||||
WHERE a.message_timestamp >= NOW() - INTERVAL '2 days'
|
||||
ORDER BY a.mmsi, a.message_timestamp DESC
|
||||
@ -72,7 +72,7 @@ SELECT t.mmsi AS missing_mmsi
|
||||
FROM tmp_chn_mmsi t
|
||||
LEFT JOIN (
|
||||
SELECT DISTINCT mmsi
|
||||
FROM t_std_snp_data.ais_target
|
||||
FROM std_snp_data.ais_target
|
||||
WHERE mmsi IN (SELECT mmsi FROM tmp_chn_mmsi)
|
||||
AND message_timestamp >= NOW() - INTERVAL '2 days'
|
||||
) a ON t.mmsi = a.mmsi
|
||||
@ -86,31 +86,31 @@ ORDER BY t.mmsi;
|
||||
SELECT
|
||||
'6시간 이내' AS time_range,
|
||||
COUNT(DISTINCT mmsi) AS distinct_mmsi
|
||||
FROM t_std_snp_data.ais_target a
|
||||
FROM std_snp_data.ais_target a
|
||||
JOIN tmp_chn_mmsi t ON a.mmsi = t.mmsi
|
||||
WHERE a.message_timestamp >= NOW() - INTERVAL '6 hours'
|
||||
|
||||
UNION ALL
|
||||
SELECT '12시간 이내', COUNT(DISTINCT mmsi)
|
||||
FROM t_std_snp_data.ais_target a
|
||||
FROM std_snp_data.ais_target a
|
||||
JOIN tmp_chn_mmsi t ON a.mmsi = t.mmsi
|
||||
WHERE a.message_timestamp >= NOW() - INTERVAL '12 hours'
|
||||
|
||||
UNION ALL
|
||||
SELECT '1일 이내', COUNT(DISTINCT mmsi)
|
||||
FROM t_std_snp_data.ais_target a
|
||||
FROM std_snp_data.ais_target a
|
||||
JOIN tmp_chn_mmsi t ON a.mmsi = t.mmsi
|
||||
WHERE a.message_timestamp >= NOW() - INTERVAL '1 day'
|
||||
|
||||
UNION ALL
|
||||
SELECT '2일 이내', COUNT(DISTINCT mmsi)
|
||||
FROM t_std_snp_data.ais_target a
|
||||
FROM std_snp_data.ais_target a
|
||||
JOIN tmp_chn_mmsi t ON a.mmsi = t.mmsi
|
||||
WHERE a.message_timestamp >= NOW() - INTERVAL '2 days'
|
||||
|
||||
UNION ALL
|
||||
SELECT '전체(무제한)', COUNT(DISTINCT mmsi)
|
||||
FROM t_std_snp_data.ais_target a
|
||||
FROM std_snp_data.ais_target a
|
||||
JOIN tmp_chn_mmsi t ON a.mmsi = t.mmsi;
|
||||
|
||||
|
||||
@ -123,7 +123,7 @@ SELECT
|
||||
COUNT(DISTINCT mmsi) AS distinct_mmsi,
|
||||
MIN(message_timestamp) AS min_ts,
|
||||
MAX(message_timestamp) AS max_ts
|
||||
FROM t_std_snp_data.ais_target a
|
||||
FROM std_snp_data.ais_target a
|
||||
JOIN tmp_chn_mmsi t ON a.mmsi = t.mmsi
|
||||
GROUP BY tableoid::regclass
|
||||
ORDER BY max_ts DESC;
|
||||
@ -139,7 +139,7 @@ SELECT
|
||||
FROM pg_inherits i
|
||||
JOIN pg_class c ON c.oid = i.inhrelid
|
||||
JOIN pg_stat_user_tables s ON s.relid = c.oid
|
||||
WHERE i.inhparent = 't_std_snp_data.ais_target'::regclass
|
||||
WHERE i.inhparent = 'std_snp_data.ais_target'::regclass
|
||||
ORDER BY c.relname DESC;
|
||||
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import org.hibernate.annotations.CreationTimestamp;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "batch_api_log", schema = "t_std_snp_data")
|
||||
@Table(name = "batch_api_log", schema = "std_snp_data")
|
||||
@Getter
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor
|
||||
|
||||
@ -7,7 +7,7 @@ import org.hibernate.annotations.CreationTimestamp;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "batch_failed_record", schema = "t_std_snp_data")
|
||||
@Table(name = "batch_failed_record", schema = "std_snp_data")
|
||||
@Getter
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor
|
||||
|
||||
@ -15,12 +15,12 @@ import java.util.Optional;
|
||||
* 설정 예시:
|
||||
* app.batch.partition:
|
||||
* daily-tables:
|
||||
* - schema: snp_data
|
||||
* - schema: std_snp_data
|
||||
* table-name: ais_target
|
||||
* partition-column: message_timestamp
|
||||
* periods-ahead: 3
|
||||
* monthly-tables:
|
||||
* - schema: snp_data
|
||||
* - schema: std_snp_data
|
||||
* table-name: some_table
|
||||
* partition-column: created_at
|
||||
* periods-ahead: 2
|
||||
@ -58,7 +58,7 @@ public class PartitionConfig {
|
||||
@Getter
|
||||
@Setter
|
||||
public static class PartitionTableConfig {
|
||||
private String schema = "snp_data";
|
||||
private String schema = "std_snp_data";
|
||||
private String tableName;
|
||||
private String partitionColumn;
|
||||
private int periodsAhead = 3; // 미리 생성할 기간 수 (daily: 일, monthly: 월)
|
||||
|
||||
@ -13,7 +13,7 @@ import java.time.OffsetDateTime;
|
||||
/**
|
||||
* AIS Target Entity
|
||||
*
|
||||
* 테이블: snp_data.ais_target
|
||||
* 테이블: std_snp_data.ais_target
|
||||
* PK: mmsi + message_timestamp (복합키)
|
||||
*
|
||||
* 용도:
|
||||
|
||||
@ -13,8 +13,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
* 프로파일별 설정 파일에서 지정할 수 있도록 구성
|
||||
*
|
||||
* 사용 예:
|
||||
* - dev: snp_data.core20 (ihslrorimoshipno, maritimemobileserviceidentitymmsinumber)
|
||||
* - prod: new_snp.core20 (lrno, mmsi)
|
||||
* - dev: std_snp_svc.tb_ship_main_info (imo_no, mmsi_no)
|
||||
* - prod: std_snp_svc.tb_ship_main_info (imo_no, mmsi_no)
|
||||
*/
|
||||
@Slf4j
|
||||
@Getter
|
||||
@ -23,24 +23,24 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
public class Core20Properties {
|
||||
|
||||
/**
|
||||
* 스키마명 (예: snp_data, new_snp)
|
||||
* 스키마명 (예: std_snp_svc)
|
||||
*/
|
||||
private String schema = "snp_data";
|
||||
private String schema = "std_snp_svc";
|
||||
|
||||
/**
|
||||
* 테이블명 (예: core20)
|
||||
* 테이블명 (예: tb_ship_main_info)
|
||||
*/
|
||||
private String table = "core20";
|
||||
private String table = "tb_ship_main_info";
|
||||
|
||||
/**
|
||||
* IMO/LRNO 컬럼명 (PK, NOT NULL)
|
||||
*/
|
||||
private String imoColumn = "ihslrorimoshipno";
|
||||
private String imoColumn = "imo_no";
|
||||
|
||||
/**
|
||||
* MMSI 컬럼명 (NULLABLE)
|
||||
*/
|
||||
private String mmsiColumn = "maritimemobileserviceidentitymmsinumber";
|
||||
private String mmsiColumn = "mmsi_no";
|
||||
|
||||
/**
|
||||
* 전체 테이블명 반환 (schema.table)
|
||||
|
||||
@ -22,12 +22,12 @@ spring:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
format_sql: true
|
||||
default_schema: t_std_snp_data
|
||||
default_schema: std_snp_data
|
||||
|
||||
# Batch Configuration
|
||||
batch:
|
||||
jdbc:
|
||||
table-prefix: "t_std_snp_data.batch_"
|
||||
table-prefix: "std_snp_data.batch_"
|
||||
initialize-schema: always # Changed to 'never' as tables already exist
|
||||
job:
|
||||
enabled: false # Prevent auto-run on startup
|
||||
@ -49,7 +49,7 @@ spring:
|
||||
org.quartz.threadPool.threadCount: 10
|
||||
org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
|
||||
org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
|
||||
org.quartz.jobStore.tablePrefix: t_std_snp_data.QRTZ_
|
||||
org.quartz.jobStore.tablePrefix: std_snp_data.QRTZ_
|
||||
org.quartz.jobStore.isClustered: false
|
||||
org.quartz.jobStore.misfireThreshold: 60000
|
||||
|
||||
@ -142,7 +142,7 @@ app:
|
||||
|
||||
# Core20 캐시 테이블 설정 (환경별로 테이블/컬럼명이 다를 수 있음)
|
||||
core20:
|
||||
schema: t_std_snp_svc # 스키마명
|
||||
schema: std_snp_svc # 스키마명
|
||||
table: tb_ship_main_info # 테이블명
|
||||
imo-column: imo_no # IMO/LRNO 컬럼명 (PK, NOT NULL)
|
||||
mmsi-column: mmsi_no # MMSI 컬럼명 (NULLABLE)
|
||||
@ -151,7 +151,7 @@ app:
|
||||
partition:
|
||||
# 일별 파티션 테이블 목록 (네이밍: {table}_YYMMDD)
|
||||
daily-tables:
|
||||
- schema: t_std_snp_data
|
||||
- schema: std_snp_data
|
||||
table-name: ais_target
|
||||
partition-column: message_timestamp
|
||||
periods-ahead: 3 # 미리 생성할 일수
|
||||
|
||||
@ -22,12 +22,12 @@ spring:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
format_sql: true
|
||||
default_schema: t_std_snp_data
|
||||
default_schema: std_snp_data
|
||||
|
||||
# Batch Configuration
|
||||
batch:
|
||||
jdbc:
|
||||
table-prefix: "t_std_snp_data.batch_"
|
||||
table-prefix: "std_snp_data.batch_"
|
||||
initialize-schema: never # Changed to 'never' as tables already exist
|
||||
job:
|
||||
enabled: false # Prevent auto-run on startup
|
||||
@ -49,7 +49,7 @@ spring:
|
||||
org.quartz.threadPool.threadCount: 10
|
||||
org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
|
||||
org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
|
||||
org.quartz.jobStore.tablePrefix: t_std_snp_data.QRTZ_
|
||||
org.quartz.jobStore.tablePrefix: std_snp_data.QRTZ_
|
||||
org.quartz.jobStore.isClustered: false
|
||||
org.quartz.jobStore.misfireThreshold: 60000
|
||||
|
||||
@ -144,7 +144,7 @@ app:
|
||||
|
||||
# Core20 캐시 테이블 설정 (환경별로 테이블/컬럼명이 다를 수 있음)
|
||||
core20:
|
||||
schema: t_std_snp_svc # 스키마명
|
||||
schema: std_snp_svc # 스키마명
|
||||
table: tb_ship_main_info # 테이블명
|
||||
imo-column: imo_no # IMO/LRNO 컬럼명 (PK, NOT NULL)
|
||||
mmsi-column: mmsi_no # MMSI 컬럼명 (NULLABLE)
|
||||
@ -153,7 +153,7 @@ app:
|
||||
partition:
|
||||
# 일별 파티션 테이블 목록 (네이밍: {table}_YYMMDD)
|
||||
daily-tables:
|
||||
- schema: t_std_snp_data
|
||||
- schema: std_snp_data
|
||||
table-name: ais_target
|
||||
partition-column: message_timestamp
|
||||
periods-ahead: 3 # 미리 생성할 일수
|
||||
|
||||
@ -22,12 +22,12 @@ spring:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
format_sql: true
|
||||
default_schema: t_std_snp_data
|
||||
default_schema: std_snp_data
|
||||
|
||||
# Batch Configuration
|
||||
batch:
|
||||
jdbc:
|
||||
table-prefix: "t_std_snp_data.batch_"
|
||||
table-prefix: "std_snp_data.batch_"
|
||||
initialize-schema: never # Changed to 'never' as tables already exist
|
||||
job:
|
||||
enabled: false # Prevent auto-run on startup
|
||||
@ -49,7 +49,7 @@ spring:
|
||||
org.quartz.threadPool.threadCount: 10
|
||||
org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
|
||||
org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
|
||||
org.quartz.jobStore.tablePrefix: t_std_snp_data.QRTZ_
|
||||
org.quartz.jobStore.tablePrefix: std_snp_data.QRTZ_
|
||||
org.quartz.jobStore.isClustered: false
|
||||
org.quartz.jobStore.misfireThreshold: 60000
|
||||
|
||||
@ -95,7 +95,7 @@ app:
|
||||
batch:
|
||||
chunk-size: 1000
|
||||
target-schema:
|
||||
name: t_std_snp_data
|
||||
name: std_snp_data
|
||||
tables:
|
||||
ship-001: tb_ship_default_info
|
||||
ship-002: tb_ship_info_mst
|
||||
@ -147,7 +147,7 @@ app:
|
||||
risk-compliance-003: tb_company_compliance_info
|
||||
ship-028: ship_detail_hash_json
|
||||
service-schema:
|
||||
name: t_std_snp_svc
|
||||
name: std_snp_svc
|
||||
tables:
|
||||
service-001: tb_ship_main_info
|
||||
api:
|
||||
@ -212,7 +212,7 @@ app:
|
||||
|
||||
# Core20 캐시 테이블 설정 (환경별로 테이블/컬럼명이 다를 수 있음)
|
||||
core20:
|
||||
schema: t_std_snp_svc # 스키마명
|
||||
schema: std_snp_svc # 스키마명
|
||||
table: tb_ship_main_info # 테이블명
|
||||
imo-column: imo_no # IMO/LRNO 컬럼명 (PK, NOT NULL)
|
||||
mmsi-column: mmsi_no # MMSI 컬럼명 (NULLABLE)
|
||||
@ -221,7 +221,7 @@ app:
|
||||
partition:
|
||||
# 일별 파티션 테이블 목록 (네이밍: {table}_YYMMDD)
|
||||
daily-tables:
|
||||
- schema: t_std_snp_data
|
||||
- schema: std_snp_data
|
||||
table-name: ais_target
|
||||
partition-column: message_timestamp
|
||||
periods-ahead: 3 # 미리 생성할 일수
|
||||
|
||||
@ -154,7 +154,7 @@ BEGIN
|
||||
RETURN EXISTS (
|
||||
SELECT 1 FROM pg_class c
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
WHERE n.nspname = 'snp_data'
|
||||
WHERE n.nspname = 'std_snp_data'
|
||||
AND c.relname = partition_name
|
||||
AND c.relkind = 'r'
|
||||
);
|
||||
@ -261,7 +261,7 @@ BEGIN
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
JOIN pg_inherits i ON i.inhrelid = c.oid
|
||||
WHERE n.nspname = 'snp_data'
|
||||
WHERE n.nspname = 'std_snp_data'
|
||||
AND c.relname LIKE 'ais_target_%'
|
||||
AND LENGTH(c.relname) = 17 -- ais_target_YYMMDD = 17자
|
||||
AND c.relkind = 'r'
|
||||
@ -304,7 +304,7 @@ BEGIN
|
||||
FROM pg_class c
|
||||
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||
JOIN pg_inherits i ON i.inhrelid = c.oid
|
||||
WHERE n.nspname = 'snp_data'
|
||||
WHERE n.nspname = 'std_snp_data'
|
||||
AND c.relname LIKE 'ais_target_%'
|
||||
AND c.relkind = 'r'
|
||||
ORDER BY c.relname;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
-- 오래된 STARTED 상태 Job을 정리하는 SQL 쿼리입니다.
|
||||
-- snp_data 스키마에 batch_ 접두사를 사용하는 예시입니다. 실제 스키마에 맞추어 수정해서 사용하세요.
|
||||
-- std_snp_data 스키마에 batch_ 접두사를 사용하는 예시입니다. 실제 스키마에 맞추어 수정해서 사용하세요.
|
||||
|
||||
-- 참고: 시간 간격 변경이 필요하면 INTERVAL '2 hours' 부분을 수정하세요:
|
||||
-- 1시간: INTERVAL '1 hour'
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user