동기 vs 비동기

동기와 비동기라는 단어는 언뜻 봤을 때 동기가 동시에 처리되고 비동기가 동시에 처리되지 않을 줄 알았습니다.

하지만 막상 반대의 단어들이였고, 언제 동시성의 발생을 두고 만든 단어였습니다.

동기(synchronous)의 경우에는 요청이 들어왔을 때 응답이 올 때까지 다음 요청을 할 수 없습니다.

요청이 들어왔을 때 응답이 동시에 나가기에 동기인 것입니다.

예를 들어 줄이 긴 놀이 기구를 기다릴 때는 다른 기구를 타지 못하는 것, 로그인을 했을 때 대기 시간이 걸리면 다른 행위를 못하는 것과 같습니다,   

 

비동기(Asynchronous)의 경우에는 요청이 들어왔을 때 바로 응답이 오지 않더라도 다음 요청을 보낼 수 있습니다.

비동기 처리는 많은 요청을 한번에 보내 빠른 처리가 가능하기 때문에 시간적 효율성이 높습니다.

그렇기 때문에 우리는 네트워크 관련 작업들을 할 때 비동기적 처리로 구현합니다.

 

비동기 처리가 동기 처리에 비해 효율적이라고 하지만 설계는 동기 처리보다 복잡합니다. 또한 동기 처리는 하나씩 반드시 수행된다는 장점이 있습니다.

 

AOP에 정리할 겸 보다보니 log4j2에 대해서도 정리할 것이 많은 것 같아서 따로 블로그에 올리기로 하였습니다.

 

배경

logging 이란 어떤 이벤트가 일어났을 때 시스템의 상태와 작동 정보를 시간의 경과에 따라 기록하는 것을 말합니다. 로깅을 하는 것은 문제가 발생했을 때 동작을 파악해 문제를 파악, 해결하기 위해서입니다.

 

log4j2를 보기 전 log4라는 것이 있길래 읽어 보니 2015년도 개발이 중단된 로깅프로그램이였습니다. 일단은 상관 없긴 하지만 급하게 읽었으면 넘어갈 뻔 했기에 적습니다.

Logback
  • Logback은 수정 시 구성 파일을 자동으로 다시 로드할 수 있습니다.
  • 필터링 기능 제공합니다.
  • logback의 Logger클래스는 기본적으로 SLF4J API를 구현하므로 기본 구현으로 logback을 사용하여 SLF4J 로거를 호출할 때 오버헤드가 발생하지 않습니다.
  • Logback은 상세하고 지속적으로 업데이트되는 문서와 함께 제공됩니다.

https://logback.qos.ch/reasonsToSwitch.html

 

Reasons to prefer logback

Reasons to prefer logback over log4j 1.x Unless specified otherwise, when we say "log4j" we mean log4j 1.x. Logback brings a large number of improvements over log4j 1.x, big and small. They are too many to enumerate exhaustively. Nevertheless, here is a no

logback.qos.ch

 

Log4j2
  • Logback과 마찬가지로 Log4j 2는 수정 시 구성을 자동으로 다시 로드할 수 있습니다.
  • Logback과 마찬가지로 Log4j 2는 컨텍스트 데이터, 마커, 정규식 및 Log 이벤트의 기타 구성 요소를 기반으로 필터링을 지원합니다.
  • 다중 스레드 시나리오에서 비동기식 로거는 Log4j 1.x 및 Logback보다 처리량이 18배 더 높고 지연 시간이 훨씬 더 낮습니다.
  • 람다 식을 지원합니다.
  • Log4j 2 API는 최고의 성능을 제공하지만 Log4j 2는 Log4j 1.2, SLF4J, Commons Logging 및 java.util.logging(JUL) API를 지원합니다.

 

https://logging.apache.org/log4j/2.x/

 

Log4j – Apache Log4j 2

<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apa

logging.apache.org

정말 많은 내용들이 있었기 때문에 각자 대표적인 내용들만 적었습니다.

 

log4j2는 비동기식이며, 멀티스레드이기 때문에 Logback과 속도 측면에서 18배 정도 차이가 나며, 지원해주는 부분에서도 세세하게 다른 것을 느끼며 다음에는 객관적으로 선택,사용해봐야겠습니다.

'WEB-DEV > SPRING' 카테고리의 다른 글

AOP와 interceptor  (0) 2022.12.05
[Spring] Sticky Session vs Redis vs Session Clustering  (0) 2022.05.26

