license-tools-pluginの移行に関するお知らせ

はじめまして、クックパッドでAndroid開発を担当している吉田です。 本記事は弊社がOSSとして公開していたAndroid向けのライセンス管理プラグインに関するお知らせです。

2行まとめ

旧license-tools-pluginをアーカイブした背景

クックパッドでは社内で利用していたライブラリのいくつかをOSSとして公開します。これらは国内外を問わず多くの方にご利用頂いていることを観測しており、皆様の開発が僅かでも効率化されていればとても嬉しく思います。 さてOSSについてまわるのがメンテナンス問題です。クックパッドのエンジニアもここ数年で入れ替わりライブラリの著者が既に在籍していないケースもあります。license-tools-pluginもそのケースに当てはまっており、issueやPRが届く度に持ち回りで対応していたのですが直近ではほとんど対応出来ていない状態でした。そういった経緯から一度チーム内で話し合いの場を設けて議論したのですが、「ずるずる公開し続けているよりは一思いにアーカイブしよう」という結論になりました。

新たにLicenseToolsPluginを開発した背景

私個人としてlicense-tools-pluginは非常に気に入っているプラグインの一つでした。過去には紹介記事も書いています。

techlife.cookpad.com

愛着のあるOSSだったため継続してメンテナンスしたかったのですが、一番の心理的な障壁がGroovyで実装されている点でした。そんな折にふとKotlinでプラグインをフルスクラッチ出来るのではないかと思い、休日にコアロジックを移植してみた所思いの外上手く行きました。後日そのような話をテックリードとの1on1で話したところ「業務時間使ってプラグインの開発をやっていいよ」とあっさり承認を頂いたので100% Kotlinによるフルスクラッチプロジェクトを始めることにしました。これは蛇足というか弊社のアピールですがクックパッドはOSS活動に理解があるのでライブラリ開発の7~8割は業務時間を利用して書いています。
そのようなわけでフルスクラッチ化のプロジェクトも無事完了し、クックパッドのAndroidアプリは既に新しいライセンスプラグインに置きかえました。互換性の問題や大きな不具合は解決したと思いますので、みなさまにも安心してご利用頂けると思います。また機能はシンプルですがgoogleから公式のプラグインが提供されているのでこちらをご検討ください。

developers.google.com

新プラグインへの移行

コマンドやオプションは互換性があるため、cookpad/license-tools-pluginをご利用のプロジェクトではプラグインだけ新しいものに切り替えれば動作します。これは一例ですがアプリケーションモジュールの差分は以下のようになるかと思います。

buildscript {
    repositories {
        google()
        jcenter()
        mavenLocal()
    }
    dependencies {
-        classpath 'com.cookpad.android.licensetools:license-tools-plugin:1.7.0'
    }
}
- apply plugin: 'com.cookpad.android.licensetools'

+plugins {
+  id "com.cookpad.android.plugin.license-tools" version "${latest_version}"
+}

apply plugin: 'com.android.application'

$latest_versionはレポジトリを参照して最新のバージョンをご利用ください。初めて利用するという方やその他詳細について知りたいという方はレポジトリのREADMEをご覧ください。

github.com

DroidKaigi2020のお知らせ

Droidkaigi 2020では「KotlinではじめるGradle Plugin」というセッションを話させて頂くことになりました。プラグイン開発はAARライブラリの開発より情報量が圧倒的に少なかったり、馴染みの薄いGroovy実装による解説記事が多かったりと初心者にはやや敷居が高いですが、難しいことは少なく業務効率の改善にも繋がりやすい技術だと思いますのでご興味があればカンファレンス会場まで足を運んで頂けると嬉しく思います。

「ぶっちゃけお給料はどうですか?」夜の合同説明会を今年も開催します!(クックパッド・freee・グリー・はてな)

f:id:mirakui:20200108164043j:plain

エンジニアとしての就職を考えている学生のみなさま、こんにちは。クックパッド CTO の成田です。 就職先やインターン先を探している方も、これから探し始める方も、そもそもどんな会社にどんな仕事があるのか、イメージは沸きますか?

企業のホンネ、知ってますか?

よくある就職説明会では、各企業の人事担当者の方や、現場のエンジニアの方とお話しする機会があると思います。しかし、本当に皆さんの聞きたいホンネは、そういった場で教えてもらえるでしょうか。

クックパッドを含むインターネット系の企業では、エンジニア組織のこと(エンジニア採用基準、育成、評価、給与など)は CTO が意思決定権を持っている事が多いため、裏事情やホンネを聞きたければ、CTO に聞くのが一番手っ取り早く、正確です。

そんな機会を用意したく、4年前からクックパッドでは「夜の合同説明会」を他社と合同で開催しています。

夜の合同説明会って?

夜の合同説明会の趣旨は、CTO やその相当職のパネラー達が、参加者からの質問に何でも答える、というものです。 もし聞きたい質問がまだないという場合は、他の人の質問を聞いているだけでも大丈夫です。

各社、都合の悪いことも含めて本当に何でも答えちゃうので、回答内容は SNS への投稿を禁止しています。悪しからず。 そういう、普通のお堅い会社説明会と違った雰囲気をつくるために、会場はクラブをお借りしています。クラブで開催するのはただの演出なので、踊ったりする必要は無いし、ドレスコードもないので、勉強会のような感覚で安心して気軽にお越しください。

パネラー

今年のパネラーは以下の方々です。

藤本真樹 (グリー株式会社 取締役 執行役員常務 最高技術責任者) @masaki_fujimoto

大西康裕 (株式会社はてな 執行役員 サービス・システム開発本部長) @yasuhiro_onishi

成田一生 (クックパッド株式会社 執行役 CTO) @mirakui

横路 隆 (freee株式会社 共同創業者 最高技術責任者) @yokoji

日時・申し込み方法

日時は 2020/01/22(水) 19時開始で、場所は東京・六本木です。

以下の connpass ページからお申し込みください。会場の住所等も記載しています。

connpass.com

どんな質問があるの?

過去3回開催しています。過去の回に出た質問の例を紹介します。

  • 新卒採用ではどんなエンジニアが欲しいですか?
  • 学生時代にやっておくべき事はなんですか?
  • ぶっちゃけお給料はどうですか?
  • 新卒が成果を出せるようになるまでの期間はどのくらいを見込んでいますか?
  • 儲かるのと社会貢献、どっちが大事ですか?
  • 新卒に対して求めるのは「カルチャーマッチ」「技術」どちらですか?
  • いいエンジニアとはなんですか?
  • 転職して出て行く人についてどう思いますか?
  • 新卒採用では、休学や留年をした人についてどのような印象がありますか?
  • 各社のホンネの裏話を一つずつ教えてください
  • 他社の10年先を行ってるような技術はありますか? また、そういう技術のための投資をしていますか?

おわりに

「夜の合同説明会」は、毎年とても有意義なホンネのトークセッションが行われており、好評をいただいています。なかなかこのパネラー達の話をまとめて聞ける機会はないので、学生のみなさま、お誘い合わせのうえ是非ご参加ください!

connpass.com

Amazon Elasticsearch ServiceをつかったRDSのスロークエリの集計と監視

こんにちは、SREの菅原です。

クックパッドの多くのシステムは AWS 上で稼動しており、そのWebサービスの多くはデータベースにAmazon RDSを使っています。

WebサービスがDBを使う場合、ボトルネックになりやすいDBのパフォーマンスを落とさないためにスロークエリの監視はとても重要です。そこで、Amazon Elasticsearch Serviceを使ったスロークエリの集計・監視システムを構築したので、それについて紹介したいと思います。

※今のところMySQLエンジンのみを対象としています

システム構成

システムの構成は以下のようになります。

f:id:winebarrel:20191225135113p:plain

また、社内のシステムと完全に同じ訳ではありませんが、同様の構成のSAMプロジェクト(Elasticsearch Serviceに保存するまでの部分)をGitHubで公開しています。

https://github.com/winebarrel/sam-rds-slowquery-to-es

Elasticsearch Service使ったスロークエリの集計はよくある構成ですが

  • pt-fingerprintでクエリを正規化して集計しやすくしている
  • Elasticsearch Service(Open Distro)のAlerting機能を使って、スロークエリが発生したときにアラートを出すようにしている
  • Alertingの設定をGitHubでコードとして管理している

などといったあたりが他のシステムには見られない部分だと思います。

pt-fingerprintを使ったクエリの正規化

「どのようなクエリに時間がかかっているか」「件数が多いのはどのクエリか」などを集計しようと思うと、クエリを正規化して値やフォーマットだけ違うようなクエリも同じものとして扱える必要があります。

mysqldumpslowやpt-query-digestなどのツールを使うとクエリを自動的に正規化して集計してくれますが、Elasticsearchにはそのような機能がないため、Elasticsearchへクエリの投入を行うLambdaファンクション内でpt-fingerprintを実行して、クエリを正規化しています。

クエリを正規化することで、Kibana上でpt-query-digestのようなダッシュボードを作成することができます。

f:id:winebarrel:20191225133508p:plain

また、クエリにはメールアドレスなどのセンシティブな情報が含まれることもあるため、そのような情報をマスクしてElasticsearchから見られないようにするという意味もあります。

Alerting機能を使った監視と設定の管理

Amazon Elasticsearch Service(Open Distro)には、オリジナルのElasticsearchのX-Packとは別に独自のAlerting機能が使えるようになっています。

Alerting機能を使うと、単位時間あたりのスロークエリの発生件数が閾値を超えた場合にSlackなどにアラートを通知することができます。

f:id:winebarrel:20191225133704p:plain

このシステムではさらに、Alertingの設定ファイルをGitHubでコードとして管理して、マージされた場合に自動的にElasticsearch Serviceの設定を変更するようにしました。

f:id:winebarrel:20191225133724p:plain

これにより、モニターの作成や閾値などの変更を容易にすることができました。

設定ファイルは以下のように記述されます。

 local action = import '../lib/action.libsonnet';

{
  type: 'monitor',
  name: 'my-service',
  schema_version: 1,
  enabled: true,
  schedule: {
    period: {
      interval: 1,
      unit: 'HOURS',
    },
  },
  inputs: [
    {
      search: {
        indices: [
          'aws_rds_cluster_my-service_slowquery-*',
        ],
        query: {
          size: 0,
          query: {
            bool: {
              filter: [
                {
                  range: {
                    timestamp: {
                      from: '{{period_end}}||-1h',
                      to: '{{period_end}}',
                      include_lower: true,
                      include_upper: true,
                      format: 'epoch_millis',
                      boost: 1,
                    },
                  },
                },
                {
                  bool: {
                    must_not: [
                      {
                        term: {
                          'log_stream.keyword': {
                            // バッチ用のDBでサービスには影響が出ないため、このDBへのクエリは無視する
                            value: 'db-batch-001',
                            boost: 1,
                          },
                        },
                      },
                      {
                        term: {
                          'sql_fingerprint_hash.keyword': {
                            // すぐに修正することが難しいため、このハッシュ値のクエリはいったん無視する
                            // see http://github.com/cookpad/my-service/issue/123
                            value: 'a43f9b2b1800fc8aa09bbcddcf63eab445b5af87',
                            boost: 1,
                          },
                        },
                      },
                    ],
                    adjust_pure_negative: true,
                    boost: 1,
                  },
                },
              ],
              adjust_pure_negative: true,
              boost: 1,
            },
          },
          aggregations: {},
        },
      },
    },
  ],
  triggers: [
    {
      name: 'slowquery-trigger',
      severity: '1',
      condition: {
        script: {
          source: 'ctx.results[0].hits.total.value > 10',
          lang: 'painless',
        },
      },
      actions: [
        action.slowqueryNotifier(
          'ap-northeast-1',
          'cluster:my-service',
          std.join(
            ' AND ', [
              'identifier.keyword:my-service',
              'NOT log_stream.keyword:db-batch-001',
              'NOT sql_fingerprint_hash.keyword:a43f9b2b1800fc8aa09bbcddcf63eab445b5af87',
            ]
          )
        ),
      ],
    },
  ],
}

※詳しい書き方についてはOpen Distroのドキュメントを参照してください

Alerting設定はJsonnetで定義され、CodeBuildからElasticsearch ServiceにポストするときにJSONに変換されます。

スロークエリはアラートでは「特定のクエリを無視したい」(例: 深夜の実行でサービスへの影響が少ない・すぐの対応が難しいクエリは無視)「特定のサーバへのクエリを無視したい」(例: バッチ用サーバへのクエリは無視)などといったことがあるので、モニタリング対象の条件にクエリのハッシュ値やサーバ名などを指定できるようにして、ノイズとなるアラートが上がらないようにしています。

そのほかに工夫した点

Lambda上でpt-fingerprintの実行

LambdaではRubyランタイム上でRubyのスクリプトを動かしているのですが、PerlのData::Dumperモジュールが含まれておらず、そのままの状態でpt-fingerprintを動かすことはできませんでした。そのため、pt-fingerprintに以下のようなパッチを適用して、Data::Dumperモジュールを利用しないようにしています。

https://github.com/winebarrel/sam-rds-slowquery-to-es/blob/master/pt-fingerprint.patch

Data::Dumperモジュールはデバッグ出力としての利用だけなので、この変更による動作への影響はないと考えています。

断片的なSQLの無視

CloudWatch Logsに出力されるスロークエリは、基本的に1メッセージに対して 1 つのクエリのログが出力されますが、まれに非常に長いクエリが複数のメッセージにまたがって出力されることがあります。 これを正しくパースするためには分割されたメッセージを一時的にDynamoDBなどに保存し、後続のメッセージがきたタイミングで他の断片と結合してパースする必要があります。

しかし、正しくパースしようとするとシステムが複雑になり運用のコストが上がってしまうため、このシステムでは単純に無視するようにしています。

rdsadminユーザの無視

RDS(MySQL)ではlog_queries_not_using_indexesを有効にすることで、実行時間にかかわらずインデックスを使用していないクエリをスローログに出力することができます。 log_queries_not_using_indexesをただ有効にしただけだと大量のスロークエリが出力されてノイズになってしまうので、min_examined_row_limitを同時に設定することで走査行数の少ないクエリが出力されるのを抑止することができます。

これらの設定を一部のRDSで有効にしたところ、rdsadminユーザによるスロークエリが大量に出力されるようになってしましました。

これは min_examined_row_limitのスコープが「セッション」であるため、既存の接続が切れない限りセッションが維持されており、新しいセッション変数が使われないことが原因でした。 rdsadminユーザはRDS側のシステムが利用するユーザであり、AWS 利用者側からは操作できません。そのため、接続を切る方法をサポートに問い合わせたところ、再起動以外の方法でrdsadminユーザの接続を切る方法はないとのことで、ワークアラウンドとしてrdsadminユーザのスロークエリは無視するようにしました。

rdsadminユーザがスロークエリを発行する可能性がないわけではないので、接続が切れるタイミングがあれば、無視する処理はなくしたいと考えています。

まとめ

RDSのスロークエリをElasticsearch Serviceに流すことで、わかりやすくダッシュボードにまとめることができ、また、アラートの設定によりスロークエリの増大を素早く気づけるようになったと思います。

じわじわと増え続けるスロークエリはすぐにサービスに影響を出すものではないため見落とされがちなのですが、突発的なアクセスの増大などがあると簡単にサービスをダウンさせてしまうものなので、このシステムでスロークエリを減らしていきたいです。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/