๐Ÿง ATMS - ELK Stack ๊ตฌ์ถ•(Kafka ๊ธฐ๋ฐ˜ ์ €๋„ ์ŠคํŠธ๋ฆฌ๋ฐ)



์ง€๋‚œ ๊ธ€ ๋ง๋ฏธ์— ๋‹ค์Œ์—” ๋ชจ๋‹ˆํ„ฐ๋ง(Prometheus + Grafana) ์ด์•ผ๊ธฐ๋ฅผ ํ•˜๊ฒ ๋‹ค๊ณ  ์˜ˆ๊ณ ํ–ˆ์—ˆ๋Š”๋ฐ, ๊ทธ ์ „์— ์ตœ๊ทผ ์ž‘์—…ํ•œ ์ „์ž์ €๋„ ์‹ค์‹œ๊ฐ„ ๊ฒ€์ƒ‰ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ•์ด ๋จผ์ € ๋งˆ๋ฌด๋ฆฌ๋ผ์„œ ์ด ๊ธ€๋ถ€ํ„ฐ ์ •๋ฆฌํ•œ๋‹ค.

๋ฌธ์ œ โ€” DB์—” ์Œ“์ด๋Š”๋ฐ, ๊ฒ€์ƒ‰ํ•  ๋ฐฉ๋ฒ•์ด SQL๋ฟ์ด๋‹ค


ATM ๊ธฐ๊ธฐ(5,500๋Œ€)๊ฐ€ ๋ณด๋‚ด๋Š” ์ „์ž์ €๋„(๊ฑฐ๋ž˜/์ƒํƒœ/์žฅ์• /๋งˆ๊ฐ/์‹œ์žฌ ๋“ฑ)์€ ์›๋ž˜๋„ Oracle DB(T_JOURNAL, T_JOURNAL_DATA)์— ์ž˜ ์Œ“์ด๊ณ  ์žˆ์—ˆ๋‹ค. ๋ฌธ์ œ๋Š” ์šด์˜ ํ˜„ํ™ฉ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋“ค์—ฌ๋‹ค๋ณผ ๋ฐฉ๋ฒ•์ด SQL ์กฐํšŒ๋ฟ์ด์—ˆ๋‹ค๋Š” ๊ฒƒ โ€” ํŠน์ • ๊ธฐ๊ธฐ๊ฐ€ ์–ธ์ œ๋ถ€ํ„ฐ ์ˆ˜์‹ ์ด ๋Š๊ฒผ๋Š”์ง€, ์žฅ์•  ์ €๋„์ด ์–ด๋А ๊ธฐ๊ธฐ์— ๋ชฐ๋ฆฌ๋Š”์ง€๋ฅผ ๋ณด๋ ค๋ฉด ๋งค๋ฒˆ ์ฟผ๋ฆฌ๋ฅผ ์งœ์•ผ ํ–ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ €๋„์ด ๋“ค์–ด์˜ค๋Š” ์ฆ‰์‹œ ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅํ•œ ํ˜•ํƒœ๋กœ ์ƒ‰์ธํ•˜๊ณ , ์šด์˜์ž๊ฐ€ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๋Œ€์‹œ๋ณด๋“œ๊นŒ์ง€ ๋ถ™์ด๊ธฐ๋กœ ํ–ˆ๋‹ค.



์„ค๊ณ„ 1 โ€” ์™œ Logstash๊ฐ€ ์•„๋‹ˆ๋ผ Kafka์ธ๊ฐ€


์„ค๊ณ„ ๋‹จ๊ณ„๋ถ€ํ„ฐ Claude Code์™€ ๋…ผ์˜ํ–ˆ๋‹ค

"๊ฒ€์ƒ‰ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค์ž"๊นŒ์ง€๋Š” ์‰ฌ์› ์ง€๋งŒ ๊ทธ ๋‹ค์Œ์ด ๊ฐˆ๋ฆผ๊ธธ์ด์—ˆ๋‹ค. ๋กœ๊ทธ ๊ธฐ๋ฐ˜(Logstash๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ๊ทธ๋ฅผ tail)์œผ๋กœ ๊ฐˆ์ง€, ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜(์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ง์ ‘ ๋ฐœํ–‰)์œผ๋กœ ๊ฐˆ์ง€ โ€” ๊ฐ ๋ฐฉ์‹์˜ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„๋ฅผ Claude Code์™€ ํ•˜๋‚˜์”ฉ ์งš์–ด๋ณด๊ณ , ์ด ํ”„๋กœ์ ํŠธ์˜ ์ œ์•ฝ ์กฐ๊ฑด์— ๋งž์ถฐ ๊ณจ๋ž๋‹ค.

