Slide 1

Slide 1 text

!"#$%& 「ISUCON」は、LINE株式会社の商標または登録商標です。

Slide 2

Slide 2 text

ISUCONとは、かの有名なイスカンダル(アレクサンドロス3世)が 
 紀元前320年頃に開いた異国の椅子品評会にルーツが遡る。その際、 
 かの王がこの品評会の名前を考得ていたところ、家臣の一人が「 
 椅子だしイスコンテストでいいんじゃないんすか?w」と放ち、 
 イスカンダル王が絶賛。以降その品評会はイスコンテストと呼ば れることになる。日本には安土桃山時代にシルクロードから伝わり、 
 楽市楽座で活発に行われるようになる。かの織田信長が打たれた本 能寺の変では、このイスコンテスト落選に腹を立てた明智光秀が 
 腹いせに矢を寺にはなったところから始まるというただのとばっ 
 ちりの説がある。その後、名前の呼び方が訛り、明治時代には 
 ついにイスコンという名前に。第二次大戦などで中止になるものの、 
 現在までに毎年コンテストが開かれて、現在に至る

Slide 3

Slide 3 text

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Webサービスの高速化を競う

Slide 6

Slide 6 text

Slide 7

Slide 7 text

Slide 8

Slide 8 text

めっちゃ遅いWebサービスの高速化を競う

Slide 9

Slide 9 text

高速化の手段

Slide 10

Slide 10 text

高速化の手段 DBのチューニング アルゴリズムの改善 サーバーの構造の工夫 → 専用のベンチマークツールでスコアを出し、そのスコアを競う etc

Slide 11

Slide 11 text

賞金

Slide 12

Slide 12 text

Webサービスの高速化を競う ※ 既に各言語で実装されたサービス(参考実装)を高速化

Slide 13

Slide 13 text

参考実装としての公式の言語

Slide 14

Slide 14 text

参考実装としての公式の言語 Ruby Rust Python Go Node.js PHP etc (isucon 12)

Slide 15

Slide 15 text

'()*+,"-./-.0123-,"4256 ※ ごめんなさい ISUCON運営の皆さん 悪気はないんです

Slide 16

Slide 16 text

—— 2021年 7月

Slide 17

Slide 17 text

ここに3人の勇者現る

Slide 18

Slide 18 text

ス ウ ィ フ ト を 愛 し ス ウ ィ フ ト に 愛 さ れ た 者 チ | ム 何 も わ か 
 ら ん

Slide 19

Slide 19 text

ことの発端

Slide 20

Slide 20 text

やったこと(やりたかったこと) Swiftへのフルスクラッチ移植 + 高速化

Slide 21

Slide 21 text

Server-side Swift ライブラリ

Slide 22

Slide 22 text

Swift Concurrency 2021年、当時 Vapor に電流走る

Slide 23

Slide 23 text

コールバック地獄が routes.get("firstUser") { req -> EventLoopFuture in User.query(on: req.db).first().unwrap(or: Abort(.notFound)).flatMap { user in user.lastAccessed = Date() return user.update(on: req.db).map { return user.name } } }

Slide 24

Slide 24 text

こんな感じに! routes.get("firstUser") { req async throws -> String in guard let user = try await User.query(on: req.db).first() else { throw Abort(.notFound) } user.lastAccessed = Date() try await user.update(on: req.db) return user.name }

Slide 25

Slide 25 text

Async/awaitで Vapor による開発が 
 もっと快適に!

Slide 26

Slide 26 text

Q. あれ?でも Swift Concurrency ってその時安定してたの?

Slide 27

Slide 27 text

Q. あれ?でも Swift Concurrency ってその時安定してたの? A. いろいろと使うのには手間があった Swift Toolchain のインストール サーバーがLinuxなのでインストール方法の工夫 デバッグが効かない(LLDB)

Slide 28

Slide 28 text

Q. あれ?でも Swift Concurrency ってその時安定してたの? A. いろいろと使うのには手間があった Swift Toolchain のインストール サーバーがLinuxなのでインストール方法の工夫 デバッグが効かない(LLDB) < だいたいやっといたでw

