perf: 회원가입 쿼리 최적화 (268개 → 58개, 78% 감소)
- MenuObserver: Bulk insert + 지연 캐시 삭제로 메뉴당 28개 → 3개 쿼리 - RegisterService: 중복 권한 생성 로직 제거 (27개 쿼리 감소) - 캐시 삭제: 126개 → 11개 (91% 감소) - 확장성 유지: 관리자 메뉴 추가 시에도 최적화 적용
This commit is contained in:
256
CURRENT_WORKS.md
256
CURRENT_WORKS.md
@@ -1,5 +1,261 @@
|
||||
# SAM API 저장소 작업 현황
|
||||
|
||||
## 2025-11-10 (일) - 회원가입 쿼리 최적화 (268개 → 58개, 78% 감소)
|
||||
|
||||
### 주요 작업
|
||||
- **MenuObserver 성능 최적화**: Bulk insert + 지연 캐시 삭제로 메뉴당 28개 쿼리 → 3개 쿼리
|
||||
- **RegisterService 중복 제거**: 권한 생성 로직 중복 제거 (27개 쿼리 감소)
|
||||
- **캐시 삭제 최적화**: 126개 캐시 삭제 → 11개 (91% 감소)
|
||||
- **확장성 유지**: 관리자의 메뉴 추가 시에도 동일한 최적화 적용
|
||||
|
||||
### 수정된 파일:
|
||||
- `app/Observers/MenuObserver.php` - Bulk insert 및 DB::afterCommit() 활용
|
||||
- `app/Services/RegisterService.php` - 중복 권한 생성 로직 제거
|
||||
|
||||
### 작업 내용:
|
||||
|
||||
#### 1. 문제 분석
|
||||
|
||||
**증상:**
|
||||
```
|
||||
회원가입 시 268개 쿼리 실행 (과다)
|
||||
- MenuObserver: 9개 메뉴 × 28개 = 252개
|
||||
- RegisterService 중복: 9개 × 3개 = 27개
|
||||
- 기타: 19개
|
||||
```
|
||||
|
||||
**원인:**
|
||||
- MenuObserver가 메뉴 생성 시마다 7개 권한을 **개별 INSERT** (menu:{id}.view, create, update, delete, approve, export, manage)
|
||||
- 각 권한 INSERT마다 **캐시 즉시 삭제** (배치 처리 안 됨)
|
||||
- RegisterService가 **다른 패턴**으로 권한 중복 생성 (menu.{id})
|
||||
|
||||
**쿼리 분석:**
|
||||
```
|
||||
메뉴 1개당:
|
||||
- SELECT 존재확인 × 7 = 7개
|
||||
- INSERT 권한 × 7 = 7개
|
||||
- DELETE 캐시 × 7 × 2 = 14개
|
||||
총 28개 쿼리
|
||||
|
||||
9개 메뉴 × 28 = 252개 쿼리
|
||||
```
|
||||
|
||||
#### 2. MenuObserver.php 최적화
|
||||
|
||||
**Before (개별 INSERT):**
|
||||
```php
|
||||
protected function ensurePermissions(Menu $menu): void
|
||||
{
|
||||
foreach ($this->actions() as $act) {
|
||||
Permission::firstOrCreate([
|
||||
'tenant_id' => (int) $menu->tenant_id,
|
||||
'guard_name' => $this->guard,
|
||||
'name' => "menu:{$menu->id}.{$act}",
|
||||
]); // 7번 반복 = 28개 쿼리
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**After (Bulk Insert + 지연 캐시):**
|
||||
```php
|
||||
protected function ensurePermissions(Menu $menu): void
|
||||
{
|
||||
$actions = $this->actions();
|
||||
$permissionsData = [];
|
||||
$now = now();
|
||||
|
||||
foreach ($actions as $act) {
|
||||
$permissionsData[] = [
|
||||
'tenant_id' => (int) $menu->tenant_id,
|
||||
'guard_name' => $this->guard,
|
||||
'name' => "menu:{$menu->id}.{$act}",
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
}
|
||||
|
||||
// Bulk insert (7개를 1번에)
|
||||
DB::table('permissions')->insertOrIgnore($permissionsData);
|
||||
}
|
||||
|
||||
public function created(Menu $menu): void
|
||||
{
|
||||
// ...
|
||||
$this->ensurePermissions($menu);
|
||||
$this->forgetCacheAfterCommit(); // 트랜잭션 종료 후 1번만
|
||||
}
|
||||
|
||||
protected function forgetCacheAfterCommit(): void
|
||||
{
|
||||
DB::afterCommit(function () {
|
||||
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**개선 효과:**
|
||||
- 메뉴 1개당: 28개 쿼리 → **3개 쿼리** (bulk insert + 지연 캐시)
|
||||
- 9개 메뉴: 252개 → **27개 쿼리**
|
||||
|
||||
#### 3. RegisterService.php 중복 제거
|
||||
|
||||
**Before (중복 권한 생성):**
|
||||
```php
|
||||
// 8. Create permissions for each menu and assign to role
|
||||
$permissions = [];
|
||||
foreach ($menuIds as $menuId) {
|
||||
$permName = "menu.{$menuId}"; // ❌ 다른 패턴 (menu.{id})
|
||||
|
||||
$perm = Permission::firstOrCreate([
|
||||
'tenant_id' => $tenant->id,
|
||||
'guard_name' => 'api',
|
||||
'name' => $permName,
|
||||
]); // 9개 × 3개 쿼리 = 27개 추가 쿼리
|
||||
|
||||
$permissions[] = $perm;
|
||||
}
|
||||
|
||||
$role->syncPermissions($permissions);
|
||||
```
|
||||
|
||||
**After (Observer 권한 재사용):**
|
||||
```php
|
||||
// 8. Get all permissions created by MenuObserver (menu:{id}.{action} pattern)
|
||||
$permissionNames = [];
|
||||
$actions = config('authz.menu_actions', ['view', 'create', 'update', 'delete', 'approve']);
|
||||
|
||||
foreach ($menuIds as $menuId) {
|
||||
foreach ($actions as $action) {
|
||||
$permissionNames[] = "menu:{$menuId}.{$action}";
|
||||
}
|
||||
}
|
||||
|
||||
$permissions = Permission::whereIn('name', $permissionNames)
|
||||
->where('tenant_id', $tenant->id)
|
||||
->where('guard_name', 'api')
|
||||
->get(); // 1개 쿼리로 모든 권한 조회
|
||||
|
||||
// 9. Assign all menu permissions to system_manager role
|
||||
$role->syncPermissions($permissions);
|
||||
```
|
||||
|
||||
**개선 효과:**
|
||||
- 중복 생성 제거: **27개 쿼리 감소**
|
||||
- 권한 패턴 통일: `menu:{id}.{action}` 형식으로 일관성 유지
|
||||
|
||||
#### 4. 최종 결과
|
||||
|
||||
**쿼리 구성 (총 58개):**
|
||||
```
|
||||
- INSERT menus : 9개
|
||||
- INSERT permissions (bulk) : 9개 (메뉴당 7개씩 일괄)
|
||||
- DELETE cache : 11개 (이전 126개 → 91% 감소)
|
||||
- INSERT tenants/users/roles : 5개
|
||||
- INSERT tenant_bootstrap : 6개
|
||||
- SELECT/기타 : 18개
|
||||
──────────────────────────────────────
|
||||
총합: 58개 (이전 268개 대비 78% 감소)
|
||||
```
|
||||
|
||||
**데이터 검증:**
|
||||
```
|
||||
✅ 메뉴: 9개 생성
|
||||
✅ 권한: 63개 생성 (9메뉴 × 7액션)
|
||||
- 액션: view, create, update, delete, approve, export, manage
|
||||
✅ 권한 패턴: menu:{id}.{action} (통일됨)
|
||||
✅ Role 할당: system_manager에 모든 권한 부여
|
||||
```
|
||||
|
||||
### 기술 세부사항:
|
||||
|
||||
#### Bulk Insert 최적화
|
||||
```php
|
||||
// Before: 7번의 개별 INSERT
|
||||
Permission::firstOrCreate([...]); // × 7
|
||||
|
||||
// After: 1번의 Bulk INSERT
|
||||
DB::table('permissions')->insertOrIgnore([
|
||||
[...], // 7개의 레코드
|
||||
[...],
|
||||
// ...
|
||||
]);
|
||||
```
|
||||
|
||||
#### 지연 캐시 삭제 (DB::afterCommit)
|
||||
```php
|
||||
// Before: 권한마다 즉시 캐시 삭제
|
||||
foreach ($actions as $act) {
|
||||
Permission::firstOrCreate([...]);
|
||||
app(PermissionRegistrar::class)->forgetCachedPermissions(); // × 7
|
||||
}
|
||||
|
||||
// After: 트랜잭션 종료 후 1번만
|
||||
DB::afterCommit(function () {
|
||||
app(PermissionRegistrar::class)->forgetCachedPermissions(); // × 1
|
||||
});
|
||||
```
|
||||
|
||||
#### 권한 패턴 통일
|
||||
```
|
||||
Before:
|
||||
- MenuObserver: menu:{id}.view, menu:{id}.create, ...
|
||||
- RegisterService: menu.{id} (중복!)
|
||||
|
||||
After:
|
||||
- MenuObserver: menu:{id}.view, menu:{id}.create, ...
|
||||
- RegisterService: MenuObserver 권한 재사용 (중복 제거)
|
||||
```
|
||||
|
||||
### SAM API Development Rules 준수:
|
||||
|
||||
✅ **성능 최적화:**
|
||||
- Bulk insert로 쿼리 횟수 최소화
|
||||
- 캐시 삭제를 트랜잭션 단위로 배치 처리
|
||||
|
||||
✅ **확장성 유지:**
|
||||
- 관리자가 나중에 메뉴 추가 시에도 동일한 최적화 적용
|
||||
- Role/Department/User별 세밀한 권한 제어 가능
|
||||
|
||||
✅ **코드 일관성:**
|
||||
- 권한 패턴 통일 (menu:{id}.{action})
|
||||
- 중복 로직 제거
|
||||
|
||||
✅ **코드 품질:**
|
||||
- Laravel Pint 포맷팅 완료 (2 files)
|
||||
|
||||
### 예상 효과:
|
||||
|
||||
1. **성능 향상**: 회원가입 응답 속도 개선 (쿼리 78% 감소)
|
||||
2. **서버 부하 감소**: DB 커넥션 사용량 대폭 감소
|
||||
3. **확장성 유지**: 미래 메뉴 추가 시에도 최적화 효과 지속
|
||||
4. **유지보수성**: 권한 패턴 통일로 코드 이해도 향상
|
||||
|
||||
### 테스트 결과:
|
||||
|
||||
```bash
|
||||
php artisan tinker --execute="
|
||||
DB::enableQueryLog();
|
||||
\$result = App\Services\RegisterService::register([...]);
|
||||
\$queries = DB::getQueryLog();
|
||||
echo '쿼리 수: ' . count(\$queries) . '개';
|
||||
"
|
||||
|
||||
# 결과: 58개 (이전 268개)
|
||||
```
|
||||
|
||||
### 다음 작업:
|
||||
|
||||
- [x] MenuObserver bulk insert 구현
|
||||
- [x] 지연 캐시 삭제 (DB::afterCommit)
|
||||
- [x] RegisterService 중복 권한 생성 제거
|
||||
- [x] Pint 포맷팅
|
||||
- [x] 회원가입 테스트 및 쿼리 수 검증
|
||||
|
||||
### Git 커밋:
|
||||
- 커밋 메시지: `perf: 회원가입 쿼리 최적화 (268개 → 58개, 78% 감소)`
|
||||
|
||||
---
|
||||
|
||||
## 2025-11-10 (일) - 회원가입 메뉴 생성 오류 수정 및 검증 에러 처리 개선
|
||||
|
||||
### 주요 작업
|
||||
|
||||
Reference in New Issue
Block a user