์ œ์•ฝ ์กฐ๊ฑด์€ ์„ธ ๊ฐ€์ง€์˜€๋‹ค.

  1. ์›๋ณธ์€ ๋ฐ˜๋“œ์‹œ DB์—ฌ์•ผ ํ•œ๋‹ค โ€” ๊ฒ€์ƒ‰ ์ธ๋ฑ์Šค๋Š” ์–ด๋””๊นŒ์ง€๋‚˜ ์‚ฌ๋ณธ์ด์–ด์•ผ ํ•œ๋‹ค.
  2. ๊ธฐ๊ธฐ๋ณ„ ์ˆœ์„œ ๋ณด์žฅ์ด ํ•„์š”ํ•˜๋‹ค โ€” ๊ฐ™์€ ๊ธฐ๊ธฐ์˜ ์ €๋„์ด ๋’ค์„ž์—ฌ ์ƒ‰์ธ๋˜๋ฉด "์ตœ๊ทผ ์ˆ˜์‹  ์‹œ๊ฐ" ๊ฐ™์€ ์ง€ํ‘œ๊ฐ€ ๋ฌด์˜๋ฏธํ•ด์ง„๋‹ค.
  3. ์ €๋„ ์ˆ˜์‹  ๊ฒฝ๋กœ(24/7 ๋ฏธ์…˜ํฌ๋ฆฌํ‹ฐ์ปฌ)์— ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์ด ์˜ํ–ฅ์„ ์ฃผ๋ฉด ์•ˆ ๋œ๋‹ค.

๋กœ๊ทธ ๊ธฐ๋ฐ˜ vs ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜
๊ตฌ๋ถ„ ๋กœ๊ทธ ๊ธฐ๋ฐ˜ (Logstash๊ฐ€ ๋กœ๊ทธ tail) ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ (์ด๋ฒˆ ์„ ํƒ)
์›๋ณธ ์œ„์น˜ ๋กœ๊ทธ ํŒŒ์ผ(ํšŒ์ „ยท์œ ์‹ค ์œ„ํ—˜) DB(ํ•ญ์ƒ ์›๋ณธ)
๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ํ…์ŠคํŠธ ํŒŒ์‹ฑ ํ•„์š” ์ด๋ฏธ ๊ตฌ์กฐํ™”๋œ JSON
๊ธฐ๊ธฐ๋ณ„ ์ˆœ์„œ ๋ณด์žฅ ์‚ฌ์‹ค์ƒ ์–ด๋ ค์›€ Kafka ํŒŒํ‹ฐ์…˜ ํ‚ค๋กœ ๋ณด์žฅ
์ˆ˜์‹  ๊ฒฝ๋กœ ์˜ํ–ฅ ๊ฑฐ์˜ ์—†์Œ(์™„์ „ ๋น„๋™๊ธฐ tail) ์žˆ์Œ โ€” ๋ฐœํ–‰ ์ฝ”๋“œ๊ฐ€ ์ˆ˜์‹  ๊ฒฝ๋กœ์— ์ถ”๊ฐ€๋จ

๋กœ๊ทธ ๊ธฐ๋ฐ˜์ด "์ˆ˜์‹  ๊ฒฝ๋กœ ๋ฌด์˜ํ–ฅ" ํ•ญ๋ชฉ์—์„œ๋Š” ๋” ์œ ๋ฆฌํ•˜๋‹ค. ๊ทธ๋Ÿฐ๋ฐ๋„ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜์„ ํƒํ•œ ์ด์œ ๋Š”, ์ €๋„์ด ์ด๋ฏธ ๊ตฌ์กฐํ™”๋œ ๋น„์ฆˆ๋‹ˆ์Šค ์ด๋ฒคํŠธ์ด์ง€ ํ…์ŠคํŠธ ๋กœ๊ทธ๊ฐ€ ์•„๋‹ˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค โ€” ๋กœ๊ทธ๋กœ ํ•œ๋ฒˆ ํ’€์–ด์ผ๋‹ค๊ฐ€ Logstash๋กœ ๋‹ค์‹œ ํŒŒ์‹ฑํ•˜๋Š” ๊ฑด ์šฐํšŒ์˜€๋‹ค.

