Post

๐ŸŽฑ ์ƒ์•  ์ฒซ ํˆฌ ์„œ๋ฒ„ ๋„์ „๊ธฐ, ์„œ๋ฒ„ ๋‘ ๊ฐœ๋ฅผ ํด๋ฆฐ~ํ•˜๊ฒŒ ์šด์˜ํ•ด๋ณด์•„์šฉ - [1ํƒ„] ์•„ํ‚คํ…์ฒ˜ ๊ตฌ์กฐ๋ฅผ ์™œ ์ด๋ ‡๊ฒŒ ์žก์•˜์–ด?

๐ŸŽฑ ์ƒ์•  ์ฒซ ํˆฌ ์„œ๋ฒ„ ๋„์ „๊ธฐ, ์„œ๋ฒ„ ๋‘ ๊ฐœ๋ฅผ ํด๋ฆฐ~ํ•˜๊ฒŒ ์šด์˜ํ•ด๋ณด์•„์šฉ - [1ํƒ„] ์•„ํ‚คํ…์ฒ˜ ๊ตฌ์กฐ๋ฅผ ์™œ ์ด๋ ‡๊ฒŒ ์žก์•˜์–ด?

๐Ÿ–ค Intro

์ด๋ฒˆ์— ์ฒ˜์Œ์œผ๋กœ ๋‘ ๊ฐœ์˜ ์„œ๋ฒ„๋ฅผ ์—ฐ๋™ํ•ด์„œ ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

์ด์œ ๋Š” ์ฑ—๋ด‡์„ ๊ตฌํ˜„ํ• ๋•Œ LLM์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ํŒŒ์ด์ฌ ์„œ๋ฒ„๋ฅผ ๋ถ™์—ฌ์•ผ ํ–ˆ๊ธฐ ๋•Œ๋ฌด๋„น

์‚ฌ์‹ค SPRING AI๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ž๋ฐ”๋กœ๋„ AI ๊ฐœ๋ฐœ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ ๋Š” ํ•˜์ง€๋งŒโ€ฆ..

์†”์งํžˆ ์•„์ง AI ํŒŒํŠธ์—์„œ๋Š” ํŒŒ์ด์ฌ์„ ๋”ฐ๋ผ๊ฐ€๊ธฐ์—” ๋ฉ€์—ˆ๋‹ค๋Š”๊ฒŒ ๋‚ด ์ƒ๊ฐ์ด๊ณ , ์ฐจํ›„ ๋ชจ๋ธ ํ•™์Šต ๋“ฑ์„ ๊ณ ๋ คํ•œ ํ™•์žฅ ๋ฐฉํ–ฅ์„ ๊ณ ๋ คํ•˜๋ฉด, ์–ด๋ ต๋”๋ผ๋„ ์ด ๋ฐฉํ–ฅ์œผ๋กœ ๊ฐ€๋Š”๊ฒŒ ๋งž์ง€ ์•Š์„๊นŒโ€ฆ.์‹ถ์–ด์„œ ์ด๋Ÿฐ ์„ ํƒ์„ ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ด ์ฐธ์— ์ด ๊ตฌ์กฐ์— ๋Œ€ํ•œ ํ‹€์„ ์ž˜ ์žก์•„๋‘๋ฉด, ๋‚ด๊ฐ€ ๋„ˆ๋ฌด๋„ˆ๋ฌด ํ•ด๋ณด๊ณ  ์‹ถ์—ˆ๋˜ MSA ์„ค๊ณ„ํ•  ๋•Œ ์ •๋ง ํฐ ๋„์›€์ด ๋  ๊ฒƒ ๊ฐ™์•„์„œ!!! ๋„์ „ํ•ด๋ณด๊ธฐ๋กœ ํ•˜์˜€๋‹ค:)

๐Ÿฉถ Start

FAST API ๋‹จ์ผ ? VS BOOT & FAST API?

์ฒ˜์Œ ์„œ๋ฒ„ ๊ตฌ์กฐ๋ฅผ ์žก์„๋•Œ, ๋‘ ๊ฐ€์ง€ ์˜๊ฒฌ ์‚ฌ์ด์—์„œ ๋‚˜์™€ ๋ฐ์ดํ„ฐํŒ€ ๋™์ƒ์ด ๊ณ ๋ฏผ์„ ๊ต‰์žฅํžˆ ๋งŽ์ด ํ–ˆ๋‹ค.

๋‚ด ์ž…์žฅ์—์„œ๋Š” ์‚ฌ์‹ค ํ›„์ž๋กœ ์ง„ํ–‰ํ•˜๊ณ  ์‹ถ์—ˆ๋˜ ๊ฐ€์žฅ ํฐ ์ด์œ ๊ฐ€, ๋‚ด๊ฐ€ SPRING ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž๋กœ ์ทจ์—…์„ ์›ํ•˜๊ธฐ๋„ ํ•˜๊ณ , ํŒŒ์ด์ฌ์œผ๋กœ ์„œ๋ฒ„ ๊ฐœ๋ฐœ์„ ํ•œ ์ ๋„ ์—†๊ณ  ์• ์ดˆ์— ์„ ํ˜ธํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด์—ˆ๋Š”๋ฐโ€ฆ

