Drupal의 새로운 게시물 표시 기간 분석

위키 | 한국 데비안 사용자 모임
이동: 둘러보기, 검색

80x15.png Westporch에 의해 작성된 Drupal의 새로운 게시물 표시 기간 분석크리에이티브 커먼즈 저작자표시-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.

서론

Drupal이란

Drupal(드루팔)은 php로 작성된 CMS(Content Management Software) 이며 오픈소스 소프트웨어입니다. [1] 드루팔을 이용하면 웹사이트를 쉽게 만들 수 있습니다.

테스트 환경

가상머신(192.168.0.7)에 드루팔(v8.2.7)을 설치하여 테스트를 진행했습니다.

버전 정보
OS(Debian) drupal Nginx php MariaDB
v9.4 v8.2.8 v1.10.3 v7.0.27 v15.1
드루팔 계정 정보
ID uid(user id) 구분
westporch 1 관리자
seo 2 일반 사용자

분석 배경 및 목적

드루팔은 로그인한 사용자가 어떠한 게시물을 읽었는지 확인하기 위해서 history라는 모듈을 사용합니다. 이 모듈은 로그인한 사용자가 게시물에 접근한 시각을 드루팔이 설치된 데이터베이스의 history 테이블에 타임스탬프로 저장합니다. history 테이블은 uid, nid, timestamp로 구성됩니다.

MariaDB [drupal_test]> desc history;
+-----------+------------------+------+-----+---------+-------+
| Field     | Type             | Null | Key | Default | Extra |
+-----------+------------------+------+-----+---------+-------+
| uid       | int(11)          | NO   | PRI | 0       |       |
| nid       | int(10) unsigned | NO   | PRI | 0       |       |
| timestamp | int(11)          | NO   |     | 0       |       |
+-----------+------------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

uid는 user id를 의미합니다. 관리자의 uid는 1이며 이후 가입한 순서대로 uid가 1씩 증가합니다. nid는 node id입니다. 게시물(node)을 작성한 순서대로 node id가 1씩 증가합니다. timestamp 필드에는 사용자가 게시물을 읽은 시각을 타임스탬프로 저장합니다.

현재는 작성한 게시물이 없으므로 아래와 같이 history 테이블은 비어있습니다.

MariaDB [drupal_test]> select * from history;
Empty set (0.00 sec)

