๋ฐฐ๊ฒฝ
๋ฉํฐ์ค๋ ๋ ๋ฐฐ์น ์์คํ ์์ ๋ฒํฌ INSERT๊ฐ ๊ฐํ์ ์ผ๋ก ํ์์์์ผ๋ก ์คํจํ๋ค. ๊ธฐ์กด ์ฝ๋๋ ์์ธ๋ฅผ ๋ก๊ทธ๋ก๋ง ๋จ๊ฒผ๊ธฐ ๋๋ฌธ์ ์คํจํ ์์ฒ ๊ฑด์ด ๊ทธ๋๋ก ์ ์ค๋๋ค. ๊ฒ๋ค๊ฐ ํ์์์์ด ๋ฐ์ํ๋ ์์ ์ DB ๋ถํ๊ฐ ๋์ ๊ตฌ๊ฐ์ด๋ค. ์คํจ๊ฐ ์ฐ๋ฌ์ ์ ์ฒ๊ฑด์ฉ N๋ฒ ๋ฐ์ํ๋ค.
์๋ํ๋ ๋ฐฉ์
๋จ์ํ ์์ธ๋ฅผ ์ก์ ์ฌ์๋ํ๋ ๋ฐฉ์์ ๋ฐฐ์น ์ค๋ ๋๋ฅผ ์ง์ฐ์ํค๊ณ , ์คํจ๊ฐ ์ฐ๋ฌ์ ๋ฐ์ํ๋ฉด ์ฌ์๋ ๋ก์ง๋ ํจ๊ป ์คํจํ๋ค.

ํ์์์์ด ๋ฐ์ํ๋ ์์ ์ DB ๋ถํ๊ฐ ๋์ ๊ตฌ๊ฐ์ด๋ค. ์ฌ์๋๋ฅผ ์ฆ์ ์คํํ๋ฉด ๊ฐ์ ๋ถํ ๊ตฌ๊ฐ์์ ๋ค์ ์คํจํ ๊ฐ๋ฅ์ฑ์ด ๋๊ณ , ๊ทธ ์ฌ์ด ๋ค๋ฅธ ๋ฐฐ์น๋ ๊ฐ์ ์ค๋ ๋ํ์ ๊ธฐ๋ค๋ฆฐ๋ค. ์คํจ๊ฐ ์ฐ๋ฌ์ ์ ์ฒ๊ฑด์ฉ N๋ฒ ๋ฐ์ํ๋ ํจํด์์ ์ธ๋ผ์ธ ์ฌ์๋๋ ์์ญ ๋ฒ ์ฐ์์ผ๋ก ์์ฌ ๋ฐฐ์น ์ ์ฒด๋ฅผ ๋ง๋น์ํฌ ์ ์๋ค.
๋์ ์คํจ ๋ฐ์ดํฐ๋ฅผ ํ์ผ๋ก ๋ณด์กดํ ๋ค ๋ณ๋ ์ค์ผ์ค๋ฌ๋ก ์ฌ์ฒ๋ฆฌํ๋ ๊ตฌ์กฐ๋ฅผ ์ ํํ๋ค. ๋ฐฐ์น ์ค๋ ๋๋ ์คํจ๋ฅผ ํ์ ๋ฃ๋ ๊ฒ๋ง ํ๊ณ ์ฆ์ ๋ค์ chunk๋ก ๋์ด๊ฐ๋ค. DB ๋ถํ๊ฐ ํด์๋ ๋ค, ๋ณ๋ ์ค์ผ์ค๋ฌ๊ฐ ๋จ๊ฑด INSERT๋ก ์ฌ์ฒ๋ฆฌํ๋ฉด ํ์์์์ด ๋ฐ์ํ์ง ์๋๋ค.
์ํคํ ์ฒ ์ค๊ณ

