2013-02-11 2 views
4

Я пытаюсь выяснить миллионную проблему разветвления для GAE с использованием JPA. Если я понимаю вещи правильно, я должен иметь следующие объекты для что-то вроде Twitter (просто пример):Миллион пользователей Fanout в Google App Engine с использованием JPA

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 
} 

Когда твит сделан, объект Твитнуть сохраняется, и TweetIndex сохраняется где tweetMaker является пользователь делает твит, а подписчики копируются из объекта User в TweetIndex. Затем я запрашиваю у подписчиков в TweetIndex, чтобы получать сообщения для конкретного абонента.

  1. Итак, так ли это? Там, где вещи становятся нечеткими для меня, я ожидаю, что подписчики будут сохранены в многозначном свойстве. Поскольку многозначные свойства могут содержать только 5000 записей, я думаю, что TweetIndex следует повторять для каждого 5000 идентификаторов подписчиков.
  2. Что контролирует разбиение многозначных свойств на группы по 5000? Нужно ли мне управлять повторяющимися сейвами в коде?
  3. И как я могу сохранить первоначальный список подписчиков? Мне кажется, что список подписчиков в объекте User также будет ограничен одним и тем же префиксом 5000.

Спасибо за любые ответы/понимание/предложения!

+0

Прежде всего, где вы получите ограничения по размеру собственности мультиплексного стоимости?Тогда, чтобы убедиться, что я правильно использую ваш случай: могут ли люди подписаться на Твит с кем-то, с кем они не подписались? Я имею в виду, может ли абонент находиться в 'TweetIndex', а не' User'? –

+0

Хорошо, я получаю его, размер свойства multi value ограничен, когда они индексируются (выглядит скорее как 20K): http://stackoverflow.com/questions/20200307/what-is-maximum-size-limitation-of-listproperty- for-google-app-engine-datastore –

ответ

1

1) Так ли это так? -> Вид Размер списка свойств для нескольких значений ограничен примерно 20K при индексировании (это ваш случай, так как вы будете запускать запросы по идентификаторам подписчиков) What is maximum size/limitation of ListProperty for Google App Engine datastore? Чтобы суммировать его, ограничения, с которыми вы столкнетесь в таком случае использования, - это : - проиндексирован размер многозначным-свойство (20K) - Entity размер (1 МБ), - которые должны быть в порядке, если вы не храните сгустки там

2) разбивка нужно будет обрабатываться вручную, так как я не Не знаю какой-либо постоянной структуры, которая это делает. Objectify - единственная инфраструктура, которая достаточно прочно связана с хранилищем данных GAE, чтобы иметь такую ​​функцию, но я не использую ее, хотя это IDK.

3) Вам необходимо четко понять ограничения, которые побуждают вас моделировать ваш прецедент в хранилище данных GAE. Мне кажется, что вы все еще сильно подвержены влиянию моделирования реляционных баз данных:

Поскольку вы планируете миллионы пользователей, вы создаете приложение для масштабирования и производительности. Эти «соединения» - именно то, чего вам следует избегать, поэтому вы не используете РСУБД в первую очередь. Дело в том, что: DUPLICATE! денормализовать, чтобы ваши данные соответствовали вашим прецедентам.

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

Теперь основные вопросы: Как вы получить «Все твиты один пользователь подписался на»?

шардинг подписок accross лиц:

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

} 
+0

Примечание. Единственная причина, по которой существует 'TweetSubscriptionShardedEntity', - это количество подписчиков, которое у вас есть. Если у вас было только подписки на 20 тыс. За твит, у вас действительно может быть только свойство подписчиков («Список подписчиков») прямо на ваш твит. –

Смежные вопросы