관리자(계정명: westporch, uid: 1) 권한으로 게시물(http://192.168.0.7/node/1) 을 작성한 후 다시 history 테이블을 확인해 보겠습니다. 아래와 같이 관리자(uid: 1)가 게시물(nid: 1)을 작성했으며 이 게시물을 마지막으로 읽은 시각의 타임스탬프가 1526778791임을 알 수 있습니다.

MariaDB [drupal_test]> select * from history; 
+-----+-----+------------+
| uid | nid | timestamp  |
+-----+-----+------------+
|   1 |   1 | 1526778791 |
+-----+-----+------------+
1 row in set (0.00 sec)

일반 사용자(계정명: seo, uid: 2)도 있지만 history 테이블에는 uid가 2인 투플은 존재하지 않습니다. 왜냐하면 해당 사용자가 해당 게시물(nid: 1)을 아직 읽지 않았기 때문입니다. http://192.168.0.7 에서 seo 계정으로 로그인한 후 포럼 목록을 확인하면 아래 화면처럼 '1 new post' 메시지가 보입니다.

1-1 one new post.png

'General discussion'을 클릭하면 아래처럼 관리자(westporch)가 작성한 글을 확인할 수 있습니다. 새로운 글이라고 말풍선 위에 별이 붙어있습니다.

1-2 new forum topic icon.png

글 제목 Test-01을 클릭해서 게시물의 내용을 확인했습니다.

1-3 post read.png


아래처럼 history 테이블에 timestamp가 기록됩니다.

MariaDB [drupal_test]> select * from history;
+-----+-----+------------+
| uid | nid | timestamp  |
+-----+-----+------------+
|   1 |   1 | 1526779523 |
|   2 |   1 | 1526780309 |
+-----+-----+------------+
2 rows in set (0.00 sec)

게시물을 다시 읽었는데도 여전히 아래 화면처럼 새로운 게시물로 표시됩니다.

1-1 one new post.png

하지만 history 테이블에서는 timestamp가 갱신되었습니다.

MariaDB [drupal_test]> select * from history;
+-----+-----+------------+
| uid | nid | timestamp  |
+-----+-----+------------+
|   1 |   1 | 1526779523 |
|   2 |   1 | 1526793759 |
+-----+-----+------------+
2 rows in set (0.00 sec)

일반 사용자 계정이 아닌 관리자 계정에서는 위와 같은 상황이 발생하지 않습니다. 즉 새로운 게시물을 읽으면 더 이상 새로운 게시물로 표시되지 않습니다. 일반 사용자 계정에서는 왜 이러한 일이 발생하는지 다음 항목에서 살펴보도록 하겠습니다.

분석 과정

ForumManagerInterface.php

ForumManagerInterface.php의 주석에 HISTORY_READ_LIMIT 보다 새로운 것은 새로운 노드(게시물)이라고 설명하고 있습니다. unreadTopics()는 core/modules/forum/src/ForumManager.php에 정의되어 있습니다.

 1 /**
 2    * Calculates the number of new posts in a forum that the user has not yet read.
 3    *
 4    * Nodes are new if they are newer than HISTORY_READ_LIMIT.
 5    *
 6    * @param int $term
 7    *   The term ID of the forum.
 8    * @param int $uid
 9    *   The user ID.
10    *
11    * @return
12    *   The number of new posts in the forum that have not been read by the user.
13    */
14   public function unreadTopics($term, $uid);

ForumManager.php

core/modules/forum/src/ForumManager.php의 unreadTopics()는 아래와 같이 정의되어 있습니다. ForumManager.php를 살펴보았으나 HISTORY_READ_LIMIT에 대한 내용은 찾을 수 없습니다. HISTORY_READ_LIMIT은 core/modules/history/history.module 에 정의되어 있습니다.

 1 (.. 생략 ..)
 2   /**
 3    * {@inheritdoc}
 4    */
 5   public function unreadTopics($term, $uid) {
 6     $query = $this->connection->select('node_field_data', 'n');
 7     $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $term));
 8     $query->leftJoin('history', 'h', 'n.nid = h.nid AND h.uid = :uid', array(':uid' => $uid));
 9     $query->addExpression('COUNT(n.nid)', 'count');
10     return $query
11       ->condition('status', 1)
12       // @todo This should be actually filtering on the desired node status
13       //   field language and just fall back to the default language.
14       ->condition('n.default_langcode', 1)
15       ->condition('n.created', HISTORY_READ_LIMIT, '>')
16       ->isNull('h.nid')
17       ->addTag('node_access')
18       ->execute()
19       ->fetchField();
20   }
21 (.. 생략 ..)

7번째 줄에서 node_field_data 테이블과 forum 테이블에 대해 조인 연산을 수행합니다. 8번째 줄에서는 history 테이블과 왼쪽 외부 조인을 수행합니다.

node_field_data 테이블

node_field_data 테이블의 구조는 아래와 같습니다. nid는 기본키(primary key)로 설정되었습니다.

MariaDB [drupal_test]> desc node_field_data;
+-------------------------------+------------------+------+-----+---------+-------+
| Field                         | Type             | Null | Key | Default | Extra |
+-------------------------------+------------------+------+-----+---------+-------+
| nid                           | int(10) unsigned | NO   | PRI | NULL    |       |
| vid                           | int(10) unsigned | NO   | MUL | NULL    |       |
| type                          | varchar(32)      | NO   | MUL | NULL    |       |
| langcode                      | varchar(12)      | NO   | PRI | NULL    |       |
| title                         | varchar(255)     | NO   | MUL | NULL    |       |
| uid                           | int(10) unsigned | NO   | MUL | NULL    |       |
| status                        | tinyint(4)       | NO   | MUL | NULL    |       |
| created                       | int(11)          | NO   | MUL | NULL    |       |
| changed                       | int(11)          | NO   | MUL | NULL    |       |
| promote                       | tinyint(4)       | NO   | MUL | NULL    |       |
| sticky                        | tinyint(4)       | NO   |     | NULL    |       |
| revision_translation_affected | tinyint(4)       | YES  |     | NULL    |       |
| default_langcode              | tinyint(4)       | NO   |     | NULL    |       |
+-------------------------------+------------------+------+-----+---------+-------+
13 rows in set (0.00 sec)

다음은 node_field_data 테이블의 모든 내용을 출력한 모습니다.

MariaDB [drupal_test]> select * from node_field_data;
+-----+-----+-------+----------+---------+-----+--------+------------+------------+---------+--------+-------------------------------+------------------+
| nid | vid | type  | langcode | title   | uid | status | created    | changed    | promote | sticky | revision_translation_affected | default_langcode |
+-----+-----+-------+----------+---------+-----+--------+------------+------------+---------+--------+-------------------------------+------------------+
|   1 |   1 | forum | en       | Test-01 |   1 |      1 | 1526778710 | 1526778790 |       0 |      0 |                             1 |                1 |
+-----+-----+-------+----------+---------+-----+--------+------------+------------+---------+--------+-------------------------------+------------------+
1 row in set (0.00 sec)

forum 테이블

다음은 forum 테이블의 구조입니다. vid가 기본키로 설정되었습니다. vid는 node revision id[2]를 의미합니다. 즉 게시물의 변경 내역을 나타낸 버전을 의미합니다.

MariaDB [drupal_test]> desc forum;
+-------+------------------+------+-----+---------+-------+
| Field | Type             | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| nid   | int(10) unsigned | NO   | MUL | 0       |       |
| vid   | int(10) unsigned | NO   | PRI | 0       |       |
| tid   | int(10) unsigned | NO   | MUL | 0       |       |
+-------+------------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

다음은 forum 테이블의 모든 내용을 출력한 모습니다.

MariaDB [drupal_test]> select * from forum;
+-----+-----+-----+
| nid | vid | tid |
+-----+-----+-----+
|   1 |   1 |   1 |
+-----+-----+-----+
1 row in set (0.00 sec)

history.module 분석

HISTORY_READ_LIMIT 상수

HISTORY_READ_LIMIT은 core/modules/history/history.module 에 정의되어 있습니다. history.module은 php 코드입니다. 8 번째 줄에서 HISTORY_READ_LIMIT은 REQUEST_TIME - 30 * 24 * 60 * 60로 정의되었음을 알 수 있습니다. 30 * 24 * 60 * 60은 30일을 초로 나타낸 것입니다. 30일은 2,592,000초입니다. (30일 * 24시간 * 60분 * 60초)

1 (..생략..)
2 /**
3  * Entities changed before this time are always shown as read.
4  *
5  * Entities changed within this time may be marked as new, updated, or read,
6  * depending on their state for the current user. Defaults to 30 days ago.
7  */
8 define('HISTORY_READ_LIMIT', REQUEST_TIME - 30 * 24 * 60 * 60);
9 (..생략..)

드루팔 API 문서를 살펴보면 REQUEST_TIME은 현재 요청한 시각을 타임스탬프로 나타낸 값[3]이라고 합니다. core/modules/user/user.module 파일에서도 아래와 같은 주석을 확인할 수 있습니다.

1 (..생략..)
2 /**
3 @param int $timestamp
4  *   A UNIX timestamp, typically REQUEST_TIME.
5  */
6 (..생략..)

history_read 함수

core/modules/history/history.module 파일에서 history_read 함수는 내부에서 history_read_multiple 함수를 호출합니다.

 1  /**
 2  * Retrieves the timestamp for the current user's last view of a specified node.
 3  *
 4  * @param int $nid
 5  *   A node ID.
 6  *
 7  * @return int
 8  *   If a node has been previously viewed by the user, the timestamp in seconds
 9  *   of when the last view occurred; otherwise, zero.
10  */
11 function history_read($nid) {
12   $history = history_read_multiple(array($nid));
13   return $history[$nid];
14 }

