広告事例|10億データ、クエリ10秒未満、OLAPベースの広告システム構築の正しい姿勢について

トラフィックの配当が徐々に薄れていく中、ますます多くの広告会社や広告実務家が、これまでのトラフィックをフルに使って大規模な広告攻撃を行うやり方に代わって、洗練されたマーケティングの新たな道を模索し始めています。洗練されたマーケティングとは、何億人もの人々の中から最も潜在的なターゲット ユーザーを選択することを意味します。これは間違いなく、基本的なエンジン サポートを提供するデータ ウェアハウス機能に大きな技術的課題をもたらします。

背景

群衆選択分析は、顧客ポートレート プラットフォーム (CDP) の中核機能です。アナリストはさまざまなラベルの組み合わせを使用して最適な人々のグループを選択し、広告をプッシュして正確な配信効果を実現します。同時に、タグの組み合わせが異なるとクラウド クエリの結果セットのサイズが異なるため、アナリストは 1 回の広告配信で「最適な」クラウド パッケージを取得するために複数のロジック調整を行う必要があります。このような高頻度の操作では、ポートレート プラットフォームは通常、次の 2 つの問題に遭遇します。

  • まず、このようなクエリ分析は一時的なものであり、さまざまなラベルの組み合わせの数が膨大であるため、オフラインの事前計算ではこの種の柔軟性を満たすことができません。

  • 第 2 に、このタイプのクエリはリアルタイム シナリオであるため、クエリのパフォーマンスが非常に重要になります。通常、クエリは数分のレベルであり、時間がかかり、アナリストのニーズを満たすことができません。

この記事では、リアルタイム分析 OLAP シナリオにおけるクラウド サークル選択クエリのソリューションを共有し、ByteHouse を使用してそのようなクエリを高速化する方法を紹介します。データ パフォーマンスの観点から見ると、10 億ユーザーのテスト データの下で、ByteHouse のクラウド クエリ P99 は 10 秒未満であり、優れたパフォーマンスを示しています。

シーンモデル

グループ選択をサポートするデータ構造はおおよそ次のとおりです。

a50d561e686d9cf25ddcd212b379ae21.png

ユーザー登録情報はユーザー フローを通じてデータ レイクに入力され、ユーザーの行動情報はイベント フローを通じてデータ レイクに入力されます。次に、ラベル作成タスクを通じて、各ユーザーにラベルを付けます。

インスタント クエリのリアルタイム性と柔軟性により、変換されたデータは通常、ByteHouse などの OLAP エンジンに書き込まれ、柔軟でリアルタイムの SQL クエリが提供されます。ユーザーが分析するときは、通常、ポートレート プラットフォームのアプリケーション インターフェイスからラベル ロジックを視覚化して構築します。その後、プラットフォーム アプリケーションがこれらのロジックを SQL に変換し、処理のために ByteHouse に送信します。

データ モデルの観点から見ると、データ ウェアハウスまたはデータ レイクに保存されている形式のほとんどは ID タグに基づいています。次に例を示します。

ユーザーID セックス タグ
10001 F 20 []
10002 M 22 [day_1、day_2]
10003 F 23 [タグ_1]
10004 M 24 [タグ_2]
10005 F 25 [day_1、day_2]

群衆分析では、たとえば次のタグベースのパターンがより適切です。

タグ アクティブなユーザー
タグ_1 [10002、10003、10005]
タグ_2 [10002、10005]

通常、データはユーザーを主体として保存されるため、ユーザー数が非常に多くなり、不要なフィールドが多くなってしまいます。次に、ユーザーがタグ (タグ) を組み合わせて群衆をフィルタリングする場合、ほぼすべての行をスキャンする必要があり、タグとユーザーの増加に応じてパフォーマンスのオーバーヘッドが増加します。

データがラベルを本体とする場合、比較的大きな変更が 2 つあります。

  • まず、群衆に関連する次元のみが保持され、性別、年齢などの他の情報は削除されます。

  • 次に、active_users はすべてのユーザー ID を配列形式で保存します。この操作の重要な利点は、行数とデータ サイズが削減されることです。

このモデルでは、タグの組み合わせに応じてユーザーを選択することがセットの交差および補完操作となり、最初のモデルと比較してパフォーマンスが大幅に向上します。

ByteHouse ビットマップ タイプ

2 番目のストレージ モデルでは、次の ByteHouse SQL を使用してテーブルを作成できます。

CREATE TABLE id_tags (
    tags            String,
    active_users    Array<UInt64>
) Engine = CnchMergeTree() order by tags

tag_1 と tag_2 の両方を満たす人の数を見つけるなどの群衆選択クエリは、次の SQL で実行できます。

WITH (SELECT active_users as tag_1
        FROM id_tags
        WHERE tags = 'tag_1') as tag_1_user,
WITH(SELECT active_users as tag_2
        FROM id_tags
        WHERE tags = 'tag_2') as tag_2_user,
SELECT length(arrayIntersect(tag_1_user, tag_2_user))

このモデルでは一部の操作を簡素化できますが、各タグの選択にはサブクエリ (部分付き) が必要です。この方法ではテーブル スキャンの無駄が多く、タグの数と直線的に関係します。

この問題を解決するために、ByteHouse にはビットを直接使用してタグが存在できるかどうかを示すことができる組み込み BitMap タイプがあります。

上記の例に従って、BitMap を使用した後、テーブル作成ステートメントは次のように変更されます。

