4

JPAを使用して、GAEの100万ファンアウトの問題を理解しようとしています。私が物事を正しく理解していれば、Twitter のようなものには次のエンティティが必要です (ほんの一例)。

public User {
    @Id Key id;
    String name;
    String displayName;
    List<Key> subscribers;  // users
}

public Tweet {
    @Id Key id;
    User tweetMaker;
    String message;
}

public TweetIndex {
    @Id Key id;
    Key tweetMaker;        // user
    List<Key> subscribers; // users
}

ツイートが作成されると、Tweet オブジェクトが保存され、tweetMaker がツイートを作成したユーザーである TweetIndex が保存され、サブスクライバーが User オブジェクトから TweetIndex にコピーされます。次に、TweetIndex のサブスクライバーに対してクエリを実行して、特定のサブスクライバーのメッセージを取得します。

  1. それで、その権利はありますか?私にとって物事があいまいになるのは、サブスクライバーが多値プロパティに格納されることを期待していることです。多値プロパティは 5000 エントリしか持てないため、TweetIndex は 5000 購読者 ID ごとに繰り返す必要があると思います。
  2. 多値プロパティを 5000 個のグループに分割するのは、どのような制御ですか? コードで繰り返し保存を管理する必要がありますか?
  3. そして、購読者の元のリストをどのように保存しますか? User オブジェクトのサブスクライバー リストも同じ 5000 制限に制限されるように思えます。

回答/洞察/提案をありがとう!

4

1 に答える 1

1

1) それで、その権利はありますか? -> インデックス作成時の複数値プロパティ リストのサイズの種類は約 20K に制限されます (サブスクライバー ID に対してクエリを実行するため、これはあなたのケースです) Google App Engine データストアの ListProperty の最大サイズ/制限は何ですか? 要約すると、このようなユース ケースで直面する制限は次のとおりです。

2) 内訳は手動で処理する必要があります。それを行う永続化フレームワークを私は知らないからです。Objectify は、この種の機能を備えた GAE データストアに特化した唯一の永続化フレームワークです。私はそれを使用していませんが、IDK.

3) GAE データストアでユースケースをモデル化することを強いる制約を明確に理解する必要があります。あなたはまだリレーショナル データベース モデリングの影響を強く受けているように思えます。

何百万ものユーザーを対象に計画しているため、スケールとパフォーマンスを考慮してアプリを構築しています。これらの「結合」はまさに避けるべきものです。そのため、そもそも RDBMS を使用していません。ポイントは次のとおりです。データがユースケースに一致するように非正規化します。

public class UserEntity {

    @Id Key id;
    String name;

    /** INDEXED : to retrieve a user by display name */
    String displayName;

    /** For the sake of the example below */
    int tweetCount;

    /** 
     * USE CASE : See a user's followers from his "profile" page.
     *
     * Easily get subscribers data from your user entity.
     * Duplicate UserEntity (this object) 's data in the UserSubscriberEntity.
     * You just need to run an ancestor query on UserSubscriberEntity using the User id.
     */
    List<UserSubscriberChildEntity> subscribers;

}

/** Duplicate user data in this entity, retrieved easily with an ancestor query */
public class UserSubscriberChildEntity {
    /** The id of this entity */
    @Id Key subscriberId;
    /** Duplicate your User Entity data */
    String name;
    String displayName;
    /** The id from the UserEntity referenced */
    String userId;
}

public class TweetEntity {
    @Id Key id;

    /**
     * The actual text message
     */
    String tweetContent;

    /**
     * USE CASE : display the tweet maker name alongside the tweet content.
     *
     * Duplicate user data to prevent an expensive join when not needed.
     * You will always need to display this along with the tweet content !
     * Model your entity based on what you want to see when you display them
     */
    String tweetMakerName;
    String tweetMakerDisplayName;
    /**
     * USE CASE 
     * 1) to retrieve tweets MADE by a given user
     * 2) In case you actually need to access the User entity
     *    (for example, if you remove this tweet and want to decrease the user tweet counter)
     *
     * INDEXED 
     */
    Key tweetMakerId;