다른 페이지에 접속할 때마다 계속해서 로그인을 반복을 방지하기 위해서 로그인 체크 기능 AOP를 적용하였습니다.

AOP

AOP란 관점 지향 프로그래밍으로 흩어진 공통점을 모아 만든 관점을 의미합니다.

@Aspect를 사용하면 AOP를 구현할 수 있기 때문에 반복되는 코드들을 정리할 수 있습니다.
또한 재사용 용이하며, 코드 누락 위험성이 적어집니다.

@Aspect
@Component
@Order(Ordered.LOWEST_PRECEDENCE)
@Log4j2
@SuppressWarnings("unchecked")
public class AuthCheckAspect {
    @Autowired
    private UserService userService;

    @Around("@annotation(com.idolticketing.idolticketing.aop.LoginCheck) && @ annotation(loginCheck)")
    public Object UserLoginCheck(ProceedingJoinPoint jp, LoginCheck loginCheck) throws Throwable {
        log.debug("AOP - User Login Check Started");
        Object[] signatureArgs = jp.getArgs();

        HttpSession session = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest().getSession();
        String sessionUserId = null;
        boolean isAdmin = false;
}

@Component

스프링으로 자동 관리를 해주기위해서 빈으로 등록하는 어노테이션입니다.

@Around

joinpointAdvice가 삽입되는데 Advice에는 5가지 시점(@After, @AfterReturning, @AfterThrowing, @Around, @Before )이 존재합니다. 그 중 Around에는 메소드 호출 자체를 가로채서 비즈니스 메소드 실행 전,후 모두에 처리할 로직을 삽입 할 수 있습니다.

저는 LoginCheck를 할 시 메소드 호출를 가로챌 수 있도록 만들었고, 세션 값과 isAdmin 값을 설정하였습니다.

@log4j2

logging이란 어떤 이벤트가 발생했을 때 로그가 남는 것을 의미합니다. 이 어노테이션을 사용하면 로그인기록이 남아 로그인이 되었는지를 확인할 수 있습니다.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LoginCheck {
    public static enum Role{
        USER,ADMIN
    }
    Role type();
}

열거형인 enum을 사용하면 말 그대로 열거가 된다는 뜻입니다.

USER = 0, ADMIN = 1이 설정되었습니다.

@PostMapping("")
    @ResponseStatus(HttpStatus.CREATED)
    @LoginCheck(type = LoginCheck.Role.USER)
    public ResponseEntity<?> createBook(String userId, boolean isAdmin,
                                        @RequestBody BookDTO bookDTO,
                                        @ApiParam(value = "lang", defaultValue = "ko") @RequestParam String lang) {
        if (userId.equals(bookDTO.getUserId())) {
            bookService.createBook(bookDTO);
        } else {
            return new ResponseEntity<>("잘못된 접근입니다.", HttpStatus.BAD_REQUEST);
        }
        return new ResponseEntity<>(bookDTO, HttpStatus.OK);
    }

Book Controller에 와서 LoginCheck를 확인해 보면

@LoginCheck를 통해 @Around가 createBook 메소드가 호출되기 전 가로채 LoginCheck 메소드를 호출합니다.

타입은 Login.Role.USER로 설정되어 있어서 유저로 인식하게 됩니다.

 

이번에 로그인 체크 AOP를 다루면서 시점에 대해 자세히 공부할 수 있어서 좋았습니다.

joinpoint에 대해 매번 헷갈렸었는데 joinpoint에 Advice(시점이 있는)가 삽입되기 때문에 왜 joinpoint가 먼저와야 하는지에 대해 이해가 잘되어서 유용했습니다. 

Custom Exception을 사용하는 이유는 단순히 404 에러 등을 보다 자세한 상황에 대해서 알려주기 위해서 Custom Exception을 만듭니다.

implementation 'net.rakugakibox.util:yaml-resource-bundle:1.1'

시작하기 전 messageSource에서 사용할 리소스를 가져오기 위해서 gradle에 yaml resourece를 적었습니다.

//exception_en
unKnown:
  code: "-404"
  msg: "An unknown error has occurred."
userNotFound:
  code: "-403"
  msg: "This member not exist."
//excepion_ko
unKnown:
  code: "-404"
  msg: "알 수 없는 오류가 발생하였습니다."
userNotFound:
  code: "-403"
  msg: "존재하지 않는 회원입니다."

 

@RequiredArgsConstructor
@RestControllerAdvice
public class ExceptionAdvice {
    private final ResponseService responseService; 
    private final MessageSource messageSource;

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    protected CommonResult defaultException(HttpServletRequest request, Exception e) {
        
        return responseService.getFailResult(Integer.valueOf(getMessage("unKnown.code")), getMessage("unKnown.msg"));
    }

