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);
}
}