📌 서론
그동안 Redis가 key-value를 저장하는 저장소라고 들어서 그냥 간단한 "category":1 같은 것만 저장하는 줄 알았다. 그러나 Redis에서도 key 값에 객체나 List를 넣을 수 있다는 것을 알게 되었다.
실제로 프로젝트를 진행하면서 DB에서 자주 조회되는 카테고리를 레디스에 저장해야할일이 생겼다. 이 과정에서 알게된 어노테이션과 각 쓰임새를 정리해 보자.
@RedisHash
@RedisHash 어노테이션은 Spring Data Redis에서 사용되는 어노테이션으로, Redis에 객체를 해시 형태로 저장할 때 사용된다. 이 어노테이션은 Redis에 저장될 엔티티 클래스를 정의하는 데 사용되며, 해시 키를 지정할 수 있다. @RedisHash 어노테이션을 사용하여 Redis에서 데이터의 저장 구조와 관리를 용이하게 할 수 있다.
Redis 해시(Hash)
Redis 해시는 key-value 쌍으로 이루어진 컬렉션으로, 데이터베이스의 테이블과 비슷하다. 해시는 필드와 값 쌍으로 저장되며, 각 해시의 키는 고유하다. 예를 들어, 카테고리 정보를 저장하는 해시의 키가 category:1234라면, 이 해시에는 name, code 등의 필드가 포함될 수 있다.
@Id
@Id 어노테이션은 엔티티 클래스의 필드를 고유 식별자로 지정하는 데 사용된다. 이는 각 객체의 고유 식별자로서, 데이터의 검색, 업데이트, 삭제 시 매우 중요하다.
여기서 사용되는 @Id 어노테이션은 JPA Entity에서 사용하는 javax.persistence.Id의 @Id와는 다른 라이브러리에서 제공되는 것이다.
@RedisHash와 @Id 어노테이션 사용 예시
다음은 @RedisHash 어노테이션을 사용하여 사용자 정보를 Redis에 저장하는 예시이다.
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
@RedisHash("category")
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Category {
@Id
private Long id; // Redis 해시의 키
private String name;
private String code;
}
위 코드에서 @RedisHash("category")는 이 클래스의 객체가 Redis의 해시로 저장될 때 해시 키의 prefix를 "category"로 지정하는 역할을 한다. 여기서 id 필드는 Redis 해시의 키가 된다.
예를 들어, id 필드를 @Id로 지정하면, Redis 해시 키는 category:<id> 형태가 된다. 여기서 id 필드의 값이 19이면, Redis 해시 키는 category:19가 된다.
정리하자면, Redis에 엔티티 객체를 저장할 때는 @Id 어노테이션이 필수적이다. 이는 Redis 해시 키를 설정하는 데 사용되기 때문이다. 또한, @RedisHash 어노테이션은 객체가 어떤 해시로 저장될지 지정하는 데 유용하다. 따라서 @RedisHash 어노테이션과 @Id 어노테이션을 함께 사용하는 것이 일반적이고 권장된다.
만약 @RedisHash 어노테이션을 사용하지 않고 다양한 엔티티에 @Id 어노테이션만 사용했다면 User 객체의 id인지 Category 객체의 id인지 구별하기 어려워진다. @RedisHash를 사용하면 데이터가 명확하게 분류되고 관리되기 쉬워진다.
- 데이터 분류: @RedisHash 어노테이션을 사용하면 데이터가 저장될 해시의 이름을 지정할 수 있다. 예를 들어, 사용자 정보와 카테고리 정보를 각각 다른 해시로 저장할 수 있다. 이를 통해 데이터의 논리적 분리가 가능하다.
- 고유 식별자: @Id 어노테이션은 각 객체의 고유 식별자를 지정하기 위해 사용된다. 이는 데이터의 검색, 업데이트, 삭제 시 매우 중요하다.
그리고 Redis 개발을 하면서 왜 이 객체 자체를 엔티티라고 지칭하는지 궁금해졌다.
Redis에 객체를 저장하는 것을 엔티티(Entity)라고 부르는 이유
Redis에 저장되는 객체도 데이터베이스의 테이블에 저장되는 엔티티와 같은 개념으로 사용되기 때문에 엔티티라고 부른다. 즉, 비관계형 데이터베이스인 Redis에서도 관계형 데이터베이스와 마찬가지로 저장되는 데이터를 엔티티로 취급하는 것이다.
다음은 User 객체를 Redis에 저장하고 조회하는 간단한 예시이다:
import org.springframework.data.repository.CrudRepository;
public interface CategoryRepository extends CrudRepository<Category, Long> {
// 추가적인 쿼리 메서드가 필요하면 여기에 정의할 수 있다.
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CategoryService {
@Autowired
private CategoryRepository categoryRepository;
// 저장
public Category saveCategory(Category category) {
return categoryRepository.save(category);
}
// 조회
public Category getCategory(Long id) {
return categoryRepository.findById(id).orElse(null);
}
}
위 예시에서 CategoryRepository는 Spring Data Redis에서 제공하는 인터페이스를 확장하여 사용자 데이터를 저장하고 조회하는 역할을 한다. 이렇게 하면 Redis를 통해 간단하게 데이터를 저장하고 관리할 수 있으며, 객체를 엔티티로 간주하여 @RedisHash 어노테이션을 통해 관리할 수 있다.
여기서 '과연 JPA Entity와 Redis의 Entity를 하나의 클래스로 동시에 적용할 수 있을 것인가?'에 대한 호기심이 들었다.
사실 하나의 클래스에 @Entity와 @RedisHash를 동시에 적용하는 것은 기술적으로 가능한 방법이지만, 복잡성 증가와 관리의 어려움 때문에 일반적으로 권장되지는 않는다고 한다. 또한 @Id 어노테이션을 사용할 때javax.persistence.Id와 org.springframework.data.annotation.Id는 서로 다른 어노테이션이기 때문에 하나의 필드에 두 개를 동시에 사용할 수 없다. 대신, 각 저장소의 역할을 분리하여 유지보수를 용이하게 하고 성능을 최적화하는 것이 좋다. 필요할 경우, 각 어노테이션이 적용된 별도의 클래스를 사용하고, 필요한 경우 데이터 변환 로직을 추가하여 두 저장소 간의 일관성을 유지하는 방법을 고려해 볼 수 있다.
또한 Redis의 조회 성능을 더 향상할 수 있는 방법이 더 있다.
@Indexed
@Indexed 어노테이션을 사용하면 Redis에 인덱스가 생성되어 특정 필드로 조회할 때 성능이 향상된다.
Redis는 기본적으로 키-값 저장소로 설계되어 있으며, 해시 구조로 데이터를 저장할 수 있지만, 필드 기반 조회는 지원하지 않는다. 인덱스를 추가하면 필드 값을 기준으로 데이터를 빠르게 조회할 수 있다.
인덱스의 장점
- 빠른 조회 성능: 인덱스를 사용하면 특정 필드 값을 기준으로 데이터를 빠르게 조회할 수 있다. 예를 들어, code 필드로 Category 엔티티를 조회할 때 인덱스를 사용하면 성능이 크게 향상된다.
- 복잡한 쿼리 지원: 인덱스는 복잡한 쿼리를 지원하는 데 유용하다. 여러 필드를 기준으로 데이터를 조회해야 하는 경우 인덱스를 사용하면 효율적이다.
- 데이터 일관성 유지: 인덱스는 데이터 일관성을 유지하는 데 도움이 된다. 필드 값을 기준으로 조회할 때 인덱스를 사용하면 중복된 데이터 없이 일관된 결과를 얻을 수 있다.
@Indexed 사용 예제
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;
@RedisHash("category")
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Category {
@Id
private Long id; // Redis 해시의 키
@Indexed
private String code;
private String name;
}
인덱스 키의 생성과 역할
인덱스를 걸면 Redis에는 두 개의 키가 생성된다:
- category:<id>: 실제 엔티티 데이터가 저장되는 해시 키
- category:<id>:idx: 인덱스 정보를 저장하는 키
예를 들어, Category 엔티티의 인스턴스가 다음과 같을 때:
Category category = new Category(19L, "ct1109", "가구");
Redis에는 다음과 같은 두 개의 키가 생성된다:
- category:19
- category:19:idx
각 키를 조회하면 다음과 같은 값들이 나온다.
category:19 조회 결과
HGETALL category:19
1) "id"
2) "19"
3) "code"
4) "ct1109"
5) "name"
6) "가구"
category:19:idx 조회 결과
SMEMBERS category:19:idx
1) "code:ct1109"
여기서 category:19:idx는 인덱스 필드인 code와 그 값을 저장한다. 이를 통해 code 필드로 빠르게 조회할 수 있다.
실제로 redis에 저장된 모든 키를 조회해 보면 다음과 같이 잘 나오는 걸 확인할 수 있다.
명령어: key *
여기서 인덱스를 건 code로도 조회가 가능하다.
SMEMBERS category:code:ct1109
1) "category:19"
여기서 category:code:ct1109는 code가 ct1109인 모든 Category 엔티티의 키를 저장한다.
요약
- @RedisHash: 엔티티 클래스를 Redis에 저장하고자 할 때 사용하며, 해시 키의 기본 이름을 지정한다.
- @Id: 엔티티의 고유 식별자를 나타내며, Redis 해시 키의 일부로 사용된다.
- @Indexed: 특정 필드에 인덱스를 걸어, 해당 필드로 조회할 때 성능을 향상시킨다.
'Spring > Spring Boot' 카테고리의 다른 글
동시성 이슈 해결을 위한 Redis Lock (0) | 2024.06.10 |
---|---|
객체 간 매핑을 도와주는 MapStruct 라이브러리(2) - 추가 어노테이션 (0) | 2024.04.09 |
객체 간 매핑을 도와주는 MapStruct 라이브러리(1) - 기본 어노테이션 (0) | 2024.04.02 |
Lombok의 @Builder 어노테이션으로 객체 생성 (0) | 2024.04.02 |
RequestDto에서 MultipartFile 필드 사용 방법: 객체 바인딩 방법부터 테스트 코드까지 (3) | 2024.01.13 |