๊ทธ์™€์ค‘์— ๋ฌธ๋“ ๊ถ๊ธˆํ•ด์กŒ๋‹ค.

๐Ÿ˜ฒ ๊ทธ๋Ÿผ, ๊ณผ์—ฐ FAST API ๋‹จ์ผ ์„œ๋ฒ„๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์— ๋น„ํ•ด BOOT & FAST API ํˆฌ ์„œ๋ฒ„๋กœ ์ฑ—๋ด‡์„ ๊ตฌํ˜„ํ•˜๋Š”๊ฑด ์–ด๋–ค ์žฅ์ ์ด ์žˆ์„๊นŒ? ์ทจ์—…์ด๋ผ๋Š” ์ด์œ ๋ฅผ ์ œ์™ธํ•˜๊ณ , ์™œ ์šฐ๋ฆฌ๊ฐ€ ์ด ์„ ํƒ์„ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์€๊ฑธ๊นŒ?

1. ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ

๋‚˜์˜ ๊ฒฝ์šฐ๋Š” ์• ์ดˆ์— ํˆฌ ์„œ๋ฒ„๋ฅผ ์ง„ํ–‰ํ•˜๊ธฐ๋กœ ํ•˜๋ฉด์„œ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ทœ์น™์„ ์„ธ์› ์—ˆ๋‹ค.

  • SpringBoot๋งŒ DB์— ์ ‘๊ทผํ•˜๊ณ , Front์™€ ํ†ต์‹ ํ•œ๋‹ค.
  • Fast api๋Š” llm๊ณผ ์—ฐ๋™, ์™ธ๋ถ€ api์™€ ์—ฐ๋™ํ•˜๋Š” ์—ญํ• ์„ ๋งก๊ณ , db ์ ‘๊ทผ๊ณผ front ํ†ต์‹  ๋“ฑ์˜ ์—…๋ฌด๋ฅผ ์ผ์ ˆ ํ•˜์ง€ ์•Š๋Š”๋‹ค.

์ด๋ ‡๊ฒŒ ํ•ด์„œ ์–‘์ชฝ์ด ์ „๋ถ€ DB์— ์ ‘๊ทผํ•ด์„œ ํŠธ๋žœ์žญ์…˜์ด ๊ผฌ์ด๋Š” ๊ฒฝ์šฐ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ณ (์ด๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์„ ๋ณด์žฅํ•œ๋‹ค.), Front ๋‹จ์—์„œ๋„ ์–ด๋–ค ์„œ๋ฒ„์— request๋ฅผ ๋‚ ๋ ค์•ผ ํ•˜๋Š”์ง€๋ฅผ ๋ช…ํ™•ํžˆ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑํ–ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์‚ฌ์‹ค์ƒ ์—ญํ• ๋ถ„๋ฆฌ๋ฅผ ๋„๋ฉ”์ธ ๊ธฐ์ค€์œผ๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฆฌํ•œ ๊ฒƒ์€,,,์ฐจํ›„ MSA๋ผ๋Š” ๋ชฉํ‘œ๋ฅผ ์œ„ํ•ด์„œ!!!์ด๊ธฐ๋„ ํ•˜๋‹ค.

2. ๊ธฐ์ˆ  ์Šคํƒ ์ตœ์ ํ™”

์•Œ๋‹ค์‹œํ”ผ. Spring boot๋Š” ์›น ์„œ๋ฒ„ ๊ฐœ๋ฐœ์— ์ตœ์ ํ™” ๋˜์–ด ์žˆ๋‹ค. ํŠนํžˆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ’๋ถ€ํ•˜๊ณ , ๋ญฃ๋ณด๋‹ค ๋‚ด๊ฐ€ JPA๋ž‘ Querydsl, ๊ทธ๋ฆฌ๊ณ  ์ž๋ฐ”์˜ ๊ฐ์ฒด ์ง€ํ–ฅ์ ์ธ ๊ตฌ์กฐ ์ด ์‚ผํ•ฉ์„ ์ •๋ง ์‚ฌ๋ž‘!ํ•˜๊ธฐ ๋•Œ๋ฌธ์—โ€ฆ์„œ๋ฒ„ ๊ฐœ๋ฐœ์€ ์ž์‹ ์ด ์žˆ์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  fast api๋Š” python ์ƒํƒœ๊ณ„ ํŠน์„ฑ์ƒ ai์— ์ •๋ง ํŠนํ™”๋˜์–ด ์žˆ๋‹ค. llm ์—ฐ๋™ ๋ฟ ์•„๋‹ˆ๋ผ ์ฐจํ›„ ์ •๋ฐ€์„ฑ์„ ์œ„ํ•ด model ํ•™์Šต์„ ๋ถ™์ผ๋•Œ๋„ scikit-learn, Tensorflow ๋“ฑ์„ ์‚ฌ์šฉํ•˜๊ธฐ์—๋„ ๋งค์šฐ ์ข‹๋‹ค. (์ฆ‰, ๋ฏธ๋ž˜์  ๊ด€์ ์—์„œ ํ™•์žฅ์„ฑ์ด ์ง„์งœ ์ข‹๋‹ค.)