์ ์ฒด ํ๋ฆ์ ์คํจ ๊ฐ์ง → ํ์ ์ฝ์ → ํ์ผ์ ์ฐ๊ธฐ → ์ฌ์ฒ๋ฆฌ ๋ค ๋จ๊ณ๋ก ๋๋๋ค. ๊ฐ ๋จ๊ณ๋ ์๋ก ๋ค๋ฅธ ์ค๋ ๋์์ ๋ ๋ฆฝ์ ์ผ๋ก ๋์ํ๋ค.
๋ฐฐ์น ์ค๋ ๋๋ ์คํจ๋ฅผ ํ์ ๋ฃ๋ ์๊ฐ ํ ์ผ์ด ๋๋๋ค. ํ ๋ค์์ ๋ฌด์จ ์ผ์ด ์ผ์ด๋๋์ง ์ ํ์๊ฐ ์๋ค. FailFileWriter๋ ํ์์ ๋ฐ์ดํฐ๋ฅผ ๊บผ๋ด ํ์ผ์ ์ฐ๋ ์ผ๋ง ํ๋ค. FailScheduler๋ ํ์ผ์ด ์ธ์ ๋ง๋ค์ด์ก๋์ง ์ ๊ฒฝ ์ฐ์ง ์๊ณ ์ ํด์ง ์๊ฐ์ ํ์ผ์ ์ฝ์ด ์ฌ์ฒ๋ฆฌํ๋ค.
๊ฐ ๋จ๊ณ๊ฐ ๋ค์ ๋จ๊ณ๋ฅผ ์ง์ ํธ์ถํ์ง ์๊ณ , ํ์ ํ์ผ์ด๋ผ๋ ์ค๊ฐ ์ ์ฅ์๋ฅผ ํตํด์๋ง ๋ฐ์ดํฐ๋ฅผ ๋๊ธด๋ค. ๊ฐ ์ปดํฌ๋ํธ๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก ๋์ํ๊ธฐ ๋๋ฌธ์ ์ฌ์ฒ๋ฆฌ ๋ก์ง์ด ๋ฐฐ์น ์ฑ๋ฅ์ ์ํฅ์ ์ฃผ์ง ์๋๋ค.
๊ตฌํ ์์ธ
DAO ๋ ์ด์ด - INSERT ์คํจ ๊ฐ์ง
public int collectProductData(User user, List<Product> products, ...) {
try (SqlSession sqlSession = ...) {
sqlSession.insert("productMapper.insertBulk", products);
return products.size();
} catch (Exception e) {
failQueue.offerAll(user, products, ...); // ์คํจ → ํ ์ฝ์
return 0;
}
}
DAO ๋ ์ด์ด์์ ์คํจ๋ฅผ ๊ฐ์งํ๋ฉด ์์ธ๋ฅผ ์์๋ก ๋์ง์ง ์๊ณ `FailQueue.offerAll()`์ ํธ์ถํด ์คํจ ๋ฐ์ดํฐ๋ฅผ ํ์ ๋ฃ๋๋ค. ๋ฐฐ์น ์ค๋ ๋ ์ ์ฅ์์ ์คํจ ์ฒ๋ฆฌ๋ ์ฌ๊ธฐ์ ๋๋๊ณ , ์ดํ ํ์ผ ๊ธฐ๋ก๊ณผ ์ฌ์ฒ๋ฆฌ๋ ๋ณ๋ ์ปดํฌ๋ํธ๊ฐ ๋ด๋นํ๋ค.
ํ ์ฝ์ ์ด ๋ธ๋กํน๋์ง ์๊ธฐ ๋๋ฌธ์ ๋ฐฐ์น ์ค๋ ๋๋ ์ฆ์ ๋ค์ chunk๋ก ๋์ด๊ฐ๋ค.
FailQueue - ๊ณต์ ํ
private final BlockingQueue<Object> queue = new LinkedBlockingQueue<>(200_000);
public void offerAll(UserInfo user, List<Product> products) {
for (Product product : products) {
queue.offer(new FailedProduct(user, product));
}
}
public boolean offer(CloseRequest closeRequest) {
return queue.offer(closeRequest);
}
FailQueue๋ LinkedBlockingQueue๋ก ๊ตฌํ๋ ๊ณต์ ๋ฒํผ๋ค. DAO ๋ ์ด์ด(producer)์ FailFileWriter(consumer) ์ฌ์ด๋ฅผ ์ฐ๊ฒฐํ๋ค. ๋ฐฐ์น ์ค๋ ๋๊ฐ ์ฌ๋ฌ ๊ฐ์ฌ๋ ํ๋ ํ๋์ด๊ธฐ ๋๋ฌธ์ ๋์์ฑ ์ฒ๋ฆฌ๋ BlockingQueue๊ฐ ๋ด๋ถ์ ์ผ๋ก ๋ณด์ฅํ๋ค.
FailedProduct(์คํจ ๋ฐ์ดํฐ)์ CloseRequest(๋ฐฐ์น ์๋ฃ ์ ํธ) ๋ ํ์ ์ ๊ฐ์ ํ๋ก ๊ด๋ฆฌํด consumer๊ฐ ์์๋ฅผ ๋ณด์ฅ๋ฐ๋๋ค.
- offer vs put: put์ ํ๊ฐ ๊ฝ ์ฐจ๋ฉด ๊ณต๊ฐ์ด ์๊ธธ ๋๊น์ง ๋ธ๋กํนํ๋ค. ๋ฐฐ์น ์ค๋ ๋๊ฐ ํ ์ฝ์ ๋๋ฌธ์ ๋ฉ์ถ๋ฉด ์ ๋๋ฏ๋ก ๋ธ๋กํนํ์ง ์๋ offer๋ฅผ ์ฌ์ฉํ๋ค. ํ๊ฐ ๊ฝ ์ฐจ์ offer๊ฐ false๋ฅผ ๋ฐํํ๋ ์ํฉ์ 200,000๊ฑด์ด ๋ชจ๋ ์์ธ ๊ทน๋จ์ ์ธ ๊ฒฝ์ฐ๋ก, ์ค์ด์์์๋ ๋ฐ์ํ์ง ์์ ๊ฒ์ผ๋ก ํ๋จํ๋ค.
FailFileWriter - ์ ์ฉ consumer ์ค๋ ๋
ํ๋ ๊ตฌ์ฑ
private final FailQueue failQueue;
private final Map<String, BufferedWriter> writerMap = new ConcurrentHashMap<>();
private final ObjectMapper objectMapper = new ObjectMapper();
private volatile boolean running = true;
ํ๋ ์ค๋ช
| ํ๋ | ์ค๋ช |
| writerMap | ํค๋ก Writer ๊ด๋ฆฌ |
| running | volatile ์ ์ธ์ผ๋ก @PreDestroy์ consumer ์ค๋ ๋ ๊ฐ ๊ฐ์์ฑ ๋ณด์ฅ |
| objectMapper | consumer ์ค๋ ๋๊ฐ 1๊ฐ์ด๋ฏ๋ก ์ธ์คํด์ค ๊ณต์ ์์ |
- writerMap : userId#...#...#date ํํ์ ํค๋ก Writer ์ธ์คํด์ค๋ฅผ ๊ด๋ฆฌํ๋ค. ๋ฐฐ์น ์ ํ, ๋ ์ง ๋ฑ ์กฐํฉ๋ง๋ค ํ์ผ์ด ํ๋์ฉ ์ด๋ฆฌ๊ธฐ ๋๋ฌธ์, ํค๋ง ๋ณด๋ฉด ์ด๋ค ๋ฐฐ์น์ ์คํจ ๋ฐ์ดํฐ๊ฐ ์ด๋ ํ์ผ์ ๊ธฐ๋ก๋๊ณ ์๋์ง ์ ์ ์๋ค. ConcurrentHashMap์ ์ด ์ด์ ๋ consumeLoop(consumer ์ค๋ ๋)์ shutdown()(@PreDestroy, ๋ฉ์ธ ์ค๋ ๋)์ด ๋์์ ๋งต์ ์ ๊ทผํ ์ ์์ด์๋ค. consumer ์ค๋ ๋๊ฐ ํ๋๋ฟ์ด๋๋ผ๋ ์ข ๋ฃ ์์ ์ ๋ ์ค๋ ๋๊ฐ ๋งต์ ๋์์ ์ฝ๊ณ ์ธ ์ ์์ผ๋ฏ๋ก ์ผ๋ฐ HashMap์ ์์ ํ์ง ์๋ค.
- running ํ๋๊ทธ๋ volatile๋ก ์ ์ธํ๋ค. @PreDestroy๊ฐ ์คํ๋๋ ๋ฉ์ธ ์ค๋ ๋์์ false๋ก ๋ณ๊ฒฝํ ๊ฐ์ด consumer ์ค๋ ๋์ ์ฆ์ ๋ณด์ฌ์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ค. volatile์ด ์์ผ๋ฉด JVM์ด ๋ ์ง์คํฐ๋ CPU ์บ์์ ๊ฐ์ ๋ค๊ณ ์์ ์ ์์ด consumer ์ค๋ ๋๊ฐ ๋ณ๊ฒฝ์ ์ธ์ํ์ง ๋ชปํ ์ ์๋ค.
voliatile์ ๋ํ ์์ธํ ๋ด์ฉ์ ์ด์ ์ ๋ค๋ฃฌ ์ ์ด ์๋ค.
[Java] volatile - ๋ฉํฐ์ค๋ ๋ ๊ฐ์์ฑ ๋ฌธ์ ํธ๋ฌ๋ธ์ํ
๋ค์ด๊ฐ๋ฉฐ ๋ ๋ฆฝ ํ + ์ ์ฉ ์์ปค ์ค๋ ๋๋ฅผ ์ฌ์ฉํ๋ ์๋น์ค์์ ์ข ๋ฃ ์ ํธ๊ฐ ์์ปค ์ค๋ ๋์ ์ ๋ฌ๋์ง ์๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. @PreDestroy์์ running = false๋ฅผ ๋ถ๋ช ํ ์ผ๋๋ฐ๋ ์์ปค ์ค๋ ๋๋ ๋ฃจํ๋ฅผ ๊ณ
programmingiraffe.tistory.com
์ค๋ ๋ ์๋ช ์ฃผ๊ธฐ
@PostConstruct
public void start() {
Thread t = new Thread(this::consumeLoop, "fail-file-writer");
t.start();
}
@PreDestroy
public void shutdown() {
running = false;
for (BufferedWriter writer : writerMap.values()) {
try { writer.close(); }
catch (IOException e) { log.error("shutdown: ํ์ผ ๋ซ๊ธฐ ์คํจ"); }
}
}
์ ํ๋ฆฌ์ผ์ด์ ๊ธฐ๋ ์ @PostConstruct๊ฐ consumer ์ค๋ ๋๋ฅผ ํ๋ ๋์ด๋ค. ์ค๋ ๋ ์ด๋ฆ์ fail-file-writer๋ก ๋ช ์ํ ๊ฒ์ ์ค๋ ๋ ๋ชจ๋ํฐ๋ง์์ ์ญํ ์ ์๋ณํ๊ธฐ ์ํด์๋ค.
@PreDestroy์์๋ running = false๋ก ๋ฃจํ๋ฅผ ๋น ์ ธ๋์ค๊ฒ ํ ๋ค ์ด๋ ค์๋ Writer๋ฅผ ๋ชจ๋ ๋ซ๋๋ค.
consumeLoop
private void consumeLoop() {
try {
while (running) {
Object object = failQueue.take(); // ํ๊ฐ ๋น ๋๊น์ง ๋ธ๋กํน
if (object instanceof FailedProduct(UserInfo user, Product product)) {
String key = user.getId() + "#" + .. + "#" + LocalDate.now();
BufferedWriter writer = writerMap.computeIfAbsent(key, k -> createWriter(k));
writer.write(objectMapper.writeValueAsString(product));
writer.newLine();
writer.flush(); // ๋งค ๊ฑด๋ง๋ค flush — ํ๋ก์ธ์ค ์ข
๋ฃ ์ ์ ์ค ๋ฐฉ์ง
}
if (object instanceof CloseRequest(String userId, ..)) {
closeWriterInternal(userId + "#" + ..);
}
}
} catch (Exception e) {
log.error("consumeLoop ๋น์ ์ ์ข
๋ฃ", e);
}
}
take()๋ ํ๊ฐ ๋น ๋๊น์ง ๋ธ๋กํนํ๋ค. poll()์ฒ๋ผ ํ์์์์ ๊ฑธ์ด ์ฃผ๊ธฐ์ ์ผ๋ก ๋ฃจํ๋ฅผ ๋๊ฒ ํ ์๋ ์์ง๋ง, ๋ฐ์ดํฐ๊ฐ ์์ ๋ CPU๋ฅผ ๋ญ๋นํ์ง ์์ผ๋ ค๋ฉด take()๋ก ๋ธ๋กํนํ๋ ๊ฒ์ด ๋ซ๋ค. ๋ฐฐ์น๊ฐ ์ค๋ซ๋์ ์คํ๋์ง ์๋ ์๊ฐ๋์๋ ์ค๋ ๋๊ฐ CPU๋ฅผ ์ ์ ํ์ง ์๋๋ค.
FailedProduct๋ฅผ ๊บผ๋ด๋ฉด, ํค๋ก Writer๋ฅผ ์ฐพ๊ฑฐ๋ ์๋ก ์์ฑํด JSON ์ง๋ ฌํ ๊ฒฐ๊ณผ๋ฅผ ํ ์ค๋ก ๊ธฐ๋กํ๊ณ ์ฆ์ flush()ํ๋ค. BufferedWriter๋ ๋ด๋ถ ๋ฒํผ๊ฐ ์ฐจ์ผ ๋์คํฌ์ ์ฐ๊ธฐ ๋๋ฌธ์, flush() ์์ด ํ๋ก์ธ์ค๊ฐ ๋น์ ์ ์ข ๋ฃ๋๋ฉด ๋ฒํผ์ ๋จ์ ๋ฐ์ดํฐ๊ฐ ์ฌ๋ผ์ง๋ค. ์ฌ์ฒ๋ฆฌ ๋์์ ๋ณด์กดํ๋ ์ญํ ์ด๋ฏ๋ก I/O ํ์๋ณด๋ค ๋ฐ์ดํฐ ์์ ์ ์ฐ์ ํ๋ค.
CloseRequest๋ฅผ ๊บผ๋ด๋ฉด ํด๋น ๋ฐฐ์น์ Writer๋ฅผ flush/closeํ๋ค. close ์ดํ ๊ฐ์ ํค๋ก ๋ฐ์ดํฐ๊ฐ ๋ค์ ๋ค์ด์ค๋ฉด computeIfAbsent๊ฐ Writer๋ฅผ ์๋ก ์์ฑํ๊ธฐ ๋๋ฌธ์ APPEND ์ต์ ์ด ์ด ๊ฒฝ์ฐ์๋ ์ค์ํ๋ค.
ํ์ผ ์์ฑ - APPEND ์ต์
private BufferedWriter createWriter(String key) {
try {
Path dir = Paths.get(FAIL_PATH);
Files.createDirectories(dir);
Path file = dir.resolve(key + ".txt");
return Files.newBufferedWriter(file,
StandardOpenOption.CREATE,
StandardOpenOption.APPEND // ์ฌ์์ ํ์๋ ๊ธฐ์กด ํ์ผ์ ์ด์ด์ฐ๊ธฐ
);
} catch (IOException e) {
log.error("[{}] ํ์ผ ์์ฑ ์คํจ", key);
return null;
}
}
APPEND ์ต์ ์ด ์์ผ๋ฉด Files.newBufferedWriter์ ๊ธฐ๋ณธ๊ฐ์ธ TRUNCATE_EXISTING์ด ์ ์ฉ๋ผ ๊ธฐ์กด ํ์ผ ๋ด์ฉ์ด ์ง์์ง๋ค. ๋ฐฐ์น ์คํ ์ค์ ์ฑ์ด ์ฌ์์๋๋ฉด ์ด์ ์คํจ ๋ฐ์ดํฐ๊ฐ ํ์ผ์ ๋จ์์๋ ์ํ์์ ์ ์คํจ ๋ฐ์ดํฐ๊ฐ ์ถ๊ฐ๋ ์ ์๋๋ฐ, APPEND ๋๋ถ์ ์ด์ ๋ฐ์ดํฐ๋ฅผ ๋ฎ์ด์ฐ์ง ์๊ณ ๋ค์ ์ด์ด์ด๋ค. ์ค์ผ์ค๋ฌ๋ ํ์ผ ์ ์ฒด๋ฅผ ์์๋๋ก ์ฒ๋ฆฌํ๋ฏ๋ก ์ฌ์์ ์ ํ ๋ฐ์ดํฐ๊ฐ ๊ฐ์ ํ์ผ์ ์์ฌ ์์ด๋ ๋ฌธ์ ์๋ค.
Writer ์ข ๋ฃ - prefix ๋งค์นญ
private void closeWriterInternal(String requestKey) {
writerMap.entrySet().removeIf(entry -> {
if (entry.getKey().startsWith(requestKey + "#")) {
try {
entry.getValue().flush();
entry.getValue().close();
} catch (IOException e) {
log.error("{} writer ๋ซ๊ธฐ ์คํจ", entry.getKey(), e);
}
return true;
}
return false;
});
}
CloseRequest๋ฅผ ํ์ ๋ฃ์ ์ด์
์ฒ์์๋ closeWriter()๋ฅผ ์ง์ ํธ์ถํ๋ค. ๊ทธ๋ฐ๋ฐ ํ์ ์์ง ๋จ์์๋ FailedProduct๊ฐ ์ฒ๋ฆฌ๋๊ธฐ ์ ์ close๊ฐ ์คํ๋๋ ๋ฌธ์ ๊ฐ ์์๋ค.
์ด๋ฅผ ํด๊ฒฐํ๋ ค๊ณ closeRequestIds Set์ ๋๊ณ consumeLoop์์ FailedProduct ์ฒ๋ฆฌ ์ง์ ์ ์ฒดํฌํ๋ ๋ฐฉ์์ ์๋ํ๋ค. ํ์ง๋ง ์ด ๋ฐฉ์์ ๋ง์ง๋ง FailedProduct ์ดํ ํ๊ฐ ๋น๋ฉด take()์์ ๋ธ๋กํน → close ์ ํธ๊ฐ Set์ ์์ด๋ ์์ํ ์คํ๋์ง ์๋ ๋ฌธ์ ๊ฐ ์์๋ค.
์ต์ข ํด๊ฒฐ์ฑ ์ CloseRequest๋ฅผ FailedProduct์ ๊ฐ์ ํ์ ๋ฃ๋ ๊ฒ์ด๋ค. ๋จ์ผ consumer ์ค๋ ๋๊ฐ ํ๋ฅผ ์์๋๋ก ์๋นํ๋ฏ๋ก, CloseRequest ์ด์ ์ ๋ชจ๋ FailedProduct๊ฐ ๋ฐ๋์ ๋จผ์ ์ฒ๋ฆฌ๋๋ค.
FailServiceImpl - ์ฌ์ฒ๋ฆฌ ๋ก์ง
public void retryInsert() {
List<Path> failFiles = Files.list(failDir)
.filter(p -> p.getFileName().toString().endsWith(".txt"))
.toList();
for (Path failFile : failFiles) {
String fileName = failFile.getFileName().toString();
String userId = extractUserId(fileName);
...
UserInfo user = userDao.findUser(userId, ..);
try (BufferedReader reader = Files.newBufferedReader(failFile)) {
String line;
while ((line = reader.readLine()) != null) {
Product product = objectMapper.readValue(line, Product.class);
insertProduct(user, product); // ๋จ๊ฑด INSERT
}
}
Files.delete(failFile);
}
}
์ ๋จ๊ฑด INSERT์ธ๊ฐ
๋ฒํฌ INSERT๊ฐ ํ์์์์ผ๋ก ์คํจํ๋ค๋ ๊ฒ์ DB์ ์์ฒ ๊ฑด์ ํ ๋ฒ์ ๋ฐ์ด๋ฃ๋ ๊ณผ์ ์์ max_statement_time์ ์ด๊ณผํ๋ค๋ ์๋ฏธ๋ค. ์ฌ์ฒ๋ฆฌ๋ฅผ ๋ค์ ๋ฒํฌ๋ก ํ๋ฉด ๊ฐ์ ์ด์ ๋ก ๋ ์คํจํ ๊ฐ๋ฅ์ฑ์ด ์๋ค. ๋จ๊ฑด INSERT๋ ํ ๊ฑด์ฉ ์์ฐจ ์ฒ๋ฆฌํ๊ธฐ ๋๋ฌธ์ ์ฟผ๋ฆฌ ํ๋์ ์คํ ์๊ฐ์ด ์งง์ ํ์์์์ด ๋ฐ์ํ์ง ์๋๋ค. ์ค์ผ์ค๋ฌ๊ฐ ๋ฐฐ์น ๋ถํ๊ฐ ๋ฎ์ ์๊ฐ๋์ ์คํ๋๋ฏ๋ก ๋จ๊ฑด์ด์ด๋ ์ ์ฒด ์ฒ๋ฆฌ ์๊ฐ์ ๋ฌธ์ ๊ฐ ๋์ง ์๋๋ค.
FailScheduler
@Scheduled(cron = "0 0 16 * * *")
public void retry() {
failService.retryInsert();
}
์ ๋ฆฌ
ํต์ฌ์ ์คํจ ๊ฐ์ง → ํ → ํ์ผ → ์ฌ์ฒ๋ฆฌ ํ๋ฆ์ ๋ฐฐ์น ๋ก์ง๊ณผ ์์ ํ ๋ถ๋ฆฌํ ๊ฒ์ด๋ค. DAO ๋ ์ด์ด๋ ์คํจ๋ฅผ ํ์ ๋ฃ๊ธฐ๋ง ํ๊ณ , ํ์ผ ์ฐ๊ธฐ์ ์ฌ์ฒ๋ฆฌ๋ ๊ฐ์ ๋ ๋ฆฝ๋ ์ปดํฌ๋ํธ๊ฐ ๋ด๋นํ๋ค. ๋๋ถ์ ์ฌ์ฒ๋ฆฌ ๋ก์ง์ด ๋ฐฐ์น ์ฑ๋ฅ์ ์ํฅ์ ์ฃผ์ง ์๊ณ , ์ปดํฌ๋ํธ๋ณ๋ก ๋ ๋ฆฝ์ ์ธ ํ ์คํธ์ ๊ต์ฒด๊ฐ ๊ฐ๋ฅํ๋ค.