Screaming Loud

日々是精進

十勝モッツァレラチーズカレー

久しぶりにカレーブログです。 今回は「十勝モッツァレラチーズカレー」です。

f:id:yuutookun:20200702202924j:plain

チーズカレーですが、モッツァレラのせいか粘り気が少なめな感じがしました。 比較的チーズ感はありつつも、少し玉ねぎの甘みがあるカレーです。

www.amazon.co.jp

iOS14におけるSkAdNetwork

以下ドキュメントを読んでのまとめなので、実際まだ動かしていません。

https://developer.apple.com/documentation/storekit/skadnetwork

  • iOS11.3から追加された機能でインストールリファラみたいもの
  • 広告表示時に認証させ、インストールした際に対象のAdNetworkにアプリから直接install postbackをする機能
  • install postbackは24時間以内に返ってくるくるが、ランダムである

https://docs-assets.developer.apple.com/published/f707b3297b/rendered2x-1590201126.png

各ロールの対応

配信側の利用要件

  • 広告配信側がAppleに登録してAdNetworkIDを取得する
  • SDKを提供している場合、配信側はAppleへの認証機構を提供する
  • アプリから直接install postbackが来るので受けれるようにする
    • 受け取ったinstall postbackが正しいか判定する

面のアプリ開発の利用要件

広告主(計測ツール)のアプリ開発要件

  • アプリからくるpostbackを受けとり、正しいか判定する

postbackのjsonのサンプルは以下

    {
      "version" : "2.0",
      "ad-network-id" : "com.example",
      "campaign-id" : 42,
      "transaction-id" : "6aafb7a5-0170-41b5-bbe4-fe71dedf1e28",
      "app-id" : 525463029,
      "attribution-signature" : "MDYCGQCsQ4y8d4BlYU9b8Qb9BPWPi+ixk\/OiRysCGQDZZ8fpJnuqs9my8iSQVbJO\/oU1AXUROYU="
      "redownload": 1,
      "source-app-id": 1234567891
      "conversion-value: 20
    }

SkAdNetworkの仕組み

registerAppForAdNetworkAttribution() , updateConversionValue(int) はともにinstall postbackのフックである

  • 起動時に registerAppForAdNetworkAttribution()を呼び、Installの起動かを判別
  • 起動時にupdateConversionValue(int)を呼び、installの起動かを判別
    • 上のregisterAppForAdNetworkAttribution は初回しか反応せず、かつ24時間中のいつ発火するかわからない。
    • updateConversionValueが呼ばれると、24時間のランダムタイマーがリセットされる
    • conversion valueはカウントアップのみ
    • 用途は多分cv地点がインストールじゃない場合の

所感

  • 広告を出す面のinfo.plistに各配信業者のIDを埋めなければいけないとなると、DSPなどは計測できない

Apache Hudiを触ってみた

最近はデータレイクに保存しているデータに対し更新、削除ができるライブラリが増えてきました。

Kudu,deltalakeやHudiなどがありますが、今回はUberが作ったHudiを触ってみました。

セットアップ

Quick-Start Guide - Apache Hudi を参考にセットアップしていきましょう。

localで試すためにspark-shellをいれて実行します。

$ brew install spark-shell
$ spark-shell --package org.apache.hudi:hudi-spark-bundle_2.11:0.5.1-incubating,org.apache.spark:spark-avro_2.11:2.4.4 \
  --conf 'spark.serializer=org.apache.spark.serializer.KryoSerializer'

データの書き込み

Quick Startに書いてあるとおり、立ち上がったspark-shell上でライブラリをimportします 以下spark-shell上で実行します。

import org.apache.hudi.QuickstartUtils._
import scala.collection.JavaConversions._
import org.apache.spark.sql.SaveMode._
import org.apache.hudi.DataSourceReadOptions._
import org.apache.hudi.DataSourceWriteOptions._
import org.apache.hudi.config.HoodieWriteConfig._

val tableName = "hudi_ad_user"
val basePath = "file:///tmp/hudi_ad_user"
val schemaStr = """{"type": "record","name": "users","fields": [ 
        {"name": "ts","type": "double"},{"name": "uuid", "type": "string"},
        {"name":"clicked_url","type": "string"}]}"""
scala> val cfg = newBuilder().
        withPath(basePath).
        forTable(tableName).
        withSchema(schemaStr)

basePathはspark-shell上でwriteしたデータが吐かれる場所です 上記の設定だと /tmp/hudi_ad_user/ 以下にデータが作成されます

ではデータをいれていきます。

val df1 = Seq((10, "1234", "https://www.google.com", "day=1"), (11, "5678", "https://twitter.com","day=2"), (15, "9012", "http://github.com","day=1")).toDF("ts", "uuid", "clicked_url", "partitionpath")
df1.write.format("hudi").
  options(getQuickstartWriteConfigs).
  option(PRECOMBINE_FIELD_OPT_KEY, "ts"). // primary keyが同じデータがあった際にこのカラムの大きい値を最新とする
  option(RECORDKEY_FIELD_OPT_KEY, "uuid").  // primary keyの役割
  option(PARTITIONPATH_FIELD_OPT_KEY, "partitionpath"). // partitionにするキー
  option(TABLE_NAME, tableName).
  mode(Overwrite).
  save(basePath)

