人工知能学会誌の特集「広告とAI」にZucks Ad Networkの取り組みを寄稿しました

hagino3000です。今週はカナダで開催中のKDD2017に参加しています。

人工知能学会誌の7月号の特集「広告とAI」に「アドネットワークにおける広告配信計画の最適化」という記事を寄稿しました。軽く内容を紹介します。

【会誌発行】人工知能学会誌 Vol. 32 No. 4 (2017/07) – 人工知能学会 (The Japanese Society for Artificial Intelligence)

内容について

導入はインターネット広告事業のタイプ(SSP, DSP, アドネットワーク, etc.)とそれぞれの役割を紹介。中でもアドネットワークは媒体社と広告主の両者の要望を満す必要があるため、広告効果と媒体社収益の2つが要件になる事を説明しました。
インターネット広告を例に出す論文は「クリック課金型広告においてはクリック率の高い広告を出せば良い」という設定をしばしば持ちだします。そのせいかアカデミアの人と話をしていると「ネット広告って、バンディットでクリック率の高いバナーを出していくだけでしょ?」と言われる事があります、が実際はそんな事無いです。

配信する広告を選択する方策については「活用と探索のジレンマ」に軽く触れた後に、広告配信の設定に適用しやすいバンディットアルゴリズムの手法であるThompson Samplingを紹介。報酬を クリック単価×クリック率 とした時の手続きを例に挙げました。

最後にCPA1を制約として媒体社収益を最大化する方策についてまとめています。 記事中では簡単のためにCPAを制約としましたが、広告主が求める効果はCPAに限らず流入ユーザーの継続率やROAS2であったりもします。後者の方がサンプルサイズが小さいため、制約の難易度はより高くなります。

まとめ

今回は地味な内容となりましたが、プロダクション未投入の新奇性のある内容も出せていければと思っています。 「広告とAI」特集の他の記事だと、理研AIP 前原氏による「ディスプレイ広告に対するリアルタイム入札」はRTBの入札戦略を離散最適化で解いているのが面白かったです。RTBの入札戦略を俯瞰している日本語の文章は他に見た事がないので、その点でも貴重だと感じました。

AI書庫へのリンク


  1. コンバージョン獲得1件あたりのコスト

  2. 広告経由で流入したユーザーによる売上を広告費用で割った物

builderscon tokyo 2017 で #ajitofm の公開収録をしました!

こんにちはこんにちは!株式会社 fluct で Web 広告配信のお手伝いをしている @jewel_x12 です!

皆さんは builderscon tokyo 2017 へ参加されましたでしょうか!?様々なセッションがあり、The・ギークのお祭りでしたね!楽しかった!

VOYAGE GROUP はランチセッションスポンサーとして、1日目のランチセッションに ajitofm 公開収録をさせていただきました。

ajitofm って?

ajito.fm

ajitofm とは VOYAGE GROUP の社内バー AJITO での語らいを収録したポッドキャストです(実は AJITO で収録していないという爆弾発言が公開収録でありました)。 すずけんさん(@suzu_v) を中心として、精力的に更新されているので興味がある方は聞いてみてください!VOYAGE GROUP のエンジニア事情や面白い話が色々聞けます。

ランチセッションで公開収録をすることになった経緯

せっかくランチセッションの枠をいただいたので、VOYAGE GROUP をより知っていただくために何かコンテンツがないかと考えていました。下記のようにぼやいていたところ

f:id:jewel12:20170807192400p:plain

すずけんさんがやりますよーと言ってくださったのでその方向でよしなにやることになりました。YAPC (Yet Another Perl Conference)発なのかどうかは要出典。

最初はトークセッションをするだけのつもりだったのですが、社内PAさん(@brtriver)の存在もあり収録にもチャレンジしてみることに。 会場にマイク 4 本とミキサー、ケーブル類を持ち込み、前セッションとの入れ替わり時にセッティングという流れでした。こういう入れ替わり時にセッティングするというような、余裕のないデプロイは大体失敗すると思っているのですが、PAさんの腕が確かなのかうまくいってよかったです!どきどきでした。

音響周りをカスタマイズしたり、机や椅子を用意していただくなどいろいろなワガママに付き合っていただいたスタッフの皆様には本当に感謝しております。

当日の様子

#builderscon ランチセッション始まりました! #ajitofm

builderscon.ioさん(@builderscon.io)がシェアした投稿 -

収録に参加したすずけんさん・ajiyoshi さん・トミールさん・yowcow さん

f:id:jewel12:20170808114541j:plain

無理やりトークパネルを埋めました。

f:id:jewel12:20170808114327j:plain

PAさん(@brtriver)の様子。手つきが素早いですね。私は主にスポンサー業としてスタッフさんとやり取りをしたりチラシを作ったりだったので当日はたいしてやることが無く、イベントホールをブラブラしてました。朝食が美味しかったですね!

30 分あっという間でしたが Twitter などで様々な反応をいただいたので、やってよかったです!

twitter.com

スタッフさんや会場の柔軟さ、腕のいいPAがいるなど、いくつかの条件が重ならないと実現は難しいかもしれないですが、テック系ラジオをされている方はこのような登壇方法もいかかでしょうか!?

最後に、スタッフの皆様、登壇された皆様、とても楽しいイベントを提供してくださりありがとうございました!

今回収録した内容は ↓ から聞けます!公開収録で ajitofm に興味を持ってくださった方は他の回も聞いてみてください!

ajito.fm

大量データの転送にEmbulkを使ってみたら本当に楽だった

はじめまして。Zucks Affiliateでエンジニアをしている宗岡です。 今回は、リアルタイム性は求めないけど、簡単に大量のデータをどこか別の場所に転送したい。 という要望に答えてくれるEmbulkを紹介したいと思います。

実際に導入に至ったきっかけや、運用上よくある課題なども触れていきたいと思います。 同じ境遇の人が「簡単そうだしEmbulk使ってみようかな」となっていただければ幸いです。

目次

背景

  • 私の所属する事業部でも、BigQueryを使って色々と解析する機会が増えてきました
    • (広告効果向上のためにさまざまなデータを取り扱い、分析等を行っています)
  • ところが、BigQueryに乗っているデータは今のところアクセスログやアプリケーションログのみ
  • これだとデータとして足りず、DBに乗ってるコンバージョンしたユーザのデータもBigQueryに上げられれば、解析がより楽になりそうだ。というのがきっかけでした。