๋Œ€์‹  ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜์˜ ์œ ์ผํ•œ ์•ฝ์ ("์ˆ˜์‹  ๊ฒฝ๋กœ ์˜ํ–ฅ")์€ ๋‹ค์Œ ์„ค๊ณ„๋กœ ์ •๋ฉด ๋Œ€์‘ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.



์„ค๊ณ„ 2 โ€” ์ˆ˜์‹  ๊ฒฝ๋กœ ๋ฌด์˜ํ–ฅ์„ ์ฝ”๋“œ๋กœ ๊ฐ•์ œํ•˜๊ธฐ


์›์น™์€ "๋ถ€๊ฐ€ ๊ธฐ๋Šฅ์ด ํ•ต์‹ฌ ๊ธฐ๋Šฅ์„ ์ ˆ๋Œ€ ๊ฑด๋“œ๋ฆฌ๋ฉด ์•ˆ ๋œ๋‹ค"๋Š” ๊ฒƒ. ์ด๊ฑธ ๋ฌธ์„œ๋กœ๋งŒ ๋‚จ๊ธฐ์ง€ ์•Š๊ณ  ๋„ค ๊ฐ€์ง€ ์žฅ์น˜๋กœ ์ฝ”๋“œ์— ๋ฐ•์•„ ๋„ฃ์—ˆ๋‹ค.

โ‘  ์™„์ „ opt-in โ€” ํ”Œ๋ž˜๊ทธ ๋‘ ๊ฐœ, ๊ธฐ๋ณธ๊ฐ’ ์ „๋ถ€ false

@ConfigurationProperties(prefix = "atms.stream")
public class JournalStreamProperties {
    private final Kafka kafka = new Kafka();                 // kafka.enabled = false (๊ธฐ๋ณธ)
    private final Elasticsearch elasticsearch = new Elasticsearch(); // enabled = false (๊ธฐ๋ณธ)
}

โ‘ก ๊ด€๋ จ ์ปดํฌ๋„ŒํŠธ ์ „๋ถ€ @ConditionalOnProperty๋กœ ๊ฒŒ์ดํŒ…

ํ† ํ”ฝ ์ƒ์„ฑ(JournalStreamTopicConfig), ์ธ๋ฑ์Šค ํ…œํ”Œ๋ฆฟ ๋“ฑ๋ก(JournalEsIndexInitializer), ์ปจ์Šˆ๋จธ(JournalEsIndexConsumer) โ€” ์…‹ ๋‹ค ํ”Œ๋ž˜๊ทธ๊ฐ€ ๊บผ์ ธ ์žˆ์œผ๋ฉด ์Šคํ”„๋ง ์ปจํ…์ŠคํŠธ์— ์•„์˜ˆ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค.

โ‘ข DB ์ปค๋ฐ‹ ์ดํ›„์—๋งŒ ๋ฐœํ–‰, ๊ทธ๋ฆฌ๊ณ  ์‹คํŒจํ•ด๋„ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€์ง€ ์•Š๋Š”๋‹ค

// JournalDispatchService
txRequired.executeWithoutResult(tx -> doDispatchInTx(key, msg));
// DB ์ ์žฌ ์„ฑ๊ณต ํ›„์—๋งŒ ์ŠคํŠธ๋ฆผ ๋ฐœํ–‰(ES ์ƒ‰์ธ์šฉ) โ€” ๋ฐœํ–‰ ์‹คํŒจ๋Š” ์ˆ˜์‹  ์‘๋‹ต์— ์˜ํ–ฅ ์—†์Œ
eventPublisher.publishStored(key, msg);
// JournalEventPublisher.publishStored()
if (!props.getKafka().isEnabled()) return;           // ๊บผ์ ธ ์žˆ์œผ๋ฉด ์ฆ‰์‹œ no-op
...
template.send(topic, kafkaKey, payload)
        .whenComplete((result, ex) -> {
            if (ex != null) log.warn("journal event publish failed ...: {}", ex.toString());
            // ์˜ˆ์™ธ๋ฅผ ์—ฌ๊ธฐ์„œ ์ ˆ๋Œ€ ๋‹ค์‹œ ๋˜์ง€์ง€ ์•Š๋Š”๋‹ค
        });

