본문 바로가기
마루아라는 개발쟁이/ASP

세션을 DB로 관리하기 + 쪽지 확인하기

by 마루아라 이야기 2022. 12. 7.

아래에도 세션을 DB로 관리하는 법을 써주신 분이 계시지만, 대개 Zend에 나와있는 예제와 같은 기본구조를 사용하시는 것 같습니다. 그래서 이걸 한번 커스터마이징해보도록 하겠습니다.

------------------------------
[1] 대개의 경우, 회원 로그인에 세션을 사용합니다.  즉 회원으로 로그인하지 않으면 세션을 사용할 일이 없는 경우가 많습니다. 따라서 여기서는 회원 전용 세션만 관리하도록 하겠습니다.

[2] 세션 테이블을 만들어서, 여기에 매번 insert, delete를 하는 것은 비록 테이블 크기가 작다고 하더라도 제법 부하를 줍니다. 따라서 여기서는 delete를 쓰지 않고 update만 사용하도록 하겠습니다. 회원이 몇만명이라고 할지라도 이게 더 나은 것 같더군요.

세션 테이블을 다음과 같이 만듭니다. 회원목록 테이블과 똑같은 rows를 갖게 되겠죠.
CREATE TABLE `user_sessions` (
  `uid` int(10) unsigned NOT NULL default '0',  /* 회원ID */
  `sess_key` varchar(32) NOT NULL default '', /* 세션키 */
  `last_log` int(11) unsigned NOT NULL default '0', /* 세션을 기록하는 시간 */
  `last_ip` varchar(15) NOT NULL default '', /* 꼭 필요하지는 않지만, 추후 필요성을 느끼게 될 것임 ;-) */
  `sess_value` text NOT NULL, /* 세션값 */
  PRIMARY KEY  (`uid`),
  KEY `sess_key` (`sess_key`)
) TYPE=MyISAM;

세션을 처리하는 스크립트는 다음과 같습니다.
(편의상 DB 클래스를 사용했습니다만, 뭐하는건지는 다 아시겠죠? --;;)

session_set_save_handler("sess_open", "sess_close", "sess_read", "sess_write", "sess_destroy", "sess_gc");
session_start();

function sess_open($save_path, $session_name) {
        return 1;
}

function sess_read($key) {
        $DB =& DB::getInstance();
        $DB->query("SELECT sess_value FROM user_sessions WHERE sess_key = '$key' AND last_log > '".(time()-get_cfg_var("session.gc_maxlifetime"))."' ");
        $row=$DB->get();
        return $row[0];
}

function sess_write($key, $value) {
        $DB =& DB::getInstance();
        $DB->query("UPDATE user_sessions SET last_log='".time()."', last_ip='".$_SERVER["REMOTE_ADDR"]."', sess_value='$value' WHERE sess_key='$key' ");
        return '';
}

function sess_close() {
        return 1;
}

function sess_destroy($key) {
        $DB =& DB::getInstance();
        $DB->query("UPDATE user_sessions SET sess_key='', last_log='".time()."', last_ip='".$_SERVER["REMOTE_ADDR"]."' WHERE sess_key='$key' ");
}

function sess_gc($lifetime) {
        return 1;
}

[3] 회원 로그인하는 부분에서 sess_key 값을 할당합니다. 그 다음부터 그 회원은 세션 테이블에서 데이타를 읽고 쓸 수 있습니다.
비회원인 경우는 session_start()를 하더라도 세션키값만 주어질 뿐, 여러가지 $_SESSION값을 사용할 수 없습니다. (물론 쿠키값을 이용해 비회원의 경우는 아예 session_start()를 실행하지 않는 방법도 있죠. ^^;;)

[4] 이렇게 함으로써 굳이 가비지 콜렉션(update 혹은 delete)을 하지 않아도 됩니다. 또 회원의 마지막 접속시간이 최신으로 유지됩니다.

[5] 회원의 중복 로그인이 방지됩니다. 다만, 같은 위치에서 다른 세션을 다시 시작하는 경우인지, 이미 로그인된 상태인지 등의 예외적 상황을 처리하기 위해 로그인을 처리하는 부분을 보강해야겠죠. ;-)

------------------------------
자, 이쯤에서 한가지 욕심을 내어볼까요? 이른바, 쪽지 기능을 추가해보겠습니다. (실시간 쪽지 기능 아닙니다. --;;)
제로보드인가? 쪽지를 받으면 "딩동, 새로운 메시지가 도착했습니다" 하는 소리가 나쟎아요.
받은 쪽지 테이블을 따로 만들면, 매번 해당 테이블을 읽어야 하지 않습니까. 뭐 굳이 2번 작업을 하냐, 세션을 시작하면서 자동적으로 그 데이타를 읽어오자 하는거지요.

`user_sessions` 테이블에 chk_msg 라는 필드를 하나 추가합니다.
ALTER TABLE `user_sessions` ADD `chk_msg` TINYINT(2) UNSIGNED NOT NULL AFTER `last_ip` ;
다른 회원이 쪽지를 보낼때, chk_msg 값을 하나씩 증가시키고, 쪽지를 확인하면 chk_msg 값을 0로 update하려는 것입니다.

sess_read 함수를 변형시켜서, chk_msg 필드값을 읽어오도록 합니다.
function sess_read($key) {
        $DB =& DB::getInstance();
        $DB->query("SELECT uid, chk_msg, sess_value FROM user_sessions WHERE sess_key = '$key' AND last_log > '".(time()-get_cfg_var("session.gc_maxlifetime"))."' ");
        $row=$DB->get();
        $GLOBALS["USER"]["id"]=$row[0];
        $GLOBALS["USER"]["chk_msg"]=$row[1];
        return $row[2];
}

여기서는 $USER라는 변수에 회원ID, 받은쪽지 갯수를 저장하도록 했습니다. 즉 회원ID를 알기 위해 $_SESSION["id"]를 쓰지 않고 $USER["id"]를 쓸 수 있다는 거지요. 받은 쪽지 갯수는 $USER["chk_msg"]에 들어가니까 매번 이 값을 검사해서 "딩동~ 메시지가 도착했습니다" 라고 알려주면 됩니다.

"뭐야, 난데없이 $USER 라는 글로벌 오브젝트라니.... 난 이런거 싫어" 하시는 분.
여기서 $USER와 같은 변수를 할당하지 않고, $_SESSION을 사용해도 됩니다. $USER["chk_msg"]="1" 대신에 $_SESSION["chk_msg"] = "1" 이라고 써도 된다는 겁니다. 세션함수 안에서 $_SESSION을 사용한다니, 황당한 트릭이죠? 번거롭게 세션값($row[2])을 직접 파싱할 필요가 없습니다.
다만, 이것은 맨처음 1번만 적용됩니다. 다음번에 $_SESSION["chk_msg"] = "2" 라고 하더라도, 이미 세션 데이타 안에 "chk_msg"값이 1로 들어있기 때문에, 결과는 "1"로 나옵니다. 따라서 $_SESSON["chk_msg"] 값을 확인해서 "딩동~" 하는 메시지를 보여주거나 말거나 한 후에, 매번 unset($_SESSION["chk_msg"])를 해줘야하겠죠?


------------------------------

재미있으셨나요?
"한번 더 생각하고 DB를 만들자"는 취지에서 한번 써봤고, 나름대로 효율적이라고 생각하는데... 장담은 못하겠네요. 오류나 정정사항은 리플 부탁드립니다.

전영규님 말데로 가비지컬렉션 부분은 빼는것보다는 있어야 할 것 같습니다.
말 그대로 쓰레기값들 처리하는 것이니까요...
지금 소스를 자세히 보지는 않았지만 아마도 gc 부분을 뺌으로서 delete 쿼리를 줄여준것 같은데 나중에는 데이터가 많이 쌓이면 여기서 쿼리 한번 줄여준 시간을 손해(?)볼 수도 있을것 같네요...

 

To. 전영규//
gc는 선택사항이죠.
user_session 테이블에다 사용자명, 등급 따위까지 다 넣어버리면 (즉 일정부분 user 테이블을 대체해버리면) 별도의 세션값을 사용할 일이 적어집니다. 그리고 대개의 경우, 일정한 세션값을 사용하기 때문에 세션 데이타가 누적되어서 테이블이 무거워지지는 않을 겁니다. 세션에는 영구보관해둘 사용자별 환경설정 같은 걸 넣어둘 수도 있겠죠.
물론 이런 경우가 아니라 쇼핑몰처럼 세션 데이타가 많고 내용이 일정하지 않게 변동되고 누적되는 경우라면 gc가 필요하겠죠.

To. TarauS //
gc를 뺌으로써 delete 쿼리를 줄였다는게 초점이 아니구요. gc와 상관없이 insert, delete를 뺐다는 거랍니다. --;; 쿼리 시간을 단축시키고, 테이블 optimize를 덜 해줘도 된다는 거지요.

728x90
반응형
LIST