Slide 29

Slide 29 text

SPMで環境構築できるので楽

Slide 30

Slide 30 text

// swift-tools-version:5.3 import PackageDescription let package = Package( name: "isucon11", platforms: [ .macOS(.v10_15) ], dependencies: [ .package(url: "https://github.com/vapor/vapor.git", .branch("async-await")), .package(url: "https://github.com/vapor/mysql-kit.git", from: "4.0.0"), .package(url: "https://github.com/Flight-School/AnyCodable", from: "0.6.0"), .package(url: "https://github.com/vapor/jwt-kit.git", from: "4.0.0"), ], targets: [ .target( name: "App", dependencies: [ .product(name: "Vapor", package: "vapor"), .product(name: "MySQLKit", package: "mysql-kit"), .product(name: "JWTKit", package: "jwt-kit"), .product(name: "AnyCodable", package: "AnyCodable") ], swiftSettings: [ .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)), // Disable availability checking to use concurrency API on macOS for development purpose // SwiftNIO exposes concurrency API with availability for deployment environment, // but in our use case, the deployment target is Linux, and we only use macOS while development, // so it's always safe to disable the checking in this situation. .unsafeFlags(["-Xfrontend", "-disable-availability-checking"]) ] ), .target(name: "Run", dependencies: [.target(name: "App")]) ] ) Package.swift (+ k8s などの設定ファイル) 作れば開発可能!

Slide 31

Slide 31 text

企業の皆さん Server Side Swift どうすか?!

Slide 32

Slide 32 text

Am○zonは独自のServer-side Swift Frameworkつかっている 
 (らしい)

Slide 33

Slide 33 text

企業の皆さん Server Side Swift どうすか?! (圧)

Slide 34

Slide 34 text

一応本番の前に壁打ち2年分くらいやった

Slide 35

Slide 35 text

一応本番の前に壁打ち2年分くらいやった 他の言語の初期スコアまでは行った 結果 だいたい時間はオーバーしている(13時間、本番は8時間)

Slide 36

Slide 36 text

そして本番

Slide 37

Slide 37 text

出題

Slide 38

Slide 38 text

出題

Slide 39

Slide 39 text

ISUCON Swift 移植RTAはーじまーるよー

Slide 40

Slide 40 text

やること

Slide 41

Slide 41 text

やること 10個くらいあるAPIのエンドポイントの実装の移植 そっからチューニング(できれば)

Slide 42

Slide 42 text

最初4時間くらい

Slide 43

Slide 43 text

最初4時間くらい 実装移植がだいたい終わる!いけるか?!

Slide 44

Slide 44 text

ベンチマークが通らない!!!!!

Slide 45

Slide 45 text

ハマりポイント その1

Slide 46

Slide 46 text

謎のPayload 上限 public func collect(max: Int? = 1 << 14) -> EventLoopFuture { switch self.request.bodyStorage { case .stream(let stream): return stream.consume(max: max, on: self.request.eventLoop).map { buffer in self.request.bodyStorage = .collected(buffer) return buffer } case .collected(let buffer): return self.request.eventLoop.makeSucceededFuture(buffer) case .none: return self.request.eventLoop.makeSucceededFuture(nil) } } max: Int? = 1 << 14 https://github.com/vapor/vapor/blob/45bc9ab1e3c4ba309606762664fc18358b7c92e5/Sources/Vapor/Request/Request%2BBody.swift#L38

Slide 47

Slide 47 text

謎のPayload 上限 max: Int? = 1 << 14

Slide 48

Slide 48 text

謎のPayload 上限 max: Int? = 1 << 14 1 << 14 = 0b100000000000000

Slide 49

Slide 49 text

謎のPayload 上限 max: Int? = 1 << 14 1 << 14 = 0b100000000000000 0b100000000000000 = 16(KB)

Slide 50

Slide 50 text

謎のPayload 上限 デフォルトは 16KB しかリクエストを送れない 気づかず時間をかなり消費

