Spring๊ณผ Redis๋ฅผ ํ์ฉํ ๋์์ฑ ์ฒ๋ฆฌ ๋ฐ ๋ฐ์ดํฐ ์ผ๊ด์ฑ
ํ๋์ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ๋์์ฑ ์ฒ๋ฆฌ๊ฐ ์ค์ํ ์์์ ๋๋ค. ํนํ ์ฌ๋ฌ ์ฌ์ฉ์๊ฐ ๋์์ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฑฐ๋ ์กฐํํ๋ ํ๊ฒฝ์์ ๋ฐ์ดํฐ์ ์ผ๊ด์ฑ์ ์ ์งํ๋ ๊ฒ์ ํฐ ๋์ ๊ณผ์ ๊ฐ ๋ฉ๋๋ค.
์ค์ ๋ก ๋ฌด์์ ํฌ์ธํธ(๋๋ค ํฌ์ธํธ) ์ง๊ธ ์ด๋ฒคํธ๋ฅผ ๊ฐ๋ฐํ๋ ๊ฒฝํ์ ์ ๋ฆฌํด๋ณด๋ ค๊ณ ๊ธ์ ์์ฑํด ๋ด ๋๋ค.
1. ๋์์ฑ ์ฒ๋ฆฌ์ ํ์์ฑ
๋์์ฑ์ด๋ ์ฌ๋ฌ ํธ๋์ญ์ ์ด ๋์์ ์คํ๋ ๋ ๋ฐ์ํ๋ ํ์์ผ๋ก, ๋ฐ์ดํฐ๋ฒ ์ด์ค์์์ ์ฝ๊ธฐ ๋ฐ ์ฐ๊ธฐ ์์ ์ ์ถฉ๋์ ๊ด๋ฆฌํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ์ด๋ฌํ ์ถฉ๋์ด ๋ฐ์ํ๋ฉด ๋ฐ์ดํฐ์ ์ผ๊ด์ฑ์ด ์ ํด๋ ์ ์์ผ๋ฉฐ, ์ด๋ ์ฌ์ฉ์ ๊ฒฝํ์ ๋ถ์ ์ ์ธ ์ํฅ์ ๋ฏธ์น ์ ์์ต๋๋ค.
2. Spring์์์ ํธ๋์ญ์ ๊ฒฉ๋ฆฌ ์์ค
Spring์์๋ ํธ๋์ญ์
์ ๊ฒฉ๋ฆฌ ์์ค์ ์ค์ ํ์ฌ ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค. ๊ฒฉ๋ฆฌ ์์ค์๋ ์ฌ๋ฌ ์ข
๋ฅ๊ฐ ์์ผ๋ฉฐ, ๋ํ์ ์ผ๋ก ๋ค์๊ณผ ๊ฐ์ ๊ฒ๋ค์ด ์์ต๋๋ค:
โ READ UNCOMMITTED: ๋ค๋ฅธ ํธ๋์ญ์
์ด ์ปค๋ฐํ์ง ์์ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ ์ ์์ต๋๋ค. ๊ฐ์ฅ ๋ฎ์ ๊ฒฉ๋ฆฌ ์์ค์ผ๋ก ๋ํฐ ๋ฆฌ๋๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
โ READ COMMITTED: ์ปค๋ฐ๋ ๋ฐ์ดํฐ๋ง ์ฝ์ ์ ์์ผ๋ฉฐ, ๋ํฐ ๋ฆฌ๋๋ ๋ฐฉ์งํ ์ ์์ต๋๋ค. ํ์ง๋ง ํฌํ
๋ฆฌ๋๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
โ REPEATABLE READ: ๊ฐ์ ํธ๋์ญ์
๋ด์์ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ฌ๋ฌ ๋ฒ ์ฝ์ ๊ฒฝ์ฐ, ํญ์ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฅํฉ๋๋ค. ํฌํ
๋ฆฌ๋๋ ๋ฐฉ์งํ์ง๋ง ์ฑ๋ฅ ์ ํ๊ฐ ์์ ์ ์์ต๋๋ค.
โ SERIALIZABLE: ๊ฐ์ฅ ๋์ ๊ฒฉ๋ฆฌ ์์ค์ผ๋ก, ํธ๋์ญ์ ์ ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌํ์ฌ ๋ฐ์ดํฐ์ ์ผ๊ด์ฑ์ ๋ณด์ฅํฉ๋๋ค. ํ์ง๋ง ์ฑ๋ฅ์ด ํฌ๊ฒ ์ ํ๋ ์ ์์ต๋๋ค.
3. Redis์ ๋ถ์ฐ ๋ฝ
Redis๋ ์ธ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก ๋น ๋ฅธ ๋ฐ์ดํฐ ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ฉฐ, ๋ถ์ฐ ๋ฝ์ ํตํด ๋์์ฑ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๋ฐ ์ ์ฉํฉ๋๋ค. Redis์ ๋ถ์ฐ ๋ฝ์ ํ์ฉํ๋ฉด ํน์ ํธ๋์ญ์
์ด ์งํ ์ค์ผ ๋ ๋ค๋ฅธ ํธ๋์ญ์
์ด ํด๋น ๋ฐ์ดํฐ์ ์ ๊ทผํ์ง ๋ชปํ๋๋ก ํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ๋ค์๊ณผ ๊ฐ์ ์ด์ ์ ์ป์ ์ ์์ต๋๋ค:
๋น ๋ฅธ ์ฑ๋ฅ: Redis๋ ์ธ๋ฉ๋ชจ๋ฆฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ด๋ฏ๋ก ๋์ ์ฑ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
๋์์ฑ ๊ด๋ฆฌ: ๋ถ์ฐ ๋ฝ์ ํตํด ๋ฐ์ดํฐ์ ๋์ ์์ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
์ค์ ์ฌ์ฉํ๋ ๋๋ค ํฌ์ธํธ ์ง๊ธ ์ด๋ฒคํธ ๋์์ฑ ์ฒ๋ฆฌ ์์ ์ฝ๋
Redis config
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@Configuration
public class RedisConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new JedisConnectionFactory();
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
return template;
}
}
Redis ๋ถ์ฐ ๋ฝ ์๋น์ค
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
public class DistributedLockService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public boolean acquireLock(String lockKey, long timeout) {
String lockValue = UUID.randomUUID().toString();
Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, timeout, TimeUnit.MILLISECONDS);
if (success != null && success) {
return true; // ๋ฝ์ ์ฑ๊ณต์ ์ผ๋ก ์ป์
}
return false; // ๋ฝ์ ์ป์ง ๋ชปํจ
}
public void releaseLock(String lockKey, String lockValue) {
// ํ์ฌ ๋ฝ์ ๊ฐ๊ณผ ๋น๊ตํ์ฌ ๋์ผํ ๊ฒฝ์ฐ์๋ง ๋ฝ ํด์
if (lockValue.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
}
ํฌ์ธํธ ๊ด๋ฆฌ ์๋น์ค
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class PointConfigService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private PointConfigRepository pointConfigRepository; // ๋ฉ์ธ DB์ ์ฐ๊ฒฐ๋ ๋ฆฌํฌ์งํ ๋ฆฌ
// ์ด๊ธฐ๊ฐ ๋ก๋ฉ
public void loadInitialPointConfig() {
int initialPoints = pointConfigRepository.getDailyPointLimit();
redisTemplate.opsForValue().set("daily_point_limit", initialPoints);
}
// ๊ด๋ฆฌ์ ํ์ด์ง์์ ํฌ์ธํธ ์กฐ์
public void updateDailyPointLimit(int newLimit) {
// ๋ฉ์ธ DB ์
๋ฐ์ดํธ
pointConfigRepository.updateDailyPointLimit(newLimit);
// Redis ์
๋ฐ์ดํธ
redisTemplate.opsForValue().set("daily_point_limit", newLimit);
}
// ํฌ์ธํธ ์กฐํ
public int getDailyPointLimit() {
Integer limit = (Integer) redisTemplate.opsForValue().get("daily_point_limit");
if (limit == null) {
// Redis์ ๊ฐ์ด ์์ผ๋ฉด ๋ฉ์ธ DB์์ ์กฐํ
limit = pointConfigRepository.getDailyPointLimit();
// Redis์ ์บ์
redisTemplate.opsForValue().set("daily_point_limit", limit);
}
return limit;
}
}
ํฌ์ธํธ ์ง๊ธ ์๋น์ค
@Service
public class PointService {
@Autowired
private DistributedLockService distributedLockService;
@Autowired
private PointRepository pointRepository; // ํฌ์ธํธ ์ ๋ณด๋ฅผ ์กฐํํ ์ ์๋ ๋ ํฌ์งํ ๋ฆฌ
public int getCurrentPoints(String userId) {
// Redis์์ ํฌ์ธํธ ์กฐํ
Integer points = redisTemplate.opsForValue().get(userId);
if (points == null) {
// Redis์ ๊ฐ์ด ์์ผ๋ฉด DB์์ ์กฐํ
points = pointRepository.getCurrentPoints(userId);
// Redis์ ์บ์
redisTemplate.opsForValue().set(userId, points);
}
return points;
}
public void allocatePoints(String userId, int pointsToAllocate) {
String lockKey = "point_lock";
long timeout = 30000; // 30์ด
if (distributedLockService.acquireLock(lockKey, timeout)) {
try {
// ๋ฉ์ธ DB์์ ํฌ์ธํธ ์
๋ฐ์ดํธ
pointRepository.allocatePoints(userId, pointsToAllocate);
// Redis์์๋ ๊ฐ ์
๋ฐ์ดํธ
int currentPoints = redisTemplate.opsForValue().get(userId);
redisTemplate.opsForValue().set(userId, currentPoints + pointsToAllocate);
} finally {
distributedLockService.releaseLock(lockKey, UUID.randomUUID().toString());
}
} else {
// ๋ฝ์ ์ป์ง ๋ชปํ์ ๊ฒฝ์ฐ ์ฒ๋ฆฌ
}
}
}
๊ด๋ฆฌ์ ํ์ด์ง์์ ์ผ์ผ ์ง๊ธ ๊ฐ๋ฅํ ํฌ์ธํธ ์กฐ์
public void adjustPoints(int dailyPointLimit) {
PointConfigService pointConfigService = new PointConfigService();
// ์ด๊ธฐ๊ฐ ๋ก๋ฉ
pointConfigService.loadInitialPointConfig();
// ํฌ์ธํธ ์กฐ์
pointConfigService.updateDailyPointLimit(dailyPointLimit); // ์๋ก์ด ํฌ์ธํธ ํ๋ ์ค์
}
์ค์ ์๊ตฌ์ฌํญ์์ ๊ด๋ฆฌ์ ํ์ด์ง๋ฅผ ํตํต ์ผ์ผ ์ง๊ธ ๊ฐ๋ฅํ ํฌ์ธํธ ์กฐ์ ์ด ์ค์๊ฐ์ผ๋ก ํ์ํ์ด์ ์ถ๊ฐ๋์๋ค.
4. ๋ํฐ ๋ฆฌ๋์ ํฌํ ๋ฆฌ๋
- ๋ํฐ ๋ฆฌ๋(Dirty Read)
๋ํฐ ๋ฆฌ๋๋ ํธ๋์ญ์
์ด ์ปค๋ฐ๋์ง ์์ ๋ฐ์ดํฐ๋ฅผ ์ฝ๋ ํ์์
๋๋ค. ์๋ฅผ ๋ค์ด, ํ ํธ๋์ญ์
์ด ๋ฐ์ดํฐ๋ฅผ ์์ ์ค์ผ ๋ ๋ค๋ฅธ ํธ๋์ญ์
์ด ๊ทธ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ผ๋ฉด, ์์ ์ด ์๋ฃ๋๊ธฐ ์ ์ ์๋ชป๋ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์์
์ ์ํํ ์ ์์ต๋๋ค. ์ด๋ ๋ฐ์ดํฐ์ ์ผ๊ด์ฑ์ ์ ํดํฉ๋๋ค.
- ํฌํ
๋ฆฌ๋(Phantom Read)
ํฌํ
๋ฆฌ๋๋ ํ ํธ๋์ญ์
์์ ํน์ ์กฐ๊ฑด์ ๋ง์กฑํ๋ ๋ฐ์ดํฐ์
์ ์กฐํํ ๋, ๋ค๋ฅธ ํธ๋์ญ์
์ด ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ๊ฑฐ๋ ์ญ์ ํ์ฌ ์ด์ ์ ์กฐํํ ๋ฐ์ดํฐ์
๊ณผ ๋ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ์ป๋ ํ์์
๋๋ค. ์ด๋ ๋น์ฆ๋์ค ๋ก์ง์ ์ค๋ฅ๋ฅผ ์ด๋ํ ์ ์์ต๋๋ค.