クックパッドにおけるサーバ監視と運用の工夫

こんにちは。インフラストラクチャー部の加藤(@EugeneK)です。 今回はWebサービスを運用する上で欠かせない、モニタリングをクックパッドでどうしているかという話をします。

死活監視と性能監視

Webサービスを運用している以上、そのサービスを稼働しているサーバがあり、サーバには故障やトラブルが発生します。 また、どれくらいのパフォーマンスが出ているか、リソースをどのくらい消費しているかなどのトレンドを把握することは、成長するサービスを支えていく上で欠かせません。

故障やトラブルにいち早く気づくための仕組みを死活監視と言います。

また、サーバリソースの時系列での推移を知るために、グラフとしてトレンドを可視化する仕組みを性能監視と言います。

ポーリング監視の限界とZabbixのアクティブ監視

クックパッドでは死活監視にNagios、性能監視にMuninを使用してきましたが、サーバ台数の増加に伴い限界が見え始めました。 具体的には、監視を行うサーバが監視対象のサーバへ定期的に通信をして死活や性能の監視を行っていましたが、監視対象のサーバ台数が500台を越えるあたりから監視が追い付かず、指定した間隔で行われないという問題が発生しました。

f:id:EugeneKato:20150427120737p:plain

その問題を解決するためにZabbixを導入しました。Zabbixとは統合監視ツールであり、監視とグラフ作成が両方行えるツールですので、NagiosとMuninを両方使う必要がなくなります。

また、Zabbixの特徴としてアクティブ監視という機能があります。アクティブ監視とは監視対象のサーバにインストールされたエージェントが各種リソースや性能の状況を取得し、メトリックスとして監視サーバに報告する機能です。

f:id:EugeneKato:20150427120731p:plain

この機能を用いると、監視サーバのポーリングがボトルネックとなって台数の増加に対応できないという問題が解決し、スケーラビリティを確保することができます。 実際にクックパッドでは1秒間に3000項目を越える監視を実現しています。 もちろん、Pingによる監視とエージェント自体が稼働しているかどうかの監視は引き続き監視サーバ側から行う必要がありますが、それ以外の監視は全てアクティブ監視で行うことが出来ます。

自動登録

クックパッドではAWS上でサーバを運用しているため、オートスケールを始めとしてサーバの増減が日常的に発生しています。 そこで、監視の仕組みもサーバの増減に追従する必要があります。

Zabbixには自動登録の仕組みがあり、エージェントが起動したときに監視サーバと通信し、監視サーバに未登録であれば新規登録して監視に追加する機能があります。 この仕組みを使ってサーバの増加に対応しています。

一方で、減少に対しては独自の仕組みを使っています。 オートスケールで減少した分については、AWSのAPIで取得したサーバリストとZabbixのAPIで取得したサーバリストを突合させて、減少分を削除するスクリプトを定期的に実行しています。 また、オートスケール以外で減少した分についてはサーバ停止後もグラフを振り返りたい場合があるため、手動で削除するようにしています。

chatopsによる作業負担の軽減

Zabbixでの監視の流れは以下の通りとなっています。

  • エージェントによるメトリクスの取得と監視サーバへの報告
  • 監視サーバ側で報告された値をもとに、トリガーとして設定された閾値と比較
  • トリガーに紐付けられたアクションによってメールによるアラートの通知など任意のタスクを実行

クックパッドでは監視で異常が発生した場合はメールによる通知と同時に、業務で使用しているチャットにも通知するようにしています。 f:id:EugeneKato:20150427163735p:plain

これは、そのアラートに対して誰が対応しているかなどの状況を素早く共有するのがねらいです。 また、手動によるオペレーションを実施する際に一時的にアラートを止めたい場合も、チャットルームに常駐しているロボットに向けて発言することで、ZabbixのAPIを通じて自動的にメンテナンス設定を行うようにしています。 f:id:EugeneKato:20150427164352p:plain

こういった「chatops」の仕組みを用いることで、最小限の手順で情報の共有や操作が行えるようになります。