Embulk以外にも出てきた案

  • re:dashを導入し、「Query Results」を使って、BigQueryとDBのデータをJOINして解析
    • これなら特に努力する必要なく、re:dashの導入と、データソースの追加だけで完結です
    • ただ、残念ながら「Query Results」機能はまだオープンソースとしては公開されていないという悲しい現実がありました
    • Pythonをデータソースに追加することで、無理やり別々のデータソースから取得した結果をマージする方法もありますが、中々辛みがあり、なるべく避けたかったです
  • MySQLのデータをBigQueryに上げるという選択肢が挙がり、(弊社的には)割りと一般的な「MySQL > S3 > GCS > BigQuery」という方法もありましたが、下記のような理由から今回はEmbulkを採用してみました
    • 定期的にバッチとしてデータを取り込み続けたい
    • 今後も同じ事をしていくテーブルが増えそう
    • 社内の他事業部でも導入実績があった

などの理由で、今回はEmbulkを使ってみました

実際のEmbulkの導入と使い方

見ていただければわかると思いますが、非常に簡単です。

1. Embulkのインストールとセットアップ

具体的なセットアップ周りでは、embulkの公式をご参照下さい。

インストールはとても簡単です。

$ curl --create-dirs -o bin/embulk -L "http://dl.embulk.org/embulk-latest.jar
$ chmod +x bin/embulk

これだけ完了です。 もしグローバルでEmbulkを使いたければ、パスを通してあげればOKです。

2. 必要なプラグインのインストール

今回はMySQLからinputして、GoogleのBigQueryにoutputしたかったので、 次の2つのプラグインをインストールします。

  • embulk-input-mysql
  • embulk-output-bigquery

まずはGemfileを用意してあげます。

# For Embulk
source 'https://rubygems.org'

# input mysql plugin
gem 'embulk-input-mysql'

# ouput bq plugin
gem 'embulk-output-bigquery'

そして、embulk bundle

$ ./bin/embulk bundle --path=bundle
2017-07-18 02:15:05.887 +0000: Embulk v0.8.27
Fetching gem metadata from https://rubygems.org/...........
Fetching version metadata from https://rubygems.org/..
Resolving dependencies......
Installing public_suffix 2.0.5
Installing addressable 2.5.1
Installing declarative 0.0.9
Installing declarative-option 0.1.0
Installing embulk-input-mysql 0.8.4
Installing multipart-post 2.0.0
Installing faraday 0.12.1
Installing jwt 1.5.6
Installing little-plugger 1.1.4
Installing multi_json 1.12.1
Installing logging 2.2.2
Installing memoist 0.16.0
Installing os 0.9.6
Installing signet 0.7.3
Installing googleauth 0.5.1
Installing httpclient 2.8.3
Installing mime-types-data 3.2016.0521
Installing mime-types 3.1
Installing uber 0.1.0
Installing representable 3.0.4
Installing retriable 3.0.2
Installing google-api-client 0.13.1
Installing thread_safe 0.3.6
Installing tzinfo 1.2.3
Installing time_with_zone 0.3.1
Installing embulk-output-bigquery 0.4.5
Using bundler 1.10.6
Bundle complete! 2 Gemfile dependencies, 27 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.

これで準備はOKです。

3. 設定ファイルを書く

次はEmbulkの設定ファイルを書いていきます。 設定ファイルは基本的にyaml形式で書きます。

Embulkでは、Liquidというテンプレートエンジンを搭載(v0.7.7以降)しているので、この機能を使いたい場合は、 ファイル名をxxxx.yml.liquidのようにします。

また、今回の例ではDBの接続情報は環境変数(.envファイル)を使ってみる事にしましたが、 実務ではAWSのCodeCommitで管理しており、大分便利ですのでオススメです。

実務でcodecommitを使った例

下記のようにすることで、.envを使うよりもより簡単にクレデンシャルを扱う事が可能になります

  • codecommitのリポジトリを特定ディレクトリ(今回は/credential)にclone
  • Makefileで必要な環境変数はexportし、設定ファイルで読み込めるようにする
  • (codecommitでクレデンシャル情報を管理出来るので、リリース時の.envファイルの扱い等は考える必要なくなります)
# rdsのクレデンシャル情報
include /credentials/rds/ro_connection_info.txt

export MYSQL_HOST      := $(host)
export MYSQL_USER      := $(user)
export MYSQL_PWD       := $(password)

# gcpのクレデンシャル情報
export CREDENTIA_JSON  := /credentials/gcp/credential.json

設定ファイルの書き方

in:
  type: mysql
  host: {{ env.MYSQL_HOST_NAME }}
  user: {{ env.MYSQL_USER }}
  password: {{ env.MYSQL_PASSWORD }}
  database: embulk_test
  table: user
out:
  type: bigquery
  auth_method: json_key
  json_keyfile: ./credentials/bq_project.json
  path_prefix: tmp
  file_ext: .csv.gz
  source_format: CSV
  project: test-bigquery-project-166901
  dataset: test_for_embulk
  auto_create_table: true
  table: users
  formatter: {type: csv, charset: UTF-8, delimiter: ',', header_line: false}
  encoders:
  - {type: gzip}

BQ側のプロジェクトや、データセットは今回の為にテスト的に作ったものを指定してます。

また、input/outputのそれぞれの設定は各プラグインのページを見ると詳細が載ってますので、イロイロ試してみると面白いです。

参考: embulk-input-mysql embulk-output-bigquery

4. まずはpreviewで問題なさそうか確認

previewは、dry-run機能として使えます。 実際にEmbulkを実行する前に、どんなデータが転送されそうかを確認出来ます。

$ ./bin/embulk preview -b . user.yml.liquid
2017-07-19 23:27:13.766 +0000: Embulk v0.8.27

... 中略 ...