โ‘ฃ ES๊ฐ€ ์ฃฝ์–ด์žˆ์–ด๋„ ์•ฑ ๊ธฐ๋™์„ ๋ง‰์ง€ ์•Š๋Š”๋‹ค

JournalEsIndexInitializer๋Š” ๊ธฐ๋™ ์‹œ ์ธ๋ฑ์Šค ํ…œํ”Œ๋ฆฟ์„ ๋“ฑ๋กํ•˜๋Š”๋ฐ, ES๊ฐ€ ์•„์ง ์•ˆ ๋–  ์žˆ์œผ๋ฉด ๊ฒฝ๊ณ  ๋กœ๊ทธ๋งŒ ๋‚จ๊ธฐ๊ณ  ๊ทธ๋ƒฅ ๋„˜์–ด๊ฐ„๋‹ค (์ƒ‰์ธ ์‹œ์ ์— ๋™์  ๋งคํ•‘์œผ๋กœ ๋Œ€์ฒด ๋™์ž‘).

"๊ทธ๋ƒฅ try-catch๋กœ ๊ฐ์‹ธ๋ฉด ๋˜๋Š” ๊ฑฐ ์•„๋‹Œ๊ฐ€?"

Claude Code์™€ ์ด ๋ถ€๋ถ„์„ ๊ฒ€ํ† ํ•˜๋ฉด์„œ ๋‚˜์˜จ ์–˜๊ธฐ๋‹ค. try-catch๋กœ ๊ฐ์‹ธ๋Š” ๊ฒƒ๊ณผ, ์ปดํฌ๋„ŒํŠธ ์ž์ฒด๋ฅผ ์กฐ๊ฑด๋ถ€๋กœ ์กด์žฌํ•˜์ง€ ์•Š๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒ์€ ๋‹ค๋ฅด๋‹ค. ํ›„์ž๋Š” "์ด ๊ธฐ๋Šฅ์„ ์ผ  ์ ์ด ์—†๋Š” ํ™˜๊ฒฝ"์—์„œ๋Š” ๊ด€๋ จ ๋นˆ์ด ์•„์˜ˆ ์ƒ์„ฑ๋˜์ง€ ์•Š์œผ๋‹ˆ, ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์‹œ์ ์—๋„ "์ด ๋ฐฐํฌ์— ์ด ๊ธฐ๋Šฅ์ด ๊ด€์—ฌํ•˜๋Š”๊ฐ€"๊ฐ€ ์„ค์ • ํ•˜๋‚˜๋กœ ๋ช…ํ™•ํ•ด์ง„๋‹ค.



์„ค๊ณ„ 3 โ€” ์žฌ์ „์†ก๋ผ๋„ ์ค‘๋ณต๋˜์ง€ ์•Š๊ฒŒ(๋ฉฑ๋“ฑ์„ฑ)


ATM ์—์ด์ „ํŠธ๋Š” ์‘๋‹ต์„ ๋ชป ๋ฐ›์œผ๋ฉด ๊ฐ™์€ ์ €๋„์„ ์žฌ์ „์†กํ•œ๋‹ค. Kafka ์ปจ์Šˆ๋จธ๊ฐ€ ๊ฐ™์€ ๋ฉ”์‹œ์ง€๋ฅผ ๋‘ ๋ฒˆ ์ฒ˜๋ฆฌํ•  ๊ฐ€๋Šฅ์„ฑ๋„ ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ƒ‰์ธ ์‹œ์ ์— ๊ฒฐ์ •๋ก ์  ๋ฌธ์„œ ID๋ฅผ ๋งŒ๋“ค๊ณ , POST๊ฐ€ ์•„๋‹Œ PUT์œผ๋กœ ์ƒ‰์ธํ•˜๋„๋ก ํ–ˆ๋‹ค.