    @ExceptionHandler(CUserNotFoundException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    protected CommonResult userNotFoundException(HttpServletRequest request, CUserNotFoundException e) {
        
        return responseService.getFailResult(Integer.valueOf(getMessage("userNotFound.code")), getMessage("userNotFound.msg"))
    }

    private String getMessage(String code) {
        return getMessage(code, null);
    }
    private String getMessage(String code, Object[] args) {
        return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
    }
}
public class CUserNotFoundException extends RuntimeException {

    public CUserNotFoundException(String msg, Throwable t) {
        super(msg, t);
    }

    public CUserNotFoundException(String msg) {
        super(msg);
    }

    public CUserNotFoundException() {
        super();
    }
}

@RestControllerAdvice와 @ExceptionHandler를 설정하면 공통 코드로 분리하여 예외처리를 해버릴 수 있습니다.

만약 특정 Controller만 예외 처리를 하고 싶으시면 하위 Controller를 설정해서 예외처리가 적용될 것 입니다. 저 같은 경우에는 전체를 예외 처리하되, 만들 당시에 에러 로그를 읽어야 하는데 예외 처리로 넘어가져버려서 일단 주석 처리를 한 후 에러 부분을 수정한 후에 다시 예외 처리를 하는 식으로 해결을 하였는데 각자 편하신 대로 하셨으면 좋겠습니다.

 

@ExceptionHandler(Exception.class)는 예외 처리의 최상위를 담당하여 어떤 예외에 걸리지 않으면 이 예외를 통과

@ExceptionHandler(CUserNotFoundException.class)는 커스텀 예외 처리로 유저가 발견되지 않을 시 이 예외를 통과

 

messageSource는 code와 args와 LocaleContextHoler.getLocale()을 읽어 현재에 맞는 메세지를 가져옵니다.

CUserNotFoundException이라는 Custom Exception 만들어서 유저가 조회되지 않을 때 예외 상황을 알려줍니다.

  @PutMapping(value = "/login")
    public ResponseEntity<?> login(@RequestBody UserDTO userDTO, HttpSession session) {

        UserDTO userInfo = userService.login(userDTO);

        if(userInfo == null)
            throw new CUserNotFoundException( messageSource.getMessage("userNotFound.msg",null, LocaleContextHolder.getLocale()));
                                              LocaleContextHolder.getLocale()));
            ...생략

실제 UserController에서 Login을 하였을 때 유저의 정보가 null 값일 시 CUserNotFoundException이 실행되도록 하였습니다.

API 호출을 하였을 때 정상적으로 호출되는 것을 볼수 있습니다.

 

이번에 Custom 예외 처리를 하면서 이해가 안가는 부분들이 있었습니다. 공식 문서를 통해 @Controller와 @ControllerAdvice 클래스들은 Controller 메소드로부터 발생한 exception들을 처리하기 위해 @ExceptionHandler 메소드를 갖는다라고 하는데 Handler가 정확히 어떤 의미인지에 대해 궁금해져서 검색을 해보니 Controller에 @RequestMapping 어노테이션이 붙은 메서드를 의미로 사용한다라는 점을 알게 되어 흥미로웠습니다.

 

MVC패턴

MVC 패턴이란 Model–View–Controller의 앞글자만 따서 지칭한 것입니다. 

Model은 내부 비즈니스 로직을 의미하며 예로 DB를 의미합니다.

View는 말 그대로 사용자에게 보여지는 UI를 의미합니다.

Controller는 Vew와 Model 사이의 중개자 역할을 한다고 보면 됩니다. View에서 받은 요청을 처리하며, Model에게 어떻게 요청을 처리할지 알려주는 역할을 합니다. 

MVC 패턴을 사용하는 이유는 사용자에게 보여지는 UI 로직과 비즈니스 로직이 독립적이기 때문에 서로 상관하지 않고 원하는 부분만 따로 처리할 수 있으며, 유지 보수에 용이합니다.

MVC 패턴에 따라 Book,User,Help,Content Controller단을 만들었으며, Idol-Ticketing 서버 프로젝트를 하면서 제일 첫번째로 한 것은 계정 정보를 관리 할 수 있는 기능을 만드는 것이였습니다.

userController

@RestController
@RequestMapping(value = "/users")
public class UserController {

    @Autowired
    UserMapper userMapper;

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;

    }