spring ai๋ผ๋Š”๊ฒŒ ์ตœ๊ทผ์— ์ƒ๊ธด๊ฑด ์•Œ๊ณ  ์žˆ์ง€๋งŒ, ํ•ด๋‹น ์˜์—ญ์— ์ƒํƒœ๊ณ„๊ฐ€ ํ’๋ถ€ํ•œ ์–ธ์–ด๋ฅผ ํ™œ์šฉํ•˜๋Š”๊ฒŒ ๋งž๋Š” ๋ฐฉํ–ฅ์ด๋ผ๊ณ  ์ƒ๊ฐํ•ด์„œ ์ด๋Ÿฌํ•œ ๊ฒฐ์ •์„ ๋‚ด๋ ธ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ์•„ํ‚คํ…์ฒ˜ ๊ตฌ์กฐ๋Š” ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑํ•ด์•ผํ•ด?

์ž ์—ฌ๊ธฐ์„œ ๋ฌธ์ œ๋Š” ์ด ๋ถ€๋ถ„์ด๋‹ค.

๋‚˜์˜ ๊ฒฝ์šฐ๋Š”, ์ตœ๋Œ€ํ•œ SRP ์›์น™์„ ์ง€ํ‚ค๊ธฐ ์œ„ํ•ด ์„œ๋ฒ„๋‹น ์—ญํ•  ๋ถ„๋ฆฌ๋ฅผ ๋ช…ํ™•ํžˆ ํ•ด๋‘˜ ์˜ˆ์ •์ด์—ˆ๋‹ค.

๊ทธ๋ ‡๊ธฐ์— ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์งœ๋‘์ง€ ์•Š์œผ๋ฉด, ๊ฐœ๋ฐœํ• ๋•Œ ๋งค์šฐ ๋งŽ์€ ๋ถ€๋ถ„์ด ๊ผฌ์ผ ๊ฒƒ ๊ฐ™์•˜๋‹ค.

๊ทธ๋ž˜์„œ ๋ช‡ ๋ฒˆ(์‚ฌ์‹ค ํ•œ ๋‹ค์„ฏ ๋ฒˆ ์ •๋„..) ๊ฐˆ์•„์—Ž์€ ๋์—, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฑ—๋ด‡ ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊ตฌ์„ฑํ•˜์˜€๋‹ค.

image.png

ํ˜„์žฌ๋Š” ๋กœ์ปฌ ๊ฐœ๋ฐœ์ด๋ผ REDIS๋ฅผ DOCKER์— ๋„์› ๋Š”๋ฐ, EC2์— ๋ฐฐํฌํ•œ ํ›„์—๋Š” ์›๋ž˜ ElasticCache๋กœ ๋ณ€๊ฒฝํ•  ์˜ˆ์ •์ด์—ˆ๋‹ค.

๊ทผ๋ฐ ์‚ฌ์‹คโ€ฆ.์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ๋ฅผ ๊ฐ•์‚ฌ๋‹˜ ์„œ๋ฒ„์— ๋„์›Œ์•ผ ํ•  ์ˆ˜๋„ ์žˆ์–ด์„œ, ๋„์ปค ์ปจํ…Œ์ด๋„ˆ๋ง ํ•œ ์ง€๊ธˆ์˜ ๊ตฌ์กฐ๋ฅผ ์œ ์ง€ํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ๊ฒ ๋‹ค. ์ด๊ฑด CI/CD ํ•  ๋•Œ ๊ฐ€๋ด์•ผ ์•Œ๋“ฏ ์‹ถ๋‹ค.

๊ตฌ์กฐ์˜ ํฌ์ธํŠธ๋ฅผ ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•˜์ž๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. ์ตœ๋Œ€ํ•œ 3-tier๋กœ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌ๋˜๋„๋ก ๊ตฌ์„ฑํ•˜์˜€๋‹ค.

  2. ์œ„์—์„œ ๋งํ–ˆ๋˜ ๋Œ€๋กœ, ์„œ๋ฒ„๋‹น ์—ญํ• ์„ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌํ•˜์˜€๋‹ค.

