TECH PLAY

株式会社カケハシ

株式会社カケハシ の技術ブログ

367

(分割キーボードのMoonlander。とても気に入ってます。) はじめまして。ソフトウェアエンジニアの椎葉(@bufferings)です。今年の4月にカケハシに入社して、今月末で3ヶ月になります。毎日楽しく過ごしていて、あっという間の3ヶ月間でした。 最初にすなおにお伝えすると、この週末と週明けにイベントで登壇するので、その宣伝をしたいなという気持ちがあってブログを書くことにしました。ぜひイベントを見に来てほしいです! そんなきっかけではあるのですが、せっかくの機会なので、この3ヶ月をふりかえって、やったことを書いてみようと思います。初の会社ブログです。僕がどんな風に仕事をしているか、雰囲気…
アバター
はじめに こんにちは、LINE上で動くおくすり連絡帳 Pocket Musubi というサービスを開発している種岡です . 社内でプライベートAPIを開発する要件があり、タイミング良くAppSyncでプライベートAPIが使えるようになったため試してみました 全体像 以下システムの全体像になります . アカウントAのLambdaからアカウントBのAppSyncにリクエストし、DynamoDBにItemが書き込まれることをゴールとしました ポイント AppSyncはPrivate APIモードかつ、認証モードはIAM認証を利用 アカウントAとアカウントBはVPC Peeringで繋いでいるためCI…
アバター
請求明細書を見てお嘆きの皆様へ。 前回の記事では、すぐにコスト削減の成果を出したい人向けに比較的簡単な方法を解説しました。 この記事では、時間がかかっても良いので根本的に原価をなんとかしたい人を対象としています。 請求明細書(Billing)やCost Explorerの参照権限を広く付ける 開発者がコストを意識するところから、すべては始まります。 意識しようにも、そもそもいくらかかっているかわからないことがあります。見ようとしても現場レベルで直接参照する権限がないことがあります。情報対称性の意味でも、初期設定で権限を付与しておきましょう。デフォルトAllow、やむを得ない事情があればDeny…
アバター
5/25にDatabricks Japan社にて報道関係者を対象とした同社の2023年度の戦略発表会が行われました。 光栄なことに、我々カケハシもDatabricksの1ユーザーとしてその場に同席し、カケハシにおけるDatabricksの活用事例の講演の機会を頂きました。 会社を代表してデータ基盤チームのメンバーで参加してきましたので、当記事ではカケハシの登壇部分を中心に戦略発表会の様子をレポートをさせて頂きます。 戦略発表会の概要 1. Databricks Japan社の発表 Databricks社の日本国内における同社の人員増強や市場認知の向上の方針の発表に加えて、データレイクハウスを支…
アバター
こんにちは、おくすり連絡帳 Pocket Musubiというサービスを開発している石井です。 私たちのチームはフロントエンド担当とかバックエンド担当とか敷居を区切らずに、エンジニアのモチベーションでフロントもバックエンドも実装するフルスタックよりなエンジニアで構成されています。 もちろんその中で得意分野は各自ありまして、私はインフラを得意(?)というかインフラが好きなエンジニアになります。 インフラの開発ではTerraformやServerless等ありますが、最近ではもっぱらCDKを使っています。 さてそんな CDK ですが、開発していると稀にnodeってでてきませんか? Node.js、、…
アバター
概要 未だ0系でありながら、22年8月に一般提供が開始された CDK for Terraform の実用性を検討し、DatadogのMonitor(およびDowntime)の構成管理をコード化しました。 背景 弊社では主要な監視ツールの一つとしてDatadogを活用しており、弊チームでもDatadogのMonitorという機能を利用して、インフラリソースの各種メトリクスが異常値を示した時のSlack通知やオンコール設定などを行っています。 しかしながら、多くの監視設定を漸進的に構築していくにはUIからの追加・変更作業が負担に感じる様になってきており、構成管理ツールの導入を検討しました。 一般的…
アバター
こんにちは、カケハシのデータ基盤チームで開発ディレクターをしている松田です。最近、歳のせいか疲れが溜まりやすくなっており、毎週サウナに通っています。 カケハシでは今までRedashを利用して全社にデータ提供をしていましたが、去年の7月からDatabricksを利用することになりました。そのため、今までRedashで使っていたクエリやダッシュボードをDatabricksへ移行する必要がありました。 その時に、Redash(Presto)とDatabricksのデータベース操作言語(DML)違いで少しハマり、みなさんにも同じ轍を踏んで欲しくないため、本記事では変更点や対応方法をまとめたいと思います。是非、チートシート的な使い方をしてくれると泣いて喜びます! Redash時代の構成とPrestoについて Databricks導入以前のアーキテクチャイメージは以下になっていました。 データ利活用観点だと、RedashからAthenaでS3を参照して分析したり、そのクエリ結果から CTAS で中間データをを作成していました。Athenaでは、DMLとしてPrestoというオープンソースを利用しているため、基本的にはPrestoのドキュメントを参照しています。 Presto は、大量なデータに対してインタラクティブな分析クエリを実行するための高性能分散型SQLクエリエンジンです。 Facebookが自社の大規模なデータセットに対して、インタラクティブに結果を返すことを目的として2012年に開発され、2013年にオープンソースとなったものです。 主に「一秒〜数分程度で終わる集計」や「コンパクトな処理を素早く実行したい場合」などに利用されるアーキテクチャとなります。 Databricks時代の構成とDatabricks SQLについて 現在のDatabricks導入後のアーキテクチャイメージは以下になります。 Databricksは、Redashとほぼ同じ様な見た目や使い勝手になっており、SQLクエリ、ビジュアライズ、ダッシュボードなどが利用できます。 Databricks SQLでDelta Lakeを参照してデータ分析したり、中間データを作成したりしています。 Databricks SQL は、Databricks Lakehouse Platformに組み込まれたエンタープライズデータウェアハウスとしてなります。 Databricks SQLのコア機能は、 SQLウェアハウス と呼ばれる最適化されたコンピューティングです。 Databricksは、Hadoopのオルタナティブとなるプロダクトを開発するためにカリフォルニア大学バークレイ校の研究室仲間で2013年に立ち上がりました。そのHadoopのオルタナティブとなるプロダクトとして開発されたのがSparkで、そのSparkを使ったクラウドサービスが「Databricks Cloud」となっています。そのため、Databricksのドキュメントでは多くの場合はSpark SQLを使用してSQLクエリと機能を説明しています。 そして、Spark SQLはDatabricks SQLを使用して作成されたクエリをサポートします。 つまり、Redash側はPresto、Databricks側はDatabricks SQL(≒ Spark SQL)で、Spark SQL自体はHiveQLと似ておりHiveの関数と互換性があります。 それらの構文の違いでハマったポイントを説明します。 DMLの違いによりハマったところ 基礎関数(配列、結合、変換) ARRAYの記述が異なる 配列を作りたい時に使用する。 Databricks SQLでは () を使い、Prestoでは [] を使う。 -- Databricks(Databricks SQL) SELECT ARRAY("a", "b"); -- 結果: ["a", "b"] -- Redash(Presto) SELECT ARRAY["a", "b"]; -- 結果: ["a", "b"] LATERAL VIEW explode / CROSS JOIN UNNEST Array型のカラムに保存されている値を行へ展開するする時に使います。 例では、患者さん1レコードに対して複数の疾患をArray型で持っている場合、疾患毎にレコードが展開されます。 -- Databricks(Databricks SQL) SELECT patient.name, disease FROM patients LATERAL VIEW explode(diseases) AS disease; -- Redash(Presto) SELECT patient.name, disease FROM patients CROSS JOIN UNNEST(diseases) AS t(disease); バッククォート / ダブルクォート AS句で日本語を利用する時は、Databricks SQLではバッククォートで囲み、Prestoではダブルクォートで囲む必要があります。 -- Databricks(Databricks SQL) SELECT '日本語' AS `日本語`; -- Redash(Presto) SELECT '日本語' AS "日本語"; Hash関数の型が異なる SHA256でHash化する時に使います。 Prestoの方はSHA256関数の引数と返り値がbinaryのため、型変換の関数が必要になる。 -- Databricks(Databricks SQL) SELECT SHA2('patient_id', 256) -- Redash(Presto) SELECT TO_HEX(SHA256(TO_UTF8('patient_id'))) varcharをcastする時のLength Parameterの有無 型変換のcast関数を使う時にDatabricks SQLはLength Parameterを指定する必要がある。 -- Databricks(Databricks SQL) cast(created_at as varchar(10)) -- Redash(Presto) cast(created_at as varchar) try_cast / try 無効なCASTが発生した際に、クエリを正常終了してNULLを返して欲しい時に使用する。 Databricks SQLでは型に厳しくないため利用の機会が少ないが、Prestoでは利用するシーンはよくあります。 Prestoの try では、CAST以外にもゼロ除算や数値範囲外も検知できる。 -- Databricks(Databricks SQL) SELECT try_cast('10' AS INT); -- 結果: 10 SELECT try_cast('a' AS INT); -- 結果: NULL -- Redash(Presto) SELECT try('10' AS INT); -- 結果: 10 SELECT try('a' AS INT); -- 結果: NULL 日付関数 datediff / date_diff 2つの日付の期間を計算したい時に使います。 Prestoでは引数に day 以外にも month などを選択できますが、Databricks SQLでは選択できないため日数の差分しか取得できません。 また、引数の順番が異なるためご注意ください。 -- Databricks(Databricks SQL) SELECT datediff('2023-02-09', '2023-02-07') --結果: 2 -- Redash(Presto) SELECT date_diff('day', CAST('2023-02-07' AS DATE), CAST('2023-02-09' AS DATE)) --結果: 2 TIMESTAMPDIFF / TIMESTAMP_DIFF 2つのタイムスタンプの期間を計算したい時に使います。 引数の順番が異なるためご注意ください。 -- Databricks(Databricks SQL) TIMESTAMPDIFF(MINUTE, c.time_created, d.time_created) --結果: 0, -- Redash(Presto) TIMESTAMP_DIFF(d.time_created, c.time_created, MINUTE) -- 結果: 0, date_addの引数指定が異なる 日付の足し引き計算をしたい時に使います。 Prestoでは引数に day 以外にも month などを選択できますが、Databricks SQLでは選択できません。 そのため、月の足し引き計算は add_months で代替します。 -- Databricks(Databricks SQL) date_add('2023-02-09', -1) -- 結果: '2023-02-08' -- Redash(Presto) date_add('day', -1, CAST('2023-02-09' AS DATE)) -- 結果: '2023-02-08' DATE_FORMATの時刻形式が異なる タイムスタンプ型の日時を日付文字列へ変換する時に使います。 時刻形式のフォーマットに差異があります。 -- Databricks(Databricks SQL) DATE_FORMAT('2023-02-09 11:22:33', 'yyyy-MM-dd') -- 結果: '2023-02-09' -- Redash(Presto) DATE_FORMAT('2023-02-09 11:22:33', '%Y-%m-%d') -- 結果: '2023-02-09' to_date / from_iso8601_date 文字列から日付へ変換する時に使います。 -- Databricks(Databricks SQL) to_date('2023-02-09') -- 結果: '2023-02-09' -- Redash(Presto) from_iso8601_date('2023-02-09') -- 結果: '2023-02-09' date_trunc / TIMESTANP_TRUC タイムスタンプを指定した日付や時刻のところで切り捨てる時に使います。 引数の順番が異なるためご注意ください。 -- Databricks(Databricks SQL) date_trunc('day', '2023-02-09 11:22:33') -- 結果: '2023-02-09' -- Redash(Presto) TIMESTAMP_TRUNC('2023-02-09 11:22:33', DAY) -- 結果: '2023-02-09' 集計関数 percentile / percentile_cont INT型のグループ内で中央値、第一四分位数などを取得する時に使用する。 例では、 0.5 に設定することで中央値を取得している。 -- Databricks(Databricks SQL) SELECT percentile(col, 0.5) FROM VALUES (0), (10) AS tab(col); -- 結果: 5 -- Redash(Presto) SELECT percentile_cont(col, 0.5) FROM (VALUES(0),(10)) t(col); -- 結果:5 row_numberでorder byを指定する必要がある 行に番号をふる時に使用する。 Databricks SQLでは ORDER BY を指定して上げる必要がある。 -- Databricks(Databricks SQL) ROW_NUMBER() over(PARTITION BY count ORDER BY count), -- Redash(Presto) ROW_NUMBER() over(PARTITION BY count), max_by, min_byの複数返却指定ができないため、指定した要素の値を取得する際にはarray_aggとfilterを利用する必要がある group by を使用した時に、第2引数で指定したカラムの最大・最小の第2引数を取得したい時に使用する。 Prestoでは第3引数に返却個数を指定して配列で取得できるが、Databricks SQLでは値しか取得できない。 そのため、Databricks SQLを利用する場合は、 group by で1番目の値ではなく、2番目の値を取得するには少し書き換えが必要になります。 -- group by でまとめられる対象のレコード -- date, name, rank -- "2023-01-01","A",1 -- "2023-01-01","B",2 -- Databricks(Databricks SQL) SELECT min_by(name, rank) as first_rank_name -- 結果: "A" SELECT array_agg(name) filter(where rank = 2)[0] as second_rank_name -- 結果: "B" -- Redash(Presto) SELECT min_by(name, rank) as first_rank_name -- 結果: "A" SELECT element_at(min_by(name, rank, 2), 2) as second_rank_name -- 結果: "B" まとめ RedashからDatabricksへ移行した際にDMLでハマったポイントとその解決方法をご紹介しました。 まだ、文法の違いで詰まることはあるとは思いますが定期的に情報をアップデートしていき、社内や社外の人に知見を共有できたらとと考えています。 もし少しでも興味を持った方がいらっしゃいましたら、データエンジニアとしてカケハシで一緒に働きませんか? 一緒に有数のデータカンパニーを目指せていければと思い、仲間を募集しております。
アバター
みなさんは、技術とは何か、考えたことはあるでしょうか。 ここでは、技術哲学の立場から考えるための参考として、ハイデガーの技術論を取り上げます。 技術論のタイプ A.フィーンバーグの技術: クリティカル・セオリーの分類によれば、 これまでの技術論は2つの立場に分けることができる、と考えられています。 道具説 自立的存在説 道具説は、世の中に広く受け入れられている見方で、 技術とは「中立的」であり、利用者の目的に奉仕する道具と考えます。 逆に、技術の中立性を否定する見方として、自立的存在説という見方もあります。 この説では、技術とはそれ自体が社会のすべてを管理の対象物として作り変えようとする、と考…
アバター
AWSコストをいますぐ最適化しませんか? キャッシュフロー、ユニットエコノミクス、改善しませんか? この記事では、とにかくいますぐなんとかしたい方向けの方法を金額面で大きい傾向にあるサービスごとに26個紹介します。 以下各見出し内の💰はコスト削減度、⚡はおまけでパフォーマンス改善度を指します。 (1) 💰💰💰 CloudWatch Logs: とにかくログを出さないこと、まとめること AWS料金のうち、CloudWatch Logsが上位を占める傾向にあります。保存期間が無期限だから費用がかかる...と見せかけて、実際はログ出力自体の料金が大半です。 レガシーWebアプリケーションのログは1リクエストにつき何回も何行も出力する傾向があります。フレームワーク特有の不要なログも付いてきます。勝手に出力されるログは放置せず整理して、リクエスト単位でイベントとしてまとめましょう。 参考記事 各種ジョブもログが多いですね。 特定のエラーに対するログが出しっぱなしなら、エラーを修正をするかログのほうを消しましょう。 Lambda実行時に必ず出るCloudWatch Logsの話 Lambda実行のたびにCloudWatch LogsにSTART / REPORT / ENDの3行が必ず出力されます。 このログはメトリクスを兼任していますが、あえてここの出力も削りたいなら、LambdaからCloudwatch LogsへのPUT権限を外すという大技があります。 Proxy的な、実行量が多いが何もしない処理にはありかもしれません。 Lambda パフォーマンス改善 ≒ コスト改善です。パフォーマンスチューニングの観点は別稿に譲るとして、ポイントだけ解説します。 以下も参考にしてください。 (2) 💰💰⚡ Lambda: ARM化する 単純に20%落とせます。Node.jsはARM化しやすいものの、Pythonは利用ライブラリによっては工数がかかります。 (3) 💰⚡ Lambda: handlerの外で初期化する Lambda実行のたびに初期化処理が実行されていることがあります。今一度確認しましょう。 (4) 💰 Lambda: Provisioned Concurrencyを削減する 本番環境でもLambda実行料金以上にかかっている事例が散見されます。精査しましょう。 (5) 💰 Lambda: ランタイムのバージョンを上げる 最近のプログラミング言語はパフォーマンスに気を使っています。 2023年2月現在ではまだ未対応ですが、Python 3.11はFaster CPythonプロジェクトにより高速化されていますし、 boto3 1.20.32からのkeepalive機能 にも期待したいところです。 (6) 💰⚡ Lambda: Node.jsでのTCP Keep-alive追加 Lambda公式ドキュメントにもあるとおりです。なお、Node.js 18やAWS SDK for JavaScript v3、CDKのNodejsFunctionでは設定済みなので対応不要です。 (7) 💰⚡ Lambda: Preflight Requestに対応する クラウド時代、アプリケーションまでOPTIONSメソッドが到達したら何かがおかしいでしょう。ルーティングをアプリではなくクラウドインフラに全部任せることをおすすめしています。API GatewayやCloudFrontなどのインフラで対応しましょう。 Access-Control-Max-Ageによるキャッシュも大事です。もちろんPreflight Requestが発行されないことが理想です。 (8) 💰⚡ Lambda: とにかく実行しないようにする 実行しなければ無料!コード負債もゼロ!ついでに障害もゼロ! 以下のような方法が考えられます。 EventBridgeで直接外部APIを実行する Event Filteringで除外処理をLambdaの外に置く Lambda経由でのS3アクセスを避ける API Gatewayの固定レスポンスを活用する DynamoDB 特にGSI関係がコストが嵩む要因です。 以下の記事も参考にしてください (9) 💰💰 DynamoDB:GSIの不要な射影を減らす なんとなく全部を射影にしてしまいがちですが、特に書き込みコストに跳ねるので控えましょう。新しい同一のテーブルにも複製して書き込むイメージです。 (10) 💰⚡⚡ DynamoDB: batchでget/writeする たまにgetを連打するコードを見かけます。batchに書き換えてアクセスをまとめ、節約しましょう。 (11) 💰 DynamoDB: 保存する属性名、キー名を短くする NoSQLであり型が決まっていない以上、レスポンス内で属性名やキー名を繰り返すコストが膨大になります。少しでも削りましょう。アプリケーションコード側でわかりやすい属性名にマッピングすると可読性とコスト、パフォーマンスを両立できます。 (12) 💰 DynamoDB: On-Demandにする キャパシティをProvisionedにすると使っていない時間帯も料金が固定でかかります。ワークロードが1日を通して大きく動くならOn-Demandのほうが無難です。 一方リザーブドキャパシティによる事前予約を考えるとProvisionedも選択肢に上がりますね。 AWS Config Advanced Queryを使えば、キャパシティ種類を全テーブル全アカウント分一覧化できます。 Aurora RDS いかにスペックを下げるか、負荷ピークを作らないかが勝負です。 (13) 💰💰 Aurora: インスタンスファミリーを最新化にする 2023年現在最新のr系インスタンスはr6gです。新しいバージョンが登場したときにスムーズにアップグレードする体制が整っているでしょうか? (14) 💰💰⚡⚡ Aurora: インデックスを付与する 残念ながらテーブル作成時にインデックスを付与していない事例が散見されます。インデックスがないとDB負荷があがり、インスタンスサイズ増、料金増につながります。MySQLなら複合インデックスの理解が浅めの方が多めです。複合インデックスが1テーブルにおおよそ1つはないと不安を感じます。APMツールで確認しながらインデックスを追加しましょう (15) 💰💰⚡ Aurora: N+1クエリを放置しない DB負荷が大きく上がりますし、処理時間がかかることでアプリケーションレイヤーのコストもかかります。APMツールを入れているとモニタリングコストも大きくかかります。 (16) 💰💰 NAT Gateway: S3 / DynamoDBのGateway endpointを設定する VPC内からNAT Gatewayを通さずにアクセスできます。NAT Gatewayの通信量が多いときは確認してみましょう。移行時のリスクはほぼありません。 (17) 💰 NAT Gateway: 通信量を減らす アプリケーション起動時にイメージ取得や外部のリポジトリアクセスによる通信が時折発生します。キャッシュ可能ならキャッシュするか、インターフェイスエンドポイントでNAT Gatewayの迂回を検討しましょう。特にFargate + ECRが危険です。 (18) 💰💰💰 S3: バージョニングの再検討 AWSのベストプラクティスにもある通り、セキュリティや運用上はオブジェクトの過去バージョンを保持したいところです。一方、過去のオブジェクトをそのまま保持するため膨大な料金がかかります。 バージョニングの停止のほか、過去バージョンのストレージクラスを変更する、過去バージョンの世代数やTTLを設定するといった選択肢があります。 (19) 💰💰💰 S3: バージョニングの残骸を確実に消す 検討の結果バージョニングを停止しても、以前のバージョンは残ったままです。新しいバージョンの生成を停止しただけなので。過去バージョンが不要なら消去する必要があります。 S3 Storage Lensを使うとアカウント単位で旧バージョンの総サイズを確認できて便利です。 (20) 💰⚡ S3: list APIよりS3インベントリを使う 詳しく見るとListObjects APIが嵩んでいることが見受けられます。S3インベントリを駆使し、毎回listしなくてよくなるか検討してみましょう。 (21) 💰⚡ API Gateway:Authorizerのttlキャッシュを設定する 認可処理は呼び出し回数が多くなります。キャッシュしましょう (22) API Gateway: gzip化する たまに設定できていないので確認しましょう。圧縮サイズを設定するとonになる仕組みです。 (23) API Gateway: 固定レスポンスを活用する バックエンドAPI呼び出しが不要ならこちらを。 (24) Glue: 既存記事をご確認ください パフォーマンスはコストということで、カケハシでGlueを主力にしているチームの記事を参考にしてください 以下の記事も非常に参考になり、なにかあるたびに勧めています。ありがとうございます🥰 (25) KMS: マネージドキー+暗号化に注意 AWS所有のキー以外を利用すると、料金が大幅に上昇します。KMS自体もかかりますが、CloudTrailの費用が膨大になります。 (26) 💰💰💰⚡⚡⚡ とにかく不要なアクセスを減らす 一番最初にやることをおすすめしますが、若干時間がかかるので最後に持ってきました。 そのフロントエンドからバックエンドの通信は本当に必要ですか? すでにあるキャッシュ機構は使えませんか?新しく導入するのは大変ですが既存なら現実的 ポーリング、リフレッシュの間隔を延長できませんか? フロントエンドのような前段側で改善すれば、後段側の料金も減るのでおすすめです。 参考記事 最後に この記事ではすぐに成果を出したい人向けに比較的簡単な方法を解説しました。開発環境やRI/契約といった原価改善の本質からそれる話は除いています。 前回の記事では、サーバレスの優位性について説明しており、 次回は根本的にコスト構造を変化させる方法について記載する予定です。 文責:高木
アバター
こんにちは、株式会社カケハシでおくすり連絡帳 Pocket Musubiの開発を担当している渡辺です。 今回は文字コードについての記事を書きました。 Pocket Musubiではお薬手帳用QRコードを読み込み、デコードした結果を利用します。ここでデコードするときにうまくいかないケースがあり、そこでの知見です。 文字コードについて 蛇足ですが、文字コードについて簡単におさらいします。 文字コードとは、文字をコンピューターで扱うために、文字ごと割り当てた数字のことです。 文字コードの対応表に基づいて、文字を数字に割り当てることを文字エンコードと言います。 文字コードの対応表には、ASCIIやUT…
アバター
はじめに こんにちは。カケハシの患者リスト開発チームの金と申します。 患者リストはWEB業務アプリとして、薬局から患者さんへのフォローで関係性を向上させるためのツールです! 患者リスト開発チームではDDDとClean Architectureを基にしてスクラム開発を行なっています。 私はこのチームに入って半年で、患者リスト開発チームに入る前まではDDDとClean Architectureに関してあまり知識がない人でした。 この半年DDDに関して学んだり気づいたことが多いですが、その中で、ユビキタス言語とドメインについて少々考えたことを記述してみたいと思います。 DDD初心者目線の記事になりま…
アバター
はじめに こんにちは、LINE上で動くおくすり連絡帳 Pocket Musubi というサービスを開発している種岡です。 ある日チーム内メンバーから CI実行時間がとても長くなり困っている というアラートが発せられました。 実際に確認しに行くと、開発初期の頃は5分ぐらいだったテストが、いつの間にか 20分 以上にもなっていました。 待ち時間は、DX体験を損なうだけでなく、本来できたはずである付加価値を生む開発時間を奪う側面も持ち合わせており、即刻対処すべき案件と捉えテストを早くするタスクに取り掛かりました。 結果、当サービス比ではありますが、3倍ほど早くすることができました。 そこで備忘録がて…
アバター
はじめに こんにちは。 Musubi の機能開発チームでフロントエンドを主に担当しております、井上です。どうぞよろしくお願い致します。 カケハシではスクラムによる開発を推進しており、その運用はあるていど各チームの裁量に任されています。 今回は私が所属しておりますチームがスクラムを運用をしていく中で使用している GitHub Projects について触れていきます。   どうぞお付き合いのほど、よろしくお願い致します。 ご注意 上記でも触れておりますが、本記事における GitHub Projects の運用は Musubi の機能開発チームにスコープしたものです カケハシ全体で GitHub Projects を用いたスクラム運用を行っているものではありません GitHub Projects とは GitHub が提供するプロジェクト運用のためのツールです。 ドキュメント では次の記載があります。 (公式ドキュメントから引用) Projects は、GitHub での作業を計画および追跡するための、適応性のある柔軟なツールです。 日々の業務を行うなかで発生する「作業のチケット化」「作業状況」「検討結果やメモ」といった動きや情報を、この GitHub Projects を使って共有しています。 なぜ GitHub Projects なのか もともとは Trello を使ってタスク管理を行っていました。 Trello でもチケットドリブンによる開発タスクの管理を行えていましたが、次の点を実現したいという欲求がありました。 スプリントを設定したい GitHub とのシームレスな連携が行いたい ベロシティを計測したい 進捗状況を視覚化させたい チームの透明性を確保したい これらの解決にあたり、 JIRA や ZenHub も選択肢に挙がったのですが 追加費用が発生することなく利用開始できる 求める機能がまぁまぁ揃っていた この 2 点から GitHub Projects の利用を決めました。 まず 1. ですが、 GitHub Projects は利用しているプラン上で利用できます。利用にあたり追加で料金が発生することはありません。 次に 2. について。 Insights という機能を用いて分析情報を視覚化することができます。また GitHub から提供されているので、 Issue と PR の連携もスムーズに行えます。 どう運用しているか では実際にスプリントを回すにあたり、どのように運用( 利用 )しているかについて触れていきます。 1. Custom fields の設定 画面右側の 「...」 から 「Settings」 を開き Custom fields を設定していきます。 ( 画像上部の棒線は個人アイコンを隠すため ) Estimate の設定例. Sprint の設定例. これは Iteration を指定しているので、対象となるアイテムを追加していくことが出来ます。 この画面からそれぞれ設定していくのですが、デフォルトで用意されているフィールドだけで足りない場合は + New field から追加します。( ↑ の画面はすでに後述のフィールドを追加した状態です ) なお Field type は一度設定すると変更できないようですのでご注意ください。誤って Field type を設定した場合はフィールド名の右にある 「...」 から Delete field で削除し、再度作成します。 ちなみに、現在は次のフィールドを設けて運用しております。 フィールド 内容 備考 Status チケットの状態を表します かんばん方式で Issue を見る際に各ステータスにチケットが配置されます Estimate チケットの作業ポイントを表します Function チケットが受け持つ機能を表します チケットが実現する機能ではなく Web App, API といった大きな括りでの機能を設定します Epic チケットが実現する内容を包括的に管理する項目です 後述の「残念な部分」でも取り上げますが, Epic としてチケットを発行できないのでフィールドで管理しようという試みです Type チケットが Epic なのか実作業としての Task なのかを区別します Sprint チケット対応を行うスプリントを表します 後述の Insights で分析する際に重要です Remarks チケットがプランニング時に対応予定となっていたものかどうかを表します こちらも Insights で分析する際に重要です ここで設定した項目を実際の Issue 作成時に埋めていきます。 そしてその内容が後述の Issue 管理にも影響します。 2. チケット管理 チケットは GitHub Projects の Add item から作成すると便利です。 作成直後はチケットが Draft 状態で配置されます。その後、チケットの詳細を埋めていく中で適切なリポジトリを選択して Issue にコンバートします。 追加したいステータス上で Add item を押します チケットタイトルを入力します 1. で選択していたステータス上に Draft でチケットが配置されました チケットを開いて詳細を記入していきます( Issue へのコンバートもここで行います ) 枠で囲った Edit からは OverView が編集できます。 その右側の枠で囲った項目からは、先述の Custom fields に対して情報を設定していきます。 Convert to issue で Draft から Issue にコンバートされます。このとき、 Issue を配置するリポジトリを選択します。 3. Issue 管理 Issue 管理は目的に応じて「かんばん方式」か「テーブル方式」が選択できます。 現在は主に次の 3つ のビューを用意して Issue の進捗状況の確認に役立てています。 目的 方式 俯瞰して確認 かんばん方式 スプリント単位で確認 テーブル方式 担当者単位で確認 テーブル方式 かんばん方式 ステータスごとに「列」が配置され、チケットはそのステータスに応じた列に配置されます 各チケットの状況が一目で分かるので状況の把握に役立ちます テーブル方式 前掲の Custom fields で設定したフィールドで グルーピング することで、フィールド単位のチケット把握に役立ちます 私達の運用では上記表のとおり スプリント と 担当者 単位の把握に利用しています 4. ワークフロー 簡素・単純ではありますが、Issue や PR の状況に応じたワークフローも利用できます。 現在用意されているワークフローは Item added to project Item reopened Item closed Code changed requested Code review approved Pull request merged Auto-archive items (Beta) があります。 これらは各ステータスに応じたワークフローで、Issue か PR, またはその両方が ( When ) 指定されたステータスになったとき ( Set ) どうするか 程度の設定しかできません。 また任意でワークフローを追加することはできないようです。より柔軟なワークフローを運用したい場合は GitHub Actions の利用が必要になります。 というわけで、いまのところ Item added to project Item closed Code changed requested Pull request merged の 4つ のワークフローでシンプルに 「チケット追加」「チケット終了」「PR発行・レビュー依頼」 「PR マージ」 時にチケットを指定のステータスに移動させる運用としています。 5. 分析 さて、GtiHub Projects を利用する最大のメリットである 分析 です。 GitHub Porjects では Insgihts という機能から作業状況の分析が行なえます。 次のチャートを作成してスプリントの振り返りに活用してしています。 目的 要約 説明 スプリントの作業状況を確認 各ステータスの積み上げを折れ線グラフで分析 ( バーンアップチャート ) ポイントを プランニング時, 差し込み, トータル でそれぞれ分析する スプリントの作業量を確認 消化ポイントを棒グラフで分析 ( ベロシティ ) スプリント単位で計測し、1スプリントあたりに消化できるポイントを把握する Epic ごとのポイントを確認 Epic の積み上げを棒グラフで分析 Epic の割合を把握する スプリントの作業状況を確認( バーンアップチャート ) スプリントの作業量を確認( ベロシティ ) Epic ごとのポイントを確認 ( 塗りつぶしていますが、各 Epic に対応するポイントを出力してます ) 6. チームの透明性の確保 ここまでに触れてきた内容がそのまま「チームの透明性の確保」につながるのですが、 メンバ各人が何をやっているのか 対応している案件はどの程度進んでいるのか トラブルが発生していないか、もしくはトラブルになりそうな気配はないか 等々... こうした「チーム・メンバの置かれた状況を相互に理解しやすくする環境づくり」ということを「透明性」という言葉で表しておりまして、その実現が GitHub Projects の運用を通して期待する効果の一つなのですが、実際にその効果は少しずつ見えてきました。 次は一例ですが、 疑問 スプリント中にプランニングで決めた作業( ポイント )の消化が低いのはなぜか 原因 プランニング外の割り込み作業が発生している 単にそのタスクを担当しているメンバの進捗が遅れている 対策 割り込み作業に対して ベロシティを計測することでスプリント辺りのポイントから予め割り込み作業分を確保しておく もしくは割り込み作業を次スプリントに回せないかの調整をする 個人の進捗遅れについて ペアプログラミングやモブプログラミングを通じて遅れとなっている原因の解決に臨む という具合に、疑問・課題に対するアクションを取りやすくなりました。 この辺はチケット発行して対応にあたるという、タスクをチケット化しただけの運用では得られなかった効果であると思いますので、導入の意義が充分にあったといえる点です。 番外. Issue と PR の紐付け GitHub Projects の利用目的の一つが Isseu と PR の連携でした。 とはいっても、GitHub Projects を利用して特別な何かをすることはありません。 公式のドキュメント Pull RequestをIssueにリンクする に従い PR を作成することで両者を紐付けます。 ただこれだけの作業ですが、これによって GitHub Porjects 上で Issue と PR を見ることができるのは日々の業務を行う上でかなり便利になります。 残念な部分 バーンダウンチャートが利用できない Epic チケットが発行できない 1. バーンダウンチャートが利用できない GitHub Projects ではバーンダウンチャート機能はまだ提供されていないようです。 こちら のように GitHub Projects からデータを取得して自前でバーンダウンチャートを作成することもできるようですが、そこにコストをかけることは目的から外れますので対応していません。 いまのところは前掲のようにバーンアップチャートで代替していますが、 スプリント単位でのポイント消化 という点においては視覚化できております。 バーンダウンチャートについては今後の GitHub Projects のアップデートに期待したいところですね。 2. Epic が発行できない チケットを Epic として発行できない のが GitHub 導入当初は歯がゆいと感じておりました。 とはいえ、前掲したように現在は「Custom fields に Epic を設定」することで運用できております。 むしろ Epic チケットを発行しなくても現在の運用で「どの案件に紐づくチケットは把握できる」 Epic チケットに Issue チケットを紐付ける作業のほうが面倒ではないか という向きもあり、Epic チケットは無いままでも良いかな、という感じで回っています。 今後のアップデートで Epic チケットについても提供されるかもしれませんが、そのときにまた改めて使用するかを検討したいと思います。 まとめにかえて まとめにかえて、というところで GitHub Projects を使ってみての所感を述べたいと思います。 ここまで簡単ではありますが、ざっと GitHub Projects での運用について触れて来ました。内容の繰り返しにはなりますが、改めて GitHub Projects を採用したことで良かった点について挙げていきます。( 残念な点については前項で触れたので割愛します ) 良かった点 スプリントを設定できる スプリントにおける進捗状況や作業分析を視覚的に訴えることができる Issue と PR の紐付けが簡単できる 上記に関連して Issue や PR との UI/UX の乖離がない ワークフローによるステータスの自動変更 チームの透明性の確保 上記は課題として解決したいと思っていたことですが、これらは GitHub Projects を導入することで解決できました。 そのなかでも、やはりスプリントの状況を視覚化できたというのは大きな点です。 作業状況がグラフとして見えるというのは直感的に理解しやすいということでモチベーションの向上に繋がります。もちろん進捗が悪いときも如実に反映されるので落ち込むこともありますが、そこは反省点として振り返り次のスプリントに臨む、という切っ掛けにもなります。 また先の項目( 6. チームの透明性の確保 )にて触れましたとおり、作業状況の相互理解を進めたいという目的の元に、トラブルの早期発見・早期解決に向けた対策の実施、というメリットが得られました。 もうひとつ触れておきたいと思います。 GitHub Projects, Issue, PR といずれも GitHub で提供されているものなので、これらの連携がスムーズに行えるというのは狙いの一つではありました。 が、 UI/UX が統一された( Issue や PR との UI/UX の乖離がない )点は意図していないメリットでした。 感覚的な話になってしまいますが、UI/UX が統一されたことで、チケット管理の際のストレスが個人的には軽減されたと感じております。 特に意識したこともなかったのですが PR は GitHub, Issue は別のツールで管理 と、見た目や操作感といった部分で大きな違いがあるというのは意外とストレスになるのだということに気付かされました。今後、またツールを選定するようなときには要件として頭の片隅に置いておこうと思った点です。 少し話が逸れました。そろそろまとめに入ります。 繰り返しになりますが、解決したいと思っていた課題は概ね対応できました。 残念な点で挙げたようにちょっと物足りないなという面はあります。ですがそれらは ( 今のところは ) 運用でカバーできていることもあり、取り立てて問題視するほどのものではありません。 スプリントを運用するにあたり、「まず何かツールを使ってみたい」とか「導入コストをかけたくない」といったときに GitHub Projects は良い選択肢となるのでは、と思います。 ZenHub や JIRA といった有料の管理ツールを使用する前の管理ツールの入門的な意図で使ってみるというのも良いのではないでしょうか。 参考 Projects を使用した計画と追跡 ランキング参加中 プログラミング
アバター
初めまして、カケハシのデータ基盤チームでデータエンジニアをしている伊藤と申します。 最近の悩みは、二郎ラーメンを食べていないのに「二郎ラーメンの匂い(臭い?)がする」と同居人に言われることです。私のニュースは置いといて、カケハシでは全社的なデータ活用基盤のプラットフォームとしてDatabricksを採用してから半年以上経過しました。 Databricksを導入してから今まではバッチ処理しかしてませんでしたが、最近になってAutoLoaderを利用してストリーム処理をするようになりました。その対象として、弊社が提供している薬歴システムのMusubiの監査ログを扱ったので紹介させて頂きます。 Au…
アバター
お金は好きですか? コストを削減したいみなさん、ようこそ。 原価を低減したいみなさん、ようこそ。 サーバレスのビジネスでの優位性は多数ありますが、今回はその中でもコスト最適化の面から説明します。 やればやるだけ成果が見える: 努力が必ず報われる物語 レイテンシを1ms削るようなリリースができたとしましょう。(ネットワーク系のコストは同等と仮定します) 従来の開発では、CPUやメモリ利用量が若干減った程度では、コストは変わりませんでした。利用率が100%にならないようバッファを持たせており、多少削ってもサーバーの台数削減につながらないからです。あえてリソースを減らすとしても、利用率100%のリス…
アバター
初めまして、カケハシのデータ基盤チームでデータエンジニアをしている伊藤と申します。 最近の悩みは、二郎ラーメンを食べていないのに「二郎ラーメンの匂い(臭い?)がする」と同居人に言われることです。私のニュースは置いといて、カケハシでは 全社的なデータ活用基盤のプラットフォームとしてDatabricksを採用し てから半年以上経過しました。 Databricksを導入してから今まではバッチ処理しかしてませんでしたが、最近になってAutoLoaderを利用してストリーム処理をするようになりました。その対象として、弊社が提供している薬歴システムのMusubiの監査ログを扱ったので紹介させて頂きます。 AutoLoaderとは Databricks上で特別な設定無しで、クラウドストレージに到着した新しいデータを段階的かつ効率的にDelta Lakeに取り込む仕組みです。AutoLoaderにはディレクトリごとに数百万のファイルにまで拡張できるスケーラビリティの対応や導入が容易で使いやすいといったメリットがあります。 以前、spark streamingでストリーム処理を行い、Delta Lakeに取り込んた際の課題として、読み込み対象のスキーマが想定外に更新されてデータ取得によく失敗していました。AutoLoaderにはスキーマの変更が起きた際に通知を行い、無視するか失われるデータを救助できる仕組みがあるため、その課題を解決できます。 AutoLoaderの動作原理 AutoLoaderでは新規ファイルを検知するために ディレクトリ一覧モード と ファイル通知モード があります。 本記事で紹介する事例では、Kinesis Firehoseを使用してファイルを日付順にアップロードしており、ディレクトリ一覧モードでAutoLoaderを起動し、API呼び出し数を削減しております。 アーキテクチャ Musubiの監査ログはCloudWatchに出力されています。AWSのKinesisを利用してCloudWatchからJSONデータをS3に格納しています。AutoLoaderで監査ログを処理する前は、日次でバッチ処理を行っていたので以下のようなアーキテクチャとなっていました。 最近になって監査ログデータのリアルタイム性が求められてきました。今までのアーキテクチャだとデータ抽出まではリアルタイムになっていたのですが、Databricksに読み込む際にバッチ処理をしていたため、利用者にとってはリアルタイム性に欠けていました。そのためアーキテクチャの見直しを行い、日次バッチからAutoLoaderで処理を行うよう変更しました。 Databricksでは、増分データの取り込みにはDelta Live Tables(DLT)でAutoLoaderを使用することが推奨されていますが、Unity Catalog/DLTの統合は本記事執筆時点の2023年1月31日時点ではサポートされていないため、現時点ではDelta Live Tablesを使用していません。 AutoLoaderでJSONファイルの読み込み 以下を実行することでS3に到着するJSONが順次処理されるストリームが起動します。 df = spark.readStream.format( "cloudFiles" ) \ .option( "cloudFiles.format" , "json" ) \ .option( "cloudFiles.schemaLocation" , "checkpointに使用するpath" ) \ .option( "cloudFiles.schemaHints" , "type string, target string, action string, year string, month string, day string, hour string) \ .option(" cloudFiles.partitionColumns ", " year,month,day,ho ur") \ .option(" cloudFiles.useIncrementalListing ", True) \ .option(" lineSep ", " \n ") \ .load(" 取り込みデータのpath ") .format("cloudFiles") と指定することでAutoLoaderを利用しています。\ .option("cloudFiles.schemaLocation") にチェックポイントディレクトリを指定することで、AutoLoaderがどこまでファイルを処理したか記録しています。\ 予めスキーマ情報がわかっていたので .option("cloudFiles.schemaHints" にカラム名と型を指定することでスキーマ情報を強制させています。\ Kinesisで順次パーティション区切りでストリーミングデータが送られてくるので、 .option("cloudFiles.partitionColumns") に年/月/日/時を指定しています。\ AutoLoaderの動作原理で触れましたがディレクトリ一覧のモードを使用しており、日付順でS3にファイルがアップロードされるため、 .option("cloudFiles.useIncrementalListing", True) を指定して、ディレクトリ一を完全に一覧するのではなく、インクリメンタルな一覧を適用することにより新規ファイルを検知するのに必要なAPI呼び出し数を削減しています。\ .option("lineSep) には二つの連続するJSONレコードの区切り文字として\nを指定しています。 Databricks Unity Catalogに書き込み 以下を実行することで読み込んだストリームをDatabricksのUnity Catalogに書き込みます。 df = spark.writeStream.format( "delta" ) \ .option( "checkpointLocation" , "checkpointに使用するpath" ) \ .outputMode( "append" ) \ .option( "mergeSchema" , "true" ) \ .partitionBy( "year" , "month" , "day" , "hour" ) \ .trigger(processingTime= "1 minutes" ) \ .toTable( "unity catalogに保存するテーブル名" ) .option("mergeSchema", "true") で新規カラムが追加された場合に自動でDeltaに取り込むようオプション指定しています。\ .trigger(processingTime="1 minutes") に1 minutesを指定することで、1分間隔でJSONを順次処理しています。 JSONファイル処理部分で躓いた ストリーミングでデータを処理すると、スキーマの予期しない変更や取り込み対象ファイルのデータが不正といった様々な要因でエラーに遭遇するかと思います。今回AutoLoaderでJSONファイルを処理する際に以下のエラーに遭遇しました。 org.apache.spark.sql.catalyst.util.UnknownFieldException: Encountered unknown field(s) during parsing: {} 上記のエラー原因は、データスキーマ変更が要因でない場合、データの中に空のJSONが存在すること起因の不具合によるものです。対処方法としては、以下2パターンのどちらかになるかと思います。 取り込み対象データの空のJSONを削除する エラー後、AutoLoaderを再実行 今回はAutoLoaderを再実行することで問題なくデータを取り込むことができて、データ利用者に最新の情報を提供することができました。 まとめ 以上、DatabricksのAutoLoaderを紹介させて頂きました。簡単にセットアップができて最新の情報をいち早くデータ利用者に届けられる仕組みは大変素敵なものだと実感しました。 本記事ではDelta Live Tablesについて触れませんでしたが、Unity Catalog/Delta Live Tablesの統合が正式版となった暁には社内でDelta Live Tables(DLT)でAutoLoaderを使用する動きが加速するかと思います。 一緒に手を動かしてデータ基盤構築しませんか。少しでも興味を持ってくださった方がいらっしゃれば以下からご応募お待ちしております。
アバター
KAKEHASHI でバックエンドエンジニアをしている横田です。 私が運用している Web サービスでは、AWS Glue で ETL 処理をしたデータを Aurora MySQL に投入することでユーザーが利用できるようにしています。 その中でも「データを Aurora MySQL に投入する」方法に関して、今まで色々なパターンを試してきました。 AWS Glue の Job で作成したデータを Aurora に投入するいくつかのパターンとそのメリット・デメリットについて紹介できればと思います。 Aurora MySQL にロードする 3 つのパターン データを MySQL に insert…
アバター
はいさい!カケハシの新米メンバー、オースティンと申します。 沖縄から参上しております! 概要 RxJS の mergeMap と switchMap の違いと使い方について解説します。 背景 Observable を使っていると、必ず直面する問題があります。それは、 複数の Observable をどうやって一緒に実行できるか 、という問題です。 とある Observable の処理が終わった後に、そのデータを元に、別の Observable でさらに非同期処理をすることは開発者として多々あります。 Promise の then でまた別の Promise を返してチェーンしていくのと同じことです。 しかし、RxJS では、 Promise と違って Observable を直列にチェーンするのに使う pipe の OperatorFunction が複数存在します。代表的なのは、 mergeMap 、 mergeWith 、および switchMap です。 なぜ RxJS には複数のチェーン手法が存在するのか この疑問は湧きませんか? この疑問に答えるためにはまず、 Observable と Promise はどう異なるのか考える必要があります。 筆者に言わせれば、 Observable の最も大きな違いは、 未解決の Observable の処理を取り消す、つまり止めることが簡単にできる点 でしょう。 Promise では、 Promise が解決してもその中で実行された処理を取り消す方法がありません。以下のコードを見ましょう。 const promise = new Promise((resolve) => { let count = 0; setInterval(() => console.log( `Loading ${++count} seconds.` ), 1000); setTimeout(resolve, 5000); } ); promise.then(() => console.log( "Resolved!!" )); 読者は読んでお分かりかと思いますが、このコードを実行してみると、以下のように、 Promise が解決された後でも、ログが出続けるのです。 同じ例を Observable で書きます。 import { Observable } from "rxjs" ; const observable$ = new Observable((observer) => { let count = 0; const interval = setInterval(() => console.log( `Loading ${++count} seconds.` ), 1000); setTimeout(() => { observer.next( "Timeout ended." ); observer.complete(); } , 5000); return () => clearInterval(interval); } ); observable$.subscribe( { next: console.log, complete: () => console.log( "Completed!" ) } ); Observable の executor 関数が、戻り値としてその interval をクリアする関数を返すのですが、これが Observable が解決(complete)された時に呼ばれるのです。 すると、 Promise と違って永遠に続く interval が残されません。 この 後片付け の機能が、 Observable の非常に優れたところなのです 。 そして、本記事につながる部分ですが、この後片付けがあるからこそ、 Observable をチェーンする時の手法が用途によって異なるのです。 mergeMap とは何か mergeMap は、map と似ていますが、簡単にいうと、一つの Oberservable から流れたデータを違う Observable に流すチェーン OperatorFunction なのです。 以下の例を見てみましょう。 import { fromEvent, scan, mergeMap, interval, takeWhile, tap, combineLatest, of } from "rxjs" ; const docClick$ = fromEvent( document , "click" ); const clickCount$ = docClick$.pipe( scan((acc) => acc + 1, 0), tap((count) => console.log( `Document was clicked ${count} time(s).` )) ); clickCount$ .pipe( mergeMap((clickCount) => { const intervalPerClickCount = [ of(clickCount), interval(500).pipe(takeWhile((i) => i < 5)) ] ; return combineLatest(intervalPerClickCount); } ) ) .subscribe(( [ clickCount, i ] ) => console.log( `mergeMap: Click no ${clickCount} , interval count: ${i} ` )); document をクリックすると、クリックした回数をまず clickCount$ で足し算します。 それから、 mergeMap を使って新しい Observable を返します。 その新しい Observable は、 combineLatest で合わせた二つの Observable 、 of(clickCount) と interval です。 要するに、 mergeMap で Observable<number> を Observable<[number, number]> というふうに変えています。 試してみる document を一回クリックすると以下のように出力されます。 Document was clicked 1 time(s). mergeMap: Click no 1, interval count: 0 mergeMap: Click no 1, interval count: 1 mergeMap: Click no 1, interval count: 2 mergeMap: Click no 1, interval count: 3 mergeMap: Click no 1, interval count: 4 document を 2 回連発でクリックすると以下のようにログが出力されます。 Document was clicked 1 time(s). Document was clicked 2 time(s). mergeMap: Click no 1, interval count: 0 mergeMap: Click no 2, interval count: 0 mergeMap: Click no 1, interval count: 1 mergeMap: Click no 2, interval count: 1 mergeMap: Click no 1, interval count: 2 mergeMap: Click no 2, interval count: 2 mergeMap: Click no 1, interval count: 3 mergeMap: Click no 2, interval count: 3 mergeMap: Click no 1, interval count: 4 mergeMap: Click no 2, interval count: 4 ここで重要な観察ですが、 2 回目のクリックがあっても、1 回目のクリックを元にした combineLatest の Observable<[number, number]> は最後まで続くのだ という結果を記憶に留めておきましょう。 switchMap switchMap は mergeMap と同じように、上流の Observable を新しい Observable に合わせます。 しかし、重要な違いがあります。 switchMap は、上流の最も最新も値をのみとって、下流の新しい Observable に流すのです 。 たとえ、以前の値に基づいて流した下流の Observable がまだ解決されていないとしても、 それらの Observable を止めるのです 。 上記のソースコードで mergeMap が使われていたところを switchMap にしてみましょう。 clickCount$ .pipe( switchMap((clickCount) => { const intervalPerClickCount = [ of(clickCount), interval(500).pipe(takeWhile((i) => i < 5)) ] ; return combineLatest(intervalPerClickCount); } ) ) .subscribe(( [ clickCount, i ] ) => console.log( `switchMap: Click no ${clickCount} , interval count: ${i} ` )); これも実験してみましょう! 試してみる 1 回だけクリックしてみる Document was clicked 1 time(s). switchMap: Click no 1, interval count: 0 switchMap: Click no 1, interval count: 1 switchMap: Click no 1, interval count: 2 switchMap: Click no 1, interval count: 3 switchMap: Click no 1, interval count: 4 mergeMap と同じ結果です。 2 回連発してクリックしてみる Document was clicked 1 time(s). Document was clicked 2 time(s). switchMap: Click no 2, interval count: 0 switchMap: Click no 2, interval count: 1 switchMap: Click no 2, interval count: 2 switchMap: Click no 2, interval count: 3 switchMap: Click no 2, interval count: 4 なるほど!**最後のクリックだけ、5 回の interval が出たのです! まとめ ここまで mergeMap と switchMap の違いを解説してきましたが、いかがでしょうか? 似たような効果があるのに、歴然な違いがあるので、筆者は知った時に驚きました。 この違いを知らずにコードを書いていると、解せないエラーが起きそうな気がします。 たとえば、HTTP リクエストでクリックに対してログを記録させたい時に、どれを使えばいいと思いますか? ビジネスモデルにもよるのですが、 switchMap だと、ログのリクエストがまだ終わっていないのに、ユーザーがまたクリックすると、前のログのリクエストがキャンセルされ、 最後のクリックだけがログに残る結果になるのです 。なので、 mergeMap が向いているでしょう。 逆に、自動推測などだと、最新の値だけに対してリクエストを投げたいはずなので、 mergeMap より switchMap が適しているのではないでしょうか? RxJS は奥深くて強力なのですが、強力だからこそ誤った使い方をすると痛い目に遭うなと思っています。 どんどん理解を深めていきましょう!
アバター
こんにちは!カケハシにて薬局と患者の関係性を向上させるためのツールである 患者リスト というWEB業務アプリケーションを開発している小室と申します。 本プロダクトのフロントエンドの開発環境としては、React + esbuildを採用しており、採用の経緯や実践している環境構築方法などは以下の通り、TechPlayやQiitaなどに記事を投稿してきました。 TechPlay: 新規事業プロダクト開発時の技術選定どうやった? スライド Qiita: esbuild + React(TS) で実現する超軽量な開発環境 しかしながら、esbuildは標準でsassに対応しておらず、今までの環境ではCS…
アバター
こちらの記事は、カケハシ Advent Calendar 2022の25日目の記事になります。 こんにちは、四番隊隊長とは声が低いこと以外何一つ共通点がないCTOの海老原です。 すみません、タイトルは釣りタイトルです。何故こんな釣りをアドベントカレンダーのラストに持ってきたかですが…しばしばスタートアップの経営陣の最大のミッション・役割の一つとして採用が挙げられますが、ご多分に漏れず私も日常の時間のかなり多くの部分をソフトウェアエンジニアやデザイナー、プロダクトマネージャーのような開発系職種のカジュアル面談での事業説明やオファー面談に割いています。 その中でよく候補者の方からご質問を頂くわけで…
アバター