Slide 51

Slide 51 text

ひ ょ っ と し て そ れ は ギ ャ グ と し て い っ て い る の か 💧 💧

Slide 52

Slide 52 text

ハマりポイント その2

Slide 53

Slide 53 text

@discardableResult @_disfavoredOverload public func values(_ values: Encodable...) -> Self { let row: [SQLExpression] = values.map(SQLBind.init) self.insert.values.append(row) return self } @discardableResult public func values(_ values: SQLExpression...) -> Self { self.insert.values.append(values) return self } SQLクエリを組み立てる関数 https://github.com/vapor/sql-kit/blob/3c5413a229bc2abc962dab17ea66d25e448ad344/Sources/SQLKit/Builders/SQLInsertBuilder.swift#L94 謎のオーバーロード

Slide 54

Slide 54 text

@discardableResult @_disfavoredOverload public func values(_ values: Encodable...) -> Self { let row: [SQLExpression] = values.map(SQLBind.init) self.insert.values.append(row) return self } @discardableResult public func values(_ values: SQLExpression...) -> Self { self.insert.values.append(values) return self } SQLクエリを組み立てる関数 https://github.com/vapor/sql-kit/blob/3c5413a229bc2abc962dab17ea66d25e448ad344/Sources/SQLKit/Builders/SQLInsertBuilder.swift#L94 こっち呼んでるつもり 謎のオーバーロード

Slide 55

Slide 55 text

SQLクエリを組み立てる関数 https://github.com/vapor/sql-kit/blob/3c5413a229bc2abc962dab17ea66d25e448ad344/Sources/SQLKit/Builders/SQLInsertBuilder.swift#L94 こっち呼ばれる ちゃんとクエリ 
 つくってくれない @discardableResult @_disfavoredOverload public func values(_ values: Encodable...) -> Self { let row: [SQLExpression] = values.map(SQLBind.init) self.insert.values.append(row) return self } @discardableResult public func values(_ values: SQLExpression...) -> Self { self.insert.values.append(values) return self } こっち呼んでるつもり 謎のオーバーロード

Slide 56

Slide 56 text

Why?

Slide 57

Slide 57 text

SQLクエリを組み立てる関数 https://github.com/vapor/sql-kit/blob/3c5413a229bc2abc962dab17ea66d25e448ad344/Sources/SQLKit/Builders/SQLInsertBuilder.swift#L94 こっち呼ばれる ちゃんとクエリ 
 つくってくれない @discardableResult @_disfavoredOverload public func values(_ values: Encodable...) -> Self { let row: [SQLExpression] = values.map(SQLBind.init) self.insert.values.append(row) return self } @discardableResult public func values(_ values: SQLExpression...) -> Self { self.insert.values.append(values) return self } こっち呼んでるつもり @_disfavoredOverload 謎のオーバーロード

Slide 58

Slide 58 text

@_disfavoredOverload 謎のオーバーロード

Slide 59

Slide 59 text

謎のオーバーロード @_disfavoredOverload これでオーバーロードの呼び出しの優先順位を低くする → 結果想定してない方が呼ばれてクエリが正しくない

Slide 60

Slide 60 text

🥺

Slide 61

Slide 61 text

主なハマりポイント バックエンド(MySQLNIO)はタイムゾーンの設定必須 今回は MySQLKit でDBとの接続を行った ベンチマークが期待しているデータの時間が9時間狂ってた など

Slide 62

Slide 62 text

結果

Slide 63

Slide 63 text

0点

Slide 64

Slide 64 text

😢

Slide 65

Slide 65 text

0点なので統計に乗らず ※ 少ないだけの可能性もある

Slide 66

Slide 66 text

😢

Slide 67

Slide 67 text

反省 タイムゾーンには気をつけろ あと段階的に実装しても良かったかも? いやでもちゃんとたのしめたのでよかった

Slide 68

Slide 68 text

まとめ

Slide 69

Slide 69 text

たのしかったです!

Slide 70

Slide 70 text

皆さんも是非!