ํŠนํžˆ ๋ช‡ ๋ฒˆ ๋ฆฌํŒฉํ„ฐ๋ง ๋์— redis์— ๋Œ€ํ•œ ์„œ๋ฒ„์˜ ์—ญํ• ๋„ ๋ถ„๋ฆฌ๋ฅผ ํ•œ ์ƒํƒœ์ธ๋ฐ, boot์˜ ๊ฒฝ์šฐ redis์—์„œ ์กฐํšŒ & ์—†์„์‹œ read-through ํŒจํ„ด์œผ๋กœ db์—์„œ ๊บผ๋‚ด์™€์„œ ์บ์‹œ์— ์ €์žฅํ•˜๋Š” ๊ฒƒ ๊นŒ์ง€ ๋‹ด๋‹นํ•˜๊ณ  (์ฆ‰, ์‚ฌ์‹ค์ƒ ์กฐํšŒ๋งŒ ๋‹ด๋‹นํ•˜๋Š” ์…ˆ์ด๋‹ค) fast api์˜ ๊ฒฝ์šฐ๋Š” redis์— ์ €์žฅํ•˜๋Š” ์—ญํ• ์„ ๋‹ด๋‹นํ•œ๋‹ค. (๊ธฐ๋ถ„ ๋ฐ์ดํ„ฐ, ์ฑ„ํŒ… ๋ฐ์ดํ„ฐ ๋“ฑโ€ฆ)

์ด๋ ‡๊ฒŒ redis์— ๋Œ€ํ•œ ์—ญํ• ๊นŒ์ง€ ๋ถ„๋ฆฌํ•œ ์ด์œ ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

์™œ REDIS์— ๋Œ€ํ•œ ์—ญํ• ๋„ ๋ถ„๋ฆฌํ–ˆ์„๊นŒ?

1. DB ์ ‘๊ทผ์€ boot๋งŒ ๊ฐ€๋Šฅํ•˜๋‹ค.

๋‚˜์˜ ๊ฒฝ์šฐ๋Š” read-through ํŒจํ„ด์„ ์‚ฌ์šฉํ–ˆ๊ธฐ์—, redis์— ์บ์‹ฑ ๊ฐ’์ด ์—†์„ ๊ฒฝ์šฐ, db๋ฅผ ์กฐํšŒํ•˜๋Š” ๋กœ์ง์„ ์‹คํ–‰ํ•ด์„œ db์—์„œ ๊ฐ’์„ ๊ฐ€์ ธ์™€์„œ redis์— ์บ์‹ฑํ•˜๊ณ , ๊ฐ’์„ ๊ฐ€์ ธ์˜จ๋‹ค.

์ด ๋ถ€๋ถ„ ์ ์šฉ ๋กœ์ง์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค

ChatCacheService ์ผ๋ถ€

1
2
3
4
5
6
7
8
9
// ๊ณ ๊ฐ ๊ธฐ๋ณธ ์ •๋ณด ์กฐํšŒ (Read-through)
public CustomerCacheDTO getCustomerInfo(Long customerId) {
    return cacheStore.getOrLoad(
            CacheSpec.CUSTOMER,
            customerId,
            CustomerCacheDTO.class,
            () -> customerRepository.findCustomerCacheInfoById(customerId)
    );
}

CacheStore ์ผ๋ถ€

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Read-Through: ์บ์‹œ ๋ฏธ์Šค ์‹œ ๋กœ๋” ์‹คํ–‰ โ†’ ์บ์‹œ์— ์ €์žฅ ํ›„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์„ค์ •ํ•œ๋‹ค.
public <T> T getOrLoad(CacheSpec spec, Object userId, Class<T> type, Supplier<T> loader) {
    // 1) ์บ์‹œ ๋จผ์ € ์กฐํšŒ
    T cached = this.get(spec, userId, type);
    if (cached != null) {
        return cached;
    }

    // 2) ๋ฏธ์Šค๋ฉด "๋กœ๋”" ์‹คํ–‰ (์—ฌ๊ธฐ์„œ DB ์ ‘๊ทผ์ด ์ˆ˜ํ–‰๋จ)
    // ์—ฌ๊ธฐ์„œ Supplier<T>๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์กฐํšŒ ๋กœ์ง์„ ์ˆจ๊ธธ ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์กฐํšŒ ๋กœ์ง์ด ์„œ๋น„์Šค ๋‹จ์œผ๋กœ ์ƒˆ์–ด๋‚˜๊ฐ€์ง€ ์•Š๋„๋ก ํ•œ๋‹ค.
    T loaded = loader.get();

    // 3) ๊ฐ’์ด ์žˆ์œผ๋ฉด ์บ์‹œ์— ๋„ฃ๊ณ  ๋ฐ˜ํ™˜ (ํ‚ค/TTL์€ spec์—์„œ ๊ฐ€์ ธ์˜ด)
    if (loaded != null) {
        this.put(spec, userId, loaded, spec.getTtl());
    }
    return loaded;
}

์ฆ‰, ์กฐํšŒ๋ฅผ ํ•˜๋ ค๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ db์— ์ ‘๊ทผํ•˜๋Š” ๋กœ์ง์ด ๋“ค์–ด๊ฐ„๋‹ค.