history_read_multiple 함수

core/modules/history/history.module의 history_read_multiple 함수는 drupal_static()을 통해 드루팔에서 static cache를 사용합니다. 처음에 페이지를 요청할 때는 데이터베이스에 쿼리를 전송한 후 결과를 메모리에 저장합니다. 이후 동일한 요청이 올 경우에는 데이터베이스에 쿼리를 전송하지 않고 메모리에서 결과 값을 가져옵니다.[4] 이렇게 함으로써 데이터베이스가 저장된 디스크에 접근하는 횟수를 조금이라도 줄여서 오버헤드를 줄일 수 있습니다.

한편으로는 'static cache 기능을 꺼야하지 않을까?'라고 생각할 수도 있습니다. 하지만 개발 서버에서는 static cache 기능을 중지해도 문제되지 않지만 실제 운영 서버에서는 성능을 향상시키기 위해서 static cache 기능을 사용하는 것이 좋습니다. [5]

 1 /**
 2  * Retrieves the last viewed timestamp for each of the passed node IDs.
 3  *
 4  * @param array $nids
 5  *   An array of node IDs.
 6  *
 7  * @return array
 8  *   Array of timestamps keyed by node ID. If a node has been previously viewed
 9  *   by the user, the timestamp in seconds of when the last view occurred;
10  *   otherwise, zero.
11  */
12 function history_read_multiple($nids) {
13   $history = &drupal_static(__FUNCTION__, array());
14 
15   $return = array();
16 
17   $nodes_to_read = array();
18   foreach ($nids as $nid) {
19     if (isset($history[$nid])) {
20       $return[$nid] = $history[$nid];
21     }
22     else {
23       // Initialize value if current user has not viewed the node.
24       $nodes_to_read[$nid] = 0;
25     }
26   }
27 
28   if (empty($nodes_to_read)) {
29     return $return;
30   }
31 
32   $result = db_query('SELECT nid, timestamp FROM {history} WHERE uid = :uid AND nid IN ( :nids[] )', array(
33     ':uid' => \Drupal::currentUser()->id(),
34     ':nids[]' => array_keys($nodes_to_read),
35   ));
36   foreach ($result as $row) {
37     $nodes_to_read[$row->nid] = (int) $row->timestamp;
38   }
39   $history += $nodes_to_read;
40 
41   return $return + $nodes_to_read;
42 }

history_write 함수

드루팔의 포럼에 대한 동작을 고려하면 history_write 함수는 사용자가 게시물에 대한 변경 작업을 수행했을 때 호출되는 것으로 보입니다.

 1 /**
 2  * Updates 'last viewed' timestamp of the specified entity for the current user.
 3  *
 4  * @param $nid
 5  *   The node ID that has been read.
 6  * @param $account
 7  *   (optional) The user account to update the history for. Defaults to the
 8  *   current user.
 9  */
10 function history_write($nid, $account = NULL) {
11 
12   if (!isset($account)) {
13     $account = \Drupal::currentUser();
14   }
15 
16   if ($account->isAuthenticated()) {
17     db_merge('history')
18       ->keys(array(
19         'uid' => $account->id(),
20         'nid' => $nid,
21       ))
22       ->fields(array('timestamp' => REQUEST_TIME))
23       ->execute();
24     // Update static cache.
25     $history = &drupal_static('history_read_multiple', array());
26     $history[$nid] = REQUEST_TIME;
27   }
28 }

history_cron 함수

history 테이블에서 timestamp < HISTORY_READ_LIMIT인 투플을 삭제합니다. 왜냐하면 timestamp < HISTORY_READ_LIMIT인 조건에서는 새로운 게시물로 표시할 필요가 없기 때문입니다. timestamp에는 사용자가 마지막으로 게시물을 접근한 시각이 타임스탬프로 저장되어 있습니다. 만약 timestamp < HISTORY_READ_LIMIT라면 사용자가 30일보다 더 오래 전에 게시물을 읽었음을 의미합니다.

