Programming/Tips, Fix, Anything Else

[TFAE] 게시판 조회수를 올리는 다양한 방법(Insert)

Supreme_YS 2023. 5. 30. 21:40

이 글이 쓰여진 이유를 보고 오시면 더욱 도움이 될 것 같다. TM 락을 해결하기 위한 방법 중의 하나는 Insert로 처리를 하는 것이다.


3부작으로 급조 기획된 이 글이 너무 급박하기 마무리 지어지는 느낌이 없지 않아있지만, 삽질할 시간을 아끼는 것도 중요하니 결론을 얘기하자면 제목과 위에도 언급된 Insert로 처리를 하는 것이다.

 

소스코드로 빠르게 알아보자

먼저, 기존의 소스 코드 먼저 보자. MyBatis ORM Framework로 작성한 구문은 다음과 같다. 

<update id="updateInquiryCount" parameterType="map">
        <![CDATA[
            UPDATE HOME_BOARD_M
               SET INQUIRY_CNT = INQUIRY_CNT + 1
             WHERE BOARD_NO = #{BOARD_NO}
        ]]>
</update>

다음은 Insert 구문으로 바꾼 구문이다.

<insert id="insertInquiryCount" parameterType="map">
    	<![CDATA[
    		INSERT INTO HOME_BOARD_CNT(
    					BOARD_NO,
    					TYPE,
    					INQUIRY_CNT,
    					CREATE_DATE)
    			 VALUES (
    			 		#{BOARD_NO},
    			 		#{TYPE},
    			 		1,
    			 		SYSDATE)
    	]]>
</insert>

생각보다 심플하죠? 너무 기본적인 INSERT 구문이기 때문이다. 여기서 중요한 건 그 이후의 Service 단과 View단에서 조회 시 어떤 데이터 값을 뿌려주냐에 달렸다. 순차적으로 보자.


먼저 기존의 Service 단이다. 게시물 클릭 시 업데이트 동작을 수행함과 동시에 View단으로 뿌려주는 간단한 로직이다.

   /**
     * 게시물 클릭 시 주어진 게시판 번호에 해당하는 게시물을 리턴함.
     */
    @Transactional
    public Map<String, Object> getBoard(String type, String number) {
        //..생략
        
        Map<String, Object> parameter = new HashMap<String, Object>();
		// 파라미터로 넘어온 게시물의 number를 BOARD_NO에 매핑 
        parameter.put("BOARD_NO", number);
        
        commonDao.update("Board.updateInquiryCount", parameter, 1);
        
        Map<String, Object> board = new HashMap<String, Object>();
        
       	//..생략
        board.put("BOARD_CONTENT", ...);

        return board;
   	}

바뀐 Service 단은 다음과 같다. 로직은 같으나, Update를 Insert로 바꿔주고, 카운트 값은 1로 고정이다. 

   /**
     * 게시물 클릭 시 주어진 게시판 번호에 해당하는 게시물을 리턴함.
     */
    @Transactional
    public Map<String, Object> getBoard(String type, String number) {
        //..생략
        
        Map<String, Object> parameter = new HashMap<String, Object>();
		// 파라미터로 넘어온 게시물의 number를 BOARD_NO에 매핑 
        parameter.put("BOARD_NO", number);
        
        // --바뀐 부분--
        commonDao.update("Board.insertInquiryCount", parameter, 1);
        
        Map<String, Object> board = new HashMap<String, Object>();
        
       	//..생략
        board.put("BOARD_CONTENT", ...);

        return board;
   	}
그래서 차이가 뭔데?

이제 게시물을 클릭할 때마다 HOME_BOARD_CNT라는 새로운 테이블에 하나씩 Row가 쌓일 것이다. 원래 로직은 HOME_BOARD_M이라는 본 테이블에 접근하여 INQUIRY_CNT(조회수)라는 칼럼을 UPDATE를 하는 로직이었다. 하지만 본 테이블이 아닌 새로운 테이블에 데이터를 쌓고 INSERT를 하는 구조로 진행되는 방식으로 변경한 것이다. 1로 값은 고정이나. 이를 BOARD_NO 기준으로 SQL의 SUM을 통해 조회수를 뿌려줄 것이다.

예) 게시물을 3번 클릭한 경우
- HOME_BOARD_CNT 테이블 에 3개의 ROW 가 쌓임
- 게시물의 내용은 다음과 같을 것
칼럼명 : BOARD_NO                    TYPE   CNT   UPDATE_DATE
데이터 : BD202305270001           A          1       2023-05-27 오후 9시 03분
데이터 : BD202305270001           A          1       2023-05-27 오후 9시 02분
데이터 : BD202305270001           A          1       2023-05-27 오후 9시 01분

그래서 이 값을 통해 SUM을 진행하고 View단으로 뿌려주기만 하면 된다. 뿌려줄 때는 ORM을 사용하는 경우엔 SELECT 절을 통해 진행할 수 있겠다.

효과는 굉장했다!

Jmeter를 사용해서 임의의 세션 수 / 반복 수 / 시간 간격 등을 설정하고 테스트를 한 결과는 다음과 같다.

 

  • 도구 : Tomcat / Jmeter
  • 환경
    • 로컬 환경 조성
    • Tomcat : Max Thread Pool  30 (운영 WAS에 설정된 Thread Pool )
    • Jmeter : Number of Thread(users)  1000 ,  Ramp-up period  1 , Loop Count  10 ( 1000명의 유저가 1초에 10개씩 요청하는 상황 가정 )
  • 결과
    • #Samples : 10000
    • Elapsed Time : 개선 전 - 3m 42s  --> 개선 후 - 1m 4s  (3배 단축)
    • Throughput :   개선 전 – 45.1/sec --> 개선 후 - 134.6/sec (3배 증가)

똑같은 상황으로 부하 테스트를 진행했을 때, Insert를 통한 방식이 약 3배 정도의 개선 효과를 보았다. 또한, 실제로 조회수 유실도 발생하지 않았으며, 테스트 동작 시 웹 사이트를 접속해보았을 때 응답 속도의 차이도 명확히 느껴졌다. 

 

 결론

UPDATE와 INSERT의 차이를 두기 전에 LOCK에 대해 정말 많이 공부가 필요하다는 것을 알게되었다. 누가봐도 간단한 UPDATE지만 사용자가 많을 때의 상황을 항상 고려해야 할 것이다. 이렇게 실제적인 문제가 발생했고, 이것을 해결하는 과정을 통해 뭔가 조금은 성장한 기분이 든다. '이렇게도 고려할 수 있구나'하는 유연한 사고방식을 지닌 개발자로 성장해야겠다.