SCM Manager란?
SCM Manager는 Git, Subversion(SVN), Mercurial 등 다양한 버전 관리 시스템을 통합적으로 관리할 수 있는 오픈소스 형상관리 플랫폼이다. 일반적으로 Git이나 SVN을 단독으로 사용할 경우, 별도의 서버 설정이나 사용자 관리, 권한 관리가 번거롭다. SCM Manager는 이런 불편을 해소하기 위해 웹 기반 UI와 Rest API를 제공하며, 다음과 같은 기능을 지원한다.
SCM Manager 주요 특징
- Git, SVN, Mercurial 등 멀티 저장소 통합 관리
- 직관적인 웹 UI 제공
- 사용자 및 권한 관리 기능
- Rest API 기반의 자동화 및 연동 지원
- 플러그인 확장을 통한 기능 추가 가능
- 소스 코드 브라우징 및 변경 이력 조회
- 사내 구축형으로 운영 가능
Issue - 대량 코드 등록 시 성능 지연
개발 / 운영 과정에서 소스 코드를 한두 개씩 등록하는 것은 문제가 없지만, 특정 상황에서는 수십 건의 소스 파일을 한 번에 등록해야 하는 경우 문제가 발생했다. 예를 들어, 엑셀 파일로 정리해 한 번에 등록하는 방식이 있다.
Problem - SCM Rest API
SCM 시스템에서는 소스 등록을 위해 Rest API를 제공하고, Rest API를 문서 정리되어 있었지만, 안내하는 방식은 다음과 같은 한계가 있었다.
✅ 단일 파일 커밋만 지원
✅ 대량 등록 시 파일 건수만큼 반복 요청 필요
✅ 매 요청마다 네트워크 통신, 인증, 서버 처리 로직이 반복되어 누적 지연 발생
실제 테스트에서 엑셀로 준비한 41건의 소스 파일을 순차적으로 등록하자, 약 2분 24초(2.4분)가 소요되었다.
단순히 소스 등록 속도의 문제를 넘어, 운영 효율성과 자동화 측면에서 상당한 제약이 있어 개선이 필요!
Solution - 다중 커밋 & 메모리 기반 처리로 성능 개선
1) 다중 파일 커밋을 지원하는 API 활용
SCM Rest API 문서에는 없었지만, SVN 플러그인을 뜯어보니 내부적으로 다중 파일을 한 번에 커밋할 수 있는 API가 존재해 이를 활용하면 별도의 반복 요청 없이, 여러 파일을 한 번에 묶어서 처리할 수 있었다.
MultipartFormDataInput는 다중 파일 처리가 가능하기 때문에 해당 소스를 분석하여 양식에 맞게 값을 설정
protected void commitFiles(String ment) throws Throwable {
try {
String url;
int i = 0;
HttpHeaders headers = createHttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
JSONObject files = JSONObject.createNewInstance();
JSONObject commitBody = new JSONObjectImpl();
commitBody.put("commitMessage", StringUtil.isBlank(ment)? " ": ment);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
for (CommitData commitData: getFileCommitList()) {
if (commitData!= null)
{
//생성된 ByteArrayResource를 멀티파트 요청에 추가
body.add("file" + i, commitData); // 멀티파트로 파일 추가
files.put(commitData.getFilename(), toPath(commitData.getCommitPath() +"/"+ commitData.getFilename(), true) );
i++;
}
}
commitBody.put("names", files);
body.add("commit", commitBody.toString());
/**
* 파일 생성/수정 커밋
*/
url = getSCMProperties().getApiUrl() + "/v2/edit/"
+ getSCMProperties().getAdminId()
+ "/" + getProjectId()
+ "/create";
post(url, new HttpEntity<>(body, headers), String.class);
}
catch (Throwable t) {
if (t instanceof HttpClientErrorException)
{
String detailMessage = ((HttpClientErrorException)t).getResponseBodyAsString();
if (!StringUtil.isBlank(detailMessage))
{
JSONObject errorJson = JSONObject.fromObject(detailMessage);
String errorCode = errorJson.getString("errorCode");
// 40RaYIeeR1 : 기존 파일과 변경사항 없을 시 오류 코드
if (VALUE_SCM_NOT_MODIFY.equals(errorCode)) return;
}
}
LOG.debug(t);
if (t instanceof RuntimeException) {
throw (RuntimeException)t;
}
throw new JexRuntimeException(t);
}
finally {
closeSource();
}
}
2) 메모리 기반 처리
기존 방식은 엑셀 데이터를 기반으로, 매번 SCM에 템플릿을 요청해 파일을 생성한 뒤, 해당 파일을 서버로 전송하는 구조였다.
즉, SCM 템플릿 요청 → 파일 생성 → SCM Commit 순서로 동작했기 때문에, 중복된 템플릿 요청이 반복되고 불필요한 파일 생성으로 인해 디스크 I/O 비용이 발생한다.
이를 개선하기 위해, 템플릿 매니저를 싱글톤 패턴으로 설계하고, 요청된 템플릿은 메모리에 캐싱하여 재사용하는 구조로 변경하였다.
이로 인해 중복 요청을 제거하고 디스크 I/O를 줄여, 전체 성능을 크게 향상시킬 수 있었다.
@Component
public class StudioPRJSettingManager {
private Map<Class<?>, Map<String, AbstractTemplet>> templetPool = new Hashtable<>();
...
<T extends AbstractTemplet> T getTempletManager(String projectId, Class<T> targetClass, Supplier<T> supplier)
{
Map<String, AbstractTemplet> pool = templetPool.computeIfAbsent(targetClass, key-> new Hashtable<String, AbstractTemplet>());
return targetClass.cast(pool.computeIfAbsent(projectId, key -> supplier.get()));
}
}
Test 결과 - 극적인 성능 개선 확인
구분 | 요청-응답 시간 |
AS-IS 기존 구조 | 약 2.4분 |
TO-BE 개선 구조 | 약 30.77ms |
반응형
'이슈 해결' 카테고리의 다른 글
[서버 / 해킹] Redis 악성 코드 삽입(backup1~4) 및 해킹 대응 (0) | 2025.06.21 |
---|---|
[K8s / 오류] x509 certificate signed by unknown authority (0) | 2024.12.13 |
[Linux / 서버] Systemctl 서비스 생성 시 실행(ExecStart) 오류 (0) | 2024.11.07 |
[Jenkins / 오류] Failed to start Jenkins Continuous Integration Server 설정 파일 (0) | 2024.08.13 |
[Jenkins / 오류] Pipeline에서shell script 실행 방법(nohup) (1) | 2024.04.01 |