1 /**
2  * Implements hook_cron().
3  */
4 function history_cron() {
5   db_delete('history')
6     ->condition('timestamp', HISTORY_READ_LIMIT, '<')
7     ->execute();
8 }

node_mark 함수

core/modules/node/node.module의 node_mark 함수에서 16~28번째 줄이 핵심입니다.

 1 /**
 2  * Determines the type of marker to be displayed for a given node.
 3  *
 4  * @param int $nid
 5  *   Node ID whose history supplies the "last viewed" timestamp.
 6  * @param int $timestamp
 7  *   Time which is compared against node's "last viewed" timestamp.
 8  *
 9  * @return int
10  *   One of the MARK constants.
11  */
12 function node_mark($nid, $timestamp) {
13 
14   $cache = &drupal_static(__FUNCTION__, array());
15 
16   if (\Drupal::currentUser()->isAnonymous() || !\Drupal::moduleHandler()->moduleExists('history')) {
17     return MARK_READ;
18   }
19   if (!isset($cache[$nid])) {	
20     $cache[$nid] = history_read($nid);	
21   }
22   if ($cache[$nid] == 0 && $timestamp > HISTORY_READ_LIMIT) {
23     return MARK_NEW;
24   }
25   elseif ($timestamp > $cache[$nid] && $timestamp > HISTORY_READ_LIMIT) {
26     return MARK_UPDATED;
27   }
28   return MARK_READ;
29 }

16~18번째 줄

사용자가 로그인하지 않았거나 history 모듈이 설치[6]되지 않았을 경우에는 게시물을 읽음(MARK_READ)로 표시합니다. 로그인한 사용자에 한해 게시물을 읽은 시각을 history 테이블에 timestamp로 저장하므로 로그인을 하지 않은 사용자에게는 게시물이 MARK_READ로 표시합니다.

19~21번째 줄

$cache[$nid]에 값이 설정되지 않은 경우에 가장 최근에 게시물을 읽었던 타임스탬프를 반환합니다. 드루팔 캐시에서 해당 게시물의 타임스탬프를 가져올 수 없다면 history_read($nid)의 리턴값은 0이 됩니다. 초기에 사용자가 게시물에 대한 직접적인 갱신 작업을 수행하지 않으면 기본적으로 $cache[$nid] == 0을 설정합니다. 또한 사용자가 해당 게시물 자체를 아예 읽지 않았다면 데이터베이스의 history 테이블에 관련 기록이 저장되지 않았으므로 history_read($nid)의 리턴값은 0입니다. 하지만 사용자가 게시물을 읽었음에도 불구하고 여전히 새로운 게시물로 표시되는 이유는 드루팔이 static cache를 사용하기 때문입니다.

로그인한 사용자가 게시물을 읽으면 데이터베이스의 history 테이블에는 timestamp가 갱신됩니다. 하지만 드루팔 캐시에는 사용자가 게시물을 읽은 timestamp 값이 곧바로 반영되지 않습니다. 앞서 설명한 것처럼 드루팔은 static cache를 사용하기 때문에 동일한 페이지를 요청하면 데이터베이스에 새로운 쿼리를 전송하지 않고 드루팔 캐시에 저장된 timestamp를 가져옵니다.

22~24번째 줄

19~24번째 줄에서 드루팔의 특성이 느껴집니다. static cache를 사용함으로써 로그인한 사용자가 단순히 게시물을 읽을 때는 DB에 쿼리를 전송하지 않고 캐시에 저장된 값을 가져다가 사용합니다. 이렇게 함으로써 성능을 향상시킬 수 있습니다. 앞서 설명한 것 처럼 초기에 사용자가 게시물에 대한 직접적인 갱신 작업을 수행하지 않으면 기본적으로 $cache[$nid] == 0을 설정합니다. 따라서 초기에 작성한 글을 보기만 하고 30일이 지나지 않은 글들은 기본적으로 새로운 게시물로 보여준다는 것을 알 수 있습니다.