2017-07-19 23:27:15.841 +0000 [INFO] (0001:preview): SQL: SELECT id,name,created_at FROM `user`
2017-07-19 23:27:15.854 +0000 [INFO] (0001:preview): > 0.01 seconds
2017-07-19 23:27:15.922 +0000 [INFO] (0001:preview): Fetched 500 rows.
+---------+------------------------+-------------------------+
| id:long |            name:string |    created_at:timestamp |
+---------+------------------------+-------------------------+
|       1 |            Valerie Cox | 2017-07-11 09:31:15 UTC |
|       2 |         Jennifer Perry | 2017-07-15 10:02:30 UTC |
|       3 |           James Garcia | 2017-07-01 05:56:45 UTC |
|       4 |         Haley Calderon | 2017-07-28 21:08:42 UTC |
|       5 |     Cindy Williams DDS | 2017-07-06 17:26:38 UTC |
|       6 |       Christina Mathis | 2017-07-27 22:45:17 UTC |
|       7 |         Timothy Barker | 2017-07-28 12:12:40 UTC |
|       8 |             Thomas Lee | 2017-07-01 01:29:39 UTC |
|       9 |            Jamie Smith | 2017-07-09 03:47:16 UTC |
|      10 |         Wesley Santana | 2017-07-23 18:42:06 UTC |

~長いので省略~

5. 問題なさそうなのでrunして実行

$ ./bin/embulk run -b . user.yml.liquid
2017-07-19 23:27:53.166 +0000: Embulk v0.8.27

... 中略 ...

2017-07-19 23:28:12.897 +0000 [INFO] (0016:task-0000): Fetched 1,000 rows.
2017-07-19 23:28:14.252 +0000 [INFO] (0016:task-0000): Fetched 2,000 rows.
2017-07-19 23:28:15.966 +0000 [INFO] (0016:task-0000): Fetched 4,000 rows.
2017-07-19 23:28:18.242 +0000 [INFO] (0016:task-0000): Fetched 8,000 rows.
2017-07-19 23:28:19.496 +0000 [INFO] (0001:transaction): {done:  1 / 1, running: 0}

... 中略 ...

2017-07-19 23:28:55.069 +0000 [INFO] (main): Committed.
2017-07-19 23:28:55.069 +0000 [INFO] (main): Next config diff: {"in":{},"out":{}}

終わりました。 今回対象件数は1万件程でしたが、 約1分 で完了です。 とっても楽ちんですね。

BigQuery上でもこのようにちゃんと1万件分のデータがアップロードされてるのが確認出来ます。

スクリーンショット 2017-07-20 08.30.37.png

Embulkの運用上、よくぶつかる課題

Embulkを使うと簡単に転送出来る事が分かった所で、運用上ハマりどこになりそうな所を上げておきます。 ハマったとしても、大体やりたい事はプラグインを見れば解決出来る設定方法が書いてあるので参考にしてみると良いと思います。

今回であれば下記2つのプラグインを参照してます。

embulk-input-mysql embulk-output-bigquery

1. 重複に気付け無い

Embulkを使う機会は、大量のデータを定期的にインサートしたい場合だと思いますので、 cron等で動かす事になるかと思います。

その場合、cronでも動かしてるものを、誤って手動でインサートした場合、そのまま重複した状態でデータがインサートされてしまいます。

対応 - prevent_duplicate_insertをtrueに設定する

これが困ってしまうという場合は、BigQueryの場合は、prevent_duplicate_insertをtrueにしておく事で、重複を弾けます。

実際に、この設定を追加してやってみましょう。

$ git diff user.yml.liquid
diff --git a/user.yml.liquid b/user.yml.liquid
index 37a7074..e1980ff 100644
--- a/user.yml.liquid
+++ b/user.yml.liquid
@@ -16,6 +16,7 @@ out:
   project: test-bigquery-project-166901
   dataset: test_for_embulk
   auto_create_table: true
+  prevent_duplicate_insert: true
   table: user
   formatter: {type: csv, charset: UTF-8, delimiter: ',', header_line: false}
   encoders:

prevent_duplicate_insertをtrueにしてみます。

$ ./bin/embulk run -b . user.yml.liquid
2017-07-19 23:44:22.042 +0000: Embulk v0.8.27
...中略 ...
Error: org.jruby.exceptions.RaiseException: (Error) failed to load tmp.30148.5930.csv.gz to test-bigquery-project-166901:test_for_embulk.LOAD_TEMP_0b5d4c5e_e440_407e_8a2d_92caeb232bd5_user, response:{:status_code=>409, :message=>"duplicate: Already Exists: Job test-bigquery-project-166901:embulk_load_job_dcec11ac837806a8524fcecf0b5b12a5", :error_class=>Google::Apis::ClientError}

Already Exists: Job test-bigquery-project-166901:embulk_load_job_dcec11ac837806a8524fcecf0b5b12a5"という事でちゃんと重複データを入れようとするとエラーで落としてくれました。

2. 転送元のスキーマを変更するとエラーで落ちる

転送元をデータベースにしている場合などは、スキーマの変更は珍しくないと思います。 この場合、デフォルト設定のままだとカラム追加時にEmbulkはエラーで落ちます。

理由は2つあります。

  • 設定ファイルのinputの設定に、テーブル名だけを指定している事
    • テーブル名だけの指定だと、全てのカラムを転送しようとするので、今回追加するageカラムも追加しようとします
  • BigQuery上に作ったスキーマと異なるデータを転送しようとしている事

今対象としてるサンプルで使ったテーブルはuserというシンプルなテーブルです。

mysql> desc user;
+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| name       | varchar(32)      | NO   |     | NULL    |                |
| created_at | timestamp        | NO   |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

こちらのテーブルに、ageカラムを追加してみます。

mysql> ALTER TABLE user ADD column age INT UNSIGNED NOT NULL AFTER name
    -> ;