多リージョン展開とプロキシ監視

クックパッドは今世界展開をしている最中であり、AWSでの多リージョン展開をしています。 リージョン毎に監視システムが存在してしまうと管理・運用の手間が増えてしまうため、Zabbixのプロキシ監視の仕組みで一元化するようにしています。 f:id:EugeneKato:20150427120743p:plain

図にあるとおり、zabbix-serverとzabbix-proxyはVPNによって接続し、各リージョンにあるzabbix-proxyはそのリージョン内の監視対象のサーバから取得したメトリクスを集約してzabbix-serverに報告するようになっています。 これによって、VPNを経由する通信はzabbixプロトコルが使用するポートのみを許可すればよいことになり、ネットワークの設定も最小限で済ませることができます。

おわりに

クックパッドでの監視の仕組みについて紹介しました。 冒頭でも触れましたように、サーバのモニタリングはサービスの品質を向上させる上で欠かせません。 モニタリングの向上はサービスの向上ととらえ、日々改善を行っています。

既存のObjective-CアプリケーションをSwiftで書き換えた話

 海外事業向けのiOSアプリケーション開発を担当している西山(@yuseinishiyama)です。クックパッドは現在、海外複数カ国に向けてサービスを展開しています。

 主にObjective-Cで記述されたアプリケーションを全面的にSwiftに書き換える機会があったので、その際に得た知見や書き換えるに至った動機を共有します。

書き換えに至るまでの経緯

 この項では、書き換えに至るまでの経緯について説明します。

Objective-C期

 アプリケーションの開発は2014年7月頃にスタートしました。Swiftの発表直後でしたが、時期尚早ということもあり、Objective-Cで実装することになりました。

Objective-C、Swift混在期

 2014年10月頃から、Swiftへの段階的な移行のために、新規のコードをSwiftで書くようになりました。Swiftの記述力や、ヘッダと実装を行き来しなくて良いことなどにメリットを感じたためです。ここから、Objective-CとSwiftが混在するようになります。

Swift期

 Objective-CとSwiftが混在している状態では、後述するSwiftによるメリットを完全には受けることができないと考え、一部を除いてほぼ全てのコードをSwiftに書き換えました。

なぜ書き換えたか

 書き換えには、エンバグやスケジュールの遅れなどのリスクが伴ないます。また、新規の言語に対する純粋な興味から、業務で使用する言語を選択することも当然すべきではありません。しかし、それでも尚、書き換えたほうが良いと思われる十分な理由がありました。この項では、その理由について説明します。

Objective-CとSwiftの混在によって生じる制約

 AppleはSwiftとObjective-Cを相互に運用する方法について、詳細なドキュメントを提供しています。しかし、単に相互利用できるというだけで、実際には様々な制約があります。

 Objective-CからSwiftを参照する場合、Swift側の一部のコードは参照することができません。Swiftでのenumstructのような概念はObjective-Cには無いため、これらをObjective-Cから利用することはできません。また、Optional型の利用にも制限があります。例えば、下記のようなクラスとそのプロパティ群があった場合、プロパティaはObjective-Cから参照できますが、プロパティbはできません。

class SampleClass : NSObject {
    var a = 0
    var b: Int?

 SwiftのInt型はObjective-CではNSIntegerとみなされます。SwiftのIntstructとして宣言されている一方、NSIntegerの実態はプリミティブな型なのでnil値をとることができません。そのため、OptionalなInt型をObjective-Cで扱うことができないのです。このようなケースでnilを許容する数値を使用したい場合は、NSNumberとして宣言せざるを得ません。

 以上のような事柄を含め、Objective-Cから利用される限り、Swiftの仕様をフルに生かすことはできず、また、Objective-Cからの利用を意識したコードを書き続ける必要があるということが分かります。

Objective-CからSwiftを参照しないようにする?

 前項の問題はObjective-CからSwiftを参照するが故に、起こりうる問題です。極力、Objective-CからSwiftを呼び出さないようなポリシーで混在させるのはどうでしょうか?

 確かに、SwiftからObjective-Cライブラリを参照する、SwiftからObjective-Cで記述されたモデルを参照する、などのSwiftからObjective-Cを参照するケースでは、これらの問題を気にする必要はありません。実際、当該プロジェクトでも、当初はビュー関連のコードだけSwiftで記述していたので、Objective-CからSwiftを参照するケースは殆どありませんでした。

Swiftのメリットを享受するには

 ところで、Swift化によるメリットを最大限に受けることができるのは、モデルやAPIクライアントです。これらのレイヤーは、Objective-Cにおいては、その動的特性のために、バグの温床となっていました。JSONをオブジェクトにマッピングする際に、予期していた型と違う型が入っていたというようなことは、皆さんも度々経験されているのではないでしょうか。

 一方、Swiftでは静的型付けやnullabilityのコントロールによって、これらのバグを解消することができます。長期的に運用されるであろうアプリケーションにおいて、こうした機能を活用してその安定性を高めることには多大なメリットがあります。

 しかし、モデルやAPIクライアントをSwiftに置き換えると、それらを呼び出していた既存のObjective-Cコード(主にViewControllerなど)がSwiftを参照することになります。モデルやAPIクライアントをSwiftの機能をフルに利用して実装すると、結局、アプリケーション全体をSwift化する必要がでてくるのです。

Swift化のメリット

 前項で、静的型付けやnullabilityのコントロールといった、Swiftのメリットについて簡単に触れました。ここでは、そうしたメリットについて、実際のコードを参照しながら、具体的に述べます。

ジェネリクスの活用

 ジェネリクスを活用することで、より安全で表現力の高いコードを記述することができます。ジェネリクスを利用することが好ましい典型的なケースについて説明します。

モデルとAPIクライアントの表現

 ジェネリクスを用いて、レスポンスの型を静的に決めることができます。これによって、前述した、予期しない型が代入されることによって生じるバグを防ぐことができます。

 まず、単一のAPIを表すためのプロトコルを以下のように宣言しました。ちなみに、現在のプロトコルの仕様ではデフォルトの実装を定義することができないので、プロトコルではなくクラスを採用するという考えも十分に有り得ます。

protocol API {
    typealias ResponseType
    typealias ResultType = Result<Response<ResponseType>, Response<CommonError>>
    typealias ResponseParserType: ResponseParser

    var method: Method { get }
    var pathString: String { get }
    var parameters: [String : AnyObject]? { get }
    var parameterEncoding: ParameterEncoding { get }
    var responseParser: ResponseParserType { get }
}

 そして、準拠しているAPIの実態は以下のようになります。

extension APIs {
    class Recipes {
        class Get: API {
            typealias ResponseType = Recipe

            let id: Int
            init(id: Int) { self.id = id }
            var method: Method = .GET
            var pathString: String { return "/recipes/\(id)" }
            var parameters: [String : AnyObject]? = nil
            var parameterEncoding: ParameterEncoding = .Default
            var responseParser = DefaultResponseParser<ResponseType>()
        }
        class Post : API {
            typealias ResponseType = Recipe

            let recipe: Recipe
            init(recipe: Recipe) { self.recipe = recipe }
            (以下省略)

 このように記述することで、それぞれのAPIの仕様が一眼で分かります。また、クラスのネストを利用して、APIの階層構造も表現することができます。

 次に、APIクライアントのインターフェースです。

protocol APIClient {
    (省略)
    func sendRequest<T: API where T.ResultType == T.ResponseParserType.ResponseType>(API: T, handler: T.ResultType -> ())
}

 ジェネリクスを活用することで、T.ResultTypeとしてレスポンスの型がAPIから一意に決まります。実際にこれらを使用するコードは下記のようになります。

let api = APIs.Recipes.Get(id: 42)
SharedAPIClient.sendRequest(api) {
    println("the title is \($0.value?.bodyObject.title)")
}

 GET /recipes/:idのレスポンスの型がRecipeであるということが静的に決まります。そして、そのプロパティにtitleがあることもコンパイラは知ることができるのです。

 ちなみに、これらの実装に当たっては、MoyaAPIKitが大変に参考になりました。

Eitherによるエラーハンドリング

 Objective-Cにおいて、APIコール時のコールバック関数の型は下記のようなものが一般的でした。

typedef void (^CompletionBlock)(id result, NSError *error);

 errornilかどうかをチェックし、nilでなければresultを参照して結果を受け取るというパターンです。しかし、resulterrornilになり得る訳で、厳密には下記4パターンが存在します。

result == nil result != nil
error == nil ? 成功
error != nil 失敗 ?

 この場合、「?」にあたる箇所では、果たしてそのリクエストが成功したのか、失敗したのか分かりません。もちろん、そうならないように実装するわけですが、厳密に起こり得ないことを強制する術はありません。

 一方でジェネリクスを使用して、Eitherと呼ばれる2つの可能性を表現する型を実装すれば、より明確に成功と失敗のコンテキストを表現することができます。Eitherの実装に関しては、こちらの実装が参考になります。

public enum Result<T,E> {
  case Success(Box<T>)
  case Failure(Box<E>)

(BoxはSwiftの値型の制限を回避するために存在しています。詳細については、Boxを参照してください。)

 これを利用すると、エラーハンドリングは下記のように記述することができます。

let api = APIs.Recipes.Get(id: 42)
SharedAPIClient.sendRequest(api) {
    switch $0 {
    case .Success(let box):
        // box.unbox <- 成功時の型Tが入っている
    case .Failure(let box):
        // box.unbox <- 失敗時の型Eが入っている
        // ここでエラーをハンドリングする
    }
}

 このようにジェネリクスを活用することで、より厳密なエラーハンドリングを行うことができます。

Enumの活用

 SwiftのEnumには計算型プロパティや関数を定義することができます。そのため、Enumの値によって振る舞いを変える場合、その振る舞い自体をEnum側に実装することができます。

enum MyTableViewSection: Int {
    case A = 0, B, C, D

    var heightForCell: CGFloat {
        switch self {
        case A:
            return 30
        case B:
            return 44
        case C:
            return 80
        case D:
            return 44
        }
    }
}

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return MyTableViewSection(rawValue: indexPath.section)!.heightForCell
}

 Objective-Cではswitch文の中が煩雑になりがちでしたが、SwiftのEnumを活用することで、より可読性の高いコードを書くことができます。

Swiftは実用段階か

 この項では、新規のプロジェクトをSwiftで始めるべきか、また、既存のプロジェクトをSwift化するべきなのか、ということについての私見を述べます。

Swift1.2の登場

 先日、Xcode6.3が正式にリリースされ、このアップデートにはSwift1.2も含まれています。正直なところ、Swift1.2がでるまでは、IDEの安定性、コンパイル時間などに大変な不満があり、Swiftへと移行したことを後悔することもありました。コードを記述すればするほど、ジェネリクスを多用すればするほど、それらの問題が顕著になってきます。しかし、Swift1.2から、これらの問題は大きく解消されています。これらを踏まえると、Swift1.2からはまともに使えるようになった、と言ってもよいでしょう。

Swiftのエコシステム

 Swiftのエコシステムには、既に素晴らしいライブラリが存在します。例えば、awesome-swiftのリストが参考になります。

 当該プロジェクトでも、すでに下記のSwift製ライブラリを使用しています。

 このようにSwiftには既に実用的なライブラリが多く存在し、効率よく開発を進めることができます。

 ちなみに、SwiftではStatic Libaryを生成することができず、また、Dynamic FrameworkはiOS8以降でのみのサポートとなっています。当該プロジェクトではiOS7もサポートする必要があったので、Swift製のライブラリはGitのサブモジュールとして管理し、プロジェクトファイル内にソースを直接追加してコンパイルしています。

Swiftに移行するべきか