$timestamp는 게시물을 읽은 마지막 시각입니다. 만약 $cache[$nid] == 0인 상황에서 $timestamp > HISTORY_READ_LIMIT 조건이 성립한다면 30일 이전의 타임스탬프 값(HISTORY_READ_LIMIT) 보다 큰 $timestamp의 값을 가진 게시물은 'new' 상태로 표시합니다. 따라서 30일 이후에는 $timestamp > HISTORY_READ_LIMIT 조건이 성립하지 않으므로 게시물의 상태를 더 이상 'new'로 표시하지 않습니다. 참고로 앞에서 설명한 history_cron 함수는 데이터베이스의 history 테이블에서 timestamp < HISTORY_READ_LIMIT인 투플을 삭제합니다

특정 이벤트가 발생했을 때 드루팔에 저장된 timestamp(사용자가 게시물을 읽은 시각)가 갱신됩니다. 이벤트는 사용자가 포럼에 새로운 글을 작성하거나 새로운 게시물로 표시된 포스트에 댓글을 작성했을 때를 의미합니다. 즉 게시물에 대한 갱신 작업이 발생해야 history_write 함수 호출 및 데이터베이스에 새로운 쿼리를 전송하기 때문에 드루팔 캐시에 저장된 timestamp 값도 갱신됩니다. 따라서 더 이상 게시물의 상태가 'new'로 표시되지 않습니다.

사용자가 새로운 게시물을 이미 읽었고 관리자 페이지에서 관리자가 드루팔의 모든 캐시를 비울 경우에도 더 이상 게시물의 상태가 'new'로 표시되지 않습니다. 왜냐하면 22번째 줄의 $cache[$nid] == 0 && $timestamp > HISTORY_READ_LIMIT 조건에서 $cache[$nid]의 값은 0이 아니라 실제 타임스탬프가 저장되기 때문에 조건식은 거짓이 되기 때문입니다.

HistoryController.php

core/modules/history/src/Controller/HistoryController.php의 readNode 함수는 사용자가 게시물을 읽었을 때 history 테이블의 timestamp 값을 업데이트합니다.

 1 /**
 2    * Marks a node as read by the current user right now.
 3    *
 4    * @param \Symfony\Component\HttpFoundation\Request $request
 5    *   The request of the page.
 6    * @param \Drupal\node\NodeInterface $node
 7    *   The node whose "last read" timestamp should be updated.
 8    */
 9   public function readNode(Request $request, NodeInterface $node) {
10     if ($this->currentUser()->isAnonymous()) {
11       throw new AccessDeniedHttpException();
12     }
13 
14     // Update the history table, stating that this user viewed this node.
15     history_write($node->id());
16 
17     return new JsonResponse((int)history_read($node->id()));
18   }

js_5NueHwEFU0msGYNoi3a_HJKlzseL7ZuA35_3UWhiNNI.js

sites/default/files/js/js_5NueHwEFU0msGYNoi3a_HJKlzseL7ZuA35_3UWhiNNI.js 파일에 아래와 같이 30일 이후의 게시물의 상태는 new 또는 updated로 표시하지 않는다고 합니다. 이 파일을 일찍 확인했으면 좋았을 텐데 파일명이 독특해서 무심코 지나쳤었습니다.

 1 (.. 생략 ..)
 2 /**
 3      * Determines whether a server check is necessary.
 4      *
 5      * Any content that is >30 days old never gets a "new" or "updated"
 6      * indicator. Any content that was published before the oldest known reading
 7      * also never gets a "new" or "updated" indicator, because it must've been
 8      * read already.
 9      *
10      * @param {number|string} nodeID
11      *   A node ID.
12      * @param {number} contentTimestamp
13      *   The time at which some content (e.g. a comment) was published.
14      *
15      * @return {bool}
16      *   Whether a server check is necessary for the given node and its
17      *   timestamp.
18      */
19     needsServerCheck: function (nodeID, contentTimestamp) {
20       // First check if the content is older than 30 days, then we can bail
21       // early.
22       if (contentTimestamp < thirtyDaysAgo) {
23         return false;
24       }
25 
26       // Use the data embedded in the page, if available.
27       if (embeddedLastReadTimestamps && embeddedLastReadTimestamps[nodeID]) {
28         return contentTimestamp > parseInt(embeddedLastReadTimestamps[nodeID], 10);
29       }
30 
31       var minLastReadTimestamp = parseInt(storage.getItem('Drupal.history.' + currentUserID + '.' + nodeID) || 0, 10);
32       return contentTimestamp > minLastReadTimestamp;
33     }
34 (.. 생략 ..)

