Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unification of the overall project call structure and modification of load test operation method. #8

Merged
merged 23 commits into from
Jul 22, 2024

Conversation

hippo-an
Copy link
Contributor

이번 pull request 에 포함된 작업 내용 다음과 같은 내용을 포함합니다.

  • 데이터베이스의 일관된 방식의 트랜잭션 처리
  • 구조체 의존성으로 역할과 계층에 대한 명확한 구분
  • 내부적인 성능 평가의 작동 방식 변경

데이터베이스의 일관된 방식의 트랜잭션 처리

기존 코드에서 데이터베이스 트랜잭션 처리가 가지고 있는 문제점은 일관되지 못한 방식으로 트랜잭션 처리를 한다는 것이었습니다. 사용자가 commit 과 rollback 에 대해 직접 코드를 작성해야 했기 때문에 예기지 않은 데이터 정합에 오류가 발생할 수 있는 가능성이 생길 수 있습니다.

작업한 내용은 다음과 같습니다.

패키지 레벨 변수로 선언된 데이터베이스 인스턴스를 제거하고 서버 구조체에 repository 의존성을 만들어서 database 호출은 repository 계층에서만 할 수 있도록 했습니다. 명확히 repository 에 db 의존성이 주입되면서 전역적으로 호출할 수 없기 때문에 단방향의 의존성 흐름을 만들었습니다.

패키지 레벨 변수 사용은 빠르고 간단하게 설정할 수 있으나 애플리케이션 전역의 디버깅 및 상태 추적에 어려움을 가지게 하고, 의존성 설정이 불가능하기 때문에 테스트의 어려움을 초래합니다. 구조체에 의존성을 주입하는 방식은 테스트가 용이하며, 명확한 상태 관리가 가능하기 때문에 더 권장되는 방식입니다.

여기서 명확한 상태 관리는 다음과 같은 내용을 포함합니다.

  • 예측 가능성:
    • 상태가 어디에서 정의되고 어떻게 변경되는지를 쉽게 추적할 수 있습니다. 구조체의 필드로 상태를 관리하면, 상태의 정의와 변경이 구조체 내부로 한정되어 예측 가능해집니다.
  • 가독성:
    • 상태 관리가 명확하면 코드의 가독성이 높아집니다. 개발자나 코드 리뷰어가 상태의 흐름을 쉽게 이해할 수 있습니다.
  • 디버깅 용이성:
    • 문제가 발생했을 때 상태가 명확하게 관리되고 있으면, 디버깅이 쉽습니다.
  • 유지보수성:
    • 상태를 명확히 관리하면 코드의 유지보수가 쉬워집니다. 새로운 기능을 추가하거나 버그를 수정할 때 상태의 흐름을 이해하기 쉽기 때문입니다.
  • 동시성 문제 최소화:
    • 상태가 명확히 관리되면 동시성 문제를 줄일 수 있습니다. 전역 변수를 사용하면 여러 고루틴이 동일한 변수에 접근하면서 경쟁 상태(race condition)가 발생할 수 있지만, 구조체 내부의 상태는 그 구조체 인스턴스에 한정되어 동시성 문제가 줄어듭니다.

다음은 일관된 방식의 트랜잭션 처리를 위한 함수를 생성했습니다.

func (r *LoadRepository) execInTransaction(ctx context.Context, fn func(*gorm.DB) error) error {
	tx := r.db.WithContext(ctx).Begin()
	if tx.Error != nil {
		return fmt.Errorf("begin transaction error: %w", tx.Error)
	}

	err := fn(tx)
	if err != nil {
		if rbErr := tx.Rollback().Error; rbErr != nil {
			return fmt.Errorf("rollback error: %v, original error: %w", rbErr, err)
		}
		return err
	}

	return tx.Commit().Error
}

트랜잭션을 시작하고 param 으로 전달받은 function 을 수행하고 error 가 있는 경우 rollback 처리에 일관성을 가질 수 있는 함수를 만들고 동일한 트랜잭션 범위에 대한 쿼리 작업을 사용자가 정의할 수 있도록 했습니다.

다음은 Pagination 처리된 monitoring agent info 조회를 하는 트랜잭션을 지원하는 함수의 구현 예시입니다.

func (r *LoadRepository) GetPagingMonitoringAgentInfosTx(ctx context.Context, param GetAllMonitoringAgentInfosParam) ([]MonitoringAgentInfo, int64, error) {
	var monitoringAgentInfos []MonitoringAgentInfo
	var totalRows int64

	err := r.execInTransaction(ctx, func(d *gorm.DB) error {
		q := d.Model(&monitoringAgentInfos)

		if param.NsId != "" {
			q = q.Where("additional_ns_id = ?", param.NsId)
		}

		if param.McisId != "" {
			q = q.Where("additional_mcis_id = ?", param.McisId)
		}

		if param.VmId != "" {
			q = q.Where("additional_vm_id = ?", param.VmId)
		}

		if err := q.Count(&totalRows).Error; err != nil {
			return err
		}

		offset := (param.Page - 1) * param.Size
		if err := q.Offset(offset).Limit(param.Size).Find(&monitoringAgentInfos).Error; err != nil {
			return err
		}

		return nil
	})

	return monitoringAgentInfos, totalRows, err

}

계층의 역할에 대한 명확한 구분

