Android Studio 4.1 RC1で公式日本語パックの導入と文字化けを解消する

2020年8月

はじめに

Android Studio 4.1系からIntelliJ IDEAのベースバージョンが2020.2に上がったので公式の日本語パックを導入できるようになりました。ですがランタイムが古く文字化けしてしまうので解消します。

image.png

手順とランタイムの最新バージョンは以下のリンクで確認できます。

ランタイムを変更する

  1. PluginsのMarketplaceタブからChoose Runtimeをインストールする。
    image.png
  2. Ctrl + Shift + AでFind Actionを開いてchooseを入力して絞り込む。
    image.png
  3. Choose Runtimeを開いて最新版のJetBrains Runtimeを選択する。
    • 現時点で1035.1が最新でした。
      image.png
  4. Android Studioを再起動する。

日本語パックをインストールする

  • PluginsのMarketplaceタブからJapanese Language Packをインストールする。
    image.png
  • Android Studioを再起動する。

以上で日本語パックを導入できます。
image.png

AppSync の DynamoDB リゾルバの Scan で意図したデータが取得出来なくなった時の対処法

2020年8月

はじめに

開発中盤で AppSync で開発している最中に追加したデータが、
突然 List Query で取得出来ない事態が発生しました... :scream_cat:

調査したところ、
AppSync のデータソースで DynamoDB のデータソース生成した際に GraphQL を自動生成したのですが、
そこにヒントがあることを発見しました:bulb:

結局の所、DynamoDB への理解不足で片付く話なのですが、、
何となく AppSync を使っていると遭遇してしまう問題だと思います... :punch:

そこで、同じ問題で悩む人を助けられればと思い、
記事に残しておくことにしました :writing_hand:

DynamoDB に登録されているはずのデータが AppSync の List Query で取得したデータに含まれない事象が発生することがある

AppSync で開発を開始した直後や、
登録データ数が少ない間は List Query の filter を用いてデータの絞り込みを行いつつ、
正常に意図したデータが取得出来るはずです :arrow_down:
4a96c5227343327499ec4c380f65b661.png
8b41f67abe2fc998ca2d6e97925d87ec.png

しかし、ある日 DynamoDB にデータが登録されているのに List Query の filter で、
正常に意図したデータが取れない事象が特定の List Query で発生するようになってしまいました... :upside_down:

20b80b348009f35572b930aeb5bbdeb2.png
5492af978c4e56f29e6f8234d999344d.png

DynamoDB には channel_id42713673-eb21-43ef-bdbc-f460989fb505 でデータが 1件登録されていることは確認済みなのに AppSync の List Query では 1件も取得できないのは何故でしょうか。。??:thinking:

DynamoDB データソース追加時に自動生成されたリゾルバを確認してみる

DynamoDB のデータソース登録時に自動で GraphQL 生成にチェックを入れていれば、
AppSync の List Query は自動生成され DynamoDB リゾルバが登録されています :white_check_mark:

原因調査のため AppSync の裏の DynamoDB リゾルバが何を行っているか中を覗いてみます :mag:

9a7d6dcbfb03aab6abfaf21cdf91a20a.png
3a91f0e79dcd05153f6e52543975dcda.png

:arrow_up: を見ると、Scan を用いて filter で検索条件を絞って limit で指定した件数のデータが取得出来るようになっているように見受けられます :mag:

DynamoDB の Scan の仕様を確認する

次に DynamoDB の公式ドキュメント を見て Scan の仕様について確認していきます。
早速公式ドキュメントのトップに Scan についての説明が記載されていました :pencil:

Amazon DynamoDB での Scan オペレーションは、テーブルまたは セカンダリインデックス のすべての項目を読み込みます。デフォルトでは、Scan オペレーションはテーブルまたはインデックスのすべての項目のデータ属性を返します。

:arrow_up: を見ると Scan はデフォでレコードを全件取得するような挙動となるようです。