 Swiftに移行することで、以前より、表現力が高く、堅牢なコードが書けるようになりました。アプリケーションが堅牢であることは、他のプラットフォームに比べてリリースサイクルが長くなってしまいがちなiOSアプリケーションにおいて、非常に重要です。

 実行時のクラッシュは!を使用して、強制的にアンラップした箇所や、unownedでキャプチャした箇所などに限局されていき、それ以外のクラッシュを招くようなコードは実行前に検出し易くなりました。

 このようなメリットを踏まえると、「新規のプロジェクト」や「まだ規模が小さく、長期的に運用されそうなプロジェクト」に関してはSwiftを積極的に選択するべきです。

 一方で、「大規模なプロジェクト」をSwift化するのは難しいと感じました。段階的移行といっても、前述のように、モデルやAPIクライアントのレイヤーを書き換えるとなると非現実的な作業量に成り兼ねません。また、新規のコードだけがSwift、というのも悪いとまではいきませんが、Swiftの利点がフルに活用できない点、言語切り替えのスイッチングコストが発生する点などを考慮すると、それほどメリットが無いのではないでしょうか。

おわりに

 Swiftはまだまだ発展途上な言語ではあるものの、既に十分な機能を備え、また、そのエコシステムも充実してきています。Swiftの機能を十分に活用すれば、Objective-Cに比べて、保守性が高く、安全なアプリケーションを実装することができるでしょう。この記事が、皆様の新規プロジェクトへのSwiftの採用、またObjective-CからSwiftへの移行の後押しに少しでもなれば幸いです。

Droidcon Italy 2015でアプリのパフォーマンスの話をしました

海外向けのAndroidアプリを開発している @rejasupotaro です。 4/12、13にDroidcon ItalyというAndroidのカンファレンスがありました。

Droidcon Italy 2015

私はそのカンファレンスでパフォーマンスの話をしてきました。この記事の前半では、カンファレンスで発表したパフォーマンスの話を、後半に海外のカンファレンスに参加してみてどうだったかということを書きます。

パフォーマンスの話

以下のような3部構成で話をしました。

  • HTTPと通信を行う環境
  • 画像の読み込みと最適化
  • UXを高めるAPI設計

以降の記事はスライドの補足が主になりますので、スライドを一読したあとに読み進めていただくとより実りがあると思います。

HTTP通信を見直す

通信の中身を見る

まず、通信を見ることができるようにします。以前は mitmproxyCharles などのプロキシツールを使っていましたが、今は Stetho を使っています。 USBに繋ぐだけで簡単にネットワークを見ることができるようになるのでAndroidに詳しくない人でも「挙動がおかしいのですが」「リクエストとレスポンス送ってもらえますか?」というやりとりをすることができるようになりました。

Gzipを有効にする

サーバー側でGzipが有効になっているからといってクライアント側で有効になっているとは限りません。Stethoを使ってGzipが有効になっていることを確認しましょう。 もし対応していなければ自分でリクエストヘッダに Accept-Encoding: gzip を付けて、レスポンスヘッダに Content-Encoding: gzip が含まれていたときにデコードする処理を書く必要があります。

OkHttpはデフォルトでGzipが有効になっています。HttpUrlConnectionや、その他のHTTPクライアントを使っている場合は実装を確認してください。 私は以下の理由からAPIや画像のリクエストにOkHttpを使うことをおすすめしています。

