๋ณธ ๋ฌธ์๋ Spring Batch ๊ธฐ๋ฐ์ผ๋ก ๊ตฌ์ถ๋ '์๋ณ ์ฒญ๊ตฌ๋ด์ญ ๋ฐ ์ฒญ๊ตฌ์ ์์ฑ ์์คํ '์ ๊ตฌํ ์ฌํญ์ ๊ธฐ์ ํฉ๋๋ค. ๋ณธ ์์คํ ์ ๋์ฉ๋ ๋ฐ์ดํฐ ํ๊ฒฝ์์๋ **์์ ์ ์ธ ์ฒ๋ฆฌ, ๋ฐ์ดํฐ ์ ํฉ์ฑ, ์ฌ์คํ ๊ฐ๋ฅ์ฑ(Restartability)**์ ํต์ฌ ๋ชฉํ๋ก ์ค๊ณ๋์์ต๋๋ค.
์์คํ ์ ๋์ฉ๋ ๋ฐ์ดํฐ์ ์์ ์ ์ธ ์ฒ๋ฆฌ๋ฅผ ์ํด Partitioning(๋ฐ์ดํฐ ๋ถํ ) ๊ธฐ๋ฒ์ ์ ์ฉํ์์ผ๋ฉฐ, ์ฅ์ ๋ฐ์ ์ ์คํจ ์ง์ ๋ถํฐ์ ์ฌ์์(Restartability)์ ๋ณด์ฅํฉ๋๋ค.
| ๊ตฌ๋ถ | ์ค๊ณ ๋ชฉํ | ๊ตฌํ ๋ด์ฉ (v1.0) |
|---|---|---|
| ํ๋ ์์ํฌ | Spring Batch | com.template.worker.global ๊ธฐ๋ฐ ๊ณตํต ๋ชจ๋ ๊ตฌํ ์๋ฃ |
| ์ฒ๋ฆฌ ๋ฐฉ์ | ๋ณ๋ ฌ ์ฒ๋ฆฌ (Partitioning) | PartitionHandler, TaskExecutor ๊ธฐ๋ฐ MasterโWorker ๊ตฌ์กฐ ๊ตฌํ |
| ๋์ ์์ | ์ฒญ๊ตฌ๋ด์ญ / ์ฒญ๊ตฌ์ ์์ฑ | InvoiceItemJob(์ฒญ๊ตฌ๋ด์ญ), InvoiceJob(์ฒญ๊ตฌ์) 2์ข
Job ๊ตฌํ |
| I/O ๋ฐฉ์ | JDBC Paging Reader | JdbcPagingItemReader ๊ธฐ๋ฐ ์กฐํ ์ต์ ํ ์ ์ฉ |
| Write ๋ฐฉ์ | Batch Insert | JdbcBatchItemWriter ๊ธฐ๋ฐ ์ผ๊ด ์ฝ์
์ ์ฉ |
| ์์ ์ฑ | ์ฌ์์ / ๋ฉฑ๋ฑ์ฑ | ExecutionContext + UNIQUE ์ ์ฝ ์ ์ฉ |
๋์ฉ๋ ์ฒ๋ฆฌ๋ฅผ ์ํด Single Thread ๋ฐฉ์์ด ์๋, ๋ฐ์ดํฐ ๋ฒ์๋ฅผ ๋ถํ ํ์ฌ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌํ๋ Partitioning ๋ชจ๋ธ์ ์ ์ฉํ์ต๋๋ค.
flowchart TD
%% Entry Point
JobLauncher[BatchJobLauncher]
JobLauncher --> Job[Job Execution]
%% Master Step
Job --> MasterStep[Partition Step]
%% Partitioner
MasterStep -->|Partitioning| P[Partitioner]
%% Worker Steps
P -- Range 1 --> W1[Worker Step 1]
P -- Range 2 --> W2[Worker Step 2]
P -- Range N --> WN[Worker Step N]
%% Worker Logic
subgraph WorkerLogic["Worker Logic"]
R[ItemReader] --> Pr[ItemProcessor] --> Wr[ItemWriter]
end
%% Connect Worker Steps to Logic
W1 --> R
W2 --> R
WN --> R
- Partitioner: ์ ์ฒด ๋ฐ์ดํฐ ID ๋ฒ์(Min/Max)๋ฅผ ์กฐํํ์ฌ, Grid Size์ ๋ง์ถฐ ๊ท ๋ฑ ๋ถํ ํฉ๋๋ค.
- Worker Step: ํ ๋น๋ฐ์ ๋ฒ์(Range) ๋ด์ ๋ฐ์ดํฐ๋ง ๋ ๋ฆฝ์ ์ผ๋ก ํธ๋์ญ์ ์ ๋งบ๊ณ ์ฒ๋ฆฌํฉ๋๋ค.
๋ณธ ์์คํ
์์๋ JpaItemReader ๋์ **JdbcPagingItemReader**๋ฅผ ์ฑํํ์ต๋๋ค.
| ๊ตฌ๋ถ | JpaItemReader | JdbcPagingItemReader | ๋น๊ณ |
|---|---|---|---|
| ์ฟผ๋ฆฌ ์ ์ด | JPQL/EntityGraph ์ ํ์ | ๋ค์ดํฐ๋ธ SQL ์์ ์ ์ด | ๋ณต์กํ ์ง๊ณ ์ฟผ๋ฆฌ ํ์ |
| ์ฑ๋ฅ | 1์ฐจ ์บ์ ์ค๋ฒํค๋ | ์ง์ JDBC, ๋ฉ๋ชจ๋ฆฌ ํจ์จ | ๋์ฉ๋ ๋ฐฐ์น์ ์ ๋ฆฌ |
| Paging ๋ฐฉ์ | Offset ๊ธฐ๋ฐ (N+1 ์ํ) | PagingQueryProvider ์ต์ ํ | ์์ ์ ํ์ด์ง ๋ณด์ฅ |
| DB ์ง๊ณ ํ์ฉ | ์ ํ์ | FILTER, GROUP BY ๋ฑ ํ์ฉ ๊ฐ๋ฅ |
DB ์ง๊ณ ์ฐ์ฐ ํ์ |
๊ฒฐ์ ๊ทผ๊ฑฐ:
- ๋ณธ ๋ฐฐ์น๋ ๋ณต์กํ UNION ALL ์ง๊ณ ์ฟผ๋ฆฌ์ DB ๋ ๋ฒจ GROUP BY ์ฐ์ฐ์ด ํ์์ ์ ๋๋ค.
- JPA์ ์์์ฑ ์ปจํ ์คํธ๋ ๋์ฉ๋ Read-Only ์์ ์์ ๋ถํ์ํ ๋ฉ๋ชจ๋ฆฌ ๋ถ๋ด์ ๋ฐ์์ํต๋๋ค.
- JDBC๋ ์์ ResultSet ๋งคํ์ผ๋ก ์ต์ํ์ ์ค๋ฒํค๋๋ง ๋ฐ์์ํต๋๋ค.
@Bean
public JdbcBatchItemWriter<InvoiceItemEntity> invoiceItemWriter(DataSource dataSource) {
return new JdbcBatchItemWriterBuilder<InvoiceItemEntity>()
.dataSource(dataSource)
.sql("""
INSERT INTO invoice_item
(sub_id, inv_month, type, source_id, amount, created_at)
VALUES (:subId, :invMonth, :type, :sourceId, :amount, :createdAt)
ON CONFLICT (sub_id, inv_month, type, source_id) DO NOTHING
""")
.beanMapped()
.assertUpdates(false) // ์ค๋ณต ์ ์์ธ ๋ฐ์ ๋ฐฉ์ง
.build();
}| ํญ๋ชฉ | ๋จ๊ฑด Insert | Batch Insert | ๊ฐ์ ํจ๊ณผ |
|---|---|---|---|
| DB ์๋ณต ํ์ | Chunk ํฌ๊ธฐ๋งํผ ๋ฐ์ | 1ํ (Batch ์คํ) | ๋คํธ์ํฌ I/O ์ต์ํ |
| ํธ๋์ญ์ ์ค๋ฒํค๋ | ๋งค ๊ฑด๋ง๋ค Lock | Chunk ๋จ์ ์ผ๊ด Commit | Lock ๊ฒฝํฉ ๊ฐ์ |
| JDBC Statement | ๋งค๋ฒ ์์ฑ | ์ฌ์ฌ์ฉ (PreparedStatement) | ํ์ฑ ๋น์ฉ ์ ๊ฐ |
์ธก์ ๊ฒฐ๊ณผ (Chunk Size 4,000 ๊ธฐ์ค):
- ๋จ๊ฑด Insert: ์ฝ 89์ด
- Batch Insert: ์ฝ 36์ด (59% ์ฑ๋ฅ ํฅ์)
Application ๋ ๋ฒจ์์ ์ฒญ๊ตฌ์๋ฅผ ์์ฑํ ๊ฒฝ์ฐ ๋ค์ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค:
๋ฌธ์ ์ํฉ:
Chunk 1: sub_id=100 ์ item 1~50 โ Invoice ์์ฑ (total: 5,000์)
Chunk 2: sub_id=100 ์ item 51~100 โ Invoice ์์ฑ (total: 3,000์)
โ ๋์ผ ๊ตฌ๋
์ ๋ํด ์ค๋ณต ์ฒญ๊ตฌ์ ๋ฐ์
ํด๊ฒฐ ๋ฐฉ์:
- Reader ๋จ๊ณ์์ DB์
GROUP BY์ฐ์ฐ์ ์ํํ์ฌ ๊ตฌ๋ ๋ณ ์ง๊ณ๋ฅผ ์๋ฃํ ์ํ๋ก ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. - ํ ๋ฒ์ Read์์ ํ๋์ ๊ตฌ๋ ์ ๋ํ ์์ ํ ์ง๊ณ ๊ฒฐ๊ณผ๊ฐ ๋ณด์ฅ๋ฉ๋๋ค.
| ์ ๊ทผ ๋ฐฉ์ | ์ฅ์ | ๋จ์ |
|---|---|---|
| App ๋ ๋ฒจ ์ง๊ณ | DB ๋ถํ ๋ฎ์ | ๋คํธ์ํฌ ์ ์ก๋ โ, ์ค๋ณต ์์ฑ ์ํ |
| DB ๋ ๋ฒจ ์ง๊ณ | ๋ฐ์ดํฐ ์ ์ก๋ โ, ์ ํฉ์ฑ ๋ณด์ฅ | GROUP BY ์ฐ์ฐ ๋น์ฉ |
์ ํ ๊ทผ๊ฑฐ:
GROUP BY์ฐ์ฐ์ DB์ ์ธ๋ฑ์ค์ ์ต์ ํ ์์ง์ ํ์ฉํ๋ฏ๋ก ์ค์ ๋น์ฉ์ ์์๋ณด๋ค ๋ฎ์ต๋๋ค.- ๋คํธ์ํฌ๋ฅผ ํตํด ์ ์ก๋๋ row ์๊ฐ 1/N๋ก ๊ฐ์ํ์ฌ ์ ์ฒด ์ฒ๋ฆฌ ์๊ฐ์ด ๋จ์ถ๋ฉ๋๋ค.
- ์ง๊ณ ์๋ฃ ๋ฐ์ดํฐ๋ง ์ ์กํ๋ฏ๋ก Application ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋๋ ์ต์ํ๋ฉ๋๋ค.
์ธก์ ๊ฒฐ๊ณผ:
- App ๋ ๋ฒจ ์ง๊ณ ์: ์ฝ 52์ด (100๋ง ๊ฑด ์ ์ก)
- DB ๋ ๋ฒจ ์ง๊ณ ์: ์ฝ 36์ด (10๋ง ๊ฑด ์ ์ก, 31% ๊ฐ์ )
Class: InvoiceItemJobConfig
Role: ์์ฒ ๋ก๊ทธ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฒญ๊ตฌ ๊ฐ๋ฅํ ์ต์ ๋จ์(Item)๋ฅผ ์ง๊ณํ์ฌ ์์ฑ
- ๊ตฌ๋ (Subscription) ID ๋ฒ์๋ฅผ ๊ธฐ์ค์ผ๋ก ํํฐ์ ๋ํฉ๋๋ค.
- subscription ํ ์ด๋ธ์ Min/Max ID๋ฅผ ์กฐํํ์ฌ ๋ฒ์๋ฅผ ํ ๋นํฉ๋๋ค.
InvoiceItemQueryProvider๋ฅผ ํตํด ์๊ธ์ , ๋ถ๊ฐ์๋น์ค, ์์ก๊ฒฐ์ , ํ ์ธ ๋ด์ญ์ UNION ALL๋ก ํตํฉ ์กฐํํฉ๋๋ค.- Paging ์ฒ๋ฆฌ๋ฅผ ํตํด ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
- Reader์์ ๊ฐ๊ณต๋
InvoiceItemAggregateRow๋ฅผ ๊ทธ๋๋ก ๋งคํํ์ฌ Writer๋ก ์ ๋ฌํฉ๋๋ค.
JdbcBatchItemWriter๋ฅผ ํตํด invoice_item ํ ์ด๋ธ์ Batch Insert๋ฅผ ์ํํฉ๋๋ค.ON CONFLICT DO NOTHING๊ตฌ๋ฌธ์ผ๋ก ๋ฉฑ๋ฑ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
Class: InvoiceJobConfig
Role: ์์ฑ๋ ์ฒญ๊ตฌ๋ด์ญ(Item)์ ๊ตฌ๋ (Subscription) ๋จ์๋ก ํฉ์ฐํ์ฌ ์ต์ข ์ฒญ๊ตฌ์(Invoice) ๋ฐํ
- ํด๋์ค๋ช
์ CustomerRangePartitioner์ด๋, ์ค์ ๊ตฌํ์ invoice_item ํ
์ด๋ธ์
sub_id(๊ตฌ๋ ID) ๋ฒ์๋ฅผ ๊ธฐ์ค์ผ๋ก ํํฐ์ ๋ํฉ๋๋ค. - ํน์ ๊ตฌ๋ ์ ๋ํ ๋ชจ๋ ์ฒญ๊ตฌ ํญ๋ชฉ์ด ๋์ผํ Worker์์ ์ฒ๋ฆฌ๋จ์ ๋ณด์ฅํฉ๋๋ค.
- DB ๋ ๋ฒจ ์ง๊ณ(Aggregation) ์ํ: Reader ๋จ๊ณ์์ GROUP BY sub_id ์ฟผ๋ฆฌ๋ฅผ ์คํํ์ฌ ๊ตฌ๋ ๋ณ ํฉ๊ณ(total_amount, total_discount)๋ฅผ ๋ฏธ๋ฆฌ ๊ณ์ฐํด ๊ฐ์ ธ์ต๋๋ค.
- ์ด๋ฅผ ํตํด Application ๋ ๋ฒจ์ ๋ถํ๋ฅผ ์ค์ด๊ณ DB์ ์ง๊ณ ์ฑ๋ฅ์ ํ์ฉํฉ๋๋ค.
- Reader๊ฐ ์ ๋ฌํ
InvoiceAggregationRow๋ฅผInvoiceEntity๋ก ๋ณํํ๋ ๋งคํ ์์ ์ ์ํํฉ๋๋ค.
JdbcBatchItemWriter๋ฅผ ํตํด invoice ํ ์ด๋ธ์ ์ต์ข ์ฒญ๊ตฌ์๋ฅผ Batch Insertํฉ๋๋ค.- ์ค๋ณต ๋ฐฉ์ง ๋ฉ์ปค๋์ฆ:
UNIQUE KEY (sub_id, inv_month)ํ์ฉ ON CONFLICT (sub_id, inv_month) DO NOTHING๊ตฌ๋ฌธ์ผ๋ก ๋์ผ ๊ตฌ๋ /์ ์กฐํฉ์ ๋ํ ์ค๋ณต ์ฒญ๊ตฌ์ ์์ฑ์ ๋ฐฉ์งํฉ๋๋ค.
๋์ฉ๋ ๋ฐฐ์น ์ฒ๋ฆฌ ์ ๊ฐ์ฅ ์ค์ํ ์์๋ ๋ฐ์ดํฐ ๋๋ฝ ๋ฐ ์ค๋ณต ๋ฐฉ์ง์ ๋๋ค. ๋ณธ ์์คํ ์์๋ ๋ค์๊ณผ ๊ฐ์ ์ ๋ต์ ํตํด ์ ํฉ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
JdbcPagingItemReader๋ ํ์ด์ง ๊ฒฝ๊ณ์์ ๋ฐ์ดํฐ ๋๋ฝ์ด ๋ฐ์ํ์ง ์๋๋ก **์ ๋ํฌํ ์ ๋ ฌ ํค(Sort Key)**๊ฐ ํ์์
๋๋ค.
๊ธฐ์กด ๋ฌธ์ :
- sub_id ๋จ๋ ์ ๋ ฌ ์ฌ์ฉ ์, ๋์ผ ๊ตฌ๋ ๋ด ์ฌ๋ฌ ํญ๋ชฉ์ด ํ์ด์ง ๊ฒฝ๊ณ์ ๊ฑธ์น ๊ฒฝ์ฐ ๋๋ฝ ๋ฐ์ ๊ฐ๋ฅ
๊ฐ์ ๋ฐฉ์ (๊ตฌํ ํ์ธ ์๋ฃ):
- InvoiceItemReader์์ ๋ณตํฉ ์ ๋ ฌ ํค๋ฅผ ์ ์ฉํ์ฌ ์์๋ฅผ ์๊ฒฉํ๊ฒ ๋ณด์ฅํ์ต๋๋ค.
provider.setSortKeys(Map.of(
"sub_id", Order.ASCENDING,
"type", Order.ASCENDING,
"source_id", Order.ASCENDING
));Partitioner๋ ์ ์ฒด ID ๋ฒ์๋ฅผ Grid Size๋ก ๋๋๋, ๋๋จธ์ง(Remainder)๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ ๋ง์ง๋ง ํํฐ์ ์ด ์ด๋ฅผ ๋ชจ๋ ์ฒ๋ฆฌํ๋๋ก ์ค๊ณํ์ต๋๋ค.
๊ตฌํ ๋ก์ง:
- targetSize = total / gridSize
- ๋ง์ง๋ง ํํฐ์ (i == gridSize - 1)์ธ ๊ฒฝ์ฐ end = max_id๋ก ๊ฐ์ ์ค์
ํจ๊ณผ:
- total % gridSize != 0 ์ํฉ์์๋ ๋ฐ์ดํฐ ๋๋ฝ ๋ฐฉ์ง
- ๋ชจ๋ ID ๋ฒ์๊ฐ ๋นํ์์ด(Gaps-free), ์ค๋ณต ์์ด ํ ๋น๋จ์ ๋ณด์ฅ
์ง๊ณ ๋ก์ง์ Application์ด ์๋ DB์์ ์ํ
SUM(amount) FILTER (WHERE amount > 0)
SUM(ABS(amount)) FILTER (WHERE amount < 0)๊ธฐ์ ์ ์ด์ :
- CASE WHEN ๋๋น ๋ถํ์ํ ์ฐ์ฐ ์ ๊ฑฐ
- ์ง๊ณ ๋์ row๋ง ๊ณ์ฐ
- PostgreSQL ์ตํฐ๋ง์ด์ ์ FILTER ์ ์ต์ ํ ํ์ฉ
SELECT
ii.sub_id,
c.name,
c.phone_enc,
c.email_enc,
SUM(ii.amount) FILTER (WHERE ii.amount > 0) AS total_amount,
SUM(ABS(ii.amount)) FILTER (WHERE ii.amount < 0) AS total_discount
FROM invoice_item ii
JOIN customer c ON ii.sub_id = c.sub_id
WHERE ii.inv_month = :invMonth
AND ii.sub_id BETWEEN :minSubId AND :maxSubId
AND ii.amount <> 0
GROUP BY ii.sub_id, c.name, c.phone_enc, c.email_enc
ORDER BY ii.sub_id;
์ฑ๋ฅ ์ต์ ํ๋ ๋ค์ ๋จ๊ณ๋ก ์งํํ์ต๋๋ค:
- Chunk Size์ Page Size ๋์ผํ๊ฒ ์ค์
- ์ด์ : Chunk์ Page๊ฐ ๋ค๋ฅผ ๊ฒฝ์ฐ, Reader๊ฐ Page ๋จ์๋ก ์ฝ์ด๋ Processor/Writer๋ Chunk ๋จ์๋ก ๋์ํ์ฌ ๋ถํ์ํ ๋ฒํผ๋ง ๋ฐ์
- ๋์ผํ๊ฒ ์ ์งํ๋ฉด Read โ Process โ Write๊ฐ ๋๊ธฐํ๋์ด ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ฑ ๊ทน๋ํ
- Chunk/Page Size Sweet Spot ํ์
- ๋ก์ปฌ ํ๊ฒฝ์์ 1,000 ~ 5,000 ๋ฒ์๋ฅผ ํ ์คํธํ์ฌ ์ต์ ๊ฐ ๋์ถ
- Grid Size Sweet Spot ํ์
- Chunk/Page Size๋ฅผ ๊ณ ์ ํ ์ํ์์ Grid Size๋ง ๋ณ๊ฒฝํ๋ฉฐ ๋ณ๋ ฌ ์ฒ๋ฆฌ ํจ์จ์ฑ ์ธก์
ํ ์คํธ ๋ฒ์: 1,000 ~ 5,000
์ต์ ๊ฐ: 4,000 / 4,000
| Chunk/Page | ์คํ ์๊ฐ | ๋น๊ณ |
|---|---|---|
| 1,000 | 93s | DB ์๋ณต ํ์ ๊ณผ๋ค |
| 4,000 | 36s | ์ต์ |
| 5,000 | 35s | ๊ฐ์ ๋ฏธ๋ฏธ, ๋ฉ๋ชจ๋ฆฌ ์ฆ๊ฐ |
๋ถ์:
- 1,000์ DB ์๋ณต ํ์๊ฐ ๋ง์ ๋คํธ์ํฌ ๋น์ฉ ์ฆ๊ฐ
- 4,000์์ ํจ์จ ์๊ณ์ ๋๋ฌ
- 5,000 ์ด์์ ๊ฐ์ ํญ์ด ๋ฏธ๋ฏธํ๋ฉฐ ๋ฉ๋ชจ๋ฆฌ ๋ถ๋ด๋ง ์ฆ๊ฐ
Chunk/Page Size ๊ณ ์ : 4,000
ํ ์คํธ ๋ฒ์: 4 ~ 8
| Grid | ์คํ ์๊ฐ | ๊ฐ์ ์จ | ๋น๊ณ |
|---|---|---|---|
| 4 | 36s | ๊ธฐ์ค | - |
| 6 | 28s | 22% | ์ต์ |
| 8 | 27s | 3% | ๊ฐ์ ๋ฏธ๋ฏธ |
๋ถ์:
- Grid 6์์ ํจ์จ ์๊ณ์ (Sweet Spot) ๋๋ฌ
- ์ดํ ๋ณ๋ ฌํ ์ด๋๋ณด๋ค Context Switching ๋น์ฉ์ด ๋ ์ปค์ง
10์ด๋ง๋ค ํ์ฌ Chunk์ Read/Write ์นด์ดํธ๋ฅผ ์ถ๋ ฅํ์ฌ ์งํ๋ฅ ์ ์ค์๊ฐ์ผ๋ก ํ์ ํฉ๋๋ค.
@Slf4j
public class TimeBasedChunkListener implements ChunkListener {
private long lastLogTime = System.currentTimeMillis();
private static final long LOG_INTERVAL_MS = 10_000; // 10์ด
@Override
public void afterChunk(ChunkContext context) {
long currentTime = System.currentTimeMillis();
if (currentTime - lastLogTime >= LOG_INTERVAL_MS) {
log.info("[Progress] Read: {}, Write: {}",
context.getStepContext().getStepExecution().getReadCount(),
context.getStepContext().getStepExecution().getWriteCount());
lastLogTime = currentTime;
}
}
}๊ฐ Worker Step ์ข ๋ฃ ์์ ์ ํํฐ์ ๋ณ ์ฒ๋ฆฌ ๊ฑด์์ ์์ ์๊ฐ์ ๊ธฐ๋กํ์ฌ, ๋ฐ์ดํฐ๊ฐ ๊ท ๋ฑํ๊ฒ ๋ถ๋ฐฐ๋์๋์ง ๊ฒ์ฆํฉ๋๋ค.
@Slf4j
public class PartitionTimingListener implements StepExecutionListener {
@Override
public void afterStep(StepExecution stepExecution) {
log.info("[Partition {}] Read: {}, Write: {}, Duration: {}ms",
stepExecution.getStepName(),
stepExecution.getReadCount(),
stepExecution.getWriteCount(),
stepExecution.getEndTime().getTime() - stepExecution.getStartTime().getTime());
}
}๊ฒ์ฆ ๊ฒฐ๊ณผ ์์:
[Partition worker-0] Read: 25,000, Write: 25,000, Duration: 4,200ms
[Partition worker-1] Read: 24,800, Write: 24,800, Duration: 4,150ms
[Partition worker-2] Read: 25,200, Write: 25,200, Duration: 4,300ms
โ ๊ฐ ํํฐ์ ์ ์ฒ๋ฆฌ๋์ด ๊ท ๋ฑํ๊ฒ ๋ถ์ฐ๋์์์ ํ์ธ
- UNIQUE INDEX:
(sub_id, inv_month, type, source_id)๋ณตํฉ ์ธ๋ฑ์ค ์์ฑ - ON CONFLICT DO NOTHING: ์ค๋ณต ๋ฐ์ดํฐ ๋ฐ์ ์ ๋ฌด์ (๋ฉฑ๋ฑ์ฑ ๋ณด์ฅ)
- assertUpdates(false): Writer์์ ์ค๋ณต์ผ๋ก ์ธํ ๋ฏธ๋ฐ์ ๊ฑด์ ์์ธ๋ก ๊ฐ์ฃผํ์ง ์์
| ํ ์คํธ ์๋๋ฆฌ์ค | ์คํ ํ์ | ๋ฐ์ดํฐ ๊ฑด์ | ๊ฒฐ๊ณผ | ๋น๊ณ |
|---|---|---|---|---|
| ์ ์ ์คํ | 1ํ | 100,000๊ฑด ์ฝ์ | PASS | - |
| ์ค๋ณต ์คํ | 2ํ | 0๊ฑด ์ถ๊ฐ ์ฝ์ | PASS | UNIQUE ์ ์ฝ์ผ๋ก ์ค๋ณต ์ฐจ๋จ |
| ๊ฐ์ ์ข ๋ฃ ํ ์ฌ์คํ | 1ํ ์ค๋จ + 1ํ ์ฌ์คํ | ๋๋จธ์ง 60,000๊ฑด ์ฝ์ | PASS | ExecutionContext ๊ธฐ๋ฐ ์ฌ์์ |
| ๋ถ๋ถ ์คํจ ํ ์ฌ์คํ | Worker-2 ์คํจ + ์ฌ์คํ | ์คํจ ํํฐ์ ๋ง ์ฌ์ฒ๋ฆฌ | PASS | ํํฐ์ ๋ณ ๋ ๋ฆฝ ํธ๋์ญ์ |
| ํ ์คํธ ์๋๋ฆฌ์ค | ์คํ ํ์ | ๋ฐ์ดํฐ ๊ฑด์ | ๊ฒฐ๊ณผ | ๋น๊ณ |
|---|---|---|---|---|
| ์ ์ ์คํ | 1ํ | 10,000๊ฑด ์ฝ์ | PASS | - |
| ์ค๋ณต ์คํ | 2ํ | 0๊ฑด ์ถ๊ฐ ์ฝ์ | PASS | (sub_id, inv_month) ์ค๋ณต ์ฐจ๋จ |
| ๊ฐ์ ์ข ๋ฃ ํ ์ฌ์คํ | 1ํ ์ค๋จ + 1ํ ์ฌ์คํ | ๋๋จธ์ง 6,000๊ฑด ์ฝ์ | PASS | ExecutionContext ๊ธฐ๋ฐ ์ฌ์์ |
| ๋ถ๋ถ ์คํจ ํ ์ฌ์คํ | Worker-1 ์คํจ + ์ฌ์คํ | ์คํจ ํํฐ์ ๋ง ์ฌ์ฒ๋ฆฌ | PASS | ํํฐ์ ๋ณ ๋ ๋ฆฝ ํธ๋์ญ์ |
1์ฐจ ์คํ: InvoiceItemJob ์๋ฃ (100,000๊ฑด)
2์ฐจ ์คํ: ๋์ผ ํ๋ผ๋ฏธํฐ๋ก ์ฌ์คํ
โ ๊ฒฐ๊ณผ: 0๊ฑด ์ฝ์
, ON CONFLICT DO NOTHING์ผ๋ก ๋ชจ๋ ๋ฌด์
โ ๊ฒ์ฆ: SELECT COUNT(*) = 100,000 (๋ณํ ์์)
1์ฐจ ์คํ: Worker-0, Worker-1 ์๋ฃ, Worker-2 ์ฒ๋ฆฌ ์ค ๊ฐ์ ์ข
๋ฃ
โ ExecutionContext ์ ์ฅ: Worker-2์ ๋ง์ง๋ง ์ฒ๋ฆฌ ์์น ๊ธฐ๋ก
2์ฐจ ์คํ: ๋์ผ JobInstance๋ก ์ฌ์์
โ ๊ฒฐ๊ณผ: Worker-0, Worker-1 ์คํต, Worker-2๋ง ์ฌ์์
โ ๊ฒ์ฆ: ์ ์ฒด 100,000๊ฑด ์ ํํ ์ฝ์
ํ์ธ
1์ฐจ ์คํ: Worker-1์์ DB Connection Timeout ๋ฐ์
โ ํด๋น ํํฐ์
FAILED ์ํ๋ก ๊ธฐ๋ก
2์ฐจ ์คํ: FAILED ์ํ ํํฐ์
๋ง ์ฌ์ฒ๋ฆฌ
โ ๊ฒฐ๊ณผ: ์คํจํ ๋ฒ์(sub_id 33,334 ~ 66,666)๋ง ์ฌ์ฒ๋ฆฌ
โ ๊ฒ์ฆ: ์ค๋ณต ์์ด ์ ํํ 100,000๊ฑด ์ฝ์
๋ณธ ์ ์์๋ ์ค์ ์ด์ ํ๊ฒฝ(Cloud Run + Cloud SQL) ์์ ๋จ๊ณ์ ์ผ๋ก ์ํํ ์ฑ๋ฅ ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ์ ๋ฆฌํ๊ณ ,
์ธํ๋ผ ๋ณ๊ฒฝยท์ฟผ๋ฆฌ ๊ฐ์ ยทBatch ๊ตฌ์กฐ ํ๋์ด ์ฒ๋ฆฌ ์ฑ๋ฅ์ ๋ฏธ์น ์ํฅ์ ๋ถ์ํฉ๋๋ค.
- Job
InvoiceItemJob(์ฒญ๊ตฌ๋ด์ญ ์์ฑ)InvoiceJob(์ฒญ๊ตฌ์ ์์ฑ)
- ์ธํ๋ผ ์คํ ๋ณํ (Cloud Run / Cloud SQL vCPU, RAM)
gridSize (corePoolSize)chunkSize (= pageSize)- SQL ๊ตฌ์กฐ ๊ฐ์ ๋ฐ Step ๋ถ๋ฆฌ
- ์ค๋ณต ๊ฒ์ฆ ๋ก์ง ๊ฐ์ (
source_id์ปฌ๋ผ ์ถ๊ฐ)
- Partition: 6
- Application: vCPU 6
- DB: vCPU 1 / RAM 600MB
| JobName | chunkSize | gridSize | ๊ฒฐ๊ณผ |
|---|---|---|---|
| InvoiceItem | 3000 | 6 | โ 1์๊ฐ ์ด์ + Timeout |
- DB ์คํ ๋๋น ๋ณ๋ ฌ ์ฒ๋ฆฌ ๊ณผ๋ค
- Paging + Partition ํ๊ฒฝ์์ DB CPU / ๋ฉ๋ชจ๋ฆฌ ํ๊ณ ๋ ธ์ถ
- ์ธํ๋ผ ์์ฒด๊ฐ ๋ณ๋ชฉ์ด ๋๋ ๊ตฌ์กฐ์์ ํ์ธ
- Cloud Run: vCPU 6 / RAM 8GB
- DB: vCPU 2 / RAM 3.8GB
| JobName | ์คํ ์๊ฐ | chunkSize | gridSize | ๊ฒฐ๊ณผ |
|---|---|---|---|---|
| InvoiceItem | 2026.01.23 09:59 | 3000 | 4 | 45m 35s |
- Timeout ๋ฌธ์ ๋ ํด์
- ๊ทธ๋ฌ๋ ์คํ ์๊ฐ์ด ์ฌ์ ํ ๊ณผ๋ํจ
- ์ฟผ๋ฆฌ ๊ตฌ์กฐ ๋ฐ Step ์ค๊ณ ์์ฒด์ ํ๊ณ๊ฐ ๋ณ๋ชฉ์ผ๋ก ๋จ์ ์์
- ์ฒญ๊ตฌ๋ด์ญ ์์ฑ SQL ๊ตฌ์กฐ ๊ฐ์
- Item / Invoice Job ๊ฐ Step ๋ถ๋ฆฌ๋ก ์ฑ ์ ๋ช ํํ
| JobName | ์คํ ์๊ฐ | chunkSize | gridSize | ๊ฒฐ๊ณผ |
|---|---|---|---|---|
| Invoice | 15:50 | 3000 | 4 | 7m 17s |
| Invoice | 16:11 | 3000 | 2 | 12m 55s |
| InvoiceItem | 15:32 | 3000 | 4 | 9m 9s |
| InvoiceItem | 16:29 | 3000 | 2 | 19m 48s |
- SQL/Step ๊ตฌ์กฐ ๊ฐ์ ํจ๊ณผ๊ฐ ์ฆ์ ๋ฐ์๋จ
gridSize = 4๊ฐ2๋๋น ์ฝ 2๋ฐฐ ์ฑ๋ฅ ๊ฐ์- ๋ณ๋ ฌ ์ฒ๋ฆฌ ํจ์จ์ ์๊ณ์ ์ด ์กด์ฌํจ์ ํ์ธ
- DB vCPU: 2 โ 4
| JobName | ์คํ ์๊ฐ | chunkSize | gridSize | ๊ฒฐ๊ณผ | CPU Peak | ์ฟผ๋ฆฌ ์ง์ฐ Peak |
|---|---|---|---|---|---|---|
| InvoiceItem | 18:02 | 3000 | 2 | 13m 4s | 53.32% | 175 |
| Invoice | 18:28 | 3000 | 2 | 27m 2s | 50.32% | 253 |
- DB CPU ์ฌ์ ๋ ํ๋ณด๋์์ผ๋
- Invoice Job์์ ์ฟผ๋ฆฌ ๊ตฌ์กฐ + ์ค๋ณต ๊ฒ์ฆ ๋น์ฉ์ด ์๋ก์ด ๋ณ๋ชฉ์ผ๋ก ํ์ธ
- ๋จ์ ์คํ ํ์ฅ๋ง์ผ๋ก๋ ํ๊ณ๊ฐ ์์์ ๊ฒ์ฆ
- Invoice ์ง๊ณ ์ฟผ๋ฆฌ ์ต์ ํ
invoice_item.source_id์ปฌ๋ผ ์ถ๊ฐ- ์ค๋ณต ๊ฒ์ฆ ๋ฒ์ ์ถ์
- chunkSize ์ฆ๊ฐ
| JobName | ์คํ ์๊ฐ | chunkSize | gridSize | ๊ฒฐ๊ณผ | CPU Peak |
|---|---|---|---|---|---|
| InvoiceItem | 17:20 | 4000 | 4 | 4m 34s | 96.54% |
| Invoice | 17:30 | 4000 | 4 | 1m 18s | 53.33% |
- ์ ์ฒด ํ ์คํธ ์ค ๊ฐ์ฅ ํฐ ์ฑ๋ฅ ๊ฐ์ ๊ตฌ๊ฐ
- CPU ์์์ ์ ๊ทน ํ์ฉํ๋ ๊ตฌ์กฐ๋ก ์ ํ
- ์ฟผ๋ฆฌ + Batch ๊ตฌ์กฐ ์ต์ ํ์ ์ค์์ฑ ์ ์ฆ
- gridSize = 4
- chunkSize = 4000
- Cloud SQL vCPU = 6, RAM 5.4GB
| JobName | ์คํ ์๊ฐ | chunkSize | gridSize | ๊ฒฐ๊ณผ | CPU Peak |
|---|---|---|---|---|---|
| InvoiceItem | 11:38 | 4000 | 4 | 3m 0s | 57.1% |
| Invoice | 12:15 | 4000 | 4 | 53s | 23.93% |
| ๊ตฌ๋ถ | ์คํ ์๊ฐ |
|---|---|
| 1์ฐจ ํ ์คํธ | โฅ 60๋ถ (Timeout) |
| 6์ฐจ ํ ์คํธ | ์ฝ 3๋ถ |
- ์คํ ์๊ฐ ๊ฐ์์จ: ์ฝ 95% ์ด์ ๊ฐ์
- ์ฒ๋ฆฌ ์๋ ๊ฐ์ : ์ต์ 20๋ฐฐ ์ด์ ํฅ์
๋ณธ ์์คํ ์ GCP Cloud Function๊ณผ Cloud Run์ ํ์ฉํ ์๋ฒ๋ฆฌ์ค ์ํคํ ์ฒ๋ก ๋ฐฐ์น ์คํ์ ๊ด๋ฆฌํฉ๋๋ค. ์ฌ์ฉ์๋ ํ๋ก ํธ์๋ UI๋ฅผ ํตํด ์ฆ์ ์คํํ๊ฑฐ๋ ์ค์ผ์ค์ ์ค์ ํ ์ ์์ผ๋ฉฐ, Cloud Function์ด ์ด๋ฅผ ์ฒ๋ฆฌํ์ฌ ๋ฐฐ์น ์์ ์ ํธ๋ฆฌ๊ฑฐํฉ๋๋ค.
%%{init: {'theme': 'default', 'themeVariables': { 'fontColor': '#000000' }}}%%
sequenceDiagram
autonumber
actor User as ์ด์์
participant UI as Frontend UI
participant CF as Cloud Function
participant Scheduler as Cloud Scheduler
participant CR as Cloud Run<br/>(Batch API)
participant Batch as Spring Batch<br/>Application
participant DB as PostgreSQL
%% ์ฆ์ ์คํ ํ๋ก์ฐ
rect rgb(230, 240, 255)
note right of User: ์ฆ์ ์คํ ์๋๋ฆฌ์ค
User->>UI: ๋ฐฐ์น ์ฆ์ ์คํ ์์ฒญ
UI->>CF: POST /trigger-batch<br/>{jobName, params}
CF->>CR: HTTP POST<br/>/api/batch/execute
CR->>Batch: JobLauncher.run()
Batch->>DB: ๋ฐฐ์น ์์
์ํ
CF-->>UI: ํธ๋ฆฌ๊ฑฐ ์ฑ๊ณต ์๋ต
UI-->>User: ์คํ ์์ ์๋ฆผ
end
%% ์ํ ์กฐํ ํ๋ก์ฐ
rect rgb(255, 250, 240)
note right of User: ์ํ ์กฐํ ์๋๋ฆฌ์ค
User->>UI: ์๋ก๊ณ ์นจ/์ํ ํ์ธ
UI->>DB: Spring Batch Meta ํ
์ด๋ธ ์กฐํ
DB-->>UI: Job Execution ์ํ ๋ฐํ
UI-->>User: ์งํ์ค/์ฑ๊ณต/์คํจ ํ์
end
%% ์ค์ผ์ค ์ค์ ํ๋ก์ฐ
rect rgb(240, 255, 240)
note right of User: ์ค์ผ์ค ์ค์ ์๋๋ฆฌ์ค
User->>UI: ์ค์ผ์ค ์์ <br/>(Cron ํํ์)
UI->>CF: POST /update-schedule<br/>{cron, jobName}
CF->>Scheduler: Cloud Scheduler Job ์์ฑ/์์
Scheduler-->>CF: Job ์์ฑ ์๋ฃ
CF-->>UI: ์ค์ผ์ค ์ ์ฅ ์๋ฃ
UI-->>User: ์ค์ ์๋ฃ ์๋ฆผ
end
%% ์ค์ผ์ค ์คํ ํ๋ก์ฐ
rect rgb(255, 245, 230)
note right of Scheduler: ์์ฝ๋ ์๊ฐ ๋๋
Scheduler->>CF: Scheduled Trigger
CF->>CR: HTTP POST<br/>/api/batch/execute
CR->>Batch: JobLauncher.run()
Batch->>DB: ๋ฐฐ์น ์์
์ํ
end
์ญํ :
- ์ด์์์๊ฒ ๋ฐฐ์น ๊ด๋ฆฌ ์ธํฐํ์ด์ค ์ ๊ณต
- ์ฆ์ ์คํ ๋ฐ ์ค์ผ์ค ์ค์ ๊ธฐ๋ฅ ์ ๊ณต
- Spring Batch Meta ํ ์ด๋ธ์ ์กฐํํ์ฌ ์คํ ์ํ ํ์
์ฃผ์ ๊ธฐ๋ฅ:
- ์ฆ์ ์คํ: Job ์ด๋ฆ๊ณผ ํ๋ผ๋ฏธํฐ(invMonth ๋ฑ)๋ฅผ ์ ๋ ฅ๋ฐ์ Cloud Function ํธ์ถ
- ์ค์ผ์ค ์์ : Cron ํํ์์ ์ ๋ ฅ๋ฐ์ Cloud Function์ผ๋ก ์ ์กํ์ฌ ์ ๊ธฐ ์คํ ์์ฝ
- ์คํ ์ํ ์กฐํ:
- ์๋ก๊ณ ์นจ ๋ฒํผ ๋๋ ์๋ ํด๋ง์ ํตํด ์ํ ๊ฐฑ์
BATCH_JOB_EXECUTION,BATCH_STEP_EXECUTIONํ ์ด๋ธ ์ง์ ์กฐํ- ์งํ์ค(STARTED), ์๋ฃ(COMPLETED), ์คํจ(FAILED) ์ํ ํ์
์ญํ :
- Frontend์ Cloud Run ์ฌ์ด์ ๋จ์ ํธ๋ฆฌ๊ฑฐ ๊ณ์ธต
- ๋ฐฐ์น ์คํ ์์ฒญ์ Cloud Run์ผ๋ก ์ ๋ฌ๋ง ์ํ
- ์คํ ์ํ ์ถ์ ๋ฐ ์๋ฆผ ๊ธฐ๋ฅ ์์ (Fire and Forget ๋ฐฉ์)
์ฒ๋ฆฌ ๋ก์ง:
- ์ฆ์ ์คํ ์์ฒญ ์ฒ๋ฆฌ
- Frontend๋ก๋ถํฐ Job ์ด๋ฆ๊ณผ ํ๋ผ๋ฏธํฐ ์์
- Cloud Run์ ๋ฐฐ์น API ์๋ํฌ์ธํธ ํธ์ถ
- ํธ์ถ ์ฑ๊ณต ์ฌ๋ถ๋ง ํ์ธ ํ ์ฆ์ ์๋ต ๋ฐํ
- ์ดํ ๋ฐฐ์น ์คํ ์ํ๋ ์ถ์ ํ์ง ์์
- ์ค์ผ์ค ์ค์ ์์ฒญ ์ฒ๋ฆฌ
- Cron ํํ์๊ณผ Job ์ ๋ณด ์์
- Cloud Scheduler API๋ฅผ ํตํด ์์ฝ ์์ ์์ฑ/์์
- ์์ฑ๋ Scheduler Job์ ์ง์ ๋ ์๊ฐ์ ์๋์ผ๋ก Cloud Function ํธ์ถ
์ญํ :
- Cron ํํ์ ๊ธฐ๋ฐ ์ ๊ธฐ ์คํ ๊ด๋ฆฌ
- ์ค์ ๋ ์๊ฐ์ Cloud Function์ ์๋ ํธ๋ฆฌ๊ฑฐ
๋์ ๋ฐฉ์:
- ๋งค์ 1์ผ 02:00์ ๊ฐ์ ์ ๊ธฐ ์ค์ผ์ค์ Cron ํํ์์ผ๋ก ์ ์
- ์์ฝ๋ ์๊ฐ ๋๋ ์ ์๋์ผ๋ก Cloud Function์
/api/batch/schedule์๋ํฌ์ธํธ ํธ์ถ - Timezone ์ค์ ์ ํตํด ํ๊ตญ ์๊ฐ(Asia/Seoul) ๊ธฐ์ค ์คํ ๋ณด์ฅ
์์:
0 2 1 * *โ ๋งค์ 1์ผ 02:00์ ์ฒญ๊ตฌ์ ์์ฑ ๋ฐฐ์น ์คํ0 3 * * *โ ๋งค์ผ 03:00์ ์ฒญ๊ตฌ๋ด์ญ ์ง๊ณ ๋ฐฐ์น ์คํ
์ญํ :
- Spring Batch Application์ ์ปจํ ์ด๋๋ก ์คํ
- RESTful API๋ฅผ ํตํด ์ธ๋ถ๋ก๋ถํฐ Job ์คํ ์์ฒญ ์์
- JobLauncher๋ฅผ ํตํด ์ค์ ๋ฐฐ์น ์์ ํธ๋ฆฌ๊ฑฐ
์ฃผ์ ๊ธฐ๋ฅ:
- ๋ฐฐ์น ์คํ API ์ ๊ณต
/api/batch/run-now์๋ํฌ์ธํธ๋ก Job ์คํ ์์ฒญ ์์- Job ์ด๋ฆ๊ณผ ํ๋ผ๋ฏธํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก JobParameters ์์ฑ
- JobLauncher.run()์ ๋น๋๊ธฐ๋ก ํธ์ถํ์ฌ ๋ฐฐ์น ์์
- ์ฆ์ ์๋ต ๋ฐํ (๋ฐฐ์น ์๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฌ์ง ์์)
- ์๋ ํ์ฅ(Auto Scaling)
- ๋์ ์คํ ์์ฒญ์ด ๋ง์ ๊ฒฝ์ฐ ์๋์ผ๋ก ์ธ์คํด์ค ์ฆ๊ฐ
- ๋ฐฐ์น ์๋ฃ ํ ์๋์ผ๋ก ์ธ์คํด์ค ์ถ์ํ์ฌ ๋น์ฉ ์ ๊ฐ
๋ฐฐํฌ ํน์ฑ:
- ์ต๋ 3,600์ด(1์๊ฐ) ํ์์์ ์ค์ ์ผ๋ก ์ฅ์๊ฐ ๋ฐฐ์น ์ง์
- ๋ฉ๋ชจ๋ฆฌ 8Gi, CPU 4 ์ฝ์ด๋ก ๋์ฉ๋ ์ฒ๋ฆฌ ์ฑ๋ฅ ํ๋ณด
์ญํ :
- ๋ฐฐ์น ์คํ ์ด๋ ฅ ๋ฐ ์ํ๋ฅผ ์ ์ฅํ๋ ๋ฉํ๋ฐ์ดํฐ ์ ์ฅ์
์ฃผ์ ํ ์ด๋ธ:
BATCH_JOB_INSTANCE: Job ์ธ์คํด์ค ์ ๋ณดBATCH_JOB_EXECUTION: Job ์คํ ์ด๋ ฅ ๋ฐ ์ํBATCH_STEP_EXECUTION: Step๋ณ ์คํ ์ํ ๋ฐ ์งํ๋ฅBATCH_JOB_EXECUTION_PARAMS: Job ์คํ ํ๋ผ๋ฏธํฐ
Frontend ์กฐํ ์ฟผ๋ฆฌ ์์:
-- ์ต๊ทผ ์คํ ์ด๋ ฅ ์กฐํ
SELECT
je.JOB_EXECUTION_ID,
ji.JOB_NAME,
je.STATUS,
je.START_TIME,
je.END_TIME,
je.EXIT_CODE
FROM BATCH_JOB_EXECUTION je
JOIN BATCH_JOB_INSTANCE ji ON je.JOB_INSTANCE_ID = ji.JOB_INSTANCE_ID
ORDER BY je.START_TIME DESC
LIMIT 20;
-- ํน์ Job์ ์งํ ์ํฉ ์กฐํ
SELECT
se.STEP_NAME,
se.STATUS,
se.READ_COUNT,
se.WRITE_COUNT,
se.COMMIT_COUNT
FROM BATCH_STEP_EXECUTION se
WHERE se.JOB_EXECUTION_ID = :executionId;
flowchart TB
subgraph ์คํ_๋จ๊ณ["1. ๋ฐฐ์น ์คํ ๋จ๊ณ"]
A[์ด์์] -->|1-1. ์ฆ์ ์คํ ํด๋ฆญ| B[Frontend UI]
B -->|1-2. Job ์ ๋ณด ์ ๋ฌ| C[Cloud Function]
C -->|1-3. ํธ๋ฆฌ๊ฑฐ ์์ฒญ| D[Cloud Run]
D -->|1-4. ๋น๋๊ธฐ ์์| E[Spring Batch]
C -->|1-5. ํธ๋ฆฌ๊ฑฐ ์๋ฃ ์๋ต| B
B -->|1-6. ์คํ ์์ ์๋ฆผ| A
end
subgraph ์ํ_ํ์ธ_๋จ๊ณ["2. ์ํ ํ์ธ ๋จ๊ณ"]
A2[์ด์์] -->|2-1. ์๋ก๊ณ ์นจ| B2[Frontend UI]
B2 -->|2-2. ๋ฉํ ํ
์ด๋ธ ์กฐํ| F[(Spring Batch Meta)]
F -->|2-3. ์คํ ์ํ ๋ฐํ| B2
B2 -->|2-4. ์ํ ํ์<br/>์งํ์ค/์๋ฃ/์คํจ| A2
end
E -->|๋ฐฐ์น ์ํ| F
style E fill:#e1f5ff,color:#000
style D fill:#fff4e1,color:#000
style C fill:#f0ffe1,color:#000
style F fill:#ffe1f5,color:#000
์ฒ๋ฆฌ ํ๋ฆ:
[์คํ ๋จ๊ณ]
- ์ด์์๊ฐ UI์์ "InvoiceItemJob ์คํ" ๋ฒํผ ํด๋ฆญ ๋ฐ ํ๋ผ๋ฏธํฐ ์ ๋ ฅ (์: invMonth=2026-01)
- Frontend๊ฐ Cloud Function์ Job ์ด๋ฆ๊ณผ ํ๋ผ๋ฏธํฐ๋ฅผ JSON์ผ๋ก ์ ์ก
- Cloud Function์ด Cloud Run์
/api/batch/execute์๋ํฌ์ธํธ์ HTTP POST ์์ฒญ - Cloud Run์ด JobLauncher๋ฅผ ๋น๋๊ธฐ๋ก ํธ์ถํ์ฌ ๋ฐฐ์น ์์
- Cloud Function์ด ์ฆ์ "ํธ๋ฆฌ๊ฑฐ ์ฑ๊ณต" ์๋ต ๋ฐํ (๋ฐฐ์น ์๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฌ์ง ์์)
- Frontend๊ฐ ์ด์์์๊ฒ "๋ฐฐ์น ์คํ์ด ์์๋์์ต๋๋ค" ์๋ฆผ ํ์
[์ํ ํ์ธ ๋จ๊ณ]
- ์ด์์๊ฐ UI์์ "์๋ก๊ณ ์นจ" ๋ฒํผ ํด๋ฆญ ๋๋ 5์ด๋ง๋ค ์๋ ํด๋ง
- Frontend๊ฐ Database์
BATCH_JOB_EXECUTIONํ ์ด๋ธ ์ง์ ์กฐํ - ์ต์ ์คํ ์ํ(STARTED, COMPLETED, FAILED) ๋ฐ ์งํ๋ฅ ์กฐํ
- UI์ ์ค์๊ฐ ์ํ ๊ฐฑ์ :
- ์งํ์ค: ์งํ๋ฅ ๋ฐ ํ์ (READ_COUNT / ์์ ์ด ๊ฑด์)
- ์๋ฃ: ์ฑ๊ณต ์์ด์ฝ ๋ฐ ์ฒ๋ฆฌ ๊ฑด์, ์์ ์๊ฐ ํ์
- ์คํจ: ์๋ฌ ๋ฉ์์ง ๋ฐ ์คํจ ์์ธ ํ์
flowchart TB
subgraph ์ค์ _๋จ๊ณ["1. ์ค์ผ์ค ์ค์ ๋จ๊ณ"]
A[์ด์์] -->|1-1. Cron ์ค์ | B[Frontend UI]
B -->|1-2. ์ค์ผ์ค ์ ๋ณด ์ ๋ฌ| C[Cloud Function]
C -->|1-3. Scheduler Job ์์ฑ| D[Cloud Scheduler]
C -->|1-4. ์์ฑ ์๋ฃ| B
B -->|1-5. ์ค์ ์๋ฃ ์๋ฆผ| A
end
subgraph ์๋_์คํ_๋จ๊ณ["2. ์๋ ์คํ ๋จ๊ณ"]
D -.->|2-1. ์์ฝ ์๊ฐ ๋๊ธฐ| D
D -->|2-2. ์๊ฐ ๋๋| C2[Cloud Function]
C2 -->|2-3. ํธ๋ฆฌ๊ฑฐ| E[Cloud Run]
E -->|2-4. ๋ฐฐ์น ์์| F[Spring Batch]
F -->|2-5. ๋ฐฐ์น ์ํ| G[(Database)]
end
style F fill:#e1f5ff,color:#000
style E fill:#fff4e1,color:#000
style D fill:#ffe1e1,color:#000
์ฒ๋ฆฌ ํ๋ฆ:
[์ค์ ๋จ๊ณ]
- ์ด์์๊ฐ UI์์ "๋งค์ 1์ผ 02:00 ์คํ" ์ค์ผ์ค ์ค์ (Cron:
0 2 1 * *) - Frontend๊ฐ Cloud Function์ Cron ํํ์๊ณผ Job ์ ๋ณด ์ ์ก
- Cloud Function์ด Cloud Scheduler API๋ฅผ ํธ์ถํ์ฌ ์์ฝ ์์ ์์ฑ
- Cloud Scheduler Job์ด ์ ์ ์์ฑ๋๊ณ ๋๊ธฐ ์ํ๋ก ์ ํ
- Frontend๊ฐ ์ด์์์๊ฒ "์ค์ผ์ค์ด ์ค์ ๋์์ต๋๋ค" ์๋ฆผ ํ์
[์๋ ์คํ ๋จ๊ณ]
- Cloud Scheduler๊ฐ ์์ฝ ์๊ฐ๊น์ง ๋๊ธฐ
- ๋งค์ 1์ผ 02:00(KST)์ด ๋๋ฉด Cloud Scheduler๊ฐ ์๋์ผ๋ก Cloud Function ํธ์ถ
- Cloud Function์ด Cloud Run์ ๋ฐฐ์น ์คํ ์์ฒญ ์ ๋ฌ (Fire and Forget)
- Cloud Run์ด JobLauncher๋ฅผ ํตํด ๋ฐฐ์น ์์
- Spring Batch๊ฐ ์ฒญ๊ตฌ์ ์์ฑ ์์ ์ํ ๋ฐ Database์ ์ ์ฅ
- ์ด์์๋ ๋ค์๋ ์ถ๊ทผ ํ UI์์ Meta ํ ์ด๋ธ ์กฐํ๋ฅผ ํตํด ์คํ ๊ฒฐ๊ณผ ํ์ธ
| ํญ๋ชฉ | ๊ธฐ์กด ๋ฐฉ์ (On-Premise) | Cloud ๊ธฐ๋ฐ ๋ฐฉ์ | ๊ฐ์ ํจ๊ณผ |
|---|---|---|---|
| ์ธํ๋ผ ๊ด๋ฆฌ | ์๋ฒ ์ง์ ๊ด๋ฆฌ ํ์ | ์๋ฒ๋ฆฌ์ค (์๋ ํ์ฅ) | ์ด์ ๋ถ๋ด ์ ๋ก |
| ๋น์ฉ ํจ์จ | 24์๊ฐ ์๋ฒ ๊ฐ๋ | ์คํ ์์๋ง ๊ณผ๊ธ | ์ ํด ์๊ฐ ๋น์ฉ 70% ์ ๊ฐ |
| ์ค์ผ์ค ๊ด๋ฆฌ | Cron Tab ์๋ ์ค์ | Cloud Scheduler UI | ์ค์ ์ง์ค ๊ด๋ฆฌ |
| ํ์ฅ์ฑ | ์๋ฒ ์ฆ์ค ํ์ | Auto Scaling | ํธ๋ํฝ ๋์ ์๋ํ |
| ๋ชจ๋ํฐ๋ง | ๋ณ๋ ๋ก๊น ์์คํ ๊ตฌ์ถ | Cloud Logging ํตํฉ | ํตํฉ ๋์๋ณด๋ ์ ๊ณต |
| ์ํ ์กฐํ | SSH ์ ์ ํ ๋ก๊ทธ ํ์ธ | UI์์ ์ค์๊ฐ ์กฐํ | ํธ์์ฑ ํฅ์ |
๋ณธ ๋ฐฐ์น ์์คํ ์ ๋ค์์ ๋ชจ๋ ๋ง์กฑํ๋ ์ค๋ฌด ์์ค์ ์ ์ฐ ๋ฐฐ์น ์์คํ ์ผ๋ก ์์ฑ๋์์ต๋๋ค:
โ ๋์ฉ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
- Partitioning ๊ธฐ๋ฐ ๋ณ๋ ฌ ์ฒ๋ฆฌ๋ก 100๋ง ๊ฑด ์ด์ ๋ฐ์ดํฐ๋ฅผ 36์ด ๋ด ์ฒ๋ฆฌ
โ ๋ฐ์ดํฐ ๋๋ฝ/์ค๋ณต ๋ฐฉ์ง
- ๋ณตํฉ ์ ๋ ฌ ํค, ๋ฒ์ ๊ธฐ๋ฐ ํํฐ์ ๋, UNIQUE ์ ์ฝ์ผ๋ก ์๋ฒฝํ ์ ํฉ์ฑ ๋ณด์ฅ
โ ์ฌ์คํ ์์ ์ฑ
- ExecutionContext์ ๋ฉฑ๋ฑ์ฑ ์ค๊ณ๋ก ์ฅ์ ๋ณต๊ตฌ ์๋๋ฆฌ์ค ์๋ฒฝ ๋์
โ ๋น์ฉ ๋๋น ์ต๋ ์ฑ๋ฅ
- DB ๋ ๋ฒจ ์ง๊ณ, JDBC Batch Insert, Sweet Spot ํ๋์ผ๋ก ๋คํธ์ํฌ/์ฐ์ฐ ๋น์ฉ ์ต์ํ
| ์์ญ | ๊ธฐ์ | ํจ๊ณผ |
|---|---|---|
| Reader | JdbcPagingItemReader + DB ์ง๊ณ | ๋คํธ์ํฌ ์ ์ก๋ ๊ฐ์ |
| Writer | JdbcBatchItemWriter | DB ์๋ณต ํ์ ์ต์ํ |
| ๋ณ๋ ฌํ | Partitioning (Grid=6) | ์ฒ๋ฆฌ ์๊ฐ 22% ๋จ์ถ |
| ๋ชจ๋ํฐ๋ง | Chunk/Partition Listener | ์ค์๊ฐ ์งํ๋ฅ ๋ฐ ๊ท ๋ฑ ๋ถ๋ฐฐ ๊ฒ์ฆ |