上記を実行することでhudi形式でparquetフォーマットのファイルが生成されます。

以下parquetファイルだけ絞って見てみます

$ ls -R  /tmp/hudi_ad_user
day=1 day=2

./day=1:
b25a75a8-8d3f-455f-8c21-6cfd9efe1cfc-0_1-117-144_20200613135956.parquet

./day=2:
6b1081c3-fd79-4a53-ab9d-f8815d5e0a2e-0_0-117-143_20200613135956.parquet

対象のパーティションディレクトリにparquetファイルが吐かれているのがわかります。

データ読み込み

では検索してみましょう。

spark.read.format("hudi").load(basePath + "/*").show()

+-------------------+--------------------+------------------+----------------------+--------------------+---+----+--------------------+-------------+
|_hoodie_commit_time|_hoodie_commit_seqno|_hoodie_record_key|_hoodie_partition_path|   _hoodie_file_name| ts|uuid|         clicked_url|partitionpath|
+-------------------+--------------------+------------------+----------------------+--------------------+---+----+--------------------+-------------+
|     20200613135956| 20200613135956_0_23|              5678|                 day=2|6b1081c3-fd79-4a5...| 11|5678| https://twitter.com|        day=2|
|     20200613135956| 20200613135956_1_21|              9012|                 day=1|b25a75a8-8d3f-455...| 15|9012|   http://github.com|        day=1|
|     20200613135956| 20200613135956_1_22|              1234|                 day=1|b25a75a8-8d3f-455...| 10|1234|https://www.googl...|        day=1|
+-------------------+--------------------+------------------+----------------------+--------------------+---+----+--------------------+-------------+

データが入っていますね。 _hoodie という接頭辞がついているカラムがhudiが管理しているメタデータです。 _hoodie_record_keyuuid が入っています

データ更新

データを更新してみます。 更新する場合はRecordKeyが同一のものに対してアップデートをかけます。 今回ではuuidがPrimaryKeyの役割を果たしているので、同一のuuidに更新がかかります。 そしてtsを比較することで大きい値を最新とみなして更新します。

val df2 = Seq((20, "1234", "https://facebook.com", "day=1")).toDF("ts", "uuid", "clicked_url", "partitionpath")
df2.write.format("hudi").
  options(getQuickstartWriteConfigs).
  option(TABLE_NAME, tableName).
  mode(Append).
  save(basePath)
spark.read.format("hudi").load(basePath + "/*").show()

+-------------------+--------------------+------------------+----------------------+--------------------+---+----+--------------------+-------------+
|_hoodie_commit_time|_hoodie_commit_seqno|_hoodie_record_key|_hoodie_partition_path|   _hoodie_file_name| ts|uuid|         clicked_url|partitionpath|
+-------------------+--------------------+------------------+----------------------+--------------------+---+----+--------------------+-------------+
|     20200613135956| 20200613135956_0_23|              5678|                 day=2|6b1081c3-fd79-4a5...| 11|5678| https://twitter.com|        day=2|
|     20200613135956| 20200613135956_1_21|              9012|                 day=1|b25a75a8-8d3f-455...| 15|9012|   http://github.com|        day=1|
|     20200613141311| 20200613141311_0_24|              1234|                 day=1|b25a75a8-8d3f-455...| 20|1234|https://facebook.com|        day=1|
+-------------------+--------------------+------------------+----------------------+--------------------+---+----+--------------------+-------------+

_hoodie_record_keyuuid をキーにアップデートされているのがわかります。

実際のディレクトリを見てみると、新たにday=1のパーティションにparquetファイルが新たに追加されました。

$ ls -R
day=1 day=2

./day=1:
b25a75a8-8d3f-455f-8c21-6cfd9efe1cfc-0_0-153-13664_20200613141311.parquet b25a75a8-8d3f-455f-8c21-6cfd9efe1cfc-0_1-117-144_20200613135956.parquet

./day=2:
6b1081c3-fd79-4a53-ab9d-f8815d5e0a2e-0_0-117-143_20200613135956.parquet

特定の時点でのクエリ

アップデートしても、アップデート前の時点の状態に対してクエリをかけたい場合もあると思います。 BEGIN_TIMEの指定とEND_TIMEの指定を以下のようにすると、その _hoodie_commit_time 内のデータを参照します。 スタートに関しては "000" 指定必須感はありますがw

val df3 = spark.read.format("hudi").
  option(QUERY_TYPE_OPT_KEY, QUERY_TYPE_INCREMENTAL_OPT_VAL).
  option(BEGIN_INSTANTTIME_OPT_KEY, "000").
  option(END_INSTANTTIME_OPT_KEY, "20200613135957").
  load(basePath)