Query OK, 0 rows affected (0.19 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> desc user;
+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| id         | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| name       | varchar(32)      | NO   |     | NULL    |                |
| age        | int(10) unsigned | NO   |     | NULL    |                |
| created_at | timestamp        | NO   |     | NULL    |                |
+------------+------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

この状態でEmbulkを走らせてみます。

$ ./bin/embulk run -b . user.yml.liquid
2017-07-20 00:01:08.421 +0000: Embulk v0.8.27

... 中略 ...

Error: org.jruby.exceptions.RaiseException: (Error) failed during waiting a Copy job, get_job(test-bigquery-project-166901, embulk_copy_job_dedd3a2b-d82d-4bc8-af97-e68aff6540fa), errors:[{:reason=>"invalid", :message=>"Provided Schema does not match Table test-bigquery-project-166901:test_for_embulk.user. Cannot add fields (field: age)."}]

BigQuery上のテーブルスキーマと異なるデータ(今回だと新しく追加したageカラム)も転送しようとして落ちてしまいます。

対策 - 転送するカラムを指定しておく

これを防ぐには、予め転送するカラムを指定しておく事で解決できます。 詳しくは、mySQL input pluginの設定に記載してありますが、今回の場合は、テーブル名だけでなく、カラムも指定してあげるようにしておけばOKです。

$ git diff user.yml.liquid
diff --git a/user.yml.liquid b/user.yml.liquid
index d6a52e9..37a7074 100644
--- a/user.yml.liquid
+++ b/user.yml.liquid
@@ -5,6 +5,7 @@ in:
   password: {{ env.MYSQL_PASSWORD }}
   database: embulk_test
   table: user
+  select: id,name,created_at
 out:
   type: bigquery
   auth_method: json_key

この状態でもう一度Embulkを実行してみましょう。

$ ./bin/embulk run -b . user.yml.liquid
2017-07-20 23:07:31.758 +0000: Embulk v0.8.27

...中略...

2017-07-20 23:07:51.614 +0000 [INFO] (0016:task-0000): Fetched 1,000 rows.
2017-07-20 23:07:52.890 +0000 [INFO] (0016:task-0000): Fetched 2,000 rows.
2017-07-20 23:07:54.619 +0000 [INFO] (0016:task-0000): Fetched 4,000 rows.
2017-07-20 23:07:56.872 +0000 [INFO] (0016:task-0000): Fetched 8,000 rows.
2017-07-20 23:07:58.057 +0000 [INFO] (0001:transaction): {done:  1 / 1, running: 0}

...中略...

2017-07-20 23:08:33.688 +0000 [INFO] (0001:transaction): embulk-output-bigquery: 
Copy job response... job_id:[embulk_copy_job_38ef8d00-a904-456d-824f-3cd36698c16f] response.statistics:{:creation_time=>1500592103079, :start_time=>1500592103219, :end_time=>1500592105762}
2017-07-20 23:08:33.688 +0000 [INFO] (0001:transaction): embulk-output-bigquery: Delete table... test-bigquery-project-166901:test_for_embulk.LOAD_TEMP_282263eb_ec81_4868_9115_bee413408092_user
2017-07-20 23:08:34.322 +0000 [INFO] (0001:transaction): embulk-output-bigquery: delete tmp.32592.5930.csv.gz
2017-07-20 23:08:34.333 +0000 [INFO] (main): Committed.
2017-07-20 23:08:34.333 +0000 [INFO] (main): Next config diff: {"in":{},"out":{}}

問題なく転送出来ている事が分かります。 このように、予めスキーマが変わりそうな場合は、 selectで対象カラムを指定し、想定内のカラムのみが転送される状態にしておくのが望ましいと思います。

また、SQLの取得には下記の様に2種類ありますので、直接Queryを書いてしまうというのもありです。

  • query にクエリを直接書く
  • query は書かずに、table/select / where / order_by を記載

3. 設定ファイルが増えてくる(対象テーブルが増えてくる)と重複が多くなる

設定ファイルの中でも、実際変わるのはテーブル名・カラム名・Project名とかだけだと思います。 その他の接続情報などはほとんど同じです。 これらを、転送テーブルを増やす度に、都度設定ファイルに書くのは面倒です。

対応 - liquidテンプレートのinclude機能を使う

実務では、liquidテンプレートの機能を使い、下記のような構成で運用しています。

- templates
    - common
        - _conf_mysql_include.yml.liquid
        - _conf_bigquery_include.yml.liquid
    - user.yml.liquid

で、実際にuser.yml.liquidは、下記のように、 commonに書いたmysqlとBigQueryの設定ファイルをincludeして使っています。 (includeされるファイルは、ファイル名の先頭に_(アンダースコア)を付けてあげます)

in:
  {% include 'common/conf_mysql_include' %}
  table: user
out:
  {% include 'common/conf_bigquery_include' %}
  table: user

これでテーブル毎に異なる設定をする場合も、 共通項はcommoに切り出せているので重複を防ぐ事が可能となります。

4. エラーメッセージが分かりづらいので整理しておく

このように、非常に簡単に、あらゆる大規模なデータソースを、短時間で転送出来るという便利な代物なEmbulkですが、若干エラーメッセージが分かり辛いので、よく遭遇するエラーメッセージを紹介しておきます。

4-1. Setting null to a task field is not allowed

これは、特定のフィールドにnull値は入れられないよ。

という意味ですが、よくあるのは、.envを使って環境変数で設定ファイルを書いている場合、 .envに書いた環境変数をexportしてない可能性が高いです。 (ここで再現した例も、環境変数のexportをしてないのが原因です)

$ ./bin/embulk run -b . user.yml.liquid
2017-07-20 23:19:19.560 +0000: Embulk v0.8.27
2017-07-20 23:19:23.055 +0000 [INFO] (0001:transaction): Loaded plugin embulk-input-mysql (0.8.4)

... 中略 ...

Error: org.embulk.config.ConfigException: com.fasterxml.jackson.databind.JsonMappingException: Setting null to a task field is not allowed. Use Optional<T> (com.google.common.base.Optional) to represent null.

4-2. Can not deserialize instance of java.lang.String out of START_OBJECT

これは、liquidテンプレートを使っているのに、ファイル名にliquidがついてない時にでます。

今回あえて、これまでのuser.yml.liquiduser.yamlに変えてみます。

$ ./bin/embulk run -b . user.yml
2017-07-20 23:13:47.582 +0000: Embulk v0.8.27
2017-07-20 23:13:50.058 +0000 [INFO] (0001:transaction): Loaded plugin embulk-input-mysql (0.8.4)
2017-07-20 23:14:01.579 +0000 [INFO] (0001:transaction): Loaded plugin embulk-output-bigquery (0.4.5)
org.embulk.exec.PartialExecutionException: org.embulk.config.ConfigException: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
 at [Source: N/A; line: -1, column: -1]

... 中略 ...
    
    Error: org.embulk.config.ConfigException: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
 at [Source: N/A; line: -1, column: -1]

4-3 Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token

これは、配列で指定する必要がある所を、文字列で指定している為に出るエラーです。 例えば、incremental_columnsは配列で指定する必要がありますが、 わざと配列指定じゃなくしてみます。 (正しくは、incremental_columns: [id]とする必要があります)

$ git diff user.yml.liquid
diff --git a/user.yml.liquid b/user.yml.liquid
index 37a7074..a30d64d 100644
--- a/user.yml.liquid
+++ b/user.yml.liquid
@@ -6,6 +6,9 @@ in:
   database: embulk_test
   table: user
   select: id,name,created_at
+  incremental: true
+  incremental_columns: id
 out:
   type: bigquery
   auth_method: json_key

これで実行してみます。

$ ./bin/embulk run -b . user.yml.liquid
2017-07-20 23:39:54.033 +0000: Embulk v0.8.27
2017-07-20 23:39:57.515 +0000 [INFO] (0001:transaction): Loaded plugin embulk-input-mysql (0.8.4)
2017-07-20 23:40:08.258 +0000 [INFO] (0001:transaction): Loaded plugin embulk-output-bigquery (0.4.5)
... 中略 ...
Error: org.embulk.config.ConfigException: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token
 at [Source: N/A; line: -1, column: -1]

まとめ

今回は、大量データを簡単に別のDWHに転送する方法としてEmbulkを紹介しました。 また、実際に運用上に困ってくる事も話してみました。

冒頭に書いた様に、リアルタイム性は求めないものの、簡単にデータ転送したいな。 という欲求をお持ちの方は、一度試してみてはいかがでしょうか。 御覧頂いた通り、非常に簡単に始められますので非常にオススメです。

技術サポートするときに気をつけている5つのこと

株式会社fluctのエンジニア長谷川です。

弊社はフルスクラッチで開発,提供をしているfluct SSPというプロダクト以外にも、Googleの認定パートナーとしてGoogleのプロダクトを利用したメディアのマネタイズのお手伝いも行っています。主なプロダクトはGoogle AdSenseDoubleClick AdExchange, DoubleClick for Publishersです。

これらのプロダクトは非常に高機能な反面、効果的に活用するにはネット広告一般やプロダクト自体に関する高度な知識が不可欠です。そこでfluctがプロダクト運用のお手伝いをしています(詳しくはこちら)。

私のfluctでのミッションとして、Googleの商材の技術的なサポートというものがあります。具体的には…

  • コンサルティングサポート
    • お客様あるいは弊社コンサルのアイディアのフィジビリティ調査
    • アドが出ないなどのトラブル対応
  • 運用サポート
    • レポーティングツール開発
    • 広告運用のシステム化検討及び開発

コンサルティングサポートはお客様のアドテクに関する知識レベルややりたいことは多種多様であるため、相手の意図を正しく理解し、かつこちらの意図をいかに正しく伝えるかが重要となってきます。

このミッションに携わってほぼ1年近く経ち、ちょうどブログ記事を書く機会を得られたのでせっかくなのでこの仕事をする上で心がけていたことをメソッド集という体で書いてみたいと思います。

目次

メソッド1: 相手の話を自分の言葉で再翻訳する

以前同僚に「あなたは相手の意図を確認する習慣があって、それがとてもよい」と指摘されたことがあります。あまり意識せずにやってたのですが、言われてから意識的にやるようになりました。

みんなではじめるデザイン批評」という書籍ではこれを「アクティブリスニング」と紹介されています。会話例がとても良かったので引用します。

アクティブ・リスニングには、受けたフィードバックに対してことばを返すときに、相手の話を別のことばに言い換えて繰り返すというやり方がある。そうすればフィードバックを与える側は、私達の理解と自分が伝えたいポイントとが一致しているかを確かめることができる。以下に例を示す。

製品のオーナー「スクリーン上のニュースフィードの配置が気になります。とても目立ちますが、実際に顧客がそれほどひんぱんに使うとは思いません。」

デザイナー「わかりました。私の理解が正しければ、心配なのは、あまり使われないかもしれないニュースフィードが注目を引きすぎて、ユーザーの注意が他のもっと重要な要素からそれてしまうのではないか、ということですね?よろしいでしょうか?」

製品のオーナー「はい、顧客の使用頻度の高いものに、より高い優先順位をつけられるよう、別の処理の仕方を検討するべきです。」

理解が正しくない場合は、さらに明確にするためにフィードバックを掘り下げる質問をすればいい。

「ニュースフィードが目立つという心配について、もう少し詳しく説明していただけますか?」

あるいは、

「わかりました。その点をもう少し考えてみましょう。このまま目立たせておいたとしたら、どうなるでしょうか?」

(「みんなではじめるデザイン批評」 158ページより。一部編集)

アクティブリスニングは相手に対して「私はあなたの話を聞いています」という姿勢を見せると同時に話者の中でも理解できたこと、わからなかったことを整理するのに役立ちます。これは次節で紹介する「目に見える形」とセットでやっています。

メソッド2: 目に見える形で共有しながら話をする

人間、あれとこれを話そうと考えていてもいざ会話を始めると必ず漏れが発生します。それを防ぐには喋っている内容を即時文字にして目に見えるようにすることです。

- 起こっていること
    - XXで広告タグを追加設置したが広告が出ない
- いつから
    - 昨日の夜ぐらいから
- 誰が観測したか
    - メディアの閲覧者からメールで連絡があり、我々に問い合わせが来た
    - 現在もタグが貼られているので我々でも確認は可能

こんな風に、聞き手がテキストを起こすのと同時に、情報が足りない箇所や深掘りしたいところを都度質問していき内容を増強していきます。あとはこれをそのままissueトラッカーに貼り付ければトラッカーベースでやり取りしていけます。

この方法は特にトラブルが発生した時に以下のような効果があると思います。

  • 一緒にトラブルを解決しよう、という姿勢により報告者を落ち着かせられる
  • 余裕がない状況でも思考を整理できる

メソッド3: 思考をまとめるための自分のベストツールを揃える

自分の中で思考を目に見える形で整理する方法を確立しておきます。日頃から可視化するようにしておくと、前述の相手との状況の可視化をするためのトレーニングにもなります。これはデジタルでもアナログでも構いません。

なお、私はevernoteなどのメモツールを挑戦してみましたがどうしてもしっくり来なくてノート + ペン というオールドスタイルで落ち着いています。

弊社でよく目にするツールはこのノートとホワイトボードが一体化したノートです。ちょっとしたお話を図にして共有するのに使っているようです。

私はアピカの方眼紙のノートがお気に入りです。ノートに1500円…と驚く方もいますが、このノートは紙質がとても良くて、メモを書いてて気持ちが良いのです。

f:id:hasegawas:20170713113654j:plain

このようにキーボードの前に置いて、考え事する時はノートにひたすら書いて、コードを書いてるときはディスプレイを見てっていう風に仕事をしています。(実際にメモが書いてるページは業務内容そのままだったので真っ白のページにしています…)

メソッド4: お客様とのミーティングに同行し積極的に発言する

自社プロダクトをサーブしている会社だとエンジニアはなかなか表に出てこない存在になりがちですが、案件の難易度によっては自らお客様との打ち合わせに同行することを願い出ます。これは以下のような効果があります。

  • 関係者が多い場合に伝言ゲームにならないようにすることができる
  • 自分の中の経験を増やす効果がある
    • 会社によってまるで文化や共通言語が違うことを体感し、コミュニケーションの仕方の手札を増やすきっかけとなる
    • 勉強会だと文化が似ている会社の人が集まりがち

メソッド5: 常に機嫌よくいる

最後は精神論かよ…と言われてしまいそうですが、これが一番大事だと思います。トラブルが発生した!!って言う時もその状況を面白がれるぐらいのメンタルが欲しいです(ライトな雰囲気は報告してきた相手の気持ちも落ち着かせる効果があると私は思っています。)

  • 優れた,面白いアイディアは機嫌が良い人に集まってくる
    • 思いついたことを気軽にSlackに投入できてそれ面白いねって反応が出るぐらいだとよい
  • 機嫌が悪いと「話しかけないでおこう」という雰囲気が出来てしまい、聞けば即時解決できたトラブルが後回しにされてしまう
    • その結果また機嫌が悪くなるという悪循環

また、話を聞いた時にそれ前も同じことあったよね、と突っ返さないのも大事です。話しかけにくい、機嫌が悪いって思われて損です。トラブルが終わって落ち着いたときの振り返りにしれっと話してみるのがよいと思います。

まとめ

以上、1つ1つは小さなことかもしれませんがコミュニケーションを円滑にするためのあなたのヒントになればと思います。

この記事を読んで弊社に興味を持ったあなた、ポジションを用意してお待ちしております!もちろん、その他のポジションも用意しておりますので興味があったら是非ご応募を!(露骨な宣伝)

チーム状態をスムーズに変えて障害対応のコストと精神的負荷を抑える

こんにちは。 @at_grandpa です。普段はバッチを書いたりメンテナンスをしています。

今回は、先日起きた障害対応の時、チームの状態をスムーズに変えることで対応コストと精神的負荷を抑えられた、ということを書きます。

 

目次

障害発生

先日の朝に「レポートの数値がおかしい」という連絡がきて確認したところ、とあることが原因で、バッチの自動実行が約半日行われていないことがわかりました。

 

f:id:at_grandpa:20170713114354p:plain:w400

 

普段の対応

普段の対応は以下のような形です。

  1. エラー発生をSlackの全体チャンネルで報告
  2. バッチ系チャンネルにて、考えや現状を垂れ流す
  3. わからないことがあれば有識者にメンションを飛ばす
  4. 実際に叩いたコマンドをどんどんSlackに書き込む
  5. 対応完了を全体チャンネルで報告
  6. チケットにまとめる

小規模なエラーの場合は上記の対応で事足ります。この方法のメリットは以下です。

  • 対応は一人ででき、他のメンバーのコストが発生しない
  • Slackに垂れ流すことでプチレビューの役割を果たす
    • 精神的負荷が多少軽減される
  • Slackにコマンドの履歴が残る

しかし、しばらく運用していると、デメリットが大きくなってきました。

  • 「一番知っている人はat_grandpa」という理由からか、指摘されることが少なくなった
  • 上記が理由で「孤独感」が強くなった
  • 孤独感から「この対応で大丈夫か?」という気持ちが生まれ、精神的負荷が増える
  • 対応方法が伝授されない
    • 記録はあるが、他のメンバーが同じ対応をできるかと問われるとなかなか難しそう
    • 実践で行わないと自信を持って対応することは難しい

信頼されることは嬉しいですが、この信頼はチームを腐敗させます。今までは「対応すること」を優先で行ってきましたが、実際はチームの動きを鈍らせてしまう原因になっているのだと気付きました。この点は反省すべき点です。(自分の共有方法やドキュメント記述方法にも問題があります。それはまた別の問題として認識します。)

しかし今回、新しいアプローチで障害対応を行ったところ、上記の懸念点も解決し、かつスムーズに対応が行えたので、ブログに記録したいと考えました。

 

今回の対応

今回の対応の流れは以下です。

  1. 原因究明と現状把握
  2. 関係者が会議室に集まる
  3. 対応用Slackチャンネルを開設
  4. ペアワークで実対応
  5. 落ち着いたら自席&Slackコミュニケーションへ移る
  6. 対応完了の確認と報告・チケットまとめ

一つずつ見ていきます。

原因究明と現状把握

あるバッチがエラーを吐くことはたまにあるのですが、今回は約半日動いていません。普段と異なる時間帯に数十のバッチを手動で叩かなければなりません。普段と規模が違います。

関係者が会議室に集まる

流石にひとりだと荷が重いと判断し、バッチ周りに触れたことのある新卒2年目の @saxsir256(以下@saxsir)と新卒1年目の@__himu__(以下@himu)にリカバリタスクをお願いしました。と、そこで以下の提案がきます。

f:id:at_grandpa:20170713085202p:plain

これはなるほどと思いました。関係者がガッと会議室に集まることで、以下のメリットがあります。

  • メイン対応者が明確になる
    • メインの対応者が明確でないと、チーム全体が「何かしないといけないのかな。。。」と不安定な状態になってしまいます。これを阻止するためにも「今回はこの3人で対応します」と名言できたのは良かったです
  • 初動が重要な場面で対面コミュニケーションできる
    • Slackは便利ですが、対面コミュニケーションの速度には敵いません
    • 初動の場面でスピーディに共有・方針決定できたのは大きかったです
  • 精神的負荷の軽減
    • 障害対応の初動は何かと不安が大きいですが、対面でのやり取りは精神的負荷をかなり抑えます

実際、この判断はとても良く、自分も把握しきれていないバッチを@himuに解読してもらいつつ、@saxsirと共にホワイトボードを用いて対応スケジュールを組み立てました。わからないことはすぐに共有、わかる人とペアワーク、という軽快な動きが可能でした。

対応用Slackチャンネルを開設

対応用Slackチャンネルの開設は、社内でも一時期話題になっていましたが、実際にメインの運用にはなっていませんでした。自分ひとりの対応の時に、試しに開設したりしていましたが、メモ程度の役割にしかなっていませんでした。しかし今回は3人がメイン対応者です。対面コミュニケーションのスピードは良いですが、記録を残すことも重要なので、とにかく、

  • 実行したコマンドとその結果
  • 現在何をしているかを投稿(他の外部メンバー向け)

を書いていきました。「このチャンネルで対応実況しています」ということを全体チャンネルで発言することで、続々と覗きにくる方が増え、メモ&外部共有が両立できたのは大きかったです。

ペアワークで実対応

先程も書きましたが、会議室に集まることでペアワークが可能となり、精神的負荷を抑えることができました。かつ、対応のちょっとしたノウハウなども詳細に伝えることができたり、実際にコマンドを叩く経験ができるため、今後の対応への自信につながるというメリットもあります。実際、自分でも詳細を知らないバッチを@saxsirと@himuが解読してくれ、それをSlackに残してくれているのでとても助かりました。

落ち着いたら自席&Slackコミュニケーションへ移る

バッチを順に叩いていきDBの状態を確認、次のバッチへ・・・という作業を続けていくと、「依存関係はもう無いし、後は順に叩いていくだけ」という場面になりました。この段階で会議室に集まっているメリットはもうあまりないと判断し、「自席に戻って他のタスクを行いつつ、Slackにて共有」という形に移りました。このようにチーム状態を変えたおかげで、午後からは他タスクに移ることができましたし、ちゃんとSlack上での確認も行え、無事対応を終えることができました。

対応完了の確認と報告・チケットまとめ

対応完了を全体チャンネルに報告し、チケットにまとめます。チケットへのまとめは、Slackの対応用チャンネルの重要部分のリンクを貼るだけです。まとめ直すのは結構コストが高いので、実対応が記録できる対応用チャンネルは便利だなと思いました。

 

まとめ

今回の対応は、以下の点がよかったと思います。

  • メイン対応者を明確にすることで、他メンバーのコストを抑えられた
  • 対応用Slackチャンネルのおかげで、記録と共有を両立できた
  • 障害対応の初動で対面コミュニケーションを取れた
    • 精神的負荷の一番大きなフェーズで負荷を抑えることができた
    • 方針決定までの速さが今までよりも早かった
    • 対応メンバー全員がバッチ周りに詳しくなった
  • 落ち着いたら自席&Slackコミュニケーションに移った
    • ずっと1日中対応しているのではなく、他のタスクにも移行できた
    • 結果、対応に対する時間を削減できた

個人的に学びがあったのは、対応の状況によって 会議室&対面 → 自席&Slack に移行したことで、対応の総コストと精神的負荷を抑えることができたという点です。

ともあれ、「何かあったら会議室」というのもおかしな話なので、臨機応変に活用していきたいと思います。

今回、「障害対応にはフェーズがある」ということと「新しい障害対応の方法」に気づけたのは非常に価値がありました。この経験を踏まえ、今後に活かしたいと思います。

 


 

VOYAGE GROUP では最近Podcastを始めました。 → Ajitofm

社内バー AJITO での語らいを収録したポッドキャストです。技術談義で盛り上がったり、もしかたら今後、障害対応の裏話なども聞けるかもしれません。

Jupyter Notebook でとりあえず Redash へクエリを投げておけばデータソースはなんでも良い状態にする

こんにちはこんにちは!株式会社 fluct で Web 広告配信のお手伝いをしている @jewel_x12 です!

本記事は Redash が便利という内容です。

Redash とは

redash.io

Redash とは Web ブラウザから様々なデータソースに対するクエリを投げて、結果を可視化する OSS になります。 Redash には便利な機能がいくつか機能があるのですが、数点挙げると

  • 様々なデータソースへの対応
  • クエリの定期実行とアラーティング
    • Slack などへの投稿
  • クエリ結果のキャッシュ
  • Google OAuth などいくつかの認証サービスでユーザー管理ができる

あたりです。

弊社では Redash が広く導入されており、エンジニアなどの職種に関わらず利用者がいます。BigQuery のクエリへ Quota をかけられたり、機微な情報のあるテーブルへアクセス制限できるところなどが誰でも気軽に利用してもらえるところかもしれませんね。

個人的にメチャ推しなポイントは様々なデータソースへの対応です。サポートしているデータソースの一覧を見ていただければ分かるのですが、MySQL や AWS の DB 系サービス、BigQuery や ElasticSearch などにも対応しています。Redash をハブとして、散らばりがちなデータソースへ1箇所からアクセスできるようになるのが便利です。

Jupyter Notebook から Redash を使う

さて、自分は広告ログ周りのデータを見ることがあります。解析結果の可視化やレポーティングは Jupyter Notebook を利用しています。Jupyter Notebook はコードのインタラクティブな実行環境であり、Python などのコードをセルという単位で処理したり可視化したりできます。実行結果はいくつかの方法で export 可能であり、グラフなどを共有するのにも役立ちます。私は Jupyter Notebook をベースとした Google Datalab の Docker コンテナを起動して利用しています。Google Datalab を使用しているのは、はじめ BigQuery だけ使用することを考えており BigQuery アクセス用ライブラリなどが最初から使えるので利用していました。*1

実際は各種 ID などを他のデータソースと JOIN したくなるケースが多く、MySQL などのデータソースも利用したくなりました。Jupyter Notebook から MySQL への接続は可能ですが、接続用パスワードの管理があったり、他のデータソースが増えてくると各クライアントの初期化や使い分けが煩雑になりそうだったので、Redash をハブとしてクエリを投げるようにしてみました。とりえあず Redash へクエリを投げることができればデータソースはなんでも良くなります。弊チーム内ではある程度のユースケースはカバーできそうでした。

Redash API Client

Redash にはクエリを実行したりする API があるので、 Jupyter Notebook からは簡単なクライアントを書いて利用しています。

github.com

Jupyter Notebook ではこのクライアントからの結果を pandas.DataFrame へ変換するラッパーをスタートアップスクリプトに書いています。

from redquery import Client

host = 'https://redash.host.example'
myr = Client(host, api_key, mysql_datasource_id)
bqr = Client(host, api_key, bigquery_datasource_id)

def mquery(q):
    return query(myr, q)

def bquery(q):
    return query(bqr, q)

def query(client, q):
    res = client.query(q)
    return (pd.DataFrame(res.rows), res)

api_key は Redash のユーザー画面に API Key というタブがあるのでそちらを利用しましょう。datasource id が admin ユーザー以外には分かりにくいのですが、てきとうなクライアントを作って client.data_sources() とかやるとデータソースとデータソースIDの対応を取得できます。

f:id:jewel12:20170630131939p:plain

クエリエラーも分かります。

f:id:jewel12:20170630132425p:plain

これで Redash とつながっているデータソースに対してクエリを投げられるようになりました。 といってもまだ MySQL や BigQuery でしか試していないので、他のデータソースだとうまく動作しない可能性があります。

それにしても API Key ひとつで異なるデータソースへクエリが投げられるのは、なかなか便利です!

おまけ

Redash を長く運用していると、過去に設定した定期実行クエリがずっと動いているというようなことがあります。 BigQuery で定額料金ではない場合など、クエリ毎に課金が生じるタイプのクエリは定期的に棚卸ししてあげるとコーヒー代くらいは浮くかもしれません。

scheduled_queries = [ q for q in client.all_queries() if q['schedule'] ]
for q in scheduled_queries:
    print("http://redash.host.example/queries/%d\t%d\t%s\t%s\t%s\t%s" % (q['id'], q['data_source_id'], q['name'], q['schedule'], q['created_at'], q['user']['name']))

*1:ちなみに Datalab のサービスを利用せずコンテナを起動して利用しているのは解析結果を GitHub の nbviewer 機能で共有するためです。解析結果を共有したい相手が Google Datalab を動かすための VM インスタンスを立てられる人ばかりではないのと Jupyter Notebook 自体を触る人はそんなに多くないので、実行環境は各自が用意すれば良いという観点から、Github アカウントを作ってもらい共有するほうが楽だと考えました。Google Cloud Source Repositories にそのような機能があると Google アカウントだけでよくなるので嬉しい……

「子供向けブロックプログラミング学習ツール」進化しました!

エンジニアの tatenosystem です。

今回の記事は「私が個人的に作成しているサービス」の紹介です。

 

前々回の2013年09月ブログ  で

 

ビジネスチャンス キタコレ!!!

 

ということで 子供向けプログラム学習ゲーム「たてのブロックサービスを個人的に作成してみました。

開発理由などは 前々回ブログ を見てください。

 

ブロックを配置することでプログラムを作り、キャラクタを動かしてゲームをクリアするゲームです。

 

その後、 文部科学省の「小学校でのプログラミング教育必修化」などの話題があったり、子供向けプログラム教室のリリースを多数見かけたり、子供向けプログラムがビジネス誌の表紙で取り上げられたり、

 

f:id:vg-k-tateno:20170622124515p:plain

 
ここ数年、子供向けプログラミング市場が盛り上がってきてました。

 

しかし「たてのブロック」は全く流行っていない。。。

 

これはもう、サービス名が悪いんじゃないかな。。。

 

そんなわけでサービス名を改名しました。

 

「たてのブロック」改め「コードゲーム」

 

コードゲーム (CODE GAME) - プログラム学習ゲーム

 f:id:vg-k-tateno:20170622130719p:plain


サービス名だけでなく、内容も進化しました。

 

■ 素晴らしい素材画像を見つけたのでキャラ、背景画像の変更

 enchant.js に添付されていた「クマ」画像から、キャラ、背景画像を変更

 

■ プログラム状態を含めて「URL」を生成

「たてのブロック」では URL にマップ情報を保持してましたが、それに加えて作成したユーザーが作成したプログラムも URL に保持しました。


これで URL を送れば、作成したプログラムを他人に見せることができます。

ぐるぐる回るサンプルプログラム

 ■ WordPress から markdown 自作の CMS へ

 

WordPress でサイトを作成してたのですが、度重なるバージョンアップ対応に心が折れたので、簡易な MarkDown 形式の CMS を作成しました。こんな感じで更新しています。

 

f:id:vg-k-tateno:20170622125016p:plain

 

■ 「城」の追加

f:id:vg-k-tateno:20170622130634p:plain

いままでは全ての「宝箱」を取ればクリアでしたが、もし画面内に「城」がある場合は、すべての「宝箱」を取った後に「城」に行くとクリアになりました。

 

 

サービス紹介がてら下記ゲームをクリアしていきましょう


下の画像クリックでゲームがスタートします。

f:id:vg-k-tateno:20170622131343p:plain

 

「中央にあるブロック」を「ドラッグ&ドロップ」で「左のプログラムエリア」に配置します。

f:id:vg-k-tateno:20170622131431p:plain

 

配置したブロックは プログラムエリア外に「ドラッグ&ドロップ」すると消えます。


配置したブロックを「クリック」すると向きや状態が変わります。

f:id:vg-k-tateno:20170622131459g:plain

 

背景が「黄色」、「白」のブロックは向きや状態をクリックで変えることができます。

 

実行ボタン でプログラム動作し「主人公」が動きます。

f:id:vg-k-tateno:20170622131523j:plain

 


「プログラムの作り方」はこのページに書かれています。


使用できる「プログラムブロック」はこのページに書かれています。


紹介のため、今回紹介したゲームの「クリア動画」を作成しました。

 

 

今後も「コードゲーム」は進化していきます!

ゲーム作成機能 もあります。是非「コードゲーム」で遊んでださい。