Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
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 31
Archives
Today
Total
관리 메뉴

밤빵's 개발일지

서버가 상태를 가지고 있으면 안된다? = Stateless 본문

단체개발일지

서버가 상태를 가지고 있으면 안된다? = Stateless

최밤빵 2024. 8. 12. 00:36

"상태 : 채팅방의 내용등 변수로써 가지고 있으면 안되는 것......?"

 

서버가 상태를 가지고 있다면 각 서버간 상호작용을 할 수 없게 된다. 

서버는 고정적인 게 아니라 변동적인것 이기 때문이다. ( 서버 이용 상황에 따라 늘리고 줄이고 하기때문에 ) 

이런 이유로 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 를 구현하면 확장성, 내결함성, 서버 관리의 용이성 등 많은 이점을 얻을 수 있고 상태는 외부저장소에 저장함으로써 서버가 요청을 줄이고 효율적이고 안정적으로 처리하는 데 계속 집중할 수 있게 된다.