    @PutMapping(value = "/login")//로그인
    public ResponseEntity<?> login(@RequestBody UserDTO userDTO, HttpSession session) {

        UserDTO userInfo = userService.login(userDTO);

        if (userInfo.isAdmin() == false) {
            SessionUtil.setLoginUserId(session, userInfo.getUserId());
            return new ResponseEntity<>(UserResponseDTO.builder()
                    .userId(userInfo.getUserId())
                    .name(userInfo.getName())
                    .code(201)
                    .message("일반 유저 로그인 성공").build(), HttpStatus.OK);
        } else if (userInfo.isAdmin()) {
            SessionUtil.setLoginAdminId(session, userInfo.getUserId());
            return new ResponseEntity<>(UserResponseDTO.builder()
                    .userId(userInfo.getUserId())
                    .name(userInfo.getName())
                    .code(202)
                    .message("관리자 로그인 성공").build(), HttpStatus.OK);
        } else {
            return new ResponseEntity<>(UserResponseDTO.builder()
                    .code(401)
                    .message("로그인 실패").build(), HttpStatus.NOT_FOUND);
        }
    }
REST API

 UserController를 만들때는 REST API URI 규칙을 지키며 회원가입, 로그인, 회원 수정, 로그아웃, 탈퇴 기능을 구성하였습니다.

REST는 Representational State Transfer의 약자로 클라이언트와 서버간에 데이터를 주고 받을 때 방식에 대한 아키텍처 스타일입니다. 

 REST API를 사용하는 이유는 웹의 장점(네트워크 연결만 되면 어디서든 실행이 가능하다.)을 활용할 수 있는 아키텍처로 만든 것이 REST이며, 가장 큰 특징은 각 요청이 어떤 동작이나 정보를 위한 것인지를 그 요청의 모습 자체로 추론이 가능하기 때문입니다.

REST API URI 규칙
  • 소문자를 사용한다.
  • 언더바 대신 하이픈을 사용한다.
  • URI의 마지막에는 슬래시를 포함하지 않는다.
  • 계층관계를 나타낼 때는 슬래시 구분자를 사용해야 한다.
  • 파일 확장자는 URI에 포함시키지 않는다.
  • 전달하고자 하는 자원의 명사를 사용하되, 컨트롤 자원을 의미하는 경우 예외적으로 동사를 허용한다.
  • URI에 작성되는 영어를 복수형으로 작성한다.

userDTO

@Data
@Builder
public class UserDTO {
    private String userId;                      //아이디
    private String name;                        //이름
    private String password;                    //비밀번호

    public UserDTO(){

    }

    public UserDTO(String userId,String name,String password){
        this.userId = userId;
        this.name = name;
        this.password = password;
    }
}

실제 DB에 들어갈 값들을 작성하여 userDTO로 구성하였습니다.

DTO를 사용하면 클라이언트가 요청한 데이터를 담아 전달하는 역할을 합니다.

DB Connection은 Java와 DB를 연결 객체로 연결될 때마다 객체를 만들면 db의 정보를 넘겨야하기 때문에 소요시간이 오래걸립니다. 그렇기 때문에 DB Connection Pool 값을 늘려 미리 DB Connection 객체를 많이 만들어 놓습니다.

userService

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;

    @Override
    public UserDTO login(UserDTO userDTO) {
        return userMapper.findByIdAndPassword(userDTO);
    }

}

@Service 을 통해 스프링 빈(Spring bean)이 자동으로 생성됩니다.

이는 스프링 특징인 제어의 역전(IoC)인데 객체의 생성 및 제어권을 사용자가 아닌 스프링이 맡는 것입니다.

 

userMappper.xml

    <select id="findByIdAndPassword" resultType="dto.UserDTO">
        SELECT userId,name,password,email,phone,address,isAdmin
        FROM user
        WHERE password = #{password}
        AND userId = #{userId}
    </select>

userMapper.java

@Mapper
public interface UserMapper {
     UserDTO findByIdAndPassword (UserDTO userDTO);//로그인
}

Mybatis를 사용하여 mapper 코드를 xml으로 작성 후 userMapper 인터페이스를 만들어 @Mapper를 설정해 주었습니다. 이렇게 하면 mapper 코드들을 한번에 관리할 수 있다는 장점이 있습니다.

 

postman API 호출

post, 회원가입 ,                          put,  로그인,                                 patch, 회원 수정

             put,로그아웃,                                                             delete, 회원탈퇴

 

