-
API Latency를 줄이는 방법 (Part. 0)극락코딩 2023. 8. 28. 20:13
API의 응답속도 및 지연에 민감한 서비스인 경우, 어떤 방식으로 latency를 줄일 수 있을까?
최근에 API의 latency가 민감한 서비스를 개발하게 되었다. 사용자 경험상, 아무리 늦더라도 60~80ms안에는 API의 응답을 받아야 했다. 그렇다면, latency를 줄이기 위한 방법으로는 무엇이 있을까?
일단, 몇가지 상황을 제시한다.
- 해당 API는 TPS 500이 넘는다고 가정한다. (그 만큼 Call이 많은 서비스)
- 해당 서비스는 다수의 쿼리를 조회하고, Redis 및 Client 호출이 많다. (Wrapping API라고 생각하자)
- 해당 서비스는 에러가 발생하더라도, 이를 방어할 수 있는 로직이 필요하다. (에러 발생보다는, 로그를 남기고 넘어가는 방향으로 진행)
그럼, Project Setup을 진행해보자.
초기 설정은 다음과 같이 진행한다. 원하는 네이밍으로 진행..
개인적으로 dependency 설정을 gradle에서 하는 걸 좋아하기 때문에, 설정 없이 바로 프로젝트를 생성한다.
Dependency는 다음과 같이 잡자. 전체 코드는 아래 깃헙 링크를 들어가면 확인 가능하다.
object DependencyVersion { const val KOTLIN_LOGGING_VERSION = "3.0.0" const val LOGBACK_ENCODER = "7.2" } dependencies { /** spring starter */ implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-validation") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-data-redis") implementation("org.springframework.boot:spring-boot-starter-webflux") kapt("org.springframework.boot:spring-boot-configuration-processor") /** kotlin */ implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") /** logger */ implementation("io.github.microutils:kotlin-logging-jvm:${DependencyVersion.KOTLIN_LOGGING_VERSION}") implementation("net.logstash.logback:logstash-logback-encoder:${DependencyVersion.LOGBACK_ENCODER}") /** mysql */ runtimeOnly("mysql:mysql-connector-java") /** etc */ developmentOnly("org.springframework.boot:spring-boot-devtools") }
다음으로는, Database 및 Redis Setup을 진행한다.
(local에 mysql과 redis가 설치되었기를..)
application.yml은 다음과 같이 설정한다.
spring: datasource: url: jdbc:mysql://localhost:3306/reduce_api_latency?useUnicode=true&charset=utf8mb4&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Seoul driver-class-name: com.mysql.cj.jdbc.Driver username: root password: hikari: minimum-idle: 10 maximum-pool-size: 20 jpa: show-sql: true hibernate: ddl-auto: none open-in-view: false redis: host: localhost port: 6379
DDL 쿼리는 다음과 같다. Local에서 실행하시길
-- CREATE TABLE CREATE DATABASE reduce_api_latency CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE TABLE `test_1` ( `id` bigint NOT NULL AUTO_INCREMENT, `title` varchar(255) DEFAULT NULL, `description` varchar(255) DEFAULT NULL, `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `modified_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; CREATE TABLE `test_2` ( `id` bigint NOT NULL AUTO_INCREMENT, `title` varchar(255) DEFAULT NULL, `description` varchar(255) DEFAULT NULL, `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `modified_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; CREATE TABLE `test_3` ( `id` bigint NOT NULL AUTO_INCREMENT, `title` varchar(255) DEFAULT NULL, `description` varchar(255) DEFAULT NULL, `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `modified_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; CREATE TABLE `test_4` ( `id` bigint NOT NULL AUTO_INCREMENT, `title` varchar(255) DEFAULT NULL, `description` varchar(255) DEFAULT NULL, `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `modified_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
이제 짜잘한 설정을 진행한다.
Jpa Auditing Config
@Configuration @EnableJpaAuditing class JpaAuditingConfig
Redis Config
@EnableCaching @Configuration @EnableConfigurationProperties(RedisProperties::class) class RedisConfig( private val properties: RedisProperties ) : CachingConfigurerSupport() { @Bean fun redisConnectionFactory(): RedisConnectionFactory { return LettuceConnectionFactory(properties.host, properties.port) } @Bean fun redisTemplate(): RedisTemplate<String, Any> { return RedisTemplate<String, Any>().apply { this.setConnectionFactory(redisConnectionFactory()) this.keySerializer = StringRedisSerializer() this.valueSerializer = GenericJackson2JsonRedisSerializer() this.hashKeySerializer = StringRedisSerializer() this.hashValueSerializer = GenericJackson2JsonRedisSerializer() } } }
BaseEntity
@MappedSuperclass @JsonIgnoreProperties(value = ["createdAt, modifiedAt"], allowGetters = true) @EntityListeners(AuditingEntityListener::class) abstract class BaseEntity( @Column(columnDefinition = "datetime default CURRENT_TIMESTAMP") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS", timezone = "Asia/Seoul") var createdAt: ZonedDateTime = ZonedDateTime.now(), @Column(columnDefinition = "datetime default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS", timezone = "Asia/Seoul") var modifiedAt: ZonedDateTime = ZonedDateTime.now() ) { @PrePersist fun prePersist() { createdAt = ZonedDateTime.now() modifiedAt = ZonedDateTime.now() } @PreUpdate fun preUpdate() { modifiedAt = ZonedDateTime.now() } }
Test Entity 1 ~ 4
@Entity @Table(name = "test_1") // 요것만 바꾸기~ class Test4( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = -1L, val title: String? = null, val description: String? = null, ) : BaseEntity()
Test Repository 1 ~ 4
@Repository interface Test1Repository : JpaRepository<Test1, Long> { }
여기까지 셋업했다면, 기본적인 구조는 다 작성한 것이다.
다음으로는 테스트를 위한 service와 conroller를 추가한다.
@RestController class TestController( private val testService: TestService ) { } @Service class TestService { }
Mysql 및 Redis Setup이 끝났다. 그리고 기본 골격도 다 잡았다.
추가적으로 외부 호출을 진행하기 위한 WebCliente도 추가하자. (이건 레포에서 확인하기..)
다음 포스팅부터는 예제 API를 작성하고, 이를 개선하는 것을 다루기로 하자...
Reference
'극락코딩' 카테고리의 다른 글
spring version up을 진행할때 참고하기 (0) 2023.11.06 API Latency를 줄이는 방법 (Part. 1) (0) 2023.08.29 Slack Message 발송 (1) 2023.08.27 @Transactional vs TransactionTemplate (0) 2023.08.22 온디맨드가 뭔디? (1) 2023.08.20