  • HTTP/2.0やWebSocketに対応している
  • コネクションをプーリングしたり、バッファをセグメントのリストとして実装して、拡張するときにはセグメントプールからメモリを再取得するなど、パフォーマンスに配慮した設計になっている
  • A Few 'Ok' Libraries (Droidcon MTL 2015) // Speaker Deck
  • Interceptorという仕組みによって柔軟なネットワーク処理を行うことができる
  • たとえば、NetworkInterceptorを使って手元でのレスポンスタイムを計測するモジュールを簡単に作ることができる

Interceptors · square/okhttp Wiki

Cache-Controlを有効にする

OkHttpはCache-Controlをサポートしています。OkHttpClientにCacheクラスのインスタンスをセットするだけで有効にすることができます。

// アプリ側
OkHttpClient client = new OkHttpClient();
Cache cache = new Cache(cacheDir, MAX_CACHE_SIZE);
client.setCache(cache);
# サーバー側
expires_in(1.hour, public: true)
# => Cache-Control: max-age=3600, public

2回目以降の表示を早くすることができるのもそうですが、ローカルにキャッシュを置くことでオフラインでもコンテンツを見れるようになるのが大きなメリットです。

有効にするのは簡単ですが、端末のキャッシュはサーバー側から消すことができないので、適切なポリシーを設定する必要があります。

Reactive Data Store

このアプリはRxJavaをベースに作られています。基本的な方針は以下の記事にある通りです。

RxJavaはデータの取得からスケジューリング、パイプライン処理、ビューへのバインディングまで、Androidフレームワークを補うように使っています。

画像のリクエストを見直す

画像はJSONのレスポンスに比べるとサイズが大きいので、ちょっとした変更で通信量を大きく減らすことができる可能性があります。そのため、アプリの通信量の大半を占める画像の読み込みライブラリは重要な役割を担っています。

パフォーマンスを左右する要素

画像読み込みライブラリでは以下の要素がパフォーマンスを左右します。