API 호출까지 성공적으로 되는 것으로 파악되었으며, 이후 User Controller 뿐만 아니라, help,content,book Controller도 해결하였습니다.

Controller를 다루는 것이 처음은 아니였지만 REST API로 작업하는 것은 처음이였기 때문에 주소창에 값을 넘긴다는 것은 알지만 어떻게 넘겨야하는 것인지에 대해 명확히 개념이 잘 안서던 REST API에 대해 Patch는 리소스의 일부분의 값만 수정할 때 사용하고, Put은 리소스의 전체 값을 수정할 때 사용하며 get은 @RequestBody을 쓰면 안되고 @RequestParam을 써야 한다고 정리할 수 있는 좋은 경험이 되었습니다.

이번에 지속적 통합 (Continuous integration), 지속적 배포(Continuous Delivery)에 대해서 적용해 보았습니다.

통합하는 이유는 각각의 목적으로 관리하기 위해 독립적으로 여러 브랜친 나누어진 소스코드들을 하나의 브랜치로 통합하고, 배포하기 위해서입니다. 

 

통합 배포 도구는 TeamCity,AWS CodeDeploy,jenkins,DeployBot 등등 찾아보니 정말 많은 종류들이 있었지만 그 중에서도 이번에 git에 있는 코드들을 통합하기 위해 사용해 봤던 툴은 젠킨스였습니다. 

젠킨스를 사용한 가장 큰 중요한 점은 유료 툴 중에서도 무료 툴이였으며, 현재까지 사용자가 많다는 점, 플러그인 지원이 좋은 편입니다. 또한 설치 및 사용이 간단합니다. 

 

jenkins CI

젠킨스를 사용하기 위해서는 우선적으로 git에 있는 프로젝트부터 가져와야 합니다.

첫번째로 원하는 git 프로젝트의 주소창을 그대로 가져와 줍니다. 

위에서 프로젝트의 주소를 가져왔다면 소스코드 관리에서는 레파지토리 주소를 가져와 주고 Credientials에서는 git에서 따로 token(엑세스 권한)을 만든 다음 Add로 설정합니다.

 

빌드 유발에서는 GitHub hook trigger for GITScm polling을 선택을 합니다.

webhook이란 클릭 시 실행되는 이벤트를 통해 git에 트리거를 유발시켜 빌드를 받을 수 있게 하는 것입니다.

트리거라는 단어를 정리하지 않고 넘어갔던 부분이 문제가 되었습니다. 트리거를 직역하면 방아쇠라는 뜻을 의미합니다.

이것을 컴퓨터 용어로 좀 더 쉽게 이해하면 트리거라는 것은 자동으로 실행하게 할 수 있는 역할을 한다는 것입니다.

각자에게 맞는 gradle 버전을 설정해주신 다음 Tasks에 bootjar를 작성해줍니다. 

 

빌드 후 조치에는 log text에 BUILD SUCCESFUL을 적은 다음 Script에 SANPSHOT.jar 파일이 있는 경로를 작성해 줍니다.

 

java gradle 기준 경로 위치

java -jar build/libs/본인 파일명-0.0.1-SNAPSHOT.jar &

 

Spring과 tomcat이 실행되는 것을 알 수 있으며, port 8080으로 API 실행을 할 시 제대로 전송되는 것을 볼 수 있습니다.

 

CD 적용하기

webhook에서도 git으로 push를 해도 연결이 안되는 문제가 있었기 때문에 결국 로컬로 접속해 연결이 되는지 확인하기로 했습니다.

We couldn’t deliver this payload: failed to connect to host

https://ngrok.com/

 

ngrok - Online in One Line

Zero Trust Add SSO, Mutual TLS, IP Policy, and webhook signature verification.

ngrok.com

로컬에서 webhook에 접속하기 위해서는 ngrok 프로그램을 다운받아 주소를 받아야합니다.

ngrok http 젠킨스포트

Forwarding에 있는 주소를 복붙하셔서 webhook URL에 적으면 됩니다.

다시 git에 가서 push를 해보았을 때 정상적으로 push가 된 것을 볼 수 있습니다.

 

이번 젠킨스 CI/CD를 하면서 CI에 비해 CD에 대한 이해가 부족했던 것에 깨달았습니다. 지속적 배포라는 것이 자동화라는 것을 단순히 책으로만 외우다가 직접 git으로 push를 했을 때 젠킨스에서 빌드가 된다는 것을 보았을 때 이렇게 자동화가 이루어 지는 것이라면 너무 많은 빌드가 이루어지는 것보단 적은 빌드로 완성도 높은 프로그램을 만드는 것이 좋을 것이라 생각되었습니다.

