데브포일 홈
WordPress 자동화
Guest
STAGE 21 · Lv.100

24/7 무인 운영 시스템

자가 치유 WordPress, 자동 에러 복구, 성능 자동 최적화, 보안 자동 대응, 종합 모니터링 시스템을 구축합니다.

CAPSTONE: 스스로 관리하는 WordPress 시스템

무인 운영의 비전: 사람 없이도 돌아가는 시스템

24/7 무인 운영 시스템은 WordPress 자동화의 궁극적 목표입니다. 사이트가 스스로 문제를 감지하고, 자동으로 복구하며, 성능을 최적화하고, 보안 위협에 대응합니다. 사람은 전략적 의사결정과 창의적 작업에만 집중하면 됩니다.


자가 치유 시스템의 3대 원칙:

1. 감지 (Detect): 문제를 실시간으로 감지 - 다운타임, 성능 저하, 보안 위협, 리소스 부족

2. 분석 (Diagnose): 문제의 원인을 자동 분석 - 어떤 프로세스가 문제인지, 어떤 파일이 변경되었는지

3. 복구 (Recover): 자동으로 복구하고 결과를 보고 - 서비스 재시작, 캐시 정리, 파일 복원, 롤백

🔄
자가 치유
문제 감지 → 자동 복구
사람 개입 최소화
80%
일반 장애의
자동 복구율
🛡️
실시간
보안 위협 감지
→ 즉시 차단
📊
100%
모든 조치 기록
감사 추적 가능

자가 치유 시스템 아키텍처

🔍
헬스 체크
(1분 간격)
⚠️
이상 감지
(임계값 초과)
🔎
원인 분석
(자동 진단)
🔧
자동 복구
(단계적)
검증 + 보고
(알림 발송)
// ============================================
// 자가 치유 WordPress 시스템 (완전판)
// mu-plugins/self-healing-system.php
// ============================================

class Self_Healing_WordPress {

    private $log_table;
    private $max_recovery_per_hour = 5;  // 시간당 최대 복구 시도

    public function __construct() {
        global $wpdb;
        $this->log_table = $wpdb->prefix . 'self_healing_log';

        // 1분마다 헬스 체크 실행
        add_action('self_healing_check', array($this, 'run_comprehensive_check'));
        if (!wp_next_scheduled('self_healing_check')) {
            wp_schedule_event(time(), 'every_minute', 'self_healing_check');
        }

        // 크론 간격 추가
        add_filter('cron_schedules', function($schedules) {
            $schedules['every_minute'] = array('interval' => 60, 'display' => 'Every Minute');
            return $schedules;
        });
    }