  • 内部で使用されるHTTPクライアントの種類と設定
  • キャッシュの管理
  • Executorの設定
  • リクエストのスケジューリング
  • Bitmapの管理

なのでこれらを中心に、どのように画像が読み込まれるかというのを見ていきました。何を使うにしても、キャッシュが有効になっているかと、適切な優先順位が設定しているかは見ると良いと思います。

Facebookが F8Fresco という新しい画像読み込みライブラリを発表しました。I/Oバウンドの処理とCPUバウンドの処理でExecutorを使い分けたり、WebPに対応していない端末にはJPEGにデコードするようにしたりするなど、PicassoやGlideや他の画像読み込みライブラリを研究して作られているので、これから検証しようと思っています。

画像のフォーマットとサイズ

FacebookのiOSアプリはProgressive JPEGを採用している(Faster Photos in Facebook for iOS | Engineering Blog | Facebook Code)ようですが、AndroidはProgressive JPEGには対応していませんでした。しかし、FrescoでProgressive JPEGに対応するようにしたようなので、Facebookは両方プラットフォームともProgressive JPEGに移行するのかもしれません。 私たちはWebPで画像を配信しています。

また、スマートフォンのディスプレイサイズは様々で、安価で小さい端末と、Nexus 10のような大きな端末では表示に必要な画像サイズが違います。 そこで、私たちは動的画像変換サーバーをS3の前に置いて、実際の画面サイズに合わせて変換するようにリクエストをしています。 それに加えて、回線の品質に応じて係数を掛けて画像を縮小するということをしています。

回線の品質を判定するのに、私たちのアプリでは通信の規格を見ています。日本では4GよりWiFiの方が速いのですが、海外では4Gの方が速かったりすることがあります。そのため規格を見るのは厳密な方法ではありませんが、お手軽な方法ではあります。

画像はURLでキャッシュされるので、回線の品質の判定が変わったときにキャッシュにヒットしなくなるということを心に留めて置く必要があります。品質の判定をするときにはスパイクに対応する必要がありますし、そうでなくてもオフライン時に画像が取得できるように、前回の回線の品質判定を保存しています。

係数をいくつに設定するかは、画像がそのサービスにおいてどれほど重要かに依存しています。クックパッドでは過去の調査から、レシピ画像はレシピを決めるときの重要な情報であるということが分かっていたので、画像が荒いと感じられない程度の値に設定しています。

API設計を見直す

レスポンスにBase64でエンコードされた10px四方のサムネイルを含めるようにしています。それによってレスポンスを受け取ったら直ちに画像のようなものを表示することができるようになります。

レスポンスを受け取った直後の状態の比較

ネットワークが不安定だったり遅いところでは、APIをリクエストする回数を減らすのが重要になります。 このアプリでは、レシピを探すという行動は重要な体験になってきます。ユーザーはレシピ間を行ったり来たりするので、そこのストレスをなるべく少なくするために、レシピ検索のAPIでレシピ詳細画面で必要なデータを返すようにして、ユーザーの行動を遮らないようにしています。 この最適化は部分レスポンスによる最適化とのトレードオフですか?という質問がありましたが、必要な情報は取得するようにして、不必要な情報は削るようにするということなので、トレードオフということではありません。

これまで説明してきた最適化は、UXを最大化することを目的で行っています。 どのようにデータを表示するか、どのデータが必要でどのデータが必要でないかは、実際のユースケースに依存するので、ユーザーに一番近いところで開発しているモバイルエンジニアにとって、APIやネットワークのスキルがこれからますます重要になってくるという話で締めました。

海外のカンファレンスに参加する

私のチームは世界各地に分散しており、朝会、チャット、GitHubはどれも英語で行われていますが、私はチームの中でも私が一番英語が苦手だったので、英語の勉強をしにいくつもりで応募しました。

Droidcon Italyについて

参加者数は、去年が400人くらいだったのに対して、今年は800人以上に増えたそうなので、開発者の注目が集まっているということが分かります。 セッションの内容を振り返ると、DIやMV*やリアクティブなどのアーキテクチャの話、EspressoやRobotiumやRobolectricなどのテストの話、プロトタイピングやマテリアルデザインやブランディングなどのデザインの話など、界隈のホットな話を聞くことができました。 イタリアでも「iOSと同じデザインで作ってくれと言われて困っている」と言っていて、日本とあまり変わらないんだなと思って親近感が湧きました。

Keynoteの様子

カンファレンスで発表するためにしたこと

Abstractをちゃんと書く

勢いで書いたら通ったー!という訳ではなく、海外で話すのは今回が初めてだったので慎重に調べて応募しました。

英語論文の書き方やテクニカルライティングの本を読んで勉強して、英語が得意な人に何回か添削してもらいながら、一ヶ月ほど掛けてAbstractを書きました。

後で知ったことですが、トークを採択するときは、その人のTwitterやブログやGitHubや他のカンファレンスの登壇の経験の有無から判断するらしいです。 私はTwitterやブログは日本語でやっているので不利であるということと、採択されたあとも参加者はAbstractを読んでセッションを決めるので重要(なんとか採択されてもAbstractが微妙だと部屋が埋まらなくて寂しい思いをしたり)なので、Abstractは時間を掛けて作る価値があったと思いました。

発表の練習をする

まず、Abstractをもとに原稿を書き起こしました。そのあとにスライドを作り始めると同時に英語の発音の練習も始めましたが、すぐに発音がうまくなるとは思わなかったので、なるべくスライドに文字を入れつつも、文字で埋めすぎないような調整をしました。 喋り以外の方法を使ってでも伝えなければと思って、たとえばキューに入れられたタスクがライフサイクルの変化とともに、どのようにスケジューリングされるのか、の説明でアニメーションを使ったりしています。 そして、スライドが出来たら頭に入るまで繰り返し声に出して練習をしました。

当日は部屋に入りきらないくらいに人が来てくれて、終わったあとに「素晴らしいトークをありがとう」と言ってもらえたので良かったです。

おわりに

細かいパフォーマンスのtipsを共有しました。パフォーマンスチューニングに没頭すると局所最適にハマってしまいがちなのですが、実際にはユーザーが使うときにどう見えるかと考えながら全体の設計をしていくという話をしました。

弊社には海外のカンファレンスでの発表の経験者が多くいる中で、経験の浅い私が知見と称してブログを書くのは気が引けましたが、最初のハードルを越えるのは大変だと思うので、そういう人のためになればと思います。

ちなみに日本では今週末の4/25に DroidKaigi ですね。運営に携わっている人からは日本でもDroidconのようなコミュニティを作っていきたいという話を聞いておりますので、参加して一緒に盛り上げていきましょう。