// JournalEsIndexConsumer
String docId = String.join("-",
        termGroupId, termId, jnlDate, jnlTime, jnlSeq);

Request request = new Request("PUT", "/" + indexName(jnlDate) + "/_doc/" + docId);

๊ฐ™์€ ์ €๋„์ด ๋ช‡ ๋ฒˆ์„ ๋‹ค์‹œ ๋“ค์–ด์™€๋„ ๋„์ฐฉํ•˜๋Š” ๋ฌธ์„œ ID๋Š” ํ•ญ์ƒ ๋™์ผํ•˜๋‹ˆ, ์ค‘๋ณต ์ƒ‰์ธ์ด ์•„๋‹ˆ๋ผ ๊ฐ™์€ ๋ฌธ์„œ๋ฅผ ๋ฎ์–ด์“ฐ๋Š” ๊ฒƒ์œผ๋กœ ๋๋‚œ๋‹ค. ์ธ๋ฑ์Šค ์ด๋ฆ„๋„ "์˜ค๋Š˜ ๋‚ ์งœ"๊ฐ€ ์•„๋‹ˆ๋ผ ์ €๋„ ์ž์ฒด์˜ ๊ฑฐ๋ž˜์ผ์ž(jnlDate)๋กœ ์ •ํ•ด์„œ, ๋’ค๋Šฆ๊ฒŒ ๋“ค์–ด์˜จ ์ง€๋‚œ ์ €๋„๋„ ์—‰๋šฑํ•œ ๋‚ ์งœ์˜ ์ธ๋ฑ์Šค์— ์Œ“์ด์ง€ ์•Š๋Š”๋‹ค.

ํ•„๋“œ ๋งคํ•‘์€ ์ €๋„ ์ข…๋ฅ˜(๊ธฐ๋™/๊ฐœ๊ตญ/์ƒํƒœ/์žฅ์• /๋งˆ๊ฐ/์‹œ์žฌ/๊ฑฐ๋ž˜ โ€” ์ด 7์ข…)๋งˆ๋‹ค ๊ตฌ์กฐ๊ฐ€ ๋‹ฌ๋ผ์„œ, head/body๋ฅผ Elasticsearch์˜ flattened ํƒ€์ž…์œผ๋กœ ๋งคํ•‘ํ•ด ์Šคํ‚ค๋งˆ ๊ฑฑ์ • ์—†์ด ํ†ต์งธ๋กœ ์ƒ‰์ธํ•˜๊ณ , ์ „์ฒด payload๋Š” searchText(text ํƒ€์ž…)์— ์ค‘๋ณต ์ €์žฅํ•ด Kibana์—์„œ ๊ณ„์ขŒยท์—๋Ÿฌ์ฝ”๋“œ ์ „๋ฌธ๊ฒ€์ƒ‰์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ–ˆ๋‹ค.



์ „์ฒด ๊ทธ๋ฆผ


graph TD
	A[ATM Agent 5,500๋Œ€]
	B[atms-journal ์ˆ˜์‹  API]
	C[Oracle DB ์ ์žฌ - T_JOURNAL]
	D[JournalEventPublisher - fire-and-forget]
	E[Kafka topic atms.journal.received - ํŒŒํ‹ฐ์…˜ 12]
	F[JournalEsIndexConsumer]
	G[Elasticsearch ์ผ๋ณ„ ์ธ๋ฑ์Šค]
	H[Kibana ๋Œ€์‹œ๋ณด๋“œ]

	A --> B --> C --> D --> E --> F --> G --> H
  • ํŒŒํ‹ฐ์…˜ ํ‚ค๋Š” termGroupId:termId โ€” ๊ฐ™์€ ๊ธฐ๊ธฐ์˜ ์ €๋„์€ ํ•ญ์ƒ ๊ฐ™์€ ํŒŒํ‹ฐ์…˜์œผ๋กœ ๊ฐ€์„œ ์ˆœ์„œ๊ฐ€ ๋ณด์žฅ๋œ๋‹ค.
  • Kafka ํ† ํ”ฝ ์ž์ฒด๋Š” ์›๋ณธ์ด ์•„๋‹ˆ๋ผ ์ƒ‰์ธ์šฉ ๋ฒ„ํผ๋ผ์„œ ๋ณด์กด๊ธฐ๊ฐ„์„ 3์ผ๋กœ ์งง๊ฒŒ ์žก์•˜๋‹ค. (์›๋ณธ์€ ์–ด๋””๊นŒ์ง€๋‚˜ DB)

