ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • withEmployee(Springboot + React) 프로젝트 코드개선 및 성능 개선
    학습로그 2022. 6. 5. 03:08

     

     

     

     

    walkerholic 프로젝트에 이어 withEmployee 프로젝트에서도 성능을 측정해 보았다. 

     

     

     

     

    성능 측정 결과 

    성능측정 결과는 다음과 같았다. 

    https://www.webpagetest.org/result/220604_AiDcE4_6PF/

     

    WebPageTest Performance Test Results

    Check out these web performance test results on WebPageTest.org:

    www.webpagetest.org

     

     

     

    Compress transfer를 제외하고는 모두 A점수를 받았고, First Byte, FIrst VIew, FCP, Speed Index, LCP 모두 양호한 것으로 확인되었다. 

     

     

    성능점수도 96점으로 측정되었다. 

     

     

     

     

     

     

    부하테스트 

    로그인하고 회사목록, 대화 목록을 가져오는 부분의 부하테스트 결과는 다음과 같았다. 

     

    1) Smoke 테스트 

     

     

    2) Load 테스트 

     

     

    로그인하고 팀 정보를 변경하는 요청에 대한 부하테스트 결과는 다음과 같았다. 

     

    1) Smoke 테스트 

     

    2) Load 테스트 

     

     

     

    인덱스 unique 설정을 이용한 코드 개선 

    팀 정보를 변경할 때는 같은 회사에 같은 이름의 팀이 존재하는지 먼저 확인한 후 이름을 변경하도록 설정했다. 

    기존에 쿼리 실행 결과를 보면 company_id에만 인덱스 설계가 되어있기 때문에 non-unique key 조회를 하는 것을 확인할 수 있었다. 

    또한, 코드를 보면 인덱스에 대한 unique 설정이 없었기 때문에 같은부분을 계속 조회하여 검사하는 부분이 확인된다. 

    // Team Repository
    @Repository
    public
    interface TeamRepository extends JpaRepository<Team, Integer> {
    
        boolean existsByName(String name);
    
        Optional<Team> findByName(String name);
    
    }
    
    // Team Service
    @Service
    @Transactional(readOnly = true)
    public class TeamService {
    
        @Transactional
        public TeamResponse create(TeamRequest request) {
            checkName(request.getName());
            Company company = companyService.findCompanyById(request.getCompanyId());
            Team team = teamRepository.save(request.toTeam(company));
            return TeamResponse.of(team, userService.simpleUserResponses(team.getUsers()));
        }
    
        private void checkName(String name) {
            checkNameIsEmpty(name);
            if (teamRepository.existsByName(name)) {
                throw new TeamNameAlreadyInUseException("This team name is already in use. name : " + name);
            }
        }
        
            @Transactional
        public TeamResponse update(LoginUser loginUser, Integer id, TeamRequest request) {
            checkName(id, request.getName());
            Team team = findTeamById(id);
            checkIsCeo(loginUser, team);
            team.changeName(request.getName());
            return TeamResponse.of(team, userService.simpleUserResponses(team.getUsers()));
        }
    
        private void checkName(Integer id, String name) {
            checkNameIsEmpty(name);
            if (teamRepository.existsByName(name) && !findTeamByName(name).isId(id)) {
                throw new TeamNameAlreadyInUseException("This team name is already in use. name : " + name);
            }
        }
    }

     

     

    이 부분을 인덱스 설계를 통하여 불필요한 부분을 제거하도록 하자. 

    // Team 객체 
    @Entity
    @Table(name = "team", indexes = @Index(name = "idx_name_companyId", columnList = "name, company_id", unique = true))
    @Getter
    @NoArgsConstructor
    public class Team {
    }
    
    // Team Service
    @Service
    @Transactional(readOnly = true)
    public class TeamService {
    
        @Transactional
        public TeamResponse create(TeamRequest request) {
            checkNameIsEmpty(request.getName());
            Company company = companyService.findCompanyById(request.getCompanyId());
            Team team = saveTeam(request.toTeam(company));
            return TeamResponse.of(team, userService.simpleUserResponses(team.getUsers()));
        }
        
        private Team saveTeam(Team team) {
            try {
                return teamRepository.save(team);
            } catch (DataIntegrityViolationException e) {
                throw new TeamNameAlreadyInUseException("This team name is already in use. name : " +  team.getName());
            }
        }
        
        @Transactional
        public TeamResponse update(LoginUser loginUser, Integer id, TeamRequest request) {
            checkNameIsEmpty(request.getName());
            Team team = findTeamById(id);
            checkIsCeo(loginUser, team);
            team.changeName(request.getName());
            saveTeam(team);
            return TeamResponse.of(team, userService.simpleUserResponses(team.getUsers()));
        }

     

    결과적으로 인덱스의 이름과 회사에 대해서 unique 옵션을 이용하기 때문에 불필요하게 findByName, existByName 등을 할 필요가 없어졌다. 

    인덱스 설계 모습

    필터링이 100으로 잘 되는 것을 확인할 수 있다. 

     

     

     

    추가로 캐시 부분과 함께 적용하면 팀 업데이트 테스트에서 다음과 같은 결과를 확인할 수 있었다. 

    1) Smoke 테스트 

     

     

    2) Load 테스트 

     

     

     

    리버스 프록시 성능개선

    여기에 속도 개선을 위해서 리버스 프록시를 이용한 gzip 및 캐시 설정을 추가해주었다. 

    events {}
    
    http {       
      upstream app {
        server 172.17.0.1:8080;
      }
      
      # Redirect all traffic to HTTPS
      server {
        listen 80;
        return 301 https://$host$request_uri;
      }
    
      ## Proxy 캐시 파일 경로, 메모리상 점유할 크기, 캐시 유지기간, 전체 캐시의 최대 크기 등 설정
      proxy_cache_path /tmp/nginx levels=1:2 keys_zone=mycache:10m inactive=10m max_size=200M;
    
      ## 캐시를 구분하기 위한 Key 규칙
      proxy_cache_key "$scheme$host$request_uri $cookie_user";
    
      server {
        listen 443 ssl http2 ;  
        ssl_certificate /etc/letsencrypt/live/withemployee.n-e.kr/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/withemployee.n-e.kr/privkey.pem;
    
        # Disable SSL
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    
        # 통신과정에서 사용할 암호화 알고리즘
        ssl_prefer_server_ciphers on;
        ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;
    
        # Enable HSTS
        # client의 browser에게 http로 어떠한 것도 load 하지 말라고 규제합니다.
        # 이를 통해 http에서 https로 redirect 되는 request를 minimize 할 수 있습니다.
        add_header Strict-Transport-Security "max-age=31536000" always;
    
        # SSL sessions
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;
    
        gzip on; ## http 블록 수준에서 gzip 압축 활성화
        gzip_comp_level 9;
        gzip_vary on;
        gzip_types text/plain text/css application/json application/x-javascript application/javascript text/xml application/xml application/rss+xml text/javascript image/svg+xml application/vnd.ms-fontobject application/x-font-ttf font/opentype;
    
    
        location / {
          proxy_pass http://app;
          
          // Web Socket을 위해서 추가되어야 하는 부분
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection "upgrade";
          proxy_http_version 1.1;
        }
      }
    }

     

     

     

     

    DB replication 적용 및 Flyway 마이그레이션 버전 적용하기 

    이제 Master DB와 Slave DB를 구성하여 post, delete, put 등의 정보변경 요청이 올때는 master DB를 이용하고 ReadOnly = true 옵션이 적용된 get 요청은 Slave DB를 이용하도록 하여 데이터베이스의 요청 부하를 줄이도록 하였다. 이때, Flyway(DDL을 통한 데이터베이스 형상관리)를 이용하여 데이터베이스의 버전이 관리되도록하여 추후 리팩토링이 이루어져도 기존의 데이터 베이스에 안전하게 변경사항을 반영할 수 있도록 해보자. 

     

    먼저 Flyway적용을 위해 dependency를 추가하자. 

    여기서 flway-mysql 옵션은 최신 버전의 flyway를 이용할 경우 mysql 8.0버전이 상호적용되지 않아 추가해주었다. 

    		<dependency>
    			<groupId>org.flywaydb</groupId>
    			<artifactId>flyway-core</artifactId>
    			<version>8.5.11</version>
    		</dependency>
    
    		<dependency>
    			<groupId>org.flywaydb</groupId>
    			<artifactId>flyway-mysql</artifactId>
    			<version>8.5.12</version>
    		</dependency>
    	</dependencies>

     

    배포 설정파일에 다음과 같이 flyway 옵션을 추가해준다. 

    #flyway
    spring.flyway.enabled=true
    spring.flyway.baseline-on-migrate=true
    spring.flyway.baseline-version=2

     

    여기서 프로젝트에 이미 데이터가 담겨있기 때문에 빈 파일의 V1__init.sql 파일을 작성해주었다. 

    resources>db>migration>V1__init.sql 파일을 생성해준다. 

     

     

     

    이제 DB replication을 진행해보자. 

    먼저 Master 서버에서 다음과 같이 진행해준다. 

    $ mysql -u root -p
    Enter password: 
    
    ## 복제용 사용자 생성 
    mysql> create user 'replication_user'@'%' identified with mysql_native_password by '비밀번호';
    Query OK, 0 rows affected (0.04 sec)
    
    ## 복제용 사용자로 지정 
    mysql> grant replication slave on *.* to 'replication_user'@'%' ;
    Query OK, 0 rows affected (0.00 sec)
    
    ## 사용자 생성 확인 
    mysql> use mysql ;
    mysql> select user, host from user;
    +------------------+-----------+
    | user             | host      |
    +------------------+-----------+
    | replication_user | %         |
    | user             | %         |
    | debian-sys-maint | localhost |
    | mysql.infoschema | localhost |
    | mysql.session    | localhost |
    | mysql.sys        | localhost |
    | root             | localhost |
    +------------------+-----------+
    7 rows in set (0.00 sec)
    
    ## 권한 반영 
    mysql> flush privileges;
    Query OK, 0 rows affected (0.00 sec)
    mysql> exit
    Bye
    
    ## Master DB 설정 (해당 설정 주석 해제)
    $ cd /etc/mysql/mysql.conf.d
    $ sudo vi mysqld.cnf
    server-id = 1
    log_bin = /var/log/mysql/mysql-bin.log
    
    ## 서버 재시작 
    $ sudo systemctl restart mysql
    
    ## 기존에 생성해둔 백업 데이터 복사해준다. (Slave 서버에 옮겨줄 것이다)
    $ cat backup.sql
    
    ## Master 상태 확인하기 
    $ mysql -u root -p;
    Enter password: 
    mysql> show master status;
    +------------------+----------+--------------+------------------+-------------------+
    | File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
    +------------------+----------+--------------+------------------+-------------------+
    | mysql-bin.000001 |      157 |              |                  |                   |
    +------------------+----------+--------------+------------------+-------------------+
    1 row in set (0.00 sec)
    
    mysql> exit
    Bye

     

     

    Slave DB 용 EC2를 만들고 다음과 같이 진행해준다. 

    ## mysql 설치 
    $ sudo apt-get update
    $ sudo apt-get install mysql-server
    $ sudo ufw allow mysql
    $ sudo systemctl start mysql
    $ sudo systemctl enable mysql
    
    ## mysql 접속
    $ sudo /usr/bin/mysql -u root -p 
    Enter password: 
    
    ## 데이터베이스 생성 
    mysql> use mysql;
    mysql> create database with_employee;
    mysql> show databases;
    +--------------------+
    | Database           |
    +--------------------+
    | information_schema |
    | mysql              |
    | performance_schema |
    | sys                |
    | with_employee      |
    +--------------------+
    5 rows in set (0.00 sec)
    
    ## 프로그램에서 데이터베이스 연결시 사용할 사용자 생성 및 권한 부여 
    mysql> create user 'user'@'%' identified by '비밀번호';
    mysql> grant all privileges on with_employee.* to 'user'@'%';
    Query OK, 0 rows affected (0.01 sec)
    mysql> flush privileges;
    Query OK, 0 rows affected (0.01 sec)
    
    ## Slave 데이터 베이스로 설정
    mysql> set global server_id = 2;
    Query OK, 0 rows affected (0.00 sec)
    
    ## Master 연결  
    mysql> CHANGE MASTER TO MASTER_HOST='[Master private ip]', MASTER_PORT=3306, MASTER_USER='replication_user', MASTER_PASSWORD='비밀번호', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=157;
    Query OK, 0 rows affected, 9 warnings (0.04 sec)
    
    ## Slave 실행하고 상태 확인 
    mysql> start slave;
    Query OK, 0 rows affected, 1 warning (0.01 sec)
    mysql> show slave status\g;
    +----------------------------------+-------------+------------------+-------------+---------------+------------------+---------------------+---------------------------------+---------------+-----------------------+------------------+-------------------+-----------------+---------------------+--------------------+------------------------+-------------------------+-----------------------------+------------+------------+--------------+---------------------+-----------------+-----------------+----------------+---------------+--------------------+--------------------+--------------------+-----------------+-------------------+----------------+-----------------------+-------------------------------+---------------+---------------+----------------+----------------+-----------------------------+------------------+--------------------------------------+-------------------------+-----------+---------------------+----------------------------------------------------------+--------------------+-------------+-------------------------+--------------------------+----------------+--------------------+--------------------+-------------------+---------------+----------------------+--------------+--------------------+------------------------+-----------------------+-------------------+
    | Slave_IO_State                   | Master_Host | Master_User      | Master_Port | Connect_Retry | Master_Log_File  | Read_Master_Log_Pos | Relay_Log_File                  | Relay_Log_Pos | Relay_Master_Log_File | Slave_IO_Running | Slave_SQL_Running | Replicate_Do_DB | Replicate_Ignore_DB | Replicate_Do_Table | Replicate_Ignore_Table | Replicate_Wild_Do_Table | Replicate_Wild_Ignore_Table | Last_Errno | Last_Error | Skip_Counter | Exec_Master_Log_Pos | Relay_Log_Space | Until_Condition | Until_Log_File | Until_Log_Pos | Master_SSL_Allowed | Master_SSL_CA_File | Master_SSL_CA_Path | Master_SSL_Cert | Master_SSL_Cipher | Master_SSL_Key | Seconds_Behind_Master | Master_SSL_Verify_Server_Cert | Last_IO_Errno | Last_IO_Error | Last_SQL_Errno | Last_SQL_Error | Replicate_Ignore_Server_Ids | Master_Server_Id | Master_UUID                          | Master_Info_File        | SQL_Delay | SQL_Remaining_Delay | Slave_SQL_Running_State                                  | Master_Retry_Count | Master_Bind | Last_IO_Error_Timestamp | Last_SQL_Error_Timestamp | Master_SSL_Crl | Master_SSL_Crlpath | Retrieved_Gtid_Set | Executed_Gtid_Set | Auto_Position | Replicate_Rewrite_DB | Channel_Name | Master_TLS_Version | Master_public_key_path | Get_master_public_key | Network_Namespace |
    +----------------------------------+-------------+------------------+-------------+---------------+------------------+---------------------+---------------------------------+---------------+-----------------------+------------------+-------------------+-----------------+---------------------+--------------------+------------------------+-------------------------+-----------------------------+------------+------------+--------------+---------------------+-----------------+-----------------+----------------+---------------+--------------------+--------------------+--------------------+-----------------+-------------------+----------------+-----------------------+-------------------------------+---------------+---------------+----------------+----------------+-----------------------------+------------------+--------------------------------------+-------------------------+-----------+---------------------+----------------------------------------------------------+--------------------+-------------+-------------------------+--------------------------+----------------+--------------------+--------------------+-------------------+---------------+----------------------+--------------+--------------------+------------------------+-----------------------+-------------------+
    | Waiting for source to send event | 172.31.0.14 | replication_user |        3306 |            60 | mysql-bin.000001 |                 157 | ip-172-31-0-56-relay-bin.000002 |           326 | mysql-bin.000001      | Yes              | Yes               |                 |                     |                    |                        |                         |                             |          0 |            |            0 |                 157 |             545 | None            |                |             0 | No                 |                    |                    |                 |                   |                |                     0 | No                            |             0 |               |              0 |                |                             |                1 | 6c169aed-e3ef-11ec-b1ab-020fcea173ea | mysql.slave_master_info |         0 |                NULL | Replica has read all relay log; waiting for more updates |              86400 |             |                         |                          |                |                    |                    |                   |             0 |                      |              |                    |                        |                     0 |                   |
    +----------------------------------+-------------+------------------+-------------+---------------+------------------+---------------------+---------------------------------+---------------+-----------------------+------------------+-------------------+-----------------+---------------------+--------------------+------------------------+-------------------------+-----------------------------+------------+------------+--------------+---------------------+-----------------+-----------------+----------------+---------------+--------------------+--------------------+--------------------+-----------------+-------------------+----------------+-----------------------+-------------------------------+---------------+---------------+----------------+----------------+-----------------------------+------------------+--------------------------------------+-------------------------+-----------+---------------------+----------------------------------------------------------+--------------------+-------------+-------------------------+--------------------------+----------------+--------------------+--------------------+-------------------+---------------+----------------------+--------------+--------------------+------------------------+-----------------------+-------------------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> show slave status\G;
        Slave_IO_Running: Yes
        Slave_SQL_Running: Yes
    ## 만약 여기서 SQL_Running이 Yes가 아니라면 다음 실행 
    mysql> stop slave;
    mysql> SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;
    mysql> start slave;
    
    ## mysqld 설정(해당 주석 해제하고 server-id = 2 설정)
    $ cd /etc/mysql/mysql.conf.d
    $ sudo vi mysqld.cnf
    	binding = 0.0.0.0
        server-id = 2
        log_bin = /var/log/mysql/mysql-bin.log
    $ sudo systemctl restart mysql
    
    ## 백업 DB 반영해주기
    $ mysql -u root -p -D with_employee < backup.sql;
    Enter password: 
    mysql> show databases;
    +--------------------+
    | Database           |
    +--------------------+
    | information_schema |
    | mysql              |
    | performance_schema |
    | sys                |
    | with_employee      |
    +--------------------+
    
    ## DB 반영 확인 
    mysql> use with_employee;
    mysql> show tables;
    +-------------------------+
    | Tables_in_with_employee |
    +-------------------------+
    | company                 |
    | conversation            |
    | conversation_user       |
    | member_team             |
    | message                 |
    | team                    |
    | user                    |
    +-------------------------+
    7 rows in set (0.00 sec)
    
    $ sudo systemctl restart mysql

     

    Slave 설정이 완료되면 Master에서 다음과 같이 프로세스를 확인할 수 있다. 

    ## 데이터 주고 받는 프로세스 확인 
    mysql> show processlist\g
    +----+------------------+------------------------------------------------------+---------------+-------------+------+-----------------------------------------------------------------+------------------+
    | Id | User             | Host                                                 | db            | Command     | Time | State                                                           | Info             |
    +----+------------------+------------------------------------------------------+---------------+-------------+------+-----------------------------------------------------------------+------------------+
    |  5 | event_scheduler  | localhost                                            | NULL          | Daemon      | 1409 | Waiting on empty queue                                          | NULL             |
    |  9 | root             | localhost                                            | NULL          | Query       |    0 | init                                                            | show processlist |
    | 24 | replication_user | ip-172-31-0-56.ap-northeast-2.compute.internal:      | NULL          | Binlog Dump |   29 | Source has sent all binlog to replica; waiting for more updates | NULL             |
    +----+------------------+------------------------------------------------------+---------------+-------------+------+-----------------------------------------------------------------+------------------+
    13 rows in set (0.00 sec)
    mysql> show processlist\G
    *************************** 9. row ***************************
         Id: 25
       User: replication_user
       Host: ip-172-31-0-56.ap-northeast-2.compute.internal:
         db: NULL
    Command: Binlog Dump
       Time: 284
      State: Source has sent all binlog to replica; waiting for more updates
       Info: NULL

     

     

    프로그램에 설정을 추가해준다. 이제 ReadOnly=true 트랜잭션에 대해서 Slave DB로 요청을 전달한다. 

    @Configuration
    @EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
    @EnableTransactionManagement
    @EnableJpaRepositories(basePackages = {"com.yunhalee.withEmployee"})
    class DataBaseConfig {
    
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.hikari.master")
        public DataSource masterDataSource() {
            return DataSourceBuilder.create().type(HikariDataSource.class).build();
        }
    
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.hikari.slave")
        public DataSource slaveDataSource() {
            return DataSourceBuilder.create().type(HikariDataSource.class).build();
        }
    
        @Bean
        public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource master,
            @Qualifier("slaveDataSource") DataSource slave) {
            ReplicationRoutingDataSource routingDataSource = new ReplicationRoutingDataSource();
    
            HashMap<Object, Object> sources = new HashMap<>();
            sources.put(DATASOURCE_KEY_MASTER, master);
            sources.put(DATASOURCE_KEY_SLAVE, slave);
    
            routingDataSource.setTargetDataSources(sources);
            routingDataSource.setDefaultTargetDataSource(master);
    
            return routingDataSource;
        }
    
        @Primary
        @Bean
        public DataSource dataSource(@Qualifier("routingDataSource") DataSource routingDataSource) {
            return new LazyConnectionDataSourceProxy(routingDataSource);
        }
    }
    public class ReplicationRoutingDataSource extends AbstractRoutingDataSource {
        public static final String DATASOURCE_KEY_MASTER = "master";
        public static final String DATASOURCE_KEY_SLAVE = "slave";
    
        @Override
        protected Object determineCurrentLookupKey() {
            boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
            return (isReadOnly)
                ? DATASOURCE_KEY_SLAVE
                : DATASOURCE_KEY_MASTER;
        }
    }

     

    실행 설정 파일에 다음과 같이 DB replication 설정을 추가해준다. 

    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.hikari.master.username=user
    spring.datasource.hikari.master.password=ThisIsUser1!
    spring.datasource.hikari.master.jdbc-url=jdbc:mysql://172.31.0.14:3306/with_employee?useSSL=false&useUnicode=yes&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&serverTimezone=UTC
    
    spring.datasource.hikari.slave.username=user
    spring.datasource.hikari.slave.password=ThisIsUser1!
    spring.datasource.hikari.slave.jdbc-url=jdbc:mysql://172.31.0.56:3306/with_employee?useSSL=false&useUnicode=yes&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&serverTimezone=UTC

     

     

    성능 개선 결과

    성능개선 설정이 완료된 프로그램의 결과는 다음과 같다. 

     

    https://www.webpagetest.org/result/220604_BiDcFZ_8AS/

     

    WebPageTest Performance Test Results

    Check out these web performance test results on WebPageTest.org:

    www.webpagetest.org

     

     


    gzip을 통해서 Compress Transfer 결과도 개선된 것을 확인할 수 있었다. 

     

     

     

    성능 점수도 개선되었다. 

    성능개선이 적용된 부하테스트 결과는 다음과 같다. 

     

    로그인, comany, conversation 조회 결과 

    1) Smoke 테스트 

     

     

    2) Load 테스트 

     

    team update 결과 

    1) Smoke 테스트 

     

    2) Load 테스트 

     

     

     

    성능 개선 개선 전  개선 후 
    First Byte 1.060S 1.092S
    First View 2.977S 2.639S
    First Contentful Paint 2.748S 2.240S
    Speed Index 2.994S 2.494S
    Largest Contentful Paint 3.031S 2.517S
    Cumulative Layout Shift .007 .007
    Total Blocking Time 0.00S 0.00S
    Total Byte 556KB 266KB

     

    성능 개선 개선 전  개선 후 
    HTTP_REQ_DURATION
    (조회 페이지 - smoke) 
    52.51ms 45.76ms
    HTTP_REQ_DURATION
    (조회 페이지 - load)
    2.5s 2.44s
    HTTP_REQ_DURATION
    (업데이트 페이지 - smoke)
    84.19ms 61.68ms
    HTTP_REQ_DURATION
    (업데이트 페이지 - load)
    3.67s 3.39s

     

     

     

     

     

Designed by Tistory.