結果セットの項目数の制限

ドキュメントを読み進めていくと、
次に 結果セットの項目数の制限 の項目が目に止まりました :eyes:

ここで、Scan にフィルタ式を追加するとします。この場合、DynamoDB は返される 6 つの項目にフィルタ式を適用し、一致しない項目を廃棄します。最終的な Scan 結果はフィルタリングされる項目の数に応じて、6 つ以下の項目を含みます。

:arrow_up: を見ると Scanfilter を使用した際の挙動として 一致しない項目を廃棄します とあります :wastebasket:
AppSync の DynamoDB リゾルバを確認した際、Scanlimit はデフォでは 20 でした。

つまり、正常にデータが取得出来なかった AppSync の List Query を元に、
実際に実行された DynamoDB の Scan の挙動を推測すると、
データを 20件取得した中に該当する channel_id のデータが見つからなかったので空を返した
という挙動になりそうです :pencil:

RDBMS の SQL の Where 句のような、
該当する channel_id のデータを 20件まで検索して取得する
という挙動を想定していましたが、それがそもそもの誤りだったようです... :upside_down:

スキャンの読み込み整合性

更にドキュメントを読み進めていくと スキャンの読み込み整合性 という項目も発見しました :eyes:

Scan オペレーションは、結果的に整合性のある読み込みをデフォルトで行います。つまり、Scan 結果が、最近完了した PutItem または UpdateItem オペレーションによる変更を反映しない場合があります。詳細については、「Read Consistency」を参照してください。

強力な整合性のある読み込みが必要な場合は、Scan が開始する時に ConsistentRead パラメータを true リクエストで Scan に設定できます。これにより、Scan が開始する前に完了した書き込みオペレーションがすべて Scan 応答に含められます。

:arrow_up: を見ると RDBMS では保証されている一貫性は無いので、
Scan を実行するタイミングによって取得可能なデータは変動する可能性がある
ということを Scan を用いたデータ取得を行う際は考慮する必要がありそうです :pencil:

対策方法

まず、AppSync の limit がデフォで 20 なので GraphQL クエリの limit100 等の、
一度の Query で filter で絞り込むデータが十分に取得出来そうな数に設定しておきます1 :printer:

8bbf374ac035a470852cad3f425466ab.png

また、Web アプリケーション等でよく使用される、画面最下部までスクロールした際にロードを行う処理の実装等を考慮した場合、nextToken を用いたページネーションの実装も考慮しておく必要があります :white_check_mark:

d3e7bd3b6c78b2c47f1693baa83fca03.png
6e5f9b851e6d2af8b0e83b716f3a6cc3.png

:arrow_up: の流れで List Query を実行するようにしておけば、
画面最下部までスクロールした際のロード処理が実装可能です :thumbsup:

おわりに

結果 AppSync というよりも DynamoDB の仕様にフォーカスに当たった記事内容になりました :writing_hand:

AppSync の DynamoDB リゾルバを雰囲気で扱っていたため起きた問題で、
ステージング環境で実際にデータがある程度追加されてきてから問題が発覚したため、
個人でデバッグしているときには全く気づくことが出来ませんでした... :bomb: :boom:

また保守運用の観点から予め DynamoDB の設計についても考慮しておいたほうが良いと思われます :raised_hand:
公式ドキュメントに DynamoDB の設計のベストプラクティス についての解説ページがあるので読んでおいた方が良さそうです :book:

以上 AppSync を用いる際に DynamoDB リゾルバを自動生成して利用する際は十分にご注意ください :bow:

参考リンク


  1. 大量のデータが登録されている状況であれば Scan の Query から limit は無くしてしまい、一度の Scan で最大の 1.0MBまでのデータを取得するようにして良いかも知れません 

Serverless Framework でエラーを検知して Webhook で Slack に通知を飛ばす方法

2020年8月

はじめに

