캐시 환경에서 AJAX 댓글(코멘트) 분리로 서버 응답시간 속도 향상
- 13:09
- 16 회
- 0 건
기존 로그인 사용자 페이지 처리 방식
워드프레스 기본 구조에서는 로그인 사용자가 페이지를 요청하면 캐시가 적용되지 않는다. 요청이 들어오면 서버는 로그인 상태를 확인하고, 사용자 정보를 포함한 HTML을 매번 새로 생성한다. 이 과정에서 댓글 폼 생성, 사용자 정보 조회, 댓글 리스트 렌더링까지 함께 처리된다.
입력: 페이지 요청
조건: 로그인 사용자
처리: PHP에서 HTML 전체 생성
결과: 동적 페이지 응답
처음에는 이 방식이 당연하다고 생각했다. 사용자 상태가 다르기 때문에 HTML을 공유하면 안 된다고 판단했기 때문이다. 하지만 실제 운영에서는 로그인 상태에서 서버 응답 시간이 2초 이상으로 늘어나는 구간이 반복적으로 발생했다. 캐시를 사용하지 않는 요청이 누적되면서 서버 처리 시간이 길어지는 구조였다.
댓글 기능이 서버 응답시간에 영향을 주는 구조
댓글 기능이 포함된 페이지에서는 서버가 처리해야 할 작업이 더 많아진다. 댓글 폼은 로그인 상태에 따라 다르게 생성되고, 댓글 리스트도 함께 렌더링된다. 이 과정이 페이지 생성 단계에 포함되면서 응답 시간이 증가한다.
입력: 댓글 활성화 페이지 요청
조건: 사용자 상태 포함
처리: 댓글 관련 HTML 생성
결과: 서버 처리 시간 증가
처음에는 댓글 영역만 별도로 처리하려고 했다. 일부 영역만 비캐시 처리하는 방식도 시도했지만, 서버 설정과 CDN 환경에 따라 결과가 달라졌다. 이 방식으로 통일하려 했지만 일부 상황에서는 다시 나눠서 처리해야 했고, 결국 댓글 기능 자체를 분리하는 방향으로 변경했다.
AJAX 댓글 분리로 처리 흐름 변경
댓글 기능을 AJAX로 분리하면서 페이지 HTML과 사용자 상태를 분리했다. 페이지는 항상 동일한 HTML을 반환하고, 사용자 정보와 댓글 처리만 별도의 요청으로 수행한다.
<?php
/*
Plugin Name: Kilho's Ajax Comment
Plugin URI: http://www.kilho.net/
Description: 길호넷에 사용되는 Ajax 댓글 플러그인입니다.
Author: Kilho, Oh
Version: 0.9
Author URI: http://www.kilho.net/
*/
defined( 'ABSPATH' ) || exit;
define( 'KH_COMMENT_VER', '0.9' );
define( 'KH_COMMENT_URL', plugin_dir_url( __FILE__ ) );
class KH_Ajax_Comment {
private $saved_user = null;
public function __construct() {
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
add_action( 'wp_ajax_kh_submit_comment', array( $this, 'handle_submit' ) );
add_action( 'wp_ajax_nopriv_kh_submit_comment', array( $this, 'handle_submit' ) );
add_action( 'wp_ajax_kh_comment_session', array( $this, 'ajax_session' ) );
add_action( 'wp_ajax_nopriv_kh_comment_session', array( $this, 'ajax_session' ) );
add_action( 'comment_form_before', array( $this, 'spoof_anonymous' ) );
add_action( 'comment_form_after', array( $this, 'restore_user' ) );
add_filter( 'pre_comment_approved', array( $this, 'block_user_email_impersonation' ), 10, 2 );
}
public function block_user_email_impersonation( $approved, $commentdata ) {
if ( ! empty( $commentdata['user_id'] ) ) {
return $approved;
}
if ( empty( $commentdata['comment_author_email'] ) ) {
return $approved;
}
if ( get_user_by( 'email', $commentdata['comment_author_email'] ) ) {
return 0;
}
return $approved;
}
public function spoof_anonymous() {
if ( ! is_user_logged_in() ) {
return;
}
global $current_user;
$this->saved_user = $current_user;
$current_user = new WP_User( 0 );
}
public function restore_user() {
if ( null === $this->saved_user ) {
return;
}
global $current_user;
$current_user = $this->saved_user;
$this->saved_user = null;
}
public function ajax_session() {
nocache_headers();
$logged_in = is_user_logged_in();
$response = array(
'logged_in' => $logged_in,
'nonce' => wp_create_nonce( 'kh_comment_nonce' ),
'name' => '',
'email' => '',
'url' => '',
);
if ( $logged_in ) {
$user = wp_get_current_user();
$response['name'] = $user->display_name ? $user->display_name : $user->user_login;
$response['email'] = $user->user_email;
$response['url'] = $user->user_url;
$response['logged_in_as_html'] = sprintf(
'<p class="logged-in-as">%s(으)로 로그인 됨. (* 질문, 건의사항 등은 "질문게시판"을 이용해주세요)</p>',
esc_html( $response['name'] )
);
} else {
$commenter = wp_get_current_commenter();
$response['name'] = $commenter['comment_author'];
$response['email'] = $commenter['comment_author_email'];
$response['url'] = $commenter['comment_author_url'];
}
wp_send_json_success( $response );
}
public function enqueue_assets() {
if ( ! is_singular() || ! comments_open() ) {
return;
}
wp_enqueue_script(
'kh-comment',
KH_COMMENT_URL . 'assets/kh-comment.js',
array( 'jquery' ),
KH_COMMENT_VER,
true
);
wp_localize_script(
'kh-comment',
'KHComment',
array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'kh_comment_nonce' ),
'i18n' => array(
'submitting' => __( '전송 중…', 'kh-comment' ),
'error' => __( '댓글 전송에 실패했습니다. 잠시 후 다시 시도해 주세요.', 'kh-comment' ),
),
)
);
}
public function handle_submit() {
if ( ! isset( $_POST['kh_comment_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['kh_comment_nonce'] ) ), 'kh_comment_nonce' ) ) {
wp_send_json_error(
array( 'message' => __( '보안 검증에 실패했습니다. 페이지를 새로고침 후 다시 시도해 주세요.', 'kh-comment' ) ),
403
);
}
$comment = wp_handle_comment_submission( wp_unslash( $_POST ) );
if ( is_wp_error( $comment ) ) {
$error_data = (int) $comment->get_error_data();
$status = $error_data ? $error_data : 400;
wp_send_json_error(
array(
'message' => $comment->get_error_message(),
),
$status
);
}
$user = wp_get_current_user();
$cookies_consent = isset( $_POST['wp-comment-cookies-consent'] );
do_action( 'set_comment_cookies', $comment, $user, $cookies_consent );
$comment_html = $this->render_comment_html( $comment );
$response = array(
'comment_id' => $comment->comment_ID,
'parent_id' => (int) $comment->comment_parent,
'approved' => (int) $comment->comment_approved,
'html' => $comment_html,
'message' => 1 === (int) $comment->comment_approved
? __( '댓글이 등록되었습니다.', 'kh-comment' )
: __( '댓글이 등록되었으며, 승인 후 표시됩니다.', 'kh-comment' ),
);
wp_send_json_success( $response );
}
private function render_comment_html( $comment ) {
$args = array(
'style' => 'ul',
'short_ping' => true,
'avatar_size' => 80,
'format' => current_theme_supports( 'html5', 'comment-list' ) ? 'html5' : 'xhtml',
'max_depth' => (int) get_option( 'thread_comments_depth' ),
);
if ( function_exists( 'porto_comment' ) ) {
$args['callback'] = 'porto_comment';
}
$walker = new Walker_Comment();
$depth = 1;
if ( $comment->comment_parent ) {
$depth = $this->get_comment_depth( $comment->comment_parent ) + 1;
}
$output = '';
$walker->start_el( $output, $comment, $depth, $args );
$walker->end_el( $output, $comment, $depth, $args );
return $output;
}
private function get_comment_depth( $comment_id ) {
$depth = 1;
$current = get_comment( $comment_id );
while ( $current && $current->comment_parent ) {
$depth++;
$current = get_comment( $current->comment_parent );
}
return $depth;
}
}
new KH_Ajax_Comment();
입력: 페이지 요청
조건: 사용자 상태 제거
처리: 캐시된 HTML 반환
결과: 동일한 응답 구조
이 구조에서는 HTML 생성 과정에서 사용자 상태를 제거하기 때문에 캐시를 유지할 수 있다.
서버 응답시간 기준으로 본 처리 구조 변화
구조 변경 이후 가장 큰 변화는 서버 응답 단계에서 발생한다. 기존에는 요청마다 HTML을 생성해야 했지만, 변경 이후에는 캐시된 HTML을 그대로 반환한다.
기존 방식
입력: 요청
처리: 사용자 판단 → HTML 생성
결과: 응답 지연
변경 방식
입력: 요청
처리: 캐시 반환
결과: 빠른 응답
실제 측정 기준으로 로그인 상태에서 서버 응답 시간이 기존 1초 이상에서 수십 ms 수준으로 줄어들었다. 요청 처리 자체가 단순해지면서 서버가 수행하는 작업이 크게 줄어든 결과다.
댓글 작성 요청과 서버 처리 흐름
댓글 작성 요청은 기존과 동일하게 1회 요청으로 처리된다. 다만 처리 범위가 다르다. 기존에는 댓글 저장 이후 페이지 전체를 다시 생성했지만, 변경 이후에는 댓글 저장만 수행한다.
입력: 댓글 작성
조건: AJAX 요청
처리: 댓글 저장
결과: JSON 응답
처음에는 AJAX 요청이 추가되면서 서버 부하가 증가할 것이라고 생각했다. 하지만 실제로는 페이지 전체 렌더링이 제거되면서 오히려 처리량이 줄어드는 구조였다.
적용 환경과 선택 기준
이 방식은 서버 응답시간을 줄이는 것이 중요한 환경에서 적합하다. 특히 로그인 사용자 비율이 높은 사이트에서는 캐시 적용 여부가 전체 성능에 큰 영향을 준다. 페이지 HTML을 정적으로 유지하고, 동적인 요소만 분리하는 구조이기 때문이다.
입력: 페이지 요청
조건: 캐시 적용 가능
처리: 빠른 응답 반환
결과: 서버 부하 감소
이 구조는 HTML과 데이터 처리를 분리하는 것을 전제로 한다. 댓글 기능이나 사용자 상태를 서버 렌더링에 포함시키지 않는 경우에 효과적으로 동작한다.











로그인 후 댓글내용을 입력해주세요