๐ฑ ์์ ์ฒซ ํฌ ์๋ฒ ๋์ ๊ธฐ, ์๋ฒ ๋ ๊ฐ๋ฅผ ํด๋ฆฐ~ํ๊ฒ ์ด์ํด๋ณด์์ฉ - [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 ์์น์ ์งํค๊ธฐ ์ํด ์๋ฒ๋น ์ญํ ๋ถ๋ฆฌ๋ฅผ ๋ช ํํ ํด๋ ์์ ์ด์๋ค.
๊ทธ๋ ๊ธฐ์ ์ํคํ ์ฒ๋ฅผ ๋ช ํํ๊ฒ ์ง๋์ง ์์ผ๋ฉด, ๊ฐ๋ฐํ ๋ ๋งค์ฐ ๋ง์ ๋ถ๋ถ์ด ๊ผฌ์ผ ๊ฒ ๊ฐ์๋ค.
๊ทธ๋์ ๋ช ๋ฒ(์ฌ์ค ํ ๋ค์ฏ ๋ฒ ์ ๋..) ๊ฐ์์์ ๋์, ๋ค์๊ณผ ๊ฐ์ด ์ฑ๋ด ์ํคํ ์ฒ๋ฅผ ๊ตฌ์ฑํ์๋ค.
ํ์ฌ๋ ๋ก์ปฌ ๊ฐ๋ฐ์ด๋ผ REDIS๋ฅผ DOCKER์ ๋์ ๋๋ฐ, EC2์ ๋ฐฐํฌํ ํ์๋ ์๋ ElasticCache๋ก ๋ณ๊ฒฝํ ์์ ์ด์๋ค.
๊ทผ๋ฐ ์ฌ์คโฆ.์ฐ๋ฆฌ ํ๋ก์ ํธ๋ฅผ ๊ฐ์ฌ๋ ์๋ฒ์ ๋์์ผ ํ ์๋ ์์ด์, ๋์ปค ์ปจํ ์ด๋๋ง ํ ์ง๊ธ์ ๊ตฌ์กฐ๋ฅผ ์ ์งํด์ผ ํ ์๋ ์๊ฒ ๋ค. ์ด๊ฑด CI/CD ํ ๋ ๊ฐ๋ด์ผ ์๋ฏ ์ถ๋ค.
๊ตฌ์กฐ์ ํฌ์ธํธ๋ฅผ ๊ฐ๋จํ ์ค๋ช ํ์๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
์ต๋ํ 3-tier๋ก ๋ช ํํ ๋ถ๋ฆฌ๋๋๋ก ๊ตฌ์ฑํ์๋ค.
์์์ ๋งํ๋ ๋๋ก, ์๋ฒ๋น ์ญํ ์ ๋ช ํํ ๋ถ๋ฆฌํ์๋ค.
ํนํ ๋ช ๋ฒ ๋ฆฌํฉํฐ๋ง ๋์ 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ํ ํ๊ฒฝ์ด๋ผ ์ด๋ฅผ ๋ฐฉ์งํ ์ ์๋ค.
