코드 최적화
사용자에게 보이는 성능과 신뢰성을 개선하면서 코드를 이해하기 쉽게 유지하고 운영 비용을 적절하게 유지합니다. 존재하는 워크로드를 최적화하고, 가상의 미래 규모를 위해 복잡성을 추가하지 마십시오.
워크플로우
- 목표와 가드레일을 정의합니다. 관련 엔드포인트, 작업, 쿼리, 워크로드 및 원하는 결과를 식별합니다. 중요한 지표(예: p95/p99 지연 시간, 처리량, CPU, 메모리, 할당 속도, 데이터베이스 쿼리 수, 종속성 라운드 트립, 오류율, 포화도)만 선택합니다. 동작, 일관성, 보안 및 기존 오류 계약을 유지합니다. 누락된 대상이나 워크로드가 건전한 선택을 방해하는 경우에만 하나의 초점 질문을 하고, 그렇지 않으면 요청과 코드에서 추론할 수 있는 것을 추정합니다.
- 베이스라인을 설정합니다. 기존 트레이스, 프로필, 쿼리 계획, 메트릭, 테스트 또는 대표적인 벤치마크를 사용합니다. 입력, 환경 및 동시성을 이전과 이후에 비교 가능하게 유지합니다. 직관이나 단일 잡음이 있는 타이밍으로 속도 향상을 주장하지 마십시오. 승인 없이 프로덕션에서 부하 테스트나 비용이 많이 드는 쿼리 분석을 실행하지 마십시오.
- 병목을 찾습니다. 종단 간 중요 경로를 추적하고 프로파일링 또는 계측을 사용하여 CPU 작업, 블로킹, 데이터베이스 시간, 네트워크 대기, 직렬화, 잠금 경합 및 큐잉을 구분합니다. 실제 쿼리/호출 패턴과 대표적인 데이터를 검사합니다. 광범위한 정리나 마이크로 최적화보다는 증거를 우선시합니다. 병목이 확립되지 않은 경우, 불확실성을 보고하고 침습적인 변경을 하기 전에 가장 작은 유용한 측정을 제안합니다.
- 가장 작은 효과적인 변경을 선택합니다. 가능할 때마다 한 번에 하나의 중요한 병목을 최적화합니다. 출력, 순서, 실패 동작, 트랜잭션 의미 및 보안 검사를 유지합니다. 벤치마크 승리를 위해 정확성이나 신뢰성을 희생하지 마십시오. 저장소의 규칙을 따르고 관련 없는 리팩터링을 피하십시오.
- 두 결과를 모두 검증합니다. 관련 정확성 테스트를 실행하고 동일한 대표 워크로드를 이전과 이후에 비교합니다. 대상 지표가 개선되었고 가드레일(특히 꼬리 지연 시간, 오류, 메모리, 종속성 포화도)이 퇴보하지 않았는지 확인합니다. 의미 있는 비교가 불가능하면 그렇게 말하고 측정되지 않은 변경을 입증된 최적화로 제시하지 마십시오.
- 결과를 보고합니다. 병목과 증거, 수행되거나 권장된 변경, 검증 및 측정된 영향(또는 측정할 수 없었던 이유), 그리고 중요한 트레이드오프를 간결하게 기술합니다. 감사 또는 계획 요청의 경우, 코드를 변경하는 대신 증거, 예상 영향, 노력 및 위험에 따라 발견 사항의 우선 순위를 지정합니다.
데이터베이스 및 데이터 액세스 최적화
- 쿼리 수를 세고 ORM/데이터베이스 액세스 패턴을 검사합니다. 적절한 조인, 배치 또는 집합 기반 연산으로 N+1 읽기 또는 쓰기를 제거합니다. 쿼리 수를 확인하고 결과 순서와 누락 레코드 동작을 유지합니다.
- 필요한 열과 행만 가져옵니다. 큰 결과 집합에 대해 페이지네이션 또는 스트리밍을 사용하고, 응용 프로그램 메모리에 큰 데이터 세트를 로드하는 대신 적절한 필터링과 집계를 데이터베이스로 푸시합니다.
- 쿼리 계획과 실제 술어를 사용하여 인덱스를 정당화합니다. 선택도, 정렬/조인 요구 사항, 기존 인덱스 및 쓰기/저장소 오버헤드를 확인합니다. 추측성 또는 중복 인덱스를 추가하지 마십시오. 대표적인 데이터에서 계획과 지연 시간을 검증하고, 안전한 환경에서만 실행 기반 계획 분석을 사용합니다.
- 불필요한 라운드 트립을 줄이고 트랜잭션 및 잠금 범위를 필요한 것보다 크게 유지하지 마십시오. 제약 조건, 격리, 검증 또는 일관성을 단축으로 약화시키지 마십시오.
- 측정된 재사용이 이를 정당화하는 경우에만 캐싱을 추가합니다. 신선도, 무효화, 키 범위 및 실패 동작을 정의합니다. 느린 쿼리에 대한 기본 반응으로 캐시를 도입하지 마십시오.
백엔드 및 서비스 호출 최적화
- 중복 호출을 제거하고, 호환 가능한 요청을 배치 처리하며, 진정으로 독립적인 호출만 병렬화합니다. 동시성을 제한하고, 필요한 순서/종속성을 유지하며, 다운스트림 용량을 고려합니다.
- 클라이언트와 연결 풀을 재사용합니다. 요청 예산에 맞는 데드라인/타임아웃을 설정하고 지원되는 경우 취소를 전파합니다.
- 재시도가 안전한 작업에 대해서만 일시적 실패를 재시도합니다. 시도 횟수와 총 재시도 시간을 제한하고, 지터가 있는 백오프를 사용하며, 로드를 증폭시키는 방식으로 여러 계층에서 재시도하지 마십시오. 입증된 실패 모드가 없는 경우 재시도, 회로 차단기 또는 큐를 추가하지 마십시오.
- 과도한 페이로드 또는 반복적인 직렬화가 측정된 경로에 중요한 경우에만 줄입니다. 필수 서비스 실패 의미 체계를 그대로 유지하고, 선택적 종속성에 대해 명시적으로 정상적인 성능 저하를 만듭니다.
런타임, 신뢰성 및 유지보수성
- 증거가 지원하는 가장 영향력 있는 수정을 선호합니다: 알고리즘 복잡성 개선, 반복 작업 제거, 불필요한 할당/복사 감소, 또는 저수준 마이크로 최적화 전에 블로킹 및 경합 해결.
- 동시성에서 리소스 사용을 제한합니다: 메모리, 큐, 풀, 팬아웃, 타임아웃, 취소 및 역압력을 고려합니다. 실패가 재시도 폭풍, 무제한 작업 또는 리소스 누수를 일으키지 않도록 합니다.
- 성능과 신뢰성을 시스템 수준의 결과로 취급합니다. 데이터베이스를 과부하시키거나 오류율을 증가시키는 더 빠른 호출자는 최적화가 아닙니다.
- YAGNI를 적용합니다: 가상의 병목에 대해 종속성, 추상화, 캐시, 인덱스, 비동기 계층, 구성 옵션 또는 아키텍처 변경을 추가하지 마십시오. 명확한 로컬 변경을 선호합니다. 복잡성이 필요한 경우 측정된 이점과 유지 관리 비용을 설명하십시오.
- 무시할 만한 시간을 절약하기 위해 입력 검증, 권한 부여, 관찰 가능성 또는 안전 검사를 제거하지 마십시오. 측정된 이점이 실질적이지 않은 한, 읽기 쉬운 코드를 유지보수하기 어려운 마이크로 최적화로 대체하지 마십시오.
검증 참고 사항
- 동일한 워크로드를 반복 가능한 실행과 충분한 샘플로 비교하여 신호와 잡음을 구분합니다. 평균뿐만 아니라 관련성이 있는 경우 분포(특히 꼬리 지연 시간)를 사용합니다.
- 배치, 캐싱, 병렬 처리, 페이지네이션, 재시도 또는 동시성에 의해 영향을 받은 기능적 동작 및 에지 케이스를 검증합니다. 쿼리 수, 순서, 타임아웃 동작 또는 선택적 서비스 폴백과 같은 의미 있는 불변성을 보호하는 곳에 초점을 맞춘 테스트를 추가합니다.
- 변경 후 다시 프로파일링합니다: 병목 지점이 이동하며, 로컬 개선이 다른 서비스나 리소스로 비용을 전가할 수 있습니다.
- 최적화가 실질적인 개선을 생성하지 않거나 복잡성이 이점보다 클 경우, 최적화를 되돌리거나 반대를 권장합니다.