결론

일반 사용자 계정으로 로그인한 상태에서 게시물을 읽었음에도 불구하고 새로운 게시물로 표시되는 것은 해당 게시물에 대한 직접적인 갱신이 발생하기 않았기 때문입니다. 드루팔은 static cache를 사용합니다. 따라서 단순히 새로운 게시물을 보는 것만으로는 데이터베이스에 쿼리를 전송하지 않습니다. 새로운 게시물로 표시된 글(포럼 토픽)을 이미 읽었어도 데이터베이스에 있는 데이터를 가져오는 것이 아니라 캐시에 있는 예전의 데이터를 읽어옵니다. 그러므로 여전히 새로운 게시물로 보이는 것 입니다.

만약 사용자가 게시물에 댓글을 작성하면 데이터베이스에 쿼리를 전송하기 때문에 새로운 페이지 정보를 가져옵니다. 따라서 새로운 게시물이란 표시는 더 이상 나타나지 않습니다. 또한 30일이 지난 게시물은 더 이상 새로운 게시물로 표시되지 않습니다.

캐시를 사용하는 게 오히려 단점이라고 생각할 수 있지만 캐시를 사용함으로써 보조기억장치의 접근 횟수를 줄일 수 있고 결과적으로 오버헤드를 줄일 수 있습니다. 캐시는 드루팔에서만 사용하는 개념이 아닙니다. 캐시 서버를 구축하기 위해 Squid, Nginx등을 사용할 수도 있으며 우리가 사용하는 CPU 내부에도 캐시 메모리가 존재합니다.

드루팔이 static cache를 사용하는 것은 드루팔의 특성입니다. 게시물 자체를 이미 읽었는데도 여전히 새로운 게시물이라고 표시되는 것은 일반적인 다른 게시판과는 다르기 때문에 이상하다고 생각할 수도 있습니다. 하지만 이상한 것이 아닙니다. 드루팔의 게시판(포럼)은 일반적인 게시판과는 특성이 다릅니다. 예를 들어 드루팔로 운영 중인 게시판 목록에서 페이지의 맨 끝에 있는 글에 댓글을 작성하면 게시물이 첫 페이지의 상단으로 이동합니다. 즉 토론에 최적화된 게시판입니다. 새로 작성된 글을 갱신하지 않고 읽기만 할 경우 30일 동안 새로운 게시물로 보여주겠다는 것이 드루팔의 정책입니다.

얼핏 생각하면 history.module 파일에서 HISTORY_READ_LIMIT 상수의 값을 변경하면 새로운 게시물이란 알림 기간을 변경할 수 있어보입니다. 하지만 자바스크립트 파일 js_5NueHwEFU0msGYNoi3a_HJKlzseL7ZuA35_3UWhiNNI.js에서는 변수 이름으로 thirtyDaysAgo를 사용하고 있습니다. HISTORY_READ_LIMIT 상수의 값 변경 만으로는 알림 기간을 변경하는 것은 힘들지 않을까 싶습니다. 예를 들어 새로운 게시물 알림 기간을 일주일로 설정하여 이 기능이 동작한다고 해도 프로그램의 전체 구조가 파괴되어 나중에는 유지보수가 어려워질 것이라고 생각합니다.

마치며

나름대로 분석한다고 했는데 잘못된 내용이 있을 수도 있습니다. 만약 정확하지 않은 부분이 있다면 westporch@debianusers.or.kr으로 메일을 보내주시거나 게시판에 글을 남겨주시면 감사하겠습니다. 아울러 이 글이 도움이 되었다면 따뜻한 격려의 말 한마디 부탁드립니다.

각주