๊ฐœ๋ฐœ ํ™˜๊ฒฝ์€ Docker Compose ํ•œ ๋ฐฉ์œผ๋กœ ๋„์šด๋‹ค.

docker compose up -d kafka elasticsearch kibana atms-journal
./infra/elastic/setup-kibana.sh    # ๋ฐ์ดํ„ฐ๋ทฐ(atms-journal-*) ๋“ฑ๋ก

Kafka๋Š” Zookeeper ์—†๋Š” KRaft ๋‹จ์ผ ๋…ธ๋“œ(3.9.1), ES/Kibana๋Š” 8.18.2. ์ „๋ถ€ ๊ฐœ๋ฐœ์šฉ ๋‹จ์ผ ๋…ธ๋“œ ๊ตฌ์„ฑ์ด๊ณ , ์šด์˜ ์ „ํ™˜ ์‹œ์—๋Š” Kafka 3๋…ธ๋“œ+SASL/TLS, ES 3๋…ธ๋“œ+๋ณด์•ˆ ํ™œ์„ฑํ™”, ๊ณ„์ขŒ๋ฒˆํ˜ธ ๋“ฑ ๊ฐœ์ธ์ •๋ณด ๋งˆ์Šคํ‚น ํ›„ ์ƒ‰์ธ, ์ˆ˜๋™ ์‚ญ์ œ ๋Œ€์‹  ILM ์ •์ฑ… ์ ์šฉ์ด ํ•„์š”ํ•˜๋‹ค โ€” ์—ฌ๊ธฐ๊นŒ์ง„ ์•„์ง TODO๋กœ ๋‚จ์•„์žˆ๋‹ค.



์‹ค์ธก โ€” ๋ชฉํ‘œ TPS๋ฅผ ์‹ค์ œ๋กœ ์ฑ„์› ๋Š”๊ฐ€


์„ค๊ณ„๋งŒ ๊ทธ๋Ÿด๋“ฏํ•œ ๊ฒƒ๊ณผ ์‹ค์ œ๋กœ ๋ถ€ํ•˜๋ฅผ ๊ฒฌ๋””๋Š” ๊ฒƒ์€ ๋‹ค๋ฅธ ๋ฌธ์ œ๋ผ, ์—์ด์ „ํŠธ ํ”„๋กœํ† ์ฝœ์„ ๊ทธ๋Œ€๋กœ ํ‰๋‚ด ๋‚ธ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ๋กœ ํ”ผํฌ ์‹œ๋‚˜๋ฆฌ์˜ค(550๋Œ€ ร— ๊ธฐ๊ธฐ๋‹น 10๊ฑด = 5,500๊ฑด, ๋ณ‘๋ ฌ 32)๋ฅผ ๋‘ ๋ฒˆ ๋Œ๋ ธ๋‹ค.

๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ
์‹œ์  ํ™˜๊ฒฝ ๊ฒฐ๊ณผ
1์ฐจ ๋กœ์ปฌ, Kafka/ES off 5,500/5,500 ์„ฑ๊ณต, 42์ดˆ, TPS 131๊ฑด/์ดˆ (๋ชฉํ‘œ ํ•˜ํ•œ 100/s ์ƒํšŒ)
2์ฐจ ๋ฐฐํฌ ์„œ๋ฒ„, Kafka/ES on 5,500/5,500 ์„ฑ๊ณต, 16์ดˆ, TPS 343๊ฑด/์ดˆ (๋ชฉํ‘œ ์ƒํ•œ 300/s๋„ ์ƒํšŒ)

2์ฐจ ํ…Œ์ŠคํŠธ์—์„œ Kafkaโ†’ES ์ƒ‰์ธ๊นŒ์ง€ 5,501/5,501 ๋ฌด์†์‹ค, dispatch ์‹คํŒจยทpublish ์‹คํŒจ ๋ชจ๋‘ 0๊ฑด.

