초기 코드에서는 벡터 저장소를 초기화하기 위해 Jedis 클라이언트를 직접 생성하고 Redis 명령어를 날리고 있었다.
// 이전코드
publicvoidclearStore() {
redis.clients.jedis.Jedisjedis=newredis.clients.jedis.Jedis("localhost",6379);
jedis.sendCommand(
Protocol.Command.valueOf("FT.DROPINDEX"),
"vector_index",
"DD"
);
Set<String>ragKeys=jedis.keys("rag:*");
jedis.del(ragKeys.toArray(newString[0]));
jedis.close();
}
처음에는 단순히 인덱스 초기화 기능만 필요했기 때문에 큰 문제를 느끼지 못했다. 하지만 코드를 다시 보니 여러 가지 문제가 눈에 들어왔다. 대표적인 문제는 다음과 같았다.
new Jedis()
→ 직접 커넥션 생성
→ close() 수동 관리
예외가 발생하면 close()가 호출되지 않을 가능성이 있었다. 즉 커넥션 누수 위험이 있었다.
vector_index
rag:*
localhost:6379
이 값들이 코드에 직접 들어가 있었다. 환경이 바뀌거나 Redis 설정이 바뀌면 코드를 수정해야 하는 구조였다.
Spring AI는 이미 VectorStore 추상화 계층을 제공하고 있다. 하지만 기존 코드는 Redis 명령어를 직접 사용하고 있었기 때문에 Spring AI의 설계 철학을 전혀 활용하지 못하는 코드였다.
리팩토링의 방향은 단순했다.
"프레임워크가 해줄 수 있는 일은 프레임워크에게 맡기자."
그래서 다음 세 가지 원칙을 세웠다.
1. Redis 커넥션은 Spring이 관리하도록 한다
2. VectorStore 추상화를 활용한다
3. 설정 값은 코드에서 제거한다
이 원칙을 기준으로 벡터 저장소 관리 로직을 다시 작성했다.
리팩토링 이후에는 별도의 관리 서비스를 하나 두었다.
// 개선된 코드
@Service
@RequiredArgsConstructor
@Slf4j
publicclassVectorStoreManagementService {
privatefinalVectorStorevectorStore;
privatefinalRedisTemplate<String,Object>redisTemplate;
@Value("${rag.redis.vectorstore.key-prefix:rag:}")
privateStringkeyPrefix;
publicvoidclearStore() {
Set<String>ragKeys=redisTemplate.keys(keyPrefix+"*");
if (ragKeys!=null&&!ragKeys.isEmpty()) {
redisTemplate.delete(ragKeys);
}
log.info("벡터 저장소가 안전하게 초기화되었다.");
}
}
이 구조의 핵심은 두 가지다.
즉 Redis를 직접 다루기보다는 Spring이 제공하는 추상화 계층을 활용하는 방식으로 바꿨다.
리팩토링 이후 체감되는 장점은 꽤 분명했다.
이제 Redis 커넥션은 다음 구조로 관리된다.
Application
↓
RedisTemplate
↓
Connection Pool
↓
Redis
Spring Boot가 자동으로 커넥션 풀링과 리소스 반환을 관리한다. 개발자가 직접 close()를 관리할 필요가 없다.
기존 구조에서는 Redis 명령어에 의존하고 있었다.
FT.DROPINDEX
rag:*
Jedis
하지만 이제는 다음 구조가 되었다.
Application
↓
VectorStore
↓
RedisVectorStore
즉 향후 다음과 같은 벡터 DB로 전환할 때도 코드 변경이 최소화된다.
Redis
PostgreSQL + pgvector
Pinecone
Weaviate
VectorStore 인터페이스만 유지하면 된다.
기존에는 인덱스 이름과 키 접두사가 코드에 있었다. 리팩토링 이후에는 설정으로 분리했다.
rag:
redis:
vectorstore:
index-name: my_custom_index
key-prefix:"prod:rag:"
이제 코드 수정 없이 환경별 설정을 분리할 수 있다.
처음에는 단순히 "일단 돌아가는 코드"를 만드는 데 집중했다. 하지만 시스템이 조금씩 커지면서 코드 구조가 점점 불안해지고 있다는 느낌이 들기 시작했다. 특히 RAG 시스템은 다음 요소들이 모두 얽혀 있다.
문서 파싱
임베딩
벡터 저장
검색
LLM 호출
이 흐름 속에서 데이터가 어디에 어떻게 저장되는지 명확하지 않으면 나중에 관리가 굉장히 어려워진다. 프레임워크가 제공하는 추상화에는 결국 이유가 있었다.
직접 모든 것을 제어하는 코드보다,프레임워크의 표준 구조를 따르는 코드가 훨씬 안정적이고 확장 가능하다. 결과적으로 이번 리팩토링은 단순한 코드 정리가 아니라 RAG 시스템 아키텍처를 다시 정리하는 과정이기도 했다.