๊ทธ๋Ÿฌ๋ฏ€๋กœ ๋ชจ๋“  ์กฐํšŒ๋Š” boot๊ฐ€ ํ•˜๊ณ , ๋Œ€์‹ ์— ์ด ์กฐํšŒํ•œ ๊ฐ’๋“ค์„ ์† ์„œ๋ฒ„์ธ fast api์— ์ „๋‹ฌํ• ๋•Œ request๋กœ ๋‹ด๋„๋ก ํ•œ๋‹ค.

๋ฌด์—‡๋ณด๋‹ค. spring์—๋Š” HikariCP ๋“ฑ ๊ฒ€์ฆ๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— DB ์ปค๋„ฅ์…˜ ํ’€ ๊ด€๋ฆฌ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ํ•  ์ˆ˜ ์žˆ๋‹ค.

2. llm ๊ฐ’๋“ค์€ fast api๊ฐ€ ๋ฐ›๋Š”๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ llm์‘๋‹ต๊ฐ’์€ fast api๊ฐ€ ๋จผ์ € response๋กœ ๋ฐ›๊ณ , ๊ทธ๊ฑธ redis์— ์บ์‹ฑํ•˜๊ณ , ์บ์‹ฑ ํ›„์— spring boot์— ๋ณด๋‚ด์•ผ ํ•œ๋‹ค.

์ฆ‰, fast api์—์„œ ์บ์‹ฑํ•˜๋Š”๊ฒŒ ๋” ๋น ๋ฅด๋‹ค.

๋˜ํ•œ, ๋‚˜์˜ ๊ฒฝ์šฐ๋Š” ๋ฉ”์„ธ์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ์‹์ด token์ˆ˜์— ๋”ฐ๋ผ ์ปคํŒ…ํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, (์‹ค์ œ ๋งŽ์€ ์ฑ—๋ด‡๋“ค์ด ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค.)

token ์ „๋žต์˜ ๊ฒฝ์šฐ, python์—์„œ tiktoken์„ ์ด์šฉํ•˜๋ฉด ํŽธํ•œ ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์—(์‹ฌ์ง€์–ด ์ด๊ฑด ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” OpenAI์—์„œ ๊ณต์‹์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹คโ€ฆ๊ตณ) fast api์— ์œ„์ž„ํ•˜๊ณ ์ž ํ–ˆ๋‹ค. ์ฆ‰, message๊ด€๋ฆฌ๋ฅผ ์ „๋ถ€ python์— ์œ„์ž„ํ•œ๋‹ค๋Š” ๊ฒƒ์ธ๋ฐ

๊ทธ๋ ‡๊ฒŒ ํ• ๊บผ๋ผ๋ฉด db ์ €์žฅ์ด ํ•„์š”์—†๋Š” ๊ฐ’๋“ค์€ ์ „๋ถ€ fast api์—์„œ ๋‹ด๋‹นํ•˜๋Š”๊ฒŒ ๋งž๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๊ธฐ์—, โ€œ์ €์žฅโ€๋‹จ์„ fast api์— ์œ„์ž„ํ–ˆ๋‹ค.

class RedisService ์ผ๋ถ€

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def save_user_mood(self, session_id: str, mood: str) -> bool:
    """์‚ฌ์šฉ์ž mood ์ €์žฅ"""
    try:
        key = f"user_mood:{session_id}"
        mood_data = {
            "mood": mood,
            "timestamp": datetime.now().isoformat()
        }
        
        self.redis_client.setex(
            key, 
            timedelta(hours=24), 
            json.dumps(mood_data, ensure_ascii=False)
        )
        logger.info(f"Mood ์ €์žฅ ์„ฑ๊ณต: session_id={session_id}, mood={mood}")
        return True
        
    except Exception as e:
        logger.error(f"Mood ์ €์žฅ ์‹คํŒจ: session_id={session_id}, error={e}")
        return False

REDIS๋ฅผ ๋„์ž…ํ•œ ์ด์œ ?

์ด๊ฒƒ๋„ ์งˆ๋ฌธ์ด ๋“ค์–ด์™”์–ด์„œ ์ •๋ฆฌํ•˜๊ณ ์ž ํ•œ๋‹ค.

REDIS๋Š” ์‚ฌ์‹ค, ์šฐ๋ฆฌ๊ฐ€ ์ข€ ๋” ์ฑ„ํŒ… ํ”Œ๋กœ์šฐ๋ฅผ ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์กฐ์ž‘ํ•˜๊ธฐ ์‰ฝ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ๋„์ž…ํ–ˆ๋‹ค.