기존엔 구조체의 의존성으로 호출되는 명확한 계층의 구분 없이 패키지 레벨 변수로 선언한 핸들러, 서비스, 매니저 등 각 역할을 하는 구조체를 호출하는 방식이었습니다.

이는 간단하나 앞서 설명한 이유에서 적절하지 않다는 판단이 들었습니다. 또한 프로젝트를 처음 접하는 개발자 입장에서 어떤 호출 순서를 가지는지 코드의 흐름을 따라가야만 판단할 수 있습니다.

구조체에 명확한 의존성을 설정을 통해 개선할 수 있겠다 생각했습니다.

서버 → 라우터 → 핸들러 → 서비스 → 레포지토리 로 요청의 흐름이 이어질 수 있도록 구조체를 선언하고 구현했습니다.

type AntServer struct {
	e        *echo.Echo
	services *antServices
}

type LoadService struct {
	loadRepo        *LoadRepository
	tumblebugClient *tumblebug.TumblebugClient
}

type LoadRepository struct {
	db *gorm.DB
}

핸들러는 요청에 필요한 값 매핑, 값 검증, 적절한 응답 형식으로 반환을 담당합니다.

서비스는 핸들러에서 적절한 값으로 파싱된 요청에 대한 여러 트랜잭션이 포함된 비지니스 로직을 수행합니다.

레포지토리는 데이터베이스 트랜잭션이 필요한 경우 서비스 계층을 통해서 호출합니다.


성능 평가의 작동 방식 변경

성능 평가를 위한 서버 프로비저닝 방식

기존 migration 된 리소스의 nsId, mcisId, vmId 를 받아서 해당 리소스와 동일한 커넥션과 네트워크를 가지는 Load Generator 를 위한 infra 를 provision 하는 방식이었습니다.

부하 테스트를 위한 인프라 프로비저닝을 위해 반드시 마이그레이션이 수행되어야 했으며 마이그레이션이 수행된 경우에도 마이그레이션 된 리소스의 nsId, mcisId 등 리소스의 상세 정보를 알아야만 했습니다.

  • 기존 request body 정보

    {
      "installLocation":"remote",
      "nsId": "testns01",
      "mcisId": "aws-sao-mcis",
      "vmId": "aws-sao-server-1"
    }

Tumblebug 에서 제공해주는 recommendVM api 를 사용하는 것으로 connection 사용에 있어 유연성을 높였으며, mcisDynamic api 를 통해 프로비저닝하는 방식으로 특정 리소스에 대한 결합 없이 성능 평가 프레임워크에서 필요한 리소스를 프로비저닝 할 수 있도록 하였습니다.

  • 변경 후 request body 정보

    {
      "installLocation": "remote"
    }

성능 평가 결과 파일 fetching 방식

기존 성능 평가 결과 파일을 가지고 오는 방식은 성능 평가가 종료된 후 scp 를 통해 전체 파일에 대한 copy 를 진행하는 방식이었습니다.

이는 두가지 문제점이 있다고 판단했는데

  1. 오래 진행되는 부하 테스트에서 결과 파일을 한번에 받아오는 경우 진행되는 상황에 대해 인지할 수 없다.
  2. 오래 진행되는 부하 테스트에서 결과 파일의 용량은 상당히 클 것이며, 한번에 다운로드 받기에는 네트워크 자원, 안정성 등에 문제가 있을 수 있다.

위 같은 문제를 일차적으로 해결하기 위해서 성능 평가가 시작되기 전 결과 파일을 fetching 하는 별도의 고루틴을 통해서 결과 파일을 주기적으로 가지고 올 수 있도록 변경하였습니다.

해당 고루틴에선 scp 명령이 아닌 rsync 를 사용하여 변경이 있는 부분에 대한 정보만 가지고 오는 방식으로 데이터 전송의 안정화와 효율성을 높였습니다.

이를 통해 사용자는 성능 평가 진행 상황에 대해서 인지할 수 있으며, 모든 데이터를 한번에 다운로드 받지 않을 수 있습니다.

hippo-an and others added 23 commits June 24, 2024 14:56
…. It has been confirmed that the handler part works without any problems, and work on the service, repository, manager, etc. will continue as additional code cleanup is required.
…ng information while load testing in accordance with the structure using the server struct
…nstallation and deletion logic based on namespace, mcis, and VM ID
…r load generator server and load generator install info
…l info

- Consideration: Ensure new LoadGeneratorServer records are inserted without deleting orphaned associations when inserting multiple LoadGeneratorInstallInfo records. Review the logical flow and verify GORM's behavior.

- Consideration: Address GORM errors occurring in the service layer, considering the dependency on GORM within the service layer.
- dockerfile base image should be ubuntu because every script is based on ubuntu.
- have to install sudo and curl because run health check by curl
- update database driver sqlite to postgres and docker-compose and make file also updated. if you use make up and down then can start and stop the application and database.
Consistent Transaction Handling, Clear Layer Separation, and Modify Load Test
@hippo-an hippo-an changed the title Consistent Transaction Handling, Clear Layer Separation, and Modify Load Test Unification of the overall project call structure and modification of load test operation method. Jul 22, 2024
@MZC-CSC MZC-CSC merged commit 7ae88ee into cloud-barista:main Jul 22, 2024
2 checks passed
MZC-CSC pushed a commit that referenced this pull request Sep 27, 2024
…heck

update label from string to map[string]string
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants