chatGPT codex 5.6 sol ultra 모드로 하루정도 투자해서 분석한 결과 입니다.
원본, 튜닝 후 결과 보고입니다.
정식 출시되었지만 실서비스에 사용이 어렵다는 판단이 듭니다.
성능 튜닝을 하지 않으면 고사양 vps 를 처음부터 사용해야 합니다. 호스팅에서는 통합검색 사용하면 메모리 소모가 극심해 퇴출될 수도 있습니다.
실제 서비스 되면서 활성화된 사이트 기준이지만 이 기준으로 해야 맞다 생각됩니다.
#그누보드7 7.0.5 성능 튜닝: 전후 결과·코드 지도·재현 가이드
https://github.com/jiwonpapa/gnuboard7/blob/codex/7.0.5-performance-lab/docs/benchmark/g7-7.0.5-performance-tuning-code-map-and-implementation-prompt-2026-07-21.md
#그누보드7 7.0.5 VM 성능·검색·서버 사양 분석
https://github.com/jiwonpapa/gnuboard7/blob/codex/7.0.5-performance-lab/docs/benchmark/g7-7.0.5-vm-performance-report-2026-07-21.md
링크 참조하세요. 아래부터 분석 내용입니다.
그누보드7 7.0.5 성능 튜닝 코드 지도 — 실측 결과와 남은 병목
- 작성일: 2026-07-21 KST
- 공식 기준선: 그누보드7
7.0.5 (d5065f1b)
- 작업 브랜치 튜닝 소스:
3379ed93, 경계 iteration 보정 526bf22d (외부 공개 전 push/merge 필요)
- 작업 브랜치 최종 벤치마크·초기화 도구:
81fece53 (benchmark v0.3.2, 외부 공개 전 push/merge 필요)
- 대상 독자: 그누보드7 코어·모듈 개발자, 운영자, 성능 개선 기여자
기술 요약
이번 튜닝은 효과가 컸지만 그누보드7 전체 성능 문제가 끝난 것은 아니다. 각 데이터셋별 동일 4 vCPU·8GB VM·동일 스냅샷에서 수동 전환·상태 확인 후 상태별 1회, 15 RPS·5분으로 측정했다. 현실 데이터 R의 전체 p95는 107.900→64.966ms(-39.79%), 성장 데이터 G는 225.161→49.947ms(-77.82%)로 감소했다. 네 phase 모두 HTTP 오류·응답 의미 오류·iteration drop·swap이 0이었다. 반복 run 분산과 순서 효과는 아직 검증하지 않았다.
가장 큰 개선은 PHP를 다른 언어로 바꾼 결과가 아니다. 잘못된 인덱스 선택, 넓은 행의 조기 hydration, 중복 count, 관계 N+1, 같은 요청 안의 반복 조회와 분산된 홈 조립을 줄인 결과다. 별도 저부하 API 관측에서 게시판 ID 정렬은 759.866→52.413ms(-93.10%), 쇼핑 홈 분류 API는 217.060→59.643ms(-72.52%)였다. 이 저부하 관측의 원시 파일은 이번 게시물에 동봉하지 않았다.
반면 현재 네이티브 MySQL 통합검색은 대량·광범위 검색에서 심각하다. 별도 4 vCPU·8GB 운영 시험에서 게시글 700,005건 가운데 678,866건이 매칭된 exact count 한 번에 mysqld RSS가 약 903MB 증가했다. 이 direct-count 프로세스 표본은 이번 게시물에 동봉하지 않은 별도 관측이다. 메모리 상한을 낮추면 error 188, 제한형 하한, 통합검색의 게시글 누락 중 하나로 이어졌다. PHP memory_limit을 256M에서 512M으로 올려도 별도 프로세스인 MySQL의 이 메모리는 제한하지 못한다.
Manticore는 도입 권고가 아니라 외부 검색 인덱스가 대안이 될 수 있는지 확인한 비교 시험이다. R 통합검색은 중앙값 82.912ms에 현재 시험 색인 기준 exact 48,468건, G는 94.247ms에 같은 기준 exact 194,062건을 반환했다. 다만 이번 연결은 Scout를 우회한 읽기 시험이므로 Scout adapter, 증분 색인, 신규·수정·삭제 freshness까지 검증한 결과가 아니다. G7에는 이미 Laravel Scout custom engine 등록 경로가 있으므로 별도 검색 driver/registry를 새로 제안하지 않는다.
현재 검증 규모의 운영 권장은 4 vCPU·8GB다. 2GB 시험은 부하 전 가용 메모리 379MB에서 안전 guard가 3초 만에 중단됐고, 별도 8GB 시험의 FULLTEXT exact count도 한 번에 약 903MB가 증가했다. 서로 다른 시험이지만 둘을 함께 보면 2GB는 운영 권장 대상이 아니다. 공유호스팅은 큐·소켓·상시 worker를 끈 설치·기본 CRUD·극저트래픽까지는 조건부로 볼 수 있지만, 공개 네이티브 통합검색까지 포함한 정상 운영 환경으로 권장하기 어렵다.
무엇을 같은 조건으로 비교했나
시험 서버와 부하
| 항목 |
시험 조건 |
| 서버 |
4 vCPU·8GB RAM·2GB swap 단일 VM |
| 런타임 |
Ubuntu 24.04, Nginx, PHP-FPM 8.5 |
| DB·캐시 |
MySQL 8.4, Redis |
| 검색 비교 |
공식+MySQL → 튜닝+MySQL → 튜닝+Manticore를 분리 phase로 실행 |
| 일반 A/B |
R/G 상태별 1회, 15 RPS·5분, 1 iteration=HTTP GET 1회 |
| 요청 비중 |
쇼핑 50%·게시판 30%·홈 10%·쓰기 전 안전조회 10% |
| peak 확인 |
G 튜닝 상태 30 RPS·15분, 27,001 GET |
| 수집 자원 |
host/PHP-FPM/MySQL/searchd CPU·RSS, 가용 메모리, swap |
| CPU 기준 |
호스트 CPU는 4 vCPU 전체 용량을 100%로 정규화 |
| 유효성 |
HTTP·응답 의미 오류·drop 0, sampler 정상, swap 0 |
일반 A/B와 위험 검색은 서로 다른 phase로 분리했다. 일반 A/B 숫자에 검색 사고를 섞지 않았고, 검색 결과도 exact와 하한을 같은 의미의 응답처럼 비교하지 않았다. 원시 JSON에는 배포 디렉터리의 Git ref가 기록되지 않아 공식/튜닝 runtime 동일성을 파일 하나만으로 독립 입증할 수 없다. 따라서 이 글은 당시 수동 전환·상태 확인을 포함한 VM 관측으로 보고하며, 일반적 성능 보증으로 확대하지 않는다.
실제 생성한 데이터
| 데이터셋 |
목적 |
회원 |
게시글 |
댓글 |
상품 / 옵션 |
주문 |
| R |
중소형 쇼핑몰 2년차 현실 기준 |
30,001 |
50,005 |
74,227 |
5,000 / 5,000 |
0 |
| G |
성장 대비·인덱스/캐시 한계 |
100,001 |
200,005 |
348,714 |
20,000 / 20,000 |
0 |
| X |
병리 검색·deep page·파괴 시험 |
30,001 |
700,005 |
876,951 |
20,000 / 20,000 |
0 |
게시글 1,000건·상품 500개는 설치·CRUD smoke에는 충분하지만 optimizer의 잘못된 인덱스 선택, deep pagination, 광범위 FULLTEXT, 관계 N+1의 누적 비용을 드러내기에는 너무 작다. R은 현실 회귀, G는 성장 한계, X는 병리 분포를 찾는 용도로 분리했다.
주문·주문상품·결제·배송·리뷰·문의 생성기는 아직 없다. 따라서 인기상품, 매출, 재고, 주문목록, 결제 경로는 이번 결과로 검증됐다고 주장하지 않는다.
같은 부하에서 확인된 개선
R/G 운영부하
| 데이터·상태 |
평균 |
p95 |
p99 |
호스트 CPU 평균 |
MySQL CPU |
최소 가용 메모리 |
오류/drop/swap |
| R 공식 7.0.5 |
63.111ms |
107.900ms |
123.028ms |
19.767% |
5.242% |
6,670MB |
0/0/0 |
| R 튜닝 |
42.146ms |
64.966ms |
74.644ms |
13.031% |
1.364% |
6,675MB |
0/0/0 |
| R 변화 |
-33.22% |
-39.79% |
-39.33% |
-34.08% |
-73.98% |
안전 |
PASS |
| G 공식 7.0.5 |
75.356ms |
225.161ms |
248.062ms |
27.465% |
12.643% |
6,537MB |
0/0/0 |
| G 튜닝 |
39.710ms |
49.947ms |
171.149ms |
12.775% |
1.695% |
6,587MB |
0/0/0 |
| G 변화 |
-47.30% |
-77.82% |
-31.01% |
-53.49% |
-86.59% |
안전 |
PASS |
G 튜닝 상태의 단일 30 RPS·15분 peak도 27,001 GET, p95 65.119ms, p99 92.298ms, 오류·drop·swap 0으로 통과했다. 다만 기준선 30 RPS, 반복 run, 30분 steady, 2시간 soak, 실쓰기와 주문 부하는 아직 비교하지 않았다.
병목별 대표 결과
| 경로 |
공식 7.0.5 |
튜닝 |
변화 |
해석 |
| 게시판 ID 정렬 중앙값 |
759.866ms |
52.413ms |
-93.10% |
잘못된 PRIMARY 역순 선택을 전용 목록 인덱스로 교정 |
| 쇼핑 홈 분류 p95 |
217.060ms |
59.643ms |
-72.52% |
분류 N+1, 반복 tree 조립과 다중 fetch 축소 |
| 상품 목록 2페이지 p95 |
88.227ms |
44.794ms |
-49.23% |
현재 페이지 ID-first 후 필요한 관계만 hydration |
| 게시판 목록 2페이지 p95 |
39.868ms |
35.430ms |
-11.13% |
목록 컬럼·본문 preview·count 재사용 |
| 홈 HTML p95 |
45.136ms |
46.291ms |
+2.56% |
직접 수정 없음. 개선 주장 대상이 아님 |
홈 HTML의 +2.56%는 3회 저부하 관측값이라 회귀로 확정하지 않았다. 유리한 값만 고르지 않고 개선되지 않은 경로도 그대로 남겼다.
코드 지도: 어디를 왜 바꿨나
라인은 공통·게시판·쇼핑 튜닝 ref 3379ed93, 운영 도구는 81fece53 기준이다. 이후 코드가 이동하면 파일과 심볼명을 정본으로 다시 찾는다.
| 영역 |
핵심 파일·심볼 |
기존 병목 |
변경 계약 |
증거 수준 |
| 공통 요청 |
app/Extension/HookListenerRegistrar.php:101-148 |
훅마다 로그 I/O 반복 |
리스너 단위 요약 로그 1회로 축소 |
자동 테스트 + VM |
| 공통 권한 |
app/Http/Middleware/PermissionMiddleware.php:121-168 |
비회원마다 guest role·permission SQL 반복 |
eager-loaded permission 재사용과 짧은 guest role cache |
자동 테스트 + VM |
| 공통 부팅 |
app/Providers/ModuleRouteServiceProvider.php:55-83, app/Services/LanguagePack/LanguagePackRegistry.php:53-68,111-124 |
활성 모듈·언어팩 중복 조회 |
manager/registry의 기존 조회 결과 재사용 |
자동 테스트 + VM |
| 게시판 목록 |
modules/_bundled/sirsoft-board/src/Repositories/PostRepository.php:81-142,1460-1805 |
넓은 본문·관계를 정렬 전에 hydration, count 중복 |
목록 컬럼·본문 preview, ID-first pagination, 선택 ID만 hydration |
R/G/X VPS_PASS |
| 게시판 인덱스 |
modules/_bundled/sirsoft-board/database/migrations/2026_07_15_000001_add_high_volume_list_indexes.php:12-46 |
ID 정렬에서 PRIMARY 역순으로 약 60만 행 건너뜀 |
(board_id,is_notice,parent_id,deleted_at,id) 전용 인덱스 조건부 강제 |
759.866→52.413ms |
| 게시판 검색 |
modules/_bundled/sirsoft-board/src/Repositories/PostRepository.php:81-142,216-872 |
광범위 FULLTEXT 중첩, 전체 후보 materialization, 실패 쿼리 반복 |
동시성 guard, branch별 cap, bounded fallback, 결과 메타 보존 |
자동 테스트 + 검색 VPS |
| 검색 응답 의미 |
modules/_bundled/sirsoft-board/src/Http/Resources/PostCollection.php:74-151,262-314 |
잘린 결과를 exact total처럼 오해 |
total_relation, total_is_exact, result_cap, search_truncated 명시 |
의미 검증 PASS |
| 게시판 상세 |
modules/_bundled/sirsoft-board/src/Repositories/PostRepository.php:1302-1444 |
이전·다음 OR 조건과 board 재조회 |
board 재사용, tie query와 MAX/MIN(created_at) 분리 |
p95 -14.75% 관측 |
| 쇼핑 홈 조립 |
modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Public/ProductController.php:37-80, templates/_bundled/sirsoft-basic/layouts/shop/index.json:6-10,69-116 |
분류·최근·인기·신상품을 각각 fetch |
storefront 응답으로 조립하고 기존 resource 계약 유지 |
쇼핑 경로 VPS_PASS |
| 상품 목록 |
modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php:400-493, modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ProductListResource.php:25-281 |
넓은 상품·관계·집계를 전체 hydration |
count·현재 ID 우선, 선택 ID 관계만 로딩, 요청 캐시 |
p95 -46.70~-49.23% |
| 카테고리 |
modules/_bundled/sirsoft-ecommerce/src/Models/Category.php:20-387, modules/_bundled/sirsoft-ecommerce/src/Services/CategoryService.php:61-92 |
부모·자식 재귀 N+1, 매 요청 tree 재조립 |
일괄 ancestor prime, 메모리 tree, 60초 CacheInterface cache |
p95 -72.52% |
| 인기상품 |
modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php:498-543 |
상품별 최근 주문 correlated subquery |
최근 30일 판매 derived aggregation 1회 join |
CODE_ONLY, 주문 0건 |
| 상품검색 |
modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php:612-862 |
Scout 전체 ID를 PHP에 적재하고 count·page 중복 |
derived ID join과 COUNT(*) OVER(), 현재 ID만 hydration |
MySQL 경로 VPS_PASS |
| 네이티브 통합검색 |
app/Http/Controllers/Api/Public/PublicSearchController.php:24-63,89-128, app/Search/Engines/DatabaseFulltextEngine.php:334-476 |
모듈 순차 fan-out과 페이지 limit 전 exact count |
병목을 숨기지 않고 bounded 결과의 정확도 메타를 응답까지 전달 |
사고 재현, 근본 재설계 필요 |
| 통합검색 대안 시험 |
app/Search/ManticoreIntegratedSearch.php:21-303 |
MySQL exact count의 순간 메모리·전체 ID 전송 |
Manticore count와 현재 페이지 ID만 조회 |
읽기 phase PASS, 제품화 미검증 |
| 통합검색 게시글 |
modules/_bundled/sirsoft-board/src/Repositories/PostRepository.php:2127-2444, modules/_bundled/sirsoft-board/src/Listeners/SearchPostsListener.php:85-286 |
모듈 count 중복과 게시글 누락 위험 |
exact 외부 결과 또는 bounded MySQL 하한을 명시적으로 반환 |
검색 A/B/C |
| DB 구조 |
modules/_bundled/sirsoft-board/database/migrations/2026_07_15_000001_add_high_volume_list_indexes.php:12-46, modules/_bundled/sirsoft-board/database/migrations/2026_07_16_000001_create_board_post_author_terms_table.php:13-154, modules/_bundled/sirsoft-benchmark/database/migrations/2026_07_15_000004_add_ecommerce_storefront_indexes.php:12-43 |
WHERE+ORDER BY와 맞지 않는 인덱스, 작성자 상관 검색 |
쿼리 목적별 복합 인덱스와 작성자 검색어 사전 |
EXPLAIN + VM |
| A/B 원복 |
scripts/benchmark/g7-performance-toggle.sh, scripts/benchmark/g7-ab-benchmark.sh |
서로 다른 소스·DB·부하 비교 위험 |
ref·checksum·runtime·schema·자원·의미 검증과 물리 원복 분리 |
A/B 실행 PASS, 현재 v0.3.2 strict source drift 미해결 |
| 검색 A/B/C |
scripts/benchmark/g7-search-backend-toggle.sh, scripts/benchmark/g7-search-ab-benchmark.sh |
검색 backend와 데이터 상태가 섞인 비교 |
MySQL/Manticore 연결만 전환하고 서비스·인덱스 보존 |
MySQL 사고 재현 + Manticore 읽기 PASS |
| 더미 초기화 |
modules/_bundled/sirsoft-benchmark/src/Services/DummyDataResetService.php:29-221,244-315, modules/_bundled/sirsoft-benchmark/src/Jobs/ResetGenerationJob.php:15-62 |
대량 삭제를 웹 세션에서 기다리고 중복 삭제 가능 |
queue 작업, 20,000행 cursor chunk, overlap lock, 완료 no-op |
기능 smoke PASS |
| 초기화 UI |
modules/_bundled/sirsoft-benchmark/src/Http/Controllers/Admin/GenerationJobController.php:123-360, modules/_bundled/sirsoft-benchmark/resources/js/handlers/startAutoRefresh.ts:83-164 |
어느 게시판을 지우는지 불명확하고 polling 중 화면 깜빡임 |
게시판명·slug·ID·실제 삭제량 표시, 상세 우선 순차 polling |
브라우저 smoke PASS |
핵심은 “캐시를 많이 넣었다”가 아니다. 쿼리의 선택도와 정렬에 맞는 인덱스, 현재 페이지까지만 가져오는 ID-first, 중복 count 제거, 요청 범위 캐시, 실패 시 정확도 저하를 숨기지 않는 응답 계약을 함께 적용했다.
대량 더미 초기화도 웹 요청에서 분리했다
몇 시간 멈춘 것처럼 보였던 직접 원인은 작업 dispatch가 database, 실제 worker가 redis를 소비하던 queue 불일치였다. 최종 구성은 benchmark queue를 redis/default로 통일하고, 삭제를 900초 queue job과 20,000행 cursor chunk로 분리했다. 완료 작업 재전달은 no-op이며 중복 초기화는 row lock과 overlap lock으로 차단한다.
아래 시간은 운영 시험 기록이며 이번 게시물에는 삭제 원시 로그를 동봉하지 않았다. 따라서 재현 가능한 보편 성능치가 아니라 구현 규모를 설명하는 관측값으로만 공개한다.
| 삭제 대상 |
경과시간 |
프로세스 최대 RSS |
swap |
| 게시글 100,000 + 댓글 125,204 + 회원 10,000 |
37.97초 |
미수집 |
0 |
| 게시글 200,000 + 댓글 250,692 + 회원 10,000 |
73.65초 |
107,340KB |
0 |
| 게시글 400,000 + 댓글 501,041 + 회원 10,000 |
161.60초 |
107,456KB |
0 |
관리자 화면에는 게시판명·slug·ID·예상 삭제량과 실제 진행량을 표시하고, 5초 순차 polling으로 동시 refetch와 화면 깜빡임을 막았다. 세 작업을 더해 “100만 건 273초”처럼 표현하지 않는다. 각 작업의 데이터 분포와 대상이 다르기 때문이다.
네이티브 통합검색은 왜 별도 문제인가
현재 통합검색은 코어 컨트롤러가 게시글·상품·페이지 검색 listener를 순차 호출하고 각 모듈의 결과와 total을 합산한다.
| 흐름 |
구조적 문제 |
app/Http/Controllers/Api/Public/PublicSearchController.php:24-63 |
활성 검색 모듈을 한 요청에서 순차 호출 |
modules/_bundled/sirsoft-board/src/Listeners/SearchPostsListener.php:85-123 |
게시글 search/count와 권한 필터 수행 |
modules/_bundled/sirsoft-ecommerce/src/Listeners/SearchProductsListener.php:87-138 |
상품 search/count 별도 수행 |
modules/_bundled/sirsoft-page/src/Listeners/SearchPagesListener.php:79-118 |
페이지 search/count 별도 수행 |
app/Http/Controllers/Api/Public/PublicSearchController.php:89-128 |
모듈 total을 다시 합산 |
페이지에 20건만 표시해도 exact total과 relevance 정렬은 광범위 후보를 처리할 수 있다. MySQL 8.4 문서상 innodb_ft_result_cache_limit은 FULLTEXT 쿼리·스레드별 중간·최종 결과 메모리 상한이며 기본값은 2GB다. 큰 결과에서 메모리 사용을 제한하지만, 상한에 도달하면 오류가 반환된다. 이번 시험도 같은 양상을 보였다.
| 검색 증거 |
관측 결과 |
판정 |
| X 게시글 exact count |
별도 4 vCPU·8GB VM, innodb_ft_result_cache_limit=2GB: 678,866건, 4.898초, mysqld RSS 511→1,414MB |
단일 요청의 순간 증가가 2GB급 서버에는 치명적일 수 있음 |
| R 공식 통합검색 |
77.234ms, HTTP 200이지만 게시글 누락·페이지 2건 |
빠른 성공이 아니라 불완전 응답 |
| R 공식 게시판 직접검색 |
3/3 HTTP 500, 당시 서버 로그는 MySQL error 188 |
네이티브 경로 실패 |
| R 튜닝+MySQL 통합검색 |
131.686ms, 하한 8건 |
서버 생존을 위한 bounded 결과 |
| R 튜닝+Manticore 통합검색 |
82.912ms, 시험 색인 기준 exact 48,468건 |
외부 인덱스 대안성 확인 |
| G 튜닝+MySQL 통합검색 |
417.988ms, 하한 8건 |
데이터 증가 시 정확도 희생 |
| G 튜닝+Manticore 통합검색 |
94.247ms, 시험 색인 기준 exact 194,062건 |
성장 데이터에서도 대안성 반복 확인 |
공식 MySQL 응답과 Manticore 응답은 반환 의미가 다르므로 77ms→83ms 같은 단순 속도 비교를 하면 안 된다. 이 시험의 중요한 결과는 현재 시험 색인 기준 전체 결과를 반환하면서 MySQL의 순간 900MB급 증가를 검색 서비스 쪽으로 격리할 수 있었다는 점이다. DB 원본과 색인 전체를 독립 재대조한 산출물은 아직 없으므로 “원본 DB와 완전 일치”로 확대하지 않는다.
HTTP 500과 게시글 누락은 검색 응답 JSON으로 재확인했지만, error 188 원문과 위 direct count의 SQL·프로세스 표본은 이번 게시물에 동봉하지 않았다. 두 값은 당시 서버 로그와 별도 관측으로 구분한다.
Manticore 비용도 0은 아니다. 별도 운영 기록에서 X 전체 색인은 29.11초, indexer 최대 RSS 약 313MB, 인덱스 디스크 약 502MB였고 유휴 searchd RSS는 약 55~105MB였다. 이번 게시물에는 indexer·프로세스 원시 로그를 동봉하지 않았으므로 이 값은 보조 관측이다. Manticore를 G7 기본 검색엔진으로 채택하라는 결론도 아니다.
Laravel Scout는 driver 계층이며 custom Engine 등록을 지원한다. G7도 app/Providers/ScoutServiceProvider.php:33-51에서 EngineManager::extend()와 core.search.engine_drivers 훅을 이미 제공하며 이 부분은 공식 7.0.5부터 있던 기능이다. 이번 직결 시험 결과는 그대로 유효하지만, Scout adapter 비용과 모델 observer/queue 기반 증분 색인 동기화 완료 증거로 확대하지 않는다.
서버 사양과 공유호스팅 판정
그누보드7 공식 요구사항은 PHP 8.2+, MySQL 8.0+ 또는 MariaDB 10.3+, 최소 디스크 700MB·권장 2GB+를 명시하지만 CPU·RAM 최소값은 제시하지 않는다. 아래 CPU·RAM은 공식 사양이 아니라 이번 데이터와 구성의 관측·운영 판단이다.
| 구성 |
판정 |
근거 |
| 4 vCPU·2GB |
부적합 |
부하 전 가용 메모리 379MB에서 guard가 3초 만에 중단 |
| 4 vCPU·4GB |
저부하 smoke 후보 |
21경로·0.2 RPS·20초만 확인, 검색·색인·큐 중첩 미검증 |
| 4 vCPU·8GB |
현재 권장 |
R/G 일반 A/B·R/G/X 검색·색인을 분리 실행해 swap 0, 충분한 가용 메모리 확인 |
| 8 vCPU·16GB 또는 분리 |
성장 구간 계획안 |
동시 부하, 큐·배치·색인 중첩, DB/검색 격리용이며 직접 A/B 수치는 아님 |
위 2GB guard와 FULLTEXT +903MB는 같은 실행 결과가 아니다. 후자는 별도 4 vCPU·8GB VM에서 innodb_ft_result_cache_limit=2GB로 측정한 관측이며, 이 문서는 둘을 합쳐 하나의 2GB 실측치로 주장하지 않는다.
카페24 뉴아우토반 절약형은 2026-07-21 공식 페이지 확인 기준 700MB SSD, PHP 8.4/8.2, MariaDB 10.x, 일 트래픽 1.6GB를 제공한다. 설치 호환 가능성과 운영 적합성은 다르다. 700MB는 G7 공식 최소 디스크와 같아 캐시·로그·업로드·업데이트 여유가 없고 공유 CPU/RAM도 보장되지 않는다.
따라서 공유호스팅 판정은 다음처럼 나눈다.
- 설치·기본 CRUD·극저트래픽: 기능을 줄인 구성에서 조건부 가능
- 큐·WebSocket·상시 worker·서버 cron: 사용하지 않거나 수동 운영 필요
- 공개 네이티브 MySQL 통합검색: 운영 권장 불가
- PHP 256M/512M: PHP 요청 힙만 바꾸므로 MySQL FULLTEXT 메모리 문제는 해결하지 못함
- 정상 쇼핑몰 운영: 주문·예약작업·검색·첨부·로그 여유를 포함하면 VPS 권장
참고 자료:
이번 결과가 증명하지 않는 것
- 5분 A/B와 15분 peak는 30분 steady·2시간 soak 인증이 아니다.
- R/G 상태별 1회 측정이라 run-to-run 분산과 실행 순서 효과를 검증하지 않았다.
- 원시 JSON만으로 배포 runtime ref를 독립 입증할 수 없어 당시 수동 전환·상태 확인에 의존한다.
- 일반 GET mix 결과는 POST, 주문, 결제, 재고, 마일리지 부하 결과가 아니다.
- 주문 데이터가 0건이므로 인기상품 판매집계 개선율은
UNVERIFIED다.
- Manticore 시험은 Scout adapter·실시간 색인·장애 전환·권한 freshness 완료 증거가 아니다.
- 일반 게시판 내부검색은 아직 MySQL bounded 경로이며 Manticore 통합검색 결과와 같지 않다.
- 페이지 통합검색은 큰 limit로 결과를 받은 뒤 PHP에서 자르는 materialization 위험이 남아 있다.
- 카테고리의 static ancestor cache는 long-lived worker에서 요청 간 남을 수 있어 lifecycle reset 검증이 필요하다. 상품 resource 캐시는 Request attributes 기반 요청 범위 캐시다.
- 홈 HTML은 직접 개선되지 않았고
+2.56% 관측값을 회귀나 개선으로 확정하지 않았다.
- 4 vCPU·8GB 권장은 일반부하·검색·색인을 분리 실행한 결과이며 세 작업의 동시 중첩은 미검증이다.
ROI 기준 다음 작업
- 네이티브 MySQL 통합검색의 광범위 exact count 의존을 재설계하고, 하한 결과는
gte/search_truncated로 UI까지 명확히 표시한다.
- 주문 정합성용 소량 corpus와 dataset-owned 대량·재개형 생성기를 만들어 주문·결제·재고·인기상품 경로를 검증한다.
- benchmark 모듈이 소유한 ecommerce 인덱스를 ecommerce 정식 migration/upgrade step으로 이전한다.
- 같은 R/G 스냅샷으로 30분 steady, 2시간 soak, POST, spike, breakpoint를 실행한다.
- 위 결과 뒤에 cursor pagination과 인기상품 집계 구조를 다시 판단한다.
재현성과 증거 경계
| 구분 |
정본 |
| 공식 기준선 |
태그 7.0.5, 커밋 d5065f1bc9ebdc31a3e4e0d0c2e757138fd7423f |
| 성능 튜닝 |
작업 브랜치 ref 3379ed93 (외부 공개 전 push/merge 필요) |
| iteration 보정 |
작업 브랜치 ref 526bf22d (외부 공개 전 push/merge 필요) |
| benchmark v0.3.2·초기화 |
작업 브랜치 ref 81fece53 (외부 공개 전 push/merge 필요) |
| 상세 코드 지도·원시 SHA-256 |
docs/benchmark/g7-7.0.5-performance-tuning-code-map-and-implementation-prompt-2026-07-21.md |
| VM 성능 보고서 |
docs/benchmark/g7-7.0.5-vm-performance-report-2026-07-21.md |
증거 수준은 구분해서 읽어야 한다.
VPS_PASS: 당시 VM 전환과 HTTP 의미·CPU/RSS/swap을 함께 확인. 공개 원시 JSON만으로 전환 ref를 독립 증명한다는 뜻은 아님
AUTO_PASS: 자동 테스트 통과
CODE_ONLY: 코드와 쿼리 구조만 확인
UNVERIFIED: 데이터·부하·운영 조건이 없어 아직 주장 불가
현재 결론은 “그누보드7은 무조건 느리다”도, “튜닝으로 모든 문제가 끝났다”도 아니다. 일반 게시판·쇼핑 경로의 큰 병목은 코드와 인덱스로 줄일 수 있었지만, 네이티브 MySQL 통합검색과 주문 기반 부하는 여전히 별도 개선·검증이 필요한 핵심 영역이다.
그누보드7 7.0.5 성능 튜닝 코드 지도 — 실측 결과와 남은 병목
7.0.5(d5065f1b)3379ed93, 경계 iteration 보정526bf22d(외부 공개 전 push/merge 필요)81fece53(benchmark v0.3.2, 외부 공개 전 push/merge 필요)기술 요약
이번 튜닝은 효과가 컸지만 그누보드7 전체 성능 문제가 끝난 것은 아니다. 각 데이터셋별 동일 4 vCPU·8GB VM·동일 스냅샷에서 수동 전환·상태 확인 후 상태별 1회, 15 RPS·5분으로 측정했다. 현실 데이터 R의 전체 p95는
107.900→64.966ms(-39.79%), 성장 데이터 G는225.161→49.947ms(-77.82%)로 감소했다. 네 phase 모두 HTTP 오류·응답 의미 오류·iteration drop·swap이 0이었다. 반복 run 분산과 순서 효과는 아직 검증하지 않았다.가장 큰 개선은 PHP를 다른 언어로 바꾼 결과가 아니다. 잘못된 인덱스 선택, 넓은 행의 조기 hydration, 중복 count, 관계 N+1, 같은 요청 안의 반복 조회와 분산된 홈 조립을 줄인 결과다. 별도 저부하 API 관측에서 게시판 ID 정렬은
759.866→52.413ms(-93.10%), 쇼핑 홈 분류 API는217.060→59.643ms(-72.52%)였다. 이 저부하 관측의 원시 파일은 이번 게시물에 동봉하지 않았다.반면 현재 네이티브 MySQL 통합검색은 대량·광범위 검색에서 심각하다. 별도 4 vCPU·8GB 운영 시험에서 게시글 700,005건 가운데 678,866건이 매칭된 exact count 한 번에
mysqldRSS가 약903MB증가했다. 이 direct-count 프로세스 표본은 이번 게시물에 동봉하지 않은 별도 관측이다. 메모리 상한을 낮추면 error 188, 제한형 하한, 통합검색의 게시글 누락 중 하나로 이어졌다. PHPmemory_limit을 256M에서 512M으로 올려도 별도 프로세스인 MySQL의 이 메모리는 제한하지 못한다.Manticore는 도입 권고가 아니라 외부 검색 인덱스가 대안이 될 수 있는지 확인한 비교 시험이다. R 통합검색은 중앙값
82.912ms에 현재 시험 색인 기준 exact 48,468건, G는94.247ms에 같은 기준 exact 194,062건을 반환했다. 다만 이번 연결은 Scout를 우회한 읽기 시험이므로 Scout adapter, 증분 색인, 신규·수정·삭제 freshness까지 검증한 결과가 아니다. G7에는 이미 Laravel Scout custom engine 등록 경로가 있으므로 별도 검색 driver/registry를 새로 제안하지 않는다.현재 검증 규모의 운영 권장은 4 vCPU·8GB다. 2GB 시험은 부하 전 가용 메모리 379MB에서 안전 guard가 3초 만에 중단됐고, 별도 8GB 시험의 FULLTEXT exact count도 한 번에 약 903MB가 증가했다. 서로 다른 시험이지만 둘을 함께 보면 2GB는 운영 권장 대상이 아니다. 공유호스팅은 큐·소켓·상시 worker를 끈 설치·기본 CRUD·극저트래픽까지는 조건부로 볼 수 있지만, 공개 네이티브 통합검색까지 포함한 정상 운영 환경으로 권장하기 어렵다.
무엇을 같은 조건으로 비교했나
시험 서버와 부하
일반 A/B와 위험 검색은 서로 다른 phase로 분리했다. 일반 A/B 숫자에 검색 사고를 섞지 않았고, 검색 결과도 exact와 하한을 같은 의미의 응답처럼 비교하지 않았다. 원시 JSON에는 배포 디렉터리의 Git ref가 기록되지 않아 공식/튜닝 runtime 동일성을 파일 하나만으로 독립 입증할 수 없다. 따라서 이 글은 당시 수동 전환·상태 확인을 포함한 VM 관측으로 보고하며, 일반적 성능 보증으로 확대하지 않는다.
실제 생성한 데이터
게시글 1,000건·상품 500개는 설치·CRUD smoke에는 충분하지만 optimizer의 잘못된 인덱스 선택, deep pagination, 광범위 FULLTEXT, 관계 N+1의 누적 비용을 드러내기에는 너무 작다. R은 현실 회귀, G는 성장 한계, X는 병리 분포를 찾는 용도로 분리했다.주문·주문상품·결제·배송·리뷰·문의 생성기는 아직 없다. 따라서 인기상품, 매출, 재고, 주문목록, 결제 경로는 이번 결과로 검증됐다고 주장하지 않는다.
같은 부하에서 확인된 개선
R/G 운영부하
G 튜닝 상태의 단일 30 RPS·15분 peak도 27,001 GET, p95
65.119ms, p9992.298ms, 오류·drop·swap 0으로 통과했다. 다만 기준선 30 RPS, 반복 run, 30분 steady, 2시간 soak, 실쓰기와 주문 부하는 아직 비교하지 않았다.병목별 대표 결과
PRIMARY역순 선택을 전용 목록 인덱스로 교정홈 HTML의
+2.56%는 3회 저부하 관측값이라 회귀로 확정하지 않았다. 유리한 값만 고르지 않고 개선되지 않은 경로도 그대로 남겼다.코드 지도: 어디를 왜 바꿨나
라인은 공통·게시판·쇼핑 튜닝 ref
3379ed93, 운영 도구는81fece53기준이다. 이후 코드가 이동하면 파일과 심볼명을 정본으로 다시 찾는다.app/Extension/HookListenerRegistrar.php:101-148app/Http/Middleware/PermissionMiddleware.php:121-168app/Providers/ModuleRouteServiceProvider.php:55-83,app/Services/LanguagePack/LanguagePackRegistry.php:53-68,111-124modules/_bundled/sirsoft-board/src/Repositories/PostRepository.php:81-142,1460-1805modules/_bundled/sirsoft-board/database/migrations/2026_07_15_000001_add_high_volume_list_indexes.php:12-46PRIMARY역순으로 약 60만 행 건너뜀(board_id,is_notice,parent_id,deleted_at,id)전용 인덱스 조건부 강제759.866→52.413msmodules/_bundled/sirsoft-board/src/Repositories/PostRepository.php:81-142,216-872modules/_bundled/sirsoft-board/src/Http/Resources/PostCollection.php:74-151,262-314total_relation,total_is_exact,result_cap,search_truncated명시modules/_bundled/sirsoft-board/src/Repositories/PostRepository.php:1302-1444MAX/MIN(created_at)분리modules/_bundled/sirsoft-ecommerce/src/Http/Controllers/Public/ProductController.php:37-80,templates/_bundled/sirsoft-basic/layouts/shop/index.json:6-10,69-116modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php:400-493,modules/_bundled/sirsoft-ecommerce/src/Http/Resources/ProductListResource.php:25-281modules/_bundled/sirsoft-ecommerce/src/Models/Category.php:20-387,modules/_bundled/sirsoft-ecommerce/src/Services/CategoryService.php:61-92modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php:498-543modules/_bundled/sirsoft-ecommerce/src/Repositories/ProductRepository.php:612-862COUNT(*) OVER(), 현재 ID만 hydrationapp/Http/Controllers/Api/Public/PublicSearchController.php:24-63,89-128,app/Search/Engines/DatabaseFulltextEngine.php:334-476app/Search/ManticoreIntegratedSearch.php:21-303modules/_bundled/sirsoft-board/src/Repositories/PostRepository.php:2127-2444,modules/_bundled/sirsoft-board/src/Listeners/SearchPostsListener.php:85-286modules/_bundled/sirsoft-board/database/migrations/2026_07_15_000001_add_high_volume_list_indexes.php:12-46,modules/_bundled/sirsoft-board/database/migrations/2026_07_16_000001_create_board_post_author_terms_table.php:13-154,modules/_bundled/sirsoft-benchmark/database/migrations/2026_07_15_000004_add_ecommerce_storefront_indexes.php:12-43scripts/benchmark/g7-performance-toggle.sh,scripts/benchmark/g7-ab-benchmark.shscripts/benchmark/g7-search-backend-toggle.sh,scripts/benchmark/g7-search-ab-benchmark.shmodules/_bundled/sirsoft-benchmark/src/Services/DummyDataResetService.php:29-221,244-315,modules/_bundled/sirsoft-benchmark/src/Jobs/ResetGenerationJob.php:15-62modules/_bundled/sirsoft-benchmark/src/Http/Controllers/Admin/GenerationJobController.php:123-360,modules/_bundled/sirsoft-benchmark/resources/js/handlers/startAutoRefresh.ts:83-164핵심은 “캐시를 많이 넣었다”가 아니다. 쿼리의 선택도와 정렬에 맞는 인덱스, 현재 페이지까지만 가져오는 ID-first, 중복 count 제거, 요청 범위 캐시, 실패 시 정확도 저하를 숨기지 않는 응답 계약을 함께 적용했다.
대량 더미 초기화도 웹 요청에서 분리했다
몇 시간 멈춘 것처럼 보였던 직접 원인은 작업 dispatch가
database, 실제 worker가redis를 소비하던 queue 불일치였다. 최종 구성은 benchmark queue를redis/default로 통일하고, 삭제를 900초 queue job과 20,000행 cursor chunk로 분리했다. 완료 작업 재전달은 no-op이며 중복 초기화는 row lock과 overlap lock으로 차단한다.아래 시간은 운영 시험 기록이며 이번 게시물에는 삭제 원시 로그를 동봉하지 않았다. 따라서 재현 가능한 보편 성능치가 아니라 구현 규모를 설명하는 관측값으로만 공개한다.
관리자 화면에는 게시판명·slug·ID·예상 삭제량과 실제 진행량을 표시하고, 5초 순차 polling으로 동시 refetch와 화면 깜빡임을 막았다. 세 작업을 더해 “100만 건 273초”처럼 표현하지 않는다. 각 작업의 데이터 분포와 대상이 다르기 때문이다.
네이티브 통합검색은 왜 별도 문제인가
현재 통합검색은 코어 컨트롤러가 게시글·상품·페이지 검색 listener를 순차 호출하고 각 모듈의 결과와 total을 합산한다.
app/Http/Controllers/Api/Public/PublicSearchController.php:24-63modules/_bundled/sirsoft-board/src/Listeners/SearchPostsListener.php:85-123modules/_bundled/sirsoft-ecommerce/src/Listeners/SearchProductsListener.php:87-138modules/_bundled/sirsoft-page/src/Listeners/SearchPagesListener.php:79-118app/Http/Controllers/Api/Public/PublicSearchController.php:89-128페이지에 20건만 표시해도 exact total과 relevance 정렬은 광범위 후보를 처리할 수 있다. MySQL 8.4 문서상
innodb_ft_result_cache_limit은 FULLTEXT 쿼리·스레드별 중간·최종 결과 메모리 상한이며 기본값은 2GB다. 큰 결과에서 메모리 사용을 제한하지만, 상한에 도달하면 오류가 반환된다. 이번 시험도 같은 양상을 보였다.innodb_ft_result_cache_limit=2GB: 678,866건, 4.898초,mysqldRSS511→1,414MB공식 MySQL 응답과 Manticore 응답은 반환 의미가 다르므로
77ms→83ms같은 단순 속도 비교를 하면 안 된다. 이 시험의 중요한 결과는 현재 시험 색인 기준 전체 결과를 반환하면서 MySQL의 순간 900MB급 증가를 검색 서비스 쪽으로 격리할 수 있었다는 점이다. DB 원본과 색인 전체를 독립 재대조한 산출물은 아직 없으므로 “원본 DB와 완전 일치”로 확대하지 않는다.HTTP 500과 게시글 누락은 검색 응답 JSON으로 재확인했지만, error 188 원문과 위 direct count의 SQL·프로세스 표본은 이번 게시물에 동봉하지 않았다. 두 값은 당시 서버 로그와 별도 관측으로 구분한다.
Manticore 비용도 0은 아니다. 별도 운영 기록에서 X 전체 색인은 29.11초, indexer 최대 RSS 약 313MB, 인덱스 디스크 약 502MB였고 유휴 searchd RSS는 약 55~105MB였다. 이번 게시물에는 indexer·프로세스 원시 로그를 동봉하지 않았으므로 이 값은 보조 관측이다. Manticore를 G7 기본 검색엔진으로 채택하라는 결론도 아니다.
Laravel Scout는 driver 계층이며 custom
Engine등록을 지원한다. G7도app/Providers/ScoutServiceProvider.php:33-51에서EngineManager::extend()와core.search.engine_drivers훅을 이미 제공하며 이 부분은 공식 7.0.5부터 있던 기능이다. 이번 직결 시험 결과는 그대로 유효하지만, Scout adapter 비용과 모델 observer/queue 기반 증분 색인 동기화 완료 증거로 확대하지 않는다.서버 사양과 공유호스팅 판정
그누보드7 공식 요구사항은 PHP 8.2+, MySQL 8.0+ 또는 MariaDB 10.3+, 최소 디스크 700MB·권장 2GB+를 명시하지만 CPU·RAM 최소값은 제시하지 않는다. 아래 CPU·RAM은 공식 사양이 아니라 이번 데이터와 구성의 관측·운영 판단이다.
위 2GB guard와 FULLTEXT
+903MB는 같은 실행 결과가 아니다. 후자는 별도 4 vCPU·8GB VM에서innodb_ft_result_cache_limit=2GB로 측정한 관측이며, 이 문서는 둘을 합쳐 하나의 2GB 실측치로 주장하지 않는다.카페24 뉴아우토반 절약형은 2026-07-21 공식 페이지 확인 기준 700MB SSD, PHP 8.4/8.2, MariaDB 10.x, 일 트래픽 1.6GB를 제공한다. 설치 호환 가능성과 운영 적합성은 다르다. 700MB는 G7 공식 최소 디스크와 같아 캐시·로그·업로드·업데이트 여유가 없고 공유 CPU/RAM도 보장되지 않는다.
따라서 공유호스팅 판정은 다음처럼 나눈다.
참고 자료:
이번 결과가 증명하지 않는 것
UNVERIFIED다.+2.56%관측값을 회귀나 개선으로 확정하지 않았다.ROI 기준 다음 작업
gte/search_truncated로 UI까지 명확히 표시한다.재현성과 증거 경계
7.0.5, 커밋d5065f1bc9ebdc31a3e4e0d0c2e757138fd7423f3379ed93(외부 공개 전 push/merge 필요)526bf22d(외부 공개 전 push/merge 필요)81fece53(외부 공개 전 push/merge 필요)docs/benchmark/g7-7.0.5-performance-tuning-code-map-and-implementation-prompt-2026-07-21.mddocs/benchmark/g7-7.0.5-vm-performance-report-2026-07-21.md증거 수준은 구분해서 읽어야 한다.
VPS_PASS: 당시 VM 전환과 HTTP 의미·CPU/RSS/swap을 함께 확인. 공개 원시 JSON만으로 전환 ref를 독립 증명한다는 뜻은 아님AUTO_PASS: 자동 테스트 통과CODE_ONLY: 코드와 쿼리 구조만 확인UNVERIFIED: 데이터·부하·운영 조건이 없어 아직 주장 불가현재 결론은 “그누보드7은 무조건 느리다”도, “튜닝으로 모든 문제가 끝났다”도 아니다. 일반 게시판·쇼핑 경로의 큰 병목은 코드와 인덱스로 줄일 수 있었지만, 네이티브 MySQL 통합검색과 주문 기반 부하는 여전히 별도 개선·검증이 필요한 핵심 영역이다.