์‚ฌ์‹ค์ƒ ์šฐ๋ฆฌ๊ฐ€ ์ถ”์ฒœ์— ํ•„์š”ํ•œ ๋ชจ๋“  ์ •๋ณด (์‚ฌ์šฉ์ž ์ •๋ณด, ํ•œ๋„, ๊ธฐ๋ถ„..)๊ฐ€ ์ „๋ถ€ DB์— ๋‹ค ๋“ค์–ด๊ฐ€ ์žˆ๋Š” ๊ฒƒ๋„ ์•„๋‹ˆ๊ณ , ์ด ์ปจํ…์ŠคํŠธ๊ฐ€ ์žˆ์–ด์•ผ ๋” ์ •๋ฐ€ํ•œ ๊ฐœ์ธํ™” ์ถ”์ฒœ์ด ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๋˜ํ•œ, ์„œ๋ฒ„๊ฐ€ ๋‹ฌ๋ผ์„œ ์„ธ์…˜ ์Šคํ† ๋ฆฌ์ง€์— ๊ฐ’๋“ค์„ ์ €์žฅํ•ด์„œ ์–‘์ชฝ์—์„œ ์‚ฌ์šฉํ•˜๊ฒŒ ํ•  ์ˆ˜ ์—†์œผ๋ฉฐ, ํ•œ๋„๋‚˜ ๊ฐœ์ธ ์ •๋ณด ๊ฐ™์€ ๋ฏผ๊ฐ ์ •๋ณด๋ฅผ ์„ธ์…˜ ์Šคํ† ๋ฆฌ์ง€์— ๋„ฃ๊ธฐ์—๋Š” ๋ณด์•ˆ์  ๋ถ€๋‹ด๊ฐ์ด ๋„ˆ๋ฌด ์ปธ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋ฌด์—‡๋ณด๋‹ค ์šฐ๋ฆฌ์˜ ๊ฒฝ์šฐ๋Š” ๋ฒกํ„ฐDB๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— DB์— ์ฑ„ํŒ… ์š”์•ฝ๋ณธ๋งŒ ์ €์žฅํ•ด๋‘๋Š” ์ƒํƒœ์ธ๋ฐ, ์š”์•ฝ๋ณธ ๋งŒ์œผ๋กœ๋Š” ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ํ”Œ๋กœ์šฐ๋กœ ์ •๋ฐ€ํ•˜๊ฒŒ ์ฑ—๋ด‡์„ ์กฐ์ •ํ•  ์ˆ˜ ์—†๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์˜ค๊ณ ๊ฐ„ message๋ฅผ ์ €์žฅํ•  ์บ์‹ฑ db๊ฐ€ ํ•„์š”ํ–ˆ๊ณ , ๋ชจ๋“  ๋‹ˆ์ฆˆ๊ฐ€ ์ž˜ ๋งž์•„ ๋–จ์–ด์ ธ์„œ redis๋ฅผ ์ฑ„ํƒํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

์–˜๊ธฐํ•˜๋˜ ์ค‘ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜๊ฒฌ๋„ ๋‚˜์™”์–ด์„œ ์ •๋ฆฌํ•˜๊ณ ์ž ํ•œ๋‹ค.

๊ทธ๋Ÿผ ํŒŒ์ด์ฌ ์„œ๋ฒ„์— ๊ทธ๋ƒฅ list ์„ ์–ธํ•ด์„œ message๋“ค ์ „๋ถ€ ์•ˆ์— ๋„ฃ๊ณ , ๊ทธ๊ฑธ๋กœ ํ”Œ๋กœ์šฐ ์กฐ์ž‘ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ’€์–ด๋‚ด๋ฉด ์•ˆ๋˜๋‚˜?

์‚ฌ์‹ค ๋‚ด๊ฐ€ ์ข‹์•„ํ•˜์ง€ ์•Š๋Š” ๋ฐฉ์‹์ด๋ผ ๊ณ ๋ คํ•˜์ง€ ์•Š์€ ๊ฒƒ๋„ ๋งž์ง€๋งŒ, Redis์— ๋Œ€๋น„ํ•ด์„œ ์ด์œ ๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ์ •์˜ํ•˜๊ณ  ๊ฐ€๋Š”๊ฒŒ ์ข‹์„ ๊ฒƒ ๊ฐ™์•„์„œ ์ •๋ฆฌํ•˜๊ณ ์ž ํ•œ๋‹ค.