Kafka/ES๋ฅผ ์ผฐ๋Š”๋ฐ ์˜คํžˆ๋ ค ๋” ๋นจ๋ผ์ง„ ๊ฑด, ๋‘ ์ฐจ๋ก€ ํ…Œ์ŠคํŠธ ์‚ฌ์ด์— ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ๊ฒฝ๋กœ ์ž์ฒด๋„ ํ•จ๊ป˜ ์†๋ดค๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค โ€” ์ŠคํŠธ๋ฆฌ๋ฐ ํŒŒ์ดํ”„๋ผ์ธ์ด "์–นํ˜€์„œ" ๋А๋ ค์ง€์ง€ ์•Š์•˜๋‹ค๋Š” ๊ฒŒ ์ด๋ฒˆ ๊ฒ€์ฆ์˜ ํ•ต์‹ฌ์ด์—ˆ๋‹ค.


๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ๊ฐ€ ์ฐพ์•„๋‚ธ ์ง„์งœ ๋ฒ„๊ทธ 2๊ฑด

์„ฑ๋Šฅ ์ˆ˜์น˜๋ณด๋‹ค ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ์ž์ฒด๊ฐ€ ์žก์•„๋‚ธ ๊ฒฐํ•จ์ด ๋” ์œ ์šฉํ–ˆ๋‹ค.

โ‘  CASSETTE ์ƒํƒœ MERGE๊ฐ€ ๋‘ ๋ฒˆ์งธ ์ €๋„๋ถ€ํ„ฐ ์‹คํŒจ

ORA-00001: unique constraint violated

๊ฐ™์€ ๊ธฐ๊ธฐ์˜ ์‹œ์žฌ/๊ฑฐ๋ž˜ ์ €๋„์ด ๋‘ ๋ฒˆ์งธ๋กœ ๋“ค์–ด์˜ค๋Š” ์ˆœ๊ฐ„๋ถ€ํ„ฐ ์‹คํŒจํ–ˆ๋‹ค. ์›์ธ์€ MERGE ๋ฌธ์˜ ๋งค์นญ ์กฐ๊ฑด โ€” DB ์ปฌ๋Ÿผ์€ CHAR(5)(๊ณต๋ฐฑ ํŒจ๋”ฉ)์ธ๋ฐ ๋ฐ”์ธ๋“œ ๊ฐ’์€ VARCHAR๋ผ ํ•ญ์ƒ NOT MATCHED๋กœ ํŒ์ •๋˜์–ด, ๋งค๋ฒˆ ์ƒˆ๋กœ INSERT๋ฅผ ์‹œ๋„ํ•˜๋‹ค PK ์ถฉ๋Œ์ด ๋‚œ ๊ฒƒ.

-- ์ˆ˜์ • ์ „: CHAR(5) vs VARCHAR ๋น„๊ต๋ผ ๋งค๋ฒˆ ๋ถˆ์ผ์น˜
ON (T.CASSETTE_NO = :cassetteNo)

-- ์ˆ˜์ • ํ›„: ๋ช…์‹œ์ ์œผ๋กœ CHAR(5)๋กœ ์บ์ŠคํŒ…ํ•ด ๋น„๊ต
ON (T.CASSETTE_NO = CAST(:cassetteNo AS CHAR(5)))

โ‘ก ๊ฑฐ๋ž˜์ €๋„ ์€ํ–‰์ฝ”๋“œ ์ž๋ฆฌ์ˆ˜ ๋ถˆ์ผ์น˜๋กœ ์ธํ•œ ์˜ค๋ฒ„ํ”Œ๋กœ

์ €๋„ ์ŠคํŽ™์ƒ ์€ํ–‰์ฝ”๋“œ๋Š” 4์ž๋ฆฌ์ธ๋ฐ, ์ •์ž‘ DB ์ปฌ๋Ÿผ์€ 3์ž๋ฆฌ๋กœ ์ •์˜๋ผ ์žˆ์–ด ๊ฐ’์ด ์ž˜๋ ค ๋“ค์–ด๊ฐ€๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค. ์ŠคํŽ™๊ณผ ์Šคํ‚ค๋งˆ ์ค‘ ์–ด๋А ์ชฝ์ด ๋งž๋Š”์ง€๋Š” ์„ค๊ณ„ ๊ฒ€์ฆ์ด ๋” ํ•„์š”ํ•œ ๋ถ€๋ถ„์ด๋ผ, ์šฐ์„  ๋’ค 3์ž๋ฆฌ๋งŒ ์ทจํ•˜๋Š” ์ •๊ทœํ™” ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•ด ์˜ค๋ฒ„ํ”Œ๋กœ๋ถ€ํ„ฐ ๋ง‰์•˜๋‹ค.