CREATE TABLE id_tags (
    tags            String,
    active_users    BitMap64
) Engine = CnchMergeTree() order by tags

ここで、active_users のタイプを Array から BitMap64 に変更しただけで、残りは変更されていないことに注意してください。

「tag_1 と tag_2 の両方を満たす人の数を見つける」という同じクエリの場合、次のクエリを使用します。

SELECT bitmapCount('tag_1&tag_2')
FROM tag_uids_map

元の配列の代わりにビットを使用することで、クエリを 1 回のテーブル スキャンで実行できるように最適化できます。

ByteDance の内部オンライン シナリオに基づくと、上記のクエリ最適化により、マルチラベル シナリオでパフォーマンスが 10 ~ 50 倍向上する可能性があることがわかりました。

データインポート

ビットマップ テーブルと通常のテーブルにデータを書き込むことには大きな違いはありません。たとえば、小規模バッチ挿入の方法は次のように使用できます。

INSERT INTO TABLE id_tags values ('tag_1', [2,4,6]),('tag_2', [1,3,5])

id_tagsのactive_usersはBitMap64のタイプとして定義されているため、配列値[1,3,5]、[2,4,6]は自動的にBitMap64に変換されます。後続の計算と保存は BitMap64 タイプになります。

大量のファイルをインポートする場合は、ByteHouse が提供するインポート サービスを使用できます。現在、オフライン (TOS、LASFS) とリアルタイム (Kafka) の両方のインポート モードが BitMap データのインポートをサポートしています。ストリーム書き込み(Flink直接書き込みなど)は、JDBCインターフェースを介した挿入形式で記述できます。

関連機能

ByteHouse には、交差演算と補数演算のための BitMap 型データのサポートに加えて、bitmapColumnAndビットマップ列を受け取り、and列内のすべてのビットマップに対して演算を実行し、bitmapColumnCardinalityすべての要素の数を返すなど、多数の組み込み列関数もあります。列内のビットマップ。詳細については公式ドキュメントを参照してください。

BitEngine の原理の紹介

ビットマップ構造解析

ユーザー ID が 32 ビットの符号なし整数で表されると仮定すると、従来のビット ストレージを使用するには、2^32 ビット ~ 512MB のスペースが必要になります。1ラベルあたり512MBの容量が必要となると、ラベル数が増えると記憶容量が膨大になってしまいます。実際、2^32 約 40 億人のユーザーに遭遇する企業はほとんどないため、実際のシナリオにおけるユーザー ID の分布は非常にまばらです。

この機能に基づいて、Roaring ビットマップを使用してこの空間をさらに圧縮できます。以下に示すように:

57ba51ac41ce7effe4ed27178a8a70d2.png

32 ビット Roaring ビットマップでは、最初の 16 ビットがバケット化に使用されます。この値の範囲にデータがない場合、バケットは作成されず、最後の 16 ビットは対応するコンテナに格納されます。コンテナには次の 2 種類があります。

  • 配列コンテナー: データ量が少ない場合 (通常、容量が 8K 未満)、より多くのスペースを節約できます。

  • ビットマップコンテナは高密度データの保存に適しており、占有スペースがほとんどありません

計算するときは、いくつかのバケットの値を計算するだけで済みます。64 ビットの Roaringbitmap に拡張する場合、map<uint32_t, Roaring> を使用してサポートできます。最初の 32 ビットはマップのキーとして使用され、最後の 32 ビットは Roaringbitmap に格納されます。

辞書の最適化

ほとんどのシナリオでは、上記の轟音ビットマップはすでに優れたパフォーマンスを備えています。しかし、バイトの実際の場面では、user_id が継続的に生成されないため、配列コンテナーの数の割合が非常に高くなることがわかりました。2 つの疎な母集団の交差および補数演算は、2 つの順序配列の計算になりますが、単純なビット計算と比較すると、この計算でもパフォーマンスに明らかな違いがあります。

したがって、ByteHouse では、データをより集中化するために辞書を通じてデータをエンコードします。

辞書の最適化を有効にする方法は次のとおりです。

CREATE TABLE id_tags (
    tags            String,
    active_users    BitMap64 BitEngineEncode
) Engine = CnchMergeTree() order by tags

本質的に、ディクショナリ サービスはオントロジー マッピングです。キーを使用して値を検索することも、値を使用してキーを検索することもできます。ここで、キーは元の値であり、値はエンコードされた値です。エンコードを有効にすると、ByteHouse は辞書ファイルに依存します。デフォルトでは、ByteHouse は内部で辞書ファイルを維持します。

一番下のテーブルが更新されると、内部辞書ファイルも非同期に更新されます。ByteHouse は、ユーザーによる外部辞書の保守もサポートしていますが、ここでは拡張されません。

要約する

群集分析はポートレート プラットフォームの基本機能です。この記事では、ByteHouse の組み込み BitMap タイプを使用して、リアルタイムのポートレート クエリと分析をサポートする方法を紹介します。現在、ByteHouse Cloud Data Warehouse と Enterprise Edition が Volcano Engine に搭載されています。今後もVolcano EngineはByteDanceとByteHouseを通じて外部のベストプラクティスを顧客に提供し続け、複雑で変化するビジネスニーズと高速成長するデータシナリオに対応するためのインタラクティブなビッグデータ分析プラットフォームを構築していきます。

おすすめ

転載: blog.csdn.net/ByteDanceTech/article/details/132242022