์šฐ์„  ์ด๋ ‡๊ฒŒ ํ•˜์ง€ ์•Š์€ ๊ฐ€์žฅ ํฐ ์ด์œ ๋Š”, ์ด๋ ‡๊ฒŒ ํ•  ๊ฒฝ์šฐ ์ฐจํ›„ โ€œ์ฑ„ํŒ… ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ด์•ผ ํ•˜๋Š” ๋ฐฉํ–ฅโ€์œผ๋กœ ๋ฆฌํŒฉํ„ฐ๋ง์„ ํ•˜๊ฑฐ๋‚˜ ์„œ๋น„์Šค ํ™•์žฅ์„ ๊ธฐํšํ•  ๊ฒฝ์šฐ, ๊ณ ์น  ๋ถ€๋ถ„์ด ๋„ˆ๋ฌด ๋งŽ๊ณ  ํ™•์žฅ์„ ํ•  ์ˆ˜ ์—†๊ฒŒ ๋˜์–ด ๋ฒ„๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ด๋Š” ๊ฐ์ฒด ์ง€ํ–ฅ ์†Œํ”„ํŠธ์›จ์–ด ์›์น™์ค‘ ํ•˜๋‚˜์ธ ํ™•์žฅ์—๋Š” ์—ด๋ ค์žˆ๊ณ  ์ˆ˜์ •์—๋Š” ๋‹ซํ˜€์žˆ์–ด์•ผ ํ•œ๋‹ค๋Š” OCP ์›์น™์—๋„ ์œ„๋ฐฐ๋œ๋‹ค. ๋ฌผ๋ก  ํŒŒ์ด์ฌ์€ ๊ฐ์ฒด์ง€ํ–ฅ ์–ธ์–ด๊ฐ€ ์•„๋‹ˆ๋‹ค๋งŒโ€ฆ..๋‚œ ์ž๋ฐ” ๊ฐœ๋ฐœ์ž๋‹ˆ๊น

๋˜ํ•œ ๊ฒฐ์ •์ ์œผ๋กœ ๋‹ค์Œ์˜ ๋‘ ๊ฐ€์ง€ ๋ฌธ์ œ๊ฐ€ ์กด์žฌํ•œ๋‹ค.

1. ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜์˜ ์œ„ํ—˜์ด ์žˆ๊ณ  ์„œ๋ฒ„ ์•ˆ์ •์„ฑ์ด ๋–จ์–ด์ง„๋‹ค.

์ฑ—๋ด‡์˜ ํŠน์„ฑ์ƒ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ณ„์† ์Œ“์ธ๋‹ค.

๋˜ํ•œ ์ฑ—๋ด‡์˜ ํŠน์„ฑ์ƒ, ์ปจํ…์ŠคํŠธ ๋ฐ์ดํ„ฐ์˜ ๋ณต์žก์„ฑ๊ณผ ์šฉ๋Ÿ‰ ๋•Œ๋ฌธ์— ๋ฉ”๋ชจ๋ฆฌ ๋ถ€ํ•˜๊ฐ€ ์ผ์–ด๋‚˜๊ธฐ ์‰ฝ๊ณ , ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜์— ๋ถ€ํ•˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ญ ์šฐ๋ฆฌ์•ผ ํ•œ ๋‹ฌ ํ”„๋กœ์ ํŠธ์—ฌ์„œ ์ด๊ฑฐ๊นŒ์ง€ ๊ณ ๋ คํ•ด์•ผ ํ•˜๋‚˜ ์‹ถ์„ ์ˆ˜ ์žˆ์ง€๋งŒ, ํ”„๋กœ์ ํŠธ๋กœ ๋๋‚ ๊ฒŒ ์•„๋‹ˆ๊ณ  ์šฐ๋ฆฌ๋Š” ์šฐ๋ฆฌ๊ฐ€ โ€œํ˜„์—… ์„œ๋น„์Šคโ€์—์„œ ์–ด๋–ป๊ฒŒ ํ• ์ง€๋ฅผ ๊ณ ๋ คํ•˜๋Š”๊ฒŒ ๋งž๋‹ค๊ณ  ๋‚˜๋Š” ์ƒ๊ฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— (์ทจ์—…ํ• ๊บผ๋‹ˆ๊นŒ) ์ด ๋ถ€๋ถ„์„ ๊ณ ๋ คํ•˜๋Š”๊ฒŒ ๋งž๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

2. List๋Š” Thread-Safe ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋™์‹œ์„ฑ ๋ฌธ์ œ๊ฐ€ ์กด์žฌํ•˜๊ณ , ์ด๋กœ ์ธํ•ด ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ์ ‘๊ทผํ•˜๋ฉด ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฑ—๋ด‡์—์„œ๋Š” ๋Œ€ํ™” ์ˆœ์„œ๊ฐ€ ๋ณด์žฅ๋˜๋Š” ๊ฒƒ์ด ๊ต‰์žฅํžˆ ์ค‘์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด ์ˆœ์„œ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ๋งฅ๋ฝ ์ž์ฒด๊ฐ€ ๊ผฌ์—ฌ์„œ ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ํ๋ฆ„์„ ์ œ์–ดํ•˜๊ธฐ๊ฐ€ ํž˜๋“ค ์ˆ˜ ์žˆ๋‹ค.

๋˜ํ•œ, ์‚ฌ์šฉ์ž๊ฐ„ ๋ฐ์ดํ„ฐ๊ฐ€ ํ˜ผ์žฌ๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ๋ช…ํ™•ํ•œ ๋‹จ์ ์ด ์กด์žฌํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด๋ณด์ž.

1
2
3
4
5
6
7
# ๋™์‹œ์„ฑ ๋ฌธ์ œ๋กœ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค
# ์‚ฌ์šฉ์ž A: "๋‚ด ์•Œ๋ ˆ๋ฅด๊ธฐ ์ •๋ณด: ๊ฒฌ๊ณผ๋ฅ˜"
# ์‚ฌ์šฉ์ž B: "๋ง›์ง‘ ์ถ”์ฒœํ•ด์ค˜"