    // ========================================
    // 종합 헬스 체크
    // ========================================
    public function run_comprehensive_check() {
        // 안전장치: 시간당 복구 횟수 제한
        if ($this->get_recovery_count_last_hour() >= $this->max_recovery_per_hour) {
            $this->notify('자가 치유 시스템 일시 중지: 시간당 복구 한도 도달', 'warning');
            return;
        }

        $issues = array();

        // 1. 데이터베이스 연결 확인
        if (!$this->check_db_connection()) {
            $issues[] = array('type' => 'db_down', 'severity' => 'critical');
        }

        // 2. 디스크 공간 확인
        $disk = $this->check_disk_space();
        if ($disk['free_percent']  'disk_critical', 'severity' => 'critical', 'value' => $disk);
        } elseif ($disk['free_percent']  'disk_warning', 'severity' => 'warning', 'value' => $disk);
        }

        // 3. PHP 메모리 확인
        $memory = $this->check_memory();
        if ($memory['percent'] > 90) {
            $issues[] = array('type' => 'memory_high', 'severity' => 'warning', 'value' => $memory);
        }

        // 4. WP Cron 건강
        if (!$this->check_cron_health()) {
            $issues[] = array('type' => 'cron_stuck', 'severity' => 'warning');
        }

        // 5. PHP 에러 급증 감지
        $error_spike = $this->detect_error_spike();
        if ($error_spike > 50) {
            $issues[] = array('type' => 'error_spike', 'severity' => 'high', 'value' => $error_spike);
        }

        // 6. 자체 응답 시간 측정
        $response = $this->measure_self_response();
        if ($response > 5000) {
            $issues[] = array('type' => 'slow_response', 'severity' => 'warning', 'value' => $response);
        }

        // 7. DB 테이블 손상 확인
        if ($this->check_db_corruption()) {
            $issues[] = array('type' => 'db_corruption', 'severity' => 'high');
        }

        // 8. .htaccess/.maintenance 파일 이상 확인
        if ($this->check_maintenance_mode()) {
            $issues[] = array('type' => 'stuck_maintenance', 'severity' => 'high');
        }

        // 각 이슈에 대해 자동 복구 시도
        foreach ($issues as $issue) {
            $this->auto_heal($issue);
        }
    }

    // ========================================
    // 자동 복구 로직 (이슈 유형별)
    // ========================================
    private function auto_heal($issue) {
        $action_taken = '';
        $result = 'success';

        switch ($issue['type']) {
            case 'disk_critical':
                // 긴급 디스크 정리
                $freed = 0;
                $freed += $this->cleanup_revisions(7);    // 7일 이상 리비전
                $freed += $this->cleanup_transients();     // 만료된 transients
                $freed += $this->cleanup_spam();           // 스팸 + 휴지통
                $freed += $this->cleanup_logs();           // 로그 파일 압축
                $freed += $this->cleanup_orphan_meta();    // 고아 메타 데이터
                $action_taken = "긴급 디스크 정리: {$freed}MB 확보";
                break;

            case 'disk_warning':
                $freed = $this->cleanup_revisions(30);
                $freed += $this->cleanup_transients();
                $action_taken = "예방적 디스크 정리: {$freed}MB 확보";
                break;

            case 'memory_high':
                wp_cache_flush();
                if (function_exists('opcache_reset')) opcache_reset();
                $action_taken = '오브젝트 캐시 + OPcache 정리';
                break;

            case 'cron_stuck':
                delete_transient('doing_cron');
                $spawned = spawn_cron();
                $action_taken = 'WP Cron 리셋 및 재시작' . ($spawned ? ' (성공)' : ' (실패)');
                break;

            case 'error_spike':
                // 최근 활성화된 플러그인 찾아 비활성화
                $culprit = $this->find_error_source();
                if ($culprit) {
                    deactivate_plugins($culprit);
                    $action_taken = "문제 플러그인 비활성화: {$culprit}";
                } else {
                    $action_taken = '에러 급증 감지, 원인 플러그인 특정 불가';
                    $result = 'partial';
                }
                break;

            case 'slow_response':
                wp_cache_flush();
                $this->rebuild_rewrite_rules();
                // 페이지 캐시 워밍업
                $this->warm_page_cache();
                $action_taken = '캐시 재구축 + 리라이트 규칙 리셋';
                break;

            case 'db_corruption':
                global $wpdb;
                $wpdb->query("REPAIR TABLE {$wpdb->posts}");
                $wpdb->query("REPAIR TABLE {$wpdb->postmeta}");
                $wpdb->query("REPAIR TABLE {$wpdb->options}");
                $action_taken = '손상된 DB 테이블 복구';
                break;

            case 'stuck_maintenance':
                $maintenance_file = ABSPATH . '.maintenance';
                if (file_exists($maintenance_file)) {
                    $mtime = filemtime($maintenance_file);
                    if (time() - $mtime > 600) { // 10분 이상 유지보수 모드
                        unlink($maintenance_file);
                        $action_taken = '장기 유지보수 모드 해제 (10분+ 경과)';
                    }
                }
                break;
        }

        if ($action_taken) {
            $this->log_action($issue, $action_taken, $result);
            $this->notify_action($issue, $action_taken, $result);
        }
    }

    // ========================================
    // 헬퍼 함수들
    // ========================================
    private function check_db_connection() {
        global $wpdb;
        return (bool) @$wpdb->get_var("SELECT 1");
    }

    private function check_disk_space() {
        $free = disk_free_space(ABSPATH);
        $total = disk_total_space(ABSPATH);
        return array(
            'free_percent' => round($free / $total * 100, 1),
            'free_gb' => round($free / 1073741824, 2),
            'total_gb' => round($total / 1073741824, 2),
        );
    }

    private function check_memory() {
        $usage = memory_get_peak_usage(true);
        $limit = wp_convert_hr_to_bytes(WP_MEMORY_LIMIT);
        return array(
            'percent' => round($usage / $limit * 100, 1),
            'used_mb' => round($usage / 1048576, 1),
            'limit_mb' => round($limit / 1048576, 1),
        );
    }

    private function check_cron_health() {
        $crons = _get_cron_array();
        if (!is_array($crons)) return false;
        $overdue = 0;
        foreach ($crons as $ts => $hooks) {
            if ($ts  $threshold_time) $recent_errors++;
            }
        }
        return $recent_errors;
    }

    private function find_error_source() {
        $debug_log = WP_CONTENT_DIR . '/debug.log';
        if (!file_exists($debug_log)) return null;
        $lines = array_slice(file($debug_log), -50);
        $plugin_errors = array();
        foreach ($lines as $line) {
            if (preg_match('/wp-content\/plugins\/([^\/]+)/', $line, $m)) {
                $plugin_errors[$m[1]] = ($plugin_errors[$m[1]] ?? 0) + 1;
            }
        }
        if (empty($plugin_errors)) return null;
        arsort($plugin_errors);
        $top_culprit = key($plugin_errors);
        // 에러가 10건 이상인 플러그인만 비활성화
        if ($plugin_errors[$top_culprit] >= 10) {
            $plugins = get_plugins();
            foreach ($plugins as $file => $data) {
                if (strpos($file, $top_culprit . '/') === 0) return $file;
            }
        }
        return null;
    }

    private function cleanup_revisions($days) {
        global $wpdb;
        $deleted = $wpdb->query($wpdb->prepare(
            "DELETE FROM {$wpdb->posts} WHERE post_type = 'revision' AND post_date query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '%_transient_timeout_%' AND option_value query("DELETE a FROM {$wpdb->options} a LEFT JOIN {$wpdb->options} b ON REPLACE(a.option_name, '_transient_', '_transient_timeout_') = b.option_name WHERE a.option_name LIKE '%_transient_%' AND a.option_name NOT LIKE '%_transient_timeout_%' AND b.option_name IS NULL");
        return 1;
    }

    private function cleanup_spam() {
        global $wpdb;
        $wpdb->query("DELETE FROM {$wpdb->comments} WHERE comment_approved = 'spam'");
        $wpdb->query("DELETE FROM {$wpdb->comments} WHERE comment_approved = 'trash'");
        return 0.5;
    }

    private function cleanup_logs() {
        $debug_log = WP_CONTENT_DIR . '/debug.log';
        if (file_exists($debug_log) && filesize($debug_log) > 50 * 1024 * 1024) {
            // 50MB 이상이면 마지막 1만 줄만 보존
            $lines = file($debug_log);
            $keep = array_slice($lines, -10000);
            file_put_contents($debug_log, implode('', $keep));
            return round((count($lines) - 10000) * 0.0001, 2);
        }
        return 0;
    }

    private function cleanup_orphan_meta() {
        global $wpdb;
        $wpdb->query("DELETE pm FROM {$wpdb->postmeta} pm LEFT JOIN {$wpdb->posts} p ON pm.post_id = p.ID WHERE p.ID IS NULL");
        return 0.5;
    }

    private function measure_self_response() {
        $start = microtime(true);
        wp_remote_get(home_url('/'), array('timeout' => 10, 'sslverify' => false));
        return round((microtime(true) - $start) * 1000);
    }

    private function check_db_corruption() {
        global $wpdb;
        $result = $wpdb->get_results("CHECK TABLE {$wpdb->posts}, {$wpdb->options}", ARRAY_A);
        foreach ($result as $row) {
            if (isset($row['Msg_type']) && $row['Msg_type'] === 'error') return true;
        }
        return false;
    }

    private function check_maintenance_mode() {
        $file = ABSPATH . '.maintenance';
        return file_exists($file) && (time() - filemtime($file)) > 600;
    }

    private function rebuild_rewrite_rules() {
        flush_rewrite_rules(false);
    }

    private function warm_page_cache() {
        $pages = array('/', '/blog/', '/shop/');
        foreach ($pages as $page) {
            wp_remote_get(home_url($page), array('timeout' => 5, 'sslverify' => false));
        }
    }

    // ========================================
    // 로깅 및 알림
    // ========================================
    private function log_action($issue, $action, $result) {
        global $wpdb;
        $wpdb->insert($this->log_table, array(
            'issue_type' => $issue['type'],
            'severity' => $issue['severity'],
            'issue_value' => json_encode($issue['value'] ?? null),
            'action_taken' => $action,
            'result' => $result,
            'created_at' => current_time('mysql'),
        ));
    }

    private function notify_action($issue, $action, $result) {
        $severity_emoji = array(
            'critical' => '🚨', 'high' => '🔴', 'warning' => '⚠️', 'info' => 'ℹ️'
        );
        $emoji = $severity_emoji[$issue['severity']] ?? '🔧';

        wp_remote_post(MAKE_HEALING_WEBHOOK, array(
            'body' => json_encode(array(
                'event' => 'self_healing_action',
                'site' => home_url(),
                'issue_type' => $issue['type'],
                'severity' => $issue['severity'],
                'action' => $action,
                'result' => $result,
                'message' => "{$emoji} [{$issue['type']}] {$action} ({$result})",
                'timestamp' => current_time('c'),
            )),
            'headers' => array('Content-Type' => 'application/json'),
        ));
    }

    private function get_recovery_count_last_hour() {
        global $wpdb;
        return (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$this->log_table} WHERE created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)"
        );
    }

    private function notify($message, $severity) {
        wp_remote_post(MAKE_HEALING_WEBHOOK, array(
            'body' => json_encode(array('event' => 'self_healing_notice', 'message' => $message, 'severity' => $severity)),
            'headers' => array('Content-Type' => 'application/json'),
        ));
    }
}

new Self_Healing_WordPress();
🔒
여기까지는 미리보기입니다
24/7 무인 운영 시스템
무료 가입하면 이어서 볼 수 있고, 강의를 완료할 때마다 XP와 레벨이 쌓입니다.
Google로 3초 만에 시작 →🧵 Threads로 시작무료 공개 강의 둘러보기 (Lv.1~3)