df3.show()
+-------------------+--------------------+------------------+----------------------+--------------------+---+----+--------------------+-------------+
|_hoodie_commit_time|_hoodie_commit_seqno|_hoodie_record_key|_hoodie_partition_path|   _hoodie_file_name| ts|uuid|         clicked_url|partitionpath|
+-------------------+--------------------+------------------+----------------------+--------------------+---+----+--------------------+-------------+
|     20200613135956| 20200613135956_0_23|              5678|                 day=2|6b1081c3-fd79-4a5...| 11|5678| https://twitter.com|        day=2|
|     20200613135956| 20200613135956_1_21|              9012|                 day=1|b25a75a8-8d3f-455...| 15|9012|   http://github.com|        day=1|
|     20200613135956| 20200613135956_1_22|              1234|                 day=1|b25a75a8-8d3f-455...| 10|1234|https://www.googl...|        day=1|
+-------------------+--------------------+------------------+----------------------+--------------------+---+----+--------------------+-------------+

まとめ

PrimaryKeyを指定してそれをベースにデータを上書きしていきます。 既存のデータをhudi形式に書き換える必要はあると思いますが、今まで貯めていたデータをアップデートできず洗い替えしていた場合にはいいソリューションなのではと思います。

リモートワークにおける制約と対処

完全リモートワークが始まって2ヶ月弱になりました。 昔アキレス腱を切ったときにリモートワークを2週間くらいやったことはあったんですが、 ここまで長期的にリモートワークを実施するのは初めてでした。

自分が体感しているリモートワークを行う上での制約を上げてみます。

対面のコミュニケーションが減る

まずどの企業もこれがまずいちばんに上がる話ではないでしょうか? 自分は出社してたときも、ほぼコミュニケーションは隣の席の人でもSlack上だったので あんまり影響はないだろうなと思っていて、実際働き始めてからもあまり変化はなかった気がします。

一方で何か新しい仕様を考えるときに、出社していたときは集まってホワイトボードに書きながら考えよう!という方式だったのが、 一旦誰かがDocsにまとめてそれを各自が読んで意見をしていくという形に変えてみました。

これによる影響は、

  • いい点としてどうしようかと思考している間、他のひとの時間がブロックされなく効率的ということ。
  • 悪い点としては、Docsにまとめるのが面倒、Docsからどういう意図かなのかを考えながら理解することが必要。

という二点かなと感じています。

特にオフィスのメリットであった

  • 仕様を考える際に同期的に考えることで全員が同じものを考える時間を作れる。
  • ホワイトボードでその場で意見をアイディアを具現化し共有できる。

ができないのが困っている。

今まではとりあえずやりますかという感じでできていましたが、オンラインMTGの敷居が若干高いこともあり、 現状オフィスと同じ体験はできていません。 かと言って、リモートの良さである効率性は落としたくないので、何かしら手を打ちたいと思っています。 いくつかホワイトボードアプリがあるので、それらを試してみたいなというところ。

Online Whiteboard for Software Diagramming - Sketchboard

Miro | Free Online Collaborative Whiteboard Platform

クラウドワークスペース『Strap』β版公開中 | Goodpatch Inc.

https://play.google.com/store/apps/details?id=com.google.android.apps.jam&hl=ja

AWW App | Online Whiteboard for Realtime Visual Collaboration

運動不足

通勤という概念がなくなったので、圧倒的に運動量が減っているというのは体感値としてわかってるんですが、 でもそれって可視化できてないよねという話でもあります。 自分はとりあえず毎日朝散歩するようにしました。 散歩をすることで擬似的に通勤を体感し、気持ちの切り替えができるようになったと感じています。 気持ちの切り替えという意味ではパジャマではなくちゃんと出社するときと同じ外向きの格好に着替えています。

また朝散歩だけでなく、昼 or 夜にも散歩してます。

あとはコロナ前から続けているリングフィットが大活躍ですね。

あと土日にランニングもはじめました。 ランニングアプリは色々試した結果、Nike RunClubに落ち着きそうです。

そして可視化できてないよね?の対策として、ガーミンのvivosmart4をポチりました。 fitbitなども考えましたが、理由としては、各ランニングアプリを試していたところ、全部に対応していたのがガーミンだけだったということです。 届くのが楽しみです。

食事

結構困るのが食事ですね。 飲み会とかってご飯が勝手に出てくるってのも結構自分としては重要な体験だったのが、 オンライン飲み会とか自分で用意しないといけないし、Uberも高い割にいいのがあんまないしでまだ解決策が見つけられていません。

結局面倒になってカップラーメンというのが増えていて、食事バランスが崩れており、口内炎もできてしまいました。 久しぶりにBASEパスタを復活させるのも手かな。

あとクラフトビールを買ってみるというのを始めてみました。 料理が苦手なので、おいしいテイクアウトとかを開拓していかないとダメなんですかね。。

いいソリューションを知りたいです。