# Thread-unsafe ์ƒํ™ฉ์—์„œ:
# ์‚ฌ์šฉ์ž B๊ฐ€ ์‚ฌ์šฉ์ž A์˜ ์•Œ๋ ˆ๋ฅด๊ธฐ ์ •๋ณด๋กœ ์ถ”์ฒœ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ! ๐Ÿšจ
# โ†’ ์‹ฌ๊ฐํ•œ ๋ณด์•ˆ/ํ”„๋ผ์ด๋ฒ„์‹œ ๋ฌธ์ œ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# ์‹ค์ œ๋กœ ์ผ์–ด๋‚  ์ˆ˜ ์žˆ๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค
import threading
import time

chat_data = []  # ์ „์—ญ ๋ฆฌ์ŠคํŠธ

def process_user_a():
    chat_data.append({
        "user": "A", 
        "type": "allergy_info",
        "content": "๊ฒฌ๊ณผ๋ฅ˜ ์•Œ๋ ˆ๋ฅด๊ธฐ ์žˆ์Œ"
    })
    time.sleep(0.1)  # ๋„คํŠธ์›Œํฌ ์ง€์—ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜

def process_user_b():
    time.sleep(0.05)  # ์•ฝ๊ฐ„์˜ ์ง€์—ฐ
    # ์‚ฌ์šฉ์ž B๊ฐ€ ์ถ”์ฒœ ์š”์ฒญ
    chat_data.append({
        "user": "B",
        "type": "recommendation_request", 
        "content": "๋ง›์ง‘ ์ถ”์ฒœํ•ด์ค˜"
    })
    
    # LLM์—๊ฒŒ ์ปจํ…์ŠคํŠธ ์ „๋‹ฌ ์‹œ
    recent_context = chat_data[-2:]  # ์ตœ๊ทผ 2๊ฐœ ๋ฉ”์‹œ์ง€
    # โ†’ ์‚ฌ์šฉ์ž A์˜ ์•Œ๋ ˆ๋ฅด๊ธฐ ์ •๋ณด๊ฐ€ ํฌํ•จ๋จ! ๐Ÿšจ

# ๋™์‹œ ์‹คํ–‰
threading.Thread(target=process_user_a).start()
threading.Thread(target=process_user_b).start()

๊ฒฐ๊ณผ์ ์œผ๋กœโ€ฆ.

1
2
3
4
5
User B: "๋ง›์ง‘ ์ถ”์ฒœํ•ด์ค˜"
LLM Context: ["๊ฒฌ๊ณผ๋ฅ˜ ์•Œ๋ ˆ๋ฅด๊ธฐ ์žˆ์Œ", "๋ง›์ง‘ ์ถ”์ฒœํ•ด์ค˜"]
LLM Response: "๊ฒฌ๊ณผ๋ฅ˜๋ฅผ ํ”ผํ•œ ์•ˆ์ „ํ•œ ๋ ˆ์Šคํ† ๋ž‘์„ ์ถ”์ฒœ๋“œ๋ฆด๊ฒŒ์š”..."

User B: "??? ๋‚˜ ์•Œ๋ ˆ๋ฅด๊ธฐ ์—†๋Š”๋ฐ?" ๐Ÿค”

์•Œ๋ ˆ๋ฅด๊ธฐ ๋ฟ ์•„๋‹ˆ๋ผ, ๊ฐœ์ธ์ ๋ณด๊ฐ€ ์„ž์ผ ๊ฒฝ์šฐ์—” ๊ฐœ์ธ์ •๋ณด ๋…ธ์ถœ๋กœ ๋ณดํ˜ธ๋ฒ• ์œ„๋ฐ˜์˜ ์œ„ํ—˜์„ฑ๋„ ์กด์žฌํ•œ๋‹ค๊ณ  ํ•œ๋‹ค.

์˜ˆ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

1
2
3
4
5
# ์‚ฌ์šฉ์ž A: "์šฐ๋ฆฌ ํšŒ์‚ฌ ๊ทผ์ฒ˜ (๊ฐ•๋‚จ์—ญ)"  
# ์‚ฌ์šฉ์ž B: "์ ์‹ฌ ์ถ”์ฒœํ•ด์ค˜"

# โ†’ ์‚ฌ์šฉ์ž B๊ฐ€ A์˜ ์œ„์น˜ ์ •๋ณด๋กœ ์ถ”์ฒœ๋ฐ›์Œ
# โ†’ ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ๋ฒ• ์œ„๋ฐ˜ ๊ฐ€๋Šฅ์„ฑ

Redis์˜ ๊ฒฝ์šฐ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Thread-safeํ•œ ํ™˜๊ฒฝ์ด๋ผ ์ด๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

This post is licensed under CC BY 4.0 by the author.