    /**
     * USE CASE : display tweet subscribers from the "tweet page"
     * 
     * Same as "UserSubscriberChildEntity", retrieve data fast by duplicating
     */
    List<TweetSubscriberChildEntity> subscribers;
}

ここで重要な質問: 「1 人のユーザーが購読しているすべてのツイート」をどのように取得しますか?

エンティティ間でサブスクリプションを分割する:

/**
 * USE CASE : Retrieve tweets one user subscribed to
 *
 * Same goes for User subscription
 */
public class TweetSubscriptionShardedEntity {
    /** unused */
    @Id Key shardKey;
    /** INDEXED : Tweet reference */
    Key tweetId;
    /** INDEXED : Users reference */
    List<Key> userKeys;
    /** INDEXED : subscriber count, to retrieve shards that are actually under the limitation of 20K */
    int subscribersCount = 0;

    /**
     * Add a subscriber and increment the subscriberCount
     */ 
    public void addSubscriber(Key userId) {
        userKeys.add(userId);
        subscribersCount++;
    }
}

すべてを結び付けるツイート サービスの例 :

/**
 * Pseudo code
 */
public class TweetService {

    public List<TweetEntity> getTweetsSubscribed(Key userId) {
        List<TweetEntity> tweetsFollowed = new ArrayList<TweetEntity>;
        // Get all the subscriptions from a user
        List<TweetSubscriberShardedEntity> shards = datastoreService.find("from TweetSubscriberShardedEntity  where userKeys contains (userId)");
        // Iterate over each subscription to retrieve the complete Tweet
        for (TweetSubscriberShardedEntity shard : shards) {
            TweetEntity tweet = datastoreService.get(TweetEntity.class, shard.getTweetId);
            tweetsFollowed.add(tweet);
        }
        return tweetsFollowed;
    }

    public void subscribeToTweet(Key subscriberId, Key tweetId) {
        TweetSubscriberShardedEntity shardToUse = null;
        // Only get the first shard with under 20000 subscribers 
        TweetSubscriberShardedEntity shardNotFull = datastoreService.find("
        FROM TweetSubscriberShardedEntity  
        WHERE tweetId == tweetId 
        AND userKeys contains (subscriberId)
        AND subscribersCount < 20000 
        LIMIT 1");
        if (shardNotFull == null) {
            // If no shard exist create one
            shardToUse = new TweetSubscriberShardedEntity();
        }
        else {
            shardToUse = shardNotFull;
        }
        // Link user and tweet
        shardToUse.setTweet(tweetId);
        shardToUse.getUserKeys().add(subscriberId);
        // Save shard
        datastoreService.put(shardToUse);
    }

    /**
     * Hard to put in a transaction with so many entities updated !
     * See cross entity group docs for more info.
     */
    public void createTweet(UserEntity creator, TweetEntity newTweet) {

        creator.tweetCount++;
        newTweet.tweetMakerName = creator.name;
        newTweet.tweetMakerDisplayName = creator.displayName;
        newTweet.tweetMakerId = creator.id;

        // Duplicate User subscribers to Tweet
        for(UserSubscriberChildEntity userSubscriber : creator.subcribers) {
            // Create a Tweet child entity 
            TweetSubscriberChildEntity tweetSubscriber = new TweetSubscriberChildEntity();
            tweetSubscriber.name = userSubscriber.name;
            // ... (duplicate all data)
            newTweet.add(tweetSubscriber);

            // Create a shard with the previous method !!
            subscribeToTweet(newTweet.id, subscriber.id);
        }           
        // Update the user (tweet count)
        datastoreService.put(creator);
        // Create the new tweet and child entities (duplicated subscribers data)
        datastoreService.put(newTweet);         
    }

}
于 2014-03-11T18:16:02.973 に答える