Klaviyo と Snowflake を接続する
アドバンストKDPはKlaviyoの標準マーケティングアプリケーションには含まれておらず、関連機能にアクセスするにはサブスクリプションが必要です。このプランの購入方法については、請求ガイドをご覧ください。
この記事では「table」という用語を使用しますが、ビュー、マテリアライズド ビュー、テーブルはいずれもインポート可能な有効な Snowflake オブジェクトです。Klaviyo がオブジェクトに対して SELECT col1 FROM table_name を実行できる限り、好みの方法を自由に使用できます。
このドキュメント内のキーワード "MUST"、"MUST NOT"、"REQUIRED"、"SHALL"、"SHALL NOT"、"SHOULD"、"SHOULD NOT"、"RECOMMENDED"、 "MAY"、および "OPTIONAL" は、RFC 2119 で説明されているとおりに解釈されます。
Snowflake 管理者のセットアップ
このセクションでは、Klaviyo がデータをインポートできるようにするために Snowflake 環境で実行する必要がある手順について説明します。
- ローカルのターミナルで次のコマンドを実行して、秘密鍵を生成します:
openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out rsa_key.p8 -nocrypt- ターミナルで次のコマンドを実行して、秘密鍵を参照する公開鍵を生成します。
openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub- rsa_key.pub をコピーして、以下のスクリプトに貼り付け、user_rsa_public_key のプレースホルダー値 ‘GENERATE_PUBLIC_KEY’ を置き換えます。以下のスクリプトは Mac ユーザー向けに動作します。必要に応じて、IDE で rsa_key.pub を開き、ファイルの内容全体をコピーすることもできます。
# Mac terminal command to write the key to your terminal and copy it to the clipboard
cat rsa_key.pub | tee /dev/tty | pbcopy- Snowflake 環境で次のスクリプトを実行し、Klaviyo が使用するサービス ユーザーを作成してください。以下のセットアップを完了するには、securityadmin と sysadmin の権限が必要です。保有しているロールを確認するには、SHOW GRANTS TO USER <your_username> を実行し、両方のロールが一覧に表示されていることを確認してください。役割の調整が必要な場合は、システム管理者にお問い合わせください。
- スクリプトの冒頭で設定されている変数は、自由に更新できます。
- 要約すると、次のことができます:
- 既存のウェアハウスを選択するか、新しいウェアハウスを作成します
- 既存のデータベースを選択するか、新しいスキーマを保持するための新しいデータベースを作成します
- 新しいスキーマを 2 つ作成します
KLAVIYO_TMP。そしてKLAVIYO_IMPORT_FROM_DWH - 新しいネットワーク ポリシーを作成し、Klaviyo IP を許可リストに追加する
- Klaviyo 用のユーザーとロールを作成する
- このスクリプトはべき等(複数回安全に実行可能)ですが、名前が競合する既存のオブジェクトは上書きしません。
BEGIN;
-- create variables for user / password / role / warehouse / database.
-- Change these to whatever you prefer.
SET role_name = 'KLAVIYO_DATA_TRANSFER_ROLE'; -- all letters must be uppercase, ex. 'KLAVIYO_DATA_TRANSFER_ROLE'
SET user_name = 'KLAVIYO_DATA_TRANSFER_USER'; -- all letters must be uppercase, ex. 'KLAVIYO_DATA_TRANSFER_USER'
SET warehouse_name = 'KLAVIYO_DATA_TRANSFER_WAREHOUSE'; -- all letters must be uppercase, ex. 'KLAVIYO_DATA_TRANSFER_WAREHOUSE'
SET database_name = 'KLAVIYO_DATABASE'; -- all letters must be uppercase, ex. 'KLAVIYO_DATABASE'. If this database doesn't exist, a new one will be created.
SET network_policy = 'KLAVIYO_DATA_TRANSFER_NETWORK_POLICY'; -- all letters must be uppercase, ex. 'KLAVIYO_NETWORK_POLICY'
SET network_rule = 'KLAVIYO_DATA_TRANSFER_NETWORK_RULE'; -- all letters must be uppercase, ex. 'KLAVIYO_NETWORK_RULE'
/* replace GENERATE_PUBLIC_KEY below with generated public key */
-- DO NOT CHANGE
SET schema_name_tmp = $database_name || '.KLAVIYO_TMP'; -- DO NOT CHANGE
SET schema_name_import = $database_name || '.KLAVIYO_IMPORT_FROM_DWH'; -- DO NOT CHANGE
SET full_network_rule_tmp = $schema_name_tmp || '.' || $network_rule; -- DO NOT CHANGE
SET full_network_rule_import = $schema_name_import || '.' || $network_rule; -- DO NOT CHANGE
-- change role to sysadmin for warehouse / database steps
USE ROLE sysadmin;
-- create a warehouse for data transfer service
CREATE WAREHOUSE IF NOT EXISTS IDENTIFIER($warehouse_name)
warehouse_size = xsmall
warehouse_type = standard
auto_suspend = 60
auto_resume = true
initially_suspended = true;
-- create database for data transfer service
CREATE DATABASE IF NOT EXISTS IDENTIFIER($database_name);
-- create schemas for data transfer service
CREATE SCHEMA IF NOT EXISTS IDENTIFIER($schema_name_tmp);
CREATE SCHEMA IF NOT EXISTS IDENTIFIER($schema_name_import);
-- change role to securityadmin for user / role steps
USE ROLE securityadmin;
-- create network rule and policy for database
GRANT USAGE ON DATABASE IDENTIFIER($database_name) TO ROLE securityadmin;
GRANT USAGE, CREATE NETWORK RULE ON SCHEMA IDENTIFIER($schema_name_tmp) TO ROLE securityadmin;
GRANT USAGE, CREATE NETWORK RULE ON SCHEMA IDENTIFIER($schema_name_import) TO ROLE securityadmin;
-- whitelist klaviyo ip ranges, for KLAVIYO_TMP schema
CREATE NETWORK RULE IF NOT EXISTS IDENTIFIER($full_network_rule_tmp)
type = IPV4
value_list = (
'184.72.183.187/32', '52.206.71.52/32', '3.227.146.32/32', '44.198.39.11/32', '35.172.58.121/32', '3.228.37.244/32', '54.88.219.8/32', '3.214.211.176/32'
)
comment = 'Klaviyo IP Ranges as of April 2025';
CREATE NETWORK POLICY IF NOT EXISTS IDENTIFIER($network_policy)
allowed_network_rule_list = ($full_network_rule_tmp);
-- whitelist klaviyo ip ranges, for KLAVIYO_IMPORT_FROM_DWH schema
CREATE NETWORK RULE IF NOT EXISTS IDENTIFIER($full_network_rule_import)
type = IPV4
value_list = (
'184.72.183.187/32', '52.206.71.52/32', '3.227.146.32/32', '44.198.39.11/32', '35.172.58.121/32', '3.228.37.244/32', '54.88.219.8/32', '3.214.211.176/32'
)
comment = 'Klaviyo IP Ranges as of April 2025';
CREATE NETWORK POLICY IF NOT EXISTS IDENTIFIER($network_policy)
allowed_network_rule_list = ($full_network_rule_import);
-- create role for data transfer service
CREATE ROLE IF NOT EXISTS IDENTIFIER($role_name);
GRANT ROLE IDENTIFIER($role_name) TO ROLE sysadmin;
-- create a user for data transfer service
CREATE USER IF NOT EXISTS IDENTIFIER($user_name)
type = SERVICE
network_policy = $network_policy
default_role = $role_name
default_warehouse = $warehouse_name
rsa_public_key = 'GENERATE_PUBLIC_KEY';
GRANT ROLE IDENTIFIER($role_name) TO USER IDENTIFIER($user_name);
ALTER USER IDENTIFIER($user_name) SET NETWORK_POLICY = $network_policy;
-- grant service role access to warehouse
GRANT USAGE
ON WAREHOUSE IDENTIFIER($warehouse_name)
TO ROLE IDENTIFIER($role_name);
-- grant service access to database
GRANT MONITOR, USAGE
ON DATABASE IDENTIFIER($database_name)
TO ROLE IDENTIFIER($role_name);
-- Grant privileges for KLAVIYO_TMP
GRANT USAGE ON SCHEMA IDENTIFIER($schema_name_tmp) TO ROLE IDENTIFIER($role_name);
GRANT MONITOR, USAGE, CREATE TABLE, CREATE VIEW, CREATE SEQUENCE, CREATE FUNCTION, CREATE PROCEDURE
ON SCHEMA IDENTIFIER($schema_name_tmp)
TO ROLE IDENTIFIER($role_name);
GRANT ALL ON FUTURE TABLES IN SCHEMA IDENTIFIER($schema_name_tmp) TO ROLE IDENTIFIER($role_name);
-- Grant privileges for KLAVIYO_IMPORT_FROM_DWH
GRANT USAGE ON SCHEMA IDENTIFIER($schema_name_import) TO ROLE IDENTIFIER($role_name);
GRANT SELECT
ON FUTURE TABLES
IN SCHEMA IDENTIFIER($schema_name_import)
TO ROLE IDENTIFIER($role_name);
COMMIT;Snowflake データ設定
上記で、2 つの新しいスキーマを作成しました。
- KLAVIYO_TMP は Klaviyo によってのみ使用されます。このスキーマで作成されたテーブルは絶対に変更しないでください。Klaviyo は、これらのテーブルが不要になった時点で削除します。
- KLAVIYO_IMPORT_FROM_DWH は、Klaviyo にインポートさせる最終テーブルを保存する場所です。同期の作成プロセスを進めると、このスキーマ内のすべてのテーブルが選択用に一覧表示されます。そのため、セットアップ中の混乱を避けるために、インポートしたい最終テーブルのみを保存するようにしてください。
Klaviyo にインポートする予定のすべてのテーブルは、次の基準を満たす必要があります。
タイムスタンプ要件
- テーブルには、行が作成または更新された日時を示すタイムスタンプ フィールドが必須です。多くの場合、inserted_at または updated_at が挿入されます。同期作成プロセス中に、各テーブルに対してこれを設定します。
- タイムスタンプ フィールドは単調増加でなければなりません(つまり、常に大きくなるか同じであり、小さくなることはありません)。
- 同期の作成後、行の timestamp 値を過去の時刻に設定してはいけません。過去の時刻に設定すると、Klaviyo がその行を検出しない可能性があります。
- 上記の要件を満たしている限り、この特定フィールドのタイムゾーンは Klaviyo にとって重要ではありません。
- タイムスタンプは UTC である必要があります。もしくは、タイムゾーン情報を含めてください。タイムゾーン情報がない場合、Klaviyo は UTC を想定します。カスタムプロパティの場合、これらのタイムスタンプは文字列形式のままとなるため、任意のタイムゾーンで解釈できます。
- タイムスタンプ フィールドは、行が挿入された時刻を反映している必要があり、現在の日付付近に集中している必要があります。Klaviyo は、テーブル内で最も古いタイムスタンプ値から開始して、1 時間のウィンドウをスキャンすることでデータを同期します。タイムスタンプがかなり過去の単一行(例: 他はすべて最近なのに 2023 年のレコードがある場合)があると、Klaviyo は同期サイクルのたびに、その日付以降のすべての 1 時間ウィンドウを反復処理することになります。これは現在の制限であり、今後のリリースで解決される予定です。
- 1 時間のタイムスタンプ ウィンドウあたりの行密度を考慮してください。データは 1 時間のタイムスタンプ ウィンドウ単位でバッチ読み込みされるため、同じ 1 時間ウィンドウに何百万件ものレコードがあると、同期が遅くなったり停止したりする可能性があります。行密度の上限は各行のデータ量によって異なりますが、目安としては 1 時間のタイムスタンプ ウィンドウあたり 100,000 行を念頭に置いてください。
- Klaviyo では、同期対象のテーブルに行を追加するたびに、CURRENT_TIMESTAMP() または同等の関数でタイムスタンプフィールドを設定することを推奨します。複数の行で同じタイムスタンプを設定できます。以下の例を参照してください。
INSERT INTO table_name AS
SELECT ...
, CURRENT_TIMESTAMP() AS inserted_at
... テーブル構造
- テーブルは追記専用(別名、挿入専用)として扱う必要があります。
- 代わりに行をその場で更新する場合は、Klaviyo が変更を識別できるように、タイムスタンプフィールドを必ず更新する必要があります。
- テーブルは、タイムスタンプ列で並べ替える必要があります。Snowflake は、挿入順に基づいてクラスタリングとパーティショニングを処理します。これにより、Klaviyo のインポートクエリの最適化に役立ち、Snowflake でのコンピューティングコストを抑えられます
プロファイルの一意性と一貫性
- 各プロファイルプロパティが 1 つのデータソース(テーブル)のみからインポートされるように MUST 確認してください。Klaviyo では同期作成時に、異なるテーブルから同じプロパティを選択できないようにしており、この要件を簡素化しています。
- 重複したプロファイルが作成されるリスクを最小限に抑えるため、すべてのインポート テーブルで同じプロファイル識別子(メール、電話番号、外部 ID など)を使用する必要があります。
- 指定したプロファイル識別子が Klaviyo 内の既存のプロファイルと一致しない場合、Klaviyo は新しいプロファイルを作成します。
- 例:Table1(Email、fav_color)+ Table2(Phone、birthday)
- プロファイルが現在存在しない場合、同じ人物に対して 2 つのプロファイルが作成される可能性があります。プロファイルが存在する場合、Klaviyo はプロファイルの解決と更新を内部的に処理します。
- この問題を回避する 1 つの方法は、すべてのプロファイルに単一のインポートテーブルのみを使用することです。
循環インポート/エクスポート ループの防止
- インポート機能とエクスポート機能の両方が使用されるシナリオは、循環するインポート・エクスポートループを防ぐために慎重に管理する必要があります。Klaviyo は現在このシナリオを検出できないため、エクスポートプロセスで、インポートテーブルの上流にあるテーブルへデータを戻さないようにしてください。
- Klaviyo には、まだこのシナリオを検出するロジックがありません。
- 次のようになります。
- 各エクスポート同期サイクルで、Klaviyo はすべてのプロファイルをエクスポートします。
- 次に、何らかの一連の変換を通じて、エクスポートしたプロファイルをすべてインポートテーブルに追加します。
- 各インポート同期サイクルで、Klaviyo はインポート テーブル内のすべてのプロファイルを読み取り、最終的にそれらは再エクスポートされます。
- これが安全である可能性が高いシナリオ
- インポートテーブルに追加される行を制限するためだけにエクスポートテーブルを使用している場合
- エクスポート テーブルがインポート テーブルに行を追加しないことを確認した場合。
- 循環インポート/エクスポートループの影響は何ですか?
- その結果、ユーザーと Klaviyo の双方にとって不要なコンピュート コストが発生します。
トラブルシューティング
同期が停止しているように見える
同期は実行中なのに数時間経っても Klaviyo にデータが表示されない場合、または同期の完了に通常より非常に時間がかかる場合、最も可能性が高い原因は、過去の日時のタイムスタンプ値です:
- テーブル内で、タイムスタンプが他のデータよりも大幅に古い行がないか確認します(例:他は過去 1 週間のデータなのに、1 行だけ 2023 年のデータがある)。外れ値の行が 1 つあるだけでも、Klaviyo は最新のデータに到達する前に、空の 1 時間ウィンドウを数千回反復処理せざるを得なくなります。
- 修復:同期を有効化または再有効化する前に、過去のタイムスタンプが大きく古い行を更新または削除するか、最近の値に設定してください。バックフィルの場合は、Klaviyo がスキャンする必要がある 1 時間のウィンドウ数を最小限に抑えるため、すべての履歴行を同じ最近のタイムスタンプ(例:現在のジョブ実行時刻)に設定してください。行が約 100,000 行を超える場合は、タイムスタンプを約 100,000 行ずつのバッチで、(少なくとも)61 分間隔で設定してください。
推奨される Snowflake クラスタリング キーの設定
Snowflake テーブルをタイムスタンプ列でクラスタリングすると、Klaviyo のインポート クエリで不要なマイクロパーティションをスキップでき、同期時間と Snowflake のコンピューティング コストの両方を削減できます:
ALTER TABLE your_database.KLAVIYO_IMPORT_FROM_DWH.your_table CLUSTER BY (your_timestamp_column);