AppSync の Lambda リゾルバを書く際に Serverless Framework を使用したのですが、
デプロイ後のバグ調査の際、毎回ブラウザから AWS Console を開いて該当 Lambda の CloudWatch のログを見に行くのが面倒でした。。 :upside_down:

そのため、エラーレポートの仕組みが欲しくなり、Lambda のエラーを Slack に通知する仕組みを Serverless Framework で実装する方法について調査したので、備忘録も兼ねて記事にまとめました :writing_hand:

追記 (2020/08/19)

ローカルから Lambda 関数のログを確認したいだけなら、Serverless Framework CLI の logs コマンド もしくは、 AWS CLI の logs コマンド で可能みたいです :mag:

動作環境

1. Slack で Webhook URL を発行する

まずは 公式サイトの手順 に従って Webhook URL を発行します :earth_americas:

無事発行できると、
https://hooks.slack.com/services/~~~~~/~~~~~/~~~~~~~~~~~ のようなフォーマットの URL が取得出来るはずなのでメモっておきます :pencil:

2. 必要な npm パッケージをインストールする

Slack の Incoming Webhook の仕組みを使用し、
チャンネルにメッセージを送信するための npm パッケージをインストールします :arrow_down:

npm install @slack/webhook --save

3. Slack にエラーレポートを送信する Lambda 関数を作成する (TypeScript)

Serverless Framework の handler に Slack にエラーレポートを送信する関数を追加します :arrow_down:

handlers/Reporter.ts
import { gunzip } from "zlib";
import { IncomingWebhook } from "@slack/webhook";
/**
 * CloudWatch のログ情報
 */
interface CloudWatchLogContent {
  messageType: string;
  owner: string;
  logGroup: string;
  logStream: string;
  subscriptionFilters: string[];
  logEvents: {
    id: string;
    timestamp: string;
    message: string;
  }[];
}
/**
 * Lambda リゾルバの型定義
 */
type LambdaResolver<TEvent = any> = (event: TEvent) => Promise<any> | any;
/**
 * CloudWatch のロググループに出現するエラーを通知する
 * @param event 該当するエラーログの内容
 * @return {object} event オブジェクトをそのまま返却する
 */
export const NotifyError: LambdaResolver = async (event: any) => {
/**
{
  awslogs: {
    data: 'H4sIAAA...'
  }
}
CloudWatch から呼ばれた際の event には上記フォーマットでデータが入っている。
data 内には Base64 でエンコードされた gzip 形式で圧縮されたデータが入っているので、
gzip 形式のデータを解凍しつつ、Base64 デコードを行い JSON 文字列を取得するための関数
*/
  const gunzipAsync = async (base64Logs): Promise<string> => {
    return new Promise(function (resolve, reject) {
      gunzip(base64Logs, function (err, binary) {
        err ? reject(err) : resolve(binary.toString("ascii"));
      });
    });
  };
// 1. Base64 でエンコードされた gzip 形式で圧縮されたデータを Base64 でデコードし、gzip のバイナリとして取得する
// gunzipAsync 関数で gzip 解凍して ascii 文字列として取得することで CloudWatch のログ内容を JSON 文字列で取得する
  const base64Logs = Buffer.from(event["awslogs"]["data"], "base64");
  const uncompressedLogs = await gunzipAsync(base64Logs);
  console.log(uncompressedLogs);
// 2. 取得した JSON 文字列を CloudWatchLogContent に変換して取得する
  const content = <CloudWatchLogContent>JSON.parse(uncompressedLogs);
  console.log(content);
// 3. 発行した Slack の Webhook URL で IncomingWebhook クラスを生成し、
// send 関数で Slack チャンネル名 (ex. #serverless-error-report) と、
// CloudWatchLogContent の内容を元に作成したテキストを引数に指定して、
// 該当する Slack チャンネルにテキストを投稿する
  const webhook = new IncomingWebhook("<1. で発行した Slack の Incoming Webhook URL>");
  await webhook.send({
    channel: "<通知したいチャンネル名 (例: #serverless-error-report)>",
    icon_emoji: "hammer", // icon_emoji パラメタを指定すると Slack へのメッセージ通知の際のアイコンを変更することが可能
    text: `*Group*\n_${content.logGroup}_\n\n*Message*\n\`\`\`${content.logEvents[0].message}\`\`\``,
  });
  return event;
};

