Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

카카오톡의 서버사이드 코틀린

2,668 views

Published on

유용하(indy.jones) / kakao corp.(톡메시징파트)
---
JVM 기반 언어의 코틀린은 자바의 생태계와 완전히 호환되면서도 간결하고 안전한 코드를 위한 문법을 가진 언어다. 이런 장점으로 최근 안드로이드를 중심으로 점차 자바를 대체해 가고 있지만, 아직 안정성과 성능에 보수적인 서버 분야에서 자바의 지위는 견고해 보인다. 하지만 기존의 자바 기반 프레임웍과도 완벽히 호환되는 코틀린은 서버사이드에서도 도입하지 않을 이유가 없으며 성공적인 도입 사례가 늘어날수록 그 추세는 가속화될 것이다. 현재 카카오톡의 일부 서버들도 코틀린으로 개발되어 대량의 요청을 안정적으로 서비스하고 있고 그 영역은 점점 늘어나고 있다. 이 세션에서는 다양한 서버 분야의 코틀린 도입에 도움이 될 만한 카카오톡 서버의 코틀린 적용 경험을 공유한다.

Published in: Software
  • Be the first to comment

카카오톡의 서버사이드 코틀린

  1. 1. 카카오톡의 서버사이드 코틀린 유용하(indy.jones) kakao corp.(톡메시징파트)
  2. 2. ________ ________ ________ ________ ________ __ / / // // / // // / _==--------_ -- / / / / / / / / / / / / / / / / | / <:/ / / <:/ / / / / / / /__ ___ / / // / / / / // / / / / / / | . . :| /___/___/ /___/___/ /___/___/ /___/___/ /_______/ / / | __.__ :| ::::::/::::::/::::::/::::::/:::::::/ / | /::::: .:| ______/______/______/______/_______/ | _ ^ _/ .:/ ________ ________ ____ ________ .:/ / // // / / / / _ ..:/ /__ __/ / / / / / / / / / / /"------"" :/ /:/ / / /___/ / <:/ / / . . . / / // / / / // / /_/ | / ::::: | | /__/ / /___/___/ /_______/ /___/___/ / | ::::::: |/ ::/ /:::::: :::::::/::::::/ / | ::::: | __/ ______/_______/______/ | | | ## 카카오톡에 오신것을 환영합니다! kakaotalk$
  3. 3. 서비스 구성 LOCO 메시지 전송 채팅방 미디어 전송 WEBAPP 가입 인증 친구 프로필 설정 톡검색
  4. 4. LOCO 단말의 API 요청 수 메시지 전송 채팅방 미디어 전송 WEBAPP 가입 인증 친구 프로필 설정 톡검색 50B+ / day 800K+ / sec 평소 피크 6.5M+ / sec 역대 최대
  5. 5. LOCO 단말의 API 요청 수 메시지 전송 채팅방 미디어 전송 WEBAPP 가입 인증 친구 프로필 설정 톡검색 평소 피크 역대 최대 하루에 500억+ 1초에 80만+ 1초에 650만+
  6. 6. 생활 속의 서비스 1:0 1:1 2:1 2:2 2:3 3:3 4:3 경기 종료
  7. 7. 생활 속의 서비스 x4
  8. 8. 생활 속의 서비스 무중단 무점검 무장애 😰 7년 ‘겁나 빠른 황소’ 프로젝트 (LOCO) 2011
  9. 9. 2017~ 카카오톡 서버 구조 개선 프로젝트 개발 언어로 코틀린 (for JVM) 도입 기존 컴포넌트 개선을 위한 대체, 서비스 투입 신규 플랫폼용 컴포넌트 개발, 검증용도 서비스 투입2017 2018 Brewery
  10. 10. 코틀린 (for JVM) 선택의 배경 ✔ 적정한 성능/안정성, 개발/운영의 편의성 ✔ 다양한 목적의 서버 환경에 검증된 자바의 생태계를 이용 ✔ 생산성이 좋은 새로운 언어 ✔ 적정 수준의 러닝 커브
  11. 11. 도입을 고민할 때 할 만한 걱정들 개발 환경은 쓸 만 한가요? 언어로 생산성과 안전함이 향상된다? 잘 돌아가나요? 자바와 어느 정도 호환되나요? 문법 그래서 생태계 개발 환경
  12. 12. 개발 환경은 쓸 만 한가요? 언어로 생산성과 안전함이 향상된다? 잘 돌아가나요? 자바와 어느 정도 호환되나요? 문법 그래서 생태계 개발 환경
  13. 13. 버전 업데이트 시 변화가 크지 않음 1.0 ~ 1.3(preview)의 문법, API 하위 호환 역사 https://github.com/JetBrains/Kotlin 1.0.0 오픈소스 1.1.0 1.2.0 안드로이드 공식 지원 M1~M14, 1.0 Beta 1~4 1.3-M1 릴리즈 하위 호환
  14. 14. Build Tool Gradle buildscript { ext.kotlin_version = '1.2.60' repositories { mavenCentral() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } apply plugin: 'kotlin' dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } Maven (v3) <properties> <kotlin.version>1.2.60</kotlin.version> </properties> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>${kotlin.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>${kotlin.version}</version> <executions> <execution> <id>compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> <execution> <id>test-compile</id> <phase>test-compile</phase> <goals> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> <plugins> </build> kotlin-gradle-plugin kotlin-maven-plugin apply plugin: 'kotlin' kotlin-stdlib kotlin-stdlib
  15. 15. Gradle Maven (v3) Build Tool buildscript { ext.kotlin_version = '1.2.60' repositories { mavenCentral() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } apply plugin: 'kotlin' dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } <properties> <kotlin.version>1.2.60</kotlin.version> </properties> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>${kotlin.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>${kotlin.version}</version> <executions> <execution> <id>compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> <execution> <id>test-compile</id> <phase>test-compile</phase> <goals> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> <plugins> </build> apply plugin: 'kotlin' kotlin-stdlib kotlin-stdlib kotlin-gradle-plugin kotlin-maven-plugin *.kt *.java *.class *.class kotlinc javac *자바 파일 혼재하는 경우 compileJava compileKotlin
  16. 16. 개발 환경 IntelliJ IDEA, Eclipse, ✔ 증분 컴파일 (Incremental compilation) ✔ 자동 완성 ✔ 코드 리팩토링 ✔ 자바 to 코틀린 코드 변환 (파일, 클립보드) ✔ 디버거
  17. 17. 개발 환경은 쓸 만 한가요? 언어로 생산성과 안전함이 향상된다? 잘 돌아가나요? 자바와 어느 정도 호환되나요? 문법 그래서 생태계 개발 환경
  18. 18. https://kotlinlang.org
  19. 19. 클래스, 인터페이스, 함수의 호환 class KotlinClass<T> : JavaInterface<T> { fun foo(arg: Int): String { … } } interface KotlinInterface<T> { } class JavaClass<T> implements KotlinInterface<T> { String foo(int arg) { … } } interface JavaInterface<T> { } Kotlin Java val java = JavaClass<Int>() java.foo(1) KotlinClass<Integer> kotlin = new KotlinClass<>(); kotlin.foo(1);
  20. 20. Annotation import org.springframework.web.bind.annotation.* import org.springframework.web.servlet.config.annotation.EnableWebMvc import javax.inject.Inject @Import(BroadcastConfig::class) @EnableWebMvc @RestController class BroadcastController { @Inject private lateinit var httpClient: HttpClient @PostMapping("/broadcast") fun broadcast(@RequestBody request: BroadcastRequest) { } } Compile-time Annotation은 kapt 빌드 플러그인 사용 메소드/필드 호환으로 자바의 Runtime Annotation이 동일하게 동작
  21. 21. 기본 데이터 타입 java.lang.Object int, java.lang.Integer double, java.lang.Double java.lang.String java.util.Collection<E> java.util.List<E> java.util.Set<E> java.util.Map<K, V> Object[] int[] kotlin.Any kotlin.Int kotlin.Double kotlin.String kotlin.collections.Collection<E> kotlin.collections.MutableCollection<E> kotlin.collections.List<E> kotlin.collections.MutableList<E> kotlin.collections.Set<E> kotlin.collections.MutableSet<E> kotlin.collections.Map<K, V> kotlin.collections.MutableMap<K, V> kotlin.Array<Any> kotlin.IntArray Bytecode Kotlin Java (등등의 primitive 타입) Object 원시 타입 문자열 컬렉션 배열
  22. 22. 자바 솔루션과의 연동 Lettuce (Redis Client) JDBC SLF4J / Logback Apache Curator (ZooKeeper) Guava Mockito Spring Netty RxJavagRPC JUnit Apache Kafka Client Hystrix
  23. 23. 개발 환경은 쓸 만 한가요? 언어로 생산성과 안전함이 향상된다? 잘 돌아가나요? 자바와 어느 정도 호환되나요? 문법 그래서 생태계 개발 환경
  24. 24. 중복 최소화 // Java class Host { private final String name; private final int port; private int weight; Host(String name, int port, int weight) { this.name = name; this.port = port; this.weight = weight; } public String getName() { return name; } public int getPort() { return port; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } @Override public boolean equals(Object obj) { return false; } @Override public int hashCode() { return 0; } @Override public String toString() { return ""; } } // Java (with Lombok) import lombok.*; @AllArgsConstructor @ToString @EqualsAndHashCode class Host { @Getter @NonNull private final String name; @Getter private final int port; @Getter @Setter private int weight; } // Java (with Lombok) import lombok.*; @AllArgsConstructor @ToString @EqualsAndHashCode class Host { @Getter @NonNull private final String name; @Getter private final int port; @Getter @Setter private int weight; } Too many annotations Lombok
  25. 25. 중복 최소화 // Java class Host { private final String name; private final int port; private int weight; Host(String name, int port, int weight) { this.name = name; this.port = port; this.weight = weight; } public String getName() { return name; } public int getPort() { return port; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } @Override public boolean equals(Object obj) { return false; } @Override public int hashCode() { return 0; } @Override public String toString() { return ""; } } // Java (with Lombok) import lombok.*; @AllArgsConstructor @ToString @EqualsAndHashCode class Host { @Getter @NonNull private final String name; @Getter private final int port; @Getter @Setter private int weight; } Lombok Kotlin // Kotlin data class Host( val name: String, val port: Int, var weight: Int )
  26. 26. 중복 최소화 // Java class Host { private final String name; private final int port; private int weight; Host(String name, int port, int weight) { this.name = name; this.port = port; this.weight = weight; } public String getName() { return name; } public int getPort() { return port; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } @Override public boolean equals(Object obj) { return false; } @Override public int hashCode() { return 0; } @Override public String toString() { return ""; } } Kotlin // Kotlin data class Host( val name: String, val port: Int, var weight: Int ) val host = Host("kakao.com", 443, 1) println("${host.name}:${host.port}") host.weight = 2
  27. 27. 안전한 코드 코딩의 실수로 인한 오류를 방지하는 방법 ✔ 유효한 패턴 적용, 안티 패턴을 지양 ✔ 테스트 작성 ✔ 공개된 API의 문서화 ✔ 문법에서 실수 가능성을 막아 컴파일 시점에 오류를 방지
  28. 28. 안전한 코드 정적 타입: 더 엄격한 타입 체크 Nullable 타입을 문법에서 구분 Primitive 타입, Boxed 타입 구분 없음: Int, Long, Float, Double, Byte, Short, Char, Boolean 클래스 이용 특수 타입: Any, Nothing, Unit 개선된 타입 시스템
  29. 29. 안전한 코드: Nullable 타입 Not-null Type Nullable Type var str: String str = null str = null if (str != null) { val len: Int = str.length } val len: Int? = str?.length val len: Int = str?.length ?: 0 var str: String? Smart Cast “컴파일 시점”에 NPE를 방지한다. Optional을 문법에서 강제한다. 그래도 자바와 호환 과정에서는 발생할 수 있다. val len: Int = str.length val len: Int = str.length
  30. 30. 안전한 코드: 불변 컬렉션 list.set(0, 100) list[0] = 100 list.add(100) list.remove(0) map.put("key", 1) map["key"] = 1 map.remove("key") “컴파일 시점”에 자료의 변경을 막는다. 그래도 자바에 넘겼을 때는 변경될 수 있다. Immutable List Immutable Map val list: List<Int> val i = list.get(0) val i = list[0] val map: Map<String, Int> val i = map.get("key") val i = map["key"] 변경용 API 변경용 API
  31. 31. 안전한 코드: 불변 컬렉션 list.set(0, 100) list[0] = 100 list.add(100) list.remove(0) map.put("key", 1) map["key"] = 1 map.remove("key") Mutable List Mutable Map val list: MutableList<Int> val i = list.get(0) val i = list[0] val map: MutableMap<String, Int> val i = map.get("key") val i = map["key"] 변경용 API 변경용 API * List<T>의 sub-interface * Map<K, V>의 sub-interface
  32. 32. 안전한 코드: final 불변 변수 val를 권장 (final 변수) 상속 제한이 디폴트 (final 클래스, 함수) open class PushServiceImpl(private val pushService: PushService) : PushService { open override fun push(request: PushRequest) { // ... } } val max = 1 // final max = 100 의외로 변경이 필요한 변수는 많지 않다. 통제되지 않은 상속에서 문제가 발생한다. 단 Spring AOP는 open이 필요. 😳 -> kotlin-spring 플러그인
  33. 33. 명확한 코드 val properties: Properties = // ... val timeout = properties.getInt("timeout") fun Properties.getInt(key: String): Int = this.getProperty(key).toInt() // Java (decompiled - pseudo code) class PropertiesUtilsKt { static int getInt(Properties receiver, String key) { return Integer.parseInt(receiver.getProperty(key)); } } // Java int timeout = PropertiesUtilsKt.getInt(properties, "timeout"); 확장함수
  34. 34. 명확한 코드 if (StringUtils.isEmpty(str)) return
  35. 35. 명확한 코드 if (StringUtils.isEmpty(str)) return if (str.isNullOrEmpty()) return
  36. 36. 명확한 코드 if (StringUtils.isEmpty(str)) return if (str.isNullOrEmpty()) return 😃 Utility, Helper 클래스 대체 (의존성 제거 가능) 😃 클래스의 핵심 기능과 확장 기능 구현 분리 😃 상속 없이 기존의 클래스의 기능을 추가 😞 기존 클래스에 새 인터페이스 적용은 안 됨 확장함수
  37. 37. 간결함 스마트 캐스트 (Smart Cast) override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { if (msg is HttpRequest) { if (msg.method() == HttpMethod.GET) { // ... } } } HttpRequest req = (HttpRequest) msg; if (req.method().equals(HttpMethod.GET)) { // ... }
  38. 38. 간결함 스마트 캐스트 (Smart Cast) override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { if (msg is HttpRequest) { if (msg.method() == HttpMethod.GET) { // ... } } }
  39. 39. 간결함 스마트 캐스트 (Smart Cast) override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { if (msg is HttpRequest) { if (msg.method() == HttpMethod.GET) { // ... } } } fun foo(str: String?) { if (str == null) throw IllegalArgumentException() val length = str.length } 코틀린 방식의 Guard 처리 Not-null 타입 (String)
  40. 40. 간결함 val members = if (response.status == 0) { logger.info("members: {}", response.members) response.members } else { emptyList() } 조건문도 값을 가지는 표현문 (Expression) val apiHostName = when(env) { Environment.PRODUCTION -> "api.dommain.name" Environment.ALPHA -> "alpha-api.dommain.name" Environment.BETA -> "beta-api.dommain.name" else -> throw IllegalArgumentException() } ‘Nothing’ 타입 선언형 문장으로 임시 변수 사용을 줄일 수 있다.
  41. 41. println("[" + index + "] " + request.length) println("[$index] ${request.length}”) if (version.compareTo(minVersion) < 0) { } if (version < minVersion) { } val member= objectMapper.readValue(json, Member::class.java) val member = objectMapper.readValue<Member>(json) val member: Member = objectMapper.readValue(json) 간결함 인라인 제네릭 함수 + reified 타입 파라미터 연산자 오버로드 스트링 템플릿 if (version.equals(minVersion)) { } if (version == minVersion) { } ==는 equals()
  42. 42. 유용한 기능 람다 파라미터 list.forEach { println(it) } val value = map.getOrPut(key) { calculateValue() } list.forEach({ str -> println(str) }) val map: ConcurrentHashMap<String, Int> val value = map.getOrPut(key, { calculateValue() }) DSL로 이용하기 좋은 문법
  43. 43. 유용한 기능 함수형 유틸리티 val addresses: List<Server> = params .filter { it.isNotBlank() } .map { it.split(Regex(":"), 2) .let { Server(it[0], it[1].toIntOrNull() ?: 80) } } class ServerNode(val address: HostAndPort){ private val logger = LoggerFactory.getLogger(javaClass) .apply { info("Initializing with {}", address) } } apply, let, run, also, Collection, Iterable의 확장 함수로 제공 Any의 확장 함수로 제공되어 모든 expr에 대해 사용 가능
  44. 44. 강력한 기능 suspend fun lastMessages(chatRoomId: ChatRoomId, maxCachedCount: Int): List<Message> { val cached = async { messageService.recent(chatRoomId, maxCachedCount) } val fetched = async { requestChatLogs(chatRoomId) } return (cached.await() + fetched.await()) .distinctBy { it.messageId } .sortedBy { it.messageId } } 😃 비동기 구현에서 Future, RxJava 대비 장점 🤔 1.3부터 정식 패키지에 포함되지만 1.3이 아직 베타 😱 StackTrace가 제대로 안 나오는 경우가 있다. 코루틴 비동기 로직을 가독성이 높은 절차적인 코드로 작성 기본적으로 컨텍스트 스위칭 없이 동작하여 성능이 좋음
  45. 45. 자바와 코틀린 비교 * 코드의 특성에 따라 달라질 수 있음 * RxJava2 기반으로 작성된 모듈 * Compile Time은 kapt 제외한 순수한 컴파일 시간 인라인 함수 익명 클래스 null 체크 메타 데이터 66% x2.2 x6.5 자바 컴파일이 빠른거 ' 코드 라인 수 바이트코드 사이즈 컴파일 시간 자바8 코틀린같은 동작을 하는 ...증분 컴파일은 체감상 빠른데..
  46. 46. 개발 환경은 쓸 만 한가요? 언어로 생산성과 안전함이 향상된다? 잘 돌아가나요? 자바와 어느 정도 호환되나요? 문법 그래서 생태계 개발 환경
  47. 47. Broadcast Service gRPC-java RxJava2 Guice Presence Service gRPC-java RxJava2 gRPC-java ZooKeeper RedisPrometheuspapi HTTP/2 HTTP/2
  48. 48. gRPC-java 550K / sec 120K / sec * 일부에 대해서만 검증 용으로 서비스 Broadcast Service gRPC-java Presence Service gRPC-java
  49. 49. Routing Service gRPC- java Vert.x Vert.x ZooKeeperPrometheus Clients Apps HTTP/2, HTTP/1.1 Services RxJava2 Guice
  50. 50. Routing Service gRPC- java Vert.x Vert.x 300K / sec 700K / sec * 일부에 대해서만 검증 용으로 서비스
  51. 51. WebApp Routing Service Undertow2 Spring Framework Spring Framework Kafka Redis Prometheuspapi Consul Clients Apps
  52. 52. Spring Framework 70K / sec WebApp Routing Service Undertow2 Spring Framework
  53. 53. 한계 상황에서의 안정성 성공 & 에러 CPU 응답 시간 분포 Load 정상 서비스 한계 상황 정상 회복
  54. 54. Java 코틀린과 자바의 성능 CPU 응답 시간 KotlinJava + Kotlin Java + Kotlin RSS Mem Java + Kotlin
  55. 55. OpenJDK JDK7 부터 기본 기능으로는 OracleJDK와 차이가 없음 기술 지원이 없으며 업데이트를 지속적으로 할 필요 OracleJDK과의 차이점 Brewery 프로젝트에 OpenJDK10 적용 코틀린&자바 빌드: 문제 없음 (코틀린 코드는 kotlinc에서) 성능 및 안정성: JDK10에 준하는 결과 카카오톡 서비스 적용
  56. 56. 마이그레이션 ⚠ 자바 코드/클래스 연동시 타입 주의 (Nullable 등) ⚠ Lombok 적용된 자바 코드와는 같은 모듈에 사용하기 힘듬 ⚠ Spring에서는 특정 클래스/함수를 open해줘야 함 ⚠ Immutable 데이터로 리팩토링하는 것은 생각보다 큰 일 ✔ 새로운 프로젝트: 처음부터 코틀린 ✔수정이 앞으로도 많을 프로젝트: 모델, 유틸리티부터 리팩토링 ✔ 그 외 잘 돌아가고 있는 자바 프로젝트: 잘 돌아가게 두자 ✔kotlin-spring 플러그인을 쓰자 ✔delombok을..
  57. 57. 도입을 고민할 때 할 만한 걱정들 개발 환경은 쓸 만 한가요? 언어로 생산성과 안전함이 향상된다? 잘 돌아가나요? 자바와 어느 정도 호환되나요? 문법 그래서 생태계 개발 환경
  58. 58. 요약 이미 안정화된 언어와 개발 환경 생산성과 가독성 높은 문법 하위 호환할 레거시 문법이 없음 잘 돌아갑니다. 😃 자바와 완전한 호환으로 생태계 공유 프레임웍의 러닝 커브가 없음 문법 그래서 생태계 개발 환경
  59. 59. ________ ________ ________ ________ ________ __ / / // // / // // / _==--------_ -- / / / / / / / / / / / / / / / / | / <:/ / / <:/ / / / / / / /__ ___ / / // / / / / // / / / / / / | . . :| /___/___/ /___/___/ /___/___/ /___/___/ /_______/ / / | __.__ :| ::::::/::::::/::::::/::::::/:::::::/ / | /::::: .:| ______/______/______/______/_______/ | _ ^ _/ .:/ ________ ________ ____ ________ .:/ / // // / / / / _ ..:/ /__ __/ / / / / / / / / / / /"------"" :/ /:/ / / /___/ / <:/ / / . . . / / // / / / // / /_/ | / ::::: | | /__/ / /___/___/ /_______/ /___/___/ / | ::::::: |/ ::/ /:::::: :::::::/::::::/ / | ::::: | __/ ______/_______/______/ | | | ## 참석해 주셔서 감사합니다! QNA$

×