private static String bank3(String bkCd) {
    // ์ŠคํŽ™(4์ž๋ฆฌ) โ†’ ์ปฌ๋Ÿผ(3์ž๋ฆฌ) ์ •๊ทœํ™”. ์˜ˆ: "1234" โ†’ "234"
    return (bkCd != null && bkCd.length() == 4) ? bkCd.substring(1) : bkCd;
}

๋‘˜ ๋‹ค ํ‰์†Œ ํŠธ๋ž˜ํ”ฝ์—์„œ๋Š” ์•ˆ ๋“œ๋Ÿฌ๋‚˜๋‹ค๊ฐ€, ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ๋กœ "๊ฐ™์€ ๊ธฐ๊ธฐ๊ฐ€ ๋ฐ˜๋ณตํ•ด์„œ ์ €๋„์„ ๋ณด๋‚ด๋Š”" ์ƒํ™ฉ์„ ์žฌํ˜„ํ•˜๊ณ  ๋‚˜์„œ์•ผ ๋‚˜์˜จ ๊ฒฐํ•จ์ด์—ˆ๋‹ค.



๊ฒฐ๋ก 

  • ๊ฒ€์ƒ‰์„ ์œ„ํ•ด ๋กœ๊ทธ๋กœ ์šฐํšŒํ•  ํ•„์š” ์—†์Œ โ€” ์ €๋„์€ ์ด๋ฏธ ๊ตฌ์กฐํ™”๋œ ์ด๋ฒคํŠธ์˜€๋‹ค.
  • ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ(๊ฒ€์ƒ‰/๋Œ€์‹œ๋ณด๋“œ)์ด ํ•ต์‹ฌ ๊ฒฝ๋กœ(์ €๋„ ์ˆ˜์‹ )๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฑด ์›์น™ ์„ ์–ธ์ด ์•„๋‹ˆ๋ผ ์˜ตํŠธ์ธ ํ”Œ๋ž˜๊ทธ + ์กฐ๊ฑด๋ถ€ ๋นˆ + ์˜ˆ์™ธ ๋ฏธ์ „ํŒŒ๋ผ๋Š” ๊ตฌ์ฒด์  ์ฝ”๋“œ ํŒจํ„ด์œผ๋กœ ๊ฐ•์ œํ•ด์•ผ ํ•œ๋‹ค.
  • ๋ฉฑ๋“ฑ์„ฑ์€ ์žฌ์ „์†ก์„ ๋ง‰๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ์žฌ์ „์†ก์ด ์ผ์–ด๋‚˜๋„ ๊ฒฐ๊ณผ๊ฐ€ ๊ฐ™๋„๋ก ๋งŒ๋“œ๋Š” ๊ฒƒ.
  • ๋ชฉํ‘œ TPS๋Š” ์ˆซ์ž๋กœ๋งŒ ์ •ํ•˜๊ณ  ๋๋‚ด์ง€ ์•Š๊ณ , ์‹ค์ œ ๋ถ€ํ•˜๋กœ ๊ฒ€์ฆํ–ˆ์„ ๋•Œ ์ง„์งœ ๊ฒฐํ•จ(๋‘ ๊ฑด)์ด ๋‚˜์™”๋‹ค.

Kibana ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ์ฝ”๋“œ(PowerShell ์Šคํฌ๋ฆฝํŠธ)๋กœ ์กฐ๋ฆฝํ•ด ๋ฐฐํฌํ•˜๋Š” ์ด์•ผ๊ธฐ๋Š” ๋ถ„๋Ÿ‰์ƒ ์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ๋‹ค๋ฃจ์ง€ ๋ชปํ–ˆ๋‹ค โ€” ๋‹ค์Œ ๊ธฐํšŒ์— ๋”ฐ๋กœ ์ •๋ฆฌํ•ด๋ณธ๋‹ค.