밤빵's 개발일지
서버가 상태를 가지고 있으면 안된다? = Stateless 본문
"상태 : 채팅방의 내용등 변수로써 가지고 있으면 안되는 것......?"
서버가 상태를 가지고 있다면 각 서버간 상호작용을 할 수 없게 된다.
서버는 고정적인 게 아니라 변동적인것 이기 때문이다. ( 서버 이용 상황에 따라 늘리고 줄이고 하기때문에 )
이런 이유로 SSE에도 상태를 가지고 있으면 안된다.
김선용 멘토님이 말씀하신 " 서버가 상태를 가지고 있으면 안된다 " 는 서버가 클라이언트의 상태 정보를 직접 저장하거나 유지하지 않는 것이 좋다는 것을 의미한다. 이 원칙은 서버 아키텍처의 확장성과 안전성을 보장하기 위한 중요한 개념으로 여기서 상태(State)란 예를들어 채팅방의 내용, 로그인 세션, 사용자 별 설정 등 클라이언트와의 상호작용에 따라 달라지는 데이터를 말한다.
이 원칙이 중요한 이유와 개념을 이해하기 위해 설명을 하자면,
▶ 확장성 (Scalability)
→ 서버 A가 채팅방의 내용(상태)을 가지고 있다고 가정했을 때, 만약 갑자기 많은 사용자가 접속하게 되어 서버 A가 부하를 견디지 못 할 경우, 서버 B를 추가해서 부하를 분산 시켜야 한다. 하지만 서버 B는 서버A의 상태에 접근할 수 없기때문에 상태를 알 수 없고 클라이언트에게 올바른 정보를 제공할 수 없다.
▷ 상태를 외부 저장소 (ex. 데이터베이스, Redis 등)에 저장하면 서버 A와 B가 모두 같은 저장소를 참조할 수 있다. 이렇게 하면 서버를 추가하더라도 상태 정보에 접근하는 데 문제가 없다.
javascript // 상태를 데이터베이스에 저장하는 예시
const saveChatMessage = async (chatRoomId, message) => {
await db.collection('chatRooms').doc(chatRoomId).update({ messages: firebase.firestore.FieldValue.arrayUnion(message)
}); };
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ChatService {
private final ChatRoomRepository chatRoomRepository;
ChatService(ChatRoomRepository chatRoomRepository) {
this.chatRoomRepository = chatRoomRepository; }
@Transactional
public void saveChatMessage(Long chatRoomId, Message message) {
ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) .orElseThrow(() -> new IllegalArgumentException("Chat room not found"));
messages chatRoom.getMessages().add(message);
chatRoomRepository.save(chatRoom); } }
▶ 내결함성 & 장애복구(Fault Tolerance)
→ 모든 로그인 세션 정보를 보유하고 있는 서버 A가 갑자기 충돌하거나, 장애로 다운되면 서버 A 가 가지고 있던 상태정보가 모두 사라진다. 사용자는 세션 데이터를 잃어버리고, 재 로그인해야하거나 저장하지 않은 진행 상황이 손실되는, 중요한 데이터를 잃을 수 있다.
▷ 상태를 외부 저장소에 저장하면, 서버에 다운되더라도 저장소에 저장된 상태정보는 유지 된다. 다른 서버가 중단된 부분부터 다시 시작할 수 있고, 이렇게 하면 세션 데이터가 그대로 유지되면서 사용자의 저장되지 않는 진행 상황도 중단 되지 않는다.
→ 내결함성 : 오류 발생 시 중단을 피하는 것 → 사전 조치
ex. 서로 다른 위치에 여러 서버가 있는 시스템(중복 설정)은 한 서버가 실패하면 다른 서버가 계속해서 요청을 처리
→ 장애복구 : 중단에 대응하고 정상적인 작동을 복원하는 것 → 사후 조치
ex. 서버가 다운되어 시스템을 다시 시작해야하거나 백업에서 데이터를 복원해야하는 경우
javascript // Redis를 사용한 세션 저장 예시
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: 'mySecret',
resave: false,
saveUninitialized: false
}));
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@Configuration
@EnableRedisHttpSession
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
▶ 간단한 서버 관리
→ 서버가 상태(사용자세션)를 저장하고 관리하는 경우 서버에 대한 업데이트 나 유지보수 할 때 그 상태 정보를 고려해야 한다. 이는 서버 유지 관리의 복잡성을 높이고 오류 가능성을 증가시킨다.
▷ 서버를 stateless로 유지하면 서버 코드가 단순해지고 관리가 쉬워진다. 클라이언트의 요청을 받고 처리한 후 응답을 반환하는 역할에만 중점을 두기 때문에 업데이트 또는 변경 중에 문제가 발생할 위험을 줄이고 상태 관리의 복잡성은 외부 저장소로 분리되기에 서버 코드가 간결해진다.
javascript // 상태를 관리하지 않는 서버 예시
app.post('/api/sendMessage', async (req, res) => {
const { chatRoomId, message } = req.body;
await saveChatMessage(chatRoomId, message);
res.status(200).send({ success: true });
});
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class ChatController {
private final ChatService chatService;
public ChatController(ChatService chatService) {
this.chatService = chatService;
}
@PostMapping("/sendMessage")
public ResponseEntity<?> sendMessage(@RequestBody ChatMessageRequest request) {
chatService.saveChatMessage(request.getChatRoomId(), request.getAnotherMessage());
return ResponseEntity.ok().body(Map.of("success", true));
}
}
▶ 결론
서버가 상태를 가지지 않는 stateless 를 구현하면 확장성, 내결함성, 서버 관리의 용이성 등 많은 이점을 얻을 수 있고 상태는 외부저장소에 저장함으로써 서버가 요청을 줄이고 효율적이고 안정적으로 처리하는 데 계속 집중할 수 있게 된다.
'단체개발일지' 카테고리의 다른 글
HikariPool...!! (0) | 2024.09.08 |
---|---|
결제시스템구현: 멱등성 문제 (PUT & PATCH) (0) | 2024.09.01 |
결제시스템구현: 동시성 문제&LOCK 개념 (1) | 2024.08.25 |
stateless 특성과 JWT의 관계 (0) | 2024.08.19 |
팀 개발일지에 합류하며 (1) | 2024.08.04 |