このトピックでは、ApsaraDB RDS for PostgreSQLインスタンスを使用してリアルタイムの精密マーケティングを実装し、ユーザーの特性に基づいて目的のユーザーを識別する方法について説明します。
前提条件
RDSインスタンスが作成されました。 詳細については、「ApsaraDB RDS For PostgreSQLインスタンスの作成」をご参照ください。
説明このトピックでは、リアルタイムの精密マーケティングを実装し、対象ユーザーを特定する3つのソリューションについて説明します。 ソリューション3を使用する場合は、roaringbitmap拡張機能が必要です。RDSインスタンスがPostgreSQL 12以降を実行していることを確認する必要があります。 このトピックでは、PostgreSQL 12を使用します。 roaringbitmap拡張機能の詳細については、「roaringbitmap拡張機能の使用」をご参照ください。
RDSインスタンスにIPアドレスホワイトリストが設定されています。 詳細については、「IPアドレスホワイトリストの設定」をご参照ください。
RDSインスタンスにアカウントが作成されます。 詳細については、「アカウントの作成」をご参照ください。
RDSインスタンスにデータベースが作成されます。 詳細については、「データベースの作成」をご参照ください。
背景情報
インターネット、ゲーム、教育業界など、ほとんどの業界ではリアルタイムの精密マーケティングが必要です。 これらの業界の企業は、システムを使用してユーザープロファイルを生成し、業界固有の条件の任意の組み合わせに基づいて目的のユーザーグループを識別する必要があります。 例:
電子商取引業界では、商人がプロモーションを開始する前に、商人は、彼らの特性に基づいて意図されたユーザのグループを識別し、ユーザを選択し、ユーザに広告をプッシュするか、または意図されたユーザがプロモーションに参加できるかどうかを決定する。
ゲーム業界では、ボーナスパッケージは、アクティブなプレーヤーを増やすために、プレーヤーの特定の特性に基づいて正確に配布されることがよくあります。
教育業界では、学生の学習スキルを向上させるために、学生の特性に基づいてさまざまな調整された演習が推進されています。
オンライン検索、ポータル、およびビデオウェブサイトに関与する企業では、コンテンツは、ユーザーの懸念と最近の行動に基づいてプッシュされます。
これらの業界は、次の問題点を共有しています。
膨大な量のデータは、多数の計算を必要とする。
多数のタグおよびフィールドが存在する。 この場合、多くのストレージリソースが消費される。
データベース内のフィールドの数が上限を超える場合があります。 ほとんどの場合、データベースには最大1,000個のフィールドを含めることができます。
複数のフィールドではなく配列を使用してタグを格納する場合、データベースは一般化逆インデックス (GIN) をサポートする必要があります。 ただし、すべてのデータベースがGINをサポートするわけではありません。
複数のフィールドの代わりに配列を使用してタグを格納し、GINを使用すると、ストレージリソースの消費が劇的に増加します。
選択条件の組み合わせは多様である。 したがって、ユーザ選択を容易にする固定インデックスは利用できない。 この場合、フィールドごとにインデックスを作成すると、ストレージリソースの消費量が大幅に増加します。
リアルタイムマーケティングの迅速な対応には高いパフォーマンスが必要です。
ユーザーを正確に選択するには、ユーザープロファイルの準リアルタイム更新が必要です。 準リアルタイム更新が可能でない場合、ユーザ選択結果は不正確であり得る。 例えば、ユーザが昨日携帯電話を閲覧し、夜遅くに注文したが、プロファイリングデータが更新されない場合、ユーザは携帯電話の販売者として選択される。 ただし、ユーザーは目的のユーザーグループに属していません。
MySQLなどの一般的なデータベースエンジンは、限られたリソースを提供し、リアルタイムのユーザーグループ選択の要件を満たすことができません。
次のPostgreSQLベースのソリューションのいずれかを使用して、リアルタイムの精密マーケティングを実装できます。
解決策
ソリューション 1
このソリューションは、PostgreSQLとMySQLの両方でサポートされています。
概要
スキーマ:
キー: ユーザーID タグ1: タグ2: ... タグN:
インデックス: タグフィールドごとに1つのインデックス
検索方法:
AND、OR、およびNOTの組み合わせ ここでタグaとタグbと...
短所:
多数のタグおよびフィールドが存在する。 この場合、多くのストレージリソースが消費される。
データベース内のフィールドの数が上限を超える場合があります。 ほとんどの場合、データベースには最大1,000個のフィールドを含めることができます。 この問題を解決するには、多対多スキーマを使用して、タグごとに1つのエントリを維持します。
選択条件の組み合わせは多様である。 したがって、ユーザ選択を容易にする固定インデックスは利用できない。 この場合、フィールドごとにインデックスを作成すると、ストレージリソースの消費量が大幅に増加します。
新しいグループタグを追加すると、大量のデータを更新する必要があります。
クエリのパフォーマンスが悪いです。
手順
各エントリがユーザーグループを表すユーザーグループテーブルを作成します。 サンプルコード:
テーブルt_tag_dictを作成する ( タグint primary key, -- ユーザーグループを識別するタグのID。 info text, -- ユーザーグループの説明。 crt_time timestamp -- 時間。 );
100,000ユーザーグループタグを作成します。 サンプルコード:
t_tag_dict値 (1、'Male' 、now()) に挿入します。t_tag_dict値に挿入する (2, 'Female', now()); t_tag_dict値に挿入します (3、「24歳より古い」、今 ()); -... t_tag_dictに挿入する generate_series(4,100000) 、md5(random()::text) 、clock_timestamp();
ユーザーのタグを表すユーザーの各エントリを持つユーザープロファイルテーブルを作成します。 サンプルコード:
テーブルt_user_tagを作成する ( uid int8, -- ユーザID。 tag int, -- ユーザーのタグ。 タグは、ユーザグループを識別する。 mod_time timestamp, -- 時間。 主キー (tag,uid) );
1,000万人の男性ユーザーと1,000万人の女性ユーザーのそれぞれに64のランダムタグを設定します。 合計1.28億のエントリが生成されます。 サンプルコード:
createまたはreplace関数gen_rand_tag(int,int) は、setof intを $$ select case when random() > 0.5 then 1::int else 2::int end as tag union all select ceil(random()*$1):: タグとしてint from generate_series(1,$2); $$ language sql strict volatile; t_user_tagに挿入する select uid, gen_rand_tag(100000,63) as tag, clock_timestamp() generate_series(1,20000000) からuid on conflict (uid,tag) として何もしません。-- タグをインポートするには、次の方法を使用することもできます。シーケンスseqを作成します。vi test.sql t_user_tagに挿入する select uid, gen_rand_tag(100000,63) as tag, clock_timestamp() nextval('seq'::regclass) からuidとして 競合 (tag、uid) で何もしません。pgbench − Mを調製した − n − r − P1 − f。/test.sql -c 50 -j 50 -t 400000
タグ1と3に一致するユーザーを照会します。 サンプルコード:
-- ユーザーグループの数を照会します。 からカウント (*) を選択 ( t_user_tag (tag=1) からuidを選択 交差 t_user_tag (tag=3) からuidを選択 ) t; -時間: 1494.789 ms (00:01.495) -ユーザーグループIDを取得します。 t_user_tag (tag=1) からuidを選択 交差 t_user_tag (tag=3) からuidを選択します。-時間: 3246.184 ms (00:03.246)
タグ1、3、10、または200に一致するユーザーを照会します。 サンプルコード:
-- ユーザーグループの数を照会します。 からカウント (*) を選択 ( t_user_tag (tag=1) からuidを選択 ユニオン t_user_tag (tag=3) からuidを選択 ユニオン t_user_tagからuidを選択します。tag=10 ユニオン t_user_tagからuidを選択します。tag=200 ) t; -時間: 3577.714 ms (00:03.578) -ユーザーグループIDを取得します。 t_user_tag (tag=1) からuidを選択 ユニオン t_user_tag (tag=3) からuidを選択 ユニオン t_user_tagからuidを選択します。tag=10 ユニオン t_user_tagからuidを選択します。tag=200; -時間: 5682.458 ms (00:05.682)
ソリューション 2
ソリューション2はPostgreSQLでのみサポートされています。 MySQLは、配列またはGINをサポートしていません。
概要
スキーマ:
の配列キー: ユーザーID 値: タグ
インデックス:
タグ配列フィールド: GIN
検索方法:
AND、OR、およびNOT ここでVALUES @> 配列 [タグ]-AND where VALUES && array [タグ] -- OR not VALUES @> 配列 [タグ]-NOT
短所:
複数のフィールドの代わりに配列を使用してタグを格納する場合、データベースはGINをサポートする必要があります。 ただし、すべてのデータベースがGINをサポートするわけではありません。
複数のフィールドの代わりに配列を使用してタグを格納し、GINを使用すると、ストレージリソースの消費が劇的に増加します。
新しいグループタグを追加すると、大量のデータを更新する必要があります。
手順
各エントリがユーザーグループを表すユーザーグループテーブルを作成します。 サンプルコード:
テーブルt_tag_dictを作成する ( タグint primary key, -- ユーザーグループを識別するタグのID。 info text, -- ユーザーグループの説明。 crt_time timestamp -- 時間。 );
100,000ユーザーグループタグを作成します。 サンプルコード:
t_tag_dict値 (1, 'Male', now()) に挿入します。t_tag_dict値に挿入する (2, 'Female', now()); t_tag_dict値に挿入します (3、「24歳より古い」、今 ()); -... t_tag_dictに挿入する generate_series(4,100000) 、md5(random()::text) 、clock_timestamp();
各ユーザーの単一のエントリがユーザーのタグの配列を表すユーザープロファイルテーブルを作成します。 サンプルコード:
テーブルt_user_tagsを作成する ( uid int8プライマリ・キー -- ユーザID。 タグint[], -- ユーザーのタグの配列。 タグは、ユーザグループを識別する。 mod_time timestamp -- 時間。 );
ランダムなタグ配列を生成する関数を作成します。 サンプルコード:
関数gen_rand_tags(int、int) を作成または置換すると、int[] が $$として返されます。 generate_series(1,$2) からarray_agg(ceil(random()*$1)::int) を選択します。$$ language sql strict;
100,000のタグから8つのタグをランダムに選択します。 サンプルコード:
gen_rand_tags(100000、8) を選択します。 gen_rand_tags --------------------------------------------------- {43494,46038、74102,25308、99129,40893、33653,29690} (1行)
2,000万人のユーザーにタグを付けます。 各ユーザーには64個のランダムタグがあります。 ユーザーの半分は男性で、残りの半分は女性です。 サンプルコード:
t_user_tagsに挿入 select generate_series(1,10000000), array_append(gen_rand_tags(100000、63) 、1), now(); t_user_tagsに挿入する select generate_series(10000001,20000000), array_append(gen_rand_tags(100000、63) 、2), now();
グループタグフィールドのGINを作成します。 サンプルコード:
gin (タグ) を使用してt_user_tagsにインデックスidx_t_user_tags_1を作成します。
タグ1と3に一致するユーザーを照会します。 サンプルコード:
-- ユーザーグループの数を照会します。 選択count(uid) from t_user_tags where tags @> array[1,3]; -ユーザーグループIDを取得します。 t_user_tagsからuidを選択します。タグ @> 配列 [1,3];
タグ1、3、10、または200に一致するユーザーを照会します。 サンプルコード:
-- ユーザーグループの数を照会します。 t_user_tagsからカウント (uid) を選択します。タグ&配列 [1,3,10,200]; -ユーザーグループIDを取得します。 t_user_tagsからuidを選択し、タグ&配列 [1,3,10,200];
ソリューション 3
ソリューション3はPostgreSQLでのみサポートされています。 MySQLはroaringbitmap拡張をサポートしていません。
ソリューション3では、roaringbitmap拡張機能を使用して高速クエリを実装します。 詳細については、「roaringbitmap拡張機能の使用」をご参照ください。
概要
スキーマ:
KEY: タグID 値: ユーザービットマップ
インデックス:
タグIDフィールド: B-tree index
検索方法:
集計ビットマップ: AND、OR、およびNOT and_agg (ビットマップ) ここでKEY in (タグ) -- AND or_agg (ビットマップ) ここでKEY in (タグ) -- OR (bitmap1、bitmap2) を除く-いいえ
利点:
テーブルは少数のストレージリソースを消費します。
インデックスは少数のストレージリソースを消費します。 インデックスエントリを格納するために必要なBツリーインデックスは1つだけです。 インデックスエントリの数は、タグの数に等しい。 ほとんどの場合、タグの数は百万未満です。
ユーザーグループにタグを追加する場合は、グループビットマップエントリを追加するだけです。 この場合、大量のデータを更新する必要はありません。
クエリのパフォーマンスは優れています。
短所:
ビットマップの最大長は1 GBです。 ユーザー数が最大長を超える場合は、オフセットを使用する必要があります。
offset0_bitmap、offset1gb_bitmap、...
ユーザーIDは数字で構成する必要があり、連続する番号を推奨します。 数値ユーザーIDが存在しない場合は、マッピングテーブルを作成する必要があります。
手順
ユーザーIDの数が40億 (INT4) を超える場合、オフセットを使用してユーザーIDを変換できます。 詳細については、「UIDオーバーフローのトラブルシューティング」をご参照ください。
roaringbitmap拡張機能の使用方法の詳細については、「pg_roaringbitmap」をご参照ください。
roaringbitmap拡張機能をインストールします。 サンプルコード:
拡張機能roaringbitmapを作成します。
ユーザータグを含むビットマップテーブルを作成します。 サンプルコード:
テーブルt_tag_usersを作成する ( tagid int primary key, -- タグIDまたはユーザーグループID。 uid_offset int, -- ユーザーIDをINT8からINT4に変換します。 userbits roaringbitmap, -- ユーザIDのビットマップ。 mod_time timestamp -- 時間。 );
データを挿入して、ユーザーIDタグを含むビットマップテーブルを生成します。 サンプルコード:
t_tag_usersに挿入 からユーザービットとしてtagid、uid_offset、rb_build_agg(uid::int) を選択します。( 選択 tagidとしてunnest (タグ) 、 (uid / (2 ^ 31)::int8) as uid_offset, mod(uid, (2 ^ 31)::int8) as uid t_user_tagsから ) t tagidによるグループ、uid_offset;
タグ1と3に一致するユーザーを照会します。 サンプルコード:
-- ユーザーグループの数を照会します。 select sum(ub) から ( uid_offset、rb_and_cardinality_agg (ユーザービット) をubとして選択 t_tag_usersから where tagid in (1,3) uid_offsetによるグループ ) t; -ユーザーグループIDを取得します。 uid_offset、rb_and_agg (ユーザービット) をubとして選択 t_tag_usersから where tagid in (1,3) グループをuid_offset;
タグ1、3、10、または200に一致するユーザーを照会します。 サンプルコード:
-- ユーザーグループの数を照会します。 select sum(ub) から ( uid_offset、rb_or_cardinality_agg (ユーザービット) をubとして選択 t_tag_usersから where tagid in (1,3,10,200) uid_offsetによるグループ ) t; -ユーザーグループIDを取得します。 uid_offset、rb_or_agg (ユーザービット) をubとして選択 t_tag_usersから where tagid in (1,3,10,200) グループをuid_offset;
ソリューションの比較
項目 | 解決策1 (MySQLおよびPostgreSQL) | 解決策2 (PostgreSQL) | 解決策3 (PostgreSQL) | ソリューション1に対するソリューション3の利点 |
ANDクエリのユーザー選択速度 | 1.5秒 | 0.042秒 | 0.0015秒 | 99900% |
ORクエリのユーザー選択速度 | 3.6秒 | 3秒 | 0.0017秒 | 211665% |
テーブルが使用するストレージ | 63,488 MB | 3,126 MB | 1390MB | 4467% |
インデックスが使用するストレージ | 62,464 MB | 3139 MB | 2 MB | 3123100% |
インデックス作成速度 | N/A | 20分 | 非常に速い (約0秒) | N/A |
MySQL 8.0とPostgreSQL 12を実行するRDSインスタンスは、上記のソリューションで使用されます。 これらのインスタンスには、8つのCPUコアと32 GBのメモリがあり、1,500 GBの拡張SSD (ESSD) を使用しています。
概要
PostgreSQL 12以降を実行するRDSインスタンスは、roaringbitmap拡張機能をサポートしています。 この拡張機能を使用すると、効率的な方法でビットマップデータを生成、圧縮、または解析できます。 この拡張機能は、AND、OR、NOT、XORなど、最も一般的なビットマップ集計操作もサポートします。 数千万のタグが追加された数億人のユーザー間のリアルタイム精密マーケティングの要件を満たしています。
MySQLベースのソリューションと比較して、PostgreSQLベースのソリューションはより費用対効果が高く効率的です。