아이돌 티켓팅 서버에서 가장 중요하게 생각하는 부분은 예매 API이였습니다. 

실제로 짧은 시간에 가장 많은 이벤트가 발생하기 때문입니다.

따라서 로그인 API와 예매 API를 성능 테스트하였습니다.

시나리오 : 고척돔 기준 6만명이 접속해 1분안에 매진된다는 상황, 초당 1000tps를 목표로 성능테스트 진행
목표 : Min과 Max의 격차를 최소화 할 것, RPS : 1000.
테스트 1

가장 먼저 테스트했던 시나리오 결과입니다. min은 0.001초, max는 10.528로 격차가 크며, RPS는 191으로 적습니다.

 

- 원인 및 해결방안

B-tree 구조에서 삽입을 할 시 데이터를 조회 후 노드를 추가하고 자식 노드가 부모 노드보다 더 많다면 상향식으로 적절한 위치를 찾아가기 때문에 시간복잡도가 증가합니다. 더불어 현재 만들어진 Mysql에서 PK가 2개였기 때문에 index 값을 삭제할 시 RPS 값이 증가한다는 것을 확인하고 primary key 중에서 userId를 삭제를 했습니다.

*여기서 헷갈리는 부분이 PK가 두개라는 부분이 무결성의 원칙을 어기는 것이냐는 것이였습니다. 하지만 결론적으로 두 칼럼을 합쳤을 때 중복이 아니면 무결성의 원칙을 지키는 것이라는 것을 알게 되었습니다.

Ex) [A=1,B=1],[A=1,B=2]는 중복된 것이 아니다.

테스트 2

RPS가 거의 3배 값인 351로 증가한 것을 볼 수 있습니다. 하지만 min 값은 max 값은 오히려 11.611초로 증가했습니다. 

 

마지막으로 해결해야하는 부분은 시간적 요소였습니다. 자바 프로그램에 DBMS로 커넥션을 생성하려면(생성할 때마다 db정보(db host, port, connection-poolsize, id, password, db name, encoinding)를 만듦) 시간이 많이 소요됩니다. 그러므로 미리 db 커넥션을 많이 만들어놓으므로 그 시간을 단축할 수 있도록 만드는 것입니다.

application.properties에서 connection-pool 값을 10에서 20으로 변경하였습니다.

spring.datasource.maximum-pool-size=20
테스트 3

최종적으로 min 값은 0.001초 max 값은 8.774초 RPS는 357로 증가한 것을 볼 수 있습니다.

마무리로 RPS를 증가시키기 위해서 scale-out을 이용하여 다른 서버를 3개 정도 증설하면 기존 원했던 RPS 1000에 도달 할 수 있습니다.

 

locust을 이용해서 실제처럼 RPS 값을 볼 수 있다는 점이 재밌었던 기억이 납니다. 하지만 제 컴퓨터의 사양에 따라 달라졌던 값이였기 때문에 이것을 토대로 이렇구나를 참조하는 것이지 백프로 믿고 나아가기에는 문제가 있습니다.

또한 locust말고도 많은 성능 테스트가 있기 때문에 좀 더 다양한 경험을 쌓는 게 중요하지 않을 까 싶습니다.

putty를 이용하여 ubuntu에 접속해 mysql을 연결하려고 했으나 제대로 설치가 되었는지 sudo netstat -tap | grep mysql 명령어를 치면 netstat : command not found가 나왔습니다.

 

검색을 해보니까 net-tools을 포함하는 관련 패키지인 netsta가 설치 되어 있지 않아 발생한 에러였기에 수동으로 설치해주면 해결했습니다.

$ sudo apt update
$ sudo apt install net-tools

다시 sudo netstat - tap | grep mysql 을 치면 성공적으로 돌아가는 걸 볼 수 있습니다

 

 

https://linuxconfig.org/bash-netstat-command-not-found-debian-ubuntu-linux

 

bash: netstat: command not found – Debian/Ubuntu Linux

In this tutorial, we show how to install the missing netstat command on Ubuntu and Debian Linux based systems.

linuxconfig.org

 

 

'WEB-DEV > etc study' 카테고리의 다른 글

동기와 비동기란  (0) 2022.12.01
URI과 URL 차이에 대해서  (0) 2022.06.14
[컴퓨터]메모리가 디스크보다 성능이 빠른 이유  (0) 2022.05.31
MySQL Safe mode 해제하기  (0) 2022.05.12

+ Recent posts