4. 3. の関数をデプロイする関数として追加し、各種 Lambda 関数の CloudWatch のログを監視するイベントと紐付ける

serverless.yml手順 3. で作成した関数 NotifyError を記載すると共に、
監視したい関数の CloudWatch ロググループを eventscloudwatchLog に定義し、filterERROR を指定します :heavy_check_mark:

これで、
該当ロググループに ERROR が含まれていた場合、
都度 Lambda 関数が実行されるようになります :fire: :arrow_down:

serverless.yml
#...
# 3. で作成した Slack にエラーレポートを送信する関数 NotifyError を functions に追記し、
# events を用いて、他 Lambda 関数のロググループに 'ERROR' が出力されていた場合、 NotifyError 関数が実行されるようにする
functions:
#...
  NotifyError:
    handler: CloudWatch.NotifyError
    events:
      - cloudwatchLog:
          logGroup: /aws/lambda/${self:service.name}-${self:provider.stage}-TestFunction1
          filter: ERROR
      - cloudwatchLog:
          logGroup: /aws/lambda/${self:service.name}-${self:provider.stage}-TestFunction2
          filter: ERROR
      - cloudwatchLog:
          logGroup: /aws/lambda/${self:service.name}-${self:provider.stage}-TestFunction3
          filter: ERROR
#...

:arrow_up: が完了したら sls deployNotifyError 関数をデプロイします :keyboard:

最後に AWS CLI で CloudWatch にイベントログデータを送信してみて、
本当に Slack に通知が飛んでくるか動作確認を行いましょう :hammer_pick:

注意事項 (複数の cloudwatchLog を定義した場合)

events に複数の cloudwatchLog を定義した場合、
関数のデプロイ後、AWS Console で該当する Lambda 関数を見に行くと、
正しく CloudWatch のイベントが紐付けられていないように見えます :arrow_down:
492d4761e7fadb62612e539c17b3d05f.png

同様のケースが発生している方は他にもいらっしゃるようですが、
手順 5. で Slack への通知まで確認出来れば問題なく設定できています :thumbsup:

5. AWS CLI を利用してCloudWatch にイベントログデータを送信して Slack に通知が飛んでくるか検証する

CloudWatch にイベントログデータを送信するコマンドです :white_check_mark: :arrow_down:

aws logs put-log-events \
    --log-group-name '<該当するロググループ名>(例: /aws/lambda/test-dev-TestFunction1)' \
    --log-stream-name '<ログストリーム名(例: test-stream)>' --log-events \
    timestamp=(node -e 'console.log(Date.now())'),message="This is ERROR"

コマンド実行後、
AWS Console から CloudWatch の該当するロググループのログストリームを見に行くと、
This is ERROR という文字列が出力されている事が確認できるはずです :mag:

あとは Slack に通知が飛んできたことまで確認できれば動作確認完了です! :tada: :arrow_down:
4a5d063485ed635c83e3ea1917043673.png

おわりに

Serverless Framework 内で完結する形で、
Lambda 関数のエラーを捕捉して Slack に通知を飛ばす方法についてまとめました :writing_hand:

events には cloudwatchLog の他にも eventBridge というものも指定できます。
eventBridge を使用すると CloudWatch 以外の様々な AWS サービスのイベント駆動で Lambda 関数を実行することが可能です :muscle:

events を有効活用することで効率よくイベント駆動の処理を書いていけるので、
是非とも